Annotation of src/usr.bin/ssh/sk-usbhid.c, Revision 1.24
1.1 djm 1: /*
2: * Copyright (c) 2019 Markus Friedl
1.22 djm 3: * Copyright (c) 2020 Pedro Martelletto
1.1 djm 4: *
5: * Permission to use, copy, modify, and distribute this software for any
6: * purpose with or without fee is hereby granted, provided that the above
7: * copyright notice and this permission notice appear in all copies.
8: *
9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16: */
17:
18: #include <stdint.h>
19: #include <stdlib.h>
20: #include <string.h>
21: #include <stdio.h>
22: #include <stddef.h>
23: #include <stdarg.h>
1.17 djm 24: #include <sha2.h>
1.1 djm 25:
1.7 naddy 26: #ifdef WITH_OPENSSL
1.1 djm 27: #include <openssl/opensslv.h>
28: #include <openssl/crypto.h>
29: #include <openssl/bn.h>
30: #include <openssl/ec.h>
31: #include <openssl/ecdsa.h>
1.17 djm 32: #include <openssl/evp.h>
1.7 naddy 33: #endif /* WITH_OPENSSL */
1.1 djm 34:
35: #include <fido.h>
1.9 djm 36: #include <fido/credman.h>
1.1 djm 37:
38: #ifndef SK_STANDALONE
1.15 djm 39: # include "log.h"
40: # include "xmalloc.h"
1.22 djm 41: # include "misc.h"
1.15 djm 42: /*
43: * If building as part of OpenSSH, then rename exported functions.
44: * This must be done before including sk-api.h.
45: */
46: # define sk_api_version ssh_sk_api_version
47: # define sk_enroll ssh_sk_enroll
48: # define sk_sign ssh_sk_sign
49: # define sk_load_resident_keys ssh_sk_load_resident_keys
50: #endif /* !SK_STANDALONE */
51:
52: #include "sk-api.h"
1.1 djm 53:
54: /* #define SK_DEBUG 1 */
55:
1.18 djm 56: #ifdef SK_DEBUG
57: #define SSH_FIDO_INIT_ARG FIDO_DEBUG
58: #else
59: #define SSH_FIDO_INIT_ARG 0
60: #endif
61:
1.22 djm 62: #define MAX_FIDO_DEVICES 8
63: #define FIDO_POLL_MS 50
64: #define SELECT_MS 15000
65: #define POLL_SLEEP_NS 200000000
1.1 djm 66:
67: /* Compatibility with OpenSSH 1.0.x */
68: #if (OPENSSL_VERSION_NUMBER < 0x10100000L)
69: #define ECDSA_SIG_get0(sig, pr, ps) \
70: do { \
71: (*pr) = sig->r; \
72: (*ps) = sig->s; \
73: } while (0)
74: #endif
75:
1.22 djm 76: struct sk_usbhid {
77: fido_dev_t *dev;
78: char *path;
79: };
80:
1.1 djm 81: /* Return the version of the middleware API */
82: uint32_t sk_api_version(void);
83:
84: /* Enroll a U2F key (private key generation) */
1.12 djm 85: int sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len,
1.10 djm 86: const char *application, uint8_t flags, const char *pin,
1.12 djm 87: struct sk_option **options, struct sk_enroll_response **enroll_response);
1.1 djm 88:
89: /* Sign a challenge */
1.12 djm 90: int sk_sign(uint32_t alg, const uint8_t *message, size_t message_len,
1.1 djm 91: const char *application, const uint8_t *key_handle, size_t key_handle_len,
1.12 djm 92: uint8_t flags, const char *pin, struct sk_option **options,
93: struct sk_sign_response **sign_response);
1.1 djm 94:
1.9 djm 95: /* Load resident keys */
1.12 djm 96: int sk_load_resident_keys(const char *pin, struct sk_option **options,
1.9 djm 97: struct sk_resident_key ***rks, size_t *nrks);
98:
1.1 djm 99: static void skdebug(const char *func, const char *fmt, ...)
100: __attribute__((__format__ (printf, 2, 3)));
101:
102: static void
103: skdebug(const char *func, const char *fmt, ...)
104: {
105: #if !defined(SK_STANDALONE)
106: char *msg;
107: va_list ap;
108:
109: va_start(ap, fmt);
110: xvasprintf(&msg, fmt, ap);
111: va_end(ap);
1.2 djm 112: debug("%s: %s", func, msg);
1.1 djm 113: free(msg);
114: #elif defined(SK_DEBUG)
115: va_list ap;
116:
117: va_start(ap, fmt);
118: fprintf(stderr, "%s: ", func);
119: vfprintf(stderr, fmt, ap);
120: fputc('\n', stderr);
121: va_end(ap);
122: #else
123: (void)func; /* XXX */
124: (void)fmt; /* XXX */
125: #endif
126: }
127:
128: uint32_t
129: sk_api_version(void)
130: {
1.15 djm 131: return SSH_SK_VERSION_MAJOR;
1.1 djm 132: }
133:
1.22 djm 134: static struct sk_usbhid *
135: sk_open(const char *path)
136: {
137: struct sk_usbhid *sk;
1.1 djm 138: int r;
139:
1.22 djm 140: if (path == NULL) {
141: skdebug(__func__, "path == NULL");
142: return NULL;
143: }
144: if ((sk = calloc(1, sizeof(*sk))) == NULL) {
145: skdebug(__func__, "calloc sk failed");
146: return NULL;
147: }
148: if ((sk->path = strdup(path)) == NULL) {
149: skdebug(__func__, "strdup path failed");
150: free(sk);
151: return NULL;
152: }
153: if ((sk->dev = fido_dev_new()) == NULL) {
154: skdebug(__func__, "fido_dev_new failed");
155: free(sk->path);
156: free(sk);
157: return NULL;
1.1 djm 158: }
1.22 djm 159: if ((r = fido_dev_open(sk->dev, sk->path)) != FIDO_OK) {
160: skdebug(__func__, "fido_dev_open %s failed: %s", sk->path,
1.1 djm 161: fido_strerr(r));
1.22 djm 162: fido_dev_free(&sk->dev);
163: free(sk->path);
164: free(sk);
165: return NULL;
166: }
167: return sk;
168: }
169:
170: static void
171: sk_close(struct sk_usbhid *sk)
172: {
173: if (sk == NULL)
174: return;
175: fido_dev_cancel(sk->dev); /* cancel any pending operation */
176: fido_dev_close(sk->dev);
177: fido_dev_free(&sk->dev);
178: free(sk->path);
179: free(sk);
180: }
181:
182: static struct sk_usbhid **
183: sk_openv(const fido_dev_info_t *devlist, size_t ndevs, size_t *nopen)
184: {
185: const fido_dev_info_t *di;
186: struct sk_usbhid **skv;
187: size_t i;
188:
189: *nopen = 0;
190: if ((skv = calloc(ndevs, sizeof(*skv))) == NULL) {
191: skdebug(__func__, "calloc skv failed");
192: return NULL;
193: }
194: for (i = 0; i < ndevs; i++) {
195: if ((di = fido_dev_info_ptr(devlist, i)) == NULL)
196: skdebug(__func__, "fido_dev_info_ptr failed");
197: else if ((skv[*nopen] = sk_open(fido_dev_info_path(di))) == NULL)
198: skdebug(__func__, "sk_open failed");
199: else
200: (*nopen)++;
1.1 djm 201: }
1.22 djm 202: if (*nopen == 0) {
203: for (i = 0; i < ndevs; i++)
204: sk_close(skv[i]);
205: free(skv);
206: skv = NULL;
1.1 djm 207: }
1.22 djm 208:
209: return skv;
210: }
211:
212: static void
213: sk_closev(struct sk_usbhid **skv, size_t nsk)
214: {
215: size_t i;
216:
217: for (i = 0; i < nsk; i++)
218: sk_close(skv[i]);
219: free(skv);
220: }
221:
222: static int
223: sk_touch_begin(struct sk_usbhid **skv, size_t nsk)
224: {
225: size_t i, ok = 0;
226: int r;
227:
228: for (i = 0; i < nsk; i++)
229: if ((r = fido_dev_get_touch_begin(skv[i]->dev)) != FIDO_OK)
230: skdebug(__func__, "fido_dev_get_touch_begin %s failed:"
231: " %s", skv[i]->path, fido_strerr(r));
232: else
233: ok++;
234:
235: return ok ? 0 : -1;
236: }
237:
238: static int
239: sk_touch_poll(struct sk_usbhid **skv, size_t nsk, int *touch, size_t *idx)
240: {
241: struct timespec ts_pause;
242: size_t npoll, i;
243: int r;
244:
245: ts_pause.tv_sec = 0;
246: ts_pause.tv_nsec = POLL_SLEEP_NS;
247: nanosleep(&ts_pause, NULL);
248: npoll = nsk;
249: for (i = 0; i < nsk; i++) {
250: if (skv[i] == NULL)
251: continue; /* device discarded */
252: skdebug(__func__, "polling %s", skv[i]->path);
253: if ((r = fido_dev_get_touch_status(skv[i]->dev, touch,
254: FIDO_POLL_MS)) != FIDO_OK) {
255: skdebug(__func__, "fido_dev_get_touch_status %s: %s",
256: skv[i]->path, fido_strerr(r));
257: sk_close(skv[i]); /* discard device */
258: skv[i] = NULL;
259: if (--npoll == 0) {
260: skdebug(__func__, "no device left to poll");
261: return -1;
262: }
263: } else if (*touch) {
264: *idx = i;
265: return 0;
266: }
1.1 djm 267: }
1.22 djm 268: *touch = 0;
269: return 0;
270: }
271:
272: /* Calculate SHA256(m) */
273: static int
274: sha256_mem(const void *m, size_t mlen, u_char *d, size_t dlen)
275: {
276: #ifdef WITH_OPENSSL
277: u_int mdlen;
278: #endif
279:
280: if (dlen != 32)
281: return -1;
282: #ifdef WITH_OPENSSL
283: mdlen = dlen;
284: if (!EVP_Digest(m, mlen, d, &mdlen, EVP_sha256(), NULL))
285: return -1;
286: #else
287: SHA256Data(m, mlen, d);
288: #endif
289: return 0;
1.1 djm 290: }
291:
1.22 djm 292: /* Check if the specified key handle exists on a given sk. */
1.1 djm 293: static int
1.22 djm 294: sk_try(const struct sk_usbhid *sk, const char *application,
295: const uint8_t *key_handle, size_t key_handle_len)
1.1 djm 296: {
297: fido_assert_t *assert = NULL;
1.22 djm 298: /* generate an invalid signature on FIDO2 tokens */
299: const char *data = "";
300: uint8_t message[32];
1.1 djm 301: int r = FIDO_ERR_INTERNAL;
302:
1.22 djm 303: if (sha256_mem(data, strlen(data), message, sizeof(message)) != 0) {
304: skdebug(__func__, "hash message failed");
305: goto out;
306: }
1.1 djm 307: if ((assert = fido_assert_new()) == NULL) {
308: skdebug(__func__, "fido_assert_new failed");
309: goto out;
310: }
311: if ((r = fido_assert_set_clientdata_hash(assert, message,
1.22 djm 312: sizeof(message))) != FIDO_OK) {
1.1 djm 313: skdebug(__func__, "fido_assert_set_clientdata_hash: %s",
314: fido_strerr(r));
315: goto out;
316: }
317: if ((r = fido_assert_set_rp(assert, application)) != FIDO_OK) {
318: skdebug(__func__, "fido_assert_set_rp: %s", fido_strerr(r));
319: goto out;
320: }
321: if ((r = fido_assert_allow_cred(assert, key_handle,
322: key_handle_len)) != FIDO_OK) {
323: skdebug(__func__, "fido_assert_allow_cred: %s", fido_strerr(r));
324: goto out;
325: }
326: if ((r = fido_assert_set_up(assert, FIDO_OPT_FALSE)) != FIDO_OK) {
327: skdebug(__func__, "fido_assert_up: %s", fido_strerr(r));
328: goto out;
329: }
1.22 djm 330: r = fido_dev_get_assert(sk->dev, assert, NULL);
1.1 djm 331: skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r));
1.3 djm 332: if (r == FIDO_ERR_USER_PRESENCE_REQUIRED) {
333: /* U2F tokens may return this */
334: r = FIDO_OK;
335: }
1.1 djm 336: out:
337: fido_assert_free(&assert);
338:
339: return r != FIDO_OK ? -1 : 0;
340: }
341:
1.22 djm 342: static struct sk_usbhid *
343: sk_select_by_cred(const fido_dev_info_t *devlist, size_t ndevs,
344: const char *application, const uint8_t *key_handle, size_t key_handle_len)
1.1 djm 345: {
1.22 djm 346: struct sk_usbhid **skv, *sk;
347: size_t skvcnt, i;
1.1 djm 348:
1.22 djm 349: if ((skv = sk_openv(devlist, ndevs, &skvcnt)) == NULL) {
350: skdebug(__func__, "sk_openv failed");
351: return NULL;
352: }
1.24 ! djm 353: if (skvcnt == 1) {
! 354: sk = skv[0];
! 355: skv[0] = NULL;
! 356: goto out;
! 357: }
1.22 djm 358: sk = NULL;
1.24 ! djm 359: for (i = 0; i < skvcnt; i++) {
1.22 djm 360: if (sk_try(skv[i], application, key_handle,
361: key_handle_len) == 0) {
362: sk = skv[i];
363: skv[i] = NULL;
364: skdebug(__func__, "found key in %s", sk->path);
365: break;
1.12 djm 366: }
1.24 ! djm 367: }
! 368: out:
1.22 djm 369: sk_closev(skv, skvcnt);
370: return sk;
371: }
372:
373: static struct sk_usbhid *
374: sk_select_by_touch(const fido_dev_info_t *devlist, size_t ndevs)
375: {
376: struct sk_usbhid **skv, *sk;
377: struct timeval tv_start, tv_now, tv_delta;
378: size_t skvcnt, idx;
379: int touch, ms_remain;
380:
381: if ((skv = sk_openv(devlist, ndevs, &skvcnt)) == NULL) {
382: skdebug(__func__, "sk_openv failed");
383: return NULL;
384: }
385: sk = NULL;
386: if (skvcnt < 2) {
387: if (skvcnt == 1) {
388: /* single candidate */
389: sk = skv[0];
390: skv[0] = NULL;
1.12 djm 391: }
1.22 djm 392: goto out;
1.12 djm 393: }
1.22 djm 394: if (sk_touch_begin(skv, skvcnt) == -1) {
395: skdebug(__func__, "sk_touch_begin failed");
396: goto out;
397: }
398: monotime_tv(&tv_start);
399: do {
400: if (sk_touch_poll(skv, skvcnt, &touch, &idx) == -1) {
401: skdebug(__func__, "sk_touch_poll failed");
402: goto out;
403: }
404: if (touch) {
405: sk = skv[idx];
406: skv[idx] = NULL;
407: goto out;
408: }
409: monotime_tv(&tv_now);
410: timersub(&tv_now, &tv_start, &tv_delta);
411: ms_remain = SELECT_MS - tv_delta.tv_sec * 1000 -
412: tv_delta.tv_usec / 1000;
413: } while (ms_remain >= FIDO_POLL_MS);
414: skdebug(__func__, "timeout");
415: out:
416: sk_closev(skv, skvcnt);
417: return sk;
418: }
419:
420: static struct sk_usbhid *
421: sk_probe(const char *application, const uint8_t *key_handle,
422: size_t key_handle_len)
423: {
424: struct sk_usbhid *sk;
425: fido_dev_info_t *devlist;
426: size_t ndevs;
427: int r;
1.12 djm 428:
1.1 djm 429: if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) {
430: skdebug(__func__, "fido_dev_info_new failed");
1.22 djm 431: return NULL;
1.1 djm 432: }
433: if ((r = fido_dev_info_manifest(devlist, MAX_FIDO_DEVICES,
1.22 djm 434: &ndevs)) != FIDO_OK) {
435: skdebug(__func__, "fido_dev_info_manifest failed: %s",
436: fido_strerr(r));
437: fido_dev_info_free(&devlist, MAX_FIDO_DEVICES);
438: return NULL;
1.1 djm 439: }
1.22 djm 440: skdebug(__func__, "%zu device(s) detected", ndevs);
441: if (ndevs == 0) {
442: sk = NULL;
443: } else if (application != NULL && key_handle != NULL) {
444: skdebug(__func__, "selecting sk by cred");
445: sk = sk_select_by_cred(devlist, ndevs, application, key_handle,
446: key_handle_len);
447: } else {
448: skdebug(__func__, "selecting sk by touch");
449: sk = sk_select_by_touch(devlist, ndevs);
1.1 djm 450: }
1.22 djm 451: fido_dev_info_free(&devlist, MAX_FIDO_DEVICES);
452: return sk;
1.1 djm 453: }
454:
1.7 naddy 455: #ifdef WITH_OPENSSL
1.1 djm 456: /*
457: * The key returned via fido_cred_pubkey_ptr() is in affine coordinates,
458: * but the API expects a SEC1 octet string.
459: */
460: static int
1.9 djm 461: pack_public_key_ecdsa(const fido_cred_t *cred,
462: struct sk_enroll_response *response)
1.1 djm 463: {
464: const uint8_t *ptr;
465: BIGNUM *x = NULL, *y = NULL;
466: EC_POINT *q = NULL;
467: EC_GROUP *g = NULL;
468: int ret = -1;
469:
470: response->public_key = NULL;
471: response->public_key_len = 0;
472:
1.5 djm 473: if ((x = BN_new()) == NULL ||
474: (y = BN_new()) == NULL ||
1.1 djm 475: (g = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)) == NULL ||
476: (q = EC_POINT_new(g)) == NULL) {
477: skdebug(__func__, "libcrypto setup failed");
478: goto out;
479: }
480: if ((ptr = fido_cred_pubkey_ptr(cred)) == NULL) {
481: skdebug(__func__, "fido_cred_pubkey_ptr failed");
482: goto out;
483: }
484: if (fido_cred_pubkey_len(cred) != 64) {
485: skdebug(__func__, "bad fido_cred_pubkey_len %zu",
486: fido_cred_pubkey_len(cred));
487: goto out;
488: }
489:
490: if (BN_bin2bn(ptr, 32, x) == NULL ||
491: BN_bin2bn(ptr + 32, 32, y) == NULL) {
492: skdebug(__func__, "BN_bin2bn failed");
493: goto out;
494: }
1.5 djm 495: if (EC_POINT_set_affine_coordinates_GFp(g, q, x, y, NULL) != 1) {
1.1 djm 496: skdebug(__func__, "EC_POINT_set_affine_coordinates_GFp failed");
497: goto out;
498: }
499: response->public_key_len = EC_POINT_point2oct(g, q,
1.5 djm 500: POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL);
1.1 djm 501: if (response->public_key_len == 0 || response->public_key_len > 2048) {
502: skdebug(__func__, "bad pubkey length %zu",
503: response->public_key_len);
504: goto out;
505: }
506: if ((response->public_key = malloc(response->public_key_len)) == NULL) {
507: skdebug(__func__, "malloc pubkey failed");
508: goto out;
509: }
510: if (EC_POINT_point2oct(g, q, POINT_CONVERSION_UNCOMPRESSED,
1.5 djm 511: response->public_key, response->public_key_len, NULL) == 0) {
1.1 djm 512: skdebug(__func__, "EC_POINT_point2oct failed");
513: goto out;
514: }
515: /* success */
516: ret = 0;
517: out:
518: if (ret != 0 && response->public_key != NULL) {
519: memset(response->public_key, 0, response->public_key_len);
520: free(response->public_key);
521: response->public_key = NULL;
522: }
523: EC_POINT_free(q);
524: EC_GROUP_free(g);
1.5 djm 525: BN_clear_free(x);
526: BN_clear_free(y);
1.1 djm 527: return ret;
528: }
1.7 naddy 529: #endif /* WITH_OPENSSL */
1.1 djm 530:
531: static int
1.9 djm 532: pack_public_key_ed25519(const fido_cred_t *cred,
533: struct sk_enroll_response *response)
1.1 djm 534: {
535: const uint8_t *ptr;
536: size_t len;
537: int ret = -1;
538:
539: response->public_key = NULL;
540: response->public_key_len = 0;
541:
542: if ((len = fido_cred_pubkey_len(cred)) != 32) {
543: skdebug(__func__, "bad fido_cred_pubkey_len len %zu", len);
544: goto out;
545: }
546: if ((ptr = fido_cred_pubkey_ptr(cred)) == NULL) {
547: skdebug(__func__, "fido_cred_pubkey_ptr failed");
548: goto out;
549: }
550: response->public_key_len = len;
551: if ((response->public_key = malloc(response->public_key_len)) == NULL) {
552: skdebug(__func__, "malloc pubkey failed");
553: goto out;
554: }
555: memcpy(response->public_key, ptr, len);
556: ret = 0;
557: out:
558: if (ret != 0)
559: free(response->public_key);
560: return ret;
561: }
562:
563: static int
1.12 djm 564: pack_public_key(uint32_t alg, const fido_cred_t *cred,
1.9 djm 565: struct sk_enroll_response *response)
1.1 djm 566: {
567: switch(alg) {
1.7 naddy 568: #ifdef WITH_OPENSSL
1.15 djm 569: case SSH_SK_ECDSA:
1.1 djm 570: return pack_public_key_ecdsa(cred, response);
1.7 naddy 571: #endif /* WITH_OPENSSL */
1.15 djm 572: case SSH_SK_ED25519:
1.1 djm 573: return pack_public_key_ed25519(cred, response);
574: default:
575: return -1;
576: }
577: }
578:
1.11 djm 579: static int
580: fidoerr_to_skerr(int fidoerr)
581: {
582: switch (fidoerr) {
583: case FIDO_ERR_UNSUPPORTED_OPTION:
1.15 djm 584: case FIDO_ERR_UNSUPPORTED_ALGORITHM:
1.11 djm 585: return SSH_SK_ERR_UNSUPPORTED;
586: case FIDO_ERR_PIN_REQUIRED:
587: case FIDO_ERR_PIN_INVALID:
588: return SSH_SK_ERR_PIN_REQUIRED;
589: default:
590: return -1;
591: }
592: }
593:
1.12 djm 594: static int
595: check_enroll_options(struct sk_option **options, char **devicep,
596: uint8_t *user_id, size_t user_id_len)
597: {
598: size_t i;
599:
600: if (options == NULL)
601: return 0;
602: for (i = 0; options[i] != NULL; i++) {
603: if (strcmp(options[i]->name, "device") == 0) {
604: if ((*devicep = strdup(options[i]->value)) == NULL) {
605: skdebug(__func__, "strdup device failed");
606: return -1;
607: }
608: skdebug(__func__, "requested device %s", *devicep);
1.14 djm 609: } else if (strcmp(options[i]->name, "user") == 0) {
1.12 djm 610: if (strlcpy(user_id, options[i]->value, user_id_len) >=
611: user_id_len) {
612: skdebug(__func__, "user too long");
613: return -1;
614: }
615: skdebug(__func__, "requested user %s",
616: (char *)user_id);
617: } else {
618: skdebug(__func__, "requested unsupported option %s",
619: options[i]->name);
620: if (options[i]->required) {
621: skdebug(__func__, "unknown required option");
622: return -1;
623: }
624: }
625: }
626: return 0;
627: }
628:
1.1 djm 629: int
1.12 djm 630: sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len,
1.10 djm 631: const char *application, uint8_t flags, const char *pin,
1.12 djm 632: struct sk_option **options, struct sk_enroll_response **enroll_response)
1.1 djm 633: {
634: fido_cred_t *cred = NULL;
635: const uint8_t *ptr;
636: uint8_t user_id[32];
1.22 djm 637: struct sk_usbhid *sk = NULL;
1.1 djm 638: struct sk_enroll_response *response = NULL;
639: size_t len;
1.19 djm 640: int credprot;
1.1 djm 641: int cose_alg;
1.11 djm 642: int ret = SSH_SK_ERR_GENERAL;
1.1 djm 643: int r;
644: char *device = NULL;
645:
1.18 djm 646: fido_init(SSH_FIDO_INIT_ARG);
647:
1.6 markus 648: if (enroll_response == NULL) {
649: skdebug(__func__, "enroll_response == NULL");
1.1 djm 650: goto out;
651: }
1.22 djm 652: *enroll_response = NULL;
1.12 djm 653: memset(user_id, 0, sizeof(user_id));
1.22 djm 654: if (check_enroll_options(options, &device, user_id,
655: sizeof(user_id)) != 0)
1.12 djm 656: goto out; /* error already logged */
657:
1.1 djm 658: switch(alg) {
1.7 naddy 659: #ifdef WITH_OPENSSL
1.15 djm 660: case SSH_SK_ECDSA:
1.1 djm 661: cose_alg = COSE_ES256;
662: break;
1.7 naddy 663: #endif /* WITH_OPENSSL */
1.15 djm 664: case SSH_SK_ED25519:
1.1 djm 665: cose_alg = COSE_EDDSA;
666: break;
667: default:
668: skdebug(__func__, "unsupported key type %d", alg);
669: goto out;
670: }
1.22 djm 671: if (device != NULL)
672: sk = sk_open(device);
673: else
674: sk = sk_probe(NULL, NULL, 0);
675: if (sk == NULL) {
676: skdebug(__func__, "failed to find sk");
1.1 djm 677: goto out;
678: }
1.22 djm 679: skdebug(__func__, "using device %s", sk->path);
1.1 djm 680: if ((cred = fido_cred_new()) == NULL) {
681: skdebug(__func__, "fido_cred_new failed");
682: goto out;
683: }
684: if ((r = fido_cred_set_type(cred, cose_alg)) != FIDO_OK) {
685: skdebug(__func__, "fido_cred_set_type: %s", fido_strerr(r));
686: goto out;
687: }
688: if ((r = fido_cred_set_clientdata_hash(cred, challenge,
689: challenge_len)) != FIDO_OK) {
690: skdebug(__func__, "fido_cred_set_clientdata_hash: %s",
691: fido_strerr(r));
1.8 djm 692: goto out;
693: }
1.15 djm 694: if ((r = fido_cred_set_rk(cred, (flags & SSH_SK_RESIDENT_KEY) != 0 ?
1.8 djm 695: FIDO_OPT_TRUE : FIDO_OPT_OMIT)) != FIDO_OK) {
696: skdebug(__func__, "fido_cred_set_rk: %s", fido_strerr(r));
1.1 djm 697: goto out;
698: }
699: if ((r = fido_cred_set_user(cred, user_id, sizeof(user_id),
700: "openssh", "openssh", NULL)) != FIDO_OK) {
701: skdebug(__func__, "fido_cred_set_user: %s", fido_strerr(r));
702: goto out;
703: }
704: if ((r = fido_cred_set_rp(cred, application, NULL)) != FIDO_OK) {
705: skdebug(__func__, "fido_cred_set_rp: %s", fido_strerr(r));
706: goto out;
707: }
1.21 djm 708: if ((flags & (SSH_SK_RESIDENT_KEY|SSH_SK_USER_VERIFICATION_REQD)) != 0) {
1.22 djm 709: if (!fido_dev_supports_cred_prot(sk->dev)) {
710: skdebug(__func__, "%s does not support credprot, "
711: "refusing to create unprotected "
712: "resident/verify-required key", sk->path);
1.19 djm 713: ret = SSH_SK_ERR_UNSUPPORTED;
714: goto out;
715: }
1.21 djm 716: if ((flags & SSH_SK_USER_VERIFICATION_REQD))
717: credprot = FIDO_CRED_PROT_UV_REQUIRED;
718: else
719: credprot = FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID;
720:
721: if ((r = fido_cred_set_prot(cred, credprot)) != FIDO_OK) {
1.19 djm 722: skdebug(__func__, "fido_cred_set_prot: %s",
723: fido_strerr(r));
724: ret = fidoerr_to_skerr(r);
725: goto out;
726: }
1.1 djm 727: }
1.22 djm 728: if ((r = fido_dev_make_cred(sk->dev, cred, pin)) != FIDO_OK) {
1.1 djm 729: skdebug(__func__, "fido_dev_make_cred: %s", fido_strerr(r));
1.11 djm 730: ret = fidoerr_to_skerr(r);
1.1 djm 731: goto out;
732: }
733: if (fido_cred_x5c_ptr(cred) != NULL) {
734: if ((r = fido_cred_verify(cred)) != FIDO_OK) {
735: skdebug(__func__, "fido_cred_verify: %s",
736: fido_strerr(r));
737: goto out;
738: }
739: } else {
740: skdebug(__func__, "self-attested credential");
741: if ((r = fido_cred_verify_self(cred)) != FIDO_OK) {
742: skdebug(__func__, "fido_cred_verify_self: %s",
743: fido_strerr(r));
744: goto out;
745: }
746: }
747: if ((response = calloc(1, sizeof(*response))) == NULL) {
748: skdebug(__func__, "calloc response failed");
749: goto out;
750: }
751: if (pack_public_key(alg, cred, response) != 0) {
752: skdebug(__func__, "pack_public_key failed");
753: goto out;
754: }
755: if ((ptr = fido_cred_id_ptr(cred)) != NULL) {
756: len = fido_cred_id_len(cred);
757: if ((response->key_handle = calloc(1, len)) == NULL) {
758: skdebug(__func__, "calloc key handle failed");
759: goto out;
760: }
761: memcpy(response->key_handle, ptr, len);
762: response->key_handle_len = len;
763: }
764: if ((ptr = fido_cred_sig_ptr(cred)) != NULL) {
765: len = fido_cred_sig_len(cred);
766: if ((response->signature = calloc(1, len)) == NULL) {
767: skdebug(__func__, "calloc signature failed");
768: goto out;
769: }
770: memcpy(response->signature, ptr, len);
771: response->signature_len = len;
772: }
773: if ((ptr = fido_cred_x5c_ptr(cred)) != NULL) {
774: len = fido_cred_x5c_len(cred);
1.16 djm 775: debug3("%s: attestation cert len=%zu", __func__, len);
1.1 djm 776: if ((response->attestation_cert = calloc(1, len)) == NULL) {
777: skdebug(__func__, "calloc attestation cert failed");
778: goto out;
779: }
780: memcpy(response->attestation_cert, ptr, len);
781: response->attestation_cert_len = len;
782: }
1.6 markus 783: *enroll_response = response;
1.1 djm 784: response = NULL;
785: ret = 0;
786: out:
787: free(device);
788: if (response != NULL) {
789: free(response->public_key);
790: free(response->key_handle);
791: free(response->signature);
792: free(response->attestation_cert);
793: free(response);
794: }
1.22 djm 795: sk_close(sk);
796: fido_cred_free(&cred);
1.1 djm 797: return ret;
798: }
799:
1.7 naddy 800: #ifdef WITH_OPENSSL
1.1 djm 801: static int
802: pack_sig_ecdsa(fido_assert_t *assert, struct sk_sign_response *response)
803: {
804: ECDSA_SIG *sig = NULL;
805: const BIGNUM *sig_r, *sig_s;
806: const unsigned char *cp;
807: size_t sig_len;
808: int ret = -1;
809:
810: cp = fido_assert_sig_ptr(assert, 0);
811: sig_len = fido_assert_sig_len(assert, 0);
812: if ((sig = d2i_ECDSA_SIG(NULL, &cp, sig_len)) == NULL) {
813: skdebug(__func__, "d2i_ECDSA_SIG failed");
814: goto out;
815: }
816: ECDSA_SIG_get0(sig, &sig_r, &sig_s);
817: response->sig_r_len = BN_num_bytes(sig_r);
818: response->sig_s_len = BN_num_bytes(sig_s);
819: if ((response->sig_r = calloc(1, response->sig_r_len)) == NULL ||
820: (response->sig_s = calloc(1, response->sig_s_len)) == NULL) {
821: skdebug(__func__, "calloc signature failed");
822: goto out;
823: }
824: BN_bn2bin(sig_r, response->sig_r);
825: BN_bn2bin(sig_s, response->sig_s);
826: ret = 0;
827: out:
828: ECDSA_SIG_free(sig);
829: if (ret != 0) {
830: free(response->sig_r);
831: free(response->sig_s);
832: response->sig_r = NULL;
833: response->sig_s = NULL;
834: }
835: return ret;
836: }
1.7 naddy 837: #endif /* WITH_OPENSSL */
1.1 djm 838:
839: static int
840: pack_sig_ed25519(fido_assert_t *assert, struct sk_sign_response *response)
841: {
842: const unsigned char *ptr;
843: size_t len;
844: int ret = -1;
845:
846: ptr = fido_assert_sig_ptr(assert, 0);
847: len = fido_assert_sig_len(assert, 0);
848: if (len != 64) {
849: skdebug(__func__, "bad length %zu", len);
850: goto out;
851: }
852: response->sig_r_len = len;
853: if ((response->sig_r = calloc(1, response->sig_r_len)) == NULL) {
854: skdebug(__func__, "calloc signature failed");
855: goto out;
856: }
857: memcpy(response->sig_r, ptr, len);
858: ret = 0;
859: out:
860: if (ret != 0) {
861: free(response->sig_r);
862: response->sig_r = NULL;
863: }
864: return ret;
865: }
866:
867: static int
1.12 djm 868: pack_sig(uint32_t alg, fido_assert_t *assert,
869: struct sk_sign_response *response)
1.1 djm 870: {
871: switch(alg) {
1.7 naddy 872: #ifdef WITH_OPENSSL
1.15 djm 873: case SSH_SK_ECDSA:
1.1 djm 874: return pack_sig_ecdsa(assert, response);
1.7 naddy 875: #endif /* WITH_OPENSSL */
1.15 djm 876: case SSH_SK_ED25519:
1.1 djm 877: return pack_sig_ed25519(assert, response);
878: default:
879: return -1;
880: }
881: }
882:
1.12 djm 883: /* Checks sk_options for sk_sign() and sk_load_resident_keys() */
884: static int
885: check_sign_load_resident_options(struct sk_option **options, char **devicep)
886: {
887: size_t i;
888:
889: if (options == NULL)
890: return 0;
891: for (i = 0; options[i] != NULL; i++) {
892: if (strcmp(options[i]->name, "device") == 0) {
893: if ((*devicep = strdup(options[i]->value)) == NULL) {
894: skdebug(__func__, "strdup device failed");
895: return -1;
896: }
897: skdebug(__func__, "requested device %s", *devicep);
898: } else {
899: skdebug(__func__, "requested unsupported option %s",
900: options[i]->name);
901: if (options[i]->required) {
902: skdebug(__func__, "unknown required option");
903: return -1;
904: }
905: }
906: }
907: return 0;
908: }
909:
1.1 djm 910: int
1.17 djm 911: sk_sign(uint32_t alg, const uint8_t *data, size_t datalen,
1.1 djm 912: const char *application,
913: const uint8_t *key_handle, size_t key_handle_len,
1.12 djm 914: uint8_t flags, const char *pin, struct sk_option **options,
915: struct sk_sign_response **sign_response)
1.1 djm 916: {
917: fido_assert_t *assert = NULL;
1.12 djm 918: char *device = NULL;
1.22 djm 919: struct sk_usbhid *sk = NULL;
1.1 djm 920: struct sk_sign_response *response = NULL;
1.17 djm 921: uint8_t message[32];
1.11 djm 922: int ret = SSH_SK_ERR_GENERAL;
1.1 djm 923: int r;
924:
1.18 djm 925: fido_init(SSH_FIDO_INIT_ARG);
1.1 djm 926:
927: if (sign_response == NULL) {
928: skdebug(__func__, "sign_response == NULL");
929: goto out;
930: }
931: *sign_response = NULL;
1.12 djm 932: if (check_sign_load_resident_options(options, &device) != 0)
933: goto out; /* error already logged */
1.17 djm 934: /* hash data to be signed before it goes to the security key */
935: if ((r = sha256_mem(data, datalen, message, sizeof(message))) != 0) {
936: skdebug(__func__, "hash message failed");
937: goto out;
938: }
1.22 djm 939: if (device != NULL)
940: sk = sk_open(device);
941: else if (pin != NULL || (flags & SSH_SK_USER_VERIFICATION_REQD))
942: sk = sk_probe(NULL, NULL, 0);
943: else
944: sk = sk_probe(application, key_handle, key_handle_len);
945: if (sk == NULL) {
946: skdebug(__func__, "failed to find sk");
1.1 djm 947: goto out;
948: }
949: if ((assert = fido_assert_new()) == NULL) {
950: skdebug(__func__, "fido_assert_new failed");
951: goto out;
952: }
953: if ((r = fido_assert_set_clientdata_hash(assert, message,
1.17 djm 954: sizeof(message))) != FIDO_OK) {
1.1 djm 955: skdebug(__func__, "fido_assert_set_clientdata_hash: %s",
956: fido_strerr(r));
957: goto out;
958: }
959: if ((r = fido_assert_set_rp(assert, application)) != FIDO_OK) {
960: skdebug(__func__, "fido_assert_set_rp: %s", fido_strerr(r));
961: goto out;
962: }
963: if ((r = fido_assert_allow_cred(assert, key_handle,
964: key_handle_len)) != FIDO_OK) {
965: skdebug(__func__, "fido_assert_allow_cred: %s", fido_strerr(r));
966: goto out;
967: }
968: if ((r = fido_assert_set_up(assert,
1.15 djm 969: (flags & SSH_SK_USER_PRESENCE_REQD) ?
1.1 djm 970: FIDO_OPT_TRUE : FIDO_OPT_FALSE)) != FIDO_OK) {
971: skdebug(__func__, "fido_assert_set_up: %s", fido_strerr(r));
972: goto out;
973: }
1.21 djm 974: if (pin == NULL && (flags & SSH_SK_USER_VERIFICATION_REQD) &&
975: (r = fido_assert_set_uv(assert, FIDO_OPT_TRUE)) != FIDO_OK) {
976: skdebug(__func__, "fido_assert_set_uv: %s", fido_strerr(r));
977: ret = FIDO_ERR_PIN_REQUIRED;
978: goto out;
979: }
1.22 djm 980: if ((r = fido_dev_get_assert(sk->dev, assert, pin)) != FIDO_OK) {
1.1 djm 981: skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r));
1.21 djm 982: ret = fidoerr_to_skerr(r);
1.1 djm 983: goto out;
984: }
985: if ((response = calloc(1, sizeof(*response))) == NULL) {
986: skdebug(__func__, "calloc response failed");
987: goto out;
988: }
989: response->flags = fido_assert_flags(assert, 0);
990: response->counter = fido_assert_sigcount(assert, 0);
991: if (pack_sig(alg, assert, response) != 0) {
992: skdebug(__func__, "pack_sig failed");
993: goto out;
994: }
995: *sign_response = response;
996: response = NULL;
997: ret = 0;
998: out:
1.17 djm 999: explicit_bzero(message, sizeof(message));
1.12 djm 1000: free(device);
1.1 djm 1001: if (response != NULL) {
1002: free(response->sig_r);
1003: free(response->sig_s);
1004: free(response);
1005: }
1.22 djm 1006: sk_close(sk);
1007: fido_assert_free(&assert);
1.1 djm 1008: return ret;
1009: }
1.9 djm 1010:
1011: static int
1.22 djm 1012: read_rks(struct sk_usbhid *sk, const char *pin,
1.9 djm 1013: struct sk_resident_key ***rksp, size_t *nrksp)
1014: {
1.11 djm 1015: int ret = SSH_SK_ERR_GENERAL, r = -1;
1.9 djm 1016: fido_credman_metadata_t *metadata = NULL;
1017: fido_credman_rp_t *rp = NULL;
1018: fido_credman_rk_t *rk = NULL;
1019: size_t i, j, nrp, nrk;
1020: const fido_cred_t *cred;
1021: struct sk_resident_key *srk = NULL, **tmp;
1022:
1.22 djm 1023: if (pin == NULL) {
1024: skdebug(__func__, "no PIN specified");
1025: ret = SSH_SK_ERR_PIN_REQUIRED;
1026: goto out;
1.9 djm 1027: }
1028: if ((metadata = fido_credman_metadata_new()) == NULL) {
1029: skdebug(__func__, "alloc failed");
1030: goto out;
1031: }
1032:
1.22 djm 1033: if ((r = fido_credman_get_dev_metadata(sk->dev, metadata, pin)) != 0) {
1.9 djm 1034: if (r == FIDO_ERR_INVALID_COMMAND) {
1035: skdebug(__func__, "device %s does not support "
1.22 djm 1036: "resident keys", sk->path);
1.11 djm 1037: ret = 0;
1.9 djm 1038: goto out;
1039: }
1040: skdebug(__func__, "get metadata for %s failed: %s",
1.22 djm 1041: sk->path, fido_strerr(r));
1.12 djm 1042: ret = fidoerr_to_skerr(r);
1.9 djm 1043: goto out;
1044: }
1045: skdebug(__func__, "existing %llu, remaining %llu",
1046: (unsigned long long)fido_credman_rk_existing(metadata),
1047: (unsigned long long)fido_credman_rk_remaining(metadata));
1048: if ((rp = fido_credman_rp_new()) == NULL) {
1049: skdebug(__func__, "alloc rp failed");
1050: goto out;
1051: }
1.22 djm 1052: if ((r = fido_credman_get_dev_rp(sk->dev, rp, pin)) != 0) {
1.9 djm 1053: skdebug(__func__, "get RPs for %s failed: %s",
1.22 djm 1054: sk->path, fido_strerr(r));
1.9 djm 1055: goto out;
1056: }
1057: nrp = fido_credman_rp_count(rp);
1058: skdebug(__func__, "Device %s has resident keys for %zu RPs",
1.22 djm 1059: sk->path, nrp);
1.9 djm 1060:
1061: /* Iterate over RP IDs that have resident keys */
1062: for (i = 0; i < nrp; i++) {
1063: skdebug(__func__, "rp %zu: name=\"%s\" id=\"%s\" hashlen=%zu",
1064: i, fido_credman_rp_name(rp, i), fido_credman_rp_id(rp, i),
1065: fido_credman_rp_id_hash_len(rp, i));
1066:
1067: /* Skip non-SSH RP IDs */
1068: if (strncasecmp(fido_credman_rp_id(rp, i), "ssh:", 4) != 0)
1069: continue;
1070:
1071: fido_credman_rk_free(&rk);
1072: if ((rk = fido_credman_rk_new()) == NULL) {
1073: skdebug(__func__, "alloc rk failed");
1074: goto out;
1075: }
1.22 djm 1076: if ((r = fido_credman_get_dev_rk(sk->dev,
1077: fido_credman_rp_id(rp, i), rk, pin)) != 0) {
1.9 djm 1078: skdebug(__func__, "get RKs for %s slot %zu failed: %s",
1.22 djm 1079: sk->path, i, fido_strerr(r));
1.9 djm 1080: goto out;
1081: }
1082: nrk = fido_credman_rk_count(rk);
1083: skdebug(__func__, "RP \"%s\" has %zu resident keys",
1084: fido_credman_rp_id(rp, i), nrk);
1085:
1086: /* Iterate over resident keys for this RP ID */
1087: for (j = 0; j < nrk; j++) {
1088: if ((cred = fido_credman_rk(rk, j)) == NULL) {
1089: skdebug(__func__, "no RK in slot %zu", j);
1090: continue;
1091: }
1092: skdebug(__func__, "Device %s RP \"%s\" slot %zu: "
1.22 djm 1093: "type %d flags 0x%02x prot 0x%02x", sk->path,
1.21 djm 1094: fido_credman_rp_id(rp, i), j, fido_cred_type(cred),
1095: fido_cred_flags(cred), fido_cred_prot(cred));
1.9 djm 1096:
1097: /* build response entry */
1098: if ((srk = calloc(1, sizeof(*srk))) == NULL ||
1099: (srk->key.key_handle = calloc(1,
1100: fido_cred_id_len(cred))) == NULL ||
1101: (srk->application = strdup(fido_credman_rp_id(rp,
1102: i))) == NULL) {
1103: skdebug(__func__, "alloc sk_resident_key");
1104: goto out;
1105: }
1106:
1107: srk->key.key_handle_len = fido_cred_id_len(cred);
1.23 djm 1108: memcpy(srk->key.key_handle, fido_cred_id_ptr(cred),
1.9 djm 1109: srk->key.key_handle_len);
1110:
1111: switch (fido_cred_type(cred)) {
1112: case COSE_ES256:
1.15 djm 1113: srk->alg = SSH_SK_ECDSA;
1.9 djm 1114: break;
1115: case COSE_EDDSA:
1.15 djm 1116: srk->alg = SSH_SK_ED25519;
1.9 djm 1117: break;
1118: default:
1119: skdebug(__func__, "unsupported key type %d",
1120: fido_cred_type(cred));
1.15 djm 1121: goto out; /* XXX free rk and continue */
1.9 djm 1122: }
1.23 djm 1123:
1124: if (fido_cred_prot(cred) == FIDO_CRED_PROT_UV_REQUIRED)
1125: srk->flags |= SSH_SK_USER_VERIFICATION_REQD;
1.9 djm 1126:
1127: if ((r = pack_public_key(srk->alg, cred,
1128: &srk->key)) != 0) {
1129: skdebug(__func__, "pack public key failed");
1130: goto out;
1131: }
1132: /* append */
1133: if ((tmp = recallocarray(*rksp, *nrksp, (*nrksp) + 1,
1134: sizeof(**rksp))) == NULL) {
1135: skdebug(__func__, "alloc rksp");
1136: goto out;
1137: }
1138: *rksp = tmp;
1139: (*rksp)[(*nrksp)++] = srk;
1140: srk = NULL;
1141: }
1142: }
1143: /* Success */
1.11 djm 1144: ret = 0;
1.9 djm 1145: out:
1146: if (srk != NULL) {
1147: free(srk->application);
1148: freezero(srk->key.public_key, srk->key.public_key_len);
1149: freezero(srk->key.key_handle, srk->key.key_handle_len);
1150: freezero(srk, sizeof(*srk));
1151: }
1152: fido_credman_rp_free(&rp);
1153: fido_credman_rk_free(&rk);
1154: fido_credman_metadata_free(&metadata);
1.11 djm 1155: return ret;
1.9 djm 1156: }
1157:
1158: int
1.12 djm 1159: sk_load_resident_keys(const char *pin, struct sk_option **options,
1.9 djm 1160: struct sk_resident_key ***rksp, size_t *nrksp)
1161: {
1.11 djm 1162: int ret = SSH_SK_ERR_GENERAL, r = -1;
1.22 djm 1163: size_t i, nrks = 0;
1.9 djm 1164: struct sk_resident_key **rks = NULL;
1.22 djm 1165: struct sk_usbhid *sk = NULL;
1.12 djm 1166: char *device = NULL;
1.22 djm 1167:
1.9 djm 1168: *rksp = NULL;
1169: *nrksp = 0;
1.18 djm 1170:
1171: fido_init(SSH_FIDO_INIT_ARG);
1.9 djm 1172:
1.12 djm 1173: if (check_sign_load_resident_options(options, &device) != 0)
1174: goto out; /* error already logged */
1.22 djm 1175: if (device != NULL)
1176: sk = sk_open(device);
1177: else
1178: sk = sk_probe(NULL, NULL, 0);
1179: if (sk == NULL) {
1180: skdebug(__func__, "failed to find sk");
1181: goto out;
1182: }
1183: skdebug(__func__, "trying %s", sk->path);
1184: if ((r = read_rks(sk, pin, &rks, &nrks)) != 0) {
1185: skdebug(__func__, "read_rks failed for %s", sk->path);
1186: ret = r;
1187: goto out;
1.9 djm 1188: }
1.12 djm 1189: /* success, unless we have no keys but a specific error */
1190: if (nrks > 0 || ret == SSH_SK_ERR_GENERAL)
1191: ret = 0;
1.9 djm 1192: *rksp = rks;
1193: *nrksp = nrks;
1194: rks = NULL;
1195: nrks = 0;
1196: out:
1.22 djm 1197: sk_close(sk);
1.9 djm 1198: for (i = 0; i < nrks; i++) {
1199: free(rks[i]->application);
1200: freezero(rks[i]->key.public_key, rks[i]->key.public_key_len);
1201: freezero(rks[i]->key.key_handle, rks[i]->key.key_handle_len);
1202: freezero(rks[i], sizeof(*rks[i]));
1203: }
1204: free(rks);
1.11 djm 1205: return ret;
1.9 djm 1206: }
1207: