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