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