diff options
Diffstat (limited to 'ssh-ecdsa-sk.c')
-rw-r--r-- | ssh-ecdsa-sk.c | 169 |
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 */ | ||
54 | int | ||
55 | ssh_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 | */ | ||
76 | static int | ||
77 | webauthn_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 */ |
53 | int | 141 | int |
54 | ssh_ecdsa_sk_verify(const struct sshkey *key, | 142 | ssh_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 */ | ||