[BACK]Return to ssh-sk.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / ssh

Annotation of src/usr.bin/ssh/ssh-sk.c, Revision 1.4

1.4     ! markus      1: /* $OpenBSD: ssh-sk.c,v 1.3 2019/11/12 19:30:21 markus Exp $ */
1.1       djm         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:
1.2       markus    142: /* Assemble key from response */
                    143: static int
                    144: sshsk_ecdsa_assemble(struct sk_enroll_response *resp, struct sshkey **keyp)
                    145: {
                    146:        struct sshkey *key = NULL;
                    147:        struct sshbuf *b = NULL;
                    148:        EC_POINT *q = NULL;
                    149:        int r;
                    150:
                    151:        *keyp = NULL;
                    152:        if ((key = sshkey_new(KEY_ECDSA_SK)) == NULL) {
                    153:                error("%s: sshkey_new failed", __func__);
                    154:                r = SSH_ERR_ALLOC_FAIL;
                    155:                goto out;
                    156:        }
                    157:        key->ecdsa_nid = NID_X9_62_prime256v1;
                    158:        if ((key->ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid)) == NULL ||
                    159:            (q = EC_POINT_new(EC_KEY_get0_group(key->ecdsa))) == NULL ||
                    160:            (b = sshbuf_new()) == NULL) {
                    161:                error("%s: allocation failed", __func__);
                    162:                r = SSH_ERR_ALLOC_FAIL;
                    163:                goto out;
                    164:        }
                    165:        if ((r = sshbuf_put_string(b,
                    166:            resp->public_key, resp->public_key_len)) != 0) {
                    167:                error("%s: buffer error: %s", __func__, ssh_err(r));
                    168:                goto out;
                    169:        }
                    170:        if ((r = sshbuf_get_ec(b, q, EC_KEY_get0_group(key->ecdsa))) != 0) {
                    171:                error("%s: parse key: %s", __func__, ssh_err(r));
                    172:                r = SSH_ERR_INVALID_FORMAT;
                    173:                goto out;
                    174:        }
                    175:        if (sshkey_ec_validate_public(EC_KEY_get0_group(key->ecdsa), q) != 0) {
                    176:                error("Security key returned invalid ECDSA key");
                    177:                r = SSH_ERR_KEY_INVALID_EC_VALUE;
                    178:                goto out;
                    179:        }
                    180:        if (EC_KEY_set_public_key(key->ecdsa, q) != 1) {
                    181:                /* XXX assume it is a allocation error */
                    182:                error("%s: allocation failed", __func__);
                    183:                r = SSH_ERR_ALLOC_FAIL;
                    184:                goto out;
                    185:        }
                    186:        /* success */
                    187:        *keyp = key;
                    188:        key = NULL; /* transferred */
                    189:        r = 0;
                    190:  out:
                    191:        EC_POINT_free(q);
                    192:        sshkey_free(key);
                    193:        sshbuf_free(b);
                    194:        return r;
                    195: }
                    196:
