/* * Copyright (c) 2014-2016 Yubico AB * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include #include #include #include "internal.h" #include "ykpiv.h" #define MAX(a,b) (a) > (b) ? (a) : (b) #define MIN(a,b) (a) < (b) ? (a) : (b) /* * Format defined in SP-800-73-4, Appendix A, Table 9 * * FASC-N containing S9999F9999F999999F0F1F0000000000300001E encoded in * 4-bit BCD with 1 bit parity. run through the tools/fasc.pl script to get * bytes. This CHUID has an expiry of 2030-01-01. * * Defined fields: * - 0x30: FASC-N (hard-coded) * - 0x34: Card UUID / GUID (settable) * - 0x35: Exp. Date (hard-coded) * - 0x3e: Signature (hard-coded, empty) * - 0xfe: Error Detection Code (hard-coded) */ const uint8_t CHUID_TMPL[] = { 0x30, 0x19, 0xd4, 0xe7, 0x39, 0xda, 0x73, 0x9c, 0xed, 0x39, 0xce, 0x73, 0x9d, 0x83, 0x68, 0x58, 0x21, 0x08, 0x42, 0x10, 0x84, 0x21, 0x38, 0x42, 0x10, 0xc3, 0xf5, 0x34, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x08, 0x32, 0x30, 0x33, 0x30, 0x30, 0x31, 0x30, 0x31, 0x3e, 0x00, 0xfe, 0x00, }; #define CHUID_GUID_OFFS 29 // f0: Card Identifier // - 0xa000000116 == GSC-IS RID // - 0xff == Manufacturer ID (dummy) // - 0x02 == Card type (javaCard) // - next 14 bytes: card ID const uint8_t CCC_TMPL[] = { 0xf0, 0x15, 0xa0, 0x00, 0x00, 0x01, 0x16, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x01, 0x21, 0xf2, 0x01, 0x21, 0xf3, 0x00, 0xf4, 0x01, 0x00, 0xf5, 0x01, 0x10, 0xf6, 0x00, 0xf7, 0x00, 0xfa, 0x00, 0xfb, 0x00, 0xfc, 0x00, 0xfd, 0x00, 0xfe, 0x00 }; #define CCC_ID_OFFS 9 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, uint8_t certinfo); 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); static size_t _obj_size_max(ykpiv_state *state) { return (state && state->isNEO) ? CB_OBJ_MAX_NEO : CB_OBJ_MAX; } /* ** YKPIV Utility API - aggregate functions and slightly nicer interface */ ykpiv_rc ykpiv_util_get_cardid(ykpiv_state *state, ykpiv_cardid *cardid) { ykpiv_rc res = YKPIV_OK; uint8_t buf[CB_OBJ_MAX]; size_t len = sizeof(buf); if (!cardid) 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 = _ykpiv_fetch_object(state, YKPIV_OBJ_CHUID, buf, (unsigned long *)&len); if (YKPIV_OK == res) { if (len != sizeof(CHUID_TMPL)) { res = YKPIV_GENERIC_ERROR; } else { memcpy(cardid->data, buf + CHUID_GUID_OFFS, YKPIV_CARDID_SIZE); } } Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_set_cardid(ykpiv_state *state, const ykpiv_cardid *cardid) { ykpiv_rc res = YKPIV_OK; uint8_t id[YKPIV_CARDID_SIZE]; uint8_t buf[sizeof(CHUID_TMPL)]; size_t len = 0; if (!state) return YKPIV_GENERIC_ERROR; if (!cardid) { if (PRNG_OK != _ykpiv_prng_generate(id, sizeof(id))) { return YKPIV_RANDOMNESS_ERROR; } } else { memcpy(id, cardid->data, sizeof(id)); } if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return YKPIV_PCSC_ERROR; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup; memcpy(buf, CHUID_TMPL, sizeof(CHUID_TMPL)); memcpy(buf + CHUID_GUID_OFFS, id, sizeof(id)); len = sizeof(CHUID_TMPL); res = _ykpiv_save_object(state, YKPIV_OBJ_CHUID, buf, len); Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_get_cccid(ykpiv_state *state, ykpiv_cccid *ccc) { ykpiv_rc res = YKPIV_OK; uint8_t buf[CB_OBJ_MAX]; size_t len = sizeof(buf); if (!ccc) 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 = _ykpiv_fetch_object(state, YKPIV_OBJ_CAPABILITY, buf, (unsigned long *)&len); if (YKPIV_OK == res) { if (len != sizeof(CCC_TMPL)) { res = YKPIV_GENERIC_ERROR; } else { memcpy(ccc->data, buf + CCC_ID_OFFS, YKPIV_CCCID_SIZE); } } Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_set_cccid(ykpiv_state *state, const ykpiv_cccid *ccc) { ykpiv_rc res = YKPIV_OK; uint8_t id[YKPIV_CCCID_SIZE]; uint8_t buf[sizeof(CCC_TMPL)]; size_t len = 0; if (!state) return YKPIV_GENERIC_ERROR; if (!ccc) { if (PRNG_OK != _ykpiv_prng_generate(id, sizeof(id))) { return YKPIV_RANDOMNESS_ERROR; } } else { memcpy(id, ccc->data, sizeof(id)); } if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return YKPIV_PCSC_ERROR; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup; len = sizeof(CCC_TMPL); memcpy(buf, CCC_TMPL, len); memcpy(buf + CCC_ID_OFFS, id, YKPIV_CCCID_SIZE); res = _ykpiv_save_object(state, YKPIV_OBJ_CAPABILITY, buf, len); Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_devmodel ykpiv_util_devicemodel(ykpiv_state *state) { if (!state || state->context == SCARD_E_INVALID_HANDLE) return DEVTYPE_UNKNOWN; return (state->isNEO ? DEVTYPE_NEOr3 : DEVTYPE_YK4); } ykpiv_rc ykpiv_util_list_keys(ykpiv_state *state, uint8_t *key_count, ykpiv_key **data, size_t *data_len) { ykpiv_rc res = YKPIV_OK; ykpiv_key *pKey = NULL; uint8_t *pData = NULL; size_t cbData = 0; size_t offset = 0; uint8_t buf[CB_BUF_MAX]; size_t cbBuf = 0; bool transaction = false; size_t i = 0; size_t cbRealloc = 0; const size_t CB_PAGE = 4096; const uint8_t SLOTS[] = { YKPIV_KEY_AUTHENTICATION, YKPIV_KEY_SIGNATURE, YKPIV_KEY_KEYMGM, YKPIV_KEY_RETIRED1, YKPIV_KEY_RETIRED2, YKPIV_KEY_RETIRED3, YKPIV_KEY_RETIRED4, YKPIV_KEY_RETIRED5, YKPIV_KEY_RETIRED6, YKPIV_KEY_RETIRED7, YKPIV_KEY_RETIRED8, YKPIV_KEY_RETIRED9, YKPIV_KEY_RETIRED10, YKPIV_KEY_RETIRED11, YKPIV_KEY_RETIRED12, YKPIV_KEY_RETIRED13, YKPIV_KEY_RETIRED14, YKPIV_KEY_RETIRED15, YKPIV_KEY_RETIRED16, YKPIV_KEY_RETIRED17, YKPIV_KEY_RETIRED18, YKPIV_KEY_RETIRED19, YKPIV_KEY_RETIRED20, YKPIV_KEY_CARDAUTH }; if ((NULL == data) || (NULL == data_len) || (NULL == key_count)) { 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; // init return parameters *key_count = 0; *data = NULL; *data_len = 0; // allocate initial page of buffer if (NULL == (pData = _ykpiv_alloc(state, CB_PAGE))) { res = YKPIV_MEMORY_ERROR; goto Cleanup; } cbData = CB_PAGE; for (i = 0; i < sizeof(SLOTS); i++) { cbBuf = sizeof(buf); if (YKPIV_OK == (res = _read_certificate(state, SLOTS[i], buf, &cbBuf))) { // add current slot to result, grow result buffer if necessary cbRealloc = (sizeof(ykpiv_key) + cbBuf - 1) > (cbData - offset) ? MAX((sizeof(ykpiv_key) + cbBuf - 1) - (cbData - offset), CB_PAGE) : 0; if (0 != cbRealloc) { if (NULL == (pData = _ykpiv_realloc(state, pData, cbData + cbRealloc))) { res = YKPIV_MEMORY_ERROR; goto Cleanup; } } cbData += cbRealloc; // If ykpiv_key is misaligned or results in padding, this causes problems // in the array we return. If this becomes a problem, we'll probably want // to go with a flat byte array. pKey = (ykpiv_key*)(pData + offset); pKey->slot = SLOTS[i]; pKey->cert_len = (uint16_t)cbBuf; memcpy(pKey->cert, buf, cbBuf); offset += sizeof(ykpiv_key) + cbBuf - 1; (*key_count)++; } } *data = (ykpiv_key*)pData; pData = NULL; if (data_len) { *data_len = offset; } res = YKPIV_OK; Cleanup: if (pData) { _ykpiv_free(state, pData); } _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_free(ykpiv_state *state, void *data) { if (!data) return YKPIV_OK; if (!state || (!(state->allocator.pfn_free))) return YKPIV_GENERIC_ERROR; _ykpiv_free(state, data); return YKPIV_OK; } ykpiv_rc ykpiv_util_read_cert(ykpiv_state *state, uint8_t slot, uint8_t **data, size_t *data_len) { ykpiv_rc res = YKPIV_OK; uint8_t buf[CB_BUF_MAX]; size_t cbBuf = sizeof(buf); if ((NULL == data )|| (NULL == data_len)) 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; *data = 0; *data_len = 0; if (YKPIV_OK == (res = _read_certificate(state, slot, buf, &cbBuf))) { if (NULL == (*data = _ykpiv_alloc(state, cbBuf))) { res = YKPIV_MEMORY_ERROR; goto Cleanup; } memcpy(*data, buf, cbBuf); *data_len = cbBuf; } Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_write_cert(ykpiv_state *state, uint8_t slot, uint8_t *data, size_t data_len, uint8_t certinfo) { ykpiv_rc res = YKPIV_OK; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return YKPIV_PCSC_ERROR; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup; res = _write_certificate(state, slot, data, data_len, certinfo); Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_delete_cert(ykpiv_state *state, uint8_t slot) { return ykpiv_util_write_cert(state, slot, NULL, 0, 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, (const char*)puk, sizeof(puk), (const char*)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]; size_t cbBuf = sizeof(buf); size_t len = 0; uint8_t *ptr = NULL; if ((NULL == containers) || (NULL == n_containers)) { res = YKPIV_GENERIC_ERROR; goto Cleanup; } if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return YKPIV_PCSC_ERROR; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup; *containers = 0; *n_containers = 0; if (YKPIV_OK == (res = _ykpiv_fetch_object(state, YKPIV_OBJ_MSCMAP, buf, (unsigned long*)&cbBuf))) { ptr = buf; // check that object contents are at least large enough to read the header if (cbBuf < CB_OBJ_TAG_MIN) { res = YKPIV_OK; goto Cleanup; } if (*ptr++ == TAG_MSCMAP) { ptr += _ykpiv_get_length(ptr, &len); // check that decoded length represents object contents if (len > (cbBuf - (ptr - buf))) { res = YKPIV_OK; goto Cleanup; } if (NULL == (*containers = _ykpiv_alloc(state, len))) { res = YKPIV_MEMORY_ERROR; goto Cleanup; } // should check if container map isn't corrupt memcpy(*containers, ptr, len); *n_containers = len / sizeof(ykpiv_container); } } Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_write_mscmap(ykpiv_state *state, ykpiv_container *containers, size_t n_containers) { ykpiv_rc res = YKPIV_OK; uint8_t buf[CB_OBJ_MAX]; size_t cbBuf = sizeof(buf); size_t offset = 0; size_t req_len = 0; size_t data_len = n_containers * sizeof(ykpiv_container); if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return YKPIV_PCSC_ERROR; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup; // check if data and data_len are zero, this means that // we intend to delete the object if ((NULL == containers) || (0 == n_containers)) { // if either containers or n_containers are non-zero, return an error, // that we only delete strictly when both are set properly if ((NULL != containers) || (0 != n_containers)) { res = YKPIV_GENERIC_ERROR; } else { res = _ykpiv_save_object(state, YKPIV_OBJ_MSCMAP, NULL, 0); } goto Cleanup; } // encode object data for storage // calculate the required length of the encoded object req_len = 1 /* data tag */ + _ykpiv_set_length(buf, data_len) + data_len; if (req_len > _obj_size_max(state)) { res = YKPIV_SIZE_ERROR; goto Cleanup; } buf[offset++] = TAG_MSCMAP; offset += _ykpiv_set_length(buf + offset, data_len); memcpy(buf + offset, (uint8_t*)containers, data_len); offset += data_len; // write onto device res = _ykpiv_save_object(state, YKPIV_OBJ_MSCMAP, buf, offset); Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_read_msroots(ykpiv_state *state, uint8_t **data, size_t *data_len) { ykpiv_rc res = YKPIV_OK; uint8_t buf[CB_BUF_MAX]; size_t cbBuf = sizeof(buf); size_t len = 0; uint8_t *ptr = NULL; int object_id = 0; uint8_t tag = 0; uint8_t *pData = NULL; size_t cbData = 0; size_t cbRealloc = 0; size_t offset = 0; if (!data || !data_len) 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; *data = 0; *data_len = 0; // allocate first page cbData = _obj_size_max(state); if (NULL == (pData = _ykpiv_alloc(state, cbData))) { res = YKPIV_MEMORY_ERROR; goto Cleanup; } for (object_id = YKPIV_OBJ_MSROOTS1; object_id <= YKPIV_OBJ_MSROOTS5; object_id++) { cbBuf = sizeof(buf); if (YKPIV_OK != (res = _ykpiv_fetch_object(state, object_id, buf, (unsigned long*)&cbBuf))) { goto Cleanup; } ptr = buf; if (cbBuf < CB_OBJ_TAG_MIN) { res = YKPIV_OK; goto Cleanup; } tag = *ptr++; if (((TAG_MSROOTS_MID != tag) && (TAG_MSROOTS_END != tag)) || ((YKPIV_OBJ_MSROOTS5 == object_id) && (TAG_MSROOTS_END != tag))) { // the current object doesn't contain a valid part of a msroots file res = YKPIV_OK; // treat condition as object isn't found goto Cleanup; } ptr += _ykpiv_get_length(ptr, &len); // check that decoded length represents object contents if (len > (cbBuf - (ptr - buf))) { res = YKPIV_OK; goto Cleanup; } cbRealloc = len > (cbData - offset) ? len - (cbData - offset) : 0; if (0 != cbRealloc) { if (NULL == (pData = _ykpiv_realloc(state, pData, cbData + cbRealloc))) { res = YKPIV_MEMORY_ERROR; goto Cleanup; } } cbData += cbRealloc; memcpy(pData + offset, ptr, len); offset += len; if (TAG_MSROOTS_END == tag) { break; } } // return data *data = pData; pData = NULL; *data_len = offset; res = YKPIV_OK; Cleanup: if (pData) { _ykpiv_free(state, pData); } _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_write_msroots(ykpiv_state *state, uint8_t *data, size_t data_len) { ykpiv_rc res = YKPIV_OK; uint8_t buf[CB_OBJ_MAX]; size_t offset = 0; size_t data_offset = 0; size_t data_chunk = 0; size_t n_objs = 0; unsigned int i = 0; size_t cb_obj_max = _obj_size_max(state); if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return YKPIV_PCSC_ERROR; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup; // check if either data and data_len are zero, this means that // we intend to delete the object if ((NULL == data) || (0 == data_len)) { // if either data or data_len are non-zero, return an error, // that we only delete strictly when both are set properly if ((NULL != data) || (0 != data_len)) { res = YKPIV_GENERIC_ERROR; } else { // it should be sufficient to just delete the first object, though // to be complete we should erase all of the MSROOTS objects res = _ykpiv_save_object(state, YKPIV_OBJ_MSROOTS1, NULL, 0); } goto Cleanup; } // calculate number of objects required to store blob n_objs = (data_len / (cb_obj_max - CB_OBJ_TAG_MAX)) + 1; // we're allowing 5 objects to be used to span the msroots file if (n_objs > 5) { res = YKPIV_SIZE_ERROR; goto Cleanup; } for (i = 0; i < n_objs; i++) { offset = 0; data_chunk = MIN(cb_obj_max - CB_OBJ_TAG_MAX, data_len - data_offset); // encode object data for storage buf[offset++] = (i == (n_objs - 1)) ? TAG_MSROOTS_END : TAG_MSROOTS_MID; offset += _ykpiv_set_length(buf + offset, data_chunk); memcpy(buf + offset, data + data_offset, data_chunk); offset += data_chunk; // write onto device res = _ykpiv_save_object(state, YKPIV_OBJ_MSROOTS1 + i, buf, offset); if (YKPIV_OK != res) { goto Cleanup; } data_offset += data_chunk; } Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_generate_key(ykpiv_state *state, uint8_t slot, uint8_t algorithm, uint8_t pin_policy, uint8_t touch_policy, uint8_t **modulus, size_t *modulus_len, uint8_t **exp, size_t *exp_len, uint8_t **point, size_t *point_len) { ykpiv_rc res = YKPIV_OK; unsigned char in_data[11]; unsigned char *in_ptr = in_data; char version[7]; unsigned char data[1024]; unsigned char templ[] = { 0, YKPIV_INS_GENERATE_ASYMMETRIC, 0, 0 }; unsigned long recv_len = sizeof(data); int sw; uint8_t *ptr_modulus = NULL; size_t cb_modulus = 0; uint8_t *ptr_exp = NULL; size_t cb_exp = 0; uint8_t *ptr_point = NULL; size_t cb_point = 0; if (ykpiv_util_devicemodel(state) == DEVTYPE_YK4 && (algorithm == YKPIV_ALGO_RSA1024 || algorithm == YKPIV_ALGO_RSA2048)) { if ((res = ykpiv_get_version(state, version, sizeof(version))) == YKPIV_OK) { int major, minor, build; fprintf(stderr, "version: %s\n", version); int match = sscanf(version, "%d.%d.%d", &major, &minor, &build); if (match == 3 && major == 4 && (minor < 3 || (minor == 3 && build < 5))) { fprintf(stderr, "On-chip RSA key generation on this YubiKey has been blocked.\n"); fprintf(stderr, "Please see https://yubi.co/ysa201701/ for details.\n"); res = YKPIV_NOT_SUPPORTED; goto Cleanup; } } else { fprintf(stderr, "Failed to get device version.\n"); goto Cleanup; } } switch (algorithm) { case YKPIV_ALGO_RSA1024: case YKPIV_ALGO_RSA2048: if (!modulus || !modulus_len || !exp || !exp_len) { if (state->verbose) { fprintf(stderr, "Invalid output parameter for RSA algorithm"); } return YKPIV_GENERIC_ERROR; } *modulus = NULL; *modulus_len = 0; *exp = NULL; *exp_len = 0; break; case YKPIV_ALGO_ECCP256: case YKPIV_ALGO_ECCP384: if (!point || !point_len) { if (state->verbose) { fprintf(stderr, "Invalid output parameter for ECC algorithm"); } return YKPIV_GENERIC_ERROR; } *point = NULL; *point_len = 0; break; default: if (state->verbose) { fprintf(stderr, "Invalid algorithm specified"); } 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; templ[3] = slot; *in_ptr++ = 0xac; *in_ptr++ = 3; *in_ptr++ = YKPIV_ALGO_TAG; *in_ptr++ = 1; *in_ptr++ = algorithm; if (in_data[4] == 0) { res = YKPIV_ALGORITHM_ERROR; if (state->verbose) { fprintf(stderr, "Unexpected algorithm.\n"); } goto Cleanup; } if (pin_policy != YKPIV_PINPOLICY_DEFAULT) { in_data[1] += 3; *in_ptr++ = YKPIV_PINPOLICY_TAG; *in_ptr++ = 1; *in_ptr++ = pin_policy; } if (touch_policy != YKPIV_TOUCHPOLICY_DEFAULT) { in_data[1] += 3; *in_ptr++ = YKPIV_TOUCHPOLICY_TAG; *in_ptr++ = 1; *in_ptr++ = touch_policy; } if (YKPIV_OK != (res = ykpiv_transfer_data(state, templ, in_data, (long)(in_ptr - in_data), data, &recv_len, &sw))) { if (state->verbose) { fprintf(stderr, "Failed to communicate.\n"); } goto Cleanup; } else if (sw != SW_SUCCESS) { if (state->verbose) { fprintf(stderr, "Failed to generate new key ("); } if (sw == SW_ERR_INCORRECT_SLOT) { res = YKPIV_KEY_ERROR; if (state->verbose) { fprintf(stderr, "incorrect slot)\n"); } } else if (sw == SW_ERR_INCORRECT_PARAM) { res = YKPIV_ALGORITHM_ERROR; if (state->verbose) { if (pin_policy != YKPIV_PINPOLICY_DEFAULT) { fprintf(stderr, "pin policy not supported?)\n"); } else if (touch_policy != YKPIV_TOUCHPOLICY_DEFAULT) { fprintf(stderr, "touch policy not supported?)\n"); } else { fprintf(stderr, "algorithm not supported?)\n"); } } } else if (sw == SW_ERR_SECURITY_STATUS) { res = YKPIV_AUTHENTICATION_ERROR; if (state->verbose) { fprintf(stderr, "not authenticated)\n"); } } else { res = YKPIV_GENERIC_ERROR; if (state->verbose) { fprintf(stderr, "error %x)\n", sw); } } goto Cleanup; } if ((YKPIV_ALGO_RSA1024 == algorithm) || (YKPIV_ALGO_RSA2048 == algorithm)) { unsigned char *data_ptr = data + 5; size_t len = 0; if (*data_ptr != TAG_RSA_MODULUS) { if (state->verbose) { fprintf(stderr, "Failed to parse public key structure (modulus).\n"); } res = YKPIV_PARSE_ERROR; goto Cleanup; } data_ptr++; data_ptr += _ykpiv_get_length(data_ptr, &len); cb_modulus = len; if (NULL == (ptr_modulus = _ykpiv_alloc(state, cb_modulus))) { if (state->verbose) { fprintf(stderr, "Failed to allocate memory for modulus.\n"); } res = YKPIV_MEMORY_ERROR; goto Cleanup; } memcpy(ptr_modulus, data_ptr, cb_modulus); data_ptr += len; if (*data_ptr != TAG_RSA_EXP) { if (state->verbose) { fprintf(stderr, "Failed to parse public key structure (public exponent).\n"); } res = YKPIV_PARSE_ERROR; goto Cleanup; } data_ptr++; data_ptr += _ykpiv_get_length(data_ptr, &len); cb_exp = len; if (NULL == (ptr_exp = _ykpiv_alloc(state, cb_exp))) { if (state->verbose) { fprintf(stderr, "Failed to allocate memory for public exponent.\n"); } res = YKPIV_MEMORY_ERROR; goto Cleanup; } memcpy(ptr_exp, data_ptr, cb_exp); // set output parameters *modulus = ptr_modulus; ptr_modulus = NULL; *modulus_len = cb_modulus; *exp = ptr_exp; ptr_exp = NULL; *exp_len = cb_exp; } else if ((YKPIV_ALGO_ECCP256 == algorithm) || (YKPIV_ALGO_ECCP384 == algorithm)) { unsigned char *data_ptr = data + 3; size_t len; if (YKPIV_ALGO_ECCP256 == algorithm) { len = CB_ECC_POINTP256; } else { len = CB_ECC_POINTP384; } if (*data_ptr++ != TAG_ECC_POINT) { if (state->verbose) { fprintf(stderr, "Failed to parse public key structure.\n"); } res = YKPIV_PARSE_ERROR; goto Cleanup; } if (*data_ptr++ != len) { /* the curve point should always be determined by the curve */ if (state->verbose) { fprintf(stderr, "Unexpected length.\n"); } res = YKPIV_ALGORITHM_ERROR; goto Cleanup; } cb_point = len; if (NULL == (ptr_point = _ykpiv_alloc(state, cb_point))) { if (state->verbose) { fprintf(stderr, "Failed to allocate memory for public point.\n"); } res = YKPIV_MEMORY_ERROR; goto Cleanup; } memcpy(ptr_point, data_ptr, cb_point); // set output parameters *point = ptr_point; ptr_point = NULL; *point_len = cb_point; } else { if (state->verbose) { fprintf(stderr, "Wrong algorithm.\n"); } res = YKPIV_ALGORITHM_ERROR; goto Cleanup; } Cleanup: if (ptr_modulus) { _ykpiv_free(state, modulus); } if (ptr_exp) { _ykpiv_free(state, ptr_exp); } if (ptr_point) { _ykpiv_free(state, ptr_exp); } _ykpiv_end_transaction(state); 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 = _ykpiv_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}; unsigned char data[0xff]; unsigned long recv_len = sizeof(data); ykpiv_rc res; int sw; /* note: the reset function is only available when both pins are blocked. */ res = ykpiv_transfer_data(state, templ, NULL, 0, data, &recv_len, &sw); if (YKPIV_OK == res && SW_SUCCESS == sw) { return YKPIV_OK; } return YKPIV_GENERIC_ERROR; } uint32_t ykpiv_util_slot_object(uint8_t slot) { int object_id = -1; switch (slot) { case YKPIV_KEY_AUTHENTICATION: object_id = YKPIV_OBJ_AUTHENTICATION; break; case YKPIV_KEY_SIGNATURE: object_id = YKPIV_OBJ_SIGNATURE; break; case YKPIV_KEY_KEYMGM: object_id = YKPIV_OBJ_KEY_MANAGEMENT; break; case YKPIV_KEY_CARDAUTH: object_id = YKPIV_OBJ_CARD_AUTH; break; case YKPIV_KEY_ATTESTATION: object_id = YKPIV_OBJ_ATTESTATION; break; default: if ((slot >= YKPIV_KEY_RETIRED1) && (slot <= YKPIV_KEY_RETIRED20)) { object_id = YKPIV_OBJ_RETIRED1 + (slot - YKPIV_KEY_RETIRED1); } break; } return object_id; } static ykpiv_rc _read_certificate(ykpiv_state *state, uint8_t slot, uint8_t *buf, size_t *buf_len) { ykpiv_rc res = YKPIV_OK; uint8_t *ptr = NULL; int object_id = ykpiv_util_slot_object(slot); size_t len = 0; if (-1 == object_id) return YKPIV_INVALID_OBJECT; if (YKPIV_OK == (res = _ykpiv_fetch_object(state, object_id, buf, (unsigned long*)buf_len))) { ptr = buf; // check that object contents are at least large enough to read the tag if (*buf_len < CB_OBJ_TAG_MIN) { *buf_len = 0; return YKPIV_OK; } // check that first byte indicates "certificate" type if (*ptr++ == TAG_CERT) { ptr += _ykpiv_get_length(ptr, &len); // check that decoded length represents object contents if (len > (*buf_len - (ptr - buf))) { *buf_len = 0; return YKPIV_OK; } memmove(buf, ptr, len); *buf_len = len; } } else { *buf_len = 0; } return res; } static ykpiv_rc _write_certificate(ykpiv_state *state, uint8_t slot, uint8_t *data, size_t data_len, uint8_t certinfo) { uint8_t buf[CB_OBJ_MAX]; size_t cbBuf = sizeof(buf); int object_id = ykpiv_util_slot_object(slot); size_t offset = 0; size_t req_len = 0; if (-1 == object_id) return YKPIV_INVALID_OBJECT; // check if data or data_len are zero, this means that we intend to delete the object if ((NULL == data) || (0 == data_len)) { // if either data or data_len are non-zero, return an error, // that we only delete strictly when both are set properly if ((NULL != data) || (0 != data_len)) { return YKPIV_GENERIC_ERROR; } return _ykpiv_save_object(state, object_id, NULL, 0); } // encode certificate data for storage // calculate the required length of the encoded object req_len = 1 /* cert tag */ + 3 /* compression tag + data*/ + 2 /* lrc */; req_len += _ykpiv_set_length(buf, data_len); if (req_len > _obj_size_max(state)) return YKPIV_SIZE_ERROR; buf[offset++] = TAG_CERT; offset += _ykpiv_set_length(buf + offset, data_len); memcpy(buf + offset, data, data_len); offset += data_len; // write compression info and LRC trailer buf[offset++] = TAG_CERT_COMPRESS; buf[offset++] = 0x01; buf[offset++] = certinfo == YKPIV_CERTINFO_GZIP ? 0x01 : 0x00; buf[offset++] = TAG_CERT_LRC; buf[offset++] = 00; // 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) { 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) { 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; }