1 /*
2 * ProFTPD - mod_auth_otp base32 implementation
3 * Copyright (c) 2015-2016 TJ Saunders
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18 *
19 * As a special exemption, TJ Saunders and other respective copyright holders
20 * give permission to link this program with OpenSSL, and distribute the
21 * resulting executable, without including the source code for OpenSSL in the
22 * source distribution.
23 */
24
25 #include "mod_auth_otp.h"
26 #include "base32.h"
27
28 /* Note that this base32 implementation does NOT emit the padding characters,
29 * as an "optimization".
30 *
31 * The base32 encoded values are used for interoperability with e.g. Google
32 * Authenticator, for entering into the app via human interaction. To
33 * reduce the friction, then, the padding characters are omitted.
34 */
35
36 static const unsigned char base32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
37
auth_otp_base32_encode(pool * p,const unsigned char * raw,size_t raw_len,const unsigned char ** encoded,size_t * encoded_len)38 int auth_otp_base32_encode(pool *p, const unsigned char *raw,
39 size_t raw_len, const unsigned char **encoded, size_t *encoded_len) {
40 unsigned char *buf;
41 size_t buflen, bufsz;
42
43 if (p == NULL ||
44 raw == NULL ||
45 encoded == NULL ||
46 encoded_len == NULL) {
47 errno = EINVAL;
48 return -1;
49 }
50
51 bufsz = (raw_len * 8) / 5 + 5;
52 buf = palloc(p, bufsz);
53 buflen = 0;
54
55 if (raw_len > 0) {
56 int d, i;
57 int bits_rem = 0;
58
59 d = raw[0];
60 i = 1;
61 bits_rem = 8;
62
63 while ((buflen < bufsz) &&
64 (bits_rem > 0 || (size_t) i < raw_len)) {
65 int j;
66
67 pr_signals_handle();
68
69 if (bits_rem < 5) {
70 if ((size_t) i < raw_len) {
71 d <<= 8;
72 d |= raw[i++] & 0xff;
73 bits_rem += 8;
74
75 } else {
76 int padding;
77
78 padding = 5 - bits_rem;
79 d <<= padding;
80 bits_rem += padding;
81 }
82 }
83
84 j = 0x1f & (d >> (bits_rem - 5));
85 bits_rem -= 5;
86 buf[buflen++] = base32[j];
87 }
88 }
89
90 if (buflen < bufsz) {
91 buf[buflen] = '\0';
92 }
93
94 *encoded = buf;
95 *encoded_len = buflen;
96 return 0;
97 }
98
auth_otp_base32_decode(pool * p,const unsigned char * encoded,size_t encoded_len,const unsigned char ** raw,size_t * raw_len)99 int auth_otp_base32_decode(pool *p, const unsigned char *encoded,
100 size_t encoded_len, const unsigned char **raw, size_t *raw_len) {
101 register const unsigned char *ptr;
102 int d;
103 unsigned char *buf;
104 size_t buflen, bufsz;
105 int bits_rem;
106
107 if (p == NULL ||
108 encoded == NULL ||
109 raw == NULL ||
110 raw_len == NULL) {
111 errno = EINVAL;
112 return -1;
113 }
114
115 if (encoded_len == 0) {
116 /* We were given an empty string; make sure we allocate at least one
117 * character, for the NUL.
118 */
119 encoded_len = 1;
120 }
121
122 bufsz = encoded_len;
123 buf = palloc(p, bufsz);
124 buflen = 0;
125
126 bits_rem = 0;
127 d = 0;
128
129 for (ptr = encoded; buflen < bufsz && *ptr; ++ptr) {
130 char c;
131
132 pr_signals_handle();
133
134 c = *ptr;
135
136 /* Per RFC 4648 recommendations, skip linefeeds and other similar
137 * characters in decoding.
138 */
139 if (c == ' ' ||
140 c == '\t' ||
141 c == '\r' ||
142 c == '\n' ||
143 c == '-') {
144 continue;
145 }
146
147 d <<= 5;
148
149 if ((c >= 'A' && c <= 'Z') ||
150 (c >= 'a' && c <= 'z')) {
151 c = (c & 0x1f) - 1;
152
153 } else if (c >= '2' && c <= '7') {
154 c -= ('2' - 26);
155
156 } else {
157 /* Invalid character. */
158 errno = EPERM;
159 return -1;
160 }
161
162 d |= c;
163 bits_rem += 5;
164 if (bits_rem >= 8) {
165 buf[buflen++] = (d >> (bits_rem - 8));
166 bits_rem -= 8;
167 }
168 }
169
170 if (buflen < bufsz) {
171 buf[buflen] = '\0';
172 }
173
174 *raw = buf;
175 *raw_len = buflen;
176 return 0;
177 }
178