blob: 43f808efcfa4ae3475d895972ebbc99e9257807e [file] [log] [blame]
markus@openbsd.org7c32b512019-11-12 19:31:45 +00001/* $OpenBSD: ssh-sk.c,v 1.6 2019/11/12 19:31:45 markus Exp $ */
djm@openbsd.orged3467c2019-10-31 21:16:20 +00002/*
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 "includes.h"
21
Damien Miller764d51e2019-11-01 13:34:49 +110022#ifdef ENABLE_SK
23
djm@openbsd.orged3467c2019-10-31 21:16:20 +000024#include <dlfcn.h>
25#include <stddef.h>
26#include <stdint.h>
27#include <string.h>
28#include <stdio.h>
29
30#include <openssl/objects.h>
31#include <openssl/ec.h>
32
33#include "log.h"
34#include "misc.h"
35#include "sshbuf.h"
36#include "sshkey.h"
37#include "ssherr.h"
38#include "digest.h"
39
40#include "ssh-sk.h"
41#include "sk-api.h"
markus@openbsd.org7c32b512019-11-12 19:31:45 +000042#include "crypto_api.h"
djm@openbsd.orged3467c2019-10-31 21:16:20 +000043
44struct sshsk_provider {
45 char *path;
46 void *dlhandle;
47
48 /* Return the version of the middleware API */
49 uint32_t (*sk_api_version)(void);
50
51 /* Enroll a U2F key (private key generation) */
52 int (*sk_enroll)(const uint8_t *challenge, size_t challenge_len,
53 const char *application, uint8_t flags,
54 struct sk_enroll_response **enroll_response);
55
56 /* Sign a challenge */
57 int (*sk_sign)(const uint8_t *message, size_t message_len,
58 const char *application,
59 const uint8_t *key_handle, size_t key_handle_len,
60 uint8_t flags, struct sk_sign_response **sign_response);
61};
62
63static void
64sshsk_free(struct sshsk_provider *p)
65{
66 if (p == NULL)
67 return;
68 free(p->path);
69 if (p->dlhandle != NULL)
70 dlclose(p->dlhandle);
71 free(p);
72}
73
74static struct sshsk_provider *
75sshsk_open(const char *path)
76{
77 struct sshsk_provider *ret = NULL;
78 uint32_t version;
79
80 if ((ret = calloc(1, sizeof(*ret))) == NULL) {
81 error("%s: calloc failed", __func__);
82 return NULL;
83 }
84 if ((ret->path = strdup(path)) == NULL) {
85 error("%s: strdup failed", __func__);
86 goto fail;
87 }
88 if ((ret->dlhandle = dlopen(path, RTLD_NOW)) == NULL) {
89 error("Security key provider %s dlopen failed: %s",
90 path, dlerror());
91 goto fail;
92 }
93 if ((ret->sk_api_version = dlsym(ret->dlhandle,
94 "sk_api_version")) == NULL) {
95 error("Security key provider %s dlsym(sk_api_version) "
96 "failed: %s", path, dlerror());
97 goto fail;
98 }
99 version = ret->sk_api_version();
100 debug("%s: provider %s implements version 0x%08lx", __func__,
101 ret->path, (u_long)version);
102 if ((version & SSH_SK_VERSION_MAJOR_MASK) != SSH_SK_VERSION_MAJOR) {
103 error("Security key provider %s implements unsupported version "
104 "0x%08lx (supported: 0x%08lx)", path, (u_long)version,
105 (u_long)SSH_SK_VERSION_MAJOR);
106 goto fail;
107 }
108 if ((ret->sk_enroll = dlsym(ret->dlhandle, "sk_enroll")) == NULL) {
109 error("Security key provider %s dlsym(sk_enroll) "
110 "failed: %s", path, dlerror());
111 goto fail;
112 }
113 if ((ret->sk_sign = dlsym(ret->dlhandle, "sk_sign")) == NULL) {
114 error("Security key provider %s dlsym(sk_sign) failed: %s",
115 path, dlerror());
116 goto fail;
117 }
118 /* success */
119 return ret;
120fail:
121 sshsk_free(ret);
122 return NULL;
123}
124
125static void
126sshsk_free_enroll_response(struct sk_enroll_response *r)
127{
128 if (r == NULL)
129 return;
130 freezero(r->key_handle, r->key_handle_len);
131 freezero(r->public_key, r->public_key_len);
132 freezero(r->signature, r->signature_len);
133 freezero(r->attestation_cert, r->attestation_cert_len);
134 freezero(r, sizeof(*r));
135};
136
137static void
138sshsk_free_sign_response(struct sk_sign_response *r)
139{
140 if (r == NULL)
141 return;
142 freezero(r->sig_r, r->sig_r_len);
143 freezero(r->sig_s, r->sig_s_len);
144 freezero(r, sizeof(*r));
145};
146
markus@openbsd.orgcef84a02019-11-12 19:29:54 +0000147/* Assemble key from response */
148static int
149sshsk_ecdsa_assemble(struct sk_enroll_response *resp, struct sshkey **keyp)
150{
151 struct sshkey *key = NULL;
152 struct sshbuf *b = NULL;
153 EC_POINT *q = NULL;
154 int r;
155
156 *keyp = NULL;
157 if ((key = sshkey_new(KEY_ECDSA_SK)) == NULL) {
158 error("%s: sshkey_new failed", __func__);
159 r = SSH_ERR_ALLOC_FAIL;
160 goto out;
161 }
162 key->ecdsa_nid = NID_X9_62_prime256v1;
163 if ((key->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid)) == NULL ||
164 (q = EC_POINT_new(EC_KEY_get0_group(key->ecdsa))) == NULL ||
165 (b = sshbuf_new()) == NULL) {
166 error("%s: allocation failed", __func__);
167 r = SSH_ERR_ALLOC_FAIL;
168 goto out;
169 }
170 if ((r = sshbuf_put_string(b,
171 resp->public_key, resp->public_key_len)) != 0) {
172 error("%s: buffer error: %s", __func__, ssh_err(r));
173 goto out;
174 }
175 if ((r = sshbuf_get_ec(b, q, EC_KEY_get0_group(key->ecdsa))) != 0) {
176 error("%s: parse key: %s", __func__, ssh_err(r));
177 r = SSH_ERR_INVALID_FORMAT;
178 goto out;
179 }
180 if (sshkey_ec_validate_public(EC_KEY_get0_group(key->ecdsa), q) != 0) {
181 error("Security key returned invalid ECDSA key");
182 r = SSH_ERR_KEY_INVALID_EC_VALUE;
183 goto out;
184 }
185 if (EC_KEY_set_public_key(key->ecdsa, q) != 1) {
186 /* XXX assume it is a allocation error */
187 error("%s: allocation failed", __func__);
188 r = SSH_ERR_ALLOC_FAIL;
189 goto out;
190 }
191 /* success */
192 *keyp = key;
193 key = NULL; /* transferred */
194 r = 0;
195 out:
196 EC_POINT_free(q);
197 sshkey_free(key);
198 sshbuf_free(b);
199 return r;
200}
201
markus@openbsd.org7c32b512019-11-12 19:31:45 +0000202static int
203sshsk_ed25519_assemble(struct sk_enroll_response *resp, struct sshkey **keyp)
204{
205 struct sshkey *key = NULL;
206 int r;
207
208 *keyp = NULL;
209 if (resp->public_key_len != ED25519_PK_SZ) {
210 error("%s: invalid size: %zu", __func__, resp->public_key_len);
211 r = SSH_ERR_INVALID_FORMAT;
212 goto out;
213 }
214 if ((key = sshkey_new(KEY_ED25519_SK)) == NULL) {
215 error("%s: sshkey_new failed", __func__);
216 r = SSH_ERR_ALLOC_FAIL;
217 goto out;
218 }
219 if ((key->ed25519_pk = malloc(ED25519_PK_SZ)) == NULL) {
220 error("%s: malloc failed", __func__);
221 r = SSH_ERR_ALLOC_FAIL;
222 goto out;
223 }
224 memcpy(key->ed25519_pk, resp->public_key, ED25519_PK_SZ);
225 /* success */
226 *keyp = key;
227 key = NULL; /* transferred */
228 r = 0;
229 out:
230 sshkey_free(key);
231 return r;
232}
233
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000234int
markus@openbsd.org7c32b512019-11-12 19:31:45 +0000235sshsk_enroll(int type, const char *provider_path, const char *application,
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000236 uint8_t flags, struct sshbuf *challenge_buf, struct sshkey **keyp,
237 struct sshbuf *attest)
238{
239 struct sshsk_provider *skp = NULL;
240 struct sshkey *key = NULL;
241 u_char randchall[32];
242 const u_char *challenge;
243 size_t challenge_len;
244 struct sk_enroll_response *resp = NULL;
245 int r = SSH_ERR_INTERNAL_ERROR;
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000246
247 *keyp = NULL;
248 if (attest)
249 sshbuf_reset(attest);
markus@openbsd.org7c32b512019-11-12 19:31:45 +0000250 switch (type) {
251 case KEY_ECDSA_SK:
252 case KEY_ED25519_SK:
253 break;
254 default:
255 error("%s: unsupported key type", __func__);
256 r = SSH_ERR_INVALID_ARGUMENT;
257 goto out;
258 }
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000259 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 */
290 if ((r = skp->sk_enroll(challenge, challenge_len, application,
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 }
markus@openbsd.org7c32b512019-11-12 19:31:45 +0000304 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 }
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000314 key->sk_flags = flags;
markus@openbsd.orgcef84a02019-11-12 19:29:54 +0000315 if ((key->sk_key_handle = sshbuf_new()) == NULL ||
316 (key->sk_reserved = sshbuf_new()) == NULL) {
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000317 error("%s: allocation failed", __func__);
318 r = SSH_ERR_ALLOC_FAIL;
319 goto out;
320 }
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000321 if ((key->sk_application = strdup(application)) == NULL) {
322 error("%s: strdup application failed", __func__);
323 r = SSH_ERR_ALLOC_FAIL;
324 goto out;
325 }
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000326 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:
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000350 sshsk_free(skp);
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000351 sshkey_free(key);
352 sshsk_free_enroll_response(resp);
353 explicit_bzero(randchall, sizeof(randchall));
354 return r;
355}
356
markus@openbsd.orgbc7b5d62019-11-12 19:30:21 +0000357static int
358sshsk_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);
markus@openbsd.orgfe05a362019-11-12 19:31:18 +0000383#endif
384 *retp = inner_sig;
385 inner_sig = NULL;
386 r = 0;
387out:
388 sshbuf_free(inner_sig);
389 return r;
390}
391
392static int
393sshsk_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);
markus@openbsd.orgbc7b5d62019-11-12 19:30:21 +0000414#endif
415 *retp = inner_sig;
416 inner_sig = NULL;
417 r = 0;
418out:
419 sshbuf_free(inner_sig);
420 return r;
421}
422
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000423int
markus@openbsd.orge03a29e2019-11-12 19:30:50 +0000424sshsk_sign(const char *provider_path, const struct sshkey *key,
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000425 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;
markus@openbsd.orgfe05a362019-11-12 19:31:18 +0000430 int type;
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000431 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;
markus@openbsd.orgfe05a362019-11-12 19:31:18 +0000439 type = sshkey_type_plain(key->type);
440 switch (type) {
441 case KEY_ECDSA_SK:
442 case KEY_ED25519_SK:
443 break;
444 default:
445 return SSH_ERR_INVALID_ARGUMENT;
446 }
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000447 if (provider_path == NULL ||
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000448 key->sk_key_handle == NULL ||
449 key->sk_application == NULL || *key->sk_application == '\0') {
450 r = SSH_ERR_INVALID_ARGUMENT;
451 goto out;
452 }
453 if ((skp = sshsk_open(provider_path)) == NULL) {
454 r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
455 goto out;
456 }
457
458 /* hash data to be signed before it goes to the security key */
459 if ((r = ssh_digest_memory(SSH_DIGEST_SHA256, data, datalen,
460 message, sizeof(message))) != 0) {
461 error("%s: hash application failed: %s", __func__, ssh_err(r));
462 r = SSH_ERR_INTERNAL_ERROR;
463 goto out;
464 }
465 if ((r = skp->sk_sign(message, sizeof(message),
466 key->sk_application,
467 sshbuf_ptr(key->sk_key_handle), sshbuf_len(key->sk_key_handle),
468 key->sk_flags, &resp)) != 0) {
469 debug("%s: sk_sign failed with code %d", __func__, r);
470 goto out;
471 }
markus@openbsd.orgbc7b5d62019-11-12 19:30:21 +0000472 /* Prepare inner signature object */
markus@openbsd.orgfe05a362019-11-12 19:31:18 +0000473 switch (type) {
474 case KEY_ECDSA_SK:
475 if ((r = sshsk_ecdsa_inner_sig(resp, &inner_sig)) != 0)
476 goto out;
477 break;
478 case KEY_ED25519_SK:
479 if ((r = sshsk_ed25519_inner_sig(resp, &inner_sig)) != 0)
480 goto out;
481 break;
482 }
markus@openbsd.orgbc7b5d62019-11-12 19:30:21 +0000483 /* Assemble outer signature */
484 if ((sig = sshbuf_new()) == NULL) {
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000485 r = SSH_ERR_ALLOC_FAIL;
486 goto out;
487 }
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000488 if ((r = sshbuf_put_cstring(sig, sshkey_ssh_name_plain(key))) != 0 ||
489 (r = sshbuf_put_stringb(sig, inner_sig)) != 0) {
490 debug("%s: buffer error (outer): %s", __func__, ssh_err(r));
491 goto out;
492 }
493#ifdef DEBUG_SK
markus@openbsd.orgfe05a362019-11-12 19:31:18 +0000494 fprintf(stderr, "%s: sig_flags = 0x%02x, sig_counter = %u\n",
495 __func__, resp->flags, resp->counter);
djm@openbsd.orged3467c2019-10-31 21:16:20 +0000496 fprintf(stderr, "%s: hashed message:\n", __func__);
497 sshbuf_dump_data(message, sizeof(message), stderr);
498 fprintf(stderr, "%s: inner:\n", __func__);
499 sshbuf_dump(inner_sig, stderr);
500 fprintf(stderr, "%s: sigbuf:\n", __func__);
501 sshbuf_dump(sig, stderr);
502#endif
503 if (sigp != NULL) {
504 if ((*sigp = malloc(sshbuf_len(sig))) == NULL) {
505 r = SSH_ERR_ALLOC_FAIL;
506 goto out;
507 }
508 memcpy(*sigp, sshbuf_ptr(sig), sshbuf_len(sig));
509 }
510 if (lenp != NULL)
511 *lenp = sshbuf_len(sig);
512 /* success */
513 r = 0;
514 out:
515 explicit_bzero(message, sizeof(message));
516 sshsk_free(skp);
517 sshsk_free_sign_response(resp);
518 sshbuf_free(sig);
519 sshbuf_free(inner_sig);
520 return r;
521}
Damien Miller764d51e2019-11-01 13:34:49 +1100522#endif /* ENABLE_SK */