diff --git a/src/cbor.c b/src/cbor.c index ab99b34d..d73d2ef4 100644 --- a/src/cbor.c +++ b/src/cbor.c @@ -1132,6 +1132,80 @@ decode_attcred(const unsigned char **buf, size_t *len, int cose_alg, return (ok); } +static int +decode_attcred_multiple_cose(const unsigned char **buf, size_t *len, fido_blob_t *cose_algs, + fido_attcred_t *attcred) +{ + cbor_item_t *item = NULL; + struct cbor_load_result cbor; + uint16_t id_len; + int ok = -1; + size_t cose_count = 0; + int *cose_algs_arr = NULL; + bool cose_match = false; + + fido_log_xxd(*buf, *len, "%s", __func__); + + if (fido_buf_read(buf, len, &attcred->aaguid, + sizeof(attcred->aaguid)) < 0) { + fido_log_debug("%s: fido_buf_read aaguid", __func__); + return (-1); + } + + if (fido_buf_read(buf, len, &id_len, sizeof(id_len)) < 0) { + fido_log_debug("%s: fido_buf_read id_len", __func__); + return (-1); + } + + attcred->id.len = (size_t)be16toh(id_len); + if ((attcred->id.ptr = malloc(attcred->id.len)) == NULL) + return (-1); + + fido_log_debug("%s: attcred->id.len=%zu", __func__, attcred->id.len); + + if (fido_buf_read(buf, len, attcred->id.ptr, attcred->id.len) < 0) { + fido_log_debug("%s: fido_buf_read id", __func__); + return (-1); + } + + if ((item = cbor_load(*buf, *len, &cbor)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + goto fail; + } + + if (cbor_decode_pubkey(item, &attcred->type, &attcred->pubkey) < 0) { + fido_log_debug("%s: cbor_decode_pubkey", __func__); + goto fail; + } + + cose_count = cose_algs->len / sizeof(int); + cose_algs_arr = (int*)cose_algs->ptr; + for (size_t i = 0; i < cose_count; i++) { + int cose_alg = cose_algs_arr[i]; + if (attcred->type != cose_alg) { + fido_log_debug("%s: cose_algs mismatch (%d != %d)", __func__, + attcred->type, cose_alg); + } else { + cose_match = true; + } + } + + if (!cose_match) { + fido_log_debug("%s: cose_alg failed to match any", __func__); + goto fail; + } + + *buf += cbor.read; + *len -= cbor.read; + + ok = 0; +fail: + if (item != NULL) + cbor_decref(&item); + + return (ok); +} + static int decode_cred_extension(const cbor_item_t *key, const cbor_item_t *val, void *arg) { @@ -1337,6 +1411,56 @@ cbor_decode_cred_authdata(const cbor_item_t *item, int cose_alg, return (FIDO_OK); } +int +cbor_decode_cred_authdata_multiple_cose(const cbor_item_t *item, fido_blob_t *cose_algs, + fido_blob_t *authdata_cbor, fido_authdata_t *authdata, + fido_attcred_t *attcred, fido_cred_ext_t *authdata_ext) +{ + const unsigned char *buf = NULL; + size_t len; + size_t alloc_len; + + if (cbor_isa_bytestring(item) == false || + cbor_bytestring_is_definite(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + if (authdata_cbor->ptr != NULL || + (authdata_cbor->len = cbor_serialize_alloc(item, + &authdata_cbor->ptr, &alloc_len)) == 0) { + fido_log_debug("%s: cbor_serialize_alloc", __func__); + return (-1); + } + + buf = cbor_bytestring_handle(item); + len = cbor_bytestring_length(item); + fido_log_xxd(buf, len, "%s", __func__); + + if (fido_buf_read(&buf, &len, authdata, sizeof(*authdata)) < 0) { + fido_log_debug("%s: fido_buf_read", __func__); + return (-1); + } + + authdata->sigcount = be32toh(authdata->sigcount); + + if (attcred != NULL) { + if ((authdata->flags & CTAP_AUTHDATA_ATT_CRED) == 0 || + decode_attcred_multiple_cose(&buf, &len, cose_algs, attcred) < 0) + return (-1); + } + + if (authdata_ext != NULL) { + if ((authdata->flags & CTAP_AUTHDATA_EXT_DATA) != 0 && + decode_cred_extensions(&buf, &len, authdata_ext) < 0) + return (-1); + } + + /* XXX we should probably ensure that len == 0 at this point */ + + return (FIDO_OK); +} + int cbor_decode_assert_authdata(const cbor_item_t *item, fido_blob_t *authdata_cbor, fido_authdata_t *authdata, fido_assert_extattr_t *authdata_ext) diff --git a/src/cred.c b/src/cred.c index 4a7a7257..0e5d52a3 100644 --- a/src/cred.c +++ b/src/cred.c @@ -556,6 +556,7 @@ fido_cred_reset_tx(fido_cred_t *cred) fido_blob_reset(&cred->cdh); fido_blob_reset(&cred->user.id); fido_blob_reset(&cred->blob); + fido_blob_reset(&cred->type_winhello); free(cred->rp.id); free(cred->rp.name); @@ -994,6 +995,37 @@ fido_cred_set_type(fido_cred_t *cred, int cose_alg) cred->type = cose_alg; + return (FIDO_OK); +} + +int fido_cred_set_type_winhello(fido_cred_t *cred, const unsigned char *ptr, size_t len) +{ + int *cose_algos = NULL; + size_t count = len / sizeof(int); + + if (cred->type != 0) + return (FIDO_ERR_INVALID_ARGUMENT); + + if (!fido_blob_is_empty(&cred->type_winhello)) + return (FIDO_ERR_INVALID_ARGUMENT); + + if (fido_blob_set(&cred->type_winhello, ptr, len) < 0) + return (FIDO_ERR_INVALID_ARGUMENT); + + cose_algos = (int*)cred->type_winhello.ptr; + + for (size_t i = 0; i < count; i++) { + int cose_alg = cose_algos[i]; + + if (cose_alg != COSE_ES256 && cose_alg != COSE_ES384 && + cose_alg != COSE_RS256 && cose_alg != COSE_EDDSA) { + fido_blob_reset(&cred->type_winhello); + return (FIDO_ERR_INVALID_ARGUMENT); + } + } + + cred->type = cose_algos[0]; + return (FIDO_OK); } @@ -1003,6 +1035,18 @@ fido_cred_type(const fido_cred_t *cred) return (cred->type); } +const unsigned char * +fido_cred_type_winhello_ptr(const fido_cred_t *cred) +{ + return (cred->type_winhello.ptr); +} + +size_t +fido_cred_type_winhello_len(const fido_cred_t *cred) +{ + return (cred->type_winhello.len); +} + uint8_t fido_cred_flags(const fido_cred_t *cred) { diff --git a/src/extern.h b/src/extern.h index 1bc95b27..132c970d 100644 --- a/src/extern.h +++ b/src/extern.h @@ -61,6 +61,8 @@ int cbor_decode_attstmt(const cbor_item_t *, fido_attstmt_t *); int cbor_decode_bool(const cbor_item_t *, bool *); int cbor_decode_cred_authdata(const cbor_item_t *, int, fido_blob_t *, fido_authdata_t *, fido_attcred_t *, fido_cred_ext_t *); +int cbor_decode_cred_authdata_multiple_cose(const cbor_item_t *, + fido_blob_t *, fido_blob_t *, fido_authdata_t *, fido_attcred_t *, fido_cred_ext_t *); int cbor_decode_assert_authdata(const cbor_item_t *, fido_blob_t *, fido_authdata_t *, fido_assert_extattr_t *); int cbor_decode_cred_id(const cbor_item_t *, fido_blob_t *); diff --git a/src/fido.h b/src/fido.h index 914e3776..d7ad95b9 100644 --- a/src/fido.h +++ b/src/fido.h @@ -122,6 +122,7 @@ const unsigned char *fido_cred_id_ptr(const fido_cred_t *); const unsigned char *fido_cred_largeblob_key_ptr(const fido_cred_t *); const unsigned char *fido_cred_pubkey_ptr(const fido_cred_t *); const unsigned char *fido_cred_sig_ptr(const fido_cred_t *); +const unsigned char *fido_cred_type_winhello_ptr(const fido_cred_t *cred); const unsigned char *fido_cred_user_id_ptr(const fido_cred_t *); const unsigned char *fido_cred_x5c_ptr(const fido_cred_t *); @@ -166,6 +167,7 @@ int fido_cred_set_rk(fido_cred_t *, fido_opt_t); int fido_cred_set_rp(fido_cred_t *, const char *, const char *); int fido_cred_set_sig(fido_cred_t *, const unsigned char *, size_t); int fido_cred_set_type(fido_cred_t *, int); +int fido_cred_set_type_winhello(fido_cred_t *cred, const unsigned char *ptr, size_t len); int fido_cred_set_uv(fido_cred_t *, fido_opt_t); int fido_cred_type(const fido_cred_t *); int fido_cred_set_user(fido_cred_t *, const unsigned char *, size_t, @@ -224,6 +226,7 @@ size_t fido_cred_largeblob_key_len(const fido_cred_t *); size_t fido_cred_pin_minlen(const fido_cred_t *); size_t fido_cred_pubkey_len(const fido_cred_t *); size_t fido_cred_sig_len(const fido_cred_t *); +size_t fido_cred_type_winhello_len(const fido_cred_t *cred); size_t fido_cred_user_id_len(const fido_cred_t *); size_t fido_cred_x5c_len(const fido_cred_t *); diff --git a/src/fido/types.h b/src/fido/types.h index 01d68200..53b1c979 100644 --- a/src/fido/types.h +++ b/src/fido/types.h @@ -167,24 +167,25 @@ typedef struct fido_cred_ext { } fido_cred_ext_t; typedef struct fido_cred { - fido_blob_t cd; /* client data */ - fido_blob_t cdh; /* client data hash */ - fido_rp_t rp; /* relying party */ - fido_user_t user; /* user entity */ - fido_blob_array_t excl; /* list of credential ids to exclude */ - fido_opt_t rk; /* resident key */ - fido_opt_t uv; /* user verification */ - fido_cred_ext_t ext; /* extensions */ - int type; /* cose algorithm */ - char *fmt; /* credential format */ - fido_cred_ext_t authdata_ext; /* decoded extensions */ - fido_blob_t authdata_cbor; /* cbor-encoded payload */ - fido_blob_t authdata_raw; /* cbor-decoded payload */ - fido_authdata_t authdata; /* decoded authdata payload */ - fido_attcred_t attcred; /* returned credential (key + id) */ - fido_attstmt_t attstmt; /* attestation statement (x509 + sig) */ - fido_blob_t largeblob_key; /* decoded large blob key */ - fido_blob_t blob; /* CTAP 2.1 credBlob */ + fido_blob_t cd; /* client data */ + fido_blob_t cdh; /* client data hash */ + fido_rp_t rp; /* relying party */ + fido_user_t user; /* user entity */ + fido_blob_array_t excl; /* list of credential ids to exclude */ + fido_opt_t rk; /* resident key */ + fido_opt_t uv; /* user verification */ + fido_cred_ext_t ext; /* extensions */ + int type; /* cose algorithm */ + char *fmt; /* credential format */ + fido_cred_ext_t authdata_ext; /* decoded extensions */ + fido_blob_t authdata_cbor; /* cbor-encoded payload */ + fido_blob_t authdata_raw; /* cbor-decoded payload */ + fido_authdata_t authdata; /* decoded authdata payload */ + fido_attcred_t attcred; /* returned credential (key + id) */ + fido_attstmt_t attstmt; /* attestation statement (x509 + sig) */ + fido_blob_t largeblob_key; /* decoded large blob key */ + fido_blob_t blob; /* CTAP 2.1 credBlob */ + fido_blob_t type_winhello; /* list of cose algorithms, windows hello supports multiple */ } fido_cred_t; typedef struct fido_assert_extattr { diff --git a/src/winhello.c b/src/winhello.c index 425ebfdc..42312008 100644 --- a/src/winhello.c +++ b/src/winhello.c @@ -43,7 +43,6 @@ struct winhello_assert { struct winhello_cred { WEBAUTHN_RP_ENTITY_INFORMATION rp; WEBAUTHN_USER_ENTITY_INFORMATION user; - WEBAUTHN_COSE_CREDENTIAL_PARAMETER alg; WEBAUTHN_COSE_CREDENTIAL_PARAMETERS cose; WEBAUTHN_CLIENT_DATA cd; WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS opt; @@ -355,30 +354,32 @@ pack_user(wchar_t **name, wchar_t **icon, wchar_t **display_name, } static int -pack_cose(WEBAUTHN_COSE_CREDENTIAL_PARAMETER *alg, - WEBAUTHN_COSE_CREDENTIAL_PARAMETERS *cose, int type) +pack_cose(WEBAUTHN_COSE_CREDENTIAL_PARAMETERS *out, const fido_cred_t *cred) { - switch (type) { - case COSE_ES256: - alg->lAlg = WEBAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256; - break; - case COSE_ES384: - alg->lAlg = WEBAUTHN_COSE_ALGORITHM_ECDSA_P384_WITH_SHA384; - break; - case COSE_EDDSA: - alg->lAlg = -8; /* XXX */; - break; - case COSE_RS256: - alg->lAlg = WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA256; - break; - default: - fido_log_debug("%s: type %d", __func__, type); - return -1; + if (!fido_blob_is_empty(&cred->type_winhello)) { + /* array of credential types was set */ + size_t count = cred->type_winhello.len / sizeof(int); + int *cose_algos = (int*)cred->type_winhello.ptr; + WEBAUTHN_COSE_CREDENTIAL_PARAMETER *alg = calloc(count, sizeof(WEBAUTHN_COSE_CREDENTIAL_PARAMETER)); + + fido_log_debug("%s: cose algo count:%d", __func__, count); + for(size_t i = 0; i < count; i++) { + alg[i].lAlg = cose_algos[i]; + alg[i].dwVersion = WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION; + alg[i].pwszCredentialType = WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY; + fido_log_debug("%s: cose algo:%d", __func__, cose_algos[i]); + } + out->cCredentialParameters = (DWORD)count; + out->pCredentialParameters = alg; + } else { + /* Only single credential type */ + WEBAUTHN_COSE_CREDENTIAL_PARAMETER *alg = calloc(1, sizeof(WEBAUTHN_COSE_CREDENTIAL_PARAMETER)); + alg->lAlg = cred->type; + alg->dwVersion = WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION; + alg->pwszCredentialType = WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY; + out->cCredentialParameters = 1; + out->pCredentialParameters = alg; } - alg->dwVersion = WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION; - alg->pwszCredentialType = WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY; - cose->cCredentialParameters = 1; - cose->pCredentialParameters = alg; return 0; } @@ -701,7 +702,7 @@ translate_fido_cred(struct winhello_cred *ctx, const fido_cred_t *cred, fido_log_debug("%s: pack_user", __func__); return FIDO_ERR_INTERNAL; } - if (pack_cose(&ctx->alg, &ctx->cose, cred->type) < 0) { + if (pack_cose(&ctx->cose, cred) < 0) { fido_log_debug("%s: pack_cose", __func__); return FIDO_ERR_INTERNAL; } @@ -763,12 +764,30 @@ decode_attobj(const cbor_item_t *key, const cbor_item_t *val, void *arg) fido_log_debug("%s: fido_blob_decode", __func__); goto fail; } - if (cbor_decode_cred_authdata(val, cred->type, - &cred->authdata_cbor, &cred->authdata, &cred->attcred, - &cred->authdata_ext) < 0) { - fido_log_debug("%s: cbor_decode_cred_authdata", - __func__); - goto fail; + + if (!fido_blob_is_empty(&cred->type_winhello)) { + /* array of credential types was set */ + if (cbor_decode_cred_authdata_multiple_cose(val, &cred->type_winhello, + &cred->authdata_cbor, &cred->authdata, &cred->attcred, + &cred->authdata_ext) < 0) { + fido_log_debug("%s: cbor_decode_cred_authdata_multiple_cose failed", + __func__); + goto fail; + } + fido_log_debug("%s: cbor_decode_cred_authdata_multiple_cose returned attcred.type %d, cred.type %d", + __func__, + cred->attcred.type, + cred->type); + cred->type = cred->attcred.type; + } else { + /* Only single credential type */ + if (cbor_decode_cred_authdata(val, cred->type, + &cred->authdata_cbor, &cred->authdata, &cred->attcred, + &cred->authdata_ext) < 0) { + fido_log_debug("%s: cbor_decode_cred_authdata", + __func__); + goto fail; + } } } @@ -880,6 +899,8 @@ winhello_cred_free(struct winhello_cred *ctx) free(e->pvExtension); } free(ctx->opt.Extensions.pExtensions); + if (ctx->cose.cCredentialParameters > 0) + free(ctx->cose.pCredentialParameters); free(ctx); }