Annotation of src/usr.bin/ssh/ssh-sk.c, Revision 1.1
1.1 ! djm 1: /* $OpenBSD$ */
! 2: /*
! 3: * Copyright (c) 2019 Google LLC
! 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: /* #define DEBUG_SK 1 */
! 19:
! 20: #include <dlfcn.h>
! 21: #include <stddef.h>
! 22: #include <stdint.h>
! 23: #include <string.h>
! 24: #include <stdio.h>
! 25:
! 26: #include <openssl/objects.h>
! 27: #include <openssl/ec.h>
! 28:
! 29: #include "log.h"
! 30: #include "misc.h"
! 31: #include "sshbuf.h"
! 32: #include "sshkey.h"
! 33: #include "ssherr.h"
! 34: #include "digest.h"
! 35:
! 36: #include "ssh-sk.h"
! 37: #include "sk-api.h"
! 38:
! 39: struct sshsk_provider {
! 40: char *path;
! 41: void *dlhandle;
! 42:
! 43: /* Return the version of the middleware API */
! 44: uint32_t (*sk_api_version)(void);
! 45:
! 46: /* Enroll a U2F key (private key generation) */
! 47: int (*sk_enroll)(const uint8_t *challenge, size_t challenge_len,
! 48: const char *application, uint8_t flags,
! 49: struct sk_enroll_response **enroll_response);
! 50:
! 51: /* Sign a challenge */
! 52: int (*sk_sign)(const uint8_t *message, size_t message_len,
! 53: const char *application,
! 54: const uint8_t *key_handle, size_t key_handle_len,
! 55: uint8_t flags, struct sk_sign_response **sign_response);
! 56: };
! 57:
! 58: static void
! 59: sshsk_free(struct sshsk_provider *p)
! 60: {
! 61: if (p == NULL)
! 62: return;
! 63: free(p->path);
! 64: if (p->dlhandle != NULL)
! 65: dlclose(p->dlhandle);
! 66: free(p);
! 67: }
! 68:
! 69: static struct sshsk_provider *
! 70: sshsk_open(const char *path)
! 71: {
! 72: struct sshsk_provider *ret = NULL;
! 73: uint32_t version;
! 74:
! 75: if ((ret = calloc(1, sizeof(*ret))) == NULL) {
! 76: error("%s: calloc failed", __func__);
! 77: return NULL;
! 78: }
! 79: if ((ret->path = strdup(path)) == NULL) {
! 80: error("%s: strdup failed", __func__);
! 81: goto fail;
! 82: }
! 83: if ((ret->dlhandle = dlopen(path, RTLD_NOW)) == NULL) {
! 84: error("Security key provider %s dlopen failed: %s",
! 85: path, dlerror());
! 86: goto fail;
! 87: }
! 88: if ((ret->sk_api_version = dlsym(ret->dlhandle,
! 89: "sk_api_version")) == NULL) {
! 90: error("Security key provider %s dlsym(sk_api_version) "
! 91: "failed: %s", path, dlerror());
! 92: goto fail;
! 93: }
! 94: version = ret->sk_api_version();
! 95: debug("%s: provider %s implements version 0x%08lx", __func__,
! 96: ret->path, (u_long)version);
! 97: if ((version & SSH_SK_VERSION_MAJOR_MASK) != SSH_SK_VERSION_MAJOR) {
! 98: error("Security key provider %s implements unsupported version "
! 99: "0x%08lx (supported: 0x%08lx)", path, (u_long)version,
! 100: (u_long)SSH_SK_VERSION_MAJOR);
! 101: goto fail;
! 102: }
! 103: if ((ret->sk_enroll = dlsym(ret->dlhandle, "sk_enroll")) == NULL) {
! 104: error("Security key provider %s dlsym(sk_enroll) "
! 105: "failed: %s", path, dlerror());
! 106: goto fail;
! 107: }
! 108: if ((ret->sk_sign = dlsym(ret->dlhandle, "sk_sign")) == NULL) {
! 109: error("Security key provider %s dlsym(sk_sign) failed: %s",
! 110: path, dlerror());
! 111: goto fail;
! 112: }
! 113: /* success */
! 114: return ret;
! 115: fail:
! 116: sshsk_free(ret);
! 117: return NULL;
! 118: }
! 119:
! 120: static void
! 121: sshsk_free_enroll_response(struct sk_enroll_response *r)
! 122: {
! 123: if (r == NULL)
! 124: return;
! 125: freezero(r->key_handle, r->key_handle_len);
! 126: freezero(r->public_key, r->public_key_len);
! 127: freezero(r->signature, r->signature_len);
! 128: freezero(r->attestation_cert, r->attestation_cert_len);
! 129: freezero(r, sizeof(*r));
! 130: };
! 131:
! 132: static void
! 133: sshsk_free_sign_response(struct sk_sign_response *r)
! 134: {
! 135: if (r == NULL)
! 136: return;
! 137: freezero(r->sig_r, r->sig_r_len);
! 138: freezero(r->sig_s, r->sig_s_len);
! 139: freezero(r, sizeof(*r));
! 140: };
! 141:
! 142: int
! 143: sshsk_enroll(const char *provider_path, const char *application,
! 144: uint8_t flags, struct sshbuf *challenge_buf, struct sshkey **keyp,
! 145: struct sshbuf *attest)
! 146: {
! 147: struct sshsk_provider *skp = NULL;
! 148: struct sshkey *key = NULL;
! 149: u_char randchall[32];
! 150: const u_char *challenge;
! 151: size_t challenge_len;
! 152: struct sk_enroll_response *resp = NULL;
! 153: int r = SSH_ERR_INTERNAL_ERROR;
! 154: struct sshbuf *b = NULL;
! 155: EC_POINT *q = NULL;
! 156:
! 157: *keyp = NULL;
! 158: if (attest)
! 159: sshbuf_reset(attest);
! 160: if (provider_path == NULL) {
! 161: error("%s: missing provider", __func__);
! 162: r = SSH_ERR_INVALID_ARGUMENT;
! 163: goto out;
! 164: }
! 165: if (application == NULL || *application == '\0') {
! 166: error("%s: missing application", __func__);
! 167: r = SSH_ERR_INVALID_ARGUMENT;
! 168: goto out;
! 169: }
! 170: if (challenge_buf == NULL) {
! 171: debug("%s: using random challenge", __func__);
! 172: arc4random_buf(randchall, sizeof(randchall));
! 173: challenge = randchall;
! 174: challenge_len = sizeof(randchall);
! 175: } else if (sshbuf_len(challenge_buf) == 0) {
! 176: error("Missing enrollment challenge");
! 177: r = SSH_ERR_INVALID_ARGUMENT;
! 178: goto out;
! 179: } else {
! 180: challenge = sshbuf_ptr(challenge_buf);
! 181: challenge_len = sshbuf_len(challenge_buf);
! 182: debug3("%s: using explicit challenge len=%zd",
! 183: __func__, challenge_len);
! 184: }
! 185: if ((skp = sshsk_open(provider_path)) == NULL) {
! 186: r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
! 187: goto out;
! 188: }
! 189: /* XXX validate flags? */
! 190: /* enroll key */
! 191: if ((r = skp->sk_enroll(challenge, challenge_len, application,
! 192: flags, &resp)) != 0) {
! 193: error("Security key provider %s returned failure %d",
! 194: provider_path, r);
! 195: r = SSH_ERR_INVALID_FORMAT; /* XXX error codes in API? */
! 196: goto out;
! 197: }
! 198: /* Check response validity */
! 199: if (resp->public_key == NULL || resp->key_handle == NULL ||
! 200: resp->signature == NULL || resp->attestation_cert == NULL) {
! 201: error("%s: sk_enroll response invalid", __func__);
! 202: r = SSH_ERR_INVALID_FORMAT;
! 203: goto out;
! 204: }
! 205: /* Assemble key from response */
! 206: if ((key = sshkey_new(KEY_ECDSA_SK)) == NULL) {
! 207: error("%s: sshkey_new failed", __func__);
! 208: r = SSH_ERR_ALLOC_FAIL;
! 209: goto out;
! 210: }
! 211: key->ecdsa_nid = NID_X9_62_prime256v1;
! 212: key->sk_flags = flags;
! 213: if ((key->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid)) == NULL ||
! 214: (q = EC_POINT_new(EC_KEY_get0_group(key->ecdsa))) == NULL ||
! 215: (key->sk_key_handle = sshbuf_new()) == NULL ||
! 216: (key->sk_reserved = sshbuf_new()) == NULL ||
! 217: (b = sshbuf_new()) == NULL) {
! 218: error("%s: allocation failed", __func__);
! 219: r = SSH_ERR_ALLOC_FAIL;
! 220: goto out;
! 221: }
! 222: if ((r = sshbuf_put_string(b,
! 223: resp->public_key, resp->public_key_len)) != 0) {
! 224: error("%s: buffer error: %s", __func__, ssh_err(r));
! 225: goto out;
! 226: }
! 227: if ((key->sk_application = strdup(application)) == NULL) {
! 228: error("%s: strdup application failed", __func__);
! 229: r = SSH_ERR_ALLOC_FAIL;
! 230: goto out;
! 231: }
! 232: if ((r = sshbuf_get_ec(b, q, EC_KEY_get0_group(key->ecdsa))) != 0) {
! 233: error("%s: parse key: %s", __func__, ssh_err(r));
! 234: r = SSH_ERR_INVALID_FORMAT;
! 235: goto out;
! 236: }
! 237: if (sshkey_ec_validate_public(EC_KEY_get0_group(key->ecdsa), q) != 0) {
! 238: error("Security key returned invalid ECDSA key");
! 239: r = SSH_ERR_KEY_INVALID_EC_VALUE;
! 240: goto out;
! 241: }
! 242: if (EC_KEY_set_public_key(key->ecdsa, q) != 1) {
! 243: /* XXX assume it is a allocation error */
! 244: error("%s: allocation failed", __func__);
! 245: r = SSH_ERR_ALLOC_FAIL;
! 246: goto out;
! 247: }
! 248: if ((r = sshbuf_put(key->sk_key_handle, resp->key_handle,
! 249: resp->key_handle_len)) != 0) {
! 250: error("%s: buffer error: %s", __func__, ssh_err(r));
! 251: goto out;
! 252: }
! 253: /* Optionally fill in the attestation information */
! 254: if (attest != NULL) {
! 255: if ((r = sshbuf_put_cstring(attest, "sk-attest-v00")) != 0 ||
! 256: (r = sshbuf_put_u32(attest, 1)) != 0 || /* XXX U2F ver */
! 257: (r = sshbuf_put_string(attest,
! 258: resp->attestation_cert, resp->attestation_cert_len)) != 0 ||
! 259: (r = sshbuf_put_string(attest,
! 260: resp->signature, resp->signature_len)) != 0 ||
! 261: (r = sshbuf_put_u32(attest, flags)) != 0 || /* XXX right? */
! 262: (r = sshbuf_put_string(attest, NULL, 0)) != 0) {
! 263: error("%s: buffer error: %s", __func__, ssh_err(r));
! 264: goto out;
! 265: }
! 266: }
! 267: /* success */
! 268: *keyp = key;
! 269: key = NULL; /* transferred */
! 270: r = 0;
! 271: out:
! 272: EC_POINT_free(q);
! 273: sshsk_free(skp);
! 274: sshbuf_free(b);
! 275: sshkey_free(key);
! 276: sshsk_free_enroll_response(resp);
! 277: explicit_bzero(randchall, sizeof(randchall));
! 278: return r;
! 279: }
! 280:
! 281: int
! 282: sshsk_ecdsa_sign(const char *provider_path, const struct sshkey *key,
! 283: u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,
! 284: u_int compat)
! 285: {
! 286: struct sshsk_provider *skp = NULL;
! 287: int r = SSH_ERR_INTERNAL_ERROR;
! 288: struct sk_sign_response *resp = NULL;
! 289: struct sshbuf *inner_sig = NULL, *sig = NULL;
! 290: uint8_t message[32];
! 291:
! 292: if (sigp != NULL)
! 293: *sigp = NULL;
! 294: if (lenp != NULL)
! 295: *lenp = 0;
! 296: if (provider_path == NULL ||
! 297: sshkey_type_plain(key->type) != KEY_ECDSA_SK ||
! 298: key->sk_key_handle == NULL ||
! 299: key->sk_application == NULL || *key->sk_application == '\0') {
! 300: r = SSH_ERR_INVALID_ARGUMENT;
! 301: goto out;
! 302: }
! 303: if ((skp = sshsk_open(provider_path)) == NULL) {
! 304: r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
! 305: goto out;
! 306: }
! 307:
! 308: /* hash data to be signed before it goes to the security key */
! 309: if ((r = ssh_digest_memory(SSH_DIGEST_SHA256, data, datalen,
! 310: message, sizeof(message))) != 0) {
! 311: error("%s: hash application failed: %s", __func__, ssh_err(r));
! 312: r = SSH_ERR_INTERNAL_ERROR;
! 313: goto out;
! 314: }
! 315: if ((r = skp->sk_sign(message, sizeof(message),
! 316: key->sk_application,
! 317: sshbuf_ptr(key->sk_key_handle), sshbuf_len(key->sk_key_handle),
! 318: key->sk_flags, &resp)) != 0) {
! 319: debug("%s: sk_sign failed with code %d", __func__, r);
! 320: goto out;
! 321: }
! 322: if ((sig = sshbuf_new()) == NULL ||
! 323: (inner_sig = sshbuf_new()) == NULL) {
! 324: r = SSH_ERR_ALLOC_FAIL;
! 325: goto out;
! 326: }
! 327: /* Prepare inner signature object */
! 328: if ((r = sshbuf_put_bignum2_bytes(inner_sig,
! 329: resp->sig_r, resp->sig_r_len)) != 0 ||
! 330: (r = sshbuf_put_bignum2_bytes(inner_sig,
! 331: resp->sig_s, resp->sig_s_len)) != 0 ||
! 332: (r = sshbuf_put_u8(inner_sig, resp->flags)) != 0 ||
! 333: (r = sshbuf_put_u32(inner_sig, resp->counter)) != 0) {
! 334: debug("%s: buffer error (inner): %s", __func__, ssh_err(r));
! 335: goto out;
! 336: }
! 337: /* Assemble outer signature */
! 338: if ((r = sshbuf_put_cstring(sig, sshkey_ssh_name_plain(key))) != 0 ||
! 339: (r = sshbuf_put_stringb(sig, inner_sig)) != 0) {
! 340: debug("%s: buffer error (outer): %s", __func__, ssh_err(r));
! 341: goto out;
! 342: }
! 343: #ifdef DEBUG_SK
! 344: fprintf(stderr, "%s: sig_r:\n", __func__);
! 345: sshbuf_dump_data(resp->sig_r, resp->sig_r_len, stderr);
! 346: fprintf(stderr, "%s: sig_s:\n", __func__);
! 347: sshbuf_dump_data(resp->sig_s, resp->sig_s_len, stderr);
! 348: fprintf(stderr, "%s: sig_flags = 0x%02x, sig_counter = %u\n",
! 349: __func__, resp->flags, resp->counter);
! 350: fprintf(stderr, "%s: hashed message:\n", __func__);
! 351: sshbuf_dump_data(message, sizeof(message), stderr);
! 352: fprintf(stderr, "%s: inner:\n", __func__);
! 353: sshbuf_dump(inner_sig, stderr);
! 354: fprintf(stderr, "%s: sigbuf:\n", __func__);
! 355: sshbuf_dump(sig, stderr);
! 356: #endif
! 357: if (sigp != NULL) {
! 358: if ((*sigp = malloc(sshbuf_len(sig))) == NULL) {
! 359: r = SSH_ERR_ALLOC_FAIL;
! 360: goto out;
! 361: }
! 362: memcpy(*sigp, sshbuf_ptr(sig), sshbuf_len(sig));
! 363: }
! 364: if (lenp != NULL)
! 365: *lenp = sshbuf_len(sig);
! 366: /* success */
! 367: r = 0;
! 368: out:
! 369: explicit_bzero(message, sizeof(message));
! 370: sshsk_free(skp);
! 371: sshsk_free_sign_response(resp);
! 372: sshbuf_free(sig);
! 373: sshbuf_free(inner_sig);
! 374: return r;
! 375: }