source: openpam/trunk/modules/pam_oath/oath_key.c @ 628

Last change on this file since 628 was 628, checked in by Dag-Erling Smørgrav, 8 years ago

Implement URI encoding.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 7.6 KB
Line 
1/*-
2 * Copyright (c) 2013 Universitetet i Oslo
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote
14 *    products derived from this software without specific prior written
15 *    permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 *
29 * $Id: oath_key.c 628 2013-02-28 12:12:53Z des $
30 */
31
32#ifdef HAVE_CONFIG_H
33# include "config.h"
34#endif
35
36#include <sys/types.h>
37
38#include <errno.h>
39#include <inttypes.h>
40#include <limits.h>
41#include <stdint.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45
46#include <security/pam_appl.h>
47#include <security/openpam.h>
48
49#include "openpam_asprintf.h"
50#include "openpam_strlcmp.h"
51
52#include "oath.h"
53
54/*
55 * Allocate a struct oath_key with sufficient additional space for the
56 * label and key.
57 */
58struct oath_key *
59oath_key_alloc(size_t extra)
60{
61        struct oath_key *key;
62
63        if ((key = calloc(1, sizeof *key + extra)) == NULL) {
64                openpam_log(PAM_LOG_ERROR, "malloc(): %s", strerror(errno));
65                return (NULL);
66        }
67        key->datalen = extra;
68        /* XXX should try to wire */
69        return (key);
70}
71
72/*
73 * Wipe and free a struct oath_key
74 */
75void
76oath_key_free(struct oath_key *key)
77{
78
79        if (key != NULL) {
80                memset(key, 0, sizeof *key + key->datalen);
81                free(key);
82        }
83}
84
85/*
86 * Allocate a struct oath_key and populate it from a Google Authenticator
87 * otpauth URI
88 */
89struct oath_key *
90oath_key_from_uri(const char *uri)
91{
92        struct oath_key *key;
93        const char *p, *q, *r;
94        uintmax_t n;
95        char *e;
96
97        /*
98         * The URI string contains the label, the base32-encoded key and
99         * some fluff, so the combined length of the label and key can
100         * never exceed the length of the URI string.
101         */
102        if ((key = oath_key_alloc(strlen(uri))) == NULL)
103                return (NULL);
104
105        /* check method */
106        p = uri;
107        if (strlcmp("otpauth://", p, 10) != 0)
108                goto invalid;
109        p += 10;
110
111        /* check mode (hotp = event, totp = time-sync) */
112        if ((q = strchr(p, '/')) == NULL)
113                goto invalid;
114        if (strlcmp("hotp", p, q - p) == 0) {
115                openpam_log(PAM_LOG_DEBUG, "OATH mode: HOTP");
116                key->mode = om_hotp;
117        } else if (strlcmp("totp", p, q - p) == 0) {
118                openpam_log(PAM_LOG_DEBUG, "OATH mode: TOTP");
119                key->mode = om_totp;
120        } else {
121                goto invalid;
122        }
123        p = q + 1;
124
125        /* extract label */
126        if ((q = strchr(p, '?')) == NULL)
127                goto invalid;
128        key->label = (char *)key->data;
129        key->labellen = (q - p) + 1;
130        memcpy(key->label, p, q - p);
131        key->label[q - p] = '\0';
132        p = q + 1;
133
134        /* extract parameters */
135        key->counter = UINTMAX_MAX;
136        while (*p != '\0') {
137                if ((q = strchr(p, '=')) == NULL)
138                        goto invalid;
139                q = q + 1;
140                if ((r = strchr(p, '&')) == NULL)
141                        r = strchr(p, '\0');
142                if (r < q)
143                        /* & before = */
144                        goto invalid;
145                /* p points to key, q points to value, r points to & or NUL */
146                if (strlcmp("secret=", p, q - p) == 0) {
147                        if (key->keylen != 0)
148                                /* dupe */
149                                goto invalid;
150                        /* base32-encoded key - multiple of 40 bits */
151                        if ((r - q) % 8 != 0 ||
152                            base32_declen(r - q) > OATH_MAX_KEYLEN)
153                                goto invalid;
154                        key->key = key->data + key->labellen;
155                        if (base32_dec(q, r - q, key->key, &key->keylen) != 0)
156                                goto invalid;
157                        if (base32_enclen(key->keylen) != (size_t)(r - q))
158                                goto invalid;
159                } else if (strlcmp("algorithm=", p, q - p) == 0) {
160                        if (key->hash != oh_undef)
161                                /* dupe */
162                                goto invalid;
163                        if (strlcmp("SHA1", q, r - q) == 0)
164                                key->hash = oh_sha1;
165                        else if (strlcmp("SHA256", q, r - q) == 0)
166                                key->hash = oh_sha256;
167                        else if (strlcmp("SHA512", q, r - q) == 0)
168                                key->hash = oh_sha512;
169                        else if (strlcmp("MD5", q, r - q) == 0)
170                                key->hash = oh_md5;
171                        else
172                                goto invalid;
173                } else if (strlcmp("digits=", p, q - p) == 0) {
174                        if (key->digits != 0)
175                                /* dupe */
176                                goto invalid;
177                        /* only 6 or 8 */
178                        if (r - q != 1 || (*q != '6' && *q != '8'))
179                                goto invalid;
180                        key->digits = *q - '0';
181                } else if (strlcmp("counter=", p, q - p) == 0) {
182                        if (key->counter != UINTMAX_MAX)
183                                /* dupe */
184                                goto invalid;
185                        n = strtoumax(q, &e, 10);
186                        if (e != r || n >= UINTMAX_MAX)
187                                goto invalid;
188                        key->counter = (uint64_t)n;
189                } else if (strlcmp("period=", p, q - p) == 0) {
190                        if (key->timestep != 0)
191                                /* dupe */
192                                goto invalid;
193                        n = strtoumax(q, &e, 10);
194                        if (e != r || n > OATH_MAX_TIMESTEP)
195                                goto invalid;
196                        key->timestep = n;
197                } else {
198                        goto invalid;
199                }
200                /* final parameter? */
201                if (*r == '\0')
202                        break;
203                /* skip & and continue */
204                p = r + 1;
205        }
206
207        /* sanity checks and default values */
208        if (key->mode == om_hotp) {
209                if (key->timestep != 0)
210                        goto invalid;
211                if (key->counter == UINTMAX_MAX)
212                        key->counter = 0;
213        } else if (key->mode == om_totp) {
214                if (key->counter != UINTMAX_MAX)
215                        goto invalid;
216                if (key->timestep == 0)
217                        key->timestep = OATH_DEF_TIMESTEP;
218        } else {
219                /* unreachable */
220                oath_key_free(key);
221                return (NULL);
222        }
223        if (key->hash == oh_undef)
224                key->hash = oh_sha1;
225        if (key->digits == 0)
226                key->digits = 6;
227        if (key->keylen == 0)
228                goto invalid;
229
230invalid:
231        openpam_log(PAM_LOG_NOTICE, "invalid OATH URI: %s", uri);
232        oath_key_free(key);
233        return (NULL);
234}
235
236struct oath_key *
237oath_key_from_file(const char *filename)
238{
239        struct oath_key *key;
240        FILE *f;
241        char *line;
242        size_t len;
243
244        if ((f = fopen(filename, "r")) == NULL)
245                return (NULL);
246        /* get first non-empty non-comment line */
247        line = openpam_readline(f, NULL, &len);
248        if (strlcmp("otpauth://", line, len) == 0) {
249                key = oath_key_from_uri(line);
250        } else {
251                openpam_log(PAM_LOG_ERROR,
252                    "unrecognized key file format: %s", filename);
253                key = NULL;
254        }
255        fclose(f);
256        return (key);
257}
258
259char *
260oath_key_to_uri(const struct oath_key *key)
261{
262        const char *hash;
263        char *tmp, *uri;
264        size_t kslen, urilen;
265
266        switch (key->hash) {
267        case oh_sha1:
268                hash = "SHA1";
269                break;
270        case oh_sha256:
271                hash = "SHA256";
272                break;
273        case oh_sha512:
274                hash = "SHA512";
275                break;
276        case oh_md5:
277                hash = "MD5";
278                break;
279        default:
280                return (NULL);
281        }
282
283        if (key->mode == om_hotp) {
284                urilen = asprintf(&uri, "otpauth://"
285                    "%s/%s?algorithm=%s&digits=%d&counter=%ju&secret=",
286                    "hotp", key->label, hash, key->digits,
287                    (uintmax_t)key->counter);
288        } else if (key->mode == om_totp) {
289                urilen = asprintf(&uri, "otpauth://"
290                    "%s/%s?algorithm=%s&digits=%d&period=%u&secret=",
291                    "totp", key->label, hash, key->digits, key->timestep);
292        } else {
293                /* unreachable */
294                return (NULL);
295        }
296
297        /* compute length of base32-encoded key and append it */
298        kslen = base32_enclen(key->keylen);
299        if ((tmp = realloc(uri, urilen + kslen + 1)) == NULL) {
300                free(uri);
301                return (NULL);
302        }
303        uri = tmp;
304        if (base32_enc(key->key, key->keylen, uri + urilen, &kslen) != 0) {
305                free(uri);
306                return (NULL);
307        }
308
309        return (uri);
310}
Note: See TracBrowser for help on using the repository browser.