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

Last change on this file since 643 was 643, checked in by Dag-Erling Smørgrav, 8 years ago
  • Add a provisional API for computing the current HOTP or TOTP code.
  • Add a provisional API for matching a user response.
  • Add a provisional API for generating a dummy key. When one of the matching functions recognizes a dummy key, it will go through the motions but never report a match.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 8.0 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 643 2013-03-05 15:24:00Z 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                key->mode = om_hotp;
116        } else if (strlcmp("totp", p, q - p) == 0) {
117                key->mode = om_totp;
118        } else {
119                goto invalid;
120        }
121        p = q + 1;
122
123        /* extract label */
124        if ((q = strchr(p, '?')) == NULL)
125                goto invalid;
126        key->label = (char *)key->data;
127        key->labellen = (q - p) + 1;
128        memcpy(key->label, p, q - p);
129        key->label[q - p] = '\0';
130        p = q + 1;
131
132        /* extract parameters */
133        key->counter = UINT64_MAX;
134        while (*p != '\0') {
135                if ((q = strchr(p, '=')) == NULL)
136                        goto invalid;
137                q = q + 1;
138                if ((r = strchr(p, '&')) == NULL)
139                        r = strchr(p, '\0');
140                if (r < q)
141                        /* & before = */
142                        goto invalid;
143                /* p points to key, q points to value, r points to & or NUL */
144                if (strlcmp("secret=", p, q - p) == 0) {
145                        if (key->keylen != 0)
146                                /* dupe */
147                                goto invalid;
148                        /* base32-encoded key - multiple of 40 bits */
149                        if ((r - q) % 8 != 0 ||
150                            base32_declen(r - q) > OATH_MAX_KEYLEN)
151                                goto invalid;
152                        key->key = key->data + key->labellen;
153                        key->keylen = key->datalen - key->labellen;
154                        if (base32_dec(q, r - q, key->key, &key->keylen) != 0)
155                                goto invalid;
156                        if (base32_enclen(key->keylen) != (size_t)(r - q))
157                                goto invalid;
158                } else if (strlcmp("algorithm=", p, q - p) == 0) {
159                        if (key->hash != oh_undef)
160                                /* dupe */
161                                goto invalid;
162                        if (strlcmp("SHA1", q, r - q) == 0)
163                                key->hash = oh_sha1;
164                        else if (strlcmp("SHA256", q, r - q) == 0)
165                                key->hash = oh_sha256;
166                        else if (strlcmp("SHA512", q, r - q) == 0)
167                                key->hash = oh_sha512;
168                        else if (strlcmp("MD5", q, r - q) == 0)
169                                key->hash = oh_md5;
170                        else
171                                goto invalid;
172                } else if (strlcmp("digits=", p, q - p) == 0) {
173                        if (key->digits != 0)
174                                /* dupe */
175                                goto invalid;
176                        /* only 6 or 8 */
177                        if (r - q != 1 || (*q != '6' && *q != '8'))
178                                goto invalid;
179                        key->digits = *q - '0';
180                } else if (strlcmp("counter=", p, q - p) == 0) {
181                        if (key->counter != UINT64_MAX)
182                                /* dupe */
183                                goto invalid;
184                        n = strtoumax(q, &e, 10);
185                        if (e != r || n >= UINT64_MAX)
186                                goto invalid;
187                        key->counter = (uint64_t)n;
188                } else if (strlcmp("period=", p, q - p) == 0) {
189                        if (key->timestep != 0)
190                                /* dupe */
191                                goto invalid;
192                        n = strtoumax(q, &e, 10);
193                        if (e != r || n > OATH_MAX_TIMESTEP)
194                                goto invalid;
195                        key->timestep = n;
196                } else {
197                        goto invalid;
198                }
199                /* final parameter? */
200                if (*r == '\0')
201                        break;
202                /* skip & and continue */
203                p = r + 1;
204        }
205
206        /* sanity checks and default values */
207        if (key->mode == om_hotp) {
208                if (key->timestep != 0)
209                        goto invalid;
210                if (key->counter == UINTMAX_MAX)
211                        key->counter = 0;
212        } else if (key->mode == om_totp) {
213                if (key->counter != UINTMAX_MAX)
214                        goto invalid;
215                if (key->timestep == 0)
216                        key->timestep = OATH_DEF_TIMESTEP;
217        } else {
218                /* unreachable */
219                oath_key_free(key);
220                return (NULL);
221        }
222        if (key->hash == oh_undef)
223                key->hash = oh_sha1;
224        if (key->digits == 0)
225                key->digits = 6;
226        if (key->keylen == 0)
227                goto invalid;
228        return (key);
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) + 1;
299        if ((tmp = realloc(uri, urilen + kslen)) == 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}
311
312struct oath_key *
313oath_dummy_key(enum oath_mode mode, enum oath_hash hash, unsigned int digits)
314{
315        struct oath_key *key;
316
317        if ((key = oath_key_alloc(DUMMY_LABELLEN + DUMMY_KEYLEN)) == NULL)
318                return (NULL);
319        key->mode = mode;
320        key->digits = digits;
321        key->counter = 0;
322        key->timestep = 30;
323        key->hash = hash;
324        key->label = (char *)key->data;
325        memcpy(key->label, DUMMY_LABEL, DUMMY_LABELLEN);
326        key->key = key->data + DUMMY_LABELLEN;
327        key->keylen = DUMMY_KEYLEN;
328        return (key);
329}
Note: See TracBrowser for help on using the repository browser.