diff --git a/CMakeLists.txt b/CMakeLists.txt index 1cc83a2..9ef3dba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,9 +17,12 @@ cmake_minimum_required(VERSION 3.13) +set(USB_VID 0x2E8A) +set(USB_PID 0x10FE) + if(ESP_PLATFORM) set(DENABLE_POWER_ON_RESET 0) -set(EXTRA_COMPONENT_DIRS src pico-keys-sdk/src) +set(EXTRA_COMPONENT_DIRS pico-keys-sdk/config/esp32/components src/fido) include($ENV{IDF_PATH}/tools/cmake/project.cmake) else() @@ -112,7 +115,7 @@ set(SOURCES ${SOURCES} ) endif() -SET_VERSION(ver_major ver_minor "${CMAKE_CURRENT_LIST_DIR}/src/fido/version.h" 2) +SET_VERSION(ver_major ver_minor "${CMAKE_CURRENT_LIST_DIR}/src/fido/version.h" 3) if(ESP_PLATFORM) project(pico_fido) endif() @@ -161,7 +164,7 @@ if(ENABLE_EMULATION) -Wl,--gc-sections ) endif (APPLE) - target_link_libraries(pico_fido PRIVATE pthread m) + target_link_libraries(pico_fido PRIVATE pico_keys_sdk mbedtls pthread m) else() target_link_libraries(pico_fido PRIVATE pico_keys_sdk pico_stdlib pico_multicore hardware_flash hardware_sync hardware_adc pico_unique_id pico_aon_timer tinyusb_device tinyusb_board) pico_add_extra_outputs(${CMAKE_PROJECT_NAME}) diff --git a/README.md b/README.md index e12f571..fd92c56 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,9 @@ Microcontrollers RP2350 and ESP32-S3 are designed to support secure environments If you own a Raspberry Pico (RP2040 or RP2350), go to [Download page](https://www.picokeys.com/getting-started/), select your vendor and model and download the proper firmware; or go to [Release page](https://www.github.com/polhenarejos/pico-fido/releases/) and download the UF2 file for your board. -Note that UF2 files are shiped with a dummy VID/PID to avoid license issues (FEFF:FCFD). If you plan to use it with OpenSC or similar tools, you should modify Info.plist of CCID driver to add these VID/PID or use the [PicoKey App](https://www.picokeys.com/picokeyapp/ "PicoKey App"). +UF2 files are shiped with a VID/PID granted by RaspberryPi (2E8A:10FE). If you plan to use it with OpenSC or similar tools, you should modify Info.plist of CCID driver to add these VID/PID or use the [PicoKey App](https://www.picokeys.com/picokeyapp/ "PicoKey App"). -You can use whatever VID/PID (i.e., 234b:0000 from FISJ), but remember that you are not authorized to distribute the binary with a VID/PID that you do not own. +You can use whatever VID/PID for internal purposes, but remember that you are not authorized to distribute the binary with a VID/PID that you do not own. Note that the [PicoKey App](https://www.picokeys.com/picokeyapp/ "PicoKey App") is the most recommended. diff --git a/build_pico_fido.sh b/build_pico_fido.sh index 497d70d..d120690 100755 --- a/build_pico_fido.sh +++ b/build_pico_fido.sh @@ -1,7 +1,7 @@ #!/bin/bash VERSION_MAJOR="7" -VERSION_MINOR="2" +VERSION_MINOR="4" SUFFIX="${VERSION_MAJOR}.${VERSION_MINOR}" #if ! [[ -z "${GITHUB_SHA}" ]]; then # SUFFIX="${SUFFIX}.${GITHUB_SHA}" diff --git a/pico-keys-sdk b/pico-keys-sdk index 263e554..12b4940 160000 --- a/pico-keys-sdk +++ b/pico-keys-sdk @@ -1 +1 @@ -Subproject commit 263e554cc6c59a5f168f8589c4bdabe6e1e64c25 +Subproject commit 12b4940662c77993eebc9c0d4e704f007fc0a8d6 diff --git a/src/fido/CMakeLists.txt b/src/fido/CMakeLists.txt index e6dfbac..e9c6774 100644 --- a/src/fido/CMakeLists.txt +++ b/src/fido/CMakeLists.txt @@ -1,6 +1,6 @@ idf_component_register( SRCS ${SOURCES} - INCLUDE_DIRS . ../../pico-keys-sdk/src ../../pico-keys-sdk/src/fs ../../pico-keys-sdk/src/rng ../../pico-keys-sdk/src/usb ../../pico-keys-sdk/tinycbor/src - REQUIRES esp_tinyusb mbedtls efuse + INCLUDE_DIRS . + REQUIRES esp_tinyusb mbedtls efuse pico-keys-sdk ) idf_component_set_property(${COMPONENT_NAME} WHOLE_ARCHIVE ON) diff --git a/src/fido/cbor_client_pin.c b/src/fido/cbor_client_pin.c index 7a4097c..b2c6732 100644 --- a/src/fido/cbor_client_pin.c +++ b/src/fido/cbor_client_pin.c @@ -419,6 +419,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) { hsh[1] = pin_len; hsh[2] = 1; // New format indicator mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash); + mbedtls_platform_zeroize(paddedNewPin, sizeof(paddedNewPin)); pin_derive_verifier(dhash, 16, hsh + 3); file_put_data(ef_pin, hsh, sizeof(hsh)); low_flash_available(); @@ -430,6 +431,8 @@ int cbor_client_pin(const uint8_t *data, size_t len) { } mbedtls_platform_zeroize(hsh, sizeof(hsh)); mbedtls_platform_zeroize(dhash, sizeof(dhash)); + needs_power_cycle = false; + goto err; //No return } else if (subcommand == 0x4) { //changePIN @@ -458,6 +461,9 @@ int cbor_client_pin(const uint8_t *data, size_t len) { if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.Y, kay.data, kay.len) != 0) { CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); } + if (needs_power_cycle) { + CBOR_ERROR(CTAP2_ERR_PIN_AUTH_BLOCKED); + } uint8_t sharedSecret[64]; int ret = ecdh((uint8_t)pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret); if (ret != 0) { @@ -587,6 +593,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) { low_flash_available(); resetPinUvAuthToken(); resetPersistentPinUvAuthToken(); + needs_power_cycle = false; goto err; // No return } else if (subcommand == 0x9 || subcommand == 0x5) { //getPinUvAuthTokenUsingPinWithPermissions @@ -623,6 +630,9 @@ int cbor_client_pin(const uint8_t *data, size_t len) { if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.Y, kay.data, kay.len) != 0) { CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); } + if (needs_power_cycle) { + CBOR_ERROR(CTAP2_ERR_PIN_AUTH_BLOCKED); + } uint8_t sharedSecret[64]; int ret = ecdh((uint8_t)pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret); if (ret != 0) { @@ -720,6 +730,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) { CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02)); CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pinUvAuthToken_enc, 32 + poff)); + needs_power_cycle = false; } else { CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION); diff --git a/src/fido/cbor_config.c b/src/fido/cbor_config.c index f00cf83..20a2a84 100644 --- a/src/fido/cbor_config.c +++ b/src/fido/cbor_config.c @@ -144,12 +144,6 @@ int cbor_config(const uint8_t *data, size_t len) { } if (subcommand == 0xFF) { -#ifndef ENABLE_EMULATION - const bool is_phy = (vendorCommandId == CTAP_CONFIG_PHY_VIDPID || - vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO || - vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS || - vendorCommandId == CTAP_CONFIG_PHY_OPTS); -#endif if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE){ if (!file_has_data(ef_keydev_enc)) { CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED); @@ -192,25 +186,6 @@ int cbor_config(const uint8_t *data, size_t len) { file_put_data(ef_keydev, NULL, 0); // Set ef to 0 bytes low_flash_available(); } - -#ifndef ENABLE_EMULATION - else if (vendorCommandId == CTAP_CONFIG_PHY_VIDPID) { - phy_data.vid = (vendorParamInt >> 16) & 0xFFFF; - phy_data.pid = vendorParamInt & 0xFFFF; - phy_data.vidpid_present = true; - } - else if (vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO) { - phy_data.led_gpio = (uint8_t)vendorParamInt; - phy_data.led_gpio_present = true; - } - else if (vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS) { - phy_data.led_brightness = (uint8_t)vendorParamInt; - phy_data.led_brightness_present = true; - } - else if (vendorCommandId == CTAP_CONFIG_PHY_OPTS) { - phy_data.opts = (uint16_t)vendorParamInt; - } -#endif else if (vendorCommandId == CTAP_CONFIG_EA_UPLOAD) { if (vendorParamByteString.present == false) { CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER); @@ -239,11 +214,6 @@ int cbor_config(const uint8_t *data, size_t len) { else { CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND); } -#ifndef ENABLE_EMULATION - if (is_phy && phy_save() != PICOKEY_OK) { - CBOR_ERROR(CTAP2_ERR_PROCESSING); - } -#endif goto err; } else if (subcommand == 0x03) { diff --git a/src/fido/cbor_get_assertion.c b/src/fido/cbor_get_assertion.c index fac8e01..3ba9fb5 100644 --- a/src/fido/cbor_get_assertion.c +++ b/src/fido/cbor_get_assertion.c @@ -428,7 +428,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { if (!silent) { for (int i = 0; i < numberOfCredentials; i++) { for (int j = i + 1; j < numberOfCredentials; j++) { - if (creds[j].creation > creds[i].creation) { + if (creds[j].board_creation > creds[i].board_creation) { Credential tmp = creds[j]; creds[j] = creds[i]; creds[i] = tmp; diff --git a/src/fido/cbor_vendor.c b/src/fido/cbor_vendor.c index e102aab..658c52e 100644 --- a/src/fido/cbor_vendor.c +++ b/src/fido/cbor_vendor.c @@ -206,7 +206,12 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) { uint8_t buffer[1024]; mbedtls_ecdsa_context ekey; mbedtls_ecdsa_init(&ekey); - int ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, file_get_data(ef_keydev), file_get_size(ef_keydev)); + uint8_t keydev[32] = {0}; + if (load_keydev(keydev) != 0) { + CBOR_ERROR(CTAP1_ERR_OTHER); + } + int ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, keydev, 32); + mbedtls_platform_zeroize(keydev, sizeof(keydev)); if (ret != 0) { mbedtls_ecdsa_free(&ekey); CBOR_ERROR(CTAP2_ERR_PROCESSING); @@ -238,41 +243,6 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) { CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, buffer + sizeof(buffer) - ret, ret)); } } -#ifndef ENABLE_EMULATION - else if (cmd == CTAP_VENDOR_PHY_OPTS) { - if (vendorCmd == 0x01) { - uint16_t opts = 0; - if (file_has_data(ef_phy)) { - uint8_t *pdata = file_get_data(ef_phy); - opts = get_uint16_t_be(pdata + PHY_OPTS); - } - CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1)); - CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); - CBOR_CHECK(cbor_encode_uint(&mapEncoder, opts)); - } - else { - CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION); - } - } - #endif - else if (cmd == CTAP_VENDOR_MEMORY) { - if (vendorCmd == 0x01) { - CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 5)); - CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); - CBOR_CHECK(cbor_encode_uint(&mapEncoder, flash_free_space())); - CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02)); - CBOR_CHECK(cbor_encode_uint(&mapEncoder, flash_used_space())); - CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03)); - CBOR_CHECK(cbor_encode_uint(&mapEncoder, flash_total_space())); - CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04)); - CBOR_CHECK(cbor_encode_uint(&mapEncoder, flash_num_files())); - CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05)); - CBOR_CHECK(cbor_encode_uint(&mapEncoder, flash_size())); - } - else { - CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION); - } - } else { CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION); } diff --git a/src/fido/credential.c b/src/fido/credential.c index 645417d..46b079a 100644 --- a/src/fido/credential.c +++ b/src/fido/credential.c @@ -29,6 +29,7 @@ #include "files.h" #include "otp.h" +extern bool has_set_rtc(); int credential_derive_chacha_key(uint8_t *outk, const uint8_t *); static int credential_silent_tag(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash, uint8_t *outk) { @@ -148,6 +149,10 @@ int credential_create(CborCharString *rpId, } CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2)); } + if (has_set_rtc()) { + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0C)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder, (uint64_t) get_rtc_time())); + } CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); size_t rs = cbor_encoder_get_buffer_size(&encoder, cred_id); *cred_id_len = CRED_PROTO_LEN + CRED_IV_LEN + (uint16_t)rs + CRED_TAG_LEN + CRED_SILENT_TAG_LEN; @@ -220,7 +225,7 @@ int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *r CBOR_FIELD_GET_TEXT(cred->userDisplayName, 1); } else if (val_u == 0x06) { - CBOR_FIELD_GET_UINT(cred->creation, 1); + CBOR_FIELD_GET_UINT(cred->board_creation, 1); } else if (val_u == 0x07) { cred->extensions.present = true; @@ -255,6 +260,9 @@ int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *r } CBOR_PARSE_MAP_END(_f1, 2); } + else if (val_u == 0x0C) { + CBOR_FIELD_GET_UINT(cred->rtc_creation, 1); + } else { CBOR_ADVANCE(1); } diff --git a/src/fido/credential.h b/src/fido/credential.h index a77b72f..76f4e67 100644 --- a/src/fido/credential.h +++ b/src/fido/credential.h @@ -43,7 +43,7 @@ typedef struct Credential { CborByteString userId; CborCharString userName; CborCharString userDisplayName; - uint64_t creation; + uint64_t board_creation; CredExtensions extensions; const bool *use_sign_count; int64_t alg; @@ -51,6 +51,7 @@ typedef struct Credential { CborByteString id; CredOptions opts; bool present; + uint64_t rtc_creation; } Credential; #define CRED_PROT_UV_OPTIONAL 0x01 diff --git a/src/fido/fido.c b/src/fido/fido.c index d6cb9d9..4bc4324 100644 --- a/src/fido/fido.c +++ b/src/fido/fido.c @@ -53,6 +53,11 @@ const uint8_t fido_aid[] = { 0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01 }; +const uint8_t fido_aid_backup[] = { + 8, + 0xB0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01 +}; + const uint8_t atr_fido[] = { 23, 0x3b, 0xfd, 0x13, 0x00, 0x00, 0x81, 0x31, 0xfe, 0x15, 0x80, 0x73, 0xc0, 0x21, 0xc0, 0x57, 0x59, @@ -86,6 +91,7 @@ INITIALIZER ( fido_ctor ) { get_version_major = fido_get_version_major; get_version_minor = fido_get_version_minor; register_app(fido_select, fido_aid); + register_app(fido_select, fido_aid_backup); } int fido_unload() { @@ -477,11 +483,13 @@ void scan_all() { } extern void init_otp(); +extern bool needs_power_cycle; void init_fido() { scan_all(); #ifdef ENABLE_OTP_APP init_otp(); #endif + needs_power_cycle = false; } bool wait_button_pressed() { @@ -530,18 +538,32 @@ extern int cmd_register(); extern int cmd_authenticate(); extern int cmd_version(); extern int cbor_parse(int, uint8_t *, size_t); +extern int cbor_vendor(const uint8_t *data, size_t len); extern void driver_init_hid(); #define CTAP_CBOR 0x10 +int cmd_vendor() { + uint8_t *old_buf = res_APDU; + driver_init_hid(); + int ret = cbor_vendor(apdu.data, apdu.nc); + res_APDU = old_buf; + if (ret != 0) { + return set_res_sw(0x64, ret); + } + res_APDU_size += 1; + memcpy(res_APDU, ctap_resp->init.data, res_APDU_size); + return SW_OK(); +} + int cmd_cbor() { uint8_t *old_buf = res_APDU; driver_init_hid(); int ret = cbor_parse(0x90, apdu.data, apdu.nc); - if (ret != 0) { - return SW_EXEC_ERROR(); - } res_APDU = old_buf; + if (ret != 0) { + return set_res_sw(0x64, ret); + } res_APDU_size += 1; memcpy(res_APDU, ctap_resp->init.data, res_APDU_size); return SW_OK(); @@ -552,6 +574,7 @@ static const cmd_t cmds[] = { { CTAP_AUTHENTICATE, cmd_authenticate }, { CTAP_VERSION, cmd_version }, { CTAP_CBOR, cmd_cbor }, + { 0x41, cmd_vendor }, { 0x00, 0x0 } }; diff --git a/src/fido/files.h b/src/fido/files.h index 1fe844b..715a306 100644 --- a/src/fido/files.h +++ b/src/fido/files.h @@ -40,6 +40,8 @@ #define EF_OATH_CODE 0xBAFF #define EF_OTP_SLOT1 0xBB00 #define EF_OTP_SLOT2 0xBB01 +#define EF_OTP_SLOT3 0xBB02 +#define EF_OTP_SLOT4 0xBB03 #define EF_OTP_PIN 0x10A0 // Nitrokey OTP PIN extern file_t *ef_keydev; diff --git a/src/fido/oath.c b/src/fido/oath.c index 0c293c7..f8de9a7 100644 --- a/src/fido/oath.c +++ b/src/fido/oath.c @@ -94,22 +94,22 @@ int oath_select(app_t *a, uint8_t force) { res_APDU[res_APDU_size++] = sizeof(challenge); memcpy(res_APDU + res_APDU_size, challenge, sizeof(challenge)); res_APDU_size += sizeof(challenge); - } - file_t *ef_otp_pin = search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF); - if (file_has_data(ef_otp_pin)) { - const uint8_t *pin_data = file_get_data(ef_otp_pin); - res_APDU[res_APDU_size++] = TAG_PIN_COUNTER; + res_APDU[res_APDU_size++] = TAG_ALGO; res_APDU[res_APDU_size++] = 1; - res_APDU[res_APDU_size++] = *pin_data; + res_APDU[res_APDU_size++] = ALG_HMAC_SHA1; } - res_APDU[res_APDU_size++] = TAG_ALGO; - res_APDU[res_APDU_size++] = 1; - res_APDU[res_APDU_size++] = ALG_HMAC_SHA1; if (is_nk) { res_APDU[res_APDU_size++] = TAG_SERIAL_NUMBER; res_APDU[res_APDU_size++] = 8; memcpy(res_APDU + res_APDU_size, pico_serial_str, 8); res_APDU_size += 8; + file_t *ef_otp_pin = search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF); + if (file_has_data(ef_otp_pin)) { + const uint8_t *pin_data = file_get_data(ef_otp_pin); + res_APDU[res_APDU_size++] = TAG_PIN_COUNTER; + res_APDU[res_APDU_size++] = 1; + res_APDU[res_APDU_size++] = *pin_data; + } } apdu.ne = res_APDU_size; return PICOKEY_OK; diff --git a/src/fido/otp.c b/src/fido/otp.c index 29d7da8..3cc5811 100644 --- a/src/fido/otp.c +++ b/src/fido/otp.c @@ -49,6 +49,11 @@ void append_keyboard_buffer(const uint8_t *buf, size_t len) {} #define CONFIG_LED_INV 0x10 #define CONFIG_STATUS_MASK 0x1f +#define CONFIG3_VALID 0x01 +#define CONFIG4_VALID 0x02 +#define CONFIG3_TOUCH 0x04 +#define CONFIG4_TOUCH 0x08 + /* EXT Flags */ #define SERIAL_BTN_VISIBLE 0x01 // Serial number visible at startup (button press) #define SERIAL_USB_VISIBLE 0x02 // Serial number visible in USB iSerial field @@ -161,7 +166,7 @@ extern void scan_all(); void init_otp() { if (scanned == false) { scan_all(); - for (uint8_t i = 0; i < 2; i++) { + for (uint8_t i = 0; i < 4; i++) { file_t *ef = search_dynamic_file(EF_OTP_SLOT1 + i); uint8_t *data = file_get_data(ef); otp_config_t *otp_config = (otp_config_t *) data; @@ -207,7 +212,8 @@ int otp_button_pressed(uint8_t slot) { if (!cap_supported(CAP_OTP)) { return 3; } - file_t *ef = search_dynamic_file(slot == 1 ? EF_OTP_SLOT1 : EF_OTP_SLOT2); + uint16_t slot_ef = EF_OTP_SLOT1 + slot - 1; + file_t *ef = search_dynamic_file(slot_ef); const uint8_t *data = file_get_data(ef); otp_config_t *otp_config = (otp_config_t *) data; if (file_has_data(ef) == false) { @@ -333,6 +339,39 @@ int otp_unload() { } uint8_t status_byte = 0x0; +uint16_t otp_status_ext() { + for (int i = 0; i < 4; i++) { + file_t *ef = search_dynamic_file(EF_OTP_SLOT1 + i); + if (file_has_data(ef)) { + res_APDU[res_APDU_size++] = 0xB0 + i; + res_APDU[res_APDU_size++] = 0; // Filled later + uint8_t *p = res_APDU + res_APDU_size; + otp_config_t *otp_config = (otp_config_t *)file_get_data(ef); + *p++ = 0xA0; + *p++ = 2; + *p++ = otp_config->tkt_flags; + *p++ = otp_config->cfg_flags; + if (otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP) { + + } + else if (otp_config->tkt_flags & OATH_HOTP) { + } + else if (otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET) { + } + else { + *p++ = 0xC0; + *p++ = 6; + memcpy(p, otp_config->fixed_data, 6); + p += 6; + } + uint8_t len = p - (res_APDU + res_APDU_size); + res_APDU[res_APDU_size - 1] = len; + res_APDU_size += len; + } + } + return SW_OK(); +} + uint16_t otp_status(bool is_otp) { if (scanned == false) { scan_all(); @@ -384,12 +423,13 @@ bool check_crc(const otp_config_t *data) { bool _is_otp = false; int cmd_otp() { uint8_t p1 = P1(apdu), p2 = P2(apdu); - if (p2 != 0x00) { - return SW_INCORRECT_P1P2(); - } if (p1 == 0x01 || p1 == 0x03) { // Configure slot otp_config_t *odata = (otp_config_t *) apdu.data; - file_t *ef = file_new(p1 == 0x01 ? EF_OTP_SLOT1 : EF_OTP_SLOT2); + if (p1 == 0x03 && p2 != 0x0) { + return SW_INCORRECT_P1P2(); + } + uint16_t slot = (p1 == 0x01 ? EF_OTP_SLOT1 : EF_OTP_SLOT2) + p2; + file_t *ef = file_new(slot); if (file_has_data(ef)) { otp_config_t *otpc = (otp_config_t *) file_get_data(ef); if (memcmp(otpc->acc_code, apdu.data + otp_config_size, ACC_CODE_SIZE) != 0) { @@ -415,10 +455,14 @@ int cmd_otp() { } else if (p1 == 0x04 || p1 == 0x05) { // Update slot otp_config_t *odata = (otp_config_t *) apdu.data; + if (p1 == 0x05 && p2 != 0x0) { + return SW_INCORRECT_P1P2(); + } + uint16_t slot = (p1 == 0x04 ? EF_OTP_SLOT1 : EF_OTP_SLOT2) + p2; if (odata->rfu[0] != 0 || odata->rfu[1] != 0 || check_crc(odata) == false) { return SW_WRONG_DATA(); } - file_t *ef = search_dynamic_file(p1 == 0x04 ? EF_OTP_SLOT1 : EF_OTP_SLOT2); + file_t *ef = search_dynamic_file(slot); if (file_has_data(ef)) { otp_config_t *otpc = (otp_config_t *) file_get_data(ef); if (memcmp(otpc->acc_code, apdu.data + otp_config_size, ACC_CODE_SIZE) != 0) { @@ -446,8 +490,16 @@ int cmd_otp() { else if (p1 == 0x06) { // Swap slots uint8_t tmp[otp_config_size + 8]; bool ef1_data = false; - file_t *ef1 = file_new(EF_OTP_SLOT1); - file_t *ef2 = file_new(EF_OTP_SLOT2); + uint16_t slot1 = EF_OTP_SLOT1, slot2 = EF_OTP_SLOT2; + if (apdu.ne > 0) { + if (apdu.ne != 2) { + return SW_WRONG_LENGTH(); + } + slot1 += apdu.data[0]; + slot2 += apdu.data[1]; + } + file_t *ef1 = file_new(slot1); + file_t *ef2 = file_new(slot2); if (file_has_data(ef1)) { memcpy(tmp, file_get_data(ef1), file_get_size(ef1)); ef1_data = true; @@ -458,7 +510,7 @@ int cmd_otp() { else { delete_file(ef1); // When a dynamic file is deleted, existing referenes are invalidated - ef2 = file_new(EF_OTP_SLOT2); + ef2 = file_new(slot2); } if (ef1_data) { file_put_data(ef2, tmp, sizeof(tmp)); @@ -478,8 +530,15 @@ int cmd_otp() { else if (p1 == 0x13) { // Get config man_get_config(); } + else if (p1 == 0x14) { + otp_status_ext(); + } else if (p1 == 0x30 || p1 == 0x38 || p1 == 0x20 || p1 == 0x28) { // Calculate OTP - file_t *ef = search_dynamic_file(p1 == 0x30 || p1 == 0x20 ? EF_OTP_SLOT1 : EF_OTP_SLOT2); + if ((p1 == 0x38 || p1 == 0x28) && p2 != 0x0) { + return SW_INCORRECT_P1P2(); + } + uint16_t slot = (p1 == 0x30 || p1 == 0x20 ? EF_OTP_SLOT1 : EF_OTP_SLOT2) + p2; + file_t *ef = search_dynamic_file(slot); if (file_has_data(ef)) { otp_config_t *otp_config = (otp_config_t *) file_get_data(ef); if (!(otp_config->tkt_flags & CHAL_RESP)) { diff --git a/src/fido/version.h b/src/fido/version.h index f8625ce..dbf1962 100644 --- a/src/fido/version.h +++ b/src/fido/version.h @@ -18,7 +18,7 @@ #ifndef __VERSION_H_ #define __VERSION_H_ -#define PICO_FIDO_VERSION 0x0702 +#define PICO_FIDO_VERSION 0x0704 #define PICO_FIDO_VERSION_MAJOR ((PICO_FIDO_VERSION >> 8) & 0xff) #define PICO_FIDO_VERSION_MINOR (PICO_FIDO_VERSION & 0xff) diff --git a/tests/pico-fido/test_010_pin.py b/tests/pico-fido/test_010_pin.py index 0e947f4..06443ae 100644 --- a/tests/pico-fido/test_010_pin.py +++ b/tests/pico-fido/test_010_pin.py @@ -51,7 +51,7 @@ def test_lockout(device, resetdevice, client_pin): res = client_pin.get_pin_retries() assert res[0] == attempts - if err == CtapError.ERR.PIN_AUTH_BLOCKED: + if e.value.code == CtapError.ERR.PIN_AUTH_BLOCKED: device.reboot() client_pin = ClientPin(resetdevice.client()._backend.ctap2) diff --git a/tools/secure_key/macos.py b/tools/secure_key/macos.py deleted file mode 100644 index 1ccc1a4..0000000 --- a/tools/secure_key/macos.py +++ /dev/null @@ -1,61 +0,0 @@ -import sys -import keyring - -DOMAIN = "PicoKeys.com" -USERNAME = "Pico-Fido" - -try: - import keyring - from keyrings.osx_keychain_keys.backend import OSXKeychainKeysBackend, OSXKeychainKeyType, OSXKeyChainKeyClassType -except: - print('ERROR: keyring module not found! Install keyring package.\nTry with `pip install keyrings.osx-keychain-keys`') - sys.exit(-1) - -try: - from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption -except: - print('ERROR: cryptography module not found! Install cryptography package.\nTry with `pip install cryptography`') - sys.exit(-1) - - -def get_backend(use_secure_enclave=False): - backend = OSXKeychainKeysBackend( - key_type=OSXKeychainKeyType.EC, # Key type, e.g. RSA, RC, DSA, ... - key_class_type=OSXKeyChainKeyClassType.Private, # Private key, Public key, Symmetric-key - key_size_in_bits=256, - is_permanent=True, # If set, saves the key in keychain; else, returns a transient key - use_secure_enclave=use_secure_enclave, # Saves the key in the T2 (TPM) chip, requires a code-signed interpreter - access_group=None, # Limits key management and retrieval to set group, requires a code-signed interpreter - is_extractable=True # If set, private key is extractable; else, it can't be retrieved, but only operated against - ) - return backend - -def generate_secure_key(use_secure_enclave=False): - backend = get_backend(use_secure_enclave) - backend.set_password(DOMAIN, USERNAME, password=None) - return backend.get_password(DOMAIN, USERNAME) - -def get_d(key): - return key.private_numbers().private_value.to_bytes(32, 'big') - -def set_secure_key(pk): - backend = get_backend(False) - try: - backend.delete_password(DOMAIN, USERNAME) - except: - pass - backend.set_password(DOMAIN, USERNAME, pk.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption())) - -def get_secure_key(): - key = None - try: - backend = get_backend(False) - key = backend.get_password(DOMAIN, USERNAME)[0] - if (key is None): - raise TypeError - except (keyring.errors.KeyringError, TypeError): - try: - key = generate_secure_key(False)[0] # It should be True, but secure enclave causes python segfault - except keyring.errors.PasswordSetError: - key = generate_secure_key(False)[0] - return get_d(key) diff --git a/tools/secure_key/windows.py b/tools/secure_key/windows.py deleted file mode 100644 index 844190a..0000000 --- a/tools/secure_key/windows.py +++ /dev/null @@ -1,44 +0,0 @@ -import sys - -DOMAIN = "PicoKeys.com" -USERNAME = "Pico-Fido" - -try: - import keyring -except: - print('ERROR: keyring module not found! Install keyring package.\nTry with `pip install keyring`') - sys.exit(-1) - -try: - from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption, load_pem_private_key - from cryptography.hazmat.primitives.asymmetric import ec -except: - print('ERROR: cryptography module not found! Install cryptography package.\nTry with `pip install cryptography`') - sys.exit(-1) - - - -def generate_secure_key(): - pkey = ec.generate_private_key(ec.SECP256R1()) - set_secure_key(pkey) - return keyring.get_password(DOMAIN, USERNAME) - -def get_d(key): - return load_pem_private_key(key, password=None).private_numbers().private_value.to_bytes(32, 'big') - -def set_secure_key(pk): - try: - keyring.delete_password(DOMAIN, USERNAME) - except: - pass - keyring.set_password(DOMAIN, USERNAME, pk.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption()).decode()) - -def get_secure_key(): - key = None - try: - key = keyring.get_password(DOMAIN, USERNAME) - if (key is None): - raise TypeError - except (keyring.errors.KeyringError, TypeError): - key = generate_secure_key() - return get_d(key.encode()) diff --git a/tools/words.py b/tools/words.py deleted file mode 100644 index 4e17003..0000000 --- a/tools/words.py +++ /dev/null @@ -1 +0,0 @@ -words = ['shape', 'reject', 'trains', 'skirt', 'magnificent', 'sloppy', 'harsh', 'valuable', 'yak', 'knit', 'ghost', 'offer', 'necessary', 'hilarious', 'wealth', 'far-flung', 'scary', 'yoke', 'cruel', 'unbecoming', 'sin', 'draconian', 'undress', 'various', 'annoying', 'great', 'flashy', 'materialistic', 'spiritual', 'writer', 'prickly', 'therapeutic', 'curvy', 'terrific', 'paste', 'reminiscent', 'range', 'female', 'trees', 'cat', 'cow', 'furtive', 'run', 'swing', 'early', 'marked', 'terrify', 'wind', 'stretch', 'bells', 'eminent', 'deserted', 'nondescript', 'basketball', 'wine', 'boast', 'food', 'fowl', 'cheerful', 'kettle', 'jazzy', 'greasy', 'sick', 'sort', 'excuse', 'impolite', 'attraction', 'ultra', 'axiomatic', 'promise', 'blood', 'camp', 'macho', 'material', 'earthquake', 'window', 'taste', 'buzz', 'identify', 'education', 'hapless', 'perfect', 'stare', 'bell', 'snail', 'powerful', 'unarmed', 'company', 'mist', 'seat', 'empty', 'plantation', 'sheet', 'pinch', 'mate', 'null', 'obtain', 'jumbled', 'awful', 'loving', 'moaning', 'chief', 'lethal', 'crabby', 'preserve', 'scratch', 'tax', 'ink', 'quack', 'produce', 'madly', 'open', 'cagey', 'mountain', 'irritating', 'attack', 'oven', 'faithful', 'sharp', 'uncle', 'wicked', 'rigid', 'theory', 'try', 'wobble', 'cloth', 'wood', 'strip', 'surround', 'uneven', 'reach', 'bright', 'agonizing', 'crate', 'rock', 'moor', 'bike', 'tired', 'tenuous', 'beg', 'boiling', 'creator', 'chance', 'scared', 'flowers', 'eye', 'limping', 'yielding', 'understood', 'longing', 'love', 'quirky', 'homely', 'crown', 'waste', 'rustic', 'thoughtless', 'flimsy', 'glossy', 'part', 'good', 'receive', 'division', 'snails', 'attractive', 'cuddly', 'release', 'gamy', 'name', 'decorate', 'move', 'plot', 'star', 'writing', 'house', 'thaw', 'robust', 'testy', 'elated', 'calm', 'sneaky', 'tomatoes', 'disgusting', 'fence', 'capable', 'rural', 'melodic', 'hysterical', 'entertaining', 'man', 'ill-informed', 'misty', 'scatter', 'jump', 'rightful', 'educated', 'adjoining', 'abashed', 'disturbed', 'fade', 'stamp', 'stream', 'boorish', 'two', 'worry', 'thank', 'trot', 'delicate', 'argument', 'creepy', 'dependent', 'rough', 'symptomatic', 'authority', 'actually', 'join', 'ignorant', 'radiate', 'aboard', 'bead', 'energetic', 'abandoned', 'shy', 'raise', 'blind', 'talented', 'stroke', 'devilish', 'death', 'type', 'taboo', 'depressed', 'level', 'equable', 'activity', 'accidental', 'ski', 'purring', 'juicy', 'maid', 'decide', 'overconfident', 'pipe', 'afternoon', 'file', 'peaceful', 'delicious', 'continue', 'imperfect', 'glow', 'haircut', 'frequent', 'arrogant', 'toothbrush', 'refuse', 'meek', 'spectacular', 'homeless', 'half', 'grateful', 'old', 'psychedelic', 'obnoxious', 'bridge', 'pies', 'invention', 'hose', 'flood', 'badge', 'unhealthy', 'deadpan', 'owe', 'undesirable', 'dock', 'insect', 'rose', 'boil', 'warlike', 'abortive', 'wriggle', 'excellent', 'fear', 'nation', 'unknown', 'press', 'wasteful', 'boundary', 'abrasive', 'glamorous', 'bag', 'army', 'punishment', 'lettuce', 'system', 'queen', 'branch', 'fretful', 'flawless', 'flap', 'observation', 'juice', 'punch', 'pretend', 'pocket', 'simplistic', 'serve', 'oval', 'weary', 'mourn', 'river', 'selective', 'heat', 'include', 'filthy', 'amuck', 'tender', 'mix', 'towering', 'shut', 'sponge', 'transport', 'communicate', 'squalid', 'unit', 'smoke', 'carriage', 'clumsy', 'abiding', 'fish', 'grease', 'frogs', 'classy', 'slim', 'needless', 'nasty', 'equal', 'relax', 'noise', 'wiry', 'cheer', 'gold', 'insidious', 'bounce', 'past', 'same', 'design', 'power', 'probable', 'sheep', 'trucks', 'agree', 'exclusive', 'smoggy', 'fumbling', 'exercise', 'astonishing', 'stupendous', 'bad', 'charming', 'birthday', 'spiteful', 'toys', 'plant', 'abnormal', 'racial', 'squeamish', 'decorous', 'woman', 'protest', 'literate', 'inquisitive', 'skillful', 'sidewalk', 'internal', 'judicious', 'hall', 'paltry', 'safe', 'deeply', 'melt', 'direction', 'relieved', 'red', 'future', 'wander', 'loaf', 'fit', 'telling', 'aquatic', 'notice', 'apathetic', 'flame', 'look', 'show', 'sable', 'hallowed', 'miss', 'premium', 'pin', 'handsome', 'volleyball', 'adhesive', 'mind', 'helpless', 'lighten', 'industry', 'mother', 'feigned', 'permit', 'plain', 'flight', 'burn', 'unruly', 'damp', 'digestion', 'air', 'x-ray', 'hook', 'squealing', 'ablaze', 'cowardly', 'dinner', 'closed', 'things', 'toothpaste', 'gaudy', 'pretty', 'finicky', 'retire', 'amount', 'sticky', 'bless', 'visitor', 'screw', 'subsequent', 'absorbing', 'left', 'parched', 'guarded', 'ants', 'squeak', 'glue', 'umbrella', 'government', 'peep', 'unkempt', 'hard', 'foolish', 'daffy', 'letters', 'ask', 'language', 'harm', 'greedy', 'wanting', 'size', 'way', 'cute', 'satisfying', 'oranges', 'second', 'pigs', 'intend', 'cast', 'somber', 'assorted', 'decision', 'ugliest', 'chop', 'excite', 'lunch', 'possessive', 'volatile', 'pricey', 'cooing', 'tearful', 'tan', 'whirl', 'worm', 'curve', 'squash', 'ceaseless', 'suck', 'obsolete', 'rabbit', 'door', 'flag', 'program', 'gullible', 'disastrous', 'lopsided', 'substance', 'kind', 'feeling', 'shiny', 'hole', 'club', 'troubled', 'heady', 'rambunctious', 'questionable', 'massive', 'balance', 'envious', 'throne', 'condemned', 'zippy', 'interesting', 'zephyr', 'thundering', 'paddle', 'nimble', 'stranger', 'fertile', 'prevent', 'hydrant', 'messy', 'floor', 'boy', 'unused', 'meal', 'occur', 'rabbits', 'spicy', 'highfalutin', 'parcel', 'action', 'dispensable', 'tree', 'better', 'bushes', 'wheel', 'fact', 'guitar', 'courageous', 'threatening', 'typical', 'paper', 'flaky', 'spoon', 'sparkle', 'sturdy', 'march', 'detail', 'coal', 'ritzy', 'team', 'duck', 'drag', 'defective', 'chivalrous', 'zesty', 'disapprove', 'cure', 'succeed', 'glib', 'grubby', 'young', 'dashing', 'clover', 'signal', 'thunder', 'illegal', 'tumble', 'wrestle', 'soft', 'waggish', 'merciful', 'stain', 'receptive', 'choke', 'jail', 'nifty', 'coast', 'berserk', 'nod', 'chilly', 'magic', 'cool', 'employ', 'alert', 'low', 'handy', 'card', 'fail', 'plate', 'lake', 'vase', 'venomous', 'violet', 'form', 'daughter', 'guttural', 'appliance', 'wren', 'doll', 'brainy', 'extend', 'trick', 'flow', 'miscreant', 'yawn', 'son', 'street', 'land', 'wing', 'grandmother', 'certain', 'rhythm', 'pray', 'cover', 'swanky', 'wrathful', 'saw', 'strong', 'amazing', 'blue', 'slave', 'hurt', 'string', 'person', 'slow', 'warm', 'babies', 'tent', 'truthful', 'white', 'bustling', 'mouth', 'property', 'art', 'oceanic', 'zip', 'parallel', 'nappy', 'dime', 'porter', 'puzzling', 'appear', 'workable', 'hesitant', 'stuff', 'defeated', 'chunky', 'bath', 'mere', 'argue', 'mature', 'waiting', 'harmonious', 'command', 'weak', 'precede', 'disagree', 'colorful', 'unequaled', 'untidy', 'tug', 'embarrassed', 'work', 'broken', 'free', 'suspect', 'event', 'limit', 'cook', 'used', 'curved', 'current', 'jolly', 'ring', 'grip', 'bizarre', 'powder', 'stew', 'careful', 'cellar', 'rely', 'ill-fated', 'sand', 'respect', 'hard-to-find', 'mixed', 'scent', 'bite', 'confuse', 'burst', 'reflect', 'breakable', 'useless', 'combative', 'trust', 'godly', 'expect', 'spiky', 'statement', 'alarm', 'ahead', 'black-and-white', 'cumbersome', 'bore', 'interfere', 'winter', 'picture', 'meaty', 'ragged', 'grouchy', 'impress', 'potato', 'edge', 'cent', 'prefer', 'roasted', 'dance', 'spot', 'itchy', 'brash', 'motion', 'bawdy', 'tour', 'old-fashioned', 'muddled', 'downtown', 'doubt', 'fortunate', 'futuristic', 'exchange', 'clean', 'flower', 'whole', 'whine', 'versed', 'groovy', 'muddle', 'divide', 'huge', 'toad', 'fast', 'lonely', 'historical', 'annoy', 'mitten', 'cattle', 'unusual', 'houses', 'magical', 'outrageous', 'pass', 'belligerent', 'knot', 'believe', 'mess up', 'dam', 'market', 'protect', 'lame', 'tame', 'sedate', 'fanatical', 'uttermost', 'list', 'dysfunctional', 'drawer', 'dirty', 'structure', 'malicious', 'hat', 'melted', 'race', 'tidy', 'pickle', 'flagrant', 'tendency', 'torpid', 'stimulating', 'cup', 'doctor', 'color', 'skip', 'bikes', 'temper', 'offbeat', 'value', 'absent', 'adorable', 'arrange', 'fine', 'space', 'changeable', 'eggs', 'remind', 'general', 'clam', 'dream', 'loss', 'repair', 'alcoholic', 'macabre', 'imminent', 'canvas', 'subdued', 'rake', 'roof', 'solid', 'military', 'physical', 'useful', 'concern', 'dislike', 'lush', 'righteous', 'wandering', 'shelf', 'rare', 'zealous', 'linen', 'grain', 'tasteful', 'camera', 'can', 'pail', 'add', 'sophisticated', 'upset', 'tremble', 'flippant', 'nail', 'snakes', 'film', 'risk', 'synonymous', 'cloistered', 'visit', 'call', 'cross', 'agreement', 'husky', 'wash', 'windy', 'hum', 'acrid', 'odd', 'depend', 'elastic', 'groan', 'ban', 'sudden', 'follow', 'rail', 'soothe', 'remove', 'intelligent', 'domineering', 'grieving', 'gaze', 'knowledge', 'birds', 'boring', 'lumpy', 'embarrass', 'many', 'drum', 'voyage', 'icicle', 'labored', 'burly', 'superficial', 'truculent', 'ice', 'obese', 'royal', 'ugly', 'van', 'high-pitched', 'tie', 'sigh', 'vacation', 'dusty', 'unfasten', 'woozy', 'treat', 'dreary', 'mellow', 'bump', 'tail', 'freezing', 'rat', 'story', 'tickle', 'onerous', 'ball', 'hungry', 'brick', 'political', 'train', 'library', 'bubble', 'imported', 'helpful', 'dull', 'unlock', 'popcorn', 'stiff', 'moan', 'crow', 'cultured', 'scale', 'force', 'zany', 'greet', 'books', 'brave', 'sweet', 'bewildered', 'thoughtful', 'dirt', 'nippy', 'pear', 'easy', 'side', 'multiply', 'gather', 'fill', 'enthusiastic', 'plan', 'living', 'blot', 'number', 'degree', 'idiotic', 'profit', 'impossible', 'nervous', 'support', 'vigorous', 'shocking', 'bottle', 'boat', 'memorize', 'trousers', 'elegant', 'unsightly', 'important', 'fabulous', 'weigh', 'erratic', 'sassy', 'absurd', 'special', 'dog', 'jittery', 'kick', 'chess', 'travel', 'song', 'sad', 'vest', 'productive', 'scene', 'stupid', 'delight', 'statuesque', 'breathe', 'awesome', 'injure', 'beam', 'class', 'screeching', 'hour', 'blow', 'bolt', 'profuse', 'bored', 'overt', 'famous', 'scissors', 'unnatural', 'kitty', 'trip', 'ruin', 'incandescent', 'jaded', 'fax', 'top', 'adjustment', 'leg', 'bait', 'cheap', 'zebra', 'develop', 'coordinated', 'educate', 'engine', 'cream', 'nine', 'admire', 'remember', 'grey', 'act', 'earthy', 'quiet', 'wound', 'awake', 'ticket', 'mundane', 'drunk', 'squeal', 'chemical', 'colour', 'connection', 'encouraging', 'roomy', 'protective', 'vessel', 'steady', 'well-groomed', 'moldy', 'cart', 'coil', 'tall', 'subtract', 'rule', 'tricky', 'alike', 'ajar', 'crash', 'mushy', 'spotless', 'volcano', 'calculate', 'uptight', 'tawdry', 'iron', 'aftermath', 'chalk', 'difficult', 'three', 'flash', 'sun', 'numerous', 'talk', 'available', 'scattered', 'striped', 'smiling', 'seashore', 'face', 'sugar', 'tick', 'overrated', 'poke', 'suit', 'elite', 'liquid', 'reply', 'habitual', 'magenta', 'clear', 'tongue', 'field', 'raspy', 'temporary', 'provide', 'sleet', 'quickest', 'scream', 'spark', 'approve', 'detect', 'exist', 'entertain', 'plastic', 'wilderness', 'crowd', 'square', 'upbeat', 'ship', 'regular', 'analyze', 'hurried', 'bathe', 'sea', 'want', 'copy', 'debt', 'pest', 'practice', 'glistening', 'tight', 'periodic', 'ubiquitous', 'aloof', 'robin', 'smile', 'confused', 'admit', 'skinny', 'whistle', 'secretary', 'satisfy', 'bulb', 'beautiful', 'unique', 'boot', 'lovely', 'inconclusive', 'childlike', 'pleasure', 'slap', 'ashamed', 'humdrum', 'interrupt', 'ignore', 'page', 'eatable', 'married', 'delay', 'object', 'foot', 'passenger', 'extra-small', 'instrument', 'industrious', 'resolute', 'dangerous', 'rate', 'big', 'deer', 'line', 'high', 'abject', 'hunt', 'lively', 'book', 'heal', 'suspend', 'smooth', 'hobbies', 'achiever', 'spring', 'possible', 'mice', 'elfin', 'snotty', 'shop', 'inexpensive', 'throat', 'suppose', 'straw', 'chicken', 'omniscient', 'steep', 'mean', 'shoe', 'moon', 'avoid', 'vanish', 'mysterious', 'fresh', 'meat', 'simple', 'wild', 'cheat', 'erect', 'kaput', 'illustrious', 'didactic', 'plough', 'fork', 'icy', 'joke', 'exotic', 'notebook', 'calendar', 'encourage', 'root', 'sulky', 'loud', 'reflective', 'school', 'expand', 'jelly', 'irritate', 'anger', 'addition', 'addicted', 'boundless', 'representative', 'lacking', 'airport', 'handle', 'hammer', 'aspiring', 'fasten', 'painful', 'cannon', 'noisy', 'offend', 'hateful', 'bedroom', 'steer', 'apparatus', 'realize', 'pastoral', 'science', 'one', 'oatmeal', 'zoo', 'dead', 'quaint', 'amused', 'exciting', 'pale', 'stem', 'end', 'discussion', 'arrive', 'bang', 'selection', 'tough', 'cushion', 'afterthought', 'dramatic', 'governor', 'sack', 'yell', 'learn', 'direful', 'locket', 'skin', 'describe', 'clever', 'obedient', 'real', 'attempt', 'surprise', 'chin', 'trace', 'pizzas', 'cough', 'weight', 'repulsive', 'hop', 'piquant', 'expert', 'box', 'dress', 'penitent', 'plants', 'substantial', 'live', 'laughable', 'abhorrent', 'friend', 'growth', 'delightful', 'sweltering', 'finger', 'monkey', 'second-hand', 'compete', 'north', 'tasteless', 'truck', 'complete', 'outgoing', 'parsimonious', 'unite', 'faulty', 'separate', 'crime', 'stitch', 'wonder', 'wary', 'squeeze', 'measure', 'treatment', 'ambiguous', 'pollution', 'cold', 'laborer', 'night', 'ambitious', 'pencil', 'bird', 'nosy', 'effect', 'soap', 'acceptable', 'pathetic', 'dust', 'spoil', 'amusing', 'lip', 'elderly', 'optimal', 'toe', 'wire', 'angry', 'example', 'heap', 'eyes', 'sneeze', 'six', 'best', 'beds', 'disgusted', 'summer', 'hate', 'petite', 'kindhearted', 'damaging', 'explode', 'steel', 'nonchalant', 'thick', 'fire', 'tip', 'unequal', 'smell', 'peel', 'lazy', 'wealthy', 'enchanting', 'chase', 'sound', 'miniature', 'efficacious', 'bouncy', 'verdant', 'abaft', 'decay', 'silent', 'late', 'grotesque', 'secretive', 'holistic', 'wiggly', 'nose', 'tranquil', 'sleep', 'pat', 'count', 'shave', 'wipe', 'seemly', 'flock', 'dare', 'wonderful', 'road', 'machine', 'children', 'stir', 'clap', 'apparel', 'strengthen', 'knock', 'pets', 'muscle', 'allow', 'insurance', 'thin', 'destruction', 'superb', 'car', 'nauseating', 'few', 'deceive', 'creature', 'momentous', 'tacit', 'curtain', 'pause', 'cooperative', 'humorous', 'hope', 'unbiased', 'clammy', 'consider', 'careless', 'sign', 'aware', 'absorbed', 'silver', 'back', 'adventurous', 'stay', 'brief', 'business', 'attract', 'pack', 'dear', 'sense', 'fireman', 'mark', 'scribble', 'girls', 'word', 'change', 'shelter', 'natural', 'funny', 'kiss', 'breath', 'holiday', 'celery', 'borrow', 'mountainous', 'coach', 'nebulous', 'harbor', 'pumped', 'round', 'voracious', 'planes', 'brush', 'common', 'trite', 'dizzy', 'grate', 'five', 'copper', 'wretched', 'fat', 'position', 'blink', 'enjoy', 'cluttered', 'legal', 'perform', 'measly', 'collect', 'tow', 'report', 'friction', 'splendid', 'silky', 'soak', 'horses', 'vegetable', 'frighten', 'regret', 'scintillating', 'welcome', 'scandalous', 'spill', 'wool', 'shiver', 'silly', 'title', 'handsomely', 'trouble', 'juggle', 'whispering', 'day', 'claim', 'women', 'repeat', 'hellish', 'rainy', 'first', 'expensive', 'fearless', 'yummy', 'wreck', 'reason', 'cable', 'prepare', 'step', 'plucky', 'bear', 'victorious', 'milky', 'sore', 'knee', 'lunchroom', 'unable', 'touch', 'trade', 'town', 'neighborly', 'approval', 'income', 'steadfast', 'scrub', 'trap', 'accessible', 'apologise', 'scare', 'bow', 'noxious', 'snatch', 'mine', 'rhetorical', 'jar', 'skate', 'staking', 'stop', 'earn', 'lamentable', 'noiseless', 'narrow', 'ruthless', 'harmony', 'abstracted', 'button', 'dynamic', 'bite-sized', 'quilt', 'leather', 'busy', 'marry', 'festive', 'thirsty', 'voice', 'majestic', 'jumpy', 'immense', 'obscene', 'pine', 'determined', 'lavish', 'agreeable', 'savory', 'unsuitable', 'grandfather', 'fragile', 'settle', 'self', 'pig', 'utter', 'stove', 'cherries', 'different', 'efficient', 'secret', 'organic', 'sister', 'cake', 'fall', 'exuberant', 'opposite', 'well-to-do', 'caption', 'vacuous', 'alive', 'smash', 'zonked', 'black', 'religion', 'maddening', 'wet', 'collar', 'sleepy', 'horn', 'seed', 'punish', 'last', 'advice', 'well-made', 'hot', 'wish', 'horse', 'credit', 'blush', 'orange', 'use', 'quixotic', 'murky', 'reproduce', 'wave', 'hurry', 'bit', 'infamous', 'soup', 'flowery', 'pen', 'brake', 'pump', 'unaccountable', 'actor', 'building', 'yarn', 'gruesome', 'angle', 'place', 'cut', 'obsequious', 'texture', 'short', 'crowded', 'spotted', 'rabid', 'concentrate', 'please', 'thinkable', 'gusty', 'disillusioned', 'laugh', 'mindless', 'bloody', 'shame', 'zipper', 'knotty', 'frog', 'tempt', 'learned', 'spy', 'hill', 'utopian', 'modern', 'craven', 'idea', 'wrap', 'load', 'new', 'quarrelsome', 'cry', 'dazzling', 'vivacious', 'cloudy', 'ripe', 'mailbox', 'overwrought', 'bitter', 'spurious', 'crib', 'vein', 'play', 'comparison', 'distance', 'blushing', 'scorch', 'ludicrous', 'thing', 'spell', 'bomb', 'start', 'squirrel', 'overflow', 'near', 'pointless', 'order', 'listen', 'belief', 'needle', 'fog', 'heartbreaking', 'hideous', 'wide', 'ill', 'group', 'beginner', 'tangible', 'escape', 'abusive', 'scarce', 'basket', 'spade', 'tremendous', 'dinosaurs', 'whisper', 'view', 'aberrant', 'sky', 'switch', 'elbow', 'aunt', 'scarf', 'songs', 'uppity', 'existence', 'discreet', 'heavenly', 'pour', 'tire', 'matter', 'instinctive', 'time', 'demonic', 'scrape', 'placid', 'puffy', 'violent', 'wail', 'debonair', 'wrong', 'irate', 'private', 'giants', 'carry', 'right', 'daily', 'able', 'foamy', 'full', 'farm', 'dapper', 'rot', 'ethereal', 'icky', 'cause', 'girl', 'advertisement', 'mighty', 'marvelous', 'male', 'earsplitting', 'adaptable', 'familiar', 'sip', 'obeisant', 'prose', 'slimy', 'hug', 'youthful', 'suggestion', 'interest', 'cabbage', 'standing', 'territory', 'shrill', 'chubby', 'outstanding', 'bare', 'drop', 'grumpy', 'precious', 'sweater', 'nerve', 'unpack', 'drab', 'battle', 'goofy', 'naive', 'little', 'tin', 'hair', 'room', 'recondite', 'friends', 'tap', 'teaching', 'cars', 'furry', 'minister', 'sparkling', 'fair', 'clip', 'butter', 'expansion', 'eggnog', 'drain', 'front', 'reduce', 'pull', 'wistful', 'price', 'point', 'carpenter', 'snore', 'vast', 'complain', 'afraid', 'invincible', 'jam', 'kindly', 'help', 'curl', 'deserve', 'makeshift', 'office', 'mom', 'mint', 'rude', 'guess', 'decisive', 'nostalgic', 'sisters', 'rub', 'walk', 'hover', 'tasty', 'tested', 'vulgar', 'snobbish', 'haunt', 'true', 'reign', 'capricious', 'fancy', 'deafening', 'reading', 'church', 'basin', 'observe', 'stage', 'fallacious', 'introduce', 'bury', 'rebel', 'telephone', 'thought', 'yellow', 'wink', 'error', 'turkey', 'shrug', 'history', 'loose', 'shoes', 'appreciate', 'attend', 'foregoing', 'frame', 'pop', 'soggy', 'aboriginal', 'pedal', 'tiresome', 'distinct', 'baby', 'border', 'wide-eyed', 'teeny-tiny', 'quartz', 'carve', 'trashy', 'ladybug', 'baseball', 'berry', 'roll', 'slope', 'pushy', 'sniff', 'post', 'ready', 'park', 'pink', 'witty', 'acoustics', 'stale', 'guiltless', 'crook', 'keen', 'donkey', 'grab', 'swim', 'afford', 'compare', 'cycle', 'flavor', 'frail', 'memory', 'tease', 'float', 'frantic', 'ratty', 'paint', 'crack', 'store', 'whimsical', 'increase', 'rest', 'grape', 'yard', 'tiny', 'applaud', 'shade', 'jagged', 'thankful', 'acidic', 'wrist', 'silk', 'glorious', 'pancake', 'impartial', 'tedious', 'belong', 'invent', 'rush', 'like', 'push', 'brawny', 'reward', 'fantastic', 'grandiose', 'lowly', 'eight', 'fairies', 'shake', 'thrill', 'accurate', 'poor', 'lackadaisical', 'four', 'obey', 'preach', 'incredible', 'quill', 'womanly', 'soda', 'base', 'hissing', 'lucky', 'puny', 'stereotyped', 'murder', 'watery', 'heavy', 'painstaking', 'marble', 'knife', 'third', 'home', 'stick', 'curly', 'manage', 'fang', 'crazy', 'drown', 'green', 'salty', 'health', 'fetch', 'hulking', 'desire', 'blue-eyed', 'attach', 'knowledgeable', 'well-off', 'examine', 'year', 'flesh', 'judge', 'tub', 'bone', 'birth', 'broad', 'neat', 'board', 'plane', 'shaggy', 'coherent', 'letter', 'deep', 'detailed', 'enchanted', 'fold', 'improve', 'incompetent', 'spiders', 'gorgeous', 'suffer', 'remain', 'rich', 'salt', 'lamp', 'coat', 'swift', 'partner', 'honey', 'pan', 'animated', 'corn', 'normal', 'oafish', 'lewd', 'bake', 'medical', 'peck', 'hands', 'thumb', 'mask', 'hollow', 'alluring', 'curious', 'abounding', 'invite', 'puncture', 'straight', 'station', 'bed', 'save', 'behavior', 'guarantee', 'sour', 'stingy', 'delirious', 'forgetful', 'panoramic', 'crush', 'hypnotic', 'bashful', 'minute', 'dogs', 'concerned', 'unwieldy', 'tramp', 'driving', 'close', 'country', 'quicksand', 'perpetual', 'sincere', 'sofa', 'pie', 'fly', 'war', 'lumber', 'innate', 'spray', 'cemetery', 'prick', 'average', 'rifle', 'grass', 'happy', 'toy', 'supreme', 'search', 'aback', 'suggest', 'relation', 'tacky', 'animal', 'sail', 'enter', 'scold', 'scrawny', 'morning', 'smart', 'bruise', 'vagabond', 'ocean', 'horrible', 'sprout', 'juvenile', 'selfish', 'grade', 'request', 'brother', 'bleach', 'unadvised', 'eager', 'consist', 'jeans', 'charge']