xref: /freebsd/crypto/openssh/ssh-xmss.c (revision e0c4386e7e71d93b0edc0c8fa156263fc4a8b0b6)
1 /* $OpenBSD: ssh-xmss.c,v 1.14 2022/10/28 00:44:44 djm Exp $*/
2 /*
3  * Copyright (c) 2017 Stefan-Lukas Gazdag.
4  * Copyright (c) 2017 Markus Friedl.
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 #include "includes.h"
19 #ifdef WITH_XMSS
20 
21 #define SSHKEY_INTERNAL
22 #include <sys/types.h>
23 #include <limits.h>
24 
25 #include <stdlib.h>
26 #include <string.h>
27 #include <stdarg.h>
28 #ifdef HAVE_STDINT_H
29 # include <stdint.h>
30 #endif
31 #include <unistd.h>
32 
33 #include "log.h"
34 #include "sshbuf.h"
35 #include "sshkey.h"
36 #include "sshkey-xmss.h"
37 #include "ssherr.h"
38 #include "ssh.h"
39 
40 #include "xmss_fast.h"
41 
42 static void
43 ssh_xmss_cleanup(struct sshkey *k)
44 {
45 	freezero(k->xmss_pk, sshkey_xmss_pklen(k));
46 	freezero(k->xmss_sk, sshkey_xmss_sklen(k));
47 	sshkey_xmss_free_state(k);
48 	free(k->xmss_name);
49 	free(k->xmss_filename);
50 	k->xmss_pk = NULL;
51 	k->xmss_sk = NULL;
52 	k->xmss_name = NULL;
53 	k->xmss_filename = NULL;
54 }
55 
56 static int
57 ssh_xmss_equal(const struct sshkey *a, const struct sshkey *b)
58 {
59 	if (a->xmss_pk == NULL || b->xmss_pk == NULL)
60 		return 0;
61 	if (sshkey_xmss_pklen(a) != sshkey_xmss_pklen(b))
62 		return 0;
63 	if (memcmp(a->xmss_pk, b->xmss_pk, sshkey_xmss_pklen(a)) != 0)
64 		return 0;
65 	return 1;
66 }
67 
68 static int
69 ssh_xmss_serialize_public(const struct sshkey *key, struct sshbuf *b,
70     enum sshkey_serialize_rep opts)
71 {
72 	int r;
73 
74 	if (key->xmss_name == NULL || key->xmss_pk == NULL ||
75 	    sshkey_xmss_pklen(key) == 0)
76 		return SSH_ERR_INVALID_ARGUMENT;
77 	if ((r = sshbuf_put_cstring(b, key->xmss_name)) != 0 ||
78 	    (r = sshbuf_put_string(b, key->xmss_pk,
79 	    sshkey_xmss_pklen(key))) != 0 ||
80 	    (r = sshkey_xmss_serialize_pk_info(key, b, opts)) != 0)
81 		return r;
82 
83 	return 0;
84 }
85 
86 static int
87 ssh_xmss_serialize_private(const struct sshkey *key, struct sshbuf *b,
88     enum sshkey_serialize_rep opts)
89 {
90 	int r;
91 
92 	if (key->xmss_name == NULL)
93 		return SSH_ERR_INVALID_ARGUMENT;
94 	/* Note: can't reuse ssh_xmss_serialize_public because of sk order */
95 	if ((r = sshbuf_put_cstring(b, key->xmss_name)) != 0 ||
96 	    (r = sshbuf_put_string(b, key->xmss_pk,
97 	    sshkey_xmss_pklen(key))) != 0 ||
98 	    (r = sshbuf_put_string(b, key->xmss_sk,
99 	    sshkey_xmss_sklen(key))) != 0 ||
100 	    (r = sshkey_xmss_serialize_state_opt(key, b, opts)) != 0)
101 		return r;
102 
103 	return 0;
104 }
105 
106 static int
107 ssh_xmss_copy_public(const struct sshkey *from, struct sshkey *to)
108 {
109 	int r = SSH_ERR_INTERNAL_ERROR;
110 	u_int32_t left;
111 	size_t pklen;
112 
113 	if ((r = sshkey_xmss_init(to, from->xmss_name)) != 0)
114 		return r;
115 	if (from->xmss_pk == NULL)
116 		return 0; /* XXX SSH_ERR_INTERNAL_ERROR ? */
117 
118 	if ((pklen = sshkey_xmss_pklen(from)) == 0 ||
119 	    sshkey_xmss_pklen(to) != pklen)
120 		return SSH_ERR_INTERNAL_ERROR;
121 	if ((to->xmss_pk = malloc(pklen)) == NULL)
122 		return SSH_ERR_ALLOC_FAIL;
123 	memcpy(to->xmss_pk, from->xmss_pk, pklen);
124 	/* simulate number of signatures left on pubkey */
125 	left = sshkey_xmss_signatures_left(from);
126 	if (left)
127 		sshkey_xmss_enable_maxsign(to, left);
128 	return 0;
129 }
130 
131 static int
132 ssh_xmss_deserialize_public(const char *ktype, struct sshbuf *b,
133     struct sshkey *key)
134 {
135 	size_t len = 0;
136 	char *xmss_name = NULL;
137 	u_char *pk = NULL;
138 	int ret = SSH_ERR_INTERNAL_ERROR;
139 
140 	if ((ret = sshbuf_get_cstring(b, &xmss_name, NULL)) != 0)
141 		goto out;
142 	if ((ret = sshkey_xmss_init(key, xmss_name)) != 0)
143 		goto out;
144 	if ((ret = sshbuf_get_string(b, &pk, &len)) != 0)
145 		goto out;
146 	if (len == 0 || len != sshkey_xmss_pklen(key)) {
147 		ret = SSH_ERR_INVALID_FORMAT;
148 		goto out;
149 	}
150 	key->xmss_pk = pk;
151 	pk = NULL;
152 	if (!sshkey_is_cert(key) &&
153 	    (ret = sshkey_xmss_deserialize_pk_info(key, b)) != 0)
154 		goto out;
155 	/* success */
156 	ret = 0;
157  out:
158 	free(xmss_name);
159 	freezero(pk, len);
160 	return ret;
161 }
162 
163 static int
164 ssh_xmss_deserialize_private(const char *ktype, struct sshbuf *b,
165     struct sshkey *key)
166 {
167 	int r;
168 	char *xmss_name = NULL;
169 	size_t pklen = 0, sklen = 0;
170 	u_char *xmss_pk = NULL, *xmss_sk = NULL;
171 
172 	/* Note: can't reuse ssh_xmss_deserialize_public because of sk order */
173 	if ((r = sshbuf_get_cstring(b, &xmss_name, NULL)) != 0 ||
174 	    (r = sshbuf_get_string(b, &xmss_pk, &pklen)) != 0 ||
175 	    (r = sshbuf_get_string(b, &xmss_sk, &sklen)) != 0)
176 		goto out;
177 	if (!sshkey_is_cert(key) &&
178 	    (r = sshkey_xmss_init(key, xmss_name)) != 0)
179 		goto out;
180 	if (pklen != sshkey_xmss_pklen(key) ||
181 	    sklen != sshkey_xmss_sklen(key)) {
182 		r = SSH_ERR_INVALID_FORMAT;
183 		goto out;
184 	}
185 	key->xmss_pk = xmss_pk;
186 	key->xmss_sk = xmss_sk;
187 	xmss_pk = xmss_sk = NULL;
188 	/* optional internal state */
189 	if ((r = sshkey_xmss_deserialize_state_opt(key, b)) != 0)
190 		goto out;
191 	/* success */
192 	r = 0;
193  out:
194 	free(xmss_name);
195 	freezero(xmss_pk, pklen);
196 	freezero(xmss_sk, sklen);
197 	return r;
198 }
199 
200 static int
201 ssh_xmss_sign(struct sshkey *key,
202     u_char **sigp, size_t *lenp,
203     const u_char *data, size_t datalen,
204     const char *alg, const char *sk_provider, const char *sk_pin, u_int compat)
205 {
206 	u_char *sig = NULL;
207 	size_t slen = 0, len = 0, required_siglen;
208 	unsigned long long smlen;
209 	int r, ret;
210 	struct sshbuf *b = NULL;
211 
212 	if (lenp != NULL)
213 		*lenp = 0;
214 	if (sigp != NULL)
215 		*sigp = NULL;
216 
217 	if (key == NULL ||
218 	    sshkey_type_plain(key->type) != KEY_XMSS ||
219 	    key->xmss_sk == NULL ||
220 	    sshkey_xmss_params(key) == NULL)
221 		return SSH_ERR_INVALID_ARGUMENT;
222 	if ((r = sshkey_xmss_siglen(key, &required_siglen)) != 0)
223 		return r;
224 	if (datalen >= INT_MAX - required_siglen)
225 		return SSH_ERR_INVALID_ARGUMENT;
226 	smlen = slen = datalen + required_siglen;
227 	if ((sig = malloc(slen)) == NULL)
228 		return SSH_ERR_ALLOC_FAIL;
229 	if ((r = sshkey_xmss_get_state(key, 1)) != 0)
230 		goto out;
231 	if ((ret = xmss_sign(key->xmss_sk, sshkey_xmss_bds_state(key), sig, &smlen,
232 	    data, datalen, sshkey_xmss_params(key))) != 0 || smlen <= datalen) {
233 		r = SSH_ERR_INVALID_ARGUMENT; /* XXX better error? */
234 		goto out;
235 	}
236 	/* encode signature */
237 	if ((b = sshbuf_new()) == NULL) {
238 		r = SSH_ERR_ALLOC_FAIL;
239 		goto out;
240 	}
241 	if ((r = sshbuf_put_cstring(b, "ssh-xmss@openssh.com")) != 0 ||
242 	    (r = sshbuf_put_string(b, sig, smlen - datalen)) != 0)
243 		goto out;
244 	len = sshbuf_len(b);
245 	if (sigp != NULL) {
246 		if ((*sigp = malloc(len)) == NULL) {
247 			r = SSH_ERR_ALLOC_FAIL;
248 			goto out;
249 		}
250 		memcpy(*sigp, sshbuf_ptr(b), len);
251 	}
252 	if (lenp != NULL)
253 		*lenp = len;
254 	/* success */
255 	r = 0;
256  out:
257 	if ((ret = sshkey_xmss_update_state(key, 1)) != 0) {
258 		/* discard signature since we cannot update the state */
259 		if (r == 0 && sigp != NULL && *sigp != NULL) {
260 			explicit_bzero(*sigp, len);
261 			free(*sigp);
262 		}
263 		if (sigp != NULL)
264 			*sigp = NULL;
265 		if (lenp != NULL)
266 			*lenp = 0;
267 		r = ret;
268 	}
269 	sshbuf_free(b);
270 	if (sig != NULL)
271 		freezero(sig, slen);
272 
273 	return r;
274 }
275 
276 static int
277 ssh_xmss_verify(const struct sshkey *key,
278     const u_char *sig, size_t siglen,
279     const u_char *data, size_t dlen, const char *alg, u_int compat,
280     struct sshkey_sig_details **detailsp)
281 {
282 	struct sshbuf *b = NULL;
283 	char *ktype = NULL;
284 	const u_char *sigblob;
285 	u_char *sm = NULL, *m = NULL;
286 	size_t len, required_siglen;
287 	unsigned long long smlen = 0, mlen = 0;
288 	int r, ret;
289 
290 	if (key == NULL ||
291 	    sshkey_type_plain(key->type) != KEY_XMSS ||
292 	    key->xmss_pk == NULL ||
293 	    sshkey_xmss_params(key) == NULL ||
294 	    sig == NULL || siglen == 0)
295 		return SSH_ERR_INVALID_ARGUMENT;
296 	if ((r = sshkey_xmss_siglen(key, &required_siglen)) != 0)
297 		return r;
298 	if (dlen >= INT_MAX - required_siglen)
299 		return SSH_ERR_INVALID_ARGUMENT;
300 
301 	if ((b = sshbuf_from(sig, siglen)) == NULL)
302 		return SSH_ERR_ALLOC_FAIL;
303 	if ((r = sshbuf_get_cstring(b, &ktype, NULL)) != 0 ||
304 	    (r = sshbuf_get_string_direct(b, &sigblob, &len)) != 0)
305 		goto out;
306 	if (strcmp("ssh-xmss@openssh.com", ktype) != 0) {
307 		r = SSH_ERR_KEY_TYPE_MISMATCH;
308 		goto out;
309 	}
310 	if (sshbuf_len(b) != 0) {
311 		r = SSH_ERR_UNEXPECTED_TRAILING_DATA;
312 		goto out;
313 	}
314 	if (len != required_siglen) {
315 		r = SSH_ERR_INVALID_FORMAT;
316 		goto out;
317 	}
318 	if (dlen >= SIZE_MAX - len) {
319 		r = SSH_ERR_INVALID_ARGUMENT;
320 		goto out;
321 	}
322 	smlen = len + dlen;
323 	mlen = smlen;
324 	if ((sm = malloc(smlen)) == NULL || (m = malloc(mlen)) == NULL) {
325 		r = SSH_ERR_ALLOC_FAIL;
326 		goto out;
327 	}
328 	memcpy(sm, sigblob, len);
329 	memcpy(sm+len, data, dlen);
330 	if ((ret = xmss_sign_open(m, &mlen, sm, smlen,
331 	    key->xmss_pk, sshkey_xmss_params(key))) != 0) {
332 		debug2_f("xmss_sign_open failed: %d", ret);
333 	}
334 	if (ret != 0 || mlen != dlen) {
335 		r = SSH_ERR_SIGNATURE_INVALID;
336 		goto out;
337 	}
338 	/* XXX compare 'm' and 'data' ? */
339 	/* success */
340 	r = 0;
341  out:
342 	if (sm != NULL)
343 		freezero(sm, smlen);
344 	if (m != NULL)
345 		freezero(m, smlen);
346 	sshbuf_free(b);
347 	free(ktype);
348 	return r;
349 }
350 
351 static const struct sshkey_impl_funcs sshkey_xmss_funcs = {
352 	/* .size = */		NULL,
353 	/* .alloc = */		NULL,
354 	/* .cleanup = */	ssh_xmss_cleanup,
355 	/* .equal = */		ssh_xmss_equal,
356 	/* .ssh_serialize_public = */ ssh_xmss_serialize_public,
357 	/* .ssh_deserialize_public = */ ssh_xmss_deserialize_public,
358 	/* .ssh_serialize_private = */ ssh_xmss_serialize_private,
359 	/* .ssh_deserialize_private = */ ssh_xmss_deserialize_private,
360 	/* .generate = */	sshkey_xmss_generate_private_key,
361 	/* .copy_public = */	ssh_xmss_copy_public,
362 	/* .sign = */		ssh_xmss_sign,
363 	/* .verify = */		ssh_xmss_verify,
364 };
365 
366 const struct sshkey_impl sshkey_xmss_impl = {
367 	/* .name = */		"ssh-xmss@openssh.com",
368 	/* .shortname = */	"XMSS",
369 	/* .sigalg = */		NULL,
370 	/* .type = */		KEY_XMSS,
371 	/* .nid = */		0,
372 	/* .cert = */		0,
373 	/* .sigonly = */	0,
374 	/* .keybits = */	256,
375 	/* .funcs = */		&sshkey_xmss_funcs,
376 };
377 
378 const struct sshkey_impl sshkey_xmss_cert_impl = {
379 	/* .name = */		"ssh-xmss-cert-v01@openssh.com",
380 	/* .shortname = */	"XMSS-CERT",
381 	/* .sigalg = */		NULL,
382 	/* .type = */		KEY_XMSS_CERT,
383 	/* .nid = */		0,
384 	/* .cert = */		1,
385 	/* .sigonly = */	0,
386 	/* .keybits = */	256,
387 	/* .funcs = */		&sshkey_xmss_funcs,
388 };
389 #endif /* WITH_XMSS */
390