From feceeb0a22630c37364b7d799a4355d2767eb769 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Tue, 13 Nov 2018 01:45:59 -0500 Subject: [PATCH] passing certification and interop with rk --- fido2/ctap.c | 60 ++++++++++--- fido2/ctap.h | 4 + fido2/ctap_parse.c | 15 ++++ fido2/ctaphid.c | 2 +- targets/stm32l442/src/app.h | 4 +- tools/ctap_test.py | 165 +++++++++++++++++++++++++++++++++++- 6 files changed, 229 insertions(+), 21 deletions(-) diff --git a/fido2/ctap.c b/fido2/ctap.c index 7670ab6..f43e73c 100644 --- a/fido2/ctap.c +++ b/fido2/ctap.c @@ -302,10 +302,10 @@ static uint32_t auth_data_update_count(CTAP_authDataHeader * authData) } uint8_t * byte = (uint8_t*) &authData->signCount; - *byte++ = (count >> 0) & 0xff; - *byte++ = (count >> 8) & 0xff; - *byte++ = (count >> 16) & 0xff; *byte++ = (count >> 24) & 0xff; + *byte++ = (count >> 16) & 0xff; + *byte++ = (count >> 8) & 0xff; + *byte++ = (count >> 0) & 0xff; return count; } @@ -344,6 +344,9 @@ static int ctap_make_auth_data(struct rpId * rp, CborEncoder * map, uint8_t * au crypto_sha256_update(rp->id, rp->size); crypto_sha256_final(authData->head.rpIdHash); + printf1(TAG_RED, "rpId: "); dump_hex1(TAG_RED, rp->id, rp->size); + printf1(TAG_RED, "hash: "); dump_hex1(TAG_RED, authData->head.rpIdHash, 32); + count = auth_data_update_count(&authData->head); device_set_status(CTAPHID_STATUS_UPNEEDED); @@ -691,10 +694,10 @@ uint8_t ctap_add_user_entity(CborEncoder * map, CTAP_userEntity * user) int ret = cbor_encode_int(map, RESP_publicKeyCredentialUserEntity); check_ret(ret); - int dispname = (user->name[0] != 0); + int dispname = (user->name[0] != 0) && getAssertionState.user_verified; if (dispname) - ret = cbor_encoder_create_map(map, &entity, 2); + ret = cbor_encoder_create_map(map, &entity, 4); else ret = cbor_encoder_create_map(map, &entity, 1); check_ret(ret); @@ -715,6 +718,20 @@ uint8_t ctap_add_user_entity(CborEncoder * map, CTAP_userEntity * user) ret = cbor_encode_text_stringz(&entity, (const char *)user->name); check_ret(ret); + + ret = cbor_encode_text_string(&entity, "displayName", 11); + check_ret(ret); + + ret = cbor_encode_text_stringz(&entity, (const char *)user->displayName); + check_ret(ret); + + ret = cbor_encode_text_string(&entity, "icon", 4); + check_ret(ret); + + ret = cbor_encode_text_stringz(&entity, (const char *)user->icon); + check_ret(ret); + + } ret = cbor_encoder_close_container(map, &entity); @@ -871,7 +888,9 @@ uint8_t ctap_get_next_assertion(CborEncoder * encoder) { int ret; CborEncoder map; - CTAP_authDataHeader * authData = &getAssertionState.authData; + CTAP_authDataHeader authData; + memmove(&authData, &getAssertionState.authData, sizeof(CTAP_authDataHeader)); + // CTAP_authDataHeader * authData = &getAssertionState.authData; CTAP_credentialDescriptor * cred = pop_credential(); @@ -880,10 +899,10 @@ uint8_t ctap_get_next_assertion(CborEncoder * encoder) return CTAP2_ERR_NOT_ALLOWED; } - auth_data_update_count(authData); - int add_user_info = cred->credential.user.id_size && getAssertionState.user_verified; + auth_data_update_count(&authData); + int add_user_info = cred->credential.user.id_size; - if (getAssertionState.user_verified) + if (add_user_info) { printf1(TAG_GREEN, "adding user info to assertion response\r\n"); ret = cbor_encoder_create_map(encoder, &map, 4); @@ -895,15 +914,24 @@ uint8_t ctap_get_next_assertion(CborEncoder * encoder) } check_ret(ret); + printf1(TAG_RED, "RPID hash: "); dump_hex1(TAG_RED, authData.rpIdHash, 32); { ret = cbor_encode_int(&map,RESP_authData); check_ret(ret); - ret = cbor_encode_byte_string(&map, (uint8_t *)authData, sizeof(CTAP_authDataHeader)); + ret = cbor_encode_byte_string(&map, (uint8_t *)&authData, sizeof(CTAP_authDataHeader)); check_ret(ret); } - ret = ctap_end_get_assertion(&map, cred, (uint8_t *)authData, getAssertionState.clientDataHash, add_user_info); + // if only one account for this RP, null out the user details + if (!getAssertionState.user_verified) + { + printf1(TAG_GREEN, "Not verified, nulling out user details on response\r\n"); + memset(cred->credential.user.name, 0, USER_NAME_LIMIT); + } + + + ret = ctap_end_get_assertion(&map, cred, (uint8_t *)&authData, getAssertionState.clientDataHash, add_user_info); check_retr(ret); ret = cbor_encoder_close_container(encoder, &map); @@ -950,7 +978,7 @@ uint8_t ctap_get_assertion(CborEncoder * encoder, uint8_t * request, int length) printf1(TAG_GA, "ALLOW_LIST has %d creds\n", GA.credLen); int validCredCount = ctap_filter_invalid_credentials(&GA); - int add_user_info = GA.creds[validCredCount - 1].credential.user.id_size && getAssertionState.user_verified; + int add_user_info = GA.creds[validCredCount - 1].credential.user.id_size; if (validCredCount > 1) { map_size += 1; @@ -995,7 +1023,7 @@ uint8_t ctap_get_assertion(CborEncoder * encoder, uint8_t * request, int length) } // if only one account for this RP, null out the user details - if (validCredCount < 2) + if (validCredCount < 2 || !getAssertionState.user_verified) { printf1(TAG_GREEN, "Only one account, nulling out user details on response\r\n"); memset(&GA.creds[0].credential.user.name, 0, USER_NAME_LIMIT); @@ -1340,11 +1368,13 @@ uint8_t ctap_request(uint8_t * pkt_raw, int length, CTAP_RESPONSE * resp) length--; uint8_t * buf = resp->data; + printf1(TAG_GREEN, "lastcmd0 = 0x%02x\r\n", getAssertionState.lastcmd); cbor_encoder_init(&encoder, buf, resp->data_size, 0); printf1(TAG_CTAP,"cbor input structure: %d bytes\n", length); printf1(TAG_DUMP,"cbor req: "); dump_hex1(TAG_DUMP, pkt_raw, length); + printf1(TAG_GREEN, "lastcmd1 = 0x%02x\r\n", getAssertionState.lastcmd); switch(cmd) { @@ -1434,7 +1464,8 @@ uint8_t ctap_request(uint8_t * pkt_raw, int length, CTAP_RESPONSE * resp) } else { - printf2(TAG_ERR, "unwanted GET_NEXT_ASSERTION\n"); + printf2(TAG_ERR, "unwanted GET_NEXT_ASSERTION. lastcmd == 0x%02x\n", getAssertionState.lastcmd); + dump_hex1(TAG_GREEN, &getAssertionState, sizeof(getAssertionState)); status = CTAP2_ERR_NOT_ALLOWED; } break; @@ -1446,6 +1477,7 @@ uint8_t ctap_request(uint8_t * pkt_raw, int length, CTAP_RESPONSE * resp) done: device_set_status(CTAPHID_STATUS_IDLE); getAssertionState.lastcmd = cmd; + printf1(TAG_GREEN, "lastcmd = 0x%02x\r\n", getAssertionState.lastcmd); if (status != CTAP1_ERR_SUCCESS) { diff --git a/fido2/ctap.h b/fido2/ctap.h index bd629d2..0871d53 100644 --- a/fido2/ctap.h +++ b/fido2/ctap.h @@ -109,6 +109,8 @@ #define RP_NAME_LIMIT 32 // application limit, name parameter isn't needed. #define USER_ID_MAX_SIZE 64 #define USER_NAME_LIMIT 65 // Must be minimum of 64 bytes but can be more. +#define DISPLAY_NAME_LIMIT 32 // Must be minimum of 64 bytes but can be more. +#define ICON_LIMIT 128 // Must be minimum of 64 bytes but can be more. #define CTAP_MAX_MESSAGE_SIZE 1200 #define CREDENTIAL_RK_FLASH_PAD 2 // size of RK should be 8-byte aligned to store in flash easily. @@ -140,6 +142,8 @@ typedef struct uint8_t id[USER_ID_MAX_SIZE]; uint8_t id_size; uint8_t name[USER_NAME_LIMIT]; + uint8_t displayName[DISPLAY_NAME_LIMIT]; + uint8_t icon[ICON_LIMIT]; }__attribute__((packed)) CTAP_userEntity; typedef struct { diff --git a/fido2/ctap_parse.c b/fido2/ctap_parse.c index d45351f..08f1c9a 100644 --- a/fido2/ctap_parse.c +++ b/fido2/ctap_parse.c @@ -175,6 +175,13 @@ uint8_t parse_user(CTAP_makeCredential * MC, CborValue * val) printf2(TAG_ERR,"Error, expecting text string type for user.displayName value\n"); return CTAP2_ERR_INVALID_CBOR_TYPE; } + sz = DISPLAY_NAME_LIMIT; + ret = cbor_value_copy_text_string(&map, (char *)MC->user.displayName, &sz, NULL); + if (ret != CborErrorOutOfMemory) + { // Just truncate the name it's okay + check_ret(ret); + } + MC->user.displayName[DISPLAY_NAME_LIMIT - 1] = 0; } else if (strcmp((const char *)key, "icon") == 0) { @@ -183,6 +190,14 @@ uint8_t parse_user(CTAP_makeCredential * MC, CborValue * val) printf2(TAG_ERR,"Error, expecting text string type for user.icon value\n"); return CTAP2_ERR_INVALID_CBOR_TYPE; } + sz = ICON_LIMIT; + ret = cbor_value_copy_text_string(&map, (char *)MC->user.icon, &sz, NULL); + if (ret != CborErrorOutOfMemory) + { // Just truncate the name it's okay + check_ret(ret); + } + MC->user.icon[ICON_LIMIT - 1] = 0; + } else { diff --git a/fido2/ctaphid.c b/fido2/ctaphid.c index e67cbb0..e573405 100644 --- a/fido2/ctaphid.c +++ b/fido2/ctaphid.c @@ -97,7 +97,7 @@ void ctaphid_init() { state = IDLE; buffer_reset(); - ctap_reset_state(); + //ctap_reset_state(); } static uint32_t get_new_cid() diff --git a/targets/stm32l442/src/app.h b/targets/stm32l442/src/app.h index 23134e2..a05b461 100644 --- a/targets/stm32l442/src/app.h +++ b/targets/stm32l442/src/app.h @@ -4,7 +4,7 @@ #define DEBUG_UART USART1 -#define DEBUG_LEVEL 1 +#define DEBUG_LEVEL 0 #define NON_BLOCK_PRINTING 0 @@ -33,7 +33,7 @@ void hw_init(void); #define SOLO_BUTTON_PORT GPIOA #define SOLO_BUTTON_PIN LL_GPIO_PIN_0 -#define SKIP_BUTTON_CHECK_WITH_DELAY 1 +#define SKIP_BUTTON_CHECK_WITH_DELAY 0 #define SKIP_BUTTON_CHECK_FAST 0 #endif diff --git a/tools/ctap_test.py b/tools/ctap_test.py index 0dc807e..4011032 100644 --- a/tools/ctap_test.py +++ b/tools/ctap_test.py @@ -390,7 +390,7 @@ class Tester(): def test_fido2_simple(self, pin_token=None): creds = [] exclude_list = [] - rp = {'id': 'examplo.org', 'name': 'ExaRP'} + rp = {'id': self.origin, 'name': 'ExaRP'} user = {'id': b'usee_od', 'name': 'AB User'} challenge = 'Y2hhbGxlbmdl' PIN = pin_token @@ -432,7 +432,6 @@ class Tester(): for i in range(0,2048**2): creds = [] - print(i) challenge = ''.join([abc[randint(0,len(abc)-1)] for x in range(0,32)]) fake_id1 = array.array('B',[randint(0,255) for i in range(0,150)]).tostring() @@ -445,6 +444,7 @@ class Tester(): for i in range(0,1): t1 = time.time() * 1000 attest, data = self.client.make_credential(rp, user, challenge, pin = PIN, exclude_list = []) + print(attest.auth_data.counter) t2 = time.time() * 1000 attest.verify(data.hash) print('Register valid (%d ms)' % (t2-t1)) @@ -460,6 +460,7 @@ class Tester(): assertions, client_data = self.client.get_assertion(rp['id'], challenge, allow_list, pin = PIN) t2 = time.time() * 1000 assertions[0].verify(client_data.hash, creds[0].public_key) + print(assertions[0].auth_data.counter) print('Assertion valid (%d ms)' % (t2-t1)) sys.stdout.flush() @@ -607,6 +608,160 @@ class Tester(): print('Warning, reset failed: ', e) print('PASS') + def test_rk(self, ): + creds = [] + rp = {'id': 'examplo.org', 'name': 'ExaRP'} + user0 = {'id': b'first one', 'name': 'single User'} + + users = [{'id': b'user' + os.urandom(16), 'name': 'AB User'} for i in range(0,2)] + challenge = 'Y2hhbGxlbmdl' + PIN = None + print('reset') + self.ctap.reset() + #if PIN: self.client.pin_protocol.set_pin(PIN) + + print('registering 1 user with RK') + t1 = time.time() * 1000 + attest, data = self.client.make_credential(rp, user0, challenge, pin = PIN, exclude_list = [], rk = True) + t2 = time.time() * 1000 + attest.verify(data.hash) + creds.append(attest.auth_data.credential_data) + print('Register valid (%d ms)' % (t2-t1)) + + print('1 assertion') + t1 = time.time() * 1000 + assertions, client_data = self.client.get_assertion(rp['id'], challenge, pin = PIN) + t2 = time.time() * 1000 + assertions[0].verify(client_data.hash, creds[0].public_key) + print('Assertion valid (%d ms)' % (t2-t1)) + + print(assertions[0], client_data) + + + print('registering %d users with RK' % len(users)) + for i in range(0,len(users)): + t1 = time.time() * 1000 + attest, data = self.client.make_credential(rp, users[i], challenge, pin = PIN, exclude_list = [], rk = True) + t2 = time.time() * 1000 + attest.verify(data.hash) + print('Register valid (%d ms)' % (t2-t1)) + + creds.append(attest.auth_data.credential_data) + + + t1 = time.time() * 1000 + assertions, client_data = self.client.get_assertion(rp['id'], challenge, pin = PIN) + t2 = time.time() * 1000 + + for x,y in zip(assertions, creds): + x.verify(client_data.hash,y.public_key) + + print('Assertion(s) valid (%d ms)' % (t2-t1)) + + + print('registering a duplicate user ') + + t1 = time.time() * 1000 + attest, data = self.client.make_credential(rp, users[1], challenge, pin = PIN, exclude_list = [], rk = True) + t2 = time.time() * 1000 + attest.verify(data.hash) + creds = creds[:2] + creds[3:] + [attest.auth_data.credential_data] + print('Register valid (%d ms)' % (t2-t1)) + + + t1 = time.time() * 1000 + assertions, client_data = self.client.get_assertion(rp['id'], challenge, pin = PIN) + t2 = time.time() * 1000 + assert(len(assertions) == len(users) +1) + for x,y in zip(assertions, creds): + x.verify(client_data.hash,y.public_key) + + print('Assertion(s) valid (%d ms)' % (t2-t1)) + + + def test_responses(self,): + PIN = '1234' + RPID = 'examplo2.org' + for dev in (CtapHidDevice.list_devices()): + print('dev',dev) + client = Fido2Client(dev, RPID) + ctap = client.ctap2 + # ctap.reset() + try: + if PIN: client.pin_protocol.set_pin(PIN) + except:pass + + inf = ctap.get_info() + #print (inf) + print('versions: ',inf.versions) + print('aaguid: ',inf.aaguid) + print('rk: ',inf.options['rk']) + print('clientPin: ',inf.options['clientPin']) + print('max_message_size: ',inf.max_msg_size) + + #rp = {'id': 'SelectDevice', 'name': 'SelectDevice'} + rp = {'id': RPID, 'name': 'ExaRP'} + user = {'id': os.urandom(10), 'name': 'SelectDevice'} + user = {'id': b'21first one', 'name': 'single User'} + challenge = 'Y2hhbGxlbmdl' + + if 1: + attest, data = client.make_credential(rp, + user, challenge, exclude_list = [], pin = PIN, rk=True) + + cred = attest.auth_data.credential_data + creds = [cred] + + allow_list = [{'id':creds[0].credential_id, 'type': 'public-key'}] + allow_list = [] + assertions, client_data = client.get_assertion(rp['id'], challenge, pin = PIN) + assertions[0].verify(client_data.hash, creds[0].public_key) + + if 0: + print('registering 1 user with RK') + t1 = time.time() * 1000 + attest, data = client.make_credential(rp, user, challenge, pin = PIN, exclude_list = [], rk = True) + t2 = time.time() * 1000 + attest.verify(data.hash) + creds = [attest.auth_data.credential_data] + print('Register valid (%d ms)' % (t2-t1)) + + print('1 assertion') + t1 = time.time() * 1000 + assertions, client_data = client.get_assertion(rp['id'], challenge, pin = PIN) + t2 = time.time() * 1000 + assertions[0].verify(client_data.hash, creds[0].public_key) + print('Assertion valid (%d ms)' % (t2-t1)) + + + + + + #print('fmt:',attest.fmt) + #print('rp_id_hash',attest.auth_data.rp_id_hash) + #print('flags:', hex(attest.auth_data.flags)) + #print('count:', hex(attest.auth_data.counter)) + print('flags MC:',attest.auth_data) + print('flags GA:',assertions[0].auth_data) + #print('cred_id:',attest.auth_data.credential_data.credential_id) + #print('pubkey:',attest.auth_data.credential_data.public_key) + #print('aaguid:',attest.auth_data.credential_data.aaguid) + # print('cred data:',attest.auth_data.credential_data) + # print('auth_data:',attest.auth_data) + #print('auth_data:',attest.auth_data) + #print('alg:',attest.att_statement['alg']) + #print('sig:',attest.att_statement['sig']) + #print('x5c:',attest.att_statement['x5c']) + #print('data:',data) + + print('assertion:', assertions[0]) + print('clientData:', client_data) + + print() + #break + + + def test_find_brute_force(): i = 0 while 1: @@ -621,10 +776,12 @@ def test_find_brute_force(): if __name__ == '__main__': t = Tester() - t.find_device() + #t.find_device() # t.test_hid() # t.test_long_ping() - t.test_fido2() + #t.test_fido2() + #t.test_rk() + t.test_responses() # test_find_brute_force() #t.test_fido2_simple() #t.test_fido2_brute_force()