1.1       djm       197: int
                    198: sshsk_enroll(const char *provider_path, const char *application,
                    199:     uint8_t flags, struct sshbuf *challenge_buf, struct sshkey **keyp,
                    200:     struct sshbuf *attest)
                    201: {
                    202:        struct sshsk_provider *skp = NULL;
                    203:        struct sshkey *key = NULL;
                    204:        u_char randchall[32];
                    205:        const u_char *challenge;
                    206:        size_t challenge_len;
                    207:        struct sk_enroll_response *resp = NULL;
                    208:        int r = SSH_ERR_INTERNAL_ERROR;
                    209:
                    210:        *keyp = NULL;
                    211:        if (attest)
                    212:                sshbuf_reset(attest);
                    213:        if (provider_path == NULL) {
                    214:                error("%s: missing provider", __func__);
                    215:                r = SSH_ERR_INVALID_ARGUMENT;
                    216:                goto out;
                    217:        }
                    218:        if (application == NULL || *application == '\0') {
                    219:                error("%s: missing application", __func__);
                    220:                r = SSH_ERR_INVALID_ARGUMENT;
                    221:                goto out;
                    222:        }
                    223:        if (challenge_buf == NULL) {
                    224:                debug("%s: using random challenge", __func__);
                    225:                arc4random_buf(randchall, sizeof(randchall));
                    226:                challenge = randchall;
                    227:                challenge_len = sizeof(randchall);
                    228:        } else if (sshbuf_len(challenge_buf) == 0) {
                    229:                error("Missing enrollment challenge");
                    230:                r = SSH_ERR_INVALID_ARGUMENT;
                    231:                goto out;
                    232:        } else {
                    233:                challenge = sshbuf_ptr(challenge_buf);
                    234:                challenge_len = sshbuf_len(challenge_buf);
                    235:                debug3("%s: using explicit challenge len=%zd",
                    236:                    __func__, challenge_len);
                    237:        }
                    238:        if ((skp = sshsk_open(provider_path)) == NULL) {
                    239:                r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
                    240:                goto out;
                    241:        }
                    242:        /* XXX validate flags? */
                    243:        /* enroll key */
                    244:        if ((r = skp->sk_enroll(challenge, challenge_len, application,
                    245:            flags, &resp)) != 0) {
                    246:                error("Security key provider %s returned failure %d",
                    247:                    provider_path, r);
                    248:                r = SSH_ERR_INVALID_FORMAT; /* XXX error codes in API? */
                    249:                goto out;
                    250:        }
                    251:        /* Check response validity */
                    252:        if (resp->public_key == NULL || resp->key_handle == NULL ||
                    253:            resp->signature == NULL || resp->attestation_cert == NULL) {
                    254:                error("%s: sk_enroll response invalid", __func__);
                    255:                r = SSH_ERR_INVALID_FORMAT;
                    256:                goto out;
                    257:        }
1.2       markus    258:        if ((r = sshsk_ecdsa_assemble(resp, &key)) != 0)
1.1       djm       259:                goto out;
                    260:        key->sk_flags = flags;
1.2       markus    261:        if ((key->sk_key_handle = sshbuf_new()) == NULL ||
                    262:            (key->sk_reserved = sshbuf_new()) == NULL) {
1.1       djm       263:                error("%s: allocation failed", __func__);
                    264:                r = SSH_ERR_ALLOC_FAIL;
                    265:                goto out;
                    266:        }
                    267:        if ((key->sk_application = strdup(application)) == NULL) {
                    268:                error("%s: strdup application failed", __func__);
                    269:                r = SSH_ERR_ALLOC_FAIL;
                    270:                goto out;
                    271:        }
                    272:        if ((r = sshbuf_put(key->sk_key_handle, resp->key_handle,
                    273:            resp->key_handle_len)) != 0) {
                    274:                error("%s: buffer error: %s", __func__, ssh_err(r));
                    275:                goto out;
                    276:        }
                    277:        /* Optionally fill in the attestation information */
                    278:        if (attest != NULL) {
                    279:                if ((r = sshbuf_put_cstring(attest, "sk-attest-v00")) != 0 ||
                    280:                    (r = sshbuf_put_u32(attest, 1)) != 0 || /* XXX U2F ver */
                    281:                    (r = sshbuf_put_string(attest,
                    282:                    resp->attestation_cert, resp->attestation_cert_len)) != 0 ||
                    283:                    (r = sshbuf_put_string(attest,
                    284:                    resp->signature, resp->signature_len)) != 0 ||
                    285:                    (r = sshbuf_put_u32(attest, flags)) != 0 || /* XXX right? */
                    286:                    (r = sshbuf_put_string(attest, NULL, 0)) != 0) {
                    287:                        error("%s: buffer error: %s", __func__, ssh_err(r));
                    288:                        goto out;
                    289:                }
                    290:        }
                    291:        /* success */
                    292:        *keyp = key;
                    293:        key = NULL; /* transferred */
                    294:        r = 0;
                    295:  out:
                    296:        sshsk_free(skp);
                    297:        sshkey_free(key);
                    298:        sshsk_free_enroll_response(resp);
                    299:        explicit_bzero(randchall, sizeof(randchall));
                    300:        return r;
                    301: }
                    302:
1.3       markus    303: static int
                    304: sshsk_ecdsa_inner_sig(struct sk_sign_response *resp, struct sshbuf **retp)
                    305: {
                    306:        struct sshbuf *inner_sig = NULL;
                    307:        int r = SSH_ERR_INTERNAL_ERROR;
                    308:
                    309:        *retp = NULL;
                    310:        if ((inner_sig = sshbuf_new()) == NULL) {
                    311:                r = SSH_ERR_ALLOC_FAIL;
                    312:                goto out;
                    313:        }
                    314:        /* Prepare inner signature object */
                    315:        if ((r = sshbuf_put_bignum2_bytes(inner_sig,
                    316:            resp->sig_r, resp->sig_r_len)) != 0 ||
                    317:            (r = sshbuf_put_bignum2_bytes(inner_sig,
                    318:            resp->sig_s, resp->sig_s_len)) != 0 ||
                    319:            (r = sshbuf_put_u8(inner_sig, resp->flags)) != 0 ||
                    320:            (r = sshbuf_put_u32(inner_sig, resp->counter)) != 0) {
                    321:                debug("%s: buffer error: %s", __func__, ssh_err(r));
                    322:                goto out;
                    323:        }
                    324: #ifdef DEBUG_SK
                    325:        fprintf(stderr, "%s: sig_r:\n", __func__);
                    326:        sshbuf_dump_data(resp->sig_r, resp->sig_r_len, stderr);
                    327:        fprintf(stderr, "%s: sig_s:\n", __func__);
                    328:        sshbuf_dump_data(resp->sig_s, resp->sig_s_len, stderr);
                    329:        fprintf(stderr, "%s: sig_flags = 0x%02x, sig_counter = %u\n",
                    330:            __func__, resp->flags, resp->counter);
                    331: #endif
                    332:        *retp = inner_sig;
                    333:        inner_sig = NULL;
                    334:        r = 0;
                    335: out:
                    336:        sshbuf_free(inner_sig);
                    337:        return r;
                    338: }
                    339:
