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
42static void
43ssh_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
56static int
57ssh_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
68static int
69ssh_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
86static int
87ssh_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
106static int
107ssh_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
131static int
132ssh_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
163static int
164ssh_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
200static int
201ssh_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
276static int
277ssh_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
351static 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
366const 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
378const 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