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