Skip to content

Commit

Permalink
Implement commands for management of resident keys
Browse files Browse the repository at this point in the history
Implement command 0x41 which is used by OpenSSH for reading RKs. It has
the following subcommands:
 * CMD_CRED_METADATA - get number of saved/remaining RKs
 * CMD_RP_BEGIN/CMD_RP_NEXT - iterate over the saved RPs
 * CMD_RK_BEGIN/CMD_RK_NEXT - iterate over the RKs for a given RP

Fixes issue solokeys#374 and issue solokeys#314
  • Loading branch information
Radoslav Gerganov committed Mar 16, 2020
1 parent 8ed7157 commit 11d929c
Show file tree
Hide file tree
Showing 6 changed files with 440 additions and 4 deletions.
284 changes: 280 additions & 4 deletions fido2/ctap.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,13 @@ static void ctap_reset_key_agreement();

struct _getAssertionState getAssertionState;

uint8_t verify_pin_auth(uint8_t * pinAuth, uint8_t * clientDataHash)

static uint8_t verify_pin_auth_ex(uint8_t * pinAuth, uint8_t *buf, size_t len)
{
uint8_t hmac[32];

crypto_sha256_hmac_init(PIN_TOKEN, PIN_TOKEN_SIZE, hmac);
crypto_sha256_update(clientDataHash, CLIENT_DATA_HASH_SIZE);
crypto_sha256_update(buf, len);
crypto_sha256_hmac_final(PIN_TOKEN, PIN_TOKEN_SIZE, hmac);

if (memcmp(pinAuth, hmac, 16) == 0)
Expand All @@ -57,10 +58,12 @@ uint8_t verify_pin_auth(uint8_t * pinAuth, uint8_t * clientDataHash)
dump_hex1(TAG_ERR,hmac,16);
return CTAP2_ERR_PIN_AUTH_INVALID;
}

}


uint8_t verify_pin_auth(uint8_t * pinAuth, uint8_t * clientDataHash)
{
return verify_pin_auth_ex(pinAuth, clientDataHash, CLIENT_DATA_HASH_SIZE);
}

uint8_t ctap_get_info(CborEncoder * encoder)
{
Expand Down Expand Up @@ -1154,6 +1157,270 @@ uint8_t ctap_get_next_assertion(CborEncoder * encoder)
return 0;
}

uint8_t ctap_cred_metadata(CborEncoder * encoder)
{
CborEncoder map;
int ret = cbor_encoder_create_map(encoder, &map, 2);
check_ret(ret);
ret = cbor_encode_int(&map, 1);
check_ret(ret);
ret = cbor_encode_int(&map, STATE.rk_stored);
check_ret(ret);
ret = cbor_encode_int(&map, 2);
check_ret(ret);
int remaining_rks = ctap_rk_size() - STATE.rk_stored;
ret = cbor_encode_int(&map, remaining_rks);
check_ret(ret);
ret = cbor_encoder_close_container(encoder, &map);
check_ret(ret);
return 0;
}

uint8_t ctap_cred_rp(CborEncoder * encoder, int rk_ind, int rp_count)
{
CTAP_residentKey rk;
ctap_load_rk(rk_ind, &rk);
// SHA256 hash of "ssh:"
uint8_t ssh_hash[32] = {0xe3, 0x06, 0x10, 0xe8, 0xa1, 0x62, 0x11, 0x59,
0x60, 0xfe, 0x1e, 0xc2, 0x23, 0xe6, 0x52, 0x9c,
0x9f, 0x4b, 0x6e, 0x80, 0x20, 0x0d, 0xcb, 0x5e,
0x5c, 0x32, 0x1c, 0x8a, 0xf1, 0xe2, 0xb1, 0xbf};

CborEncoder map;
size_t map_size = rp_count > 0 ? 3 : 2;
int ret = cbor_encoder_create_map(encoder, &map, map_size);
check_ret(ret);
ret = cbor_encode_int(&map, 3);
check_ret(ret);
{
CborEncoder rp;
ret = cbor_encoder_create_map(&map, &rp, 2);
check_ret(ret);
ret = cbor_encode_text_stringz(&rp, "id");
check_ret(ret);
if (memcmp(ssh_hash, rk.id.rpIdHash, 32) == 0) {
// recover the rpId from the hash because Solo stores only the hash :(
ret = cbor_encode_text_stringz(&rp, "ssh:");
} else {
ret = cbor_encode_text_stringz(&rp, "");
}
check_ret(ret);
ret = cbor_encode_text_stringz(&rp, "name");
check_ret(ret);
ret = cbor_encode_text_stringz(&rp, rk.user.name);
check_ret(ret);
ret = cbor_encoder_close_container(&map, &rp);
check_ret(ret);
}
ret = cbor_encode_int(&map, 4);
check_ret(ret);
cbor_encode_byte_string(&map, rk.id.rpIdHash, 32);
check_ret(ret);
if (rp_count > 0)
{
ret = cbor_encode_int(&map, 5);
check_ret(ret);
ret = cbor_encode_int(&map, rp_count);
check_ret(ret);
}
ret = cbor_encoder_close_container(encoder, &map);
check_ret(ret);
return 0;
}

