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