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