xref: /freebsd/crypto/openssh/ssh-ecdsa-sk.c (revision 19261079b74319502c6ffa1249920079f0f69a72)
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