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