summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordjm@openbsd.org <djm@openbsd.org>2020-01-28 08:01:34 +0000
committerDamien Miller <djm@mindrot.org>2020-01-29 18:52:55 +1100
commit24c0f752adf9021277a7b0a84931bb5fe48ea379 (patch)
treecd1b9474e73ad7647b4ad88775365e7430d3fe64
parent156bef36f93a48212383235bb8e3d71eaf2b2777 (diff)
upstream: changes to support FIDO attestation
Allow writing to disk the attestation certificate that is generated by the FIDO token at key enrollment time. These certificates may be used by an out-of-band workflow to prove that a particular key is held in trustworthy hardware. Allow passing in a challenge that will be sent to the card during key enrollment. These are needed to build an attestation workflow that resists replay attacks. ok markus@ OpenBSD-Commit-ID: 457dc3c3d689ba39eed328f0817ed9b91a5f78f6
-rw-r--r--PROTOCOL.u2f21
-rw-r--r--sk-usbhid.c1
-rw-r--r--ssh-keygen.116
-rw-r--r--ssh-keygen.c36
-rw-r--r--ssh-sk.c10
5 files changed, 65 insertions, 19 deletions
diff --git a/PROTOCOL.u2f b/PROTOCOL.u2f
index 58f75ba28..748111d56 100644
--- a/PROTOCOL.u2f
+++ b/PROTOCOL.u2f
@@ -141,17 +141,20 @@ least manufacturer and batch number granularity. For this reason, we
141choose not to include this information in the public key or save it by 141choose not to include this information in the public key or save it by
142default. 142default.
143 143
144Attestation information is very useful however in an organisational 144Attestation information is useful for out-of-band key and certificate
145context, where it may be used by a CA as part of certificate 145registration worksflows, e.g. proving to a CA that a key is backed
146issuance. In this case, exposure to the CA of hardware identity is 146by trusted hardware before it will issue a certificate. To support this
147desirable. To support this case, OpenSSH optionally allows retaining the 147case, OpenSSH optionally allows retaining the attestation information
148attestation information at the time of key generation. It will take the 148at the time of key generation. It will take the following format:
149following format: 149
150 150 string "ssh-sk-attest-v00"
151 string "sk-attest-v00"
152 uint32 version (1 for U2F, 2 for FIDO2 in future)
153 string attestation certificate 151 string attestation certificate
154 string enrollment signature 152 string enrollment signature
153 uint32 reserved flags
154 string reserved string
155
156OpenSSH treats the attestation certificate and enrollment signatures as
157opaque objects and does no interpretation of them itself.
155 158
156SSH U2F signatures 159SSH U2F signatures
157------------------ 160------------------
diff --git a/sk-usbhid.c b/sk-usbhid.c
index 2148e1d79..ad83054ad 100644
--- a/sk-usbhid.c
+++ b/sk-usbhid.c
@@ -570,6 +570,7 @@ sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len,
570 } 570 }
571 if ((ptr = fido_cred_x5c_ptr(cred)) != NULL) { 571 if ((ptr = fido_cred_x5c_ptr(cred)) != NULL) {
572 len = fido_cred_x5c_len(cred); 572 len = fido_cred_x5c_len(cred);
573 debug3("%s: attestation cert len=%zu", __func__, len);
573 if ((response->attestation_cert = calloc(1, len)) == NULL) { 574 if ((response->attestation_cert = calloc(1, len)) == NULL) {
574 skdebug(__func__, "calloc attestation cert failed"); 575 skdebug(__func__, "calloc attestation cert failed");
575 goto out; 576 goto out;
diff --git a/ssh-keygen.1 b/ssh-keygen.1
index b4a873920..c6a976183 100644
--- a/ssh-keygen.1
+++ b/ssh-keygen.1
@@ -1,4 +1,4 @@
1.\" $OpenBSD: ssh-keygen.1,v 1.196 2020/01/23 23:31:52 djm Exp $ 1.\" $OpenBSD: ssh-keygen.1,v 1.197 2020/01/28 08:01:34 djm Exp $
2.\" 2.\"
3.\" Author: Tatu Ylonen <ylo@cs.hut.fi> 3.\" Author: Tatu Ylonen <ylo@cs.hut.fi>
4.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -35,7 +35,7 @@
35.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 35.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
36.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37.\" 37.\"
38.Dd $Mdocdate: January 23 2020 $ 38.Dd $Mdocdate: January 28 2020 $
39.Dt SSH-KEYGEN 1 39.Dt SSH-KEYGEN 1
40.Os 40.Os
41.Sh NAME 41.Sh NAME
@@ -483,6 +483,14 @@ Note that
483.Xr sshd 8 483.Xr sshd 8
484will refuse such signatures by default, unless overridden via 484will refuse such signatures by default, unless overridden via
485an authorized_keys option. 485an authorized_keys option.
486.It Cm challenge=path
487Specifies a path to a challenge string that will be passed to the
488FIDO token during key generation.
489The challenge string is optional, but may be used as part of an out-of-band
490protocol for key enrollment.
491If no
492.Cm challenge
493is specified, a random challenge is used.
486.It Cm resident 494.It Cm resident
487Indicate that the key should be stored on the FIDO authenticator itself. 495Indicate that the key should be stored on the FIDO authenticator itself.
488Resident keys may be supported on FIDO2 tokens and typically require that 496Resident keys may be supported on FIDO2 tokens and typically require that
@@ -494,6 +502,10 @@ A username to be associated with a resident key,
494overriding the empty default username. 502overriding the empty default username.
495Specifying a username may be useful when generating multiple resident keys 503Specifying a username may be useful when generating multiple resident keys
496for the same application name. 504for the same application name.
505.It Cm write-attestation=path
506May be used at key generation time to record the attestation certificate
507returned from FIDO tokens during key generation.
508By default this information is discarded.
497.El 509.El
498.Pp 510.Pp
499The 511The
diff --git a/ssh-keygen.c b/ssh-keygen.c
index 8df55f2c2..4ee43ab98 100644
--- a/ssh-keygen.c
+++ b/ssh-keygen.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: ssh-keygen.c,v 1.394 2020/01/25 23:13:09 djm Exp $ */ 1/* $OpenBSD: ssh-keygen.c,v 1.395 2020/01/28 08:01:34 djm Exp $ */
2/* 2/*
3 * Author: Tatu Ylonen <ylo@cs.hut.fi> 3 * Author: Tatu Ylonen <ylo@cs.hut.fi>
4 * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4 * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -3114,6 +3114,8 @@ main(int argc, char **argv)
3114 unsigned long long cert_serial = 0; 3114 unsigned long long cert_serial = 0;
3115 char *identity_comment = NULL, *ca_key_path = NULL, **opts = NULL; 3115 char *identity_comment = NULL, *ca_key_path = NULL, **opts = NULL;
3116 char *sk_application = NULL, *sk_device = NULL, *sk_user = NULL; 3116 char *sk_application = NULL, *sk_device = NULL, *sk_user = NULL;
3117 char *sk_attestaion_path = NULL;
3118 struct sshbuf *challenge = NULL, *attest = NULL;
3117 size_t i, nopts = 0; 3119 size_t i, nopts = 0;
3118 u_int32_t bits = 0; 3120 u_int32_t bits = 0;
3119 uint8_t sk_flags = SSH_SK_USER_PRESENCE_REQD; 3121 uint8_t sk_flags = SSH_SK_USER_PRESENCE_REQD;
@@ -3557,6 +3559,16 @@ main(int argc, char **argv)
3557 sk_device = xstrdup(opts[i] + 7); 3559 sk_device = xstrdup(opts[i] + 7);
3558 } else if (strncasecmp(opts[i], "user=", 5) == 0) { 3560 } else if (strncasecmp(opts[i], "user=", 5) == 0) {
3559 sk_user = xstrdup(opts[i] + 5); 3561 sk_user = xstrdup(opts[i] + 5);
3562 } else if (strncasecmp(opts[i], "challenge=", 10) == 0) {
3563 if ((r = sshbuf_load_file(opts[i] + 10,
3564 &challenge)) != 0) {
3565 fatal("Unable to load FIDO enrollment "
3566 "challenge \"%s\": %s",
3567 opts[i] + 10, ssh_err(r));
3568 }
3569 } else if (strncasecmp(opts[i],
3570 "write-attestation=", 18) == 0) {
3571 sk_attestaion_path = opts[i] + 18;
3560 } else if (strncasecmp(opts[i], 3572 } else if (strncasecmp(opts[i],
3561 "application=", 12) == 0) { 3573 "application=", 12) == 0) {
3562 sk_application = xstrdup(opts[i] + 12); 3574 sk_application = xstrdup(opts[i] + 12);
@@ -3570,12 +3582,14 @@ main(int argc, char **argv)
3570 "to authorize key generation.\n"); 3582 "to authorize key generation.\n");
3571 } 3583 }
3572 passphrase = NULL; 3584 passphrase = NULL;
3585 if ((attest = sshbuf_new()) == NULL)
3586 fatal("sshbuf_new failed");
3573 for (i = 0 ; i < 3; i++) { 3587 for (i = 0 ; i < 3; i++) {
3574 fflush(stdout); 3588 fflush(stdout);
3575 r = sshsk_enroll(type, sk_provider, sk_device, 3589 r = sshsk_enroll(type, sk_provider, sk_device,
3576 sk_application == NULL ? "ssh:" : sk_application, 3590 sk_application == NULL ? "ssh:" : sk_application,
3577 sk_user, sk_flags, passphrase, NULL, 3591 sk_user, sk_flags, passphrase, challenge,
3578 &private, NULL); 3592 &private, attest);
3579 if (r == 0) 3593 if (r == 0)
3580 break; 3594 break;
3581 if (r != SSH_ERR_KEY_WRONG_PASSPHRASE) 3595 if (r != SSH_ERR_KEY_WRONG_PASSPHRASE)
@@ -3668,6 +3682,22 @@ main(int argc, char **argv)
3668 free(fp); 3682 free(fp);
3669 } 3683 }
3670 3684
3685 if (sk_attestaion_path != NULL) {
3686 if (attest == NULL || sshbuf_len(attest) == 0) {
3687 fatal("Enrollment did not return attestation "
3688 "certificate");
3689 }
3690 if ((r = sshbuf_write_file(sk_attestaion_path, attest)) != 0) {
3691 fatal("Unable to write attestation certificate "
3692 "\"%s\": %s", sk_attestaion_path, ssh_err(r));
3693 }
3694 if (!quiet) {
3695 printf("Your FIDO attestation certificate has been "
3696 "saved in %s\n", sk_attestaion_path);
3697 }
3698 }
3699 sshbuf_free(attest);
3671 sshkey_free(public); 3700 sshkey_free(public);
3701
3672 exit(0); 3702 exit(0);
3673} 3703}
diff --git a/ssh-sk.c b/ssh-sk.c
index a8d4de832..3e88aafff 100644
--- a/ssh-sk.c
+++ b/ssh-sk.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: ssh-sk.c,v 1.25 2020/01/25 23:13:09 djm Exp $ */ 1/* $OpenBSD: ssh-sk.c,v 1.26 2020/01/28 08:01:34 djm Exp $ */
2/* 2/*
3 * Copyright (c) 2019 Google LLC 3 * Copyright (c) 2019 Google LLC
4 * 4 *
@@ -504,14 +504,14 @@ sshsk_enroll(int type, const char *provider_path, const char *device,
504 504
505 /* Optionally fill in the attestation information */ 505 /* Optionally fill in the attestation information */
506 if (attest != NULL) { 506 if (attest != NULL) {
507 if ((r = sshbuf_put_cstring(attest, "sk-attest-v00")) != 0 || 507 if ((r = sshbuf_put_cstring(attest,
508 (r = sshbuf_put_u32(attest, 1)) != 0 || /* XXX U2F ver */ 508 "ssh-sk-attest-v00")) != 0 ||
509 (r = sshbuf_put_string(attest, 509 (r = sshbuf_put_string(attest,
510 resp->attestation_cert, resp->attestation_cert_len)) != 0 || 510 resp->attestation_cert, resp->attestation_cert_len)) != 0 ||
511 (r = sshbuf_put_string(attest, 511 (r = sshbuf_put_string(attest,
512 resp->signature, resp->signature_len)) != 0 || 512 resp->signature, resp->signature_len)) != 0 ||
513 (r = sshbuf_put_u32(attest, flags)) != 0 || /* XXX right? */ 513 (r = sshbuf_put_u32(attest, 0)) != 0 || /* resvd flags */
514 (r = sshbuf_put_string(attest, NULL, 0)) != 0) { 514 (r = sshbuf_put_string(attest, NULL, 0)) != 0 /* resvd */) {
515 error("%s: buffer error: %s", __func__, ssh_err(r)); 515 error("%s: buffer error: %s", __func__, ssh_err(r));
516 goto out; 516 goto out;
517 } 517 }