Annotation of src/usr.bin/ssh/ssh-sk.c, Revision 1.12
1.12 ! djm 1: /* $OpenBSD: ssh-sk.c,v 1.11 2019/11/13 20:25:45 markus 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:
259: *keyp = NULL;
260: if (attest)
261: sshbuf_reset(attest);
1.6 markus 262: switch (type) {
263: case KEY_ECDSA_SK:
1.7 markus 264: alg = SSH_SK_ECDSA;
265: break;
1.6 markus 266: case KEY_ED25519_SK:
1.7 markus 267: alg = SSH_SK_ED25519;
1.6 markus 268: break;
269: default:
270: error("%s: unsupported key type", __func__);
271: r = SSH_ERR_INVALID_ARGUMENT;
272: goto out;
273: }
1.1 djm 274: if (provider_path == NULL) {
275: error("%s: missing provider", __func__);
276: r = SSH_ERR_INVALID_ARGUMENT;
277: goto out;
278: }
279: if (application == NULL || *application == '\0') {
280: error("%s: missing application", __func__);
281: r = SSH_ERR_INVALID_ARGUMENT;
282: goto out;
283: }
284: if (challenge_buf == NULL) {
285: debug("%s: using random challenge", __func__);
286: arc4random_buf(randchall, sizeof(randchall));
287: challenge = randchall;
288: challenge_len = sizeof(randchall);
289: } else if (sshbuf_len(challenge_buf) == 0) {
290: error("Missing enrollment challenge");
291: r = SSH_ERR_INVALID_ARGUMENT;
292: goto out;
293: } else {
294: challenge = sshbuf_ptr(challenge_buf);
295: challenge_len = sshbuf_len(challenge_buf);
296: debug3("%s: using explicit challenge len=%zd",
297: __func__, challenge_len);
298: }
299: if ((skp = sshsk_open(provider_path)) == NULL) {
300: r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
301: goto out;
302: }
303: /* XXX validate flags? */
304: /* enroll key */
1.7 markus 305: if ((r = skp->sk_enroll(alg, challenge, challenge_len, application,
1.1 djm 306: flags, &resp)) != 0) {
307: error("Security key provider %s returned failure %d",
308: provider_path, r);
309: r = SSH_ERR_INVALID_FORMAT; /* XXX error codes in API? */
310: goto out;
311: }
312: /* Check response validity */
313: if (resp->public_key == NULL || resp->key_handle == NULL ||
1.10 djm 314: resp->signature == NULL ||
315: (resp->attestation_cert == NULL && resp->attestation_cert_len != 0)) {
1.1 djm 316: error("%s: sk_enroll response invalid", __func__);
317: r = SSH_ERR_INVALID_FORMAT;
318: goto out;
319: }
1.6 markus 320: switch (type) {
321: case KEY_ECDSA_SK:
322: if ((r = sshsk_ecdsa_assemble(resp, &key)) != 0)
323: goto out;
324: break;
325: case KEY_ED25519_SK:
326: if ((r = sshsk_ed25519_assemble(resp, &key)) != 0)
327: goto out;
328: break;
329: }
1.1 djm 330: key->sk_flags = flags;
1.2 markus 331: if ((key->sk_key_handle = sshbuf_new()) == NULL ||
332: (key->sk_reserved = sshbuf_new()) == NULL) {
1.1 djm 333: error("%s: allocation failed", __func__);
334: r = SSH_ERR_ALLOC_FAIL;
335: goto out;
336: }
337: if ((key->sk_application = strdup(application)) == NULL) {
338: error("%s: strdup application failed", __func__);
339: r = SSH_ERR_ALLOC_FAIL;
340: goto out;
341: }
342: if ((r = sshbuf_put(key->sk_key_handle, resp->key_handle,
343: resp->key_handle_len)) != 0) {
344: error("%s: buffer error: %s", __func__, ssh_err(r));
345: goto out;
346: }
347: /* Optionally fill in the attestation information */
348: if (attest != NULL) {
349: if ((r = sshbuf_put_cstring(attest, "sk-attest-v00")) != 0 ||
350: (r = sshbuf_put_u32(attest, 1)) != 0 || /* XXX U2F ver */
351: (r = sshbuf_put_string(attest,
352: resp->attestation_cert, resp->attestation_cert_len)) != 0 ||
353: (r = sshbuf_put_string(attest,
354: resp->signature, resp->signature_len)) != 0 ||
355: (r = sshbuf_put_u32(attest, flags)) != 0 || /* XXX right? */
356: (r = sshbuf_put_string(attest, NULL, 0)) != 0) {
357: error("%s: buffer error: %s", __func__, ssh_err(r));
358: goto out;
359: }
360: }
361: /* success */
362: *keyp = key;
363: key = NULL; /* transferred */
364: r = 0;
365: out:
366: sshsk_free(skp);
367: sshkey_free(key);
368: sshsk_free_enroll_response(resp);
369: explicit_bzero(randchall, sizeof(randchall));
370: return r;
371: }
372:
1.3 markus 373: static int
1.9 markus 374: sshsk_ecdsa_sig(struct sk_sign_response *resp, struct sshbuf *sig)
1.3 markus 375: {
376: struct sshbuf *inner_sig = NULL;
377: int r = SSH_ERR_INTERNAL_ERROR;
378:
1.8 markus 379: /* Check response validity */
1.11 markus 380: if (resp->sig_r == NULL || resp->sig_s == NULL) {
1.8 markus 381: error("%s: sk_sign response invalid", __func__);
382: r = SSH_ERR_INVALID_FORMAT;
383: goto out;
384: }
1.3 markus 385: if ((inner_sig = sshbuf_new()) == NULL) {
386: r = SSH_ERR_ALLOC_FAIL;
387: goto out;
388: }
1.9 markus 389: /* Prepare and append inner signature object */
1.3 markus 390: if ((r = sshbuf_put_bignum2_bytes(inner_sig,
391: resp->sig_r, resp->sig_r_len)) != 0 ||
392: (r = sshbuf_put_bignum2_bytes(inner_sig,
393: resp->sig_s, resp->sig_s_len)) != 0 ||
394: (r = sshbuf_put_u8(inner_sig, resp->flags)) != 0 ||
395: (r = sshbuf_put_u32(inner_sig, resp->counter)) != 0) {
396: debug("%s: buffer error: %s", __func__, ssh_err(r));
397: goto out;
398: }
1.9 markus 399: if ((r = sshbuf_put_stringb(sig, inner_sig)) != 0) {
400: debug("%s: buffer error: %s", __func__, ssh_err(r));
401: goto out;
402: }
1.3 markus 403: #ifdef DEBUG_SK
404: fprintf(stderr, "%s: sig_r:\n", __func__);
405: sshbuf_dump_data(resp->sig_r, resp->sig_r_len, stderr);
406: fprintf(stderr, "%s: sig_s:\n", __func__);
407: sshbuf_dump_data(resp->sig_s, resp->sig_s_len, stderr);
1.9 markus 408: fprintf(stderr, "%s: inner:\n", __func__);
409: sshbuf_dump(inner_sig, stderr);
1.5 markus 410: #endif
411: r = 0;
1.9 markus 412: out:
1.5 markus 413: sshbuf_free(inner_sig);
414: return r;
415: }
416:
417: static int
1.9 markus 418: sshsk_ed25519_sig(struct sk_sign_response *resp, struct sshbuf *sig)
1.5 markus 419: {
420: int r = SSH_ERR_INTERNAL_ERROR;
421:
1.8 markus 422: /* Check response validity */
423: if (resp->sig_r == NULL) {
424: error("%s: sk_sign response invalid", __func__);
425: r = SSH_ERR_INVALID_FORMAT;
426: goto out;
427: }
1.9 markus 428: if ((r = sshbuf_put_string(sig,
1.5 markus 429: resp->sig_r, resp->sig_r_len)) != 0 ||
1.9 markus 430: (r = sshbuf_put_u8(sig, resp->flags)) != 0 ||
431: (r = sshbuf_put_u32(sig, resp->counter)) != 0) {
1.5 markus 432: debug("%s: buffer error: %s", __func__, ssh_err(r));
433: goto out;
434: }
435: #ifdef DEBUG_SK
436: fprintf(stderr, "%s: sig_r:\n", __func__);
437: sshbuf_dump_data(resp->sig_r, resp->sig_r_len, stderr);
1.3 markus 438: #endif
439: r = 0;
1.9 markus 440: out:
441: return 0;
1.3 markus 442: }
443:
1.1 djm 444: int
1.4 markus 445: sshsk_sign(const char *provider_path, const struct sshkey *key,
1.1 djm 446: u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,
447: u_int compat)
448: {
449: struct sshsk_provider *skp = NULL;
450: int r = SSH_ERR_INTERNAL_ERROR;
1.7 markus 451: int type, alg;
1.1 djm 452: struct sk_sign_response *resp = NULL;
453: struct sshbuf *inner_sig = NULL, *sig = NULL;
454: uint8_t message[32];
455:
456: if (sigp != NULL)
457: *sigp = NULL;
458: if (lenp != NULL)
459: *lenp = 0;
1.5 markus 460: type = sshkey_type_plain(key->type);
461: switch (type) {
462: case KEY_ECDSA_SK:
1.7 markus 463: alg = SSH_SK_ECDSA;
464: break;
1.5 markus 465: case KEY_ED25519_SK:
1.7 markus 466: alg = SSH_SK_ED25519;
1.5 markus 467: break;
468: default:
469: return SSH_ERR_INVALID_ARGUMENT;
470: }
1.1 djm 471: if (provider_path == NULL ||
472: key->sk_key_handle == NULL ||
473: key->sk_application == NULL || *key->sk_application == '\0') {
474: r = SSH_ERR_INVALID_ARGUMENT;
475: goto out;
476: }
477: if ((skp = sshsk_open(provider_path)) == NULL) {
478: r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
479: goto out;
480: }
481:
482: /* hash data to be signed before it goes to the security key */
483: if ((r = ssh_digest_memory(SSH_DIGEST_SHA256, data, datalen,
484: message, sizeof(message))) != 0) {
485: error("%s: hash application failed: %s", __func__, ssh_err(r));
486: r = SSH_ERR_INTERNAL_ERROR;
487: goto out;
488: }
1.7 markus 489: if ((r = skp->sk_sign(alg, message, sizeof(message),
1.1 djm 490: key->sk_application,
491: sshbuf_ptr(key->sk_key_handle), sshbuf_len(key->sk_key_handle),
492: key->sk_flags, &resp)) != 0) {
493: debug("%s: sk_sign failed with code %d", __func__, r);
494: goto out;
495: }
1.9 markus 496: /* Assemble signature */
497: if ((sig = sshbuf_new()) == NULL) {
498: r = SSH_ERR_ALLOC_FAIL;
499: goto out;
500: }
501: if ((r = sshbuf_put_cstring(sig, sshkey_ssh_name_plain(key))) != 0) {
502: debug("%s: buffer error (outer): %s", __func__, ssh_err(r));
503: goto out;
504: }
1.5 markus 505: switch (type) {
506: case KEY_ECDSA_SK:
1.9 markus 507: if ((r = sshsk_ecdsa_sig(resp, sig)) != 0)
1.5 markus 508: goto out;
509: break;
510: case KEY_ED25519_SK:
1.9 markus 511: if ((r = sshsk_ed25519_sig(resp, sig)) != 0)
1.5 markus 512: goto out;
513: break;
514: }
1.1 djm 515: #ifdef DEBUG_SK
1.5 markus 516: fprintf(stderr, "%s: sig_flags = 0x%02x, sig_counter = %u\n",
517: __func__, resp->flags, resp->counter);
1.1 djm 518: fprintf(stderr, "%s: hashed message:\n", __func__);
519: sshbuf_dump_data(message, sizeof(message), stderr);
520: fprintf(stderr, "%s: sigbuf:\n", __func__);
521: sshbuf_dump(sig, stderr);
522: #endif
523: if (sigp != NULL) {
524: if ((*sigp = malloc(sshbuf_len(sig))) == NULL) {
525: r = SSH_ERR_ALLOC_FAIL;
526: goto out;
527: }
528: memcpy(*sigp, sshbuf_ptr(sig), sshbuf_len(sig));
529: }
530: if (lenp != NULL)
531: *lenp = sshbuf_len(sig);
532: /* success */
533: r = 0;
534: out:
535: explicit_bzero(message, sizeof(message));
536: sshsk_free(skp);
537: sshsk_free_sign_response(resp);
538: sshbuf_free(sig);
539: sshbuf_free(inner_sig);
540: return r;
541: }