uint8_t ctap_cred_rk(CborEncoder * encoder, int rk_ind, int rk_count)
{
CTAP_residentKey rk;
ctap_load_rk(rk_ind, &rk);

CborEncoder map;
size_t map_size = rk_count > 0 ? 4 : 3;
int ret = cbor_encoder_create_map(encoder, &map, map_size);
check_ret(ret);
ret = cbor_encode_int(&map, 6);
check_ret(ret);
{
CborEncoder usr;
ret = cbor_encoder_create_map(&map, &usr, 4);
check_ret(ret);
ret = cbor_encode_text_stringz(&usr, "id");
check_ret(ret);
ret = cbor_encode_byte_string(&usr, rk.user.id, rk.user.id_size);
check_ret(ret);
ret = cbor_encode_text_stringz(&usr, "icon");
check_ret(ret);
ret = cbor_encode_text_stringz(&usr, rk.user.icon);
check_ret(ret);
ret = cbor_encode_text_stringz(&usr, "name");
check_ret(ret);
ret = cbor_encode_text_stringz(&usr, rk.user.name);
check_ret(ret);
ret = cbor_encode_text_stringz(&usr, "displayName");
check_ret(ret);
ret = cbor_encode_text_stringz(&usr, rk.user.displayName);
check_ret(ret);
ret = cbor_encoder_close_container(&map, &usr);
check_ret(ret);
}

ret = cbor_encode_int(&map, 7);
check_ret(ret);
{
CborEncoder credId;
ret = cbor_encoder_create_map(&map, &credId, 1);
check_ret(ret);
ret = cbor_encode_text_stringz(&credId, "id");
check_ret(ret);
ret = cbor_encode_byte_string(&credId, (uint8_t*)&rk.id, sizeof(CredentialId));
check_ret(ret);
ret = cbor_encoder_close_container(&map, &credId);
check_ret(ret);
}

ret = cbor_encode_int(&map, 8);
check_ret(ret);
ctap_generate_cose_key(&map, (uint8_t*)&rk.id, sizeof(CredentialId), PUB_KEY_CRED_PUB_KEY, COSE_ALG_ES256);

if (rk_count > 0)
{
ret = cbor_encode_int(&map, 9);
check_ret(ret);
ret = cbor_encode_int(&map, rk_count);
check_ret(ret);
}
ret = cbor_encoder_close_container(encoder, &map);
check_ret(ret);
return 0;
}

uint8_t ctap_cred_mgmt(CborEncoder * encoder, uint8_t * request, int length)
{
CTAP_credMgmt CM;
CTAP_residentKey rk;
int i = 0;
// use the same index for both RP and RK commands, it make things simpler
static int curr_rk_ind = 0;
// keep the rpIdHash specified in CM_cmdRKBegin cause it's not present in CM_cmdRKNext
static uint8_t rpIdHash[32];
// number of stored RPs
int rp_count = 0;
// number of RKs with the specified rpIdHash
int rk_count = 0;

int ret = ctap_parse_cred_mgmt(&CM, request, length);
if (ret != 0)
{
printf2(TAG_ERR,"error, ctap_parse_cred_mgmt failed\n");
return ret;
}
if (CM.cmd == CM_cmdMetadata || CM.cmd == CM_cmdRPBegin)
{
if (CM.pinProtocol != 1)
{
return CTAP1_ERR_OTHER;
}
uint8_t cmd = (uint8_t) CM.cmd;
ret = verify_pin_auth_ex(CM.pinAuth, &cmd, 1);
}
if (CM.cmd == CM_cmdRKBegin)
{
uint8_t params[5 + sizeof(CM.rpIdHash)] = {CM.cmd, 0xa1, 0x01, 0x58, 0x20};
if (CM.pinProtocol != 1)
{
return CTAP1_ERR_OTHER;
}
memcpy(&params[5], CM.rpIdHash, sizeof(CM.rpIdHash));
ret = verify_pin_auth_ex(CM.pinAuth, params, sizeof(params));
}
if (ret == CTAP2_ERR_PIN_AUTH_INVALID)
{
ctap_decrement_pin_attempts();
if (ctap_device_boot_locked())
{
return CTAP2_ERR_PIN_AUTH_BLOCKED;
}
return CTAP2_ERR_PIN_AUTH_INVALID;
}
else
{
ctap_reset_pin_attempts();
}
if (CM.cmd >= CM_cmdRPBegin && CM.cmd <= CM_cmdRKNext)
{
if (STATE.rk_stored == 0)
{
printf2(TAG_ERR,"No resident keys\n");
return CTAP2_ERR_NO_CREDENTIALS;
}
}
if (CM.cmd == CM_cmdRPNext || CM.cmd == CM_cmdRKNext)
{
if (curr_rk_ind >= STATE.rk_stored)
{
printf2(TAG_ERR,"No more resident keys\n");
return CTAP2_ERR_NO_CREDENTIALS;
}
}
if (CM.cmd == CM_cmdRPBegin)
{
curr_rk_ind = 0;
rp_count = STATE.rk_stored;
}
if (CM.cmd == CM_cmdRKBegin)
{
curr_rk_ind = 0;
// store the specified hash, we will need it for CM_cmdRKNext
memcpy(rpIdHash, CM.rpIdHash, 32);
// count how many RKs have this hash
for (i = 0; i < STATE.rk_stored; i++)
{
ctap_load_rk(i, &rk);
if (memcmp(rk.id.rpIdHash, rpIdHash, 32) == 0)
{
rk_count++;
}
}
}
if (CM.cmd == CM_cmdRKBegin || CM.cmd == CM_cmdRKNext)
{
ctap_load_rk(curr_rk_ind, &rk);
// skip resident keys with different rpIdHash
while (memcmp(rk.id.rpIdHash, rpIdHash, 32) != 0 && curr_rk_ind < STATE.rk_stored)
{
curr_rk_ind++;
ctap_load_rk(curr_rk_ind, &rk);
}
if (curr_rk_ind == STATE.rk_stored)
{
printf2(TAG_ERR,"No more resident keys with this rpIdHash\n");
return CTAP2_ERR_NO_CREDENTIALS;
}
}
switch (CM.cmd)
{
case CM_cmdMetadata:
ret = ctap_cred_metadata(encoder);
check_ret(ret);
break;
case CM_cmdRPBegin:
case CM_cmdRPNext:
ret = ctap_cred_rp(encoder, curr_rk_ind, rp_count);
check_ret(ret);
curr_rk_ind++;
break;
case CM_cmdRKBegin:
case CM_cmdRKNext:
ret = ctap_cred_rk(encoder, curr_rk_ind, rk_count);
check_ret(ret);
curr_rk_ind++;
break;
default:
printf2(TAG_ERR, "error, invalid credMgmt cmd: 0x%02x\n", CM.cmd);
return CTAP1_ERR_INVALID_COMMAND;
}
return 0;
}

