Annotation of src/usr.bin/ssh/ssh-sk.c, Revision 1.14
1.14 ! djm 1: /* $OpenBSD: ssh-sk.c,v 1.13 2019/11/16 22:42:30 djm Exp $ */
1.1 djm 2: /*
3: * Copyright (c) 2019 Google LLC
4: *
5: * Permission to use, copy, modify, and distribute this software for any
6: * purpose with or without fee is hereby granted, provided that the above
7: * copyright notice and this permission notice appear in all copies.
8: *
9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16: */
17:
18: /* #define DEBUG_SK 1 */
19:
20: #include <dlfcn.h>
21: #include <stddef.h>
22: #include <stdint.h>
23: #include <string.h>
24: #include <stdio.h>
25:
26: #include <openssl/objects.h>
27: #include <openssl/ec.h>
28:
29: #include "log.h"
30: #include "misc.h"
31: #include "sshbuf.h"
32: #include "sshkey.h"
33: #include "ssherr.h"
34: #include "digest.h"
35:
36: #include "ssh-sk.h"
37: #include "sk-api.h"
1.6 markus 38: #include "crypto_api.h"
1.1 djm 39:
40: struct sshsk_provider {
41: char *path;
42: void *dlhandle;
43:
44: /* Return the version of the middleware API */
45: uint32_t (*sk_api_version)(void);
46:
47: /* Enroll a U2F key (private key generation) */
1.7 markus 48: int (*sk_enroll)(int alg, const uint8_t *challenge,
49: size_t challenge_len, const char *application, uint8_t flags,
1.1 djm 50: struct sk_enroll_response **enroll_response);
51:
52: /* Sign a challenge */
1.7 markus 53: int (*sk_sign)(int alg, const uint8_t *message, size_t message_len,
1.1 djm 54: const char *application,
55: const uint8_t *key_handle, size_t key_handle_len,
56: uint8_t flags, struct sk_sign_response **sign_response);
57: };
58:
1.12 djm 59: /* Built-in version */
60: int ssh_sk_enroll(int alg, const uint8_t *challenge,
61: size_t challenge_len, const char *application, uint8_t flags,
62: struct sk_enroll_response **enroll_response);
63: int ssh_sk_sign(int alg, const uint8_t *message, size_t message_len,
64: const char *application,
65: const uint8_t *key_handle, size_t key_handle_len,
66: uint8_t flags, struct sk_sign_response **sign_response);
67:
1.1 djm 68: static void
69: sshsk_free(struct sshsk_provider *p)
70: {
71: if (p == NULL)
72: return;
73: free(p->path);
74: if (p->dlhandle != NULL)
75: dlclose(p->dlhandle);
76: free(p);
77: }
78:
79: static struct sshsk_provider *
80: sshsk_open(const char *path)
81: {
82: struct sshsk_provider *ret = NULL;
83: uint32_t version;
84:
85: if ((ret = calloc(1, sizeof(*ret))) == NULL) {
86: error("%s: calloc failed", __func__);
87: return NULL;
88: }
89: if ((ret->path = strdup(path)) == NULL) {
90: error("%s: strdup failed", __func__);
91: goto fail;
1.12 djm 92: }
93: /* Skip the rest if we're using the linked in middleware */
94: if (strcasecmp(ret->path, "internal") == 0) {
95: ret->sk_enroll = ssh_sk_enroll;
96: ret->sk_sign = ssh_sk_sign;
97: return ret;
1.1 djm 98: }
99: if ((ret->dlhandle = dlopen(path, RTLD_NOW)) == NULL) {
100: error("Security key provider %s dlopen failed: %s",
101: path, dlerror());
102: goto fail;
103: }
104: if ((ret->sk_api_version = dlsym(ret->dlhandle,
105: "sk_api_version")) == NULL) {
106: error("Security key provider %s dlsym(sk_api_version) "
107: "failed: %s", path, dlerror());
108: goto fail;
109: }
110: version = ret->sk_api_version();
111: debug("%s: provider %s implements version 0x%08lx", __func__,
112: ret->path, (u_long)version);
113: if ((version & SSH_SK_VERSION_MAJOR_MASK) != SSH_SK_VERSION_MAJOR) {
114: error("Security key provider %s implements unsupported version "
115: "0x%08lx (supported: 0x%08lx)", path, (u_long)version,
116: (u_long)SSH_SK_VERSION_MAJOR);
117: goto fail;
118: }
119: if ((ret->sk_enroll = dlsym(ret->dlhandle, "sk_enroll")) == NULL) {
120: error("Security key provider %s dlsym(sk_enroll) "
121: "failed: %s", path, dlerror());
122: goto fail;
123: }
124: if ((ret->sk_sign = dlsym(ret->dlhandle, "sk_sign")) == NULL) {
125: error("Security key provider %s dlsym(sk_sign) failed: %s",
126: path, dlerror());
127: goto fail;
128: }
129: /* success */
130: return ret;
131: fail:
132: sshsk_free(ret);
133: return NULL;
134: }
135:
136: static void
137: sshsk_free_enroll_response(struct sk_enroll_response *r)
138: {
139: if (r == NULL)
140: return;
141: freezero(r->key_handle, r->key_handle_len);
142: freezero(r->public_key, r->public_key_len);
143: freezero(r->signature, r->signature_len);
144: freezero(r->attestation_cert, r->attestation_cert_len);
145: freezero(r, sizeof(*r));
146: };
147:
148: static void
149: sshsk_free_sign_response(struct sk_sign_response *r)
150: {
151: if (r == NULL)
152: return;
153: freezero(r->sig_r, r->sig_r_len);
154: freezero(r->sig_s, r->sig_s_len);
155: freezero(r, sizeof(*r));
156: };
157:
1.2 markus 158: /* Assemble key from response */
159: static int
160: sshsk_ecdsa_assemble(struct sk_enroll_response *resp, struct sshkey **keyp)
161: {
162: struct sshkey *key = NULL;
163: struct sshbuf *b = NULL;
164: EC_POINT *q = NULL;
165: int r;
166:
167: *keyp = NULL;
168: if ((key = sshkey_new(KEY_ECDSA_SK)) == NULL) {
169: error("%s: sshkey_new failed", __func__);
170: r = SSH_ERR_ALLOC_FAIL;
171: goto out;
172: }
173: key->ecdsa_nid = NID_X9_62_prime256v1;
174: if ((key->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid)) == NULL ||
175: (q = EC_POINT_new(EC_KEY_get0_group(key->ecdsa))) == NULL ||
176: (b = sshbuf_new()) == NULL) {
177: error("%s: allocation failed", __func__);
178: r = SSH_ERR_ALLOC_FAIL;
179: goto out;
180: }
181: if ((r = sshbuf_put_string(b,
182: resp->public_key, resp->public_key_len)) != 0) {
183: error("%s: buffer error: %s", __func__, ssh_err(r));
184: goto out;
185: }
186: if ((r = sshbuf_get_ec(b, q, EC_KEY_get0_group(key->ecdsa))) != 0) {
187: error("%s: parse key: %s", __func__, ssh_err(r));
188: r = SSH_ERR_INVALID_FORMAT;
189: goto out;
190: }
191: if (sshkey_ec_validate_public(EC_KEY_get0_group(key->ecdsa), q) != 0) {
192: error("Security key returned invalid ECDSA key");
193: r = SSH_ERR_KEY_INVALID_EC_VALUE;
194: goto out;
195: }
196: if (EC_KEY_set_public_key(key->ecdsa, q) != 1) {
197: /* XXX assume it is a allocation error */
198: error("%s: allocation failed", __func__);
199: r = SSH_ERR_ALLOC_FAIL;
200: goto out;
201: }
202: /* success */
203: *keyp = key;
204: key = NULL; /* transferred */
205: r = 0;
206: out:
207: EC_POINT_free(q);
208: sshkey_free(key);
209: sshbuf_free(b);
210: return r;
211: }
212:
1.6 markus 213: static int
214: sshsk_ed25519_assemble(struct sk_enroll_response *resp, struct sshkey **keyp)
215: {
216: struct sshkey *key = NULL;
217: int r;
218:
219: *keyp = NULL;
220: if (resp->public_key_len != ED25519_PK_SZ) {
221: error("%s: invalid size: %zu", __func__, resp->public_key_len);
222: r = SSH_ERR_INVALID_FORMAT;
223: goto out;
224: }
225: if ((key = sshkey_new(KEY_ED25519_SK)) == NULL) {
226: error("%s: sshkey_new failed", __func__);
227: r = SSH_ERR_ALLOC_FAIL;
228: goto out;
229: }
230: if ((key->ed25519_pk = malloc(ED25519_PK_SZ)) == NULL) {
231: error("%s: malloc failed", __func__);
232: r = SSH_ERR_ALLOC_FAIL;
233: goto out;
234: }
235: memcpy(key->ed25519_pk, resp->public_key, ED25519_PK_SZ);
236: /* success */
237: *keyp = key;
238: key = NULL; /* transferred */
239: r = 0;
240: out:
241: sshkey_free(key);
242: return r;
243: }
244:
1.1 djm 245: int
1.6 markus 246: sshsk_enroll(int type, const char *provider_path, const char *application,
1.1 djm 247: uint8_t flags, struct sshbuf *challenge_buf, struct sshkey **keyp,
248: struct sshbuf *attest)
249: {
250: struct sshsk_provider *skp = NULL;
251: struct sshkey *key = NULL;
252: u_char randchall[32];
253: const u_char *challenge;
254: size_t challenge_len;
255: struct sk_enroll_response *resp = NULL;
256: int r = SSH_ERR_INTERNAL_ERROR;
1.7 markus 257: int alg;
1.1 djm 258:
1.13 djm 259: debug("%s: provider \"%s\", application \"%s\", flags 0x%02x, "
260: "challenge len %zu", __func__, provider_path, application,
261: flags, challenge_buf == NULL ? 0 : sshbuf_len(challenge_buf));
262:
1.1 djm 263: *keyp = NULL;
264: if (attest)
265: sshbuf_reset(attest);
1.6 markus 266: switch (type) {
267: case KEY_ECDSA_SK:
1.7 markus 268: alg = SSH_SK_ECDSA;
269: break;
1.6 markus 270: case KEY_ED25519_SK:
1.7 markus 271: alg = SSH_SK_ED25519;
1.6 markus 272: break;
273: default:
274: error("%s: unsupported key type", __func__);
275: r = SSH_ERR_INVALID_ARGUMENT;
276: goto out;
277: }
1.1 djm 278: if (provider_path == NULL) {
279: error("%s: missing provider", __func__);
280: r = SSH_ERR_INVALID_ARGUMENT;
281: goto out;
282: }
283: if (application == NULL || *application == '\0') {
284: error("%s: missing application", __func__);
285: r = SSH_ERR_INVALID_ARGUMENT;
286: goto out;
287: }
288: if (challenge_buf == NULL) {
289: debug("%s: using random challenge", __func__);
290: arc4random_buf(randchall, sizeof(randchall));
291: challenge = randchall;
292: challenge_len = sizeof(randchall);
293: } else if (sshbuf_len(challenge_buf) == 0) {
294: error("Missing enrollment challenge");
295: r = SSH_ERR_INVALID_ARGUMENT;
296: goto out;
297: } else {
298: challenge = sshbuf_ptr(challenge_buf);
299: challenge_len = sshbuf_len(challenge_buf);
300: debug3("%s: using explicit challenge len=%zd",
301: __func__, challenge_len);
302: }
303: if ((skp = sshsk_open(provider_path)) == NULL) {
304: r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
305: goto out;
306: }
307: /* XXX validate flags? */
308: /* enroll key */
1.7 markus 309: if ((r = skp->sk_enroll(alg, challenge, challenge_len, application,
1.1 djm 310: flags, &resp)) != 0) {
311: error("Security key provider %s returned failure %d",
312: provider_path, r);
313: r = SSH_ERR_INVALID_FORMAT; /* XXX error codes in API? */
314: goto out;
315: }
316: /* Check response validity */
317: if (resp->public_key == NULL || resp->key_handle == NULL ||
1.10 djm 318: resp->signature == NULL ||
319: (resp->attestation_cert == NULL && resp->attestation_cert_len != 0)) {
1.1 djm 320: error("%s: sk_enroll response invalid", __func__);
321: r = SSH_ERR_INVALID_FORMAT;
322: goto out;
323: }
1.6 markus 324: switch (type) {
325: case KEY_ECDSA_SK:
326: if ((r = sshsk_ecdsa_assemble(resp, &key)) != 0)
327: goto out;
328: break;
329: case KEY_ED25519_SK:
330: if ((r = sshsk_ed25519_assemble(resp, &key)) != 0)
331: goto out;
332: break;
333: }
1.1 djm 334: key->sk_flags = flags;
1.2 markus 335: if ((key->sk_key_handle = sshbuf_new()) == NULL ||
336: (key->sk_reserved = sshbuf_new()) == NULL) {
1.1 djm 337: error("%s: allocation failed", __func__);
338: r = SSH_ERR_ALLOC_FAIL;
339: goto out;
340: }
341: if ((key->sk_application = strdup(application)) == NULL) {
342: error("%s: strdup application failed", __func__);
343: r = SSH_ERR_ALLOC_FAIL;
344: goto out;
345: }
346: if ((r = sshbuf_put(key->sk_key_handle, resp->key_handle,
347: resp->key_handle_len)) != 0) {
348: error("%s: buffer error: %s", __func__, ssh_err(r));
349: goto out;
350: }
351: /* Optionally fill in the attestation information */
352: if (attest != NULL) {
353: if ((r = sshbuf_put_cstring(attest, "sk-attest-v00")) != 0 ||
354: (r = sshbuf_put_u32(attest, 1)) != 0 || /* XXX U2F ver */
355: (r = sshbuf_put_string(attest,
356: resp->attestation_cert, resp->attestation_cert_len)) != 0 ||
357: (r = sshbuf_put_string(attest,
358: resp->signature, resp->signature_len)) != 0 ||
359: (r = sshbuf_put_u32(attest, flags)) != 0 || /* XXX right? */
360: (r = sshbuf_put_string(attest, NULL, 0)) != 0) {
361: error("%s: buffer error: %s", __func__, ssh_err(r));
362: goto out;
363: }
364: }
365: /* success */
366: *keyp = key;
367: key = NULL; /* transferred */
368: r = 0;
369: out:
370: sshsk_free(skp);
371: sshkey_free(key);
372: sshsk_free_enroll_response(resp);
373: explicit_bzero(randchall, sizeof(randchall));
374: return r;
375: }
376:
1.3 markus 377: static int
1.9 markus 378: sshsk_ecdsa_sig(struct sk_sign_response *resp, struct sshbuf *sig)
1.3 markus 379: {
380: struct sshbuf *inner_sig = NULL;
381: int r = SSH_ERR_INTERNAL_ERROR;
382:
1.8 markus 383: /* Check response validity */
1.11 markus 384: if (resp->sig_r == NULL || resp->sig_s == NULL) {
1.8 markus 385: error("%s: sk_sign response invalid", __func__);
386: r = SSH_ERR_INVALID_FORMAT;
387: goto out;
388: }
1.3 markus 389: if ((inner_sig = sshbuf_new()) == NULL) {
390: r = SSH_ERR_ALLOC_FAIL;
391: goto out;
392: }
1.9 markus 393: /* Prepare and append inner signature object */
1.3 markus 394: if ((r = sshbuf_put_bignum2_bytes(inner_sig,
395: resp->sig_r, resp->sig_r_len)) != 0 ||
396: (r = sshbuf_put_bignum2_bytes(inner_sig,
397: resp->sig_s, resp->sig_s_len)) != 0 ||
398: (r = sshbuf_put_u8(inner_sig, resp->flags)) != 0 ||
399: (r = sshbuf_put_u32(inner_sig, resp->counter)) != 0) {
400: debug("%s: buffer error: %s", __func__, ssh_err(r));
401: goto out;
402: }
1.9 markus 403: if ((r = sshbuf_put_stringb(sig, inner_sig)) != 0) {
404: debug("%s: buffer error: %s", __func__, ssh_err(r));
405: goto out;
406: }
1.3 markus 407: #ifdef DEBUG_SK
408: fprintf(stderr, "%s: sig_r:\n", __func__);
409: sshbuf_dump_data(resp->sig_r, resp->sig_r_len, stderr);
410: fprintf(stderr, "%s: sig_s:\n", __func__);
411: sshbuf_dump_data(resp->sig_s, resp->sig_s_len, stderr);
1.9 markus 412: fprintf(stderr, "%s: inner:\n", __func__);
413: sshbuf_dump(inner_sig, stderr);
1.5 markus 414: #endif
415: r = 0;
1.9 markus 416: out:
1.5 markus 417: sshbuf_free(inner_sig);
418: return r;
419: }
420:
421: static int
1.9 markus 422: sshsk_ed25519_sig(struct sk_sign_response *resp, struct sshbuf *sig)
1.5 markus 423: {
424: int r = SSH_ERR_INTERNAL_ERROR;
425:
1.8 markus 426: /* Check response validity */
427: if (resp->sig_r == NULL) {
428: error("%s: sk_sign response invalid", __func__);
429: r = SSH_ERR_INVALID_FORMAT;
430: goto out;
431: }
1.9 markus 432: if ((r = sshbuf_put_string(sig,
1.5 markus 433: resp->sig_r, resp->sig_r_len)) != 0 ||
1.9 markus 434: (r = sshbuf_put_u8(sig, resp->flags)) != 0 ||
435: (r = sshbuf_put_u32(sig, resp->counter)) != 0) {
1.5 markus 436: debug("%s: buffer error: %s", __func__, ssh_err(r));
437: goto out;
438: }
439: #ifdef DEBUG_SK
440: fprintf(stderr, "%s: sig_r:\n", __func__);
441: sshbuf_dump_data(resp->sig_r, resp->sig_r_len, stderr);
1.3 markus 442: #endif
443: r = 0;
1.9 markus 444: out:
445: return 0;
1.3 markus 446: }
447:
1.1 djm 448: int
1.4 markus 449: sshsk_sign(const char *provider_path, const struct sshkey *key,
1.1 djm 450: u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,
451: u_int compat)
452: {
453: struct sshsk_provider *skp = NULL;
454: int r = SSH_ERR_INTERNAL_ERROR;
1.7 markus 455: int type, alg;
1.1 djm 456: struct sk_sign_response *resp = NULL;
457: struct sshbuf *inner_sig = NULL, *sig = NULL;
458: uint8_t message[32];
1.13 djm 459:
1.14 ! djm 460: debug("%s: provider \"%s\", key %s, flags 0x%02x", __func__,
1.13 djm 461: provider_path, sshkey_type(key), key->sk_flags);
1.1 djm 462:
463: if (sigp != NULL)
464: *sigp = NULL;
465: if (lenp != NULL)
466: *lenp = 0;
1.5 markus 467: type = sshkey_type_plain(key->type);
468: switch (type) {
469: case KEY_ECDSA_SK:
1.7 markus 470: alg = SSH_SK_ECDSA;
471: break;
1.5 markus 472: case KEY_ED25519_SK:
1.7 markus 473: alg = SSH_SK_ED25519;
1.5 markus 474: break;
475: default:
476: return SSH_ERR_INVALID_ARGUMENT;
477: }
1.1 djm 478: if (provider_path == NULL ||
479: key->sk_key_handle == NULL ||
480: key->sk_application == NULL || *key->sk_application == '\0') {
481: r = SSH_ERR_INVALID_ARGUMENT;
482: goto out;
483: }
484: if ((skp = sshsk_open(provider_path)) == NULL) {
485: r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
486: goto out;
487: }
488:
489: /* hash data to be signed before it goes to the security key */
490: if ((r = ssh_digest_memory(SSH_DIGEST_SHA256, data, datalen,
491: message, sizeof(message))) != 0) {
492: error("%s: hash application failed: %s", __func__, ssh_err(r));
493: r = SSH_ERR_INTERNAL_ERROR;
494: goto out;
495: }
1.7 markus 496: if ((r = skp->sk_sign(alg, message, sizeof(message),
1.1 djm 497: key->sk_application,
498: sshbuf_ptr(key->sk_key_handle), sshbuf_len(key->sk_key_handle),
499: key->sk_flags, &resp)) != 0) {
500: debug("%s: sk_sign failed with code %d", __func__, r);
501: goto out;
502: }
1.9 markus 503: /* Assemble signature */
504: if ((sig = sshbuf_new()) == NULL) {
505: r = SSH_ERR_ALLOC_FAIL;
506: goto out;
507: }
508: if ((r = sshbuf_put_cstring(sig, sshkey_ssh_name_plain(key))) != 0) {
509: debug("%s: buffer error (outer): %s", __func__, ssh_err(r));
510: goto out;
511: }
1.5 markus 512: switch (type) {
513: case KEY_ECDSA_SK:
1.9 markus 514: if ((r = sshsk_ecdsa_sig(resp, sig)) != 0)
1.5 markus 515: goto out;
516: break;
517: case KEY_ED25519_SK:
1.9 markus 518: if ((r = sshsk_ed25519_sig(resp, sig)) != 0)
1.5 markus 519: goto out;
520: break;
521: }
1.1 djm 522: #ifdef DEBUG_SK
1.5 markus 523: fprintf(stderr, "%s: sig_flags = 0x%02x, sig_counter = %u\n",
524: __func__, resp->flags, resp->counter);
1.1 djm 525: fprintf(stderr, "%s: hashed message:\n", __func__);
526: sshbuf_dump_data(message, sizeof(message), stderr);
527: fprintf(stderr, "%s: sigbuf:\n", __func__);
528: sshbuf_dump(sig, stderr);
529: #endif
530: if (sigp != NULL) {
531: if ((*sigp = malloc(sshbuf_len(sig))) == NULL) {
532: r = SSH_ERR_ALLOC_FAIL;
533: goto out;
534: }
535: memcpy(*sigp, sshbuf_ptr(sig), sshbuf_len(sig));
536: }
537: if (lenp != NULL)
538: *lenp = sshbuf_len(sig);
539: /* success */
540: r = 0;
541: out:
542: explicit_bzero(message, sizeof(message));
543: sshsk_free(skp);
544: sshsk_free_sign_response(resp);
545: sshbuf_free(sig);
546: sshbuf_free(inner_sig);
547: return r;
548: }