Commit 7f5af1fb authored by Adam Langley's avatar Adam Langley Committed by Commit Bot

device/fido: refactor pin.cc to enable testing.

In order to test this code we'll want to implement the authenticator
side of the PIN protocol in |VirtualCtap2Device|. It'll be helpful when
doing so to have access to some code from pin.cc.

This change exposes some PIN-protocol internals via a |pin_internal.h|
header for a future implementation in |VirtualCtap2Device|.

Change-Id: I76f2441185b4d1a058240de5d2cb9bbf49dc1061
Reviewed-on: https://chromium-review.googlesource.com/c/1481083Reviewed-by: default avatarMartin Kreichgauer <martinkr@google.com>
Commit-Queue: Adam Langley <agl@chromium.org>
Cr-Commit-Position: refs/heads/master@{#634759}
parent 4708ae48
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "components/cbor/writer.h" #include "components/cbor/writer.h"
#include "device/fido/fido_constants.h" #include "device/fido/fido_constants.h"
#include "device/fido/pin.h" #include "device/fido/pin.h"
#include "device/fido/pin_internal.h"
#include "third_party/boringssl/src/include/openssl/aes.h" #include "third_party/boringssl/src/include/openssl/aes.h"
#include "third_party/boringssl/src/include/openssl/bn.h" #include "third_party/boringssl/src/include/openssl/bn.h"
#include "third_party/boringssl/src/include/openssl/ec.h" #include "third_party/boringssl/src/include/openssl/ec.h"
...@@ -25,32 +26,6 @@ ...@@ -25,32 +26,6 @@
namespace device { namespace device {
namespace pin { namespace pin {
// kProtocolVersion is the version of the PIN protocol that this code
// implements.
constexpr int kProtocolVersion = 1;
// Subcommand enumerates the subcommands to the main |authenticatorClientPIN|
// command. See
// https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#authenticatorClientPIN
enum class Subcommand : uint8_t {
kGetRetries = 0x01,
kGetKeyAgreement = 0x02,
kSetPIN = 0x03,
kChangePIN = 0x04,
kGetPINToken = 0x05,
};
// CommandKeys enumerates the keys in the top-level CBOR map for all PIN
// commands.
enum class CommandKeys : int {
kProtocol = 1,
kSubcommand = 2,
kKeyAgreement = 3,
kPINAuth = 4,
kNewPINEnc = 5,
kPINHashEnc = 6,
};
// HasAtLeastFourCodepoints returns true if |pin| is UTF-8 encoded and contains // HasAtLeastFourCodepoints returns true if |pin| is UTF-8 encoded and contains
// four or more code points. This reflects the "4 Unicode characters" // four or more code points. This reflects the "4 Unicode characters"
// requirement in CTAP2. // requirement in CTAP2.
...@@ -72,8 +47,8 @@ static std::vector<uint8_t> EncodePINCommand( ...@@ -72,8 +47,8 @@ static std::vector<uint8_t> EncodePINCommand(
Subcommand subcommand, Subcommand subcommand,
std::function<void(cbor::Value::MapValue*)> add_additional = nullptr) { std::function<void(cbor::Value::MapValue*)> add_additional = nullptr) {
cbor::Value::MapValue map; cbor::Value::MapValue map;
map.emplace(static_cast<int>(CommandKeys::kProtocol), kProtocolVersion); map.emplace(static_cast<int>(RequestKey::kProtocol), kProtocolVersion);
map.emplace(static_cast<int>(CommandKeys::kSubcommand), map.emplace(static_cast<int>(RequestKey::kSubcommand),
static_cast<int>(subcommand)); static_cast<int>(subcommand));
if (add_additional) { if (add_additional) {
...@@ -109,7 +84,8 @@ base::Optional<RetriesResponse> RetriesResponse::Parse( ...@@ -109,7 +84,8 @@ base::Optional<RetriesResponse> RetriesResponse::Parse(
} }
const auto& response_map = decoded_response->GetMap(); const auto& response_map = decoded_response->GetMap();
auto it = response_map.find(cbor::Value(3)); auto it =
response_map.find(cbor::Value(static_cast<int>(ResponseKey::kRetries)));
if (it == response_map.end() || !it->second.is_unsigned()) { if (it == response_map.end() || !it->second.is_unsigned()) {
return base::nullopt; return base::nullopt;
} }
...@@ -128,7 +104,7 @@ KeyAgreementResponse::KeyAgreementResponse() = default; ...@@ -128,7 +104,7 @@ KeyAgreementResponse::KeyAgreementResponse() = default;
// PointFromKeyAgreementResponse returns an |EC_POINT| that represents the same // PointFromKeyAgreementResponse returns an |EC_POINT| that represents the same
// P-256 point as |response|. It returns |nullopt| if |response| encodes an // P-256 point as |response|. It returns |nullopt| if |response| encodes an
// invalid point. // invalid point.
static base::Optional<bssl::UniquePtr<EC_POINT>> PointFromKeyAgreementResponse( base::Optional<bssl::UniquePtr<EC_POINT>> PointFromKeyAgreementResponse(
const EC_GROUP* group, const EC_GROUP* group,
const KeyAgreementResponse& response) { const KeyAgreementResponse& response) {
bssl::UniquePtr<EC_POINT> ret(EC_POINT_new(group)); bssl::UniquePtr<EC_POINT> ret(EC_POINT_new(group));
...@@ -167,13 +143,20 @@ base::Optional<KeyAgreementResponse> KeyAgreementResponse::Parse( ...@@ -167,13 +143,20 @@ base::Optional<KeyAgreementResponse> KeyAgreementResponse::Parse(
} }
const auto& response_map = decoded_response->GetMap(); const auto& response_map = decoded_response->GetMap();
// The ephemeral key is encoded as a COSE structure under key 1. // The ephemeral key is encoded as a COSE structure.
auto it = response_map.find(cbor::Value(1)); auto it = response_map.find(
cbor::Value(static_cast<int>(ResponseKey::kKeyAgreement)));
if (it == response_map.end() || !it->second.is_map()) { if (it == response_map.end() || !it->second.is_map()) {
return base::nullopt; return base::nullopt;
} }
const auto& cose_key = it->second.GetMap(); const auto& cose_key = it->second.GetMap();
return ParseFromCOSE(cose_key);
}
// static
base::Optional<KeyAgreementResponse> KeyAgreementResponse::ParseFromCOSE(
const cbor::Value::MapValue& cose_key) {
// The COSE key must be a P-256 point. See // The COSE key must be a P-256 point. See
// https://tools.ietf.org/html/rfc8152#section-7.1 // https://tools.ietf.org/html/rfc8152#section-7.1
for (const auto& pair : std::vector<std::pair<int, int>>({ for (const auto& pair : std::vector<std::pair<int, int>>({
...@@ -239,32 +222,30 @@ static void* SHA256KDF(const void* in, ...@@ -239,32 +222,30 @@ static void* SHA256KDF(const void* in,
return out; return out;
} }
// CalculateSharedKey generates and returns an ephemeral key, and writes the // CalculateSharedKey writes the CTAP2 shared key between |key| and |peers_key|
// shared key between that ephemeral key and the authenticator's ephemeral key // to |out_shared_key|.
// (from |peers_key|) to |out_shared_key|. void CalculateSharedKey(const EC_KEY* key,
static cbor::Value::MapValue CalculateSharedKey( const EC_POINT* peers_key,
const KeyAgreementResponse& peers_key,
uint8_t out_shared_key[SHA256_DIGEST_LENGTH]) { uint8_t out_shared_key[SHA256_DIGEST_LENGTH]) {
bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
CHECK(EC_KEY_generate_key(key.get()));
auto peers_point =
PointFromKeyAgreementResponse(EC_KEY_get0_group(key.get()), peers_key);
CHECK_EQ(static_cast<int>(SHA256_DIGEST_LENGTH), CHECK_EQ(static_cast<int>(SHA256_DIGEST_LENGTH),
ECDH_compute_key(out_shared_key, SHA256_DIGEST_LENGTH, ECDH_compute_key(out_shared_key, SHA256_DIGEST_LENGTH, peers_key,
peers_point->get(), key.get(), SHA256KDF)); key, SHA256KDF));
}
// EncodeCOSEPublicKey returns the public part of |key| as a COSE structure.
cbor::Value::MapValue EncodeCOSEPublicKey(const EC_KEY* key) {
// X9.62 is the standard for serialising elliptic-curve points. // X9.62 is the standard for serialising elliptic-curve points.
uint8_t x962[1 /* type byte */ + 32 /* x */ + 32 /* y */]; uint8_t x962[1 /* type byte */ + 32 /* x */ + 32 /* y */];
CHECK_EQ(sizeof(x962), CHECK_EQ(
EC_POINT_point2oct(EC_KEY_get0_group(key.get()), sizeof(x962),
EC_KEY_get0_public_key(key.get()), EC_POINT_point2oct(EC_KEY_get0_group(key), EC_KEY_get0_public_key(key),
POINT_CONVERSION_UNCOMPRESSED, x962, sizeof(x962), POINT_CONVERSION_UNCOMPRESSED, x962, sizeof(x962),
nullptr /* BN_CTX */)); nullptr /* BN_CTX */));
cbor::Value::MapValue cose_key; cbor::Value::MapValue cose_key;
cose_key.emplace(1 /* key type */, 2 /* uncompressed elliptic curve */); cose_key.emplace(1 /* key type */, 2 /* uncompressed elliptic curve */);
cose_key.emplace(3 /* algorithm */, cose_key.emplace(3 /* algorithm */,
2 /* ECDH, ephemeral–static, HKDF-SHA-256 */); -25 /* ECDH, ephemeral–static, HKDF-SHA-256 */);
cose_key.emplace(-1 /* curve */, 1 /* P-256 */); cose_key.emplace(-1 /* curve */, 1 /* P-256 */);
cose_key.emplace(-2 /* x */, base::span<const uint8_t>(x962 + 1, 32)); cose_key.emplace(-2 /* x */, base::span<const uint8_t>(x962 + 1, 32));
cose_key.emplace(-3 /* y */, base::span<const uint8_t>(x962 + 33, 32)); cose_key.emplace(-3 /* y */, base::span<const uint8_t>(x962 + 33, 32));
...@@ -272,23 +253,47 @@ static cbor::Value::MapValue CalculateSharedKey( ...@@ -272,23 +253,47 @@ static cbor::Value::MapValue CalculateSharedKey(
return cose_key; return cose_key;
} }
std::vector<uint8_t> SetRequest::EncodeAsCBOR() const { // GenerateSharedKey generates and returns an ephemeral key, and writes the
// See // shared key between that ephemeral key and the authenticator's ephemeral key
// https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#settingNewPin // (from |peers_key|) to |out_shared_key|.
uint8_t shared_key[SHA256_DIGEST_LENGTH]; static cbor::Value::MapValue GenerateSharedKey(
auto cose_key = CalculateSharedKey(peer_key_, shared_key); const KeyAgreementResponse& peers_key,
uint8_t out_shared_key[SHA256_DIGEST_LENGTH]) {
bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
CHECK(EC_KEY_generate_key(key.get()));
auto peers_point =
PointFromKeyAgreementResponse(EC_KEY_get0_group(key.get()), peers_key);
CalculateSharedKey(key.get(), peers_point->get(), out_shared_key);
return EncodeCOSEPublicKey(key.get());
}
// Encrypt encrypts |plaintext| using |key|, writing the ciphertext to
// |out_ciphertext|. |plaintext| must be a whole number of AES blocks.
void Encrypt(const uint8_t key[SHA256_DIGEST_LENGTH],
base::span<const uint8_t> plaintext,
uint8_t* out_ciphertext) {
DCHECK_EQ(0u, plaintext.size() % AES_BLOCK_SIZE);
EVP_CIPHER_CTX aes_ctx; EVP_CIPHER_CTX aes_ctx;
EVP_CIPHER_CTX_init(&aes_ctx); EVP_CIPHER_CTX_init(&aes_ctx);
const uint8_t kZeroIV[AES_BLOCK_SIZE] = {0}; const uint8_t kZeroIV[AES_BLOCK_SIZE] = {0};
CHECK(EVP_EncryptInit_ex(&aes_ctx, EVP_aes_256_cbc(), nullptr, shared_key, CHECK(EVP_EncryptInit_ex(&aes_ctx, EVP_aes_256_cbc(), nullptr, key, kZeroIV));
kZeroIV)); CHECK(EVP_CIPHER_CTX_set_padding(&aes_ctx, 0 /* no padding */));
CHECK(
EVP_Cipher(&aes_ctx, out_ciphertext, plaintext.data(), plaintext.size()));
EVP_CIPHER_CTX_cleanup(&aes_ctx);
}
std::vector<uint8_t> SetRequest::EncodeAsCBOR() const {
// See
// https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#settingNewPin
uint8_t shared_key[SHA256_DIGEST_LENGTH];
auto cose_key = GenerateSharedKey(peer_key_, shared_key);
static_assert((sizeof(pin_) % AES_BLOCK_SIZE) == 0, static_assert((sizeof(pin_) % AES_BLOCK_SIZE) == 0,
"pin_ is not a multiple of the AES block size"); "pin_ is not a multiple of the AES block size");
uint8_t encrypted_pin[sizeof(pin_) + AES_BLOCK_SIZE]; uint8_t encrypted_pin[sizeof(pin_)];
CHECK(EVP_Cipher(&aes_ctx, encrypted_pin, pin_, sizeof(pin_))); Encrypt(shared_key, pin_, encrypted_pin);
EVP_CIPHER_CTX_cleanup(&aes_ctx);
uint8_t pin_auth[SHA256_DIGEST_LENGTH]; uint8_t pin_auth[SHA256_DIGEST_LENGTH];
unsigned hmac_bytes; unsigned hmac_bytes;
...@@ -299,13 +304,12 @@ std::vector<uint8_t> SetRequest::EncodeAsCBOR() const { ...@@ -299,13 +304,12 @@ std::vector<uint8_t> SetRequest::EncodeAsCBOR() const {
return EncodePINCommand( return EncodePINCommand(
Subcommand::kSetPIN, Subcommand::kSetPIN,
[&cose_key, &encrypted_pin, &pin_auth](cbor::Value::MapValue* map) { [&cose_key, &encrypted_pin, &pin_auth](cbor::Value::MapValue* map) {
map->emplace(static_cast<int>(CommandKeys::kKeyAgreement), map->emplace(static_cast<int>(RequestKey::kKeyAgreement),
std::move(cose_key)); std::move(cose_key));
map->emplace(static_cast<int>(CommandKeys::kNewPINEnc), map->emplace(
// Note that the final AES block of |encrypted_pin| is static_cast<int>(RequestKey::kNewPINEnc),
// discarded because CTAP2 doesn't include any padding. base::span<const uint8_t>(encrypted_pin, sizeof(encrypted_pin)));
base::span<const uint8_t>(encrypted_pin, sizeof(pin_))); map->emplace(static_cast<int>(RequestKey::kPINAuth),
map->emplace(static_cast<int>(CommandKeys::kPINAuth),
base::span<const uint8_t>(pin_auth)); base::span<const uint8_t>(pin_auth));
}); });
} }
...@@ -341,35 +345,22 @@ std::vector<uint8_t> ChangeRequest::EncodeAsCBOR() const { ...@@ -341,35 +345,22 @@ std::vector<uint8_t> ChangeRequest::EncodeAsCBOR() const {
// See // See
// https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#changingExistingPin // https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#changingExistingPin
uint8_t shared_key[SHA256_DIGEST_LENGTH]; uint8_t shared_key[SHA256_DIGEST_LENGTH];
auto cose_key = CalculateSharedKey(peer_key_, shared_key); auto cose_key = GenerateSharedKey(peer_key_, shared_key);
EVP_CIPHER_CTX aes_ctx;
EVP_CIPHER_CTX_init(&aes_ctx);
const uint8_t kZeroIV[AES_BLOCK_SIZE] = {0};
CHECK(EVP_EncryptInit_ex(&aes_ctx, EVP_aes_256_cbc(), nullptr, shared_key,
kZeroIV));
static_assert((sizeof(new_pin_) % AES_BLOCK_SIZE) == 0, static_assert((sizeof(new_pin_) % AES_BLOCK_SIZE) == 0,
"new_pin_ is not a multiple of the AES block size"); "new_pin_ is not a multiple of the AES block size");
uint8_t encrypted_pin[sizeof(new_pin_) + AES_BLOCK_SIZE]; uint8_t encrypted_pin[sizeof(new_pin_)];
CHECK(EVP_Cipher(&aes_ctx, encrypted_pin, new_pin_, sizeof(new_pin_))); Encrypt(shared_key, new_pin_, encrypted_pin);
CHECK(EVP_EncryptInit_ex(&aes_ctx, EVP_aes_256_cbc(), nullptr, shared_key,
kZeroIV));
static_assert((sizeof(old_pin_hash_) % AES_BLOCK_SIZE) == 0, static_assert((sizeof(old_pin_hash_) % AES_BLOCK_SIZE) == 0,
"old_pin_hash_ is not a multiple of the AES block size"); "old_pin_hash_ is not a multiple of the AES block size");
uint8_t old_pin_hash_enc[sizeof(old_pin_hash_) + AES_BLOCK_SIZE]; uint8_t old_pin_hash_enc[sizeof(old_pin_hash_)];
CHECK(EVP_Cipher(&aes_ctx, old_pin_hash_enc, old_pin_hash_, Encrypt(shared_key, old_pin_hash_, old_pin_hash_enc);
sizeof(old_pin_hash_)));
EVP_CIPHER_CTX_cleanup(&aes_ctx); uint8_t ciphertexts_concat[sizeof(encrypted_pin) + sizeof(old_pin_hash_enc)];
memcpy(ciphertexts_concat, encrypted_pin, sizeof(encrypted_pin));
uint8_t ciphertexts_concat[sizeof(new_pin_) + sizeof(old_pin_hash_)]; memcpy(ciphertexts_concat + sizeof(encrypted_pin), old_pin_hash_enc,
// Note that the final AES blocks of |encrypted_pin| and |old_pin_hash_enc| sizeof(old_pin_hash_enc));
// are discarded because CTAP2 doesn't include any padding.
memcpy(ciphertexts_concat, encrypted_pin, sizeof(new_pin_));
memcpy(ciphertexts_concat + sizeof(new_pin_), old_pin_hash_enc,
sizeof(old_pin_hash_));
uint8_t pin_auth[SHA256_DIGEST_LENGTH]; uint8_t pin_auth[SHA256_DIGEST_LENGTH];
unsigned hmac_bytes; unsigned hmac_bytes;
...@@ -380,15 +371,15 @@ std::vector<uint8_t> ChangeRequest::EncodeAsCBOR() const { ...@@ -380,15 +371,15 @@ std::vector<uint8_t> ChangeRequest::EncodeAsCBOR() const {
return EncodePINCommand( return EncodePINCommand(
Subcommand::kChangePIN, [&cose_key, &encrypted_pin, &old_pin_hash_enc, Subcommand::kChangePIN, [&cose_key, &encrypted_pin, &old_pin_hash_enc,
&pin_auth](cbor::Value::MapValue* map) { &pin_auth](cbor::Value::MapValue* map) {
map->emplace(static_cast<int>(CommandKeys::kKeyAgreement), map->emplace(static_cast<int>(RequestKey::kKeyAgreement),
std::move(cose_key)); std::move(cose_key));
map->emplace(static_cast<int>(RequestKey::kPINHashEnc),
base::span<const uint8_t>(old_pin_hash_enc,
sizeof(old_pin_hash_enc)));
map->emplace( map->emplace(
static_cast<int>(CommandKeys::kPINHashEnc), static_cast<int>(RequestKey::kNewPINEnc),
base::span<const uint8_t>(old_pin_hash_enc, sizeof(old_pin_hash_))); base::span<const uint8_t>(encrypted_pin, sizeof(encrypted_pin)));
map->emplace( map->emplace(static_cast<int>(RequestKey::kPINAuth),
static_cast<int>(CommandKeys::kNewPINEnc),
base::span<const uint8_t>(encrypted_pin, sizeof(new_pin_)));
map->emplace(static_cast<int>(CommandKeys::kPINAuth),
base::span<const uint8_t>(pin_auth)); base::span<const uint8_t>(pin_auth));
}); });
} }
...@@ -399,7 +390,7 @@ std::vector<uint8_t> ResetRequest::EncodeAsCBOR() const { ...@@ -399,7 +390,7 @@ std::vector<uint8_t> ResetRequest::EncodeAsCBOR() const {
TokenRequest::TokenRequest(const std::string& pin, TokenRequest::TokenRequest(const std::string& pin,
const KeyAgreementResponse& peer_key) const KeyAgreementResponse& peer_key)
: cose_key_(CalculateSharedKey(peer_key, shared_key_.data())) { : cose_key_(GenerateSharedKey(peer_key, shared_key_.data())) {
DCHECK_EQ(static_cast<size_t>(SHA256_DIGEST_LENGTH), shared_key_.size()); DCHECK_EQ(static_cast<size_t>(SHA256_DIGEST_LENGTH), shared_key_.size());
uint8_t digest[SHA256_DIGEST_LENGTH]; uint8_t digest[SHA256_DIGEST_LENGTH];
SHA256(reinterpret_cast<const uint8_t*>(pin.data()), pin.size(), digest); SHA256(reinterpret_cast<const uint8_t*>(pin.data()), pin.size(), digest);
...@@ -413,26 +404,19 @@ const std::array<uint8_t, 32>& TokenRequest::shared_key() const { ...@@ -413,26 +404,19 @@ const std::array<uint8_t, 32>& TokenRequest::shared_key() const {
} }
std::vector<uint8_t> TokenRequest::EncodeAsCBOR() const { std::vector<uint8_t> TokenRequest::EncodeAsCBOR() const {
EVP_CIPHER_CTX aes_ctx;
EVP_CIPHER_CTX_init(&aes_ctx);
const uint8_t kZeroIV[AES_BLOCK_SIZE] = {0};
CHECK(EVP_EncryptInit_ex(&aes_ctx, EVP_aes_256_cbc(), nullptr,
shared_key_.data(), kZeroIV));
static_assert((sizeof(pin_hash_) % AES_BLOCK_SIZE) == 0, static_assert((sizeof(pin_hash_) % AES_BLOCK_SIZE) == 0,
"pin_hash_ is not a multiple of the AES block size"); "pin_hash_ is not a multiple of the AES block size");
uint8_t encrypted_pin[sizeof(pin_hash_) + AES_BLOCK_SIZE]; uint8_t encrypted_pin[sizeof(pin_hash_)];
CHECK(EVP_Cipher(&aes_ctx, encrypted_pin, pin_hash_, sizeof(pin_hash_))); Encrypt(shared_key_.data(), pin_hash_, encrypted_pin);
EVP_CIPHER_CTX_cleanup(&aes_ctx);
return EncodePINCommand( return EncodePINCommand(
Subcommand::kGetPINToken, Subcommand::kGetPINToken,
[this, &encrypted_pin](cbor::Value::MapValue* map) { [this, &encrypted_pin](cbor::Value::MapValue* map) {
map->emplace(static_cast<int>(CommandKeys::kKeyAgreement), map->emplace(static_cast<int>(RequestKey::kKeyAgreement),
std::move(this->cose_key_)); std::move(this->cose_key_));
map->emplace( map->emplace(
static_cast<int>(CommandKeys::kPINHashEnc), static_cast<int>(RequestKey::kPINHashEnc),
base::span<const uint8_t>(encrypted_pin, sizeof(pin_hash_))); base::span<const uint8_t>(encrypted_pin, sizeof(encrypted_pin)));
}); });
} }
...@@ -440,6 +424,24 @@ TokenResponse::TokenResponse() = default; ...@@ -440,6 +424,24 @@ TokenResponse::TokenResponse() = default;
TokenResponse::~TokenResponse() = default; TokenResponse::~TokenResponse() = default;
TokenResponse::TokenResponse(const TokenResponse&) = default; TokenResponse::TokenResponse(const TokenResponse&) = default;
// Decrypt AES-256 CBC decrypts some number of whole blocks from |ciphertext|
// into |plaintext|, using |key|.
void Decrypt(const uint8_t key[SHA256_DIGEST_LENGTH],
base::span<const uint8_t> ciphertext,
uint8_t* out_plaintext) {
DCHECK_EQ(0u, ciphertext.size() % AES_BLOCK_SIZE);
EVP_CIPHER_CTX aes_ctx;
EVP_CIPHER_CTX_init(&aes_ctx);
const uint8_t kZeroIV[AES_BLOCK_SIZE] = {0};
CHECK(EVP_DecryptInit_ex(&aes_ctx, EVP_aes_256_cbc(), nullptr, key, kZeroIV));
CHECK(EVP_CIPHER_CTX_set_padding(&aes_ctx, 0 /* no padding */));
CHECK(EVP_Cipher(&aes_ctx, out_plaintext, ciphertext.data(),
ciphertext.size()));
EVP_CIPHER_CTX_cleanup(&aes_ctx);
}
base::Optional<TokenResponse> TokenResponse::Parse( base::Optional<TokenResponse> TokenResponse::Parse(
std::array<uint8_t, 32> shared_key, std::array<uint8_t, 32> shared_key,
base::span<const uint8_t> buffer) { base::span<const uint8_t> buffer) {
...@@ -456,8 +458,8 @@ base::Optional<TokenResponse> TokenResponse::Parse( ...@@ -456,8 +458,8 @@ base::Optional<TokenResponse> TokenResponse::Parse(
} }
const auto& response_map = decoded_response->GetMap(); const auto& response_map = decoded_response->GetMap();
// The encrypted PIN-token is under key 2. auto it =
auto it = response_map.find(cbor::Value(2)); response_map.find(cbor::Value(static_cast<int>(ResponseKey::kPINToken)));
if (it == response_map.end() || !it->second.is_bytestring()) { if (it == response_map.end() || !it->second.is_bytestring()) {
return base::nullopt; return base::nullopt;
} }
...@@ -466,18 +468,9 @@ base::Optional<TokenResponse> TokenResponse::Parse( ...@@ -466,18 +468,9 @@ base::Optional<TokenResponse> TokenResponse::Parse(
return base::nullopt; return base::nullopt;
} }
EVP_CIPHER_CTX aes_ctx;
EVP_CIPHER_CTX_init(&aes_ctx);
const uint8_t kZeroIV[AES_BLOCK_SIZE] = {0};
CHECK(EVP_DecryptInit_ex(&aes_ctx, EVP_aes_256_cbc(), nullptr,
shared_key.data(), kZeroIV));
CHECK(EVP_CIPHER_CTX_set_padding(&aes_ctx, 0 /* no padding */));
TokenResponse ret; TokenResponse ret;
ret.token_.resize(encrypted_token.size()); ret.token_.resize(encrypted_token.size());
CHECK(EVP_Cipher(&aes_ctx, ret.token_.data(), encrypted_token.data(), Decrypt(shared_key.data(), encrypted_token, ret.token_.data());
encrypted_token.size()));
EVP_CIPHER_CTX_cleanup(&aes_ctx);
return ret; return ret;
} }
......
...@@ -66,6 +66,8 @@ struct KeyAgreementRequest { ...@@ -66,6 +66,8 @@ struct KeyAgreementRequest {
struct KeyAgreementResponse { struct KeyAgreementResponse {
static base::Optional<KeyAgreementResponse> Parse( static base::Optional<KeyAgreementResponse> Parse(
base::span<const uint8_t> buffer); base::span<const uint8_t> buffer);
static base::Optional<KeyAgreementResponse> ParseFromCOSE(
const cbor::Value::MapValue& cose_key);
// x and y contain the big-endian coordinates of a P-256 point. It is ensured // x and y contain the big-endian coordinates of a P-256 point. It is ensured
// that this is a valid point on the curve. // that this is a valid point on the curve.
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file contains additional declarations for CTAP2 PIN support. Only
// implementations of the PIN protocol should need to include this file. For all
// other code, see |pin.h|.
#ifndef DEVICE_FIDO_PIN_INTERNAL_H_
#define DEVICE_FIDO_PIN_INTERNAL_H_
#include <stdint.h>
#include "components/cbor/values.h"
#include "device/fido/pin.h"
#include "third_party/boringssl/src/include/openssl/ec.h"
#include "third_party/boringssl/src/include/openssl/sha.h"
namespace device {
namespace pin {
// kProtocolVersion is the version of the PIN protocol that this code
// implements.
constexpr int kProtocolVersion = 1;
// Subcommand enumerates the subcommands to the main |authenticatorClientPIN|
// command. See
// https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#authenticatorClientPIN
enum class Subcommand : uint8_t {
kGetRetries = 0x01,
kGetKeyAgreement = 0x02,
kSetPIN = 0x03,
kChangePIN = 0x04,
kGetPINToken = 0x05,
};
// RequestKey enumerates the keys in the top-level CBOR map for all PIN
// commands.
enum class RequestKey : int {
kProtocol = 1,
kSubcommand = 2,
kKeyAgreement = 3,
kPINAuth = 4,
kNewPINEnc = 5,
kPINHashEnc = 6,
};
// ResponseKey enumerates the keys in the top-level CBOR map for all PIN
// responses.
enum class ResponseKey : int {
kKeyAgreement = 1,
kPINToken = 2,
kRetries = 3,
};
// PointFromKeyAgreementResponse returns an |EC_POINT| that represents the same
// P-256 point as |response|. It returns |nullopt| if |response| encodes an
// invalid point.
base::Optional<bssl::UniquePtr<EC_POINT>> PointFromKeyAgreementResponse(
const EC_GROUP* group,
const KeyAgreementResponse& response);
// CalculateSharedKey writes the CTAP2 shared key between |key| and |peers_key|
// to |out_shared_key|.
void CalculateSharedKey(const EC_KEY* key,
const EC_POINT* peers_key,
uint8_t out_shared_key[SHA256_DIGEST_LENGTH]);
// EncodeCOSEPublicKey returns the public part of |key| as a COSE structure.
cbor::Value::MapValue EncodeCOSEPublicKey(const EC_KEY* key);
// Encrypt encrypts |plaintext| using |key|, writing the ciphertext to
// |out_ciphertext|. |plaintext| must be a whole number of AES blocks.
void Encrypt(const uint8_t key[SHA256_DIGEST_LENGTH],
base::span<const uint8_t> plaintext,
uint8_t* out_ciphertext);
// Decrypt AES-256 CBC decrypts some number of whole blocks from |ciphertext|
// into |plaintext|, using |key|.
void Decrypt(const uint8_t key[SHA256_DIGEST_LENGTH],
base::span<const uint8_t> ciphertext,
uint8_t* out_plaintext);
} // namespace pin
} // namespace device
#endif // DEVICE_FIDO_PIN_INTERNAL_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment