diff options
Diffstat (limited to 'regress/misc/sk-dummy/sk-dummy.c')
-rw-r--r-- | regress/misc/sk-dummy/sk-dummy.c | 526 |
1 files changed, 526 insertions, 0 deletions
diff --git a/regress/misc/sk-dummy/sk-dummy.c b/regress/misc/sk-dummy/sk-dummy.c new file mode 100644 index 000000000..dca158ded --- /dev/null +++ b/regress/misc/sk-dummy/sk-dummy.c | |||
@@ -0,0 +1,526 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2019 Markus Friedl | ||
3 | * | ||
4 | * Permission to use, copy, modify, and distribute this software for any | ||
5 | * purpose with or without fee is hereby granted, provided that the above | ||
6 | * copyright notice and this permission notice appear in all copies. | ||
7 | * | ||
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
15 | */ | ||
16 | |||
17 | #include "includes.h" | ||
18 | |||
19 | #ifdef HAVE_STDINT_H | ||
20 | #include <stdint.h> | ||
21 | #endif | ||
22 | #include <stdlib.h> | ||
23 | #include <string.h> | ||
24 | #include <stdio.h> | ||
25 | #include <stddef.h> | ||
26 | #include <stdarg.h> | ||
27 | |||
28 | #include "crypto_api.h" | ||
29 | #include "sk-api.h" | ||
30 | |||
31 | #include <openssl/opensslv.h> | ||
32 | #include <openssl/crypto.h> | ||
33 | #include <openssl/evp.h> | ||
34 | #include <openssl/bn.h> | ||
35 | #include <openssl/ec.h> | ||
36 | #include <openssl/ecdsa.h> | ||
37 | #include <openssl/pem.h> | ||
38 | |||
39 | /* #define SK_DEBUG 1 */ | ||
40 | |||
41 | /* Compatibility with OpenSSH 1.0.x */ | ||
42 | #if (OPENSSL_VERSION_NUMBER < 0x10100000L) | ||
43 | #define ECDSA_SIG_get0(sig, pr, ps) \ | ||
44 | do { \ | ||
45 | (*pr) = sig->r; \ | ||
46 | (*ps) = sig->s; \ | ||
47 | } while (0) | ||
48 | #endif | ||
49 | |||
50 | #if SSH_SK_VERSION_MAJOR != 0x00040000 | ||
51 | # error SK API has changed, sk-dummy.c needs an update | ||
52 | #endif | ||
53 | |||
54 | static void skdebug(const char *func, const char *fmt, ...) | ||
55 | __attribute__((__format__ (printf, 2, 3))); | ||
56 | |||
57 | static void | ||
58 | skdebug(const char *func, const char *fmt, ...) | ||
59 | { | ||
60 | #if defined(SK_DEBUG) | ||
61 | va_list ap; | ||
62 | |||
63 | va_start(ap, fmt); | ||
64 | fprintf(stderr, "sk-dummy %s: ", func); | ||
65 | vfprintf(stderr, fmt, ap); | ||
66 | fputc('\n', stderr); | ||
67 | va_end(ap); | ||
68 | #else | ||
69 | (void)func; /* XXX */ | ||
70 | (void)fmt; /* XXX */ | ||
71 | #endif | ||
72 | } | ||
73 | |||
74 | uint32_t | ||
75 | sk_api_version(void) | ||
76 | { | ||
77 | return SSH_SK_VERSION_MAJOR; | ||
78 | } | ||
79 | |||
80 | static int | ||
81 | pack_key_ecdsa(struct sk_enroll_response *response) | ||
82 | { | ||
83 | #ifdef OPENSSL_HAS_ECC | ||
84 | EC_KEY *key = NULL; | ||
85 | const EC_GROUP *g; | ||
86 | const EC_POINT *q; | ||
87 | int ret = -1; | ||
88 | long privlen; | ||
89 | BIO *bio = NULL; | ||
90 | char *privptr; | ||
91 | |||
92 | response->public_key = NULL; | ||
93 | response->public_key_len = 0; | ||
94 | response->key_handle = NULL; | ||
95 | response->key_handle_len = 0; | ||
96 | |||
97 | if ((key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)) == NULL) { | ||
98 | skdebug(__func__, "EC_KEY_new_by_curve_name"); | ||
99 | goto out; | ||
100 | } | ||
101 | if (EC_KEY_generate_key(key) != 1) { | ||
102 | skdebug(__func__, "EC_KEY_generate_key"); | ||
103 | goto out; | ||
104 | } | ||
105 | EC_KEY_set_asn1_flag(key, OPENSSL_EC_NAMED_CURVE); | ||
106 | if ((bio = BIO_new(BIO_s_mem())) == NULL || | ||
107 | (g = EC_KEY_get0_group(key)) == NULL || | ||
108 | (q = EC_KEY_get0_public_key(key)) == NULL) { | ||
109 | skdebug(__func__, "couldn't get key parameters"); | ||
110 | goto out; | ||
111 | } | ||
112 | response->public_key_len = EC_POINT_point2oct(g, q, | ||
113 | POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL); | ||
114 | if (response->public_key_len == 0 || response->public_key_len > 2048) { | ||
115 | skdebug(__func__, "bad pubkey length %zu", | ||
116 | response->public_key_len); | ||
117 | goto out; | ||
118 | } | ||
119 | if ((response->public_key = malloc(response->public_key_len)) == NULL) { | ||
120 | skdebug(__func__, "malloc pubkey failed"); | ||
121 | goto out; | ||
122 | } | ||
123 | if (EC_POINT_point2oct(g, q, POINT_CONVERSION_UNCOMPRESSED, | ||
124 | response->public_key, response->public_key_len, NULL) == 0) { | ||
125 | skdebug(__func__, "EC_POINT_point2oct failed"); | ||
126 | goto out; | ||
127 | } | ||
128 | /* Key handle contains PEM encoded private key */ | ||
129 | if (!PEM_write_bio_ECPrivateKey(bio, key, NULL, NULL, 0, NULL, NULL)) { | ||
130 | skdebug(__func__, "PEM_write_bio_ECPrivateKey failed"); | ||
131 | goto out; | ||
132 | } | ||
133 | if ((privlen = BIO_get_mem_data(bio, &privptr)) <= 0) { | ||
134 | skdebug(__func__, "BIO_get_mem_data failed"); | ||
135 | goto out; | ||
136 | } | ||
137 | if ((response->key_handle = malloc(privlen)) == NULL) { | ||
138 | skdebug(__func__, "malloc key_handle failed"); | ||
139 | goto out; | ||
140 | } | ||
141 | response->key_handle_len = (size_t)privlen; | ||
142 | memcpy(response->key_handle, privptr, response->key_handle_len); | ||
143 | /* success */ | ||
144 | ret = 0; | ||
145 | out: | ||
146 | if (ret != 0) { | ||
147 | if (response->public_key != NULL) { | ||
148 | memset(response->public_key, 0, | ||
149 | response->public_key_len); | ||
150 | free(response->public_key); | ||
151 | response->public_key = NULL; | ||
152 | } | ||
153 | if (response->key_handle != NULL) { | ||
154 | memset(response->key_handle, 0, | ||
155 | response->key_handle_len); | ||
156 | free(response->key_handle); | ||
157 | response->key_handle = NULL; | ||
158 | } | ||
159 | } | ||
160 | BIO_free(bio); | ||
161 | EC_KEY_free(key); | ||
162 | return ret; | ||
163 | #else | ||
164 | return -1; | ||
165 | #endif | ||
166 | } | ||
167 | |||
168 | static int | ||
169 | pack_key_ed25519(struct sk_enroll_response *response) | ||
170 | { | ||
171 | int ret = -1; | ||
172 | u_char pk[crypto_sign_ed25519_PUBLICKEYBYTES]; | ||
173 | u_char sk[crypto_sign_ed25519_SECRETKEYBYTES]; | ||
174 | |||
175 | response->public_key = NULL; | ||
176 | response->public_key_len = 0; | ||
177 | response->key_handle = NULL; | ||
178 | response->key_handle_len = 0; | ||
179 | |||
180 | memset(pk, 0, sizeof(pk)); | ||
181 | memset(sk, 0, sizeof(sk)); | ||
182 | crypto_sign_ed25519_keypair(pk, sk); | ||
183 | |||
184 | response->public_key_len = sizeof(pk); | ||
185 | if ((response->public_key = malloc(response->public_key_len)) == NULL) { | ||
186 | skdebug(__func__, "malloc pubkey failed"); | ||
187 | goto out; | ||
188 | } | ||
189 | memcpy(response->public_key, pk, sizeof(pk)); | ||
190 | /* Key handle contains sk */ | ||
191 | response->key_handle_len = sizeof(sk); | ||
192 | if ((response->key_handle = malloc(response->key_handle_len)) == NULL) { | ||
193 | skdebug(__func__, "malloc key_handle failed"); | ||
194 | goto out; | ||
195 | } | ||
196 | memcpy(response->key_handle, sk, sizeof(sk)); | ||
197 | /* success */ | ||
198 | ret = 0; | ||
199 | out: | ||
200 | if (ret != 0) | ||
201 | free(response->public_key); | ||
202 | return ret; | ||
203 | } | ||
204 | |||
205 | static int | ||
206 | check_options(struct sk_option **options) | ||
207 | { | ||
208 | size_t i; | ||
209 | |||
210 | if (options == NULL) | ||
211 | return 0; | ||
212 | for (i = 0; options[i] != NULL; i++) { | ||
213 | skdebug(__func__, "requested unsupported option %s", | ||
214 | options[i]->name); | ||
215 | if (options[i]->required) { | ||
216 | skdebug(__func__, "unknown required option"); | ||
217 | return -1; | ||
218 | } | ||
219 | } | ||
220 | return 0; | ||
221 | } | ||
222 | |||
223 | int | ||
224 | sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len, | ||
225 | const char *application, uint8_t flags, const char *pin, | ||
226 | struct sk_option **options, struct sk_enroll_response **enroll_response) | ||
227 | { | ||
228 | struct sk_enroll_response *response = NULL; | ||
229 | int ret = SSH_SK_ERR_GENERAL; | ||
230 | |||
231 | (void)flags; /* XXX; unused */ | ||
232 | |||
233 | if (enroll_response == NULL) { | ||
234 | skdebug(__func__, "enroll_response == NULL"); | ||
235 | goto out; | ||
236 | } | ||
237 | *enroll_response = NULL; | ||
238 | if (check_options(options) != 0) | ||
239 | goto out; /* error already logged */ | ||
240 | if ((response = calloc(1, sizeof(*response))) == NULL) { | ||
241 | skdebug(__func__, "calloc response failed"); | ||
242 | goto out; | ||
243 | } | ||
244 | switch(alg) { | ||
245 | case SSH_SK_ECDSA: | ||
246 | if (pack_key_ecdsa(response) != 0) | ||
247 | goto out; | ||
248 | break; | ||
249 | case SSH_SK_ED25519: | ||
250 | if (pack_key_ed25519(response) != 0) | ||
251 | goto out; | ||
252 | break; | ||
253 | default: | ||
254 | skdebug(__func__, "unsupported key type %d", alg); | ||
255 | return -1; | ||
256 | } | ||
257 | /* Have to return something here */ | ||
258 | if ((response->signature = calloc(1, 1)) == NULL) { | ||
259 | skdebug(__func__, "calloc signature failed"); | ||
260 | goto out; | ||
261 | } | ||
262 | response->signature_len = 0; | ||
263 | |||
264 | *enroll_response = response; | ||
265 | response = NULL; | ||
266 | ret = 0; | ||
267 | out: | ||
268 | if (response != NULL) { | ||
269 | free(response->public_key); | ||
270 | free(response->key_handle); | ||
271 | free(response->signature); | ||
272 | free(response->attestation_cert); | ||
273 | free(response); | ||
274 | } | ||
275 | return ret; | ||
276 | } | ||
277 | |||
278 | static void | ||
279 | dump(const char *preamble, const void *sv, size_t l) | ||
280 | { | ||
281 | #ifdef SK_DEBUG | ||
282 | const u_char *s = (const u_char *)sv; | ||
283 | size_t i; | ||
284 | |||
285 | fprintf(stderr, "%s (len %zu):\n", preamble, l); | ||
286 | for (i = 0; i < l; i++) { | ||
287 | if (i % 16 == 0) | ||
288 | fprintf(stderr, "%04zu: ", i); | ||
289 | fprintf(stderr, "%02x", s[i]); | ||
290 | if (i % 16 == 15 || i == l - 1) | ||
291 | fprintf(stderr, "\n"); | ||
292 | } | ||
293 | #endif | ||
294 | } | ||
295 | |||
296 | static int | ||
297 | sig_ecdsa(const uint8_t *message, size_t message_len, | ||
298 | const char *application, uint32_t counter, uint8_t flags, | ||
299 | const uint8_t *key_handle, size_t key_handle_len, | ||
300 | struct sk_sign_response *response) | ||
301 | { | ||
302 | #ifdef OPENSSL_HAS_ECC | ||
303 | ECDSA_SIG *sig = NULL; | ||
304 | const BIGNUM *sig_r, *sig_s; | ||
305 | int ret = -1; | ||
306 | BIO *bio = NULL; | ||
307 | EVP_PKEY *pk = NULL; | ||
308 | EC_KEY *ec = NULL; | ||
309 | SHA256_CTX ctx; | ||
310 | uint8_t apphash[SHA256_DIGEST_LENGTH]; | ||
311 | uint8_t sighash[SHA256_DIGEST_LENGTH]; | ||
312 | uint8_t countbuf[4]; | ||
313 | |||
314 | /* Decode EC_KEY from key handle */ | ||
315 | if ((bio = BIO_new(BIO_s_mem())) == NULL || | ||
316 | BIO_write(bio, key_handle, key_handle_len) != (int)key_handle_len) { | ||
317 | skdebug(__func__, "BIO setup failed"); | ||
318 | goto out; | ||
319 | } | ||
320 | if ((pk = PEM_read_bio_PrivateKey(bio, NULL, NULL, "")) == NULL) { | ||
321 | skdebug(__func__, "PEM_read_bio_PrivateKey failed"); | ||
322 | goto out; | ||
323 | } | ||
324 | if (EVP_PKEY_base_id(pk) != EVP_PKEY_EC) { | ||
325 | skdebug(__func__, "Not an EC key: %d", EVP_PKEY_base_id(pk)); | ||
326 | goto out; | ||
327 | } | ||
328 | if ((ec = EVP_PKEY_get1_EC_KEY(pk)) == NULL) { | ||
329 | skdebug(__func__, "EVP_PKEY_get1_EC_KEY failed"); | ||
330 | goto out; | ||
331 | } | ||
332 | /* Expect message to be pre-hashed */ | ||
333 | if (message_len != SHA256_DIGEST_LENGTH) { | ||
334 | skdebug(__func__, "bad message len %zu", message_len); | ||
335 | goto out; | ||
336 | } | ||
337 | /* Prepare data to be signed */ | ||
338 | dump("message", message, message_len); | ||
339 | SHA256_Init(&ctx); | ||
340 | SHA256_Update(&ctx, application, strlen(application)); | ||
341 | SHA256_Final(apphash, &ctx); | ||
342 | dump("apphash", apphash, sizeof(apphash)); | ||
343 | countbuf[0] = (counter >> 24) & 0xff; | ||
344 | countbuf[1] = (counter >> 16) & 0xff; | ||
345 | countbuf[2] = (counter >> 8) & 0xff; | ||
346 | countbuf[3] = counter & 0xff; | ||
347 | dump("countbuf", countbuf, sizeof(countbuf)); | ||
348 | dump("flags", &flags, sizeof(flags)); | ||
349 | SHA256_Init(&ctx); | ||
350 | SHA256_Update(&ctx, apphash, sizeof(apphash)); | ||
351 | SHA256_Update(&ctx, &flags, sizeof(flags)); | ||
352 | SHA256_Update(&ctx, countbuf, sizeof(countbuf)); | ||
353 | SHA256_Update(&ctx, message, message_len); | ||
354 | SHA256_Final(sighash, &ctx); | ||
355 | dump("sighash", sighash, sizeof(sighash)); | ||
356 | /* create and encode signature */ | ||
357 | if ((sig = ECDSA_do_sign(sighash, sizeof(sighash), ec)) == NULL) { | ||
358 | skdebug(__func__, "ECDSA_do_sign failed"); | ||
359 | goto out; | ||
360 | } | ||
361 | ECDSA_SIG_get0(sig, &sig_r, &sig_s); | ||
362 | response->sig_r_len = BN_num_bytes(sig_r); | ||
363 | response->sig_s_len = BN_num_bytes(sig_s); | ||
364 | if ((response->sig_r = calloc(1, response->sig_r_len)) == NULL || | ||
365 | (response->sig_s = calloc(1, response->sig_s_len)) == NULL) { | ||
366 | skdebug(__func__, "calloc signature failed"); | ||
367 | goto out; | ||
368 | } | ||
369 | BN_bn2bin(sig_r, response->sig_r); | ||
370 | BN_bn2bin(sig_s, response->sig_s); | ||
371 | ret = 0; | ||
372 | out: | ||
373 | explicit_bzero(&ctx, sizeof(ctx)); | ||
374 | explicit_bzero(&apphash, sizeof(apphash)); | ||
375 | explicit_bzero(&sighash, sizeof(sighash)); | ||
376 | ECDSA_SIG_free(sig); | ||
377 | if (ret != 0) { | ||
378 | free(response->sig_r); | ||
379 | free(response->sig_s); | ||
380 | response->sig_r = NULL; | ||
381 | response->sig_s = NULL; | ||
382 | } | ||
383 | BIO_free(bio); | ||
384 | EC_KEY_free(ec); | ||
385 | EVP_PKEY_free(pk); | ||
386 | return ret; | ||
387 | #else | ||
388 | return -1; | ||
389 | #endif | ||
390 | } | ||
391 | |||
392 | static int | ||
393 | sig_ed25519(const uint8_t *message, size_t message_len, | ||
394 | const char *application, uint32_t counter, uint8_t flags, | ||
395 | const uint8_t *key_handle, size_t key_handle_len, | ||
396 | struct sk_sign_response *response) | ||
397 | { | ||
398 | size_t o; | ||
399 | int ret = -1; | ||
400 | SHA256_CTX ctx; | ||
401 | uint8_t apphash[SHA256_DIGEST_LENGTH]; | ||
402 | uint8_t signbuf[sizeof(apphash) + sizeof(flags) + | ||
403 | sizeof(counter) + SHA256_DIGEST_LENGTH]; | ||
404 | uint8_t sig[crypto_sign_ed25519_BYTES + sizeof(signbuf)]; | ||
405 | unsigned long long smlen; | ||
406 | |||
407 | if (key_handle_len != crypto_sign_ed25519_SECRETKEYBYTES) { | ||
408 | skdebug(__func__, "bad key handle length %zu", key_handle_len); | ||
409 | goto out; | ||
410 | } | ||
411 | /* Expect message to be pre-hashed */ | ||
412 | if (message_len != SHA256_DIGEST_LENGTH) { | ||
413 | skdebug(__func__, "bad message len %zu", message_len); | ||
414 | goto out; | ||
415 | } | ||
416 | /* Prepare data to be signed */ | ||
417 | dump("message", message, message_len); | ||
418 | SHA256_Init(&ctx); | ||
419 | SHA256_Update(&ctx, application, strlen(application)); | ||
420 | SHA256_Final(apphash, &ctx); | ||
421 | dump("apphash", apphash, sizeof(apphash)); | ||
422 | |||
423 | memcpy(signbuf, apphash, sizeof(apphash)); | ||
424 | o = sizeof(apphash); | ||
425 | signbuf[o++] = flags; | ||
426 | signbuf[o++] = (counter >> 24) & 0xff; | ||
427 | signbuf[o++] = (counter >> 16) & 0xff; | ||
428 | signbuf[o++] = (counter >> 8) & 0xff; | ||
429 | signbuf[o++] = counter & 0xff; | ||
430 | memcpy(signbuf + o, message, message_len); | ||
431 | o += message_len; | ||
432 | if (o != sizeof(signbuf)) { | ||
433 | skdebug(__func__, "bad sign buf len %zu, expected %zu", | ||
434 | o, sizeof(signbuf)); | ||
435 | goto out; | ||
436 | } | ||
437 | dump("signbuf", signbuf, sizeof(signbuf)); | ||
438 | /* create and encode signature */ | ||
439 | smlen = sizeof(signbuf); | ||
440 | if (crypto_sign_ed25519(sig, &smlen, signbuf, sizeof(signbuf), | ||
441 | key_handle) != 0) { | ||
442 | skdebug(__func__, "crypto_sign_ed25519 failed"); | ||
443 | goto out; | ||
444 | } | ||
445 | if (smlen <= sizeof(signbuf)) { | ||
446 | skdebug(__func__, "bad sign smlen %llu, expected min %zu", | ||
447 | smlen, sizeof(signbuf) + 1); | ||
448 | goto out; | ||
449 | } | ||
450 | response->sig_r_len = (size_t)(smlen - sizeof(signbuf)); | ||
451 | if ((response->sig_r = calloc(1, response->sig_r_len)) == NULL) { | ||
452 | skdebug(__func__, "calloc signature failed"); | ||
453 | goto out; | ||
454 | } | ||
455 | memcpy(response->sig_r, sig, response->sig_r_len); | ||
456 | dump("sig_r", response->sig_r, response->sig_r_len); | ||
457 | ret = 0; | ||
458 | out: | ||
459 | explicit_bzero(&ctx, sizeof(ctx)); | ||
460 | explicit_bzero(&apphash, sizeof(apphash)); | ||
461 | explicit_bzero(&signbuf, sizeof(signbuf)); | ||
462 | explicit_bzero(&sig, sizeof(sig)); | ||
463 | if (ret != 0) { | ||
464 | free(response->sig_r); | ||
465 | response->sig_r = NULL; | ||
466 | } | ||
467 | return ret; | ||
468 | } | ||
469 | |||
470 | int | ||
471 | sk_sign(uint32_t alg, const uint8_t *message, size_t message_len, | ||
472 | const char *application, const uint8_t *key_handle, size_t key_handle_len, | ||
473 | uint8_t flags, const char *pin, struct sk_option **options, | ||
474 | struct sk_sign_response **sign_response) | ||
475 | { | ||
476 | struct sk_sign_response *response = NULL; | ||
477 | int ret = SSH_SK_ERR_GENERAL; | ||
478 | |||
479 | if (sign_response == NULL) { | ||
480 | skdebug(__func__, "sign_response == NULL"); | ||
481 | goto out; | ||
482 | } | ||
483 | *sign_response = NULL; | ||
484 | if (check_options(options) != 0) | ||
485 | goto out; /* error already logged */ | ||
486 | if ((response = calloc(1, sizeof(*response))) == NULL) { | ||
487 | skdebug(__func__, "calloc response failed"); | ||
488 | goto out; | ||
489 | } | ||
490 | response->flags = flags; | ||
491 | response->counter = 0x12345678; | ||
492 | switch(alg) { | ||
493 | case SSH_SK_ECDSA: | ||
494 | if (sig_ecdsa(message, message_len, application, | ||
495 | response->counter, flags, key_handle, key_handle_len, | ||
496 | response) != 0) | ||
497 | goto out; | ||
498 | break; | ||
499 | case SSH_SK_ED25519: | ||
500 | if (sig_ed25519(message, message_len, application, | ||
501 | response->counter, flags, key_handle, key_handle_len, | ||
502 | response) != 0) | ||
503 | goto out; | ||
504 | break; | ||
505 | default: | ||
506 | skdebug(__func__, "unsupported key type %d", alg); | ||
507 | return -1; | ||
508 | } | ||
509 | *sign_response = response; | ||
510 | response = NULL; | ||
511 | ret = 0; | ||
512 | out: | ||
513 | if (response != NULL) { | ||
514 | free(response->sig_r); | ||
515 | free(response->sig_s); | ||
516 | free(response); | ||
517 | } | ||
518 | return ret; | ||
519 | } | ||
520 | |||
521 | int | ||
522 | sk_load_resident_keys(const char *pin, struct sk_option **options, | ||
523 | struct sk_resident_key ***rks, size_t *nrks) | ||
524 | { | ||
525 | return SSH_SK_ERR_UNSUPPORTED; | ||
526 | } | ||