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