]> git.ipfire.org Git - thirdparty/openssh-portable.git/commitdiff
upstream: Add support for FIDO webauthn (verification only).
authordjm@openbsd.org <djm@openbsd.org>
Mon, 22 Jun 2020 05:58:35 +0000 (05:58 +0000)
committerDamien Miller <djm@mindrot.org>
Mon, 22 Jun 2020 06:27:27 +0000 (16:27 +1000)
webauthn is a standard for using FIDO keys in web browsers. webauthn
signatures are a slightly different format to plain FIDO signatures - this
support allows verification of these. Feedback and ok markus@

OpenBSD-Commit-ID: ab7e3a9fb5782d99d574f408614d833379e564ad

PROTOCOL.u2f
ssh-ecdsa-sk.c
sshkey.c

index 69347e9992edd952eb8f5c3b4d14e1b223c58ebc..fd31ea4efde937867dbf8d462dc8e89cf0876727 100644 (file)
@@ -209,6 +209,32 @@ For Ed25519 keys the signature is encoded as:
        byte            flags
        uint32          counter
 
+webauthn signatures
+-------------------
+
+The W3C/FIDO webauthn[1] standard defines a mechanism for a web browser to
+interact with FIDO authentication tokens. This standard builds upon the
+FIDO standards, but requires different signature contents to raw FIDO
+messages. OpenSSH supports ECDSA/p256 webauthn signatures through the
+"webauthn-sk-ecdsa-sha2-nistp256@openssh.com" signature algorithm.
+
+The wire encoding for a webauthn-sk-ecdsa-sha2-nistp256@openssh.com
+signature is similar to the sk-ecdsa-sha2-nistp256@openssh.com format:
+
+       string          "webauthn-sk-ecdsa-sha2-nistp256@openssh.com"
+       string          ecdsa_signature
+       byte            flags
+       uint32          counter
+       string          origin
+       string          clientData
+       string          extensions
+
+Where "origin" is the HTTP origin making the signature, "clientData" is
+the JSON-like structure signed by the browser and "extensions" are any
+extensions used in making the signature.
+
+[1] https://www.w3.org/TR/webauthn-2/
+
 ssh-agent protocol extensions
 -----------------------------
 