uint8_t ctap_get_assertion(CborEncoder * encoder, uint8_t * request, int length)
{
CTAP_getAssertion GA;
Expand Down Expand Up @@ -1641,6 +1908,7 @@ uint8_t ctap_request(uint8_t * pkt_raw, int length, CTAP_RESPONSE * resp)
{
case CTAP_MAKE_CREDENTIAL:
case CTAP_GET_ASSERTION:
case CTAP_CBOR_CRED_MGMT_PRE:
if (ctap_device_locked())
{
status = CTAP2_ERR_PIN_BLOCKED;
Expand Down Expand Up @@ -1722,6 +1990,14 @@ uint8_t ctap_request(uint8_t * pkt_raw, int length, CTAP_RESPONSE * resp)
status = CTAP2_ERR_NOT_ALLOWED;
}
break;
case CTAP_CBOR_CRED_MGMT_PRE:
printf1(TAG_CTAP,"CTAP_CBOR_CRED_MGMT_PRE\n");
status = ctap_cred_mgmt(&encoder, pkt_raw, length);

resp->length = cbor_encoder_get_buffer_size(&encoder, buf);

dump_hex1(TAG_DUMP,buf, resp->length);
break;
default:
status = CTAP1_ERR_INVALID_COMMAND;
printf2(TAG_ERR,"error, invalid cmd: 0x%02x\n", cmd);
Expand Down
21 changes: 21 additions & 0 deletions fido2/ctap.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#define CTAP_RESET 0x07
#define GET_NEXT_ASSERTION 0x08
#define CTAP_VENDOR_FIRST 0x40
#define CTAP_CBOR_CRED_MGMT_PRE 0x41
#define CTAP_VENDOR_LAST 0xBF

#define MC_clientDataHash 0x01
Expand All @@ -37,6 +38,16 @@
#define GA_pinAuth 0x06
#define GA_pinProtocol 0x07

#define CM_cmd 0x01
#define CM_cmdMetadata 0x01
#define CM_cmdRPBegin 0x02
#define CM_cmdRPNext 0x03
#define CM_cmdRKBegin 0x04
#define CM_cmdRKNext 0x05
#define CM_rpIdHash 0x02
#define CM_pinProtocol 0x03
#define CM_pinAuth 0x04

#define CP_pinProtocol 0x01
#define CP_subCommand 0x02
#define CP_cmdGetRetries 0x01
Expand Down Expand Up @@ -285,6 +296,16 @@ typedef struct

} CTAP_getAssertion;

typedef struct
{
int cmd;
uint8_t rpIdHash[32];
uint8_t pinAuth[16];
uint8_t pinAuthPresent;
int pinProtocol;
} CTAP_credMgmt;


typedef struct
{
int pinProtocol;
Expand Down
Loading

0 comments on commit 11d929c

Please sign in to comment.