From fb00baf6729d6d8e59accc3539fb3abd7c56a91d Mon Sep 17 00:00:00 2001 From: Trevor Bentley Date: Mon, 28 Aug 2017 12:32:56 +0200 Subject: [PATCH] Backport from minidriver: commit 90020fea0ac34b2f98b68a5798fa85cb5ad12175 (tag: 3.2) Author: Dave Pate Date: Thu Jul 27 00:31:54 2017 -0700 Release 3.2 Adds automatic PUK blocking Adds feature to turn automatic PUK blocking off Miscellaneous fixes with metadata handling --- lib/Makefile.am | 2 +- lib/internal.h | 12 +- lib/tests/util.c | 6 + lib/util.c | 700 +++++++++++++++++++++++++++++++++++++++++++---- lib/ykpiv.c | 186 ++++++++----- lib/ykpiv.h | 97 ++++++- 6 files changed, 873 insertions(+), 130 deletions(-) diff --git a/lib/Makefile.am b/lib/Makefile.am index 951ea9e..c21df60 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -32,7 +32,7 @@ AM_CPPFLAGS = $(OPENSSL_CFLAGS) $(PCSC_CFLAGS) lib_LTLIBRARIES = libykpiv.la -libykpiv_la_SOURCES = ykpiv.c util.c version.c ykpiv.pc.in ykpiv.map internal.h +libykpiv_la_SOURCES = ykpiv.c util.c des.c des.h version.c ykpiv.pc.in ykpiv.map internal.h libykpiv_la_SOURCES += error.c libykpiv_la_includedir = $(includedir)/ykpiv libykpiv_la_include_HEADERS = ykpiv.h ykpiv-version.h diff --git a/lib/internal.h b/lib/internal.h index 472b3c7..411fd39 100644 --- a/lib/internal.h +++ b/lib/internal.h @@ -55,12 +55,8 @@ struct ykpiv_state { SCARDHANDLE card; int verbose; char *pin; - ykpiv_allocator allocator; bool isNEO; - uint8_t mgmKey[CB_MGM_KEY]; - bool fMgmKeySet; - }; union u_APDU { @@ -93,7 +89,11 @@ extern unsigned const char aid[]; #define CB_ATR_MAX 33 -#define ATR_NEO_R3 "\x3b\xfc\x13\x00\x00\x81\x31\xfe\x15\x59\x75\x62\x69\x6b\x65\x79\x4e\x45\x4f\x72\x33\xe1" -#define ATR_YK4 "\x3b\xf8\x13\x00\x00\x81\x31\xfe\x15\x59\x75\x62\x69\x6b\x65\x79\x34\xd4" +#define YKPIV_ATR_NEO_R3 "\x3b\xfc\x13\x00\x00\x81\x31\xfe\x15\x59\x75\x62\x69\x6b\x65\x79\x4e\x45\x4f\x72\x33\xe1" +#define YKPIV_ATR_YK4 "\x3b\xf8\x13\x00\x00\x81\x31\xfe\x15\x59\x75\x62\x69\x6b\x65\x79\x34\xd4" + +#define CHREF_ACT_CHANGE_PIN 0 +#define CHREF_ACT_UNBLOCK_PIN 1 +#define CHREF_ACT_CHANGE_PUK 2 #endif diff --git a/lib/tests/util.c b/lib/tests/util.c index 11b2dd8..6c19f38 100644 --- a/lib/tests/util.c +++ b/lib/tests/util.c @@ -460,6 +460,12 @@ START_TEST(test_authenticate) { res = ykpiv_authenticate(g_state, key); ck_assert_int_eq(res, YKPIV_OK); + // Verify same key works twice + res = ykpiv_hex_decode(default_mgm_key, strlen(default_mgm_key), key, &key_len); + ck_assert_int_eq(res, YKPIV_OK); + res = ykpiv_authenticate(g_state, key); + ck_assert_int_eq(res, YKPIV_OK); + // Change to new key res = ykpiv_hex_decode(mgm_key, strlen(mgm_key), key, &key_len); ck_assert_int_eq(res, YKPIV_OK); diff --git a/lib/util.c b/lib/util.c index 8c41de4..6aa5dc8 100644 --- a/lib/util.c +++ b/lib/util.c @@ -34,6 +34,7 @@ #include #include +#include "des.h" #include #include #include @@ -62,26 +63,29 @@ const uint8_t CCC_TMPL[] = { #define CCC_ID_OFFS 9 #define CB_CCC_ID 14 -#define TAG_CERT 0x70 -#define TAG_CERT_COMPRESS 0x71 -#define TAG_CERT_LRC 0xFE -#define TAG_PIVMAN_DATA 0x80 -#define TAG_FLAGS_1 0x81 -#define TAG_SALT 0x82 -#define TAG_PIN_TIMESTAMP 0x83 -#define TAG_MSCMAP 0x81 -#define TAG_MSROOTS_END 0x82 -#define TAG_MSROOTS_MID 0x83 +#define TAG_CERT 0x70 +#define TAG_CERT_COMPRESS 0x71 +#define TAG_CERT_LRC 0xFE +#define TAG_ADMIN 0x80 +#define TAG_ADMIN_FLAGS_1 0x81 +#define TAG_ADMIN_SALT 0x82 +#define TAG_ADMIN_TIMESTAMP 0x83 +#define TAG_PROTECTED 0x88 +#define TAG_PROTECTED_FLAGS_1 0x81 +#define TAG_PROTECTED_MGM 0x89 +#define TAG_MSCMAP 0x81 +#define TAG_MSROOTS_END 0x82 +#define TAG_MSROOTS_MID 0x83 -#define TAG_RSA_MODULUS 0x81 -#define TAG_RSA_EXP 0x82 -#define TAG_ECC_POINT 0x86 +#define TAG_RSA_MODULUS 0x81 +#define TAG_RSA_EXP 0x82 +#define TAG_ECC_POINT 0x86 #define CB_ECC_POINTP256 65 #define CB_ECC_POINTP384 97 -#define YKPIV_OBJ_PIVMAN_DATA 0x5fff00 +#define YKPIV_OBJ_ADMIN_DATA 0x5fff00 #define YKPIV_OBJ_ATTESTATION 0x5fff01 #define YKPIV_OBJ_MSCMAP 0x5fff10 #define YKPIV_OBJ_MSROOTS1 0x5fff11 @@ -90,45 +94,24 @@ const uint8_t CCC_TMPL[] = { #define YKPIV_OBJ_MSROOTS4 0x5fff14 #define YKPIV_OBJ_MSROOTS5 0x5fff15 +#define ADMIN_FLAGS_1_PUK_BLOCKED 0x01 +#define ADMIN_FLAGS_1_PROTECTED_MGM 0x02 + +#define CB_ADMIN_SALT 16 +#define CB_ADMIN_TIMESTAMP 4 + +#define ITER_MGM_PBKDF2 10000 + +#define PROTECTED_FLAGS_1_PUK_NOBLOCK 0x01 + #define CB_OBJ_TAG_MIN 2 // 1 byte tag + 1 byte len #define CB_OBJ_TAG_MAX (CB_OBJ_TAG_MIN + 2) // 1 byte tag + 3 bytes len -typedef enum { - PRNG_OK = 0, - PRNG_GENERAL_ERROR = -1 -} prng_rc; +#define member_size(type, member) sizeof(((type*)0)->member) static ykpiv_rc _read_certificate(ykpiv_state *state, uint8_t slot, uint8_t *buf, size_t *buf_len); static ykpiv_rc _write_certificate(ykpiv_state *state, uint8_t slot, uint8_t *data, size_t data_len); -prng_rc prng_generate(unsigned char *buffer, const size_t cb_req) { - // TREV TODO: ykpiv.c needs to use this - prng_rc rc = PRNG_OK; - -#ifdef _WINDOWS - HCRYPTPROV hProv = 0; - - if (CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { - if (!CryptGenRandom(hProv, (DWORD)cb_req, buffer)) { - rc = PRNG_GENERAL_ERROR; - } - - CryptReleaseContext(hProv, 0); - } - else { - rc = PRNG_GENERAL_ERROR; - } - -#else - if (-1 == RAND_pseudo_bytes(buffer, cb_req)) { - rc = PRNG_GENERAL_ERROR; - } - -#endif - - return rc; -} - static size_t _obj_size_max(ykpiv_state *state) { return (state && state->isNEO) ? CB_OBJ_MAX_NEO : CB_OBJ_MAX; } @@ -145,6 +128,11 @@ ykpiv_rc _ykpiv_begin_transaction(ykpiv_state *state); ykpiv_rc _ykpiv_end_transaction(ykpiv_state *state); ykpiv_rc _ykpiv_ensure_application_selected(ykpiv_state *state); +static ykpiv_rc _read_metadata(ykpiv_state *state, uint8_t tag, uint8_t* data, size_t* pcb_data); +static ykpiv_rc _write_metadata(ykpiv_state *state, uint8_t tag, uint8_t *data, size_t cb_data); +static ykpiv_rc _get_metadata_item(uint8_t *data, size_t cb_data, uint8_t tag, uint8_t **pp_item, size_t *pcb_item); +static ykpiv_rc _set_metadata_item(uint8_t *data, size_t *pcb_data, size_t cb_data_max, uint8_t tag, uint8_t *p_item, size_t cb_item); + /* ** YKPIV Utility API - aggregate functions and slightly nicer interface */ @@ -387,6 +375,66 @@ ykpiv_rc ykpiv_util_delete_cert(ykpiv_state *state, uint8_t slot) { return ykpiv_util_write_cert(state, slot, NULL, 0); } +ykpiv_rc ykpiv_util_block_puk(ykpiv_state *state) { + ykpiv_rc res = YKPIV_OK; + uint8_t puk[] = { 0x30, 0x42, 0x41, 0x44, 0x46, 0x30, 0x30, 0x44 }; + int tries = -1; + uint8_t data[CB_BUF_MAX]; + size_t cb_data = sizeof(data); + uint8_t *p_item = NULL; + size_t cb_item = 0; + uint8_t flags = 0; + + if (NULL == state) return YKPIV_GENERIC_ERROR; + + if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return YKPIV_PCSC_ERROR; + if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup; + + while (tries != 0) { + if (YKPIV_OK == (res = ykpiv_change_puk(state, puk, sizeof(puk), puk, sizeof(puk), &tries))) { + /* did we accidentally choose the correct PUK?, change our puk and try again */ + puk[0]++; + } + else { + /* depending on the firmware, tries may not be set to zero when the PUK is blocked, */ + /* instead, the return code will be PIN_LOCKED and tries will be unset */ + if (YKPIV_PIN_LOCKED == res) { + tries = 0; + res = YKPIV_OK; + } + } + } + + /* attempt to set the puk blocked flag in admin data */ + + if (YKPIV_OK == _read_metadata(state, TAG_ADMIN, data, &cb_data)) { + if (YKPIV_OK == _get_metadata_item(data, cb_data, TAG_ADMIN_FLAGS_1, &p_item, &cb_item)) { + if (sizeof(flags) == cb_item) { + memcpy(&flags, p_item, cb_item); + } + else { + if (state->verbose) { fprintf(stderr, "admin flags exist, but are incorrect size = %zu", cb_item); } + } + } + } + + flags |= ADMIN_FLAGS_1_PUK_BLOCKED; + + if (YKPIV_OK != _set_metadata_item(data, &cb_data, CB_OBJ_MAX, TAG_ADMIN_FLAGS_1, (uint8_t*)&flags, sizeof(flags))) { + if (state->verbose) { fprintf(stderr, "could not set admin flags"); } + } + else { + if (YKPIV_OK != _write_metadata(state, TAG_ADMIN, data, cb_data)) { + if (state->verbose) { fprintf(stderr, "could not write admin metadata"); } + } + } + +Cleanup: + + _ykpiv_end_transaction(state); + return res; +} + ykpiv_rc ykpiv_util_read_mscmap(ykpiv_state *state, ykpiv_container **containers, size_t *n_containers) { ykpiv_rc res = YKPIV_OK; uint8_t buf[CB_BUF_MAX]; @@ -857,6 +905,327 @@ Cleanup: return res; } +ykpiv_rc ykpiv_util_get_config(ykpiv_state *state, ykpiv_config *config) { + ykpiv_rc res = YKPIV_OK; + uint8_t data[CB_BUF_MAX] = { 0 }; + size_t cb_data = sizeof(data); + uint8_t *p_item = NULL; + size_t cb_item = 0; + + if (NULL == state) return YKPIV_GENERIC_ERROR; + if (NULL == config) return YKPIV_GENERIC_ERROR; + + // initialize default values + + config->protected_data_available = false; + config->puk_blocked = false; + config->puk_noblock_on_upgrade = false; + config->pin_last_changed = 0; + config->mgm_type = YKPIV_CONFIG_MGM_MANUAL; + + if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return YKPIV_PCSC_ERROR; + if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup; + + /* recover admin data */ + if (YKPIV_OK == _read_metadata(state, TAG_ADMIN, data, &cb_data)) { + if (YKPIV_OK == _get_metadata_item(data, cb_data, TAG_ADMIN_FLAGS_1, &p_item, &cb_item)) { + if (*p_item & ADMIN_FLAGS_1_PUK_BLOCKED) config->puk_blocked = true; + if (*p_item & ADMIN_FLAGS_1_PROTECTED_MGM) config->mgm_type = YKPIV_CONFIG_MGM_PROTECTED; + } + + if (YKPIV_OK == _get_metadata_item(data, cb_data, TAG_ADMIN_SALT, &p_item, &cb_item)) { + if (config->mgm_type != YKPIV_CONFIG_MGM_MANUAL) { + if (state->verbose) { + fprintf(stderr, "conflicting types of mgm key administration configured\n"); + } + } + else { + config->mgm_type = YKPIV_CONFIG_MGM_DERIVED; + } + } + + if (YKPIV_OK == _get_metadata_item(data, cb_data, TAG_ADMIN_TIMESTAMP, &p_item, &cb_item)) { + if (CB_ADMIN_TIMESTAMP != cb_item) { + if (state->verbose) { + fprintf(stderr, "pin timestamp in admin metadata is an invalid size"); + } + } + else { + memcpy(&(config->pin_last_changed), p_item, cb_item); + } + } + } + + /* recover protected data */ + cb_data = sizeof(data); + + if (YKPIV_OK == _read_metadata(state, TAG_PROTECTED, data, &cb_data)) { + config->protected_data_available = true; + + if (YKPIV_OK == _get_metadata_item(data, cb_data, TAG_PROTECTED_FLAGS_1, &p_item, &cb_item)) { + if (*p_item & PROTECTED_FLAGS_1_PUK_NOBLOCK) config->puk_noblock_on_upgrade = true; + } + + if (YKPIV_OK == _get_metadata_item(data, cb_data, TAG_PROTECTED_MGM, &p_item, &cb_item)) { + if (config->mgm_type != YKPIV_CONFIG_MGM_PROTECTED) { + if (state->verbose) { + fprintf(stderr, "conflicting types of mgm key administration configured - protected mgm exists\n"); + } + } + config->mgm_type = YKPIV_CONFIG_MGM_PROTECTED; /* always favor protected mgm */ + } + } + +Cleanup: + + _ykpiv_end_transaction(state); + return res; +} + +ykpiv_rc ykpiv_util_set_pin_last_changed(ykpiv_state *state) { + ykpiv_rc res = YKPIV_OK; + ykpiv_rc ykrc = YKPIV_OK; + uint8_t data[CB_BUF_MAX] = { 0 }; + size_t cb_data = sizeof(data); + time_t tnow = 0; + + if (NULL == state) return YKPIV_GENERIC_ERROR; + + if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return YKPIV_PCSC_ERROR; + if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup; + + /* recover admin data */ + if (YKPIV_OK != (ykrc = _read_metadata(state, TAG_ADMIN, data, &cb_data))) { + cb_data = 0; /* set current metadata blob size to zero, we'll add the timestamp to the blank blob */ + } + + tnow = time(NULL); + + if (YKPIV_OK != (res = _set_metadata_item(data, &cb_data, CB_OBJ_MAX, TAG_ADMIN_TIMESTAMP, (uint8_t*)&tnow, CB_ADMIN_TIMESTAMP))) { + if (state->verbose) fprintf(stderr, "could not set pin timestamp, err = %d\n", res); + } + else { + if (YKPIV_OK != (res = _write_metadata(state, TAG_ADMIN, data, cb_data))) { + /* Note: this can fail if authenticate() wasn't called previously - expected behavior */ + if (state->verbose) fprintf(stderr, "could not write admin data, err = %d\n", res); + } + } + +Cleanup: + + _ykpiv_end_transaction(state); + return res; +} + +ykpiv_rc ykpiv_util_get_derived_mgm(ykpiv_state *state, const uint8_t *pin, const size_t pin_len, ykpiv_mgm *mgm) { + ykpiv_rc res = YKPIV_OK; + pkcs5_rc p5rc = PKCS5_OK; + uint8_t data[CB_BUF_MAX] = { 0 }; + size_t cb_data = sizeof(data); + uint8_t *p_item = NULL; + size_t cb_item = 0; + + if (NULL == state) return YKPIV_GENERIC_ERROR; + if ((NULL == pin) || (0 == pin_len) || (NULL == mgm)) return YKPIV_GENERIC_ERROR; + + if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return YKPIV_PCSC_ERROR; + if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup; + + /* recover management key */ + if (YKPIV_OK == (res = _read_metadata(state, TAG_ADMIN, data, &cb_data))) { + if (YKPIV_OK == (res = _get_metadata_item(data, cb_data, TAG_ADMIN_SALT, &p_item, &cb_item))) { + if (cb_item != CB_ADMIN_SALT) { + if (state->verbose) fprintf(stderr, "derived mgm salt exists, but is incorrect size = %zu\n", cb_item); + res = YKPIV_GENERIC_ERROR; + goto Cleanup; + } + + if (PKCS5_OK != (p5rc = pkcs5_pbkdf2_sha1(pin, pin_len, p_item, cb_item, ITER_MGM_PBKDF2, mgm->data, member_size(ykpiv_mgm, data)))) { + if (state->verbose) fprintf(stderr, "pbkdf2 failure, err = %d\n", p5rc); + res = YKPIV_GENERIC_ERROR; + goto Cleanup; + } + } + } + +Cleanup: + + _ykpiv_end_transaction(state); + return res; +} + +ykpiv_rc ykpiv_util_get_protected_mgm(ykpiv_state *state, ykpiv_mgm *mgm) { + ykpiv_rc res = YKPIV_OK; + uint8_t data[CB_BUF_MAX] = { 0 }; + size_t cb_data = sizeof(data); + uint8_t *p_item = NULL; + size_t cb_item = 0; + + if (NULL == state) return YKPIV_GENERIC_ERROR; + if (NULL == mgm) return YKPIV_GENERIC_ERROR; + + if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return YKPIV_PCSC_ERROR; + if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup; + + if (YKPIV_OK != (res = _read_metadata(state, TAG_PROTECTED, data, &cb_data))) { + if (state->verbose) fprintf(stderr, "could not read protected data, err = %d\n", res); + goto Cleanup; + } + + if (YKPIV_OK != (res = _get_metadata_item(data, cb_data, TAG_PROTECTED_MGM, &p_item, &cb_item))) { + if (state->verbose) fprintf(stderr, "could not read protected mgm from metadata, err = %d\n", res); + goto Cleanup; + } + + if (cb_item != member_size(ykpiv_mgm, data)) { + if (state->verbose) fprintf(stderr, "protected data contains mgm, but is the wrong size = %zu\n", cb_item); + res = YKPIV_AUTHENTICATION_ERROR; + goto Cleanup; + } + + memcpy(mgm->data, p_item, cb_item); + +Cleanup: + + memset(data, 0, sizeof(data)); + + _ykpiv_end_transaction(state); + return res; + +} + +/* to set a generated mgm, pass NULL for mgm, or set mgm.data to all zeroes */ +ykpiv_rc ykpiv_util_set_protected_mgm(ykpiv_state *state, ykpiv_mgm *mgm) { + ykpiv_rc res = YKPIV_OK; + ykpiv_rc ykrc = YKPIV_OK; + prng_rc prngrc = PRNG_OK; + bool fGenerate = false; + uint8_t mgm_key[member_size(ykpiv_mgm, data)] = { 0 }; + size_t i = 0; + uint8_t data[CB_BUF_MAX] = { 0 }; + size_t cb_data = sizeof(data); + uint8_t *p_item = NULL; + size_t cb_item = 0; + uint8_t flags_1 = 0; + + if (NULL == state) return YKPIV_GENERIC_ERROR; + + if (!mgm) { + fGenerate = true; + } + else { + fGenerate = true; + memcpy(mgm_key, mgm->data, sizeof(mgm_key)); + + for (i = 0; i < sizeof(mgm_key); i++) { + if (mgm_key[i] != 0) { + fGenerate = false; + break; + } + } + } + + if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return YKPIV_PCSC_ERROR; + if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup; + + /* try to set the mgm key as long as we don't encounter a fatal error */ + do { + if (fGenerate) { + /* generate a new mgm key */ + if (PRNG_OK != (prngrc = prng_generate(mgm_key, sizeof(mgm_key)))) { + if (state->verbose) fprintf(stderr, "could not set generate new mgm, err = %d\n", prngrc); + res = YKPIV_RANDOMNESS_ERROR; + goto Cleanup; + } + } + + if (YKPIV_OK != (ykrc = ykpiv_set_mgmkey(state, mgm_key))) { + /* + ** if _set_mgmkey fails with YKPIV_KEY_ERROR, it means the generated key is weak + ** otherwise, log a warning, since the device mgm key is corrupt or we're in + ** a state where we can't set the mgm key + */ + if (YKPIV_KEY_ERROR != ykrc) { + if (state->verbose) fprintf(stderr, "could not set new derived mgm key, err = %d\n", ykrc); + res = ykrc; + goto Cleanup; + } + } + else { + /* _set_mgmkey succeeded, stop generating */ + fGenerate = false; + } + } while (fGenerate); + + /* set output mgm */ + if (mgm) { + memcpy(mgm->data, mgm_key, sizeof(mgm_key)); + } + + /* after this point, we've set the mgm key, so the function should succeed, regardless of being able to set the metadata */ + + /* set the new mgm key in protected data */ + if (YKPIV_OK != (ykrc = _read_metadata(state, TAG_PROTECTED, data, &cb_data))) { + cb_data = 0; /* set current metadata blob size to zero, we'll add to the blank blob */ + } + + if (YKPIV_OK != (ykrc = _set_metadata_item(data, &cb_data, CB_OBJ_MAX, TAG_PROTECTED_MGM, mgm_key, sizeof(mgm_key)))) { + if (state->verbose) fprintf(stderr, "could not set protected mgm item, err = %d\n", ykrc); + } + else { + if (YKPIV_OK != (ykrc = _write_metadata(state, TAG_PROTECTED, data, cb_data))) { + if (state->verbose) fprintf(stderr, "could not write protected data, err = %d\n", ykrc); + goto Cleanup; + } + } + + /* set the protected mgm flag in admin data */ + cb_data = sizeof(data); + + if (YKPIV_OK != (ykrc = _read_metadata(state, TAG_ADMIN, data, &cb_data))) { + cb_data = 0; + } + else { + + if (YKPIV_OK != (ykrc = _get_metadata_item(data, cb_data, TAG_ADMIN_FLAGS_1, &p_item, &cb_item))) { + /* flags are not set */ + if (state->verbose) fprintf(stderr, "admin data exists, but flags are not present\n"); + } + + if (cb_item == sizeof(flags_1)) { + memcpy(&flags_1, p_item, cb_item); + } + else { + if (state->verbose) fprintf(stderr, "admin data flags are an incorrect size = %zu\n", cb_item); + } + + /* remove any existing salt */ + if (YKPIV_OK != (ykrc = _set_metadata_item(data, &cb_data, CB_OBJ_MAX, TAG_ADMIN_SALT, NULL, 0))) { + if (state->verbose) fprintf(stderr, "could not unset derived mgm salt, err = %d\n", ykrc); + } + } + + flags_1 |= ADMIN_FLAGS_1_PROTECTED_MGM; + + if (YKPIV_OK != (ykrc = _set_metadata_item(data, &cb_data, CB_OBJ_MAX, TAG_ADMIN_FLAGS_1, &flags_1, sizeof(flags_1)))) { + if (state->verbose) fprintf(stderr, "could not set admin flags item, err = %d\n", ykrc); + } + else { + if (YKPIV_OK != (ykrc = _write_metadata(state, TAG_ADMIN, data, cb_data))) { + if (state->verbose) fprintf(stderr, "could not write admin data, err = %d\n", ykrc); + goto Cleanup; + } + } + + +Cleanup: + + memset(data, 0, sizeof(data)); + memset(mgm_key, 0, sizeof(mgm_key)); + + _ykpiv_end_transaction(state); + return res; +} ykpiv_rc ykpiv_util_reset(ykpiv_state *state) { unsigned char templ[] = {0, YKPIV_INS_RESET, 0, 0}; @@ -908,6 +1277,7 @@ static int _slot2object(uint8_t slot) { } static ykpiv_rc _read_certificate(ykpiv_state *state, uint8_t slot, uint8_t *buf, size_t *buf_len) { + // TREV TODO: should this select application? ykpiv_rc res = YKPIV_OK; uint8_t *ptr = NULL; int object_id = _slot2object(slot); @@ -947,6 +1317,7 @@ static ykpiv_rc _read_certificate(ykpiv_state *state, uint8_t slot, uint8_t *buf } static ykpiv_rc _write_certificate(ykpiv_state *state, uint8_t slot, uint8_t *data, size_t data_len) { + // TREV TODO: should this select application? uint8_t buf[CB_OBJ_MAX]; size_t cbBuf = sizeof(buf); int object_id = _slot2object(slot); @@ -990,3 +1361,240 @@ static ykpiv_rc _write_certificate(ykpiv_state *state, uint8_t slot, uint8_t *da // write onto device return ykpiv_save_object(state, object_id, buf, offset); } + +/* +** PIV Manager data helper functions +** +** These functions allow the PIV Manager to extend the YKPIV_OBJ_ADMIN_DATA object without having to change +** this implementation. New items may be added without modifying these functions. Data items are picked +** from the pivman_data buffer by tag, and replaced either in place if length allows or the data object is +** expanded to fit a new/updated data item. +*/ + +/* +** _get_metadata_item +** +** Parses the metadata blob, specified by data, looking for the specified tag. If found, the item is +** returned in pp_item and its size in pcb_item. +** +** If the item is not found, this function returns YKPIV_GENERIC_ERROR. +*/ +static ykpiv_rc _get_metadata_item(uint8_t *data, size_t cb_data, uint8_t tag, uint8_t **pp_item, size_t *pcb_item) { + uint8_t *p_temp = data; + size_t cb_temp = 0; + uint8_t tag_temp = 0; + + if (!data || !pp_item || !pcb_item) return YKPIV_GENERIC_ERROR; + + *pp_item = NULL; + *pcb_item = 0; + + while (p_temp < (data + cb_data)) { + tag_temp = *p_temp++; + p_temp += _ykpiv_get_length(p_temp, &cb_temp); + + if (tag_temp == tag) { + // found tag + break; + } + + p_temp += cb_temp; + } + + if (p_temp < (data + cb_data)) { + *pp_item = p_temp; + *pcb_item = cb_temp; + return YKPIV_OK; + } + + return YKPIV_GENERIC_ERROR; +} + +static int _get_length_size(size_t length) { + if (length < 0x80) { + return 1; + } + else if (length < 0xff) { + return 2; + } + else { + return 3; + } +} + +/* +** _set_metadata_item +** +** Adds or replaces a data item encoded in a metadata blob, specified by tag to the existing +** metadata blob (data) until it reaches the a maximum buffer size (cb_data_max). +** +** If adding/replacing the item would exceed cb_data_max, this function returns YKPIV_GENERIC_ERROR. +** +** The new size of the blob is returned in pcb_data. +*/ +static ykpiv_rc _set_metadata_item(uint8_t *data, size_t *pcb_data, size_t cb_data_max, uint8_t tag, uint8_t *p_item, size_t cb_item) { + uint8_t *p_temp = data; + size_t cb_temp = 0; + uint8_t tag_temp = 0; + size_t cb_len = 0; + uint8_t *p_next = NULL; + long cb_moved = 0; /* must be signed to have negative offsets */ + + if (!data || !pcb_data) return YKPIV_GENERIC_ERROR; + + while (p_temp < (data + *pcb_data)) { + tag_temp = *p_temp++; + cb_len = _ykpiv_get_length(p_temp, &cb_temp); + p_temp += cb_len; + + if (tag_temp == tag) { + /* found tag */ + + /* check length, if it matches, overwrite */ + if (cb_temp == cb_item) { + memcpy(p_temp, p_item, cb_item); + return YKPIV_OK; + } + + /* length doesn't match, expand/shrink to fit */ + p_next = p_temp + cb_temp; + cb_moved = (long)cb_item - (long)cb_temp + ((long)(cb_item != 0 ? _get_length_size(cb_item) : -1 /* for tag, if deleting */) - (long)cb_len); /* accounts for different length encoding */ + + /* length would cause buffer overflow, return error */ + if (*pcb_data + cb_moved > cb_data_max) { + return YKPIV_GENERIC_ERROR; + } + + /* move remaining data */ + memmove(p_next + cb_moved, p_next, *pcb_data - (p_next - data)); + *pcb_data += cb_moved; + + /* re-encode item and insert */ + if (cb_item != 0) { + p_temp -= cb_len; + p_temp += _ykpiv_set_length(p_temp, cb_item); + memcpy(p_temp, p_item, cb_item); + } + + return YKPIV_OK; + } //if tag found + + p_temp += cb_temp; + } + + if (cb_item == 0) { + /* we've been asked to delete an existing item that isn't in the blob */ + return YKPIV_OK; + } + + // we did not find an existing tag, append + p_temp = data + *pcb_data; + cb_len = _get_length_size(cb_item); + + // length would cause buffer overflow, return error + if (*pcb_data + cb_len + cb_item > cb_data_max) { + return YKPIV_GENERIC_ERROR; + } + + *p_temp++ = tag; + p_temp += _ykpiv_set_length(p_temp, cb_item); + memcpy(p_temp, p_item, cb_item); + *pcb_data += 1 + cb_len + cb_item; + + return YKPIV_OK; +} + +/* +** _read_metadata +** +** Reads admin or protected data (specified by tag) from its associated object. +** +** The data stored in the object is parsed to ensure it has the correct tag and valid length. +** +** data must point to a buffer of at least CB_BUF_MAX bytes, and pcb_data should point to +** the size of data. +** +** To read from protected data, the pin must be verified prior to calling this function. +*/ +static ykpiv_rc _read_metadata(ykpiv_state *state, uint8_t tag, uint8_t* data, size_t* pcb_data) { + // TREV TODO: should this select application? + ykpiv_rc res = YKPIV_OK; + uint8_t *p_temp = NULL; + size_t cb_temp = 0; + int obj_id = 0; + + if (!data || !data || !pcb_data || (CB_BUF_MAX > *pcb_data)) return YKPIV_GENERIC_ERROR; + + switch (tag) { + case TAG_ADMIN: obj_id = YKPIV_OBJ_ADMIN_DATA; break; + case TAG_PROTECTED: obj_id = YKPIV_OBJ_PRINTED; break; + default: return YKPIV_INVALID_OBJECT; + } + + cb_temp = *pcb_data; + *pcb_data = 0; + + if (YKPIV_OK != (res = ykpiv_fetch_object(state, obj_id, data, (unsigned long*)&cb_temp))) { + return res; + } + + if (cb_temp < CB_OBJ_TAG_MIN) return YKPIV_GENERIC_ERROR; + + p_temp = data; + + if (tag != *p_temp++) return YKPIV_GENERIC_ERROR; + + p_temp += _ykpiv_get_length(p_temp, pcb_data); + + if (*pcb_data > ((size_t)cb_temp - (p_temp - data))) { + *pcb_data = 0; + return YKPIV_GENERIC_ERROR; + } + + memmove(data, p_temp, *pcb_data); + + return YKPIV_OK; +} + +/* +** _write_metadata +** +** Writes admin/protected data, specified by tag to its associated object. +** +** To delete the metadata, set data to NULL and cb_data to 0. +** +** To write protected data, the pin must be verified prior to calling this function. +*/ +static ykpiv_rc _write_metadata(ykpiv_state *state, uint8_t tag, uint8_t *data, size_t cb_data) { + // TREV TODO: should this select application? + ykpiv_rc res = YKPIV_OK; + uint8_t buf[CB_OBJ_MAX] = { 0 }; + uint8_t *pTemp = buf; + int obj_id = 0; + + if (cb_data > (_obj_size_max(state) - CB_OBJ_TAG_MAX)) { + return YKPIV_GENERIC_ERROR; + } + + switch (tag) { + case TAG_ADMIN: obj_id = YKPIV_OBJ_ADMIN_DATA; break; + case TAG_PROTECTED: obj_id = YKPIV_OBJ_PRINTED; break; + default: return YKPIV_INVALID_OBJECT; + } + + if (!data || (0 == cb_data)) { + // deleting metadata + res = ykpiv_save_object(state, obj_id, NULL, 0); + } + else { + *pTemp++ = tag; + pTemp += _ykpiv_set_length(pTemp, cb_data); + + memcpy(pTemp, data, cb_data); + pTemp += cb_data; + + res = ykpiv_save_object(state, obj_id, buf, pTemp - buf); + } + + return res; +} diff --git a/lib/ykpiv.c b/lib/ykpiv.c index 0e58ee0..99eaf46 100644 --- a/lib/ykpiv.c +++ b/lib/ykpiv.c @@ -34,14 +34,14 @@ #include #include -#include -#include -#include +#include "des.h" #include "internal.h" #include "ykpiv.h" -static ykpiv_rc send_data(ykpiv_state *state, APDU *apdu, +#define YKPIV_MGM_DEFAULT "\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08" + +static ykpiv_rc _send_data(ykpiv_state *state, APDU *apdu, unsigned char *data, unsigned long *recv_len, int *sw); unsigned const char aid[] = { @@ -158,7 +158,7 @@ ykpiv_rc ykpiv_init_with_allocator(ykpiv_state **state, int verbose, const ykpiv s->pin = NULL; s->allocator = *allocator; s->verbose = verbose; - s->context = SCARD_E_INVALID_HANDLE; // TREV TODO -1 on Windows + s->context = SCARD_E_INVALID_HANDLE; // TREV TODO -1 on Windows *state = s; return YKPIV_OK; } @@ -202,7 +202,7 @@ ykpiv_rc _ykpiv_select_application(ykpiv_state *state) { apdu.st.lc = sizeof(aid); memcpy(apdu.st.data, aid, sizeof(aid)); - if((res = send_data(state, &apdu, data, &recv_len, &sw)) != YKPIV_OK) { + if((res = _send_data(state, &apdu, data, &recv_len, &sw)) != YKPIV_OK) { if(state->verbose) { fprintf(stderr, "Failed communicating with card: '%s'\n", ykpiv_strerror(res)); } @@ -283,7 +283,7 @@ ykpiv_rc ykpiv_connect(ykpiv_state *state, const char *wanted) { return YKPIV_PCSC_ERROR; } - state->isNEO = (((sizeof(ATR_NEO_R3) - 1) == atr_len) && (0 == memcmp(ATR_NEO_R3, atr, atr_len))); + state->isNEO = (((sizeof(YKPIV_ATR_NEO_R3) - 1) == atr_len) && (0 == memcmp(YKPIV_ATR_NEO_R3, atr, atr_len))); } state->card = card; @@ -434,7 +434,7 @@ ykpiv_rc ykpiv_transfer_data(ykpiv_state *state, const unsigned char *templ, } apdu.st.lc = this_size; memcpy(apdu.st.data, in_ptr, this_size); - res = send_data(state, &apdu, data, &recv_len, sw); + res = _send_data(state, &apdu, data, &recv_len, sw); if(res != YKPIV_OK) { _ykpiv_end_transaction(state); return res; @@ -466,7 +466,7 @@ ykpiv_rc ykpiv_transfer_data(ykpiv_state *state, const unsigned char *templ, memset(apdu.raw, 0, sizeof(apdu.raw)); apdu.st.ins = 0xc0; - res = send_data(state, &apdu, data, &recv_len, sw); + res = _send_data(state, &apdu, data, &recv_len, sw); if(res != YKPIV_OK) { _ykpiv_end_transaction(state); return res; @@ -485,7 +485,7 @@ ykpiv_rc ykpiv_transfer_data(ykpiv_state *state, const unsigned char *templ, return _ykpiv_end_transaction(state); } -static ykpiv_rc send_data(ykpiv_state *state, APDU *apdu, +static ykpiv_rc _send_data(ykpiv_state *state, APDU *apdu, unsigned char *data, unsigned long *recv_len, int *sw) { long rc; unsigned int send_len = (unsigned int)apdu->st.lc + 5; @@ -519,24 +519,24 @@ static ykpiv_rc send_data(ykpiv_state *state, APDU *apdu, ykpiv_rc ykpiv_authenticate(ykpiv_state *state, unsigned const char *key) { APDU apdu; unsigned char data[261]; - DES_cblock challenge; + unsigned char challenge[8]; unsigned long recv_len = sizeof(data); int sw; ykpiv_rc res; + des_key* mgm_key = NULL; + size_t out_len = 0; - DES_key_schedule ks1, ks2, ks3; + if (NULL == state) return YKPIV_GENERIC_ERROR; - // TREV TODO: default/derived key + if (NULL == key) { + /* use the derived mgm key to authenticate, if it hasn't been derived, use default */ + key = YKPIV_MGM_DEFAULT; + } /* set up our key */ - { - const_DES_cblock key_tmp; - memcpy(key_tmp, key, 8); - DES_set_key_unchecked(&key_tmp, &ks1); - memcpy(key_tmp, key + 8, 8); - DES_set_key_unchecked(&key_tmp, &ks2); - memcpy(key_tmp, key + 16, 8); - DES_set_key_unchecked(&key_tmp, &ks3); + if (DES_OK != des_import_key(DES_TYPE_3DES, key, CB_MGM_KEY, &mgm_key)) { + res = YKPIV_ALGORITHM_ERROR; + goto Cleanup; } /* get a challenge from the card */ @@ -549,10 +549,12 @@ ykpiv_rc ykpiv_authenticate(ykpiv_state *state, unsigned const char *key) { apdu.st.data[0] = 0x7c; apdu.st.data[1] = 0x02; apdu.st.data[2] = 0x80; - if((res = send_data(state, &apdu, data, &recv_len, &sw)) != YKPIV_OK) { - return res; - } else if(sw != SW_SUCCESS) { - return YKPIV_AUTHENTICATION_ERROR; + if ((res = _send_data(state, &apdu, data, &recv_len, &sw)) != YKPIV_OK) { + goto Cleanup; + } + else if (sw != SW_SUCCESS) { + res = YKPIV_AUTHENTICATION_ERROR; + goto Cleanup; } memcpy(challenge, data + 4, 8); } @@ -560,8 +562,9 @@ ykpiv_rc ykpiv_authenticate(ykpiv_state *state, unsigned const char *key) { /* send a response to the cards challenge and a challenge of our own. */ { unsigned char *dataptr = apdu.st.data; - DES_cblock response; - DES_ecb3_encrypt(&challenge, &response, &ks1, &ks2, &ks3, 0); + unsigned char response[8]; + out_len = sizeof(response); + des_decrypt(mgm_key, challenge, sizeof(challenge), response, &out_len); recv_len = sizeof(data); memset(apdu.raw, 0, sizeof(apdu)); @@ -576,32 +579,45 @@ ykpiv_rc ykpiv_authenticate(ykpiv_state *state, unsigned const char *key) { dataptr += 8; *dataptr++ = 0x81; *dataptr++ = 8; - if(RAND_pseudo_bytes(dataptr, 8) == -1) { - if(state->verbose) { - fprintf(stderr, "Failed getting randomness for authentication.\n"); + if (PRNG_GENERAL_ERROR == prng_generate(dataptr, 8)) { + if (state->verbose) { + fprintf(stderr, "Failed getting randomness for authentication.\n"); } - return YKPIV_RANDOMNESS_ERROR; + res = YKPIV_RANDOMNESS_ERROR; + goto Cleanup; } memcpy(challenge, dataptr, 8); dataptr += 8; - apdu.st.lc = dataptr - apdu.st.data; - if((res = send_data(state, &apdu, data, &recv_len, &sw)) != YKPIV_OK) { - return res; - } else if(sw != SW_SUCCESS) { - return YKPIV_AUTHENTICATION_ERROR; + apdu.st.lc = (unsigned char)(dataptr - apdu.st.data); + if ((res = _send_data(state, &apdu, data, &recv_len, &sw)) != YKPIV_OK) { + goto Cleanup; + } + else if (sw != SW_SUCCESS) { + res = YKPIV_AUTHENTICATION_ERROR; + goto Cleanup; } } /* compare the response from the card with our challenge */ { - DES_cblock response; - DES_ecb3_encrypt(&challenge, &response, &ks1, &ks2, &ks3, 1); - if(memcmp(response, data + 4, 8) == 0) { - return YKPIV_OK; - } else { - return YKPIV_AUTHENTICATION_ERROR; + unsigned char response[8]; + out_len = sizeof(response); + des_encrypt(mgm_key, challenge, sizeof(challenge), response, &out_len); + if (memcmp(response, data + 4, 8) == 0) { + res = YKPIV_OK; + } + else { + res = YKPIV_AUTHENTICATION_ERROR; } } + +Cleanup: + + if (mgm_key) { + des_destroy_key(mgm_key); + } + + return res; } ykpiv_rc ykpiv_set_mgmkey(ykpiv_state *state, const unsigned char *new_key) { @@ -613,45 +629,43 @@ ykpiv_rc ykpiv_set_mgmkey2(ykpiv_state *state, const unsigned char *new_key, con unsigned char data[261]; unsigned long recv_len = sizeof(data); int sw; - size_t i; - ykpiv_rc res; + ykpiv_rc res = YKPIV_OK; - for(i = 0; i < 3; i++) { - const_DES_cblock key_tmp; - memcpy(key_tmp, new_key + i * 8, 8); - DES_set_odd_parity(&key_tmp); - if(DES_is_weak_key(&key_tmp) != 0) { - if(state->verbose) { - fprintf(stderr, "Won't set new key '"); - dump_hex(new_key + i * 8, 8); - fprintf(stderr, "' since it's weak (with parity the key is: "); - dump_hex(key_tmp, 8); - fprintf(stderr, ").\n"); - } - return YKPIV_GENERIC_ERROR; + if (yk_des_is_weak_key(new_key, DES_LEN_3DES)) { + if (state->verbose) { + fprintf(stderr, "Won't set new key '"); + dump_hex(new_key, DES_LEN_3DES); + fprintf(stderr, "' since it's weak (with odd parity).\n"); } + return YKPIV_KEY_ERROR; } memset(apdu.raw, 0, sizeof(apdu)); apdu.st.ins = YKPIV_INS_SET_MGMKEY; apdu.st.p1 = 0xff; - if(touch == 0) { + if (touch == 0) { apdu.st.p2 = 0xff; - } else if(touch == 1) { + } + else if (touch == 1) { apdu.st.p2 = 0xfe; - } else { + } + else { return YKPIV_GENERIC_ERROR; } - apdu.st.lc = DES_KEY_SZ * 3 + 3; + + apdu.st.lc = DES_LEN_3DES + 3; apdu.st.data[0] = YKPIV_ALGO_3DES; apdu.st.data[1] = YKPIV_KEY_CARDMGM; - apdu.st.data[2] = DES_KEY_SZ * 3; - memcpy(apdu.st.data + 3, new_key, DES_KEY_SZ * 3); - if((res = send_data(state, &apdu, data, &recv_len, &sw)) != YKPIV_OK) { + apdu.st.data[2] = DES_LEN_3DES; + memcpy(apdu.st.data + 3, new_key, DES_LEN_3DES); + + if ((res = _send_data(state, &apdu, data, &recv_len, &sw)) != YKPIV_OK) { return res; - } else if(sw == SW_SUCCESS) { + } + else if (sw == SW_SUCCESS) { return YKPIV_OK; } + return YKPIV_GENERIC_ERROR; } @@ -793,16 +807,39 @@ ykpiv_rc ykpiv_sign_data(ykpiv_state *state, const unsigned char *raw_in, size_t in_len, unsigned char *sign_out, size_t *out_len, unsigned char algorithm, unsigned char key) { + ykpiv_rc res = YKPIV_OK; - return _general_authenticate(state, raw_in, in_len, sign_out, out_len, - algorithm, key, false); + if (NULL == state) return YKPIV_GENERIC_ERROR; + + if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return YKPIV_PCSC_ERROR; + // TREV TODO: clean up selections + if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup; + + res = _general_authenticate(state, raw_in, in_len, sign_out, out_len, + algorithm, key, false); +Cleanup: + _ykpiv_end_transaction(state); + return res; } ykpiv_rc ykpiv_decipher_data(ykpiv_state *state, const unsigned char *in, size_t in_len, unsigned char *out, size_t *out_len, unsigned char algorithm, unsigned char key) { - return _general_authenticate(state, in, in_len, out, out_len, - algorithm, key, true); + ykpiv_rc res = YKPIV_OK; + + if (NULL == state) return YKPIV_GENERIC_ERROR; + + if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return YKPIV_PCSC_ERROR; + if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup; + + + res = _general_authenticate(state, in, in_len, out, out_len, + algorithm, key, true); + +Cleanup: + + _ykpiv_end_transaction(state); + return res; } ykpiv_rc ykpiv_get_version(ykpiv_state *state, char *version, size_t len) { @@ -814,7 +851,7 @@ ykpiv_rc ykpiv_get_version(ykpiv_state *state, char *version, size_t len) { memset(apdu.raw, 0, sizeof(apdu)); apdu.st.ins = YKPIV_INS_GET_VERSION; - if((res = send_data(state, &apdu, data, &recv_len, &sw)) != YKPIV_OK) { + if((res = _send_data(state, &apdu, data, &recv_len, &sw)) != YKPIV_OK) { return res; } else if(sw == SW_SUCCESS) { int result = snprintf(version, len, "%d.%d.%d", data[0], data[1], data[2]); @@ -828,6 +865,7 @@ ykpiv_rc ykpiv_get_version(ykpiv_state *state, char *version, size_t len) { } ykpiv_rc ykpiv_verify(ykpiv_state *state, const char *pin, int *tries) { + // TREV TODO: pin len? APDU apdu; unsigned char data[261]; unsigned long recv_len = sizeof(data); @@ -853,7 +891,7 @@ ykpiv_rc ykpiv_verify(ykpiv_state *state, const char *pin, int *tries) { memset(apdu.st.data + len, 0xff, 8 - len); } } - if((res = send_data(state, &apdu, data, &recv_len, &sw)) != YKPIV_OK) { + if((res = _send_data(state, &apdu, data, &recv_len, &sw)) != YKPIV_OK) { return res; } else if(sw == SW_SUCCESS) { if (pin) { @@ -895,7 +933,7 @@ ykpiv_rc ykpiv_set_pin_retries(ykpiv_state *state, const int tries) { unsigned char templ[] = {0, YKPIV_INS_SET_PIN_RETRIES, (unsigned char)tries, YKPIV_RETRIES_DEFAULT}; unsigned char data[0xff]; unsigned long recv_len = sizeof(data); - int sw; + int sw = 0; if (0 == tries) { //zero value means no change in retry count according to minidriver spec @@ -919,10 +957,6 @@ ykpiv_rc ykpiv_set_pin_retries(ykpiv_state *state, const int tries) { return res; } -#define CHREF_ACT_CHANGE_PIN 0 -#define CHREF_ACT_UNBLOCK_PIN 1 -#define CHREF_ACT_CHANGE_PUK 2 - static ykpiv_rc change_pin_internal(ykpiv_state *state, int action, const char * current_pin, size_t current_pin_len, const char * new_pin, size_t new_pin_len, int *tries) { int sw; unsigned char templ[] = {0, YKPIV_INS_CHANGE_REFERENCE, 0, 0x80}; diff --git a/lib/ykpiv.h b/lib/ykpiv.h index 0af2e79..37221ce 100644 --- a/lib/ykpiv.h +++ b/lib/ykpiv.h @@ -70,7 +70,7 @@ extern "C" ykpiv_pfn_alloc pfn_alloc; ykpiv_pfn_realloc pfn_realloc; ykpiv_pfn_free pfn_free; - void * alloc_data; + void * alloc_data; } ykpiv_allocator; const char *ykpiv_strerror(ykpiv_rc err); @@ -308,6 +308,92 @@ extern "C" ykpiv_rc ykpiv_util_read_msroots(ykpiv_state *state, uint8_t **data, size_t *data_len); ykpiv_rc ykpiv_util_write_msroots(ykpiv_state *state, uint8_t *data, size_t data_len); + typedef enum { + YKPIV_CONFIG_MGM_MANUAL = 0, + YKPIV_CONFIG_MGM_DERIVED = 1, + YKPIV_CONFIG_MGM_PROTECTED = 2 + } ykpiv_config_mgm_type; + +#pragma pack(push, 1) + typedef struct _ykpiv_config { + uint8_t protected_data_available; + uint8_t puk_blocked; + uint8_t puk_noblock_on_upgrade; + uint32_t pin_last_changed; + ykpiv_config_mgm_type mgm_type; + } ykpiv_config; + + typedef struct _ykpiv_mgm { + uint8_t data[24]; + } ykpiv_mgm; +#pragma pack(pop) + + /** + * Get current PIV applet administration configuration state + * + * @param state [in] state + * @param config [out] output ykpiv_config struct with current applet data + * + * @return ykpiv_rc error code + */ + ykpiv_rc ykpiv_util_get_config(ykpiv_state *state, ykpiv_config *config); + + /** + * Set last pin changed time to current time + * + * The applet must be authenticated to call this function + * + * @param state state + * + * @return ykpiv_rc error code + */ + ykpiv_rc ykpiv_util_set_pin_last_changed(ykpiv_state *state); + + /** + * Get Derived MGM key + * + * @param state [in] state + * @param pin [in] pin used to derive mgm key + * @param pin_len [in] length of pin + * @param mgm [out] protected mgm key + * + * @return ykpiv_rc error code + */ + ykpiv_rc ykpiv_util_get_derived_mgm(ykpiv_state *state, const uint8_t *pin, const size_t pin_len, ykpiv_mgm *mgm); + + /** + * Get Protected MGM key + * + * The user pin must be verified to call this function + * + * @param state [in] state + * @param mgm [out] returns protected mgm key + * + * @return ykpiv_rc error code + */ + ykpiv_rc ykpiv_util_get_protected_mgm(ykpiv_state *state, ykpiv_mgm *mgm); + + /** + * Set Protected MGM key + * + * The applet must be authenticated and the user pin verified to call this function + * + * @param state state + * @param mgm [in] if mgm is NULL or mgm.data is all zeroes, generate mgm, otherwise set specified key; [out] returns generated mgm key + * + * @return ykpiv_rc error code + */ + ykpiv_rc ykpiv_util_set_protected_mgm(ykpiv_state *state, ykpiv_mgm *mgm); + + /** + * Reset PIV applet + * + * The user pin and puk must be blocked to call this function. + * + * @param state state + * + * @return ykpiv_rc error code + */ ykpiv_rc ykpiv_util_reset(ykpiv_state *state); /** @@ -352,6 +438,15 @@ extern "C" */ ykpiv_devmodel ykpiv_util_devicemodel(ykpiv_state *state); + /** + * Block PUK + * + * Utility function to block the PUK. + * + * To set the PUK blocked flag in the admin data, the applet must be authenticated. + */ + ykpiv_rc ykpiv_util_block_puk(ykpiv_state *state); + #ifdef __cplusplus }