summaryrefslogtreecommitdiff
path: root/ssh-ecdsa-sk.c
diff options
context:
space:
mode:
Diffstat (limited to 'ssh-ecdsa-sk.c')
-rw-r--r--ssh-ecdsa-sk.c169
1 files changed, 142 insertions, 27 deletions
diff --git a/ssh-ecdsa-sk.c b/ssh-ecdsa-sk.c
index 981d60d74..c6927ecb2 100644
--- a/ssh-ecdsa-sk.c
+++ b/ssh-ecdsa-sk.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: ssh-ecdsa-sk.c,v 1.5 2019/11/26 03:04:27 djm Exp $ */ 1/* $OpenBSD: ssh-ecdsa-sk.c,v 1.8 2020/06/22 23:44:27 djm Exp $ */
2/* 2/*
3 * Copyright (c) 2000 Markus Friedl. All rights reserved. 3 * Copyright (c) 2000 Markus Friedl. All rights reserved.
4 * Copyright (c) 2010 Damien Miller. All rights reserved. 4 * Copyright (c) 2010 Damien Miller. All rights reserved.
@@ -49,6 +49,94 @@
49#define SSHKEY_INTERNAL 49#define SSHKEY_INTERNAL
50#include "sshkey.h" 50#include "sshkey.h"
51 51
52#ifndef OPENSSL_HAS_ECC
53/* ARGSUSED */
54int
55ssh_ecdsa_sk_verify(const struct sshkey *key,
56 const u_char *signature, size_t signaturelen,
57 const u_char *data, size_t datalen, u_int compat,
58 struct sshkey_sig_details **detailsp)
59{
60 return SSH_ERR_FEATURE_UNSUPPORTED;
61}
62#else /* OPENSSL_HAS_ECC */
63
64/*
65 * Check FIDO/W3C webauthn signatures clientData field against the expected
66 * format and prepare a hash of it for use in signature verification.
67 *
68 * webauthn signatures do not sign the hash of the message directly, but
69 * instead sign a JSON-like "clientData" wrapper structure that contains the
70 * message hash along with a other information.
71 *
72 * Fortunately this structure has a fixed format so it is possible to verify
73 * that the hash of the signed message is present within the clientData
74 * structure without needing to implement any JSON parsing.
75 */
76static int
77webauthn_check_prepare_hash(const u_char *data, size_t datalen,
78 const char *origin, const struct sshbuf *wrapper,
79 uint8_t flags, const struct sshbuf *extensions,
80 u_char *msghash, size_t msghashlen)
81{
82 int r = SSH_ERR_INTERNAL_ERROR;
83 struct sshbuf *chall = NULL, *m = NULL;
84
85 if ((m = sshbuf_new()) == NULL ||
86 (chall = sshbuf_from(data, datalen)) == NULL) {
87 r = SSH_ERR_ALLOC_FAIL;
88 goto out;
89 }
90 /*
91 * Ensure origin contains no quote character and that the flags are
92 * consistent with what we received
93 */
94 if (strchr(origin, '\"') != NULL ||
95 (flags & 0x40) != 0 /* AD */ ||
96 ((flags & 0x80) == 0 /* ED */) != (sshbuf_len(extensions) == 0)) {
97 r = SSH_ERR_INVALID_FORMAT;
98 goto out;
99 }
100
101 /*
102 * Prepare the preamble to clientData that we expect, poking the
103 * challenge and origin into their canonical positions in the
104 * structure. The crossOrigin flag and any additional extension
105 * fields present are ignored.
106 */
107#define WEBAUTHN_0 "{\"type\":\"webauthn.get\",\"challenge\":\""
108#define WEBAUTHN_1 "\",\"origin\":\""
109#define WEBAUTHN_2 "\""
110 if ((r = sshbuf_put(m, WEBAUTHN_0, sizeof(WEBAUTHN_0) - 1)) != 0 ||
111 (r = sshbuf_dtourlb64(chall, m, 0)) != 0 ||
112 (r = sshbuf_put(m, WEBAUTHN_1, sizeof(WEBAUTHN_1) - 1)) != 0 ||
113 (r = sshbuf_put(m, origin, strlen(origin))) != 0 ||
114 (r = sshbuf_put(m, WEBAUTHN_2, sizeof(WEBAUTHN_2) - 1)) != 0)
115 goto out;
116#ifdef DEBUG_SK
117 fprintf(stderr, "%s: received origin: %s\n", __func__, origin);
118 fprintf(stderr, "%s: received clientData:\n", __func__);
119 sshbuf_dump(wrapper, stderr);
120 fprintf(stderr, "%s: expected clientData premable:\n", __func__);
121 sshbuf_dump(m, stderr);
122#endif
123 /* Check that the supplied clientData has the preamble we expect */
124 if ((r = sshbuf_cmp(wrapper, 0, sshbuf_ptr(m), sshbuf_len(m))) != 0)
125 goto out;
126
127 /* Prepare hash of clientData */
128 if ((r = ssh_digest_buffer(SSH_DIGEST_SHA256, wrapper,
129 msghash, msghashlen)) != 0)
130 goto out;
131
132 /* success */
133 r = 0;
134 out:
135 sshbuf_free(chall);
136 sshbuf_free(m);
137 return r;
138}
139
52/* ARGSUSED */ 140/* ARGSUSED */
53int 141int
54ssh_ecdsa_sk_verify(const struct sshkey *key, 142ssh_ecdsa_sk_verify(const struct sshkey *key,
@@ -56,15 +144,15 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
56 const u_char *data, size_t datalen, u_int compat, 144 const u_char *data, size_t datalen, u_int compat,
57 struct sshkey_sig_details **detailsp) 145 struct sshkey_sig_details **detailsp)
58{ 146{
59#ifdef OPENSSL_HAS_ECC
60 ECDSA_SIG *sig = NULL; 147 ECDSA_SIG *sig = NULL;
61 BIGNUM *sig_r = NULL, *sig_s = NULL; 148 BIGNUM *sig_r = NULL, *sig_s = NULL;
62 u_char sig_flags; 149 u_char sig_flags;
63 u_char msghash[32], apphash[32], sighash[32]; 150 u_char msghash[32], apphash[32], sighash[32];
64 u_int sig_counter; 151 u_int sig_counter;
65 int ret = SSH_ERR_INTERNAL_ERROR; 152 int is_webauthn = 0, ret = SSH_ERR_INTERNAL_ERROR;
66 struct sshbuf *b = NULL, *sigbuf = NULL, *original_signed = NULL; 153 struct sshbuf *b = NULL, *sigbuf = NULL, *original_signed = NULL;
67 char *ktype = NULL; 154 struct sshbuf *webauthn_wrapper = NULL, *webauthn_exts = NULL;
155 char *ktype = NULL, *webauthn_origin = NULL;
68 struct sshkey_sig_details *details = NULL; 156 struct sshkey_sig_details *details = NULL;
69#ifdef DEBUG_SK 157#ifdef DEBUG_SK
70 char *tmp = NULL; 158 char *tmp = NULL;
@@ -83,16 +171,33 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
83 /* fetch signature */ 171 /* fetch signature */
84 if ((b = sshbuf_from(signature, signaturelen)) == NULL) 172 if ((b = sshbuf_from(signature, signaturelen)) == NULL)
85 return SSH_ERR_ALLOC_FAIL; 173 return SSH_ERR_ALLOC_FAIL;
86 if (sshbuf_get_cstring(b, &ktype, NULL) != 0 || 174 if ((details = calloc(1, sizeof(*details))) == NULL) {
87 sshbuf_froms(b, &sigbuf) != 0 || 175 ret = SSH_ERR_ALLOC_FAIL;
176 goto out;
177 }
178 if (sshbuf_get_cstring(b, &ktype, NULL) != 0) {
179 ret = SSH_ERR_INVALID_FORMAT;
180 goto out;
181 }
182 if (strcmp(ktype, "webauthn-sk-ecdsa-sha2-nistp256@openssh.com") == 0)
183 is_webauthn = 1;
184 else if (strcmp(ktype, "sk-ecdsa-sha2-nistp256@openssh.com") != 0) {
185 ret = SSH_ERR_INVALID_FORMAT;
186 goto out;
187 }
188 if (sshbuf_froms(b, &sigbuf) != 0 ||
88 sshbuf_get_u8(b, &sig_flags) != 0 || 189 sshbuf_get_u8(b, &sig_flags) != 0 ||
89 sshbuf_get_u32(b, &sig_counter) != 0) { 190 sshbuf_get_u32(b, &sig_counter) != 0) {
90 ret = SSH_ERR_INVALID_FORMAT; 191 ret = SSH_ERR_INVALID_FORMAT;
91 goto out; 192 goto out;
92 } 193 }
93 if (strcmp(sshkey_ssh_name_plain(key), ktype) != 0) { 194 if (is_webauthn) {
94 ret = SSH_ERR_KEY_TYPE_MISMATCH; 195 if (sshbuf_get_cstring(b, &webauthn_origin, NULL) != 0 ||
95 goto out; 196 sshbuf_froms(b, &webauthn_wrapper) != 0 ||
197 sshbuf_froms(b, &webauthn_exts) != 0) {
198 ret = SSH_ERR_INVALID_FORMAT;
199 goto out;
200 }
96 } 201 }
97 if (sshbuf_len(b) != 0) { 202 if (sshbuf_len(b) != 0) {
98 ret = SSH_ERR_UNEXPECTED_TRAILING_DATA; 203 ret = SSH_ERR_UNEXPECTED_TRAILING_DATA;
@@ -105,14 +210,11 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
105 ret = SSH_ERR_INVALID_FORMAT; 210 ret = SSH_ERR_INVALID_FORMAT;
106 goto out; 211 goto out;
107 } 212 }
108 if ((sig = ECDSA_SIG_new()) == NULL) { 213 if (sshbuf_len(sigbuf) != 0) {
109 ret = SSH_ERR_ALLOC_FAIL; 214 ret = SSH_ERR_UNEXPECTED_TRAILING_DATA;
110 goto out;
111 }
112 if (!ECDSA_SIG_set0(sig, sig_r, sig_s)) {
113 ret = SSH_ERR_LIBCRYPTO_ERROR;
114 goto out; 215 goto out;
115 } 216 }
217
116#ifdef DEBUG_SK 218#ifdef DEBUG_SK
117 fprintf(stderr, "%s: data: (len %zu)\n", __func__, datalen); 219 fprintf(stderr, "%s: data: (len %zu)\n", __func__, datalen);
118 /* sshbuf_dump_data(data, datalen, stderr); */ 220 /* sshbuf_dump_data(data, datalen, stderr); */
@@ -122,20 +224,34 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
122 free(tmp); 224 free(tmp);
123 fprintf(stderr, "%s: sig_flags = 0x%02x, sig_counter = %u\n", 225 fprintf(stderr, "%s: sig_flags = 0x%02x, sig_counter = %u\n",
124 __func__, sig_flags, sig_counter); 226 __func__, sig_flags, sig_counter);
227 if (is_webauthn) {
228 fprintf(stderr, "%s: webauthn origin: %s\n", __func__,
229 webauthn_origin);
230 fprintf(stderr, "%s: webauthn_wrapper:\n", __func__);
231 sshbuf_dump(webauthn_wrapper, stderr);
232 }
125#endif 233#endif
126 sig_r = sig_s = NULL; /* transferred */ 234 if ((sig = ECDSA_SIG_new()) == NULL) {
127 235 ret = SSH_ERR_ALLOC_FAIL;
128 if (sshbuf_len(sigbuf) != 0) {
129 ret = SSH_ERR_UNEXPECTED_TRAILING_DATA;
130 goto out; 236 goto out;
131 } 237 }
238 if (!ECDSA_SIG_set0(sig, sig_r, sig_s)) {
239 ret = SSH_ERR_LIBCRYPTO_ERROR;
240 goto out;
241 }
242 sig_r = sig_s = NULL; /* transferred */
132 243
133 /* Reconstruct data that was supposedly signed */ 244 /* Reconstruct data that was supposedly signed */
134 if ((original_signed = sshbuf_new()) == NULL) { 245 if ((original_signed = sshbuf_new()) == NULL) {
135 ret = SSH_ERR_ALLOC_FAIL; 246 ret = SSH_ERR_ALLOC_FAIL;
136 goto out; 247 goto out;
137 } 248 }
138 if ((ret = ssh_digest_memory(SSH_DIGEST_SHA256, data, datalen, 249 if (is_webauthn) {
250 if ((ret = webauthn_check_prepare_hash(data, datalen,
251 webauthn_origin, webauthn_wrapper, sig_flags, webauthn_exts,
252 msghash, sizeof(msghash))) != 0)
253 goto out;
254 } else if ((ret = ssh_digest_memory(SSH_DIGEST_SHA256, data, datalen,
139 msghash, sizeof(msghash))) != 0) 255 msghash, sizeof(msghash))) != 0)
140 goto out; 256 goto out;
141 /* Application value is hashed before signature */ 257 /* Application value is hashed before signature */
@@ -152,16 +268,13 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
152 apphash, sizeof(apphash))) != 0 || 268 apphash, sizeof(apphash))) != 0 ||
153 (ret = sshbuf_put_u8(original_signed, sig_flags)) != 0 || 269 (ret = sshbuf_put_u8(original_signed, sig_flags)) != 0 ||
154 (ret = sshbuf_put_u32(original_signed, sig_counter)) != 0 || 270 (ret = sshbuf_put_u32(original_signed, sig_counter)) != 0 ||
271 (ret = sshbuf_putb(original_signed, webauthn_exts)) != 0 ||
155 (ret = sshbuf_put(original_signed, msghash, sizeof(msghash))) != 0) 272 (ret = sshbuf_put(original_signed, msghash, sizeof(msghash))) != 0)
156 goto out; 273 goto out;
157 /* Signature is over H(original_signed) */ 274 /* Signature is over H(original_signed) */
158 if ((ret = ssh_digest_buffer(SSH_DIGEST_SHA256, original_signed, 275 if ((ret = ssh_digest_buffer(SSH_DIGEST_SHA256, original_signed,
159 sighash, sizeof(sighash))) != 0) 276 sighash, sizeof(sighash))) != 0)
160 goto out; 277 goto out;
161 if ((details = calloc(1, sizeof(*details))) == NULL) {
162 ret = SSH_ERR_ALLOC_FAIL;
163 goto out;
164 }
165 details->sk_counter = sig_counter; 278 details->sk_counter = sig_counter;
166 details->sk_flags = sig_flags; 279 details->sk_flags = sig_flags;
167#ifdef DEBUG_SK 280#ifdef DEBUG_SK
@@ -195,6 +308,9 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
195 explicit_bzero(sighash, sizeof(msghash)); 308 explicit_bzero(sighash, sizeof(msghash));
196 explicit_bzero(apphash, sizeof(apphash)); 309 explicit_bzero(apphash, sizeof(apphash));
197 sshkey_sig_details_free(details); 310 sshkey_sig_details_free(details);
311 sshbuf_free(webauthn_wrapper);
312 sshbuf_free(webauthn_exts);
313 free(webauthn_origin);
198 sshbuf_free(original_signed); 314 sshbuf_free(original_signed);
199 sshbuf_free(sigbuf); 315 sshbuf_free(sigbuf);
200 sshbuf_free(b); 316 sshbuf_free(b);
@@ -203,7 +319,6 @@ ssh_ecdsa_sk_verify(const struct sshkey *key,
203 BN_clear_free(sig_s); 319 BN_clear_free(sig_s);
204 free(ktype); 320 free(ktype);
205 return ret; 321 return ret;
206#else
207 return SSH_ERR_INTERNAL_ERROR;
208#endif
209} 322}
323
324#endif /* OPENSSL_HAS_ECC */