index dcf605ba1cce68e4933b7737df7215d7586644f4..0004a73cade83d6925c44cde709894c4485f75c0 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-ecdsa-sk.c,v 1.6 2020/06/22 05:56:23 djm Exp $ */
+/* $OpenBSD: ssh-ecdsa-sk.c,v 1.7 2020/06/22 05:58:35 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  * Copyright (c) 2010 Damien Miller.  All rights reserved.
 #define SSHKEY_INTERNAL
 #include "sshkey.h"
 
+#ifndef OPENSSL_HAS_ECC
+/* ARGSUSED */
+int
+ssh_ecdsa_sk_verify(const struct sshkey *key,
+    const u_char *signature, size_t signaturelen,
+    const u_char *data, size_t datalen, u_int compat,
+    struct sshkey_sig_details **detailsp)
+{
+       return SSH_ERR_FEATURE_UNSUPPORTED;
+}
+#else /* OPENSSL_HAS_ECC */
+
+/*
+ * Check FIDO/W3C webauthn signatures clientData field against the expected
+ * format and prepare a hash of it for use in signature verification.
+ *
+ * webauthn signatures do not sign the hash of the message directly, but
+ * instead sign a JSON-like "clientData" wrapper structure that contains the
+ * message hash along with a other information.
+ *
+ * Fortunately this structure has a fixed format so it is possible to verify
+ * that the hash of the signed message is present within the clientData
+ * structure without needing to implement any JSON parsing.
+ */
+static int
+webauthn_check_prepare_hash(const u_char *data, size_t datalen,
+    const char *origin, const struct sshbuf *wrapper,
+    uint8_t flags, const struct sshbuf *extensions,
+    u_char *msghash, size_t msghashlen)
+{
+       int r = SSH_ERR_INTERNAL_ERROR;
+       struct sshbuf *chall = NULL, *m = NULL;
+
+       if ((m = sshbuf_new()) == NULL ||
+           (chall = sshbuf_from(data, datalen)) == NULL) {
+               r = SSH_ERR_ALLOC_FAIL;
+               goto out;
+       }
+       /*
+        * Ensure origin contains no quote character and that the flags are
+        * consistent with what we received
+        */
+       if (strchr(origin, '\"') != NULL ||
+           (flags & 0x40) != 0 /* AD */ ||
+           ((flags & 0x80) == 0 /* ED */) != (sshbuf_len(extensions) == 0)) {
+               r = SSH_ERR_INVALID_FORMAT;
+               goto out;
+       }
+#define WEBAUTHN_0     "{\"type\":\"webauthn.get\",\"challenge\":\""
+#define WEBAUTHN_1     "\",\"origin\":\""
+#define WEBAUTHN_2     "\""
+       if ((r = sshbuf_put(m, WEBAUTHN_0, sizeof(WEBAUTHN_0) - 1)) != 0 ||
+           (r = sshbuf_dtourlb64(chall, m, 0)) != 0 ||
+           (r = sshbuf_put(m, WEBAUTHN_1, sizeof(WEBAUTHN_1) - 1)) != 0 ||
+           (r = sshbuf_put(m, origin, strlen(origin))) != 0 ||
+           (r = sshbuf_put(m, WEBAUTHN_2, sizeof(WEBAUTHN_2) - 1)) != 0)
+               goto out;
+#ifdef DEBUG_SK
+       fprintf(stderr, "%s: received origin: %s\n", __func__, origin);
+       fprintf(stderr, "%s: received clientData:\n", __func__);
+       sshbuf_dump(wrapper, stderr);
+       fprintf(stderr, "%s: expected clientData premable:\n", __func__);
+       sshbuf_dump(m, stderr);
+#endif
+       /* Check that the supplied clientData matches what we expect */
+       if ((r = sshbuf_cmp(wrapper, 0, sshbuf_ptr(m), sshbuf_len(m))) != 0)
+               goto out;
+
+       /* Prepare hash of clientData */
+       if ((r = ssh_digest_buffer(SSH_DIGEST_SHA256, wrapper,
+           msghash, msghashlen)) != 0)
+               goto out;
+
+       /* success */
+       r = 0;
+ out:
+       sshbuf_free(chall);
+       sshbuf_free(m);
+       return r;
+}
+
 /* ARGSUSED */
 int
 ssh_ecdsa_sk_verify(const struct sshkey *key,
@@ -56,15 +137,15 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
     const u_char *data, size_t datalen, u_int compat,
     struct sshkey_sig_details **detailsp)
 {
-#ifdef OPENSSL_HAS_ECC
        ECDSA_SIG *sig = NULL;
        BIGNUM *sig_r = NULL, *sig_s = NULL;
        u_char sig_flags;
        u_char msghash[32], apphash[32], sighash[32];
        u_int sig_counter;
-       int ret = SSH_ERR_INTERNAL_ERROR;
+       int is_webauthn = 0, ret = SSH_ERR_INTERNAL_ERROR;
        struct sshbuf *b = NULL, *sigbuf = NULL, *original_signed = NULL;
-       char *ktype = NULL;
+       struct sshbuf *webauthn_wrapper = NULL, *webauthn_exts = NULL;
+       char *ktype = NULL, *webauthn_origin = NULL;
        struct sshkey_sig_details *details = NULL;
 #ifdef DEBUG_SK
        char *tmp = NULL;
@@ -91,7 +172,9 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
                ret = SSH_ERR_INVALID_FORMAT;
                goto out;
        }
-       if (strcmp(ktype, "sk-ecdsa-sha2-nistp256@openssh.com") != 0) {
+       if (strcmp(ktype, "webauthn-sk-ecdsa-sha2-nistp256@openssh.com") == 0)
+               is_webauthn = 1;
+       else if (strcmp(ktype, "sk-ecdsa-sha2-nistp256@openssh.com") != 0) {
                ret = SSH_ERR_INVALID_FORMAT;
                goto out;
        }
@@ -101,6 +184,14 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
                ret = SSH_ERR_INVALID_FORMAT;
                goto out;
        }
+       if (is_webauthn) {
+               if (sshbuf_get_cstring(b, &webauthn_origin, NULL) != 0 ||
+                   sshbuf_froms(b, &webauthn_wrapper) != 0 ||
+                   sshbuf_froms(b, &webauthn_exts) != 0) {
+                       ret = SSH_ERR_INVALID_FORMAT;
+                       goto out;
+               }
+       }
        if (sshbuf_len(b) != 0) {
                ret = SSH_ERR_UNEXPECTED_TRAILING_DATA;
                goto out;
@@ -116,6 +207,7 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
                ret = SSH_ERR_UNEXPECTED_TRAILING_DATA;
                goto out;
        }
+
 #ifdef DEBUG_SK
        fprintf(stderr, "%s: data: (len %zu)\n", __func__, datalen);
        /* sshbuf_dump_data(data, datalen, stderr); */
@@ -125,6 +217,12 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
        free(tmp);
        fprintf(stderr, "%s: sig_flags = 0x%02x, sig_counter = %u\n",
            __func__, sig_flags, sig_counter);
+       if (is_webauthn) {
+               fprintf(stderr, "%s: webauthn origin: %s\n", __func__,
+                   webauthn_origin);
+               fprintf(stderr, "%s: webauthn_wrapper:\n", __func__);
+               sshbuf_dump(webauthn_wrapper, stderr);
+       }
 #endif
        if ((sig = ECDSA_SIG_new()) == NULL) {
                ret = SSH_ERR_ALLOC_FAIL;
@@ -141,7 +239,12 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
                ret = SSH_ERR_ALLOC_FAIL;
                goto out;
        }
-       if ((ret = ssh_digest_memory(SSH_DIGEST_SHA256, data, datalen,
+       if (is_webauthn) {
+               if ((ret = webauthn_check_prepare_hash(data, datalen,
+                   webauthn_origin, webauthn_wrapper, sig_flags, webauthn_exts,
+                   msghash, sizeof(msghash))) != 0)
+                       goto out;
+       } else if ((ret = ssh_digest_memory(SSH_DIGEST_SHA256, data, datalen,
            msghash, sizeof(msghash))) != 0)
                goto out;
        /* Application value is hashed before signature */
@@ -158,6 +261,7 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
            apphash, sizeof(apphash))) != 0 ||
            (ret = sshbuf_put_u8(original_signed, sig_flags)) != 0 ||
            (ret = sshbuf_put_u32(original_signed, sig_counter)) != 0 ||
+           (ret = sshbuf_putb(original_signed, webauthn_exts)) != 0 ||
            (ret = sshbuf_put(original_signed, msghash, sizeof(msghash))) != 0)
                goto out;
        /* Signature is over H(original_signed) */
@@ -197,6 +301,9 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
        explicit_bzero(sighash, sizeof(msghash));
        explicit_bzero(apphash, sizeof(apphash));
        sshkey_sig_details_free(details);
+       sshbuf_free(webauthn_wrapper);
+       sshbuf_free(webauthn_exts);
+       free(webauthn_origin);
        sshbuf_free(original_signed);
        sshbuf_free(sigbuf);
        sshbuf_free(b);
@@ -205,7 +312,6 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
        BN_clear_free(sig_s);
        free(ktype);
        return ret;
-#else
-       return SSH_ERR_INTERNAL_ERROR;
-#endif
 }
+
+#endif /* OPENSSL_HAS_ECC */
index 1571e3d93878828eb11b1926a3804979ee829210..5497497cdd3445b9e2993438f6ae04ddf5ce13ad 100644 (file)
--- a/sshkey.c
+++ b/sshkey.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshkey.c,v 1.108 2020/04/11 10:16:11 djm Exp $ */
+/* $OpenBSD: sshkey.c,v 1.109 2020/06/22 05:58:35 djm Exp $ */
 /*
  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
  * Copyright (c) 2008 Alexander von Gernler.  All rights reserved.
@@ -132,6 +132,8 @@ static const struct keytype keytypes[] = {
 #  endif /* OPENSSL_HAS_NISTP521 */
        { "sk-ecdsa-sha2-nistp256@openssh.com", "ECDSA-SK", NULL,
            KEY_ECDSA_SK, NID_X9_62_prime256v1, 0, 0 },
+       { "webauthn-sk-ecdsa-sha2-nistp256@openssh.com", "ECDSA-SK", NULL,
+           KEY_ECDSA_SK, NID_X9_62_prime256v1, 0, 1 },
 # endif /* OPENSSL_HAS_ECC */
        { "ssh-rsa-cert-v01@openssh.com", "RSA-CERT", NULL,
            KEY_RSA_CERT, 0, 1, 0 },