1 /* $OpenBSD: ssh-ecdsa-sk.c,v 1.7 2020/06/22 05:58:35 djm Exp $ */ 2 /* 3 * Copyright (c) 2000 Markus Friedl. All rights reserved. 4 * Copyright (c) 2010 Damien Miller. All rights reserved. 5 * Copyright (c) 2019 Google Inc. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 /* #define DEBUG_SK 1 */ 29 30 #include "includes.h" 31 32 #include <sys/types.h> 33 34 #ifdef WITH_OPENSSL 35 #include <openssl/bn.h> 36 #include <openssl/ec.h> 37 #include <openssl/ecdsa.h> 38 #include <openssl/evp.h> 39 #endif 40 41 #include <string.h> 42 #include <stdio.h> /* needed for DEBUG_SK only */ 43 44 #include "openbsd-compat/openssl-compat.h" 45 46 #include "sshbuf.h" 47 #include "ssherr.h" 48 #include "digest.h" 49 #define SSHKEY_INTERNAL 50 #include "sshkey.h" 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 #define WEBAUTHN_0 "{\"type\":\"webauthn.get\",\"challenge\":\"" 101 #define WEBAUTHN_1 "\",\"origin\":\"" 102 #define WEBAUTHN_2 "\"" 103 if ((r = sshbuf_put(m, WEBAUTHN_0, sizeof(WEBAUTHN_0) - 1)) != 0 || 104 (r = sshbuf_dtourlb64(chall, m, 0)) != 0 || 105 (r = sshbuf_put(m, WEBAUTHN_1, sizeof(WEBAUTHN_1) - 1)) != 0 || 106 (r = sshbuf_put(m, origin, strlen(origin))) != 0 || 107 (r = sshbuf_put(m, WEBAUTHN_2, sizeof(WEBAUTHN_2) - 1)) != 0) 108 goto out; 109 #ifdef DEBUG_SK 110 fprintf(stderr, "%s: received origin: %s\n", __func__, origin); 111 fprintf(stderr, "%s: received clientData:\n", __func__); 112 sshbuf_dump(wrapper, stderr); 113 fprintf(stderr, "%s: expected clientData premable:\n", __func__); 114 sshbuf_dump(m, stderr); 115 #endif 116 /* Check that the supplied clientData matches what we expect */ 117 if ((r = sshbuf_cmp(wrapper, 0, sshbuf_ptr(m), sshbuf_len(m))) != 0) 118 goto out; 119 120 /* Prepare hash of clientData */ 121 if ((r = ssh_digest_buffer(SSH_DIGEST_SHA256, wrapper, 122 msghash, msghashlen)) != 0) 123 goto out; 124 125 /* success */ 126 r = 0; 127 out: 128 sshbuf_free(chall); 129 sshbuf_free(m); 130 return r; 131 } 132 133 /* ARGSUSED */ 134 int 135 ssh_ecdsa_sk_verify(const struct sshkey *key, 136 const u_char *signature, size_t signaturelen, 137 const u_char *data, size_t datalen, u_int compat, 138 struct sshkey_sig_details **detailsp) 139 { 140 ECDSA_SIG *sig = NULL; 141 BIGNUM *sig_r = NULL, *sig_s = NULL; 142 u_char sig_flags; 143 u_char msghash[32], apphash[32], sighash[32]; 144 u_int sig_counter; 145 int is_webauthn = 0, ret = SSH_ERR_INTERNAL_ERROR; 146 struct sshbuf *b = NULL, *sigbuf = NULL, *original_signed = NULL; 147 struct sshbuf *webauthn_wrapper = NULL, *webauthn_exts = NULL; 148 char *ktype = NULL, *webauthn_origin = NULL; 149 struct sshkey_sig_details *details = NULL; 150 #ifdef DEBUG_SK 151 char *tmp = NULL; 152 #endif 153 154 if (detailsp != NULL) 155 *detailsp = NULL; 156 if (key == NULL || key->ecdsa == NULL || 157 sshkey_type_plain(key->type) != KEY_ECDSA_SK || 158 signature == NULL || signaturelen == 0) 159 return SSH_ERR_INVALID_ARGUMENT; 160 161 if (key->ecdsa_nid != NID_X9_62_prime256v1) 162 return SSH_ERR_INTERNAL_ERROR; 163 164 /* fetch signature */ 165 if ((b = sshbuf_from(signature, signaturelen)) == NULL) 166 return SSH_ERR_ALLOC_FAIL; 167 if ((details = calloc(1, sizeof(*details))) == NULL) { 168 ret = SSH_ERR_ALLOC_FAIL; 169 goto out; 170 } 171 if (sshbuf_get_cstring(b, &ktype, NULL) != 0) { 172 ret = SSH_ERR_INVALID_FORMAT; 173 goto out; 174 } 175 if (strcmp(ktype, "webauthn-sk-ecdsa-sha2-nistp256@openssh.com") == 0) 176 is_webauthn = 1; 177 else if (strcmp(ktype, "sk-ecdsa-sha2-nistp256@openssh.com") != 0) { 178 ret = SSH_ERR_INVALID_FORMAT; 179 goto out; 180 } 181 if (sshbuf_froms(b, &sigbuf) != 0 || 182 sshbuf_get_u8(b, &sig_flags) != 0 || 183 sshbuf_get_u32(b, &sig_counter) != 0) { 184 ret = SSH_ERR_INVALID_FORMAT; 185 goto out; 186 } 187 if (is_webauthn) { 188 if (sshbuf_get_cstring(b, &webauthn_origin, NULL) != 0 || 189 sshbuf_froms(b, &webauthn_wrapper) != 0 || 190 sshbuf_froms(b, &webauthn_exts) != 0) { 191 ret = SSH_ERR_INVALID_FORMAT; 192 goto out; 193 } 194 } 195 if (sshbuf_len(b) != 0) { 196 ret = SSH_ERR_UNEXPECTED_TRAILING_DATA; 197 goto out; 198 } 199 200 /* parse signature */ 201 if (sshbuf_get_bignum2(sigbuf, &sig_r) != 0 || 202 sshbuf_get_bignum2(sigbuf, &sig_s) != 0) { 203 ret = SSH_ERR_INVALID_FORMAT; 204 goto out; 205 } 206 if (sshbuf_len(sigbuf) != 0) { 207 ret = SSH_ERR_UNEXPECTED_TRAILING_DATA; 208 goto out; 209 } 210 211 #ifdef DEBUG_SK 212 fprintf(stderr, "%s: data: (len %zu)\n", __func__, datalen); 213 /* sshbuf_dump_data(data, datalen, stderr); */ 214 fprintf(stderr, "%s: sig_r: %s\n", __func__, (tmp = BN_bn2hex(sig_r))); 215 free(tmp); 216 fprintf(stderr, "%s: sig_s: %s\n", __func__, (tmp = BN_bn2hex(sig_s))); 217 free(tmp); 218 fprintf(stderr, "%s: sig_flags = 0x%02x, sig_counter = %u\n", 219 __func__, sig_flags, sig_counter); 220 if (is_webauthn) { 221 fprintf(stderr, "%s: webauthn origin: %s\n", __func__, 222 webauthn_origin); 223 fprintf(stderr, "%s: webauthn_wrapper:\n", __func__); 224 sshbuf_dump(webauthn_wrapper, stderr); 225 } 226 #endif 227 if ((sig = ECDSA_SIG_new()) == NULL) { 228 ret = SSH_ERR_ALLOC_FAIL; 229 goto out; 230 } 231 if (!ECDSA_SIG_set0(sig, sig_r, sig_s)) { 232 ret = SSH_ERR_LIBCRYPTO_ERROR; 233 goto out; 234 } 235 sig_r = sig_s = NULL; /* transferred */ 236 237 /* Reconstruct data that was supposedly signed */ 238 if ((original_signed = sshbuf_new()) == NULL) { 239 ret = SSH_ERR_ALLOC_FAIL; 240 goto out; 241 } 242 if (is_webauthn) { 243 if ((ret = webauthn_check_prepare_hash(data, datalen, 244 webauthn_origin, webauthn_wrapper, sig_flags, webauthn_exts, 245 msghash, sizeof(msghash))) != 0) 246 goto out; 247 } else if ((ret = ssh_digest_memory(SSH_DIGEST_SHA256, data, datalen, 248 msghash, sizeof(msghash))) != 0) 249 goto out; 250 /* Application value is hashed before signature */ 251 if ((ret = ssh_digest_memory(SSH_DIGEST_SHA256, key->sk_application, 252 strlen(key->sk_application), apphash, sizeof(apphash))) != 0) 253 goto out; 254 #ifdef DEBUG_SK 255 fprintf(stderr, "%s: hashed application:\n", __func__); 256 sshbuf_dump_data(apphash, sizeof(apphash), stderr); 257 fprintf(stderr, "%s: hashed message:\n", __func__); 258 sshbuf_dump_data(msghash, sizeof(msghash), stderr); 259 #endif 260 if ((ret = sshbuf_put(original_signed, 261 apphash, sizeof(apphash))) != 0 || 262 (ret = sshbuf_put_u8(original_signed, sig_flags)) != 0 || 263 (ret = sshbuf_put_u32(original_signed, sig_counter)) != 0 || 264 (ret = sshbuf_putb(original_signed, webauthn_exts)) != 0 || 265 (ret = sshbuf_put(original_signed, msghash, sizeof(msghash))) != 0) 266 goto out; 267 /* Signature is over H(original_signed) */ 268 if ((ret = ssh_digest_buffer(SSH_DIGEST_SHA256, original_signed, 269 sighash, sizeof(sighash))) != 0) 270 goto out; 271 details->sk_counter = sig_counter; 272 details->sk_flags = sig_flags; 273 #ifdef DEBUG_SK 274 fprintf(stderr, "%s: signed buf:\n", __func__); 275 sshbuf_dump(original_signed, stderr); 276 fprintf(stderr, "%s: signed hash:\n", __func__); 277 sshbuf_dump_data(sighash, sizeof(sighash), stderr); 278 #endif 279 280 /* Verify it */ 281 switch (ECDSA_do_verify(sighash, sizeof(sighash), sig, key->ecdsa)) { 282 case 1: 283 ret = 0; 284 break; 285 case 0: 286 ret = SSH_ERR_SIGNATURE_INVALID; 287 goto out; 288 default: 289 ret = SSH_ERR_LIBCRYPTO_ERROR; 290 goto out; 291 } 292 /* success */ 293 if (detailsp != NULL) { 294 *detailsp = details; 295 details = NULL; 296 } 297 out: 298 explicit_bzero(&sig_flags, sizeof(sig_flags)); 299 explicit_bzero(&sig_counter, sizeof(sig_counter)); 300 explicit_bzero(msghash, sizeof(msghash)); 301 explicit_bzero(sighash, sizeof(msghash)); 302 explicit_bzero(apphash, sizeof(apphash)); 303 sshkey_sig_details_free(details); 304 sshbuf_free(webauthn_wrapper); 305 sshbuf_free(webauthn_exts); 306 free(webauthn_origin); 307 sshbuf_free(original_signed); 308 sshbuf_free(sigbuf); 309 sshbuf_free(b); 310 ECDSA_SIG_free(sig); 311 BN_clear_free(sig_r); 312 BN_clear_free(sig_s); 313 free(ktype); 314 return ret; 315 } 316 317 #endif /* OPENSSL_HAS_ECC */ 318