djm@openbsd.org | 6bff952 | 2019-11-14 21:27:29 +0000 | [diff] [blame] | 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 ENABLE_SK_INTERNAL |
| 20 | |
| 21 | #include <stdint.h> |
| 22 | #include <stdlib.h> |
| 23 | #include <string.h> |
| 24 | #include <stdio.h> |
| 25 | #include <stddef.h> |
| 26 | #include <stdarg.h> |
| 27 | |
| 28 | #include <openssl/opensslv.h> |
| 29 | #include <openssl/crypto.h> |
| 30 | #include <openssl/bn.h> |
| 31 | #include <openssl/ec.h> |
| 32 | #include <openssl/ecdsa.h> |
| 33 | |
| 34 | #include <fido.h> |
| 35 | |
| 36 | #ifndef SK_STANDALONE |
| 37 | #include "log.h" |
| 38 | #include "xmalloc.h" |
| 39 | #endif |
| 40 | |
| 41 | /* #define SK_DEBUG 1 */ |
| 42 | |
| 43 | #define MAX_FIDO_DEVICES 256 |
| 44 | |
| 45 | /* Compatibility with OpenSSH 1.0.x */ |
| 46 | #if (OPENSSL_VERSION_NUMBER < 0x10100000L) |
| 47 | #define ECDSA_SIG_get0(sig, pr, ps) \ |
| 48 | do { \ |
| 49 | (*pr) = sig->r; \ |
| 50 | (*ps) = sig->s; \ |
| 51 | } while (0) |
| 52 | #endif |
| 53 | |
| 54 | #define SK_VERSION_MAJOR 0x00020000 /* current API version */ |
| 55 | |
| 56 | /* Flags */ |
| 57 | #define SK_USER_PRESENCE_REQD 0x01 |
| 58 | |
| 59 | /* Algs */ |
| 60 | #define SK_ECDSA 0x00 |
| 61 | #define SK_ED25519 0x01 |
| 62 | |
| 63 | struct sk_enroll_response { |
| 64 | uint8_t *public_key; |
| 65 | size_t public_key_len; |
| 66 | uint8_t *key_handle; |
| 67 | size_t key_handle_len; |
| 68 | uint8_t *signature; |
| 69 | size_t signature_len; |
| 70 | uint8_t *attestation_cert; |
| 71 | size_t attestation_cert_len; |
| 72 | }; |
| 73 | |
| 74 | struct sk_sign_response { |
| 75 | uint8_t flags; |
| 76 | uint32_t counter; |
| 77 | uint8_t *sig_r; |
| 78 | size_t sig_r_len; |
| 79 | uint8_t *sig_s; |
| 80 | size_t sig_s_len; |
| 81 | }; |
| 82 | |
| 83 | /* If building as part of OpenSSH, then rename exported functions */ |
| 84 | #if !defined(SK_STANDALONE) |
| 85 | #define sk_api_version ssh_sk_api_version |
| 86 | #define sk_enroll ssh_sk_enroll |
| 87 | #define sk_sign ssh_sk_sign |
| 88 | #endif |
| 89 | |
| 90 | /* Return the version of the middleware API */ |
| 91 | uint32_t sk_api_version(void); |
| 92 | |
| 93 | /* Enroll a U2F key (private key generation) */ |
| 94 | int sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len, |
| 95 | const char *application, uint8_t flags, |
| 96 | struct sk_enroll_response **enroll_response); |
| 97 | |
| 98 | /* Sign a challenge */ |
| 99 | int sk_sign(int alg, const uint8_t *message, size_t message_len, |
| 100 | const char *application, const uint8_t *key_handle, size_t key_handle_len, |
| 101 | uint8_t flags, struct sk_sign_response **sign_response); |
| 102 | |
| 103 | static void skdebug(const char *func, const char *fmt, ...) |
| 104 | __attribute__((__format__ (printf, 2, 3))); |
| 105 | |
| 106 | static void |
| 107 | skdebug(const char *func, const char *fmt, ...) |
| 108 | { |
| 109 | #if !defined(SK_STANDALONE) |
| 110 | char *msg; |
| 111 | va_list ap; |
| 112 | |
| 113 | va_start(ap, fmt); |
| 114 | xvasprintf(&msg, fmt, ap); |
| 115 | va_end(ap); |
djm@openbsd.org | 22a8271 | 2019-11-15 02:20:06 +0000 | [diff] [blame] | 116 | debug("%s: %s", func, msg); |
djm@openbsd.org | 6bff952 | 2019-11-14 21:27:29 +0000 | [diff] [blame] | 117 | free(msg); |
| 118 | #elif defined(SK_DEBUG) |
| 119 | va_list ap; |
| 120 | |
| 121 | va_start(ap, fmt); |
| 122 | fprintf(stderr, "%s: ", func); |
| 123 | vfprintf(stderr, fmt, ap); |
| 124 | fputc('\n', stderr); |
| 125 | va_end(ap); |
| 126 | #else |
| 127 | (void)func; /* XXX */ |
| 128 | (void)fmt; /* XXX */ |
| 129 | #endif |
| 130 | } |
| 131 | |
| 132 | uint32_t |
| 133 | sk_api_version(void) |
| 134 | { |
| 135 | return SK_VERSION_MAJOR; |
| 136 | } |
| 137 | |
| 138 | /* Select the first identified FIDO device attached to the system */ |
| 139 | static char * |
| 140 | pick_first_device(void) |
| 141 | { |
| 142 | char *ret = NULL; |
| 143 | fido_dev_info_t *devlist = NULL; |
| 144 | size_t olen = 0; |
| 145 | int r; |
| 146 | const fido_dev_info_t *di; |
| 147 | |
| 148 | if ((devlist = fido_dev_info_new(1)) == NULL) { |
| 149 | skdebug(__func__, "fido_dev_info_new failed"); |
| 150 | goto out; |
| 151 | } |
| 152 | if ((r = fido_dev_info_manifest(devlist, 1, &olen)) != FIDO_OK) { |
| 153 | skdebug(__func__, "fido_dev_info_manifest failed: %s", |
| 154 | fido_strerr(r)); |
| 155 | goto out; |
| 156 | } |
| 157 | if (olen != 1) { |
| 158 | skdebug(__func__, "fido_dev_info_manifest bad len %zu", olen); |
| 159 | goto out; |
| 160 | } |
| 161 | di = fido_dev_info_ptr(devlist, 0); |
| 162 | if ((ret = strdup(fido_dev_info_path(di))) == NULL) { |
| 163 | skdebug(__func__, "fido_dev_info_path failed"); |
| 164 | goto out; |
| 165 | } |
| 166 | out: |
| 167 | fido_dev_info_free(&devlist, 1); |
| 168 | return ret; |
| 169 | } |
| 170 | |
| 171 | /* Check if the specified key handle exists on a given device. */ |
| 172 | static int |
| 173 | try_device(fido_dev_t *dev, const uint8_t *message, size_t message_len, |
| 174 | const char *application, const uint8_t *key_handle, size_t key_handle_len) |
| 175 | { |
| 176 | fido_assert_t *assert = NULL; |
| 177 | int r = FIDO_ERR_INTERNAL; |
| 178 | |
| 179 | if ((assert = fido_assert_new()) == NULL) { |
| 180 | skdebug(__func__, "fido_assert_new failed"); |
| 181 | goto out; |
| 182 | } |
| 183 | if ((r = fido_assert_set_clientdata_hash(assert, message, |
| 184 | message_len)) != FIDO_OK) { |
| 185 | skdebug(__func__, "fido_assert_set_clientdata_hash: %s", |
| 186 | fido_strerr(r)); |
| 187 | goto out; |
| 188 | } |
| 189 | if ((r = fido_assert_set_rp(assert, application)) != FIDO_OK) { |
| 190 | skdebug(__func__, "fido_assert_set_rp: %s", fido_strerr(r)); |
| 191 | goto out; |
| 192 | } |
| 193 | if ((r = fido_assert_allow_cred(assert, key_handle, |
| 194 | key_handle_len)) != FIDO_OK) { |
| 195 | skdebug(__func__, "fido_assert_allow_cred: %s", fido_strerr(r)); |
| 196 | goto out; |
| 197 | } |
| 198 | if ((r = fido_assert_set_up(assert, FIDO_OPT_FALSE)) != FIDO_OK) { |
| 199 | skdebug(__func__, "fido_assert_up: %s", fido_strerr(r)); |
| 200 | goto out; |
| 201 | } |
| 202 | r = fido_dev_get_assert(dev, assert, NULL); |
| 203 | skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r)); |
djm@openbsd.org | 01362cf | 2019-11-15 03:41:57 +0000 | [diff] [blame] | 204 | if (r == FIDO_ERR_USER_PRESENCE_REQUIRED) { |
| 205 | /* U2F tokens may return this */ |
| 206 | r = FIDO_OK; |
| 207 | } |
djm@openbsd.org | 6bff952 | 2019-11-14 21:27:29 +0000 | [diff] [blame] | 208 | out: |
| 209 | fido_assert_free(&assert); |
| 210 | |
| 211 | return r != FIDO_OK ? -1 : 0; |
| 212 | } |
| 213 | |
| 214 | /* Iterate over configured devices looking for a specific key handle */ |
| 215 | static fido_dev_t * |
| 216 | find_device(const uint8_t *message, size_t message_len, const char *application, |
| 217 | const uint8_t *key_handle, size_t key_handle_len) |
| 218 | { |
| 219 | fido_dev_info_t *devlist = NULL; |
| 220 | fido_dev_t *dev = NULL; |
deraadt@openbsd.org | d165bb5 | 2019-11-15 05:26:56 +0000 | [diff] [blame] | 221 | size_t devlist_len = 0, i; |
djm@openbsd.org | 6bff952 | 2019-11-14 21:27:29 +0000 | [diff] [blame] | 222 | const char *path; |
| 223 | int r; |
| 224 | |
| 225 | if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) { |
| 226 | skdebug(__func__, "fido_dev_info_new failed"); |
| 227 | goto out; |
| 228 | } |
| 229 | if ((r = fido_dev_info_manifest(devlist, MAX_FIDO_DEVICES, |
| 230 | &devlist_len)) != FIDO_OK) { |
| 231 | skdebug(__func__, "fido_dev_info_manifest: %s", fido_strerr(r)); |
| 232 | goto out; |
| 233 | } |
| 234 | |
| 235 | skdebug(__func__, "found %zu device(s)", devlist_len); |
| 236 | |
deraadt@openbsd.org | d165bb5 | 2019-11-15 05:26:56 +0000 | [diff] [blame] | 237 | for (i = 0; i < devlist_len; i++) { |
djm@openbsd.org | 6bff952 | 2019-11-14 21:27:29 +0000 | [diff] [blame] | 238 | const fido_dev_info_t *di = fido_dev_info_ptr(devlist, i); |
| 239 | |
| 240 | if (di == NULL) { |
| 241 | skdebug(__func__, "fido_dev_info_ptr %zu failed", i); |
| 242 | continue; |
| 243 | } |
| 244 | if ((path = fido_dev_info_path(di)) == NULL) { |
| 245 | skdebug(__func__, "fido_dev_info_path %zu failed", i); |
| 246 | continue; |
| 247 | } |
| 248 | skdebug(__func__, "trying device %zu: %s", i, path); |
| 249 | if ((dev = fido_dev_new()) == NULL) { |
| 250 | skdebug(__func__, "fido_dev_new failed"); |
| 251 | continue; |
| 252 | } |
| 253 | if ((r = fido_dev_open(dev, path)) != FIDO_OK) { |
| 254 | skdebug(__func__, "fido_dev_open failed"); |
| 255 | fido_dev_free(&dev); |
| 256 | continue; |
| 257 | } |
| 258 | if (try_device(dev, message, message_len, application, |
| 259 | key_handle, key_handle_len) == 0) { |
| 260 | skdebug(__func__, "found key"); |
| 261 | break; |
| 262 | } |
| 263 | fido_dev_close(dev); |
| 264 | fido_dev_free(&dev); |
| 265 | } |
| 266 | |
| 267 | out: |
| 268 | if (devlist != NULL) |
| 269 | fido_dev_info_free(&devlist, MAX_FIDO_DEVICES); |
| 270 | |
| 271 | return dev; |
| 272 | } |
| 273 | |
| 274 | /* |
| 275 | * The key returned via fido_cred_pubkey_ptr() is in affine coordinates, |
| 276 | * but the API expects a SEC1 octet string. |
| 277 | */ |
| 278 | static int |
| 279 | pack_public_key_ecdsa(fido_cred_t *cred, struct sk_enroll_response *response) |
| 280 | { |
| 281 | const uint8_t *ptr; |
| 282 | BIGNUM *x = NULL, *y = NULL; |
| 283 | EC_POINT *q = NULL; |
| 284 | EC_GROUP *g = NULL; |
djm@openbsd.org | 6bff952 | 2019-11-14 21:27:29 +0000 | [diff] [blame] | 285 | int ret = -1; |
| 286 | |
| 287 | response->public_key = NULL; |
| 288 | response->public_key_len = 0; |
| 289 | |
djm@openbsd.org | fd1a964 | 2019-11-15 06:00:20 +0000 | [diff] [blame] | 290 | if ((x = BN_new()) == NULL || |
| 291 | (y = BN_new()) == NULL || |
djm@openbsd.org | 6bff952 | 2019-11-14 21:27:29 +0000 | [diff] [blame] | 292 | (g = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)) == NULL || |
| 293 | (q = EC_POINT_new(g)) == NULL) { |
| 294 | skdebug(__func__, "libcrypto setup failed"); |
| 295 | goto out; |
| 296 | } |
| 297 | if ((ptr = fido_cred_pubkey_ptr(cred)) == NULL) { |
| 298 | skdebug(__func__, "fido_cred_pubkey_ptr failed"); |
| 299 | goto out; |
| 300 | } |
| 301 | if (fido_cred_pubkey_len(cred) != 64) { |
| 302 | skdebug(__func__, "bad fido_cred_pubkey_len %zu", |
| 303 | fido_cred_pubkey_len(cred)); |
| 304 | goto out; |
| 305 | } |
| 306 | |
| 307 | if (BN_bin2bn(ptr, 32, x) == NULL || |
| 308 | BN_bin2bn(ptr + 32, 32, y) == NULL) { |
| 309 | skdebug(__func__, "BN_bin2bn failed"); |
| 310 | goto out; |
| 311 | } |
djm@openbsd.org | fd1a964 | 2019-11-15 06:00:20 +0000 | [diff] [blame] | 312 | if (EC_POINT_set_affine_coordinates_GFp(g, q, x, y, NULL) != 1) { |
djm@openbsd.org | 6bff952 | 2019-11-14 21:27:29 +0000 | [diff] [blame] | 313 | skdebug(__func__, "EC_POINT_set_affine_coordinates_GFp failed"); |
| 314 | goto out; |
| 315 | } |
| 316 | response->public_key_len = EC_POINT_point2oct(g, q, |
djm@openbsd.org | fd1a964 | 2019-11-15 06:00:20 +0000 | [diff] [blame] | 317 | POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL); |
djm@openbsd.org | 6bff952 | 2019-11-14 21:27:29 +0000 | [diff] [blame] | 318 | if (response->public_key_len == 0 || response->public_key_len > 2048) { |
| 319 | skdebug(__func__, "bad pubkey length %zu", |
| 320 | response->public_key_len); |
| 321 | goto out; |
| 322 | } |
| 323 | if ((response->public_key = malloc(response->public_key_len)) == NULL) { |
| 324 | skdebug(__func__, "malloc pubkey failed"); |
| 325 | goto out; |
| 326 | } |
| 327 | if (EC_POINT_point2oct(g, q, POINT_CONVERSION_UNCOMPRESSED, |
djm@openbsd.org | fd1a964 | 2019-11-15 06:00:20 +0000 | [diff] [blame] | 328 | response->public_key, response->public_key_len, NULL) == 0) { |
djm@openbsd.org | 6bff952 | 2019-11-14 21:27:29 +0000 | [diff] [blame] | 329 | skdebug(__func__, "EC_POINT_point2oct failed"); |
| 330 | goto out; |
| 331 | } |
| 332 | /* success */ |
| 333 | ret = 0; |
| 334 | out: |
| 335 | if (ret != 0 && response->public_key != NULL) { |
| 336 | memset(response->public_key, 0, response->public_key_len); |
| 337 | free(response->public_key); |
| 338 | response->public_key = NULL; |
| 339 | } |
| 340 | EC_POINT_free(q); |
| 341 | EC_GROUP_free(g); |
djm@openbsd.org | fd1a964 | 2019-11-15 06:00:20 +0000 | [diff] [blame] | 342 | BN_clear_free(x); |
| 343 | BN_clear_free(y); |
djm@openbsd.org | 6bff952 | 2019-11-14 21:27:29 +0000 | [diff] [blame] | 344 | return ret; |
| 345 | } |
| 346 | |
| 347 | static int |
| 348 | pack_public_key_ed25519(fido_cred_t *cred, struct sk_enroll_response *response) |
| 349 | { |
| 350 | const uint8_t *ptr; |
| 351 | size_t len; |
| 352 | int ret = -1; |
| 353 | |
| 354 | response->public_key = NULL; |
| 355 | response->public_key_len = 0; |
| 356 | |
| 357 | if ((len = fido_cred_pubkey_len(cred)) != 32) { |
| 358 | skdebug(__func__, "bad fido_cred_pubkey_len len %zu", len); |
| 359 | goto out; |
| 360 | } |
| 361 | if ((ptr = fido_cred_pubkey_ptr(cred)) == NULL) { |
| 362 | skdebug(__func__, "fido_cred_pubkey_ptr failed"); |
| 363 | goto out; |
| 364 | } |
| 365 | response->public_key_len = len; |
| 366 | if ((response->public_key = malloc(response->public_key_len)) == NULL) { |
| 367 | skdebug(__func__, "malloc pubkey failed"); |
| 368 | goto out; |
| 369 | } |
| 370 | memcpy(response->public_key, ptr, len); |
| 371 | ret = 0; |
| 372 | out: |
| 373 | if (ret != 0) |
| 374 | free(response->public_key); |
| 375 | return ret; |
| 376 | } |
| 377 | |
| 378 | static int |
| 379 | pack_public_key(int alg, fido_cred_t *cred, struct sk_enroll_response *response) |
| 380 | { |
| 381 | switch(alg) { |
| 382 | case SK_ECDSA: |
| 383 | return pack_public_key_ecdsa(cred, response); |
| 384 | case SK_ED25519: |
| 385 | return pack_public_key_ed25519(cred, response); |
| 386 | default: |
| 387 | return -1; |
| 388 | } |
| 389 | } |
| 390 | |
| 391 | int |
| 392 | sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len, |
| 393 | const char *application, uint8_t flags, |
markus@openbsd.org | d431778 | 2019-11-15 15:41:01 +0000 | [diff] [blame] | 394 | struct sk_enroll_response **enroll_response) |
djm@openbsd.org | 6bff952 | 2019-11-14 21:27:29 +0000 | [diff] [blame] | 395 | { |
| 396 | fido_cred_t *cred = NULL; |
| 397 | fido_dev_t *dev = NULL; |
| 398 | const uint8_t *ptr; |
| 399 | uint8_t user_id[32]; |
| 400 | struct sk_enroll_response *response = NULL; |
| 401 | size_t len; |
| 402 | int cose_alg; |
| 403 | int ret = -1; |
| 404 | int r; |
| 405 | char *device = NULL; |
| 406 | |
| 407 | (void)flags; /* XXX; unused */ |
| 408 | #ifdef SK_DEBUG |
| 409 | fido_init(FIDO_DEBUG); |
| 410 | #endif |
markus@openbsd.org | d431778 | 2019-11-15 15:41:01 +0000 | [diff] [blame] | 411 | if (enroll_response == NULL) { |
| 412 | skdebug(__func__, "enroll_response == NULL"); |
djm@openbsd.org | 6bff952 | 2019-11-14 21:27:29 +0000 | [diff] [blame] | 413 | goto out; |
| 414 | } |
markus@openbsd.org | d431778 | 2019-11-15 15:41:01 +0000 | [diff] [blame] | 415 | *enroll_response = NULL; |
djm@openbsd.org | 6bff952 | 2019-11-14 21:27:29 +0000 | [diff] [blame] | 416 | switch(alg) { |
| 417 | case SK_ECDSA: |
| 418 | cose_alg = COSE_ES256; |
| 419 | break; |
| 420 | case SK_ED25519: |
| 421 | cose_alg = COSE_EDDSA; |
| 422 | break; |
| 423 | default: |
| 424 | skdebug(__func__, "unsupported key type %d", alg); |
| 425 | goto out; |
| 426 | } |
| 427 | if ((device = pick_first_device()) == NULL) { |
| 428 | skdebug(__func__, "pick_first_device failed"); |
| 429 | goto out; |
| 430 | } |
| 431 | skdebug(__func__, "using device %s", device); |
| 432 | if ((cred = fido_cred_new()) == NULL) { |
| 433 | skdebug(__func__, "fido_cred_new failed"); |
| 434 | goto out; |
| 435 | } |
| 436 | memset(user_id, 0, sizeof(user_id)); |
| 437 | if ((r = fido_cred_set_type(cred, cose_alg)) != FIDO_OK) { |
| 438 | skdebug(__func__, "fido_cred_set_type: %s", fido_strerr(r)); |
| 439 | goto out; |
| 440 | } |
| 441 | if ((r = fido_cred_set_clientdata_hash(cred, challenge, |
| 442 | challenge_len)) != FIDO_OK) { |
| 443 | skdebug(__func__, "fido_cred_set_clientdata_hash: %s", |
| 444 | fido_strerr(r)); |
| 445 | goto out; |
| 446 | } |
| 447 | if ((r = fido_cred_set_user(cred, user_id, sizeof(user_id), |
| 448 | "openssh", "openssh", NULL)) != FIDO_OK) { |
| 449 | skdebug(__func__, "fido_cred_set_user: %s", fido_strerr(r)); |
| 450 | goto out; |
| 451 | } |
| 452 | if ((r = fido_cred_set_rp(cred, application, NULL)) != FIDO_OK) { |
| 453 | skdebug(__func__, "fido_cred_set_rp: %s", fido_strerr(r)); |
| 454 | goto out; |
| 455 | } |
| 456 | if ((dev = fido_dev_new()) == NULL) { |
| 457 | skdebug(__func__, "fido_dev_new failed"); |
| 458 | goto out; |
| 459 | } |
| 460 | if ((r = fido_dev_open(dev, device)) != FIDO_OK) { |
| 461 | skdebug(__func__, "fido_dev_open: %s", fido_strerr(r)); |
| 462 | goto out; |
| 463 | } |
| 464 | if ((r = fido_dev_make_cred(dev, cred, NULL)) != FIDO_OK) { |
| 465 | skdebug(__func__, "fido_dev_make_cred: %s", fido_strerr(r)); |
| 466 | goto out; |
| 467 | } |
| 468 | if (fido_cred_x5c_ptr(cred) != NULL) { |
| 469 | if ((r = fido_cred_verify(cred)) != FIDO_OK) { |
| 470 | skdebug(__func__, "fido_cred_verify: %s", |
| 471 | fido_strerr(r)); |
| 472 | goto out; |
| 473 | } |
| 474 | } else { |
| 475 | skdebug(__func__, "self-attested credential"); |
| 476 | if ((r = fido_cred_verify_self(cred)) != FIDO_OK) { |
| 477 | skdebug(__func__, "fido_cred_verify_self: %s", |
| 478 | fido_strerr(r)); |
| 479 | goto out; |
| 480 | } |
| 481 | } |
| 482 | if ((response = calloc(1, sizeof(*response))) == NULL) { |
| 483 | skdebug(__func__, "calloc response failed"); |
| 484 | goto out; |
| 485 | } |
| 486 | if (pack_public_key(alg, cred, response) != 0) { |
| 487 | skdebug(__func__, "pack_public_key failed"); |
| 488 | goto out; |
| 489 | } |
| 490 | if ((ptr = fido_cred_id_ptr(cred)) != NULL) { |
| 491 | len = fido_cred_id_len(cred); |
| 492 | if ((response->key_handle = calloc(1, len)) == NULL) { |
| 493 | skdebug(__func__, "calloc key handle failed"); |
| 494 | goto out; |
| 495 | } |
| 496 | memcpy(response->key_handle, ptr, len); |
| 497 | response->key_handle_len = len; |
| 498 | } |
| 499 | if ((ptr = fido_cred_sig_ptr(cred)) != NULL) { |
| 500 | len = fido_cred_sig_len(cred); |
| 501 | if ((response->signature = calloc(1, len)) == NULL) { |
| 502 | skdebug(__func__, "calloc signature failed"); |
| 503 | goto out; |
| 504 | } |
| 505 | memcpy(response->signature, ptr, len); |
| 506 | response->signature_len = len; |
| 507 | } |
| 508 | if ((ptr = fido_cred_x5c_ptr(cred)) != NULL) { |
| 509 | len = fido_cred_x5c_len(cred); |
| 510 | if ((response->attestation_cert = calloc(1, len)) == NULL) { |
| 511 | skdebug(__func__, "calloc attestation cert failed"); |
| 512 | goto out; |
| 513 | } |
| 514 | memcpy(response->attestation_cert, ptr, len); |
| 515 | response->attestation_cert_len = len; |
| 516 | } |
markus@openbsd.org | d431778 | 2019-11-15 15:41:01 +0000 | [diff] [blame] | 517 | *enroll_response = response; |
djm@openbsd.org | 6bff952 | 2019-11-14 21:27:29 +0000 | [diff] [blame] | 518 | response = NULL; |
| 519 | ret = 0; |
| 520 | out: |
| 521 | free(device); |
| 522 | if (response != NULL) { |
| 523 | free(response->public_key); |
| 524 | free(response->key_handle); |
| 525 | free(response->signature); |
| 526 | free(response->attestation_cert); |
| 527 | free(response); |
| 528 | } |
| 529 | if (dev != NULL) { |
| 530 | fido_dev_close(dev); |
| 531 | fido_dev_free(&dev); |
| 532 | } |
| 533 | if (cred != NULL) { |
| 534 | fido_cred_free(&cred); |
| 535 | } |
| 536 | return ret; |
| 537 | } |
| 538 | |
| 539 | static int |
| 540 | pack_sig_ecdsa(fido_assert_t *assert, struct sk_sign_response *response) |
| 541 | { |
| 542 | ECDSA_SIG *sig = NULL; |
| 543 | const BIGNUM *sig_r, *sig_s; |
| 544 | const unsigned char *cp; |
| 545 | size_t sig_len; |
| 546 | int ret = -1; |
| 547 | |
| 548 | cp = fido_assert_sig_ptr(assert, 0); |
| 549 | sig_len = fido_assert_sig_len(assert, 0); |
| 550 | if ((sig = d2i_ECDSA_SIG(NULL, &cp, sig_len)) == NULL) { |
| 551 | skdebug(__func__, "d2i_ECDSA_SIG failed"); |
| 552 | goto out; |
| 553 | } |
| 554 | ECDSA_SIG_get0(sig, &sig_r, &sig_s); |
| 555 | response->sig_r_len = BN_num_bytes(sig_r); |
| 556 | response->sig_s_len = BN_num_bytes(sig_s); |
| 557 | if ((response->sig_r = calloc(1, response->sig_r_len)) == NULL || |
| 558 | (response->sig_s = calloc(1, response->sig_s_len)) == NULL) { |
| 559 | skdebug(__func__, "calloc signature failed"); |
| 560 | goto out; |
| 561 | } |
| 562 | BN_bn2bin(sig_r, response->sig_r); |
| 563 | BN_bn2bin(sig_s, response->sig_s); |
| 564 | ret = 0; |
| 565 | out: |
| 566 | ECDSA_SIG_free(sig); |
| 567 | if (ret != 0) { |
| 568 | free(response->sig_r); |
| 569 | free(response->sig_s); |
| 570 | response->sig_r = NULL; |
| 571 | response->sig_s = NULL; |
| 572 | } |
| 573 | return ret; |
| 574 | } |
| 575 | |
| 576 | static int |
| 577 | pack_sig_ed25519(fido_assert_t *assert, struct sk_sign_response *response) |
| 578 | { |
| 579 | const unsigned char *ptr; |
| 580 | size_t len; |
| 581 | int ret = -1; |
| 582 | |
| 583 | ptr = fido_assert_sig_ptr(assert, 0); |
| 584 | len = fido_assert_sig_len(assert, 0); |
| 585 | if (len != 64) { |
| 586 | skdebug(__func__, "bad length %zu", len); |
| 587 | goto out; |
| 588 | } |
| 589 | response->sig_r_len = len; |
| 590 | if ((response->sig_r = calloc(1, response->sig_r_len)) == NULL) { |
| 591 | skdebug(__func__, "calloc signature failed"); |
| 592 | goto out; |
| 593 | } |
| 594 | memcpy(response->sig_r, ptr, len); |
| 595 | ret = 0; |
| 596 | out: |
| 597 | if (ret != 0) { |
| 598 | free(response->sig_r); |
| 599 | response->sig_r = NULL; |
| 600 | } |
| 601 | return ret; |
| 602 | } |
| 603 | |
| 604 | static int |
| 605 | pack_sig(int alg, fido_assert_t *assert, struct sk_sign_response *response) |
| 606 | { |
| 607 | switch(alg) { |
| 608 | case SK_ECDSA: |
| 609 | return pack_sig_ecdsa(assert, response); |
| 610 | case SK_ED25519: |
| 611 | return pack_sig_ed25519(assert, response); |
| 612 | default: |
| 613 | return -1; |
| 614 | } |
| 615 | } |
| 616 | |
| 617 | int |
| 618 | sk_sign(int alg, const uint8_t *message, size_t message_len, |
| 619 | const char *application, |
| 620 | const uint8_t *key_handle, size_t key_handle_len, |
| 621 | uint8_t flags, struct sk_sign_response **sign_response) |
| 622 | { |
| 623 | fido_assert_t *assert = NULL; |
| 624 | fido_dev_t *dev = NULL; |
| 625 | struct sk_sign_response *response = NULL; |
| 626 | int ret = -1; |
| 627 | int r; |
| 628 | |
| 629 | #ifdef SK_DEBUG |
| 630 | fido_init(FIDO_DEBUG); |
| 631 | #endif |
| 632 | |
| 633 | if (sign_response == NULL) { |
| 634 | skdebug(__func__, "sign_response == NULL"); |
| 635 | goto out; |
| 636 | } |
| 637 | *sign_response = NULL; |
| 638 | if ((dev = find_device(message, message_len, application, key_handle, |
| 639 | key_handle_len)) == NULL) { |
| 640 | skdebug(__func__, "couldn't find device for key handle"); |
| 641 | goto out; |
| 642 | } |
| 643 | if ((assert = fido_assert_new()) == NULL) { |
| 644 | skdebug(__func__, "fido_assert_new failed"); |
| 645 | goto out; |
| 646 | } |
| 647 | if ((r = fido_assert_set_clientdata_hash(assert, message, |
| 648 | message_len)) != FIDO_OK) { |
| 649 | skdebug(__func__, "fido_assert_set_clientdata_hash: %s", |
| 650 | fido_strerr(r)); |
| 651 | goto out; |
| 652 | } |
| 653 | if ((r = fido_assert_set_rp(assert, application)) != FIDO_OK) { |
| 654 | skdebug(__func__, "fido_assert_set_rp: %s", fido_strerr(r)); |
| 655 | goto out; |
| 656 | } |
| 657 | if ((r = fido_assert_allow_cred(assert, key_handle, |
| 658 | key_handle_len)) != FIDO_OK) { |
| 659 | skdebug(__func__, "fido_assert_allow_cred: %s", fido_strerr(r)); |
| 660 | goto out; |
| 661 | } |
| 662 | if ((r = fido_assert_set_up(assert, |
| 663 | (flags & SK_USER_PRESENCE_REQD) ? |
| 664 | FIDO_OPT_TRUE : FIDO_OPT_FALSE)) != FIDO_OK) { |
| 665 | skdebug(__func__, "fido_assert_set_up: %s", fido_strerr(r)); |
| 666 | goto out; |
| 667 | } |
| 668 | if ((r = fido_dev_get_assert(dev, assert, NULL)) != FIDO_OK) { |
| 669 | skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r)); |
| 670 | goto out; |
| 671 | } |
| 672 | if ((response = calloc(1, sizeof(*response))) == NULL) { |
| 673 | skdebug(__func__, "calloc response failed"); |
| 674 | goto out; |
| 675 | } |
| 676 | response->flags = fido_assert_flags(assert, 0); |
| 677 | response->counter = fido_assert_sigcount(assert, 0); |
| 678 | if (pack_sig(alg, assert, response) != 0) { |
| 679 | skdebug(__func__, "pack_sig failed"); |
| 680 | goto out; |
| 681 | } |
| 682 | *sign_response = response; |
| 683 | response = NULL; |
| 684 | ret = 0; |
| 685 | out: |
| 686 | if (response != NULL) { |
| 687 | free(response->sig_r); |
| 688 | free(response->sig_s); |
| 689 | free(response); |
| 690 | } |
| 691 | if (dev != NULL) { |
| 692 | fido_dev_close(dev); |
| 693 | fido_dev_free(&dev); |
| 694 | } |
| 695 | if (assert != NULL) { |
| 696 | fido_assert_free(&assert); |
| 697 | } |
| 698 | return ret; |
| 699 | } |
| 700 | #endif /* ENABLE_SK_INTERNAL */ |