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