Line | Count | Source (jump to first uncovered line) |
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 | 0 | { |
45 | 0 | freezero(k->xmss_pk, sshkey_xmss_pklen(k)); |
46 | 0 | freezero(k->xmss_sk, sshkey_xmss_sklen(k)); |
47 | 0 | sshkey_xmss_free_state(k); |
48 | 0 | free(k->xmss_name); |
49 | 0 | free(k->xmss_filename); |
50 | 0 | k->xmss_pk = NULL; |
51 | 0 | k->xmss_sk = NULL; |
52 | 0 | k->xmss_name = NULL; |
53 | 0 | k->xmss_filename = NULL; |
54 | 0 | } |
55 | | |
56 | | static int |
57 | | ssh_xmss_equal(const struct sshkey *a, const struct sshkey *b) |
58 | 0 | { |
59 | 0 | if (a->xmss_pk == NULL || b->xmss_pk == NULL) |
60 | 0 | return 0; |
61 | 0 | if (sshkey_xmss_pklen(a) != sshkey_xmss_pklen(b)) |
62 | 0 | return 0; |
63 | 0 | if (memcmp(a->xmss_pk, b->xmss_pk, sshkey_xmss_pklen(a)) != 0) |
64 | 0 | return 0; |
65 | 0 | return 1; |
66 | 0 | } |
67 | | |
68 | | static int |
69 | | ssh_xmss_serialize_public(const struct sshkey *key, struct sshbuf *b, |
70 | | enum sshkey_serialize_rep opts) |
71 | 0 | { |
72 | 0 | int r; |
73 | |
|
74 | 0 | if (key->xmss_name == NULL || key->xmss_pk == NULL || |
75 | 0 | sshkey_xmss_pklen(key) == 0) |
76 | 0 | return SSH_ERR_INVALID_ARGUMENT; |
77 | 0 | if ((r = sshbuf_put_cstring(b, key->xmss_name)) != 0 || |
78 | 0 | (r = sshbuf_put_string(b, key->xmss_pk, |
79 | 0 | sshkey_xmss_pklen(key))) != 0 || |
80 | 0 | (r = sshkey_xmss_serialize_pk_info(key, b, opts)) != 0) |
81 | 0 | return r; |
82 | | |
83 | 0 | return 0; |
84 | 0 | } |
85 | | |
86 | | static int |
87 | | ssh_xmss_serialize_private(const struct sshkey *key, struct sshbuf *b, |
88 | | enum sshkey_serialize_rep opts) |
89 | 0 | { |
90 | 0 | int r; |
91 | |
|
92 | 0 | if (key->xmss_name == NULL) |
93 | 0 | return SSH_ERR_INVALID_ARGUMENT; |
94 | | /* Note: can't reuse ssh_xmss_serialize_public because of sk order */ |
95 | 0 | if ((r = sshbuf_put_cstring(b, key->xmss_name)) != 0 || |
96 | 0 | (r = sshbuf_put_string(b, key->xmss_pk, |
97 | 0 | sshkey_xmss_pklen(key))) != 0 || |
98 | 0 | (r = sshbuf_put_string(b, key->xmss_sk, |
99 | 0 | sshkey_xmss_sklen(key))) != 0 || |
100 | 0 | (r = sshkey_xmss_serialize_state_opt(key, b, opts)) != 0) |
101 | 0 | return r; |
102 | | |
103 | 0 | return 0; |
104 | 0 | } |
105 | | |
106 | | static int |
107 | | ssh_xmss_copy_public(const struct sshkey *from, struct sshkey *to) |
108 | 0 | { |
109 | 0 | int r = SSH_ERR_INTERNAL_ERROR; |
110 | 0 | u_int32_t left; |
111 | 0 | size_t pklen; |
112 | |
|
113 | 0 | if ((r = sshkey_xmss_init(to, from->xmss_name)) != 0) |
114 | 0 | return r; |
115 | 0 | if (from->xmss_pk == NULL) |
116 | 0 | return 0; /* XXX SSH_ERR_INTERNAL_ERROR ? */ |
117 | | |
118 | 0 | if ((pklen = sshkey_xmss_pklen(from)) == 0 || |
119 | 0 | sshkey_xmss_pklen(to) != pklen) |
120 | 0 | return SSH_ERR_INTERNAL_ERROR; |
121 | 0 | if ((to->xmss_pk = malloc(pklen)) == NULL) |
122 | 0 | return SSH_ERR_ALLOC_FAIL; |
123 | 0 | memcpy(to->xmss_pk, from->xmss_pk, pklen); |
124 | | /* simulate number of signatures left on pubkey */ |
125 | 0 | left = sshkey_xmss_signatures_left(from); |
126 | 0 | if (left) |
127 | 0 | sshkey_xmss_enable_maxsign(to, left); |
128 | 0 | return 0; |
129 | 0 | } |
130 | | |
131 | | static int |
132 | | ssh_xmss_deserialize_public(const char *ktype, struct sshbuf *b, |
133 | | struct sshkey *key) |
134 | 0 | { |
135 | 0 | size_t len = 0; |
136 | 0 | char *xmss_name = NULL; |
137 | 0 | u_char *pk = NULL; |
138 | 0 | int ret = SSH_ERR_INTERNAL_ERROR; |
139 | |
|
140 | 0 | if ((ret = sshbuf_get_cstring(b, &xmss_name, NULL)) != 0) |
141 | 0 | goto out; |
142 | 0 | if ((ret = sshkey_xmss_init(key, xmss_name)) != 0) |
143 | 0 | goto out; |
144 | 0 | if ((ret = sshbuf_get_string(b, &pk, &len)) != 0) |
145 | 0 | goto out; |
146 | 0 | if (len == 0 || len != sshkey_xmss_pklen(key)) { |
147 | 0 | ret = SSH_ERR_INVALID_FORMAT; |
148 | 0 | goto out; |
149 | 0 | } |
150 | 0 | key->xmss_pk = pk; |
151 | 0 | pk = NULL; |
152 | 0 | if (!sshkey_is_cert(key) && |
153 | 0 | (ret = sshkey_xmss_deserialize_pk_info(key, b)) != 0) |
154 | 0 | goto out; |
155 | | /* success */ |
156 | 0 | ret = 0; |
157 | 0 | out: |
158 | 0 | free(xmss_name); |
159 | 0 | freezero(pk, len); |
160 | 0 | return ret; |
161 | 0 | } |
162 | | |
163 | | static int |
164 | | ssh_xmss_deserialize_private(const char *ktype, struct sshbuf *b, |
165 | | struct sshkey *key) |
166 | 0 | { |
167 | 0 | int r; |
168 | 0 | char *xmss_name = NULL; |
169 | 0 | size_t pklen = 0, sklen = 0; |
170 | 0 | u_char *xmss_pk = NULL, *xmss_sk = NULL; |
171 | | |
172 | | /* Note: can't reuse ssh_xmss_deserialize_public because of sk order */ |
173 | 0 | if ((r = sshbuf_get_cstring(b, &xmss_name, NULL)) != 0 || |
174 | 0 | (r = sshbuf_get_string(b, &xmss_pk, &pklen)) != 0 || |
175 | 0 | (r = sshbuf_get_string(b, &xmss_sk, &sklen)) != 0) |
176 | 0 | goto out; |
177 | 0 | if (!sshkey_is_cert(key) && |
178 | 0 | (r = sshkey_xmss_init(key, xmss_name)) != 0) |
179 | 0 | goto out; |
180 | 0 | if (pklen != sshkey_xmss_pklen(key) || |
181 | 0 | sklen != sshkey_xmss_sklen(key)) { |
182 | 0 | r = SSH_ERR_INVALID_FORMAT; |
183 | 0 | goto out; |
184 | 0 | } |
185 | 0 | key->xmss_pk = xmss_pk; |
186 | 0 | key->xmss_sk = xmss_sk; |
187 | 0 | xmss_pk = xmss_sk = NULL; |
188 | | /* optional internal state */ |
189 | 0 | if ((r = sshkey_xmss_deserialize_state_opt(key, b)) != 0) |
190 | 0 | goto out; |
191 | | /* success */ |
192 | 0 | r = 0; |
193 | 0 | out: |
194 | 0 | free(xmss_name); |
195 | 0 | freezero(xmss_pk, pklen); |
196 | 0 | freezero(xmss_sk, sklen); |
197 | 0 | return r; |
198 | 0 | } |
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 | 0 | { |
206 | 0 | u_char *sig = NULL; |
207 | 0 | size_t slen = 0, len = 0, required_siglen; |
208 | 0 | unsigned long long smlen; |
209 | 0 | int r, ret; |
210 | 0 | struct sshbuf *b = NULL; |
211 | |
|
212 | 0 | if (lenp != NULL) |
213 | 0 | *lenp = 0; |
214 | 0 | if (sigp != NULL) |
215 | 0 | *sigp = NULL; |
216 | |
|
217 | 0 | if (key == NULL || |
218 | 0 | sshkey_type_plain(key->type) != KEY_XMSS || |
219 | 0 | key->xmss_sk == NULL || |
220 | 0 | sshkey_xmss_params(key) == NULL) |
221 | 0 | return SSH_ERR_INVALID_ARGUMENT; |
222 | 0 | if ((r = sshkey_xmss_siglen(key, &required_siglen)) != 0) |
223 | 0 | return r; |
224 | 0 | if (datalen >= INT_MAX - required_siglen) |
225 | 0 | return SSH_ERR_INVALID_ARGUMENT; |
226 | 0 | smlen = slen = datalen + required_siglen; |
227 | 0 | if ((sig = malloc(slen)) == NULL) |
228 | 0 | return SSH_ERR_ALLOC_FAIL; |
229 | 0 | if ((r = sshkey_xmss_get_state(key, 1)) != 0) |
230 | 0 | goto out; |
231 | 0 | if ((ret = xmss_sign(key->xmss_sk, sshkey_xmss_bds_state(key), sig, &smlen, |
232 | 0 | data, datalen, sshkey_xmss_params(key))) != 0 || smlen <= datalen) { |
233 | 0 | r = SSH_ERR_INVALID_ARGUMENT; /* XXX better error? */ |
234 | 0 | goto out; |
235 | 0 | } |
236 | | /* encode signature */ |
237 | 0 | if ((b = sshbuf_new()) == NULL) { |
238 | 0 | r = SSH_ERR_ALLOC_FAIL; |
239 | 0 | goto out; |
240 | 0 | } |
241 | 0 | if ((r = sshbuf_put_cstring(b, "ssh-xmss@openssh.com")) != 0 || |
242 | 0 | (r = sshbuf_put_string(b, sig, smlen - datalen)) != 0) |
243 | 0 | goto out; |
244 | 0 | len = sshbuf_len(b); |
245 | 0 | if (sigp != NULL) { |
246 | 0 | if ((*sigp = malloc(len)) == NULL) { |
247 | 0 | r = SSH_ERR_ALLOC_FAIL; |
248 | 0 | goto out; |
249 | 0 | } |
250 | 0 | memcpy(*sigp, sshbuf_ptr(b), len); |
251 | 0 | } |
252 | 0 | if (lenp != NULL) |
253 | 0 | *lenp = len; |
254 | | /* success */ |
255 | 0 | r = 0; |
256 | 0 | out: |
257 | 0 | if ((ret = sshkey_xmss_update_state(key, 1)) != 0) { |
258 | | /* discard signature since we cannot update the state */ |
259 | 0 | if (r == 0 && sigp != NULL && *sigp != NULL) { |
260 | 0 | explicit_bzero(*sigp, len); |
261 | 0 | free(*sigp); |
262 | 0 | } |
263 | 0 | if (sigp != NULL) |
264 | 0 | *sigp = NULL; |
265 | 0 | if (lenp != NULL) |
266 | 0 | *lenp = 0; |
267 | 0 | r = ret; |
268 | 0 | } |
269 | 0 | sshbuf_free(b); |
270 | 0 | if (sig != NULL) |
271 | 0 | freezero(sig, slen); |
272 | |
|
273 | 0 | return r; |
274 | 0 | } |
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 | 0 | { |
282 | 0 | struct sshbuf *b = NULL; |
283 | 0 | char *ktype = NULL; |
284 | 0 | const u_char *sigblob; |
285 | 0 | u_char *sm = NULL, *m = NULL; |
286 | 0 | size_t len, required_siglen; |
287 | 0 | unsigned long long smlen = 0, mlen = 0; |
288 | 0 | int r, ret; |
289 | |
|
290 | 0 | if (key == NULL || |
291 | 0 | sshkey_type_plain(key->type) != KEY_XMSS || |
292 | 0 | key->xmss_pk == NULL || |
293 | 0 | sshkey_xmss_params(key) == NULL || |
294 | 0 | sig == NULL || siglen == 0) |
295 | 0 | return SSH_ERR_INVALID_ARGUMENT; |
296 | 0 | if ((r = sshkey_xmss_siglen(key, &required_siglen)) != 0) |
297 | 0 | return r; |
298 | 0 | if (dlen >= INT_MAX - required_siglen) |
299 | 0 | return SSH_ERR_INVALID_ARGUMENT; |
300 | | |
301 | 0 | if ((b = sshbuf_from(sig, siglen)) == NULL) |
302 | 0 | return SSH_ERR_ALLOC_FAIL; |
303 | 0 | if ((r = sshbuf_get_cstring(b, &ktype, NULL)) != 0 || |
304 | 0 | (r = sshbuf_get_string_direct(b, &sigblob, &len)) != 0) |
305 | 0 | goto out; |
306 | 0 | if (strcmp("ssh-xmss@openssh.com", ktype) != 0) { |
307 | 0 | r = SSH_ERR_KEY_TYPE_MISMATCH; |
308 | 0 | goto out; |
309 | 0 | } |
310 | 0 | if (sshbuf_len(b) != 0) { |
311 | 0 | r = SSH_ERR_UNEXPECTED_TRAILING_DATA; |
312 | 0 | goto out; |
313 | 0 | } |
314 | 0 | if (len != required_siglen) { |
315 | 0 | r = SSH_ERR_INVALID_FORMAT; |
316 | 0 | goto out; |
317 | 0 | } |
318 | 0 | if (dlen >= SIZE_MAX - len) { |
319 | 0 | r = SSH_ERR_INVALID_ARGUMENT; |
320 | 0 | goto out; |
321 | 0 | } |
322 | 0 | smlen = len + dlen; |
323 | 0 | mlen = smlen; |
324 | 0 | if ((sm = malloc(smlen)) == NULL || (m = malloc(mlen)) == NULL) { |
325 | 0 | r = SSH_ERR_ALLOC_FAIL; |
326 | 0 | goto out; |
327 | 0 | } |
328 | 0 | memcpy(sm, sigblob, len); |
329 | 0 | memcpy(sm+len, data, dlen); |
330 | 0 | if ((ret = xmss_sign_open(m, &mlen, sm, smlen, |
331 | 0 | key->xmss_pk, sshkey_xmss_params(key))) != 0) { |
332 | 0 | debug2_f("xmss_sign_open failed: %d", ret); |
333 | 0 | } |
334 | 0 | if (ret != 0 || mlen != dlen) { |
335 | 0 | r = SSH_ERR_SIGNATURE_INVALID; |
336 | 0 | goto out; |
337 | 0 | } |
338 | | /* XXX compare 'm' and 'data' ? */ |
339 | | /* success */ |
340 | 0 | r = 0; |
341 | 0 | out: |
342 | 0 | if (sm != NULL) |
343 | 0 | freezero(sm, smlen); |
344 | 0 | if (m != NULL) |
345 | 0 | freezero(m, smlen); |
346 | 0 | sshbuf_free(b); |
347 | 0 | free(ktype); |
348 | 0 | return r; |
349 | 0 | } |
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 */ |