diff options
author | djm@openbsd.org <djm@openbsd.org> | 2020-01-28 08:01:34 +0000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2020-01-29 18:52:55 +1100 |
commit | 24c0f752adf9021277a7b0a84931bb5fe48ea379 (patch) | |
tree | cd1b9474e73ad7647b4ad88775365e7430d3fe64 | |
parent | 156bef36f93a48212383235bb8e3d71eaf2b2777 (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.u2f | 21 | ||||
-rw-r--r-- | sk-usbhid.c | 1 | ||||
-rw-r--r-- | ssh-keygen.1 | 16 | ||||
-rw-r--r-- | ssh-keygen.c | 36 | ||||
-rw-r--r-- | ssh-sk.c | 10 |
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 | |||
141 | choose not to include this information in the public key or save it by | 141 | choose not to include this information in the public key or save it by |
142 | default. | 142 | default. |
143 | 143 | ||
144 | Attestation information is very useful however in an organisational | 144 | Attestation information is useful for out-of-band key and certificate |
145 | context, where it may be used by a CA as part of certificate | 145 | registration worksflows, e.g. proving to a CA that a key is backed |
146 | issuance. In this case, exposure to the CA of hardware identity is | 146 | by trusted hardware before it will issue a certificate. To support this |
147 | desirable. To support this case, OpenSSH optionally allows retaining the | 147 | case, OpenSSH optionally allows retaining the attestation information |
148 | attestation information at the time of key generation. It will take the | 148 | at the time of key generation. It will take the following format: |
149 | following 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 | |||
156 | OpenSSH treats the attestation certificate and enrollment signatures as | ||
157 | opaque objects and does no interpretation of them itself. | ||
155 | 158 | ||
156 | SSH U2F signatures | 159 | SSH 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 |
484 | will refuse such signatures by default, unless overridden via | 484 | will refuse such signatures by default, unless overridden via |
485 | an authorized_keys option. | 485 | an authorized_keys option. |
486 | .It Cm challenge=path | ||
487 | Specifies a path to a challenge string that will be passed to the | ||
488 | FIDO token during key generation. | ||
489 | The challenge string is optional, but may be used as part of an out-of-band | ||
490 | protocol for key enrollment. | ||
491 | If no | ||
492 | .Cm challenge | ||
493 | is specified, a random challenge is used. | ||
486 | .It Cm resident | 494 | .It Cm resident |
487 | Indicate that the key should be stored on the FIDO authenticator itself. | 495 | Indicate that the key should be stored on the FIDO authenticator itself. |
488 | Resident keys may be supported on FIDO2 tokens and typically require that | 496 | Resident keys may be supported on FIDO2 tokens and typically require that |
@@ -494,6 +502,10 @@ A username to be associated with a resident key, | |||
494 | overriding the empty default username. | 502 | overriding the empty default username. |
495 | Specifying a username may be useful when generating multiple resident keys | 503 | Specifying a username may be useful when generating multiple resident keys |
496 | for the same application name. | 504 | for the same application name. |
505 | .It Cm write-attestation=path | ||
506 | May be used at key generation time to record the attestation certificate | ||
507 | returned from FIDO tokens during key generation. | ||
508 | By default this information is discarded. | ||
497 | .El | 509 | .El |
498 | .Pp | 510 | .Pp |
499 | The | 511 | The |
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 | } |
@@ -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 | } |