// U2F / CTAP1 over CTAPHID_MSG (FIDO U2F Raw Message Formats v1.2). #include #include "attestation.h" #include "cred.h" #include "crypto.h" #include "ctap.h" #include "presence.h" #include "storage.h" #include "util.h" #define U2F_REGISTER 0x01 #define U2F_AUTHENTICATE 0x02 #define U2F_VERSION 0x03 #define AUTH_ENFORCE_PRESENCE 0x03 #define AUTH_CHECK_ONLY 0x07 #define AUTH_DONT_ENFORCE_PRESENCE 0x08 #define SW_NO_ERROR 0x9000 #define SW_WRONG_LENGTH 0x6700 #define SW_CONDITIONS_NOT_SATISFIED 0x6985 #define SW_WRONG_DATA 0x6a80 #define SW_INS_NOT_SUPPORTED 0x6d00 #define SW_CLA_NOT_SUPPORTED 0x6e00 #define SW_UNKNOWN 0x6f00 static size_t status(uint8_t *resp, size_t len, uint16_t sw) { resp[len] = (uint8_t)(sw >> 8); resp[len + 1] = (uint8_t)sw; return len + 2; } static size_t u2f_register(const uint8_t *data, size_t lc, uint8_t *resp) { const uint8_t *challenge = data, *app_id = data + 32; uint8_t cred_id[CRED_ID_LEN], priv[32], pub[64]; static const uint8_t reserved = 0x00; if (lc != 64) return status(resp, 0, SW_WRONG_LENGTH); if (!presence_check()) return status(resp, 0, SW_CONDITIONS_NOT_SATISFIED); if (!cred_create(app_id, cred_id, priv, pub)) return status(resp, 0, SW_UNKNOWN); crypto_wipe(priv, sizeof(priv)); // 0x05 | public key | key handle length | key handle | certificate | signature size_t len = 0; resp[len++] = 0x05; resp[len++] = 0x04; // uncompressed point memcpy(resp + len, pub, 64); len += 64; resp[len++] = CRED_ID_LEN; memcpy(resp + len, cred_id, CRED_ID_LEN); len += CRED_ID_LEN; memcpy(resp + len, attestation_cert, attestation_cert_len); len += attestation_cert_len; crypto_part_t signed_data[] = { {&reserved, 1}, {app_id, 32}, {challenge, 32}, {cred_id, CRED_ID_LEN}, {resp + 1, 65}, }; size_t sig_len = crypto_p256_sign(attestation_key, signed_data, 5, resp + len); if (!sig_len) return status(resp, 0, SW_UNKNOWN); return status(resp, len + sig_len, SW_NO_ERROR); } static size_t u2f_authenticate(uint8_t mode, const uint8_t *data, size_t lc, uint8_t *resp) { const uint8_t *challenge = data, *app_id = data + 32, *key_handle = data + 65; uint8_t priv[32]; if (lc < 65 || lc != 65u + data[64]) return status(resp, 0, SW_WRONG_LENGTH); if (mode != AUTH_ENFORCE_PRESENCE && mode != AUTH_CHECK_ONLY && mode != AUTH_DONT_ENFORCE_PRESENCE) return status(resp, 0, SW_WRONG_DATA); if (!cred_unwrap(app_id, key_handle, data[64], priv)) return status(resp, 0, SW_WRONG_DATA); // Check-only answers "this key handle is mine" with a presence error. uint16_t sw = SW_NO_ERROR; uint8_t flags = 0; if (mode == AUTH_CHECK_ONLY) { sw = SW_CONDITIONS_NOT_SATISFIED; } else if (mode == AUTH_ENFORCE_PRESENCE) { if (presence_check()) flags = 0x01; else sw = SW_CONDITIONS_NOT_SATISFIED; } if (sw != SW_NO_ERROR) { crypto_wipe(priv, sizeof(priv)); return status(resp, 0, sw); } // user presence | counter | signature resp[0] = flags; put_be32(resp + 1, storage_counter_next()); crypto_part_t signed_data[] = {{app_id, 32}, {resp, 5}, {challenge, 32}}; size_t sig_len = crypto_p256_sign(priv, signed_data, 3, resp + 5); crypto_wipe(priv, sizeof(priv)); if (!sig_len) return status(resp, 0, SW_UNKNOWN); return status(resp, 5 + sig_len, SW_NO_ERROR); } size_t ctap1_handle(const uint8_t *req, size_t len, uint8_t *resp) { const uint8_t *data = req + 4; size_t lc = 0; if (len < 4) return status(resp, 0, SW_WRONG_LENGTH); if (len > 4) { if (req[4] == 0 && len >= 7) { // extended length: 00 Lc1 Lc2 lc = (size_t)req[5] << 8 | req[6]; data = req + 7; } else { lc = req[4]; data = req + 5; } // Anything past the data is Le, which we don't need. if (lc > len - (size_t)(data - req)) return status(resp, 0, SW_WRONG_LENGTH); } if (req[0] != 0) return status(resp, 0, SW_CLA_NOT_SUPPORTED); switch (req[1]) { case U2F_REGISTER: return u2f_register(data, lc, resp); case U2F_AUTHENTICATE: return u2f_authenticate(req[2], data, lc, resp); case U2F_VERSION: if (lc != 0) return status(resp, 0, SW_WRONG_LENGTH); memcpy(resp, "U2F_V2", 6); return status(resp, 6, SW_NO_ERROR); default: return status(resp, 0, SW_INS_NOT_SUPPORTED); } }