Commit 9b18194e authored by Adam Langley's avatar Adam Langley Committed by Commit Bot

device/fido: fully support hmac_secret extension in virtual authenticator.

Change-Id: I3d1f5a8773a6ee6f81039c141e428e6df415cfce
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2255213
Commit-Queue: Adam Langley <agl@chromium.org>
Reviewed-by: default avatarMartin Kreichgauer <martinkr@google.com>
Cr-Commit-Position: refs/heads/master@{#783104}
parent 0655c039
...@@ -97,4 +97,26 @@ AuthenticatorGetAssertionResponse::SetNumCredentials(uint8_t num_credentials) { ...@@ -97,4 +97,26 @@ AuthenticatorGetAssertionResponse::SetNumCredentials(uint8_t num_credentials) {
return *this; return *this;
} }
base::Optional<base::span<const uint8_t>>
AuthenticatorGetAssertionResponse::hmac_secret() const {
if (hmac_secret_) {
return *hmac_secret_;
}
return base::nullopt;
}
void AuthenticatorGetAssertionResponse::set_hmac_secret(
std::vector<uint8_t> hmac_secret) {
hmac_secret_ = std::move(hmac_secret);
}
bool AuthenticatorGetAssertionResponse::hmac_secret_not_evaluated() const {
return hmac_secret_not_evaluated_;
}
void AuthenticatorGetAssertionResponse::set_hmac_secret_not_evaluated(
bool value) {
hmac_secret_not_evaluated_ = value;
}
} // namespace device } // namespace device
...@@ -67,12 +67,26 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorGetAssertionResponse ...@@ -67,12 +67,26 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorGetAssertionResponse
android_client_data_ext_ = data; android_client_data_ext_ = data;
} }
// hmac_secret contains the output of the hmac_secret extension.
base::Optional<base::span<const uint8_t>> hmac_secret() const;
void set_hmac_secret(std::vector<uint8_t>);
// hmac_secret_not_evaluated will be true in cases where the
// |FidoAuthenticator| was unable to process the extension, even though it
// supports hmac_secret in general. This is intended for a case of Windows,
// where some versions of webauthn.dll can only express the extension for
// makeCredential, not getAssertion.
bool hmac_secret_not_evaluated() const;
void set_hmac_secret_not_evaluated(bool);
private: private:
base::Optional<PublicKeyCredentialDescriptor> credential_; base::Optional<PublicKeyCredentialDescriptor> credential_;
AuthenticatorData authenticator_data_; AuthenticatorData authenticator_data_;
std::vector<uint8_t> signature_; std::vector<uint8_t> signature_;
base::Optional<PublicKeyCredentialUserEntity> user_entity_; base::Optional<PublicKeyCredentialUserEntity> user_entity_;
base::Optional<uint8_t> num_credentials_; base::Optional<uint8_t> num_credentials_;
base::Optional<std::vector<uint8_t>> hmac_secret_;
bool hmac_secret_not_evaluated_ = false;
// If not base::nullopt, the content of the googleAndroidClientData extension // If not base::nullopt, the content of the googleAndroidClientData extension
// authenticator output. // authenticator output.
......
...@@ -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/fido_parsing_utils.h" #include "device/fido/fido_parsing_utils.h"
#include "device/fido/pin.h"
namespace device { namespace device {
...@@ -37,6 +38,19 @@ bool AreGetAssertionRequestMapKeysCorrect( ...@@ -37,6 +38,19 @@ bool AreGetAssertionRequestMapKeysCorrect(
} }
} // namespace } // namespace
CtapGetAssertionRequest::HMACSecret::HMACSecret(
base::span<const uint8_t, kP256X962Length> in_public_key_x962,
base::span<const uint8_t> in_encrypted_salts,
base::span<const uint8_t> in_salts_auth)
: public_key_x962(fido_parsing_utils::Materialize(in_public_key_x962)),
encrypted_salts(fido_parsing_utils::Materialize(in_encrypted_salts)),
salts_auth(fido_parsing_utils::Materialize(in_salts_auth)) {}
CtapGetAssertionRequest::HMACSecret::HMACSecret(const HMACSecret&) = default;
CtapGetAssertionRequest::HMACSecret::~HMACSecret() = default;
CtapGetAssertionRequest::HMACSecret&
CtapGetAssertionRequest::HMACSecret::operator=(const HMACSecret&) = default;
// static // static
base::Optional<CtapGetAssertionRequest> CtapGetAssertionRequest::Parse( base::Optional<CtapGetAssertionRequest> CtapGetAssertionRequest::Parse(
const cbor::Value::MapValue& request_map, const cbor::Value::MapValue& request_map,
...@@ -102,7 +116,8 @@ base::Optional<CtapGetAssertionRequest> CtapGetAssertionRequest::Parse( ...@@ -102,7 +116,8 @@ base::Optional<CtapGetAssertionRequest> CtapGetAssertionRequest::Parse(
return base::nullopt; return base::nullopt;
} }
if (extension.first.GetString() == kExtensionAndroidClientData) { const std::string& extension_id = extension.first.GetString();
if (extension_id == kExtensionAndroidClientData) {
base::Optional<AndroidClientDataExtensionInput> base::Optional<AndroidClientDataExtensionInput>
android_client_data_ext = android_client_data_ext =
AndroidClientDataExtensionInput::Parse(extension.second); AndroidClientDataExtensionInput::Parse(extension.second);
...@@ -110,6 +125,42 @@ base::Optional<CtapGetAssertionRequest> CtapGetAssertionRequest::Parse( ...@@ -110,6 +125,42 @@ base::Optional<CtapGetAssertionRequest> CtapGetAssertionRequest::Parse(
return base::nullopt; return base::nullopt;
} }
request.android_client_data_ext = std::move(*android_client_data_ext); request.android_client_data_ext = std::move(*android_client_data_ext);
} else if (extension_id == kExtensionHmacSecret) {
if (!extension.second.is_map()) {
return base::nullopt;
}
const auto& hmac_extension = extension.second.GetMap();
auto hmac_it = hmac_extension.find(cbor::Value(1));
if (hmac_it == hmac_extension.end() || !hmac_it->second.is_map()) {
return base::nullopt;
}
const base::Optional<pin::KeyAgreementResponse> key(
pin::KeyAgreementResponse::ParseFromCOSE(hmac_it->second.GetMap()));
hmac_it = hmac_extension.find(cbor::Value(2));
if (hmac_it == hmac_extension.end() ||
!hmac_it->second.is_bytestring()) {
return base::nullopt;
}
const std::vector<uint8_t>& encrypted_salts =
hmac_it->second.GetBytestring();
hmac_it = hmac_extension.find(cbor::Value(3));
if (hmac_it == hmac_extension.end() ||
!hmac_it->second.is_bytestring()) {
return base::nullopt;
}
const std::vector<uint8_t>& salts_auth =
hmac_it->second.GetBytestring();
if (!key ||
(encrypted_salts.size() != 32 && encrypted_salts.size() != 64) ||
salts_auth.size() != 16) {
return base::nullopt;
}
request.hmac_secret.emplace(key->X962(), encrypted_salts, salts_auth);
} }
} }
} }
...@@ -196,10 +247,24 @@ AsCTAPRequestValuePair(const CtapGetAssertionRequest& request) { ...@@ -196,10 +247,24 @@ AsCTAPRequestValuePair(const CtapGetAssertionRequest& request) {
cbor_map[cbor::Value(3)] = cbor::Value(std::move(allow_list_array)); cbor_map[cbor::Value(3)] = cbor::Value(std::move(allow_list_array));
} }
cbor::Value::MapValue extensions;
if (request.android_client_data_ext) { if (request.android_client_data_ext) {
cbor::Value::MapValue extensions;
extensions.emplace(kExtensionAndroidClientData, extensions.emplace(kExtensionAndroidClientData,
AsCBOR(*request.android_client_data_ext)); AsCBOR(*request.android_client_data_ext));
}
if (request.hmac_secret) {
const auto& hmac_secret = *request.hmac_secret;
cbor::Value::MapValue hmac_extension;
hmac_extension.emplace(
1, pin::EncodeCOSEPublicKey(hmac_secret.public_key_x962));
hmac_extension.emplace(2, hmac_secret.encrypted_salts);
hmac_extension.emplace(3, hmac_secret.salts_auth);
extensions.emplace(kExtensionHmacSecret, std::move(hmac_extension));
}
if (!extensions.empty()) {
cbor_map[cbor::Value(4)] = cbor::Value(std::move(extensions)); cbor_map[cbor::Value(4)] = cbor::Value(std::move(extensions));
} }
......
...@@ -40,6 +40,21 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionRequest { ...@@ -40,6 +40,21 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionRequest {
bool reject_all_extensions = false; bool reject_all_extensions = false;
}; };
// HMACSecret contains the inputs to the hmac-secret extension:
// https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#sctn-hmac-secret-extension
struct HMACSecret {
HMACSecret(base::span<const uint8_t, kP256X962Length> public_key_x962,
base::span<const uint8_t> encrypted_salts,
base::span<const uint8_t> salts_auth);
HMACSecret(const HMACSecret&);
~HMACSecret();
HMACSecret& operator=(const HMACSecret&);
std::array<uint8_t, kP256X962Length> public_key_x962;
std::vector<uint8_t> encrypted_salts;
std::vector<uint8_t> salts_auth;
};
// Decodes a CTAP2 authenticatorGetAssertion request message. The request's // Decodes a CTAP2 authenticatorGetAssertion request message. The request's
// |client_data_json| will be empty and |client_data_hash| will be set. // |client_data_json| will be empty and |client_data_hash| will be set.
// //
...@@ -73,6 +88,7 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionRequest { ...@@ -73,6 +88,7 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionRequest {
base::Optional<std::string> app_id; base::Optional<std::string> app_id;
base::Optional<std::array<uint8_t, crypto::kSHA256Length>> base::Optional<std::array<uint8_t, crypto::kSHA256Length>>
alternative_application_parameter; alternative_application_parameter;
base::Optional<HMACSecret> hmac_secret;
bool is_incognito_mode = false; bool is_incognito_mode = false;
bool is_u2f_only = false; bool is_u2f_only = false;
......
...@@ -36,8 +36,8 @@ static bool HasAtLeastFourCodepoints(const std::string& pin) { ...@@ -36,8 +36,8 @@ static bool HasAtLeastFourCodepoints(const std::string& pin) {
} }
// MakePinAuth returns `LEFT(HMAC-SHA-256(secret, data), 16)`. // MakePinAuth returns `LEFT(HMAC-SHA-256(secret, data), 16)`.
static std::vector<uint8_t> MakePinAuth(base::span<const uint8_t> secret, std::vector<uint8_t> MakePinAuth(base::span<const uint8_t> secret,
base::span<const uint8_t> data) { base::span<const uint8_t> data) {
std::vector<uint8_t> pin_auth; std::vector<uint8_t> pin_auth;
pin_auth.resize(SHA256_DIGEST_LENGTH); pin_auth.resize(SHA256_DIGEST_LENGTH);
unsigned hmac_bytes; unsigned hmac_bytes;
...@@ -203,6 +203,16 @@ base::Optional<KeyAgreementResponse> KeyAgreementResponse::ParseFromCOSE( ...@@ -203,6 +203,16 @@ base::Optional<KeyAgreementResponse> KeyAgreementResponse::ParseFromCOSE(
return ret; return ret;
} }
std::array<uint8_t, kP256X962Length> KeyAgreementResponse::X962() const {
std::array<uint8_t, kP256X962Length> ret;
static_assert(ret.size() == 1 + sizeof(this->x) + sizeof(this->y),
"Bad length for return type");
ret[0] = POINT_CONVERSION_UNCOMPRESSED;
memcpy(&ret[1], this->x, sizeof(this->x));
memcpy(&ret[1 + sizeof(this->x)], this->y, sizeof(this->y));
return ret;
}
SetRequest::SetRequest(const std::string& pin, SetRequest::SetRequest(const std::string& pin,
const KeyAgreementResponse& peer_key) const KeyAgreementResponse& peer_key)
: peer_key_(peer_key) { : peer_key_(peer_key) {
...@@ -235,23 +245,16 @@ void CalculateSharedKey(const EC_KEY* key, ...@@ -235,23 +245,16 @@ void CalculateSharedKey(const EC_KEY* key,
key, SHA256KDF)); key, SHA256KDF));
} }
// EncodeCOSEPublicKey returns the public part of |key| as a COSE structure. // EncodeCOSEPublicKey converts an X9.62 public key into a COSE structure.
cbor::Value::MapValue EncodeCOSEPublicKey(const EC_KEY* key) { cbor::Value::MapValue EncodeCOSEPublicKey(
// X9.62 is the standard for serialising elliptic-curve points. base::span<const uint8_t, kP256X962Length> x962) {
uint8_t x962[1 /* type byte */ + 32 /* x */ + 32 /* y */];
CHECK_EQ(
sizeof(x962),
EC_POINT_point2oct(EC_KEY_get0_group(key), EC_KEY_get0_public_key(key),
POINT_CONVERSION_UNCOMPRESSED, x962, sizeof(x962),
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 */,
-25 /* 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 */, x962.subspan(1, 32));
cose_key.emplace(-3 /* y */, base::span<const uint8_t>(x962 + 33, 32)); cose_key.emplace(-3 /* y */, x962.subspan(33, 32));
return cose_key; return cose_key;
} }
...@@ -259,7 +262,7 @@ cbor::Value::MapValue EncodeCOSEPublicKey(const EC_KEY* key) { ...@@ -259,7 +262,7 @@ cbor::Value::MapValue EncodeCOSEPublicKey(const EC_KEY* key) {
// GenerateSharedKey generates and returns an ephemeral key, and writes the // GenerateSharedKey generates and returns an ephemeral key, and writes the
// shared key between that ephemeral key and the authenticator's ephemeral key // shared key between that ephemeral key and the authenticator's ephemeral key
// (from |peers_key|) to |out_shared_key|. // (from |peers_key|) to |out_shared_key|.
static cbor::Value::MapValue GenerateSharedKey( static std::array<uint8_t, kP256X962Length> GenerateSharedKey(
const KeyAgreementResponse& 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)); bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
...@@ -267,7 +270,14 @@ static cbor::Value::MapValue GenerateSharedKey( ...@@ -267,7 +270,14 @@ static cbor::Value::MapValue GenerateSharedKey(
auto peers_point = auto peers_point =
PointFromKeyAgreementResponse(EC_KEY_get0_group(key.get()), peers_key); PointFromKeyAgreementResponse(EC_KEY_get0_group(key.get()), peers_key);
CalculateSharedKey(key.get(), peers_point->get(), out_shared_key); CalculateSharedKey(key.get(), peers_point->get(), out_shared_key);
return EncodeCOSEPublicKey(key.get()); std::array<uint8_t, kP256X962Length> x962;
CHECK_EQ(x962.size(),
EC_POINT_point2oct(EC_KEY_get0_group(key.get()),
EC_KEY_get0_public_key(key.get()),
POINT_CONVERSION_UNCOMPRESSED, x962.data(),
x962.size(), nullptr /* BN_CTX */));
return x962;
} }
// Encrypt encrypts |plaintext| using |key|, writing the ciphertext to // Encrypt encrypts |plaintext| using |key|, writing the ciphertext to
...@@ -392,7 +402,8 @@ AsCTAPRequestValuePair(const SetRequest& request) { ...@@ -392,7 +402,8 @@ AsCTAPRequestValuePair(const SetRequest& request) {
// See // See
// https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#settingNewPin // 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]; uint8_t shared_key[SHA256_DIGEST_LENGTH];
auto cose_key = GenerateSharedKey(request.peer_key_, shared_key); auto cose_key =
EncodeCOSEPublicKey(GenerateSharedKey(request.peer_key_, shared_key));
static_assert((sizeof(request.pin_) % AES_BLOCK_SIZE) == 0, static_assert((sizeof(request.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");
...@@ -422,7 +433,8 @@ AsCTAPRequestValuePair(const ChangeRequest& request) { ...@@ -422,7 +433,8 @@ AsCTAPRequestValuePair(const ChangeRequest& request) {
// 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 = GenerateSharedKey(request.peer_key_, shared_key); auto cose_key =
EncodeCOSEPublicKey(GenerateSharedKey(request.peer_key_, shared_key));
static_assert((sizeof(request.new_pin_) % AES_BLOCK_SIZE) == 0, static_assert((sizeof(request.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");
...@@ -465,7 +477,7 @@ AsCTAPRequestValuePair(const ResetRequest&) { ...@@ -465,7 +477,7 @@ AsCTAPRequestValuePair(const ResetRequest&) {
} }
TokenRequest::TokenRequest(const KeyAgreementResponse& peer_key) TokenRequest::TokenRequest(const KeyAgreementResponse& peer_key)
: cose_key_(GenerateSharedKey(peer_key, shared_key_.data())) { : public_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());
} }
...@@ -501,7 +513,7 @@ AsCTAPRequestValuePair(const PinTokenRequest& request) { ...@@ -501,7 +513,7 @@ AsCTAPRequestValuePair(const PinTokenRequest& request) {
Subcommand::kGetPINToken, Subcommand::kGetPINToken,
[&request, &encrypted_pin](cbor::Value::MapValue* map) { [&request, &encrypted_pin](cbor::Value::MapValue* map) {
map->emplace(static_cast<int>(RequestKey::kKeyAgreement), map->emplace(static_cast<int>(RequestKey::kKeyAgreement),
std::move(request.cose_key_)); EncodeCOSEPublicKey(request.public_key_));
map->emplace( map->emplace(
static_cast<int>(RequestKey::kPINHashEnc), static_cast<int>(RequestKey::kPINHashEnc),
base::span<const uint8_t>(encrypted_pin, sizeof(encrypted_pin))); base::span<const uint8_t>(encrypted_pin, sizeof(encrypted_pin)));
...@@ -527,7 +539,7 @@ AsCTAPRequestValuePair(const PinTokenWithPermissionsRequest& request) { ...@@ -527,7 +539,7 @@ AsCTAPRequestValuePair(const PinTokenWithPermissionsRequest& request) {
Subcommand::kGetPinUvAuthTokenUsingPinWithPermissions, Subcommand::kGetPinUvAuthTokenUsingPinWithPermissions,
[&request, encrypted_pin](cbor::Value::MapValue* map) { [&request, encrypted_pin](cbor::Value::MapValue* map) {
map->emplace(static_cast<int>(RequestKey::kKeyAgreement), map->emplace(static_cast<int>(RequestKey::kKeyAgreement),
std::move(request.cose_key_)); EncodeCOSEPublicKey(request.public_key_));
map->emplace( map->emplace(
static_cast<int>(RequestKey::kPINHashEnc), static_cast<int>(RequestKey::kPINHashEnc),
base::span<const uint8_t>(encrypted_pin, sizeof(encrypted_pin))); base::span<const uint8_t>(encrypted_pin, sizeof(encrypted_pin)));
...@@ -559,7 +571,7 @@ AsCTAPRequestValuePair(const UvTokenRequest& request) { ...@@ -559,7 +571,7 @@ AsCTAPRequestValuePair(const UvTokenRequest& request) {
return EncodePINCommand( return EncodePINCommand(
Subcommand::kGetUvToken, [&request](cbor::Value::MapValue* map) { Subcommand::kGetUvToken, [&request](cbor::Value::MapValue* map) {
map->emplace(static_cast<int>(RequestKey::kKeyAgreement), map->emplace(static_cast<int>(RequestKey::kKeyAgreement),
std::move(request.cose_key_)); EncodeCOSEPublicKey(request.public_key_));
map->emplace(static_cast<int>(RequestKey::kPermissions), map->emplace(static_cast<int>(RequestKey::kPermissions),
static_cast<uint8_t>(Permissions::kMakeCredential) | static_cast<uint8_t>(Permissions::kMakeCredential) |
static_cast<uint8_t>(Permissions::kGetAssertion)); static_cast<uint8_t>(Permissions::kGetAssertion));
......
...@@ -51,6 +51,10 @@ constexpr size_t kMinBytes = 4; ...@@ -51,6 +51,10 @@ constexpr size_t kMinBytes = 4;
// accept. // accept.
constexpr size_t kMaxBytes = 63; constexpr size_t kMaxBytes = 63;
// EncodeCOSEPublicKey converts an X9.62 public key to a COSE structure.
cbor::Value::MapValue EncodeCOSEPublicKey(
base::span<const uint8_t, kP256X962Length> x962);
// PinRetriesRequest asks an authenticator for the number of remaining PIN // PinRetriesRequest asks an authenticator for the number of remaining PIN
// attempts before the device is locked. // attempts before the device is locked.
struct PinRetriesRequest {}; struct PinRetriesRequest {};
...@@ -93,6 +97,9 @@ struct KeyAgreementResponse { ...@@ -93,6 +97,9 @@ struct KeyAgreementResponse {
static base::Optional<KeyAgreementResponse> ParseFromCOSE( static base::Optional<KeyAgreementResponse> ParseFromCOSE(
const cbor::Value::MapValue& cose_key); const cbor::Value::MapValue& cose_key);
// X962 returns the public key from the response in X9.62 form.
std::array<uint8_t, kP256X962Length> X962() const;
// 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.
uint8_t x[32], y[32]; uint8_t x[32], y[32];
...@@ -163,7 +170,7 @@ class TokenRequest { ...@@ -163,7 +170,7 @@ class TokenRequest {
explicit TokenRequest(const KeyAgreementResponse& peer_key); explicit TokenRequest(const KeyAgreementResponse& peer_key);
~TokenRequest(); ~TokenRequest();
std::array<uint8_t, 32> shared_key_; std::array<uint8_t, 32> shared_key_;
cbor::Value::MapValue cose_key_; std::array<uint8_t, kP256X962Length> public_key_;
}; };
class PinTokenRequest : public TokenRequest { class PinTokenRequest : public TokenRequest {
......
...@@ -65,15 +65,16 @@ base::Optional<bssl::UniquePtr<EC_POINT>> PointFromKeyAgreementResponse( ...@@ -65,15 +65,16 @@ base::Optional<bssl::UniquePtr<EC_POINT>> PointFromKeyAgreementResponse(
const EC_GROUP* group, const EC_GROUP* group,
const KeyAgreementResponse& response); const KeyAgreementResponse& response);
// MakePinAuth returns a PIN protocol v1 authentication tag for |data|.
std::vector<uint8_t> MakePinAuth(base::span<const uint8_t> secret,
base::span<const uint8_t> data);
// CalculateSharedKey writes the CTAP2 shared key between |key| and |peers_key| // CalculateSharedKey writes the CTAP2 shared key between |key| and |peers_key|
// to |out_shared_key|. // to |out_shared_key|.
void CalculateSharedKey(const EC_KEY* key, void CalculateSharedKey(const EC_KEY* key,
const EC_POINT* peers_key, const EC_POINT* peers_key,
uint8_t out_shared_key[SHA256_DIGEST_LENGTH]); 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 // Encrypt encrypts |plaintext| using |key|, writing the ciphertext to
// |out_ciphertext|. |plaintext| must be a whole number of AES blocks. // |out_ciphertext|. |plaintext| must be a whole number of AES blocks.
void Encrypt(const uint8_t key[SHA256_DIGEST_LENGTH], void Encrypt(const uint8_t key[SHA256_DIGEST_LENGTH],
......
...@@ -1031,6 +1031,15 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential( ...@@ -1031,6 +1031,15 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential(
} }
registration.protection = cred_protect; registration.protection = cred_protect;
if (request.hmac_secret) {
registration.hmac_key.emplace();
RAND_bytes(registration.hmac_key->first.data(),
registration.hmac_key->first.size());
RAND_bytes(registration.hmac_key->second.data(),
registration.hmac_key->second.size());
}
StoreNewKey(key_handle, std::move(registration)); StoreNewKey(key_handle, std::move(registration));
return CtapDeviceResponseCode::kSuccess; return CtapDeviceResponseCode::kSuccess;
} }
...@@ -1152,13 +1161,57 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnGetAssertion( ...@@ -1152,13 +1161,57 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnGetAssertion(
return CtapDeviceResponseCode::kCtap2ErrNoCredentials; return CtapDeviceResponseCode::kCtap2ErrNoCredentials;
} }
base::Optional<cbor::Value> extensions; base::Optional<std::array<uint8_t, SHA256_DIGEST_LENGTH>> hmac_shared_key;
cbor::Value::MapValue extensions_map; base::Optional<std::array<uint8_t, 32>> hmac_salt1;
if (config_.add_extra_extension) { base::Optional<std::array<uint8_t, 32>> hmac_salt2;
extensions_map.emplace(cbor::Value("unsolicited"), cbor::Value(42));
} if (request.hmac_secret) {
if (!extensions_map.empty()) { if (!mutable_state()->ecdh_key) {
extensions = cbor::Value(std::move(extensions_map)); // Platform did not fetch the authenticator ECDH key first.
NOTREACHED();
return CtapDeviceResponseCode::kCtap2ErrMissingParameter;
}
const auto& x962 = request.hmac_secret->public_key_x962;
bssl::UniquePtr<EC_GROUP> p256(
EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
bssl::UniquePtr<EC_POINT> platform_point(EC_POINT_new(p256.get()));
if (!EC_POINT_oct2point(p256.get(), platform_point.get(), x962.data(),
x962.size(), /*ctx=*/nullptr)) {
NOTREACHED();
return CtapDeviceResponseCode::kCtap1ErrInvalidParameter;
}
uint8_t shared_key[SHA256_DIGEST_LENGTH];
pin::CalculateSharedKey(mutable_state()->ecdh_key.get(),
platform_point.get(), shared_key);
const auto& encrypted_salts = request.hmac_secret->encrypted_salts;
if (encrypted_salts.size() != 32 && encrypted_salts.size() != 64) {
NOTREACHED();
return CtapDeviceResponseCode::kCtap1ErrInvalidParameter;
}
uint8_t salts[64];
pin::Decrypt(shared_key, encrypted_salts, salts);
if (pin::MakePinAuth(shared_key, encrypted_salts) !=
request.hmac_secret->salts_auth) {
NOTREACHED();
return CtapDeviceResponseCode::kCtap1ErrInvalidParameter;
}
hmac_salt1.emplace();
memcpy(hmac_salt1->data(), salts, hmac_salt1->size());
if (encrypted_salts.size() == 64) {
hmac_salt2.emplace();
memcpy(hmac_salt2->data(), salts + hmac_salt1->size(),
hmac_salt2->size());
}
hmac_shared_key.emplace();
CHECK_EQ(hmac_shared_key->size(), sizeof(shared_key));
memcpy(hmac_shared_key->data(), shared_key, sizeof(shared_key));
} }
// This implementation does not sort credentials by creation time as the spec // This implementation does not sort credentials by creation time as the spec
...@@ -1176,10 +1229,51 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnGetAssertion( ...@@ -1176,10 +1229,51 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnGetAssertion(
registration.second->private_key->GetPublicKey())); registration.second->private_key->GetPublicKey()));
} }
cbor::Value::MapValue extensions_map;
if (config_.add_extra_extension) {
extensions_map.emplace(cbor::Value("unsolicited"), cbor::Value(42));
}
if (hmac_salt1 && registration.second->hmac_key) {
const std::pair<std::array<uint8_t, 32>, std::array<uint8_t, 32>>&
hmac_keys = *registration.second->hmac_key;
const std::array<uint8_t, 32>& hmac_key =
user_verified ? hmac_keys.second : hmac_keys.first;
unsigned hmac_out_length;
uint8_t hmac_result[SHA256_DIGEST_LENGTH];
std::vector<uint8_t> outputs;
HMAC(EVP_sha256(), hmac_key.data(), hmac_key.size(), hmac_salt1->data(),
hmac_salt1->size(), hmac_result, &hmac_out_length);
DCHECK_EQ(hmac_out_length, sizeof(hmac_result));
outputs.insert(outputs.end(), &hmac_result[0],
&hmac_result[sizeof(hmac_result)]);
if (hmac_salt2) {
HMAC(EVP_sha256(), hmac_key.data(), hmac_key.size(), hmac_salt2->data(),
hmac_salt2->size(), hmac_result, &hmac_out_length);
DCHECK_EQ(hmac_out_length, sizeof(hmac_result));
outputs.insert(outputs.end(), &hmac_result[0],
&hmac_result[sizeof(hmac_result)]);
}
std::vector<uint8_t> encrypted_outputs(outputs.size());
pin::Encrypt(hmac_shared_key->data(), outputs, encrypted_outputs.data());
extensions_map.emplace(kExtensionHmacSecret,
std::move(encrypted_outputs));
}
base::Optional<cbor::Value> extensions;
if (!extensions_map.empty()) {
extensions.emplace(std::move(extensions_map));
}
AuthenticatorData authenticator_data( AuthenticatorData authenticator_data(
rp_id_hash, /*user_present=*/true, user_verified, rp_id_hash, /*user_present=*/true, user_verified,
registration.second->counter, std::move(opt_attested_cred_data), registration.second->counter, std::move(opt_attested_cred_data),
extensions ? base::make_optional(extensions->Clone()) : base::nullopt); std::move(extensions));
base::Optional<std::string> opt_android_client_data_json; base::Optional<std::string> opt_android_client_data_json;
if (request.android_client_data_ext && if (request.android_client_data_ext &&
...@@ -1274,12 +1368,6 @@ CtapDeviceResponseCode VirtualCtap2Device::OnGetNextAssertion( ...@@ -1274,12 +1368,6 @@ CtapDeviceResponseCode VirtualCtap2Device::OnGetNextAssertion(
base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand( base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand(
base::span<const uint8_t> request_bytes, base::span<const uint8_t> request_bytes,
std::vector<uint8_t>* response) { std::vector<uint8_t>* response) {
if (device_info_->options.client_pin_availability ==
AuthenticatorSupportedOptions::ClientPinAvailability::kNotSupported &&
!config_.pin_uv_auth_token_support) {
return CtapDeviceResponseCode::kCtap1ErrInvalidCommand;
}
const auto& cbor_request = cbor::Reader::Read(request_bytes); const auto& cbor_request = cbor::Reader::Read(request_bytes);
if (!cbor_request || !cbor_request->is_map()) { if (!cbor_request || !cbor_request->is_map()) {
return CtapDeviceResponseCode::kCtap2ErrCBORUnexpectedType; return CtapDeviceResponseCode::kCtap2ErrCBORUnexpectedType;
...@@ -1303,6 +1391,18 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand( ...@@ -1303,6 +1391,18 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand(
} }
const int64_t subcommand = subcommand_it->second.GetUnsigned(); const int64_t subcommand = subcommand_it->second.GetUnsigned();
if (device_info_->options.client_pin_availability ==
AuthenticatorSupportedOptions::ClientPinAvailability::kNotSupported &&
!config_.pin_uv_auth_token_support &&
// hmac_secret requires the platform to fetch the key-agreement key and
// so, presumably, devices that support it must support at least that
// subcommand of PIN support too.
(!config_.hmac_secret_support ||
subcommand !=
static_cast<int>(device::pin::Subcommand::kGetKeyAgreement))) {
return CtapDeviceResponseCode::kCtap1ErrInvalidCommand;
}
cbor::Value::MapValue response_map; cbor::Value::MapValue response_map;
switch (subcommand) { switch (subcommand) {
case static_cast<int>(device::pin::Subcommand::kGetRetries): case static_cast<int>(device::pin::Subcommand::kGetRetries):
...@@ -1319,8 +1419,15 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand( ...@@ -1319,8 +1419,15 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand(
bssl::UniquePtr<EC_KEY> key( bssl::UniquePtr<EC_KEY> key(
EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
CHECK(EC_KEY_generate_key(key.get())); CHECK(EC_KEY_generate_key(key.get()));
std::array<uint8_t, kP256X962Length> x962;
CHECK_EQ(x962.size(),
EC_POINT_point2oct(EC_KEY_get0_group(key.get()),
EC_KEY_get0_public_key(key.get()),
POINT_CONVERSION_UNCOMPRESSED, x962.data(),
x962.size(), nullptr /* BN_CTX */));
response_map.emplace(static_cast<int>(pin::ResponseKey::kKeyAgreement), response_map.emplace(static_cast<int>(pin::ResponseKey::kKeyAgreement),
pin::EncodeCOSEPublicKey(key.get())); pin::EncodeCOSEPublicKey(x962));
mutable_state()->ecdh_key = std::move(key); mutable_state()->ecdh_key = std::move(key);
break; break;
} }
......
...@@ -108,6 +108,12 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice { ...@@ -108,6 +108,12 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice {
// rp is only valid if |is_resident| is true. // rp is only valid if |is_resident| is true.
base::Optional<device::PublicKeyCredentialRpEntity> rp; base::Optional<device::PublicKeyCredentialRpEntity> rp;
// hmac_key is present iff the credential has the hmac_secret extension
// enabled. The first element of the pair is the HMAC key for non-UV, and
// the second for when UV is used.
base::Optional<std::pair<std::array<uint8_t, 32>, std::array<uint8_t, 32>>>
hmac_key;
DISALLOW_COPY_AND_ASSIGN(RegistrationData); DISALLOW_COPY_AND_ASSIGN(RegistrationData);
}; };
......
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