[openssh-commits] [openssh] 02/02: upstream: add a work-in-progress tool to verify FIDO attestation

git+noreply at mindrot.org git+noreply at mindrot.org
Thu Dec 5 03:45:16 AEDT 2024


This is an automated email from the git hooks/post-receive script.

djm pushed a commit to branch master
in repository openssh.

commit 8c9ee046d40e4254c6c1711783ea11027b72c3e9
Author: djm at openbsd.org <djm at openbsd.org>
AuthorDate: Wed Dec 4 16:42:49 2024 +0000

    upstream: add a work-in-progress tool to verify FIDO attestation
    
    blobs that ssh-keygen can write when enrolling FIDO keys.
    
    OpenBSD-Regress-ID: 6c97bf3f46e48866677ad69f54b77683eb92437f
---
 regress/misc/ssh-verify-attestation/Makefile       |  79 +++++
 .../ssh-verify-attestation.c                       | 368 +++++++++++++++++++++
 2 files changed, 447 insertions(+)

diff --git a/regress/misc/ssh-verify-attestation/Makefile b/regress/misc/ssh-verify-attestation/Makefile
new file mode 100644
index 00000000..2a797aec
--- /dev/null
+++ b/regress/misc/ssh-verify-attestation/Makefile
@@ -0,0 +1,79 @@
+#	$OpenBSD: Makefile,v 1.1 2024/12/04 16:42:49 djm Exp $
+
+.include <bsd.own.mk>
+.include <bsd.obj.mk>
+
+PROG=	ssh-verify-attestation
+NOMAN=
+
+SSHREL=../../../../../usr.bin/ssh
+.PATH: ${.CURDIR}/${SSHREL}
+
+SRCS=ssh-verify-attestation.c
+# From usr.bin/ssh
+SRCS+=sshbuf-getput-basic.c sshbuf-getput-crypto.c sshbuf-misc.c sshbuf.c
+SRCS+=sshbuf-io.c atomicio.c sshkey.c authfile.c cipher.c log.c ssh-rsa.c
+SRCS+=ssh-dss.c ssh-ecdsa.c ssh-ed25519.c mac.c umac.c umac128.c hmac.c misc.c
+SRCS+=ssherr.c uidswap.c cleanup.c xmalloc.c match.c krl.c fatal.c
+SRCS+=addr.c addrmatch.c bitmap.c
+SRCS+=ed25519.c hash.c
+SRCS+=cipher-chachapoly.c chacha.c poly1305.c ssh-ecdsa-sk.c ssh-sk.c
+SRCS+=ssh-ed25519-sk.c sk-usbhid.c
+
+SRCS+=digest-openssl.c
+#SRCS+=digest-libc.c
+SRCS+=utf8.c
+
+OPENSSL?=	yes
+
+.if (${OPENSSL:L} == "yes")
+CFLAGS+=	-DWITH_OPENSSL
+.endif
+
+# enable warnings
+WARNINGS=Yes
+
+DEBUG=-g
+CFLAGS+=	-fstack-protector-all
+CDIAGFLAGS=	-Wall
+CDIAGFLAGS+=	-Wextra
+CDIAGFLAGS+=	-Werror
+CDIAGFLAGS+=	-Wchar-subscripts
+CDIAGFLAGS+=	-Wcomment
+CDIAGFLAGS+=	-Wformat
+CDIAGFLAGS+=	-Wformat-security
+CDIAGFLAGS+=	-Wimplicit
+CDIAGFLAGS+=	-Winline
+CDIAGFLAGS+=	-Wmissing-declarations
+CDIAGFLAGS+=	-Wmissing-prototypes
+CDIAGFLAGS+=	-Wparentheses
+CDIAGFLAGS+=	-Wpointer-arith
+CDIAGFLAGS+=	-Wreturn-type
+CDIAGFLAGS+=	-Wshadow
+CDIAGFLAGS+=	-Wsign-compare
+CDIAGFLAGS+=	-Wstrict-aliasing
+CDIAGFLAGS+=	-Wstrict-prototypes
+CDIAGFLAGS+=	-Wswitch
+CDIAGFLAGS+=	-Wtrigraphs
+CDIAGFLAGS+=	-Wuninitialized
+CDIAGFLAGS+=	-Wunused
+CDIAGFLAGS+=	-Wno-unused-parameter
+.if ${COMPILER_VERSION:L} != "gcc3"
+CDIAGFLAGS+=	-Wold-style-definition
+.endif
+
+CFLAGS+=-I${.CURDIR}/${SSHREL}
+
+.if (${OPENSSL:L} == "yes")
+LDADD+= -lcrypto
+DPADD+= ${LIBCRYPTO}
+.endif
+
+LDADD+= -lfido2 -lcbor
+DPADD+= ${LIBFIDO2} ${LIBCBOR}
+
+LDADD+= -lutil
+DPADD+= ${LIBUTIL}
+
+.include <bsd.prog.mk>
+
diff --git a/regress/misc/ssh-verify-attestation/ssh-verify-attestation.c b/regress/misc/ssh-verify-attestation/ssh-verify-attestation.c
new file mode 100644
index 00000000..4cada356
--- /dev/null
+++ b/regress/misc/ssh-verify-attestation/ssh-verify-attestation.c
@@ -0,0 +1,368 @@
+/* $OpenBSD: ssh-verify-attestation.c,v 1.1 2024/12/04 16:42:49 djm Exp $ */
+/*
+ * Copyright (c) 2022-2024 Damien Miller
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is a small program to verify FIDO attestation objects that
+ * ssh-keygen(1) can record when enrolling a FIDO key. It requires that
+ * the attestation object and challenge used when creating the key be
+ * recorded.
+ *
+ * Example usage:
+ *
+ * $ # Generate a random challenge.
+ * $ dd if=/dev/urandom of=key_ecdsa_sk.challenge bs=32 count=1
+ * $ # Generate a key, record the attestation blob.
+ * $ ssh-keygen -f key_ecdsa_sk -t ecdsa-sk \
+ *       -Ochallenge=key_ecdsa_sk.challenge \
+ *       -Owrite-attestation=key_ecdsa_sk.attest -N ''
+ * $ # Validate the challenge (-A = print attestation CA cert)
+ * $ ./obj/ssh-verify-attestation -A key_ecdsa_sk key_ecdsa_sk.challenge \
+ *       key_ecdsa_sk.attest
+ *
+ * Limitations/TODO:
+ *
+ * 1) It doesn't automatically detect the attestation statement format. It
+ *    assumes the "packed" format used by FIDO2 keys. If that doesn't work,
+ *    then try using the -U option to select the "fido-u2f" format.
+ * 2) Only ECDSA keys are currently supported. Ed25519 support is not yet
+ *    implemented.
+ * 3) Probably bugs.
+ *
+ */
+
+#include <stdint.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+#include "xmalloc.h"
+#include "log.h"
+#include "sshbuf.h"
+#include "sshkey.h"
+#include "authfile.h"
+#include "ssherr.h"
+#include "misc.h"
+#include "digest.h"
+
+#include <fido.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/bio.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+
+extern char *__progname;
+
+#define ATTEST_MAGIC	"ssh-sk-attest-v01"
+
+static int
+prepare_fido_cred(fido_cred_t *cred, int credtype, const char *attfmt,
+    const char *rp_id, struct sshbuf *b, const struct sshbuf *challenge,
+    struct sshbuf **attestation_certp)
+{
+	struct sshbuf *attestation_cert = NULL, *sig = NULL, *authdata = NULL;
+	char *magic = NULL;
+	int r = SSH_ERR_INTERNAL_ERROR;
+
+	*attestation_certp = NULL;
+
+	/* Make sure it's the format we're expecting */
+	if ((r = sshbuf_get_cstring(b, &magic, NULL)) != 0) {
+		error_fr(r, "parse header");
+		goto out;
+	}
+	if (strcmp(magic, ATTEST_MAGIC) != 0) {
+		error_f("unsupported format");
+		r = SSH_ERR_INVALID_FORMAT;
+		goto out;
+	}
+	/* Parse the remaining fields */
+	if ((r = sshbuf_froms(b, &attestation_cert)) != 0 ||
+	    (r = sshbuf_froms(b, &sig)) != 0 ||
+	    (r = sshbuf_froms(b, &authdata)) != 0 ||
+	    (r = sshbuf_get_u32(b, NULL)) != 0 || /* reserved flags */
+	    (r = sshbuf_get_string_direct(b, NULL, NULL)) != 0) { /* reserved */
+		error_fr(r, "parse body");
+		goto out;
+	}
+	debug3_f("blob len=%zu, attestation cert len=%zu, sig len=%zu, "
+	    "authdata len=%zu challenge len=%zu", sshbuf_len(attestation_cert),
+	    sshbuf_len(b), sshbuf_len(sig), sshbuf_len(authdata),
+	    sshbuf_len(challenge));
+
+	fido_cred_set_type(cred, COSE_ES256);
+	fido_cred_set_fmt(cred, attfmt);
+	fido_cred_set_clientdata(cred, sshbuf_ptr(challenge),
+	    sshbuf_len(challenge));
+	fido_cred_set_rp(cred, rp_id, NULL);
+	fido_cred_set_authdata(cred, sshbuf_ptr(authdata),
+	    sshbuf_len(authdata));
+	/* XXX set_extensions, set_rk, set_uv */
+	fido_cred_set_x509(cred, sshbuf_ptr(attestation_cert),
+	    sshbuf_len(attestation_cert));
+	fido_cred_set_sig(cred, sshbuf_ptr(sig), sshbuf_len(sig));
+
+	/* success */
+	*attestation_certp = attestation_cert;
+	attestation_cert = NULL;
+	r = 0;
+ out:
+	free(magic);
+	sshbuf_free(attestation_cert);
+	sshbuf_free(sig);
+	sshbuf_free(authdata);
+	return r;
+}
+
+static uint8_t *
+get_pubkey_from_cred_ecdsa(const fido_cred_t *cred, size_t *pubkey_len)
+{
+	const uint8_t *ptr;
+	uint8_t *pubkey = NULL, *ret = NULL;
+	BIGNUM *x = NULL, *y = NULL;
+	EC_POINT *q = NULL;
+	EC_GROUP *g = NULL;
+
+	if ((x = BN_new()) == NULL ||
+	    (y = BN_new()) == NULL ||
+	    (g = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)) == NULL ||
+	    (q = EC_POINT_new(g)) == NULL) {
+		error_f("libcrypto setup failed");
+		goto out;
+	}
+	if ((ptr = fido_cred_pubkey_ptr(cred)) == NULL) {
+		error_f("fido_cred_pubkey_ptr failed");
+		goto out;
+	}
+	if (fido_cred_pubkey_len(cred) != 64) {
+		error_f("bad fido_cred_pubkey_len %zu",
+		    fido_cred_pubkey_len(cred));
+		goto out;
+	}
+
+	if (BN_bin2bn(ptr, 32, x) == NULL ||
+	    BN_bin2bn(ptr + 32, 32, y) == NULL) {
+		error_f("BN_bin2bn failed");
+		goto out;
+	}
+	if (EC_POINT_set_affine_coordinates_GFp(g, q, x, y, NULL) != 1) {
+		error_f("EC_POINT_set_affine_coordinates_GFp failed");
+		goto out;
+	}
+	*pubkey_len = EC_POINT_point2oct(g, q,
+	    POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL);
+	if (*pubkey_len == 0 || *pubkey_len > 2048) {
+		error_f("bad pubkey length %zu", *pubkey_len);
+		goto out;
+	}
+	if ((pubkey = malloc(*pubkey_len)) == NULL) {
+		error_f("malloc pubkey failed");
+		goto out;
+	}
+	if (EC_POINT_point2oct(g, q, POINT_CONVERSION_UNCOMPRESSED,
+	    pubkey, *pubkey_len, NULL) == 0) {
+		error_f("EC_POINT_point2oct failed");
+		goto out;
+	}
+	/* success */
+	ret = pubkey;
+	pubkey = NULL;
+ out:
+	free(pubkey);
+	EC_POINT_free(q);
+	EC_GROUP_free(g);
+	BN_clear_free(x);
+	BN_clear_free(y);
+	return ret;
+}
+
+/* copied from sshsk_ecdsa_assemble() */
+static int
+cred_matches_key_ecdsa(const fido_cred_t *cred, const struct sshkey *k)
+{
+	struct sshkey *key = NULL;
+	struct sshbuf *b = NULL;
+	EC_KEY *ec = NULL;
+	uint8_t *pubkey = NULL;
+	size_t pubkey_len;
+	int r;
+
+	if ((key = sshkey_new(KEY_ECDSA_SK)) == NULL) {
+		error_f("sshkey_new failed");
+		r = SSH_ERR_ALLOC_FAIL;
+		goto out;
+	}
+	key->ecdsa_nid = NID_X9_62_prime256v1;
+	if ((key->pkey = EVP_PKEY_new()) == NULL ||
+	    (ec = EC_KEY_new_by_curve_name(key->ecdsa_nid)) == NULL ||
+	    (b = sshbuf_new()) == NULL) {
+		error_f("allocation failed");
+		r = SSH_ERR_ALLOC_FAIL;
+		goto out;
+	}
+	if ((pubkey = get_pubkey_from_cred_ecdsa(cred, &pubkey_len)) == NULL) {
+		error_f("get_pubkey_from_cred_ecdsa failed");
+		r = SSH_ERR_INVALID_FORMAT;
+		goto out;
+	}
+	if ((r = sshbuf_put_string(b, pubkey, pubkey_len)) != 0) {
+		error_fr(r, "sshbuf_put_string");
+		goto out;
+	}
+	if ((r = sshbuf_get_eckey(b, ec)) != 0) {
+		error_fr(r, "parse");
+		r = SSH_ERR_INVALID_FORMAT;
+		goto out;
+	}
+	if (sshkey_ec_validate_public(EC_KEY_get0_group(ec),
+	    EC_KEY_get0_public_key(ec)) != 0) {
+		error("Authenticator returned invalid ECDSA key");
+		r = SSH_ERR_KEY_INVALID_EC_VALUE;
+		goto out;
+	}
+	if (EVP_PKEY_set1_EC_KEY(key->pkey, ec) != 1) {
+		/* XXX assume it is a allocation error */
+		error_f("allocation failed");
+		r = SSH_ERR_ALLOC_FAIL;
+		goto out;
+	}
+	key->sk_application = xstrdup(k->sk_application); /* XXX */
+	if (!sshkey_equal_public(key, k)) {
+		error("sshkey_equal_public failed");
+		r = SSH_ERR_INVALID_ARGUMENT;
+		goto out;
+	}
+	r = 0; /* success */
+ out:
+	EC_KEY_free(ec);
+	free(pubkey);
+	sshkey_free(key);
+	sshbuf_free(b);
+	return r;
+}
+
+static int
+cred_matches_key(const fido_cred_t *cred, const struct sshkey *k)
+{
+	switch (sshkey_type_plain(k->type)) {
+	case KEY_ECDSA_SK:
+		return cred_matches_key_ecdsa(cred, k);
+	default:
+		error_f("key type %s not supported", sshkey_type(k));
+		return -1;
+	}
+}
+
+int
+main(int argc, char **argv)
+{
+	LogLevel log_level = SYSLOG_LEVEL_INFO;
+	int r, ch, credtype = -1;
+	struct sshkey *k = NULL;
+	struct sshbuf *attestation = NULL, *challenge = NULL;
+	struct sshbuf *attestation_cert = NULL;
+	char *fp;
+	const char *attfmt = "packed";
+	fido_cred_t *cred = NULL;
+	int write_attestation_cert = 0;
+	extern int optind;
+	/* extern char *optarg; */
+
+	ERR_load_crypto_strings();
+
+	sanitise_stdfd();
+	log_init(__progname, log_level, SYSLOG_FACILITY_AUTH, 1);
+
+	while ((ch = getopt(argc, argv, "UAv")) != -1) {
+		switch (ch) {
+		case 'U':
+			attfmt = "fido-u2f";
+			break;
+		case 'A':
+			write_attestation_cert = 1;
+			break;
+		case 'v':
+			if (log_level == SYSLOG_LEVEL_ERROR)
+				log_level = SYSLOG_LEVEL_DEBUG1;
+			else if (log_level < SYSLOG_LEVEL_DEBUG3)
+				log_level++;
+			break;
+		default:
+			goto usage;
+		}
+	}
+	log_init(__progname, log_level, SYSLOG_FACILITY_AUTH, 1);
+	argv += optind;
+	argc -= optind;
+
+	if (argc < 3) {
+ usage:
+		fprintf(stderr, "usage: %s [-vAU] "
+		   "pubkey challenge attestation-blob\n", __progname);
+		exit(1);
+	}
+	if ((r = sshkey_load_public(argv[0], &k, NULL)) != 0)
+		fatal_r(r, "load key %s", argv[0]);
+	if ((fp = sshkey_fingerprint(k, SSH_FP_HASH_DEFAULT,
+	    SSH_FP_DEFAULT)) == NULL)
+		fatal("sshkey_fingerprint failed");
+	debug2("key %s: %s %s", argv[2], sshkey_type(k), fp);
+	free(fp);
+	if ((r = sshbuf_load_file(argv[1], &challenge)) != 0)
+		fatal_r(r, "load challenge %s", argv[1]);
+	if ((r = sshbuf_load_file(argv[2], &attestation)) != 0)
+		fatal_r(r, "load attestation %s", argv[2]);
+	if ((cred = fido_cred_new()) == NULL)
+		fatal("fido_cred_new failed");
+
+	switch (sshkey_type_plain(k->type)) {
+	case KEY_ECDSA_SK:
+		credtype = COSE_ES256;
+		break;
+	default:
+		fatal("unsupported key type %s", sshkey_type(k));
+	}
+
+	if ((r = prepare_fido_cred(cred, credtype, attfmt, k->sk_application,
+	    attestation, challenge, &attestation_cert)) != 0)
+		fatal_r(r, "prepare_fido_cred %s", argv[2]);
+	if (fido_cred_x5c_ptr(cred) != NULL) {
+		debug("basic attestation");
+		r = fido_cred_verify(cred);
+	} else {
+		debug("self attestation");
+		r = fido_cred_verify_self(cred);
+	}
+	if (r != FIDO_OK)
+		fatal("verification of attestation data failed");
+	if (cred_matches_key(cred, k) != 0)
+		fatal("cred authdata does not match key");
+
+	fido_cred_free(&cred);
+
+	if (write_attestation_cert) {
+		PEM_write(stdout, "CERTIFICATE", NULL,
+		    sshbuf_ptr(attestation_cert), sshbuf_len(attestation_cert));
+	}
+	sshbuf_free(attestation_cert);
+
+	logit("%s: GOOD", argv[2]);
+
+	return (0);
+}

-- 
To stop receiving notification emails like this one, please contact
djm at mindrot.org.


More information about the openssh-commits mailing list