1.1       djm       340: int
1.4     ! markus    341: sshsk_sign(const char *provider_path, const struct sshkey *key,
1.1       djm       342:     u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,
                    343:     u_int compat)
                    344: {
                    345:        struct sshsk_provider *skp = NULL;
                    346:        int r = SSH_ERR_INTERNAL_ERROR;
                    347:        struct sk_sign_response *resp = NULL;
                    348:        struct sshbuf *inner_sig = NULL, *sig = NULL;
                    349:        uint8_t message[32];
                    350:
                    351:        if (sigp != NULL)
                    352:                *sigp = NULL;
                    353:        if (lenp != NULL)
                    354:                *lenp = 0;
                    355:        if (provider_path == NULL ||
                    356:            sshkey_type_plain(key->type) != KEY_ECDSA_SK ||
                    357:            key->sk_key_handle == NULL ||
                    358:            key->sk_application == NULL || *key->sk_application == '\0') {
                    359:                r = SSH_ERR_INVALID_ARGUMENT;
                    360:                goto out;
                    361:        }
                    362:        if ((skp = sshsk_open(provider_path)) == NULL) {
                    363:                r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
                    364:                goto out;
                    365:        }
                    366:
                    367:        /* hash data to be signed before it goes to the security key */
                    368:        if ((r = ssh_digest_memory(SSH_DIGEST_SHA256, data, datalen,
                    369:            message, sizeof(message))) != 0) {
                    370:                error("%s: hash application failed: %s", __func__, ssh_err(r));
                    371:                r = SSH_ERR_INTERNAL_ERROR;
                    372:                goto out;
                    373:        }
                    374:        if ((r = skp->sk_sign(message, sizeof(message),
                    375:            key->sk_application,
                    376:            sshbuf_ptr(key->sk_key_handle), sshbuf_len(key->sk_key_handle),
                    377:            key->sk_flags, &resp)) != 0) {
                    378:                debug("%s: sk_sign failed with code %d", __func__, r);
                    379:                goto out;
                    380:        }
1.3       markus    381:        /* Prepare inner signature object */
                    382:        if ((r = sshsk_ecdsa_inner_sig(resp, &inner_sig)) != 0)
                    383:                goto out;
                    384:        /* Assemble outer signature */
                    385:        if ((sig = sshbuf_new()) == NULL) {
1.1       djm       386:                r = SSH_ERR_ALLOC_FAIL;
                    387:                goto out;
                    388:        }
                    389:        if ((r = sshbuf_put_cstring(sig, sshkey_ssh_name_plain(key))) != 0 ||
                    390:            (r = sshbuf_put_stringb(sig, inner_sig)) != 0) {
                    391:                debug("%s: buffer error (outer): %s", __func__, ssh_err(r));
                    392:                goto out;
                    393:        }
                    394: #ifdef DEBUG_SK
                    395:        fprintf(stderr, "%s: hashed message:\n", __func__);
                    396:        sshbuf_dump_data(message, sizeof(message), stderr);
                    397:        fprintf(stderr, "%s: inner:\n", __func__);
                    398:        sshbuf_dump(inner_sig, stderr);
                    399:        fprintf(stderr, "%s: sigbuf:\n", __func__);
                    400:        sshbuf_dump(sig, stderr);
                    401: #endif
                    402:        if (sigp != NULL) {
                    403:                if ((*sigp = malloc(sshbuf_len(sig))) == NULL) {
                    404:                        r = SSH_ERR_ALLOC_FAIL;
                    405:                        goto out;
                    406:                }
                    407:                memcpy(*sigp, sshbuf_ptr(sig), sshbuf_len(sig));
                    408:        }
                    409:        if (lenp != NULL)
                    410:                *lenp = sshbuf_len(sig);
                    411:        /* success */
                    412:        r = 0;
                    413:  out:
                    414:        explicit_bzero(message, sizeof(message));
                    415:        sshsk_free(skp);
                    416:        sshsk_free_sign_response(resp);
                    417:        sshbuf_free(sig);
                    418:        sshbuf_free(inner_sig);
                    419:        return r;
                    420: }