1663 lines
49 KiB
C
1663 lines
49 KiB
C
/*
|
|
* 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 <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <ctype.h>
|
|
#include <time.h>
|
|
|
|
#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, 0xc8, 0x42, 0x10, 0xc3,
|
|
0xeb, 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 || (state->context == (uintptr_t)-1)) {
|
|
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;
|
|
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);
|
|
res = _read_certificate(state, SLOTS[i], buf, &cbBuf);
|
|
|
|
if ((res == YKPIV_OK) && (cbBuf > 0)) {
|
|
// 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))) {
|
|
|
|
/* handle those who write empty certificate blobs to PIV objects */
|
|
if (cbBuf == 0) {
|
|
*data = NULL;
|
|
*data_len = 0;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (!(*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 = %lu", (unsigned long)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 += (unsigned long)_ykpiv_get_length(ptr, &len);
|
|
|
|
/* check that decoded length represents object contents */
|
|
if (len > (cbBuf - (size_t)(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 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 */ + (unsigned long)_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 - (size_t)(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, (int)(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;
|
|
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;
|
|
|
|
setting_bool_t setting_roca = { 0 };
|
|
const char sz_setting_roca[] = "Enable_Unsafe_Keygen_ROCA";
|
|
const char sz_roca_format[] = "YubiKey serial number %u is affected by vulnerability "
|
|
"CVE-2017-15361 (ROCA) and should be replaced. On-chip key generation %s "
|
|
"See YSA-2017-01 <https://www.yubico.com/support/security-advisories/ysa-2017-01/> "
|
|
"for additional information on device replacement and mitigation assistance.\n";
|
|
const char sz_roca_allow_user[] = "was permitted by an end-user configuration setting, but is not recommended.";
|
|
const char sz_roca_allow_admin[] = "was permitted by an administrator configuration setting, but is not recommended.";
|
|
const char sz_roca_block_user[] = "was blocked due to an end-user configuration setting.";
|
|
const char sz_roca_block_admin[] = "was blocked due to an administrator configuration setting.";
|
|
const char sz_roca_default[] = "was permitted by default, but is not recommended. "
|
|
"The default behavior will change in a future Yubico release.";
|
|
|
|
if (!state) return YKPIV_ARGUMENT_ERROR;
|
|
|
|
if (ykpiv_util_devicemodel(state) == DEVTYPE_YK4 && (algorithm == YKPIV_ALGO_RSA1024 || algorithm == YKPIV_ALGO_RSA2048)) {
|
|
if ((state->ver.major == 4) && (state->ver.minor < 3 || ((state->ver.minor == 3) && (state->ver.patch < 5)))) {
|
|
const char *psz_msg = NULL;
|
|
setting_roca = setting_get_bool(sz_setting_roca, true);
|
|
|
|
switch (setting_roca.source) {
|
|
case SETTING_SOURCE_ADMIN:
|
|
psz_msg = setting_roca.value ? sz_roca_allow_admin : sz_roca_block_admin;
|
|
break;
|
|
|
|
case SETTING_SOURCE_USER:
|
|
psz_msg = setting_roca.value ? sz_roca_allow_user : sz_roca_block_user;
|
|
break;
|
|
|
|
default:
|
|
case SETTING_SOURCE_DEFAULT:
|
|
psz_msg = sz_roca_default;
|
|
break;
|
|
}
|
|
|
|
fprintf(stderr, sz_roca_format, state->serial, psz_msg);
|
|
yc_log_event(1, setting_roca.value ? YC_LOG_LEVEL_WARN : YC_LOG_LEVEL_ERROR, sz_roca_format, state->serial, psz_msg);
|
|
|
|
if (!setting_roca.value) {
|
|
return YKPIV_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = %lu\n", (unsigned long)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 = %lu\n", (unsigned long)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 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 = %lu\n", (unsigned long)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 (uint32_t)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 = (int)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 - (size_t)(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];
|
|
int object_id = (int)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 */
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wsign-conversion"
|
|
if ((size_t)(*pcb_data + cb_moved) > cb_data_max) {
|
|
return YKPIV_GENERIC_ERROR;
|
|
}
|
|
#pragma GCC diagnostic pop
|
|
|
|
/* move remaining data */
|
|
memmove(p_next + cb_moved, p_next, *pcb_data - (size_t)(p_next - data));
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wsign-conversion"
|
|
*pcb_data += cb_moved;
|
|
#pragma GCC diagnostic pop
|
|
|
|
/* 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 = (size_t)_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 || !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 > (cb_temp - (size_t)(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, (size_t)(pTemp - buf));
|
|
}
|
|
|
|
return res;
|
|
}
|