Annotation of src/usr.bin/ssh/ssh-sk.c, Revision 1.9
1.9 ! markus 1: /* $OpenBSD: ssh-sk.c,v 1.8 2019/11/12 19:34:00 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
1.9 ! markus 358: sshsk_ecdsa_sig(struct sk_sign_response *resp, struct sshbuf *sig)
1.3 markus 359: {
360: struct sshbuf *inner_sig = NULL;
361: int r = SSH_ERR_INTERNAL_ERROR;
362:
1.8 markus 363: /* Check response validity */
364: if (resp->sig_r == NULL || resp->sig_r == NULL) {
365: error("%s: sk_sign response invalid", __func__);
366: r = SSH_ERR_INVALID_FORMAT;
367: goto out;
368: }
1.3 markus 369: if ((inner_sig = sshbuf_new()) == NULL) {
370: r = SSH_ERR_ALLOC_FAIL;
371: goto out;
372: }
1.9 ! markus 373: /* Prepare and append inner signature object */
1.3 markus 374: if ((r = sshbuf_put_bignum2_bytes(inner_sig,
375: resp->sig_r, resp->sig_r_len)) != 0 ||
376: (r = sshbuf_put_bignum2_bytes(inner_sig,
377: resp->sig_s, resp->sig_s_len)) != 0 ||
378: (r = sshbuf_put_u8(inner_sig, resp->flags)) != 0 ||
379: (r = sshbuf_put_u32(inner_sig, resp->counter)) != 0) {
380: debug("%s: buffer error: %s", __func__, ssh_err(r));
381: goto out;
382: }
1.9 ! markus 383: if ((r = sshbuf_put_stringb(sig, inner_sig)) != 0) {
! 384: debug("%s: buffer error: %s", __func__, ssh_err(r));
! 385: goto out;
! 386: }
1.3 markus 387: #ifdef DEBUG_SK
388: fprintf(stderr, "%s: sig_r:\n", __func__);
389: sshbuf_dump_data(resp->sig_r, resp->sig_r_len, stderr);
390: fprintf(stderr, "%s: sig_s:\n", __func__);
391: sshbuf_dump_data(resp->sig_s, resp->sig_s_len, stderr);
1.9 ! markus 392: fprintf(stderr, "%s: inner:\n", __func__);
! 393: sshbuf_dump(inner_sig, stderr);
1.5 markus 394: #endif
395: r = 0;
1.9 ! markus 396: out:
1.5 markus 397: sshbuf_free(inner_sig);
398: return r;
399: }
400:
401: static int
1.9 ! markus 402: sshsk_ed25519_sig(struct sk_sign_response *resp, struct sshbuf *sig)
1.5 markus 403: {
404: int r = SSH_ERR_INTERNAL_ERROR;
405:
1.8 markus 406: /* Check response validity */
407: if (resp->sig_r == NULL) {
408: error("%s: sk_sign response invalid", __func__);
409: r = SSH_ERR_INVALID_FORMAT;
410: goto out;
411: }
1.9 ! markus 412: if ((r = sshbuf_put_string(sig,
1.5 markus 413: resp->sig_r, resp->sig_r_len)) != 0 ||
1.9 ! markus 414: (r = sshbuf_put_u8(sig, resp->flags)) != 0 ||
! 415: (r = sshbuf_put_u32(sig, resp->counter)) != 0) {
1.5 markus 416: debug("%s: buffer error: %s", __func__, ssh_err(r));
417: goto out;
418: }
419: #ifdef DEBUG_SK
420: fprintf(stderr, "%s: sig_r:\n", __func__);
421: sshbuf_dump_data(resp->sig_r, resp->sig_r_len, stderr);
1.3 markus 422: #endif
423: r = 0;
1.9 ! markus 424: out:
! 425: return 0;
1.3 markus 426: }
427:
1.1 djm 428: int
1.4 markus 429: sshsk_sign(const char *provider_path, const struct sshkey *key,
1.1 djm 430: u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,
431: u_int compat)
432: {
433: struct sshsk_provider *skp = NULL;
434: int r = SSH_ERR_INTERNAL_ERROR;
1.7 markus 435: int type, alg;
1.1 djm 436: struct sk_sign_response *resp = NULL;
437: struct sshbuf *inner_sig = NULL, *sig = NULL;
438: uint8_t message[32];
439:
440: if (sigp != NULL)
441: *sigp = NULL;
442: if (lenp != NULL)
443: *lenp = 0;
1.5 markus 444: type = sshkey_type_plain(key->type);
445: switch (type) {
446: case KEY_ECDSA_SK:
1.7 markus 447: alg = SSH_SK_ECDSA;
448: break;
1.5 markus 449: case KEY_ED25519_SK:
1.7 markus 450: alg = SSH_SK_ED25519;
1.5 markus 451: break;
452: default:
453: return SSH_ERR_INVALID_ARGUMENT;
454: }
1.1 djm 455: if (provider_path == NULL ||
456: key->sk_key_handle == NULL ||
457: key->sk_application == NULL || *key->sk_application == '\0') {
458: r = SSH_ERR_INVALID_ARGUMENT;
459: goto out;
460: }
461: if ((skp = sshsk_open(provider_path)) == NULL) {
462: r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
463: goto out;
464: }
465:
466: /* hash data to be signed before it goes to the security key */
467: if ((r = ssh_digest_memory(SSH_DIGEST_SHA256, data, datalen,
468: message, sizeof(message))) != 0) {
469: error("%s: hash application failed: %s", __func__, ssh_err(r));
470: r = SSH_ERR_INTERNAL_ERROR;
471: goto out;
472: }
1.7 markus 473: if ((r = skp->sk_sign(alg, message, sizeof(message),
1.1 djm 474: key->sk_application,
475: sshbuf_ptr(key->sk_key_handle), sshbuf_len(key->sk_key_handle),
476: key->sk_flags, &resp)) != 0) {
477: debug("%s: sk_sign failed with code %d", __func__, r);
478: goto out;
479: }
1.9 ! markus 480: /* Assemble signature */
! 481: if ((sig = sshbuf_new()) == NULL) {
! 482: r = SSH_ERR_ALLOC_FAIL;
! 483: goto out;
! 484: }
! 485: if ((r = sshbuf_put_cstring(sig, sshkey_ssh_name_plain(key))) != 0) {
! 486: debug("%s: buffer error (outer): %s", __func__, ssh_err(r));
! 487: goto out;
! 488: }
1.5 markus 489: switch (type) {
490: case KEY_ECDSA_SK:
1.9 ! markus 491: if ((r = sshsk_ecdsa_sig(resp, sig)) != 0)
1.5 markus 492: goto out;
493: break;
494: case KEY_ED25519_SK:
1.9 ! markus 495: if ((r = sshsk_ed25519_sig(resp, sig)) != 0)
1.5 markus 496: goto out;
497: break;
498: }
1.1 djm 499: #ifdef DEBUG_SK
1.5 markus 500: fprintf(stderr, "%s: sig_flags = 0x%02x, sig_counter = %u\n",
501: __func__, resp->flags, resp->counter);
1.1 djm 502: fprintf(stderr, "%s: hashed message:\n", __func__);
503: sshbuf_dump_data(message, sizeof(message), stderr);
504: fprintf(stderr, "%s: sigbuf:\n", __func__);
505: sshbuf_dump(sig, stderr);
506: #endif
507: if (sigp != NULL) {
508: if ((*sigp = malloc(sshbuf_len(sig))) == NULL) {
509: r = SSH_ERR_ALLOC_FAIL;
510: goto out;
511: }
512: memcpy(*sigp, sshbuf_ptr(sig), sshbuf_len(sig));
513: }
514: if (lenp != NULL)
515: *lenp = sshbuf_len(sig);
516: /* success */
517: r = 0;
518: out:
519: explicit_bzero(message, sizeof(message));
520: sshsk_free(skp);
521: sshsk_free_sign_response(resp);
522: sshbuf_free(sig);
523: sshbuf_free(inner_sig);
524: return r;
525: }