device/fido: add PIN support to |VirtualCtap2Device|
This will be needed for testing PIN support.
Change-Id: I274226528fe5fd3616d4e81233516e08f66406d9
Reviewed-on: https://chromium-review.googlesource.com/c/1484080
Commit-Queue: Adam Langley <agl@chromium.org>
Auto-Submit: Adam Langley <agl@chromium.org>
Reviewed-by: Martin Kreichgauer <martinkr@google.com>
Cr-Commit-Position: refs/heads/master@{#636113}
diff --git a/content/browser/webauth/scoped_virtual_authenticator_environment.cc b/content/browser/webauth/scoped_virtual_authenticator_environment.cc
index abd4326..a3e43ca 100644
--- a/content/browser/webauth/scoped_virtual_authenticator_environment.cc
+++ b/content/browser/webauth/scoped_virtual_authenticator_environment.cc
@@ -108,8 +108,8 @@
// If no bindings are active then create a virtual device. This is useful
// for web-platform tests which assume that they can make webauthn calls,
// but which don't implement the Chromium-specific mock Mojo interfaces.
- auto device =
- std::make_unique<device::VirtualCtap2Device>(virtual_device_state_);
+ auto device = std::make_unique<device::VirtualCtap2Device>(
+ virtual_device_state_, false /* no PIN support */);
discovery->AddVirtualDevice(std::move(device));
} else {
for (auto& authenticator : authenticators_) {
diff --git a/device/fido/features.cc b/device/fido/features.cc
index ea36b7b..0b623bd 100644
--- a/device/fido/features.cc
+++ b/device/fido/features.cc
@@ -19,4 +19,7 @@
extern const base::Feature kWebAuthProxyCryptotoken{
"WebAuthenticationProxyCryptotoken", base::FEATURE_ENABLED_BY_DEFAULT};
+extern const base::Feature kWebAuthPINSupport{
+ "WebAuthenticationPINSupport", base::FEATURE_DISABLED_BY_DEFAULT};
+
} // namespace device
diff --git a/device/fido/features.h b/device/fido/features.h
index 1ed614c7..00129f1 100644
--- a/device/fido/features.h
+++ b/device/fido/features.h
@@ -20,6 +20,10 @@
COMPONENT_EXPORT(DEVICE_FIDO)
extern const base::Feature kWebAuthProxyCryptotoken;
+// Enable support for PIN-based user-verification.
+COMPONENT_EXPORT(DEVICE_FIDO)
+extern const base::Feature kWebAuthPINSupport;
+
} // namespace device
#endif // DEVICE_FIDO_FEATURES_H_
diff --git a/device/fido/scoped_virtual_fido_device.cc b/device/fido/scoped_virtual_fido_device.cc
index 9aa0c74..3bba1ce2 100644
--- a/device/fido/scoped_virtual_fido_device.cc
+++ b/device/fido/scoped_virtual_fido_device.cc
@@ -26,19 +26,22 @@
explicit VirtualFidoDeviceDiscovery(
FidoTransportProtocol transport,
scoped_refptr<VirtualFidoDevice::State> state,
- ProtocolVersion supported_protocol)
+ ProtocolVersion supported_protocol,
+ bool enable_pin)
: FidoDeviceDiscovery(transport),
state_(std::move(state)),
- supported_protocol_(supported_protocol) {}
+ supported_protocol_(supported_protocol),
+ enable_pin_(enable_pin) {}
~VirtualFidoDeviceDiscovery() override = default;
protected:
void StartInternal() override {
std::unique_ptr<FidoDevice> device;
- if (supported_protocol_ == ProtocolVersion::kCtap)
- device = std::make_unique<VirtualCtap2Device>(state_);
- else
+ if (supported_protocol_ == ProtocolVersion::kCtap) {
+ device = std::make_unique<VirtualCtap2Device>(state_, enable_pin_);
+ } else {
device = std::make_unique<VirtualU2fDevice>(state_);
+ }
AddDevice(std::move(device));
base::SequencedTaskRunnerHandle::Get()->PostTask(
@@ -49,7 +52,8 @@
private:
scoped_refptr<VirtualFidoDevice::State> state_;
- ProtocolVersion supported_protocol_;
+ const ProtocolVersion supported_protocol_;
+ const bool enable_pin_;
DISALLOW_COPY_AND_ASSIGN(VirtualFidoDeviceDiscovery);
};
@@ -66,6 +70,11 @@
transport_ = transport;
}
+void ScopedVirtualFidoDevice::EnablePINSupport() {
+ supported_protocol_ = ProtocolVersion::kCtap;
+ enable_pin_ = true;
+}
+
VirtualFidoDevice::State* ScopedVirtualFidoDevice::mutable_state() {
return state_.get();
}
@@ -76,8 +85,8 @@
if (transport != transport_) {
return nullptr;
}
- return std::make_unique<VirtualFidoDeviceDiscovery>(transport_, state_,
- supported_protocol_);
+ return std::make_unique<VirtualFidoDeviceDiscovery>(
+ transport_, state_, supported_protocol_, enable_pin_);
}
} // namespace test
diff --git a/device/fido/scoped_virtual_fido_device.h b/device/fido/scoped_virtual_fido_device.h
index 24765bec..ab4426f 100644
--- a/device/fido/scoped_virtual_fido_device.h
+++ b/device/fido/scoped_virtual_fido_device.h
@@ -34,6 +34,9 @@
void SetTransport(FidoTransportProtocol transport);
void SetSupportedProtocol(ProtocolVersion supported_protocol);
+ // EnablePINSupport causes the virtual devices to support PINs and sets the
+ // protocol version to CTAP2.
+ void EnablePINSupport();
VirtualFidoDevice::State* mutable_state();
protected:
@@ -45,6 +48,7 @@
ProtocolVersion supported_protocol_ = ProtocolVersion::kU2f;
FidoTransportProtocol transport_ =
FidoTransportProtocol::kUsbHumanInterfaceDevice;
+ bool enable_pin_ = false;
scoped_refptr<VirtualFidoDevice::State> state_;
DISALLOW_COPY_AND_ASSIGN(ScopedVirtualFidoDevice);
};
diff --git a/device/fido/virtual_ctap2_device.cc b/device/fido/virtual_ctap2_device.cc
index ba91245..b42ecd5 100644
--- a/device/fido/virtual_ctap2_device.cc
+++ b/device/fido/virtual_ctap2_device.cc
@@ -23,6 +23,17 @@
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/opaque_attestation_statement.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/digest.h"
+#include "third_party/boringssl/src/include/openssl/ec.h"
+#include "third_party/boringssl/src/include/openssl/ec_key.h"
+#include "third_party/boringssl/src/include/openssl/hmac.h"
+#include "third_party/boringssl/src/include/openssl/mem.h"
+#include "third_party/boringssl/src/include/openssl/obj.h"
+#include "third_party/boringssl/src/include/openssl/rand.h"
+#include "third_party/boringssl/src/include/openssl/sha.h"
namespace device {
@@ -50,6 +61,22 @@
data.value_or(std::vector<uint8_t>{}))));
}
+// CheckPINToken returns true iff |pin_auth| is a valid authentication of
+// |client_data_hash| given that the PIN token in effect is |pin_token|.
+bool CheckPINToken(base::span<const uint8_t> pin_token,
+ base::span<const uint8_t> pin_auth,
+ base::span<const uint8_t> client_data_hash) {
+ uint8_t calculated_pin_auth[SHA256_DIGEST_LENGTH];
+ unsigned hmac_bytes;
+ CHECK(HMAC(EVP_sha256(), pin_token.data(), pin_token.size(),
+ client_data_hash.data(), client_data_hash.size(),
+ calculated_pin_auth, &hmac_bytes));
+ DCHECK_EQ(sizeof(calculated_pin_auth), static_cast<size_t>(hmac_bytes));
+
+ return pin_auth.size() == 16 &&
+ CRYPTO_memcmp(pin_auth.data(), calculated_pin_auth, 16) == 0;
+}
+
// CheckUserVerification implements the first, common steps of
// makeCredential and getAssertion from the CTAP2 spec.
CtapDeviceResponseCode CheckUserVerification(
@@ -57,6 +84,8 @@
const AuthenticatorSupportedOptions& options,
const base::Optional<std::vector<uint8_t>>& pin_auth,
const base::Optional<uint8_t>& pin_protocol,
+ base::span<const uint8_t> pin_token,
+ base::span<const uint8_t> client_data_hash,
UserVerificationRequirement user_verification,
base::RepeatingCallback<void(void)> simulate_press_callback,
bool* out_user_verified) {
@@ -88,7 +117,7 @@
// and the pinProtocol is not supported, return CTAP2_ERR_PIN_AUTH_INVALID
// error."
if (supports_pin && pin_auth && (!pin_protocol || *pin_protocol != 1)) {
- return CtapDeviceResponseCode::kCtap2ErrPinInvalid;
+ return CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid;
}
// 3. "If authenticator is not protected by some form of user verification and
@@ -110,7 +139,9 @@
// Step 4.
bool uv = false;
if (can_do_uv) {
- if (user_verification == UserVerificationRequirement::kRequired) {
+ if (options.user_verification_availability ==
+ AuthenticatorSupportedOptions::UserVerificationAvailability::
+ kSupportedAndConfigured) {
// Internal UV is assumed to always succeed.
if (simulate_press_callback) {
simulate_press_callback.Run();
@@ -118,10 +149,15 @@
uv = true;
}
- if (pin_auth) {
+ if (pin_auth && options.client_pin_availability ==
+ AuthenticatorSupportedOptions::ClientPinAvailability::
+ kSupportedAndPinSet) {
DCHECK(pin_protocol && *pin_protocol == 1);
- // The pin_auth argument is assumed to be correct.
- uv = true;
+ if (CheckPINToken(pin_token, *pin_auth, client_data_hash)) {
+ uv = true;
+ } else {
+ return CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid;
+ }
}
if (is_make_credential && !uv) {
@@ -256,19 +292,149 @@
});
}
+base::Optional<std::vector<uint8_t>> GetPINBytestring(
+ const cbor::Value::MapValue& request,
+ pin::RequestKey key) {
+ const auto it = request.find(cbor::Value(static_cast<int>(key)));
+ if (it == request.end() || !it->second.is_bytestring()) {
+ return base::nullopt;
+ }
+ return it->second.GetBytestring();
+}
+
+base::Optional<bssl::UniquePtr<EC_POINT>> GetPINKey(
+ const cbor::Value::MapValue& request,
+ pin::RequestKey map_key) {
+ const auto it = request.find(cbor::Value(static_cast<int>(map_key)));
+ if (it == request.end() || !it->second.is_map()) {
+ return base::nullopt;
+ }
+ const auto& cose_key = it->second.GetMap();
+ auto response = pin::KeyAgreementResponse::ParseFromCOSE(cose_key);
+ if (!response) {
+ return base::nullopt;
+ }
+
+ bssl::UniquePtr<EC_GROUP> group(
+ EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+ return pin::PointFromKeyAgreementResponse(group.get(), *response).value();
+}
+
+// ConfirmPresentedPIN checks whether |encrypted_pin_hash| is a valid proof-of-
+// possession of the PIN, given that |shared_key| is the result of the ECDH key
+// agreement.
+CtapDeviceResponseCode ConfirmPresentedPIN(
+ VirtualCtap2Device::State* state,
+ const uint8_t shared_key[SHA256_DIGEST_LENGTH],
+ const std::vector<uint8_t>& encrypted_pin_hash) {
+ if (state->retries == 0) {
+ return CtapDeviceResponseCode::kCtap2ErrPinBlocked;
+ }
+ if (state->soft_locked) {
+ return CtapDeviceResponseCode::kCtap2ErrPinAuthBlocked;
+ }
+
+ state->retries--;
+ state->retries_since_insertion++;
+
+ DCHECK((encrypted_pin_hash.size() % AES_BLOCK_SIZE) == 0);
+ uint8_t pin_hash[AES_BLOCK_SIZE];
+ pin::Decrypt(shared_key, encrypted_pin_hash, pin_hash);
+
+ uint8_t calculated_pin_hash[SHA256_DIGEST_LENGTH];
+ SHA256(reinterpret_cast<const uint8_t*>(state->pin.data()), state->pin.size(),
+ calculated_pin_hash);
+
+ if (state->pin.empty() ||
+ CRYPTO_memcmp(pin_hash, calculated_pin_hash, sizeof(pin_hash)) != 0) {
+ if (state->retries == 0) {
+ return CtapDeviceResponseCode::kCtap2ErrPinBlocked;
+ }
+ if (state->retries_since_insertion == 3) {
+ state->soft_locked = true;
+ return CtapDeviceResponseCode::kCtap2ErrPinAuthBlocked;
+ }
+ return CtapDeviceResponseCode::kCtap2ErrPinInvalid;
+ }
+
+ state->retries = 8;
+ state->retries_since_insertion = 0;
+
+ return CtapDeviceResponseCode::kSuccess;
+}
+
+// SetPIN sets the current PIN based on the ciphertext in |encrypted_pin|, given
+// that |shared_key| is the result of the ECDH key agreement.
+CtapDeviceResponseCode SetPIN(VirtualCtap2Device::State* state,
+ const uint8_t shared_key[SHA256_DIGEST_LENGTH],
+ const std::vector<uint8_t>& encrypted_pin,
+ const std::vector<uint8_t>& pin_auth) {
+ // See
+ // https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#settingNewPin
+ uint8_t calculated_pin_auth[SHA256_DIGEST_LENGTH];
+ unsigned hmac_bytes;
+ CHECK(HMAC(EVP_sha256(), shared_key, SHA256_DIGEST_LENGTH,
+ encrypted_pin.data(), encrypted_pin.size(), calculated_pin_auth,
+ &hmac_bytes));
+ DCHECK_EQ(sizeof(calculated_pin_auth), static_cast<size_t>(hmac_bytes));
+
+ if (pin_auth.size() != sizeof(calculated_pin_auth) ||
+ CRYPTO_memcmp(calculated_pin_auth, pin_auth.data(),
+ sizeof(calculated_pin_auth)) != 0) {
+ return CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid;
+ }
+
+ if (encrypted_pin.size() < 64) {
+ return CtapDeviceResponseCode::kCtap2ErrPinPolicyViolation;
+ }
+
+ std::vector<uint8_t> plaintext_pin(encrypted_pin.size());
+ pin::Decrypt(shared_key, encrypted_pin, plaintext_pin.data());
+
+ size_t padding_len = 0;
+ while (padding_len < plaintext_pin.size() &&
+ plaintext_pin[plaintext_pin.size() - padding_len - 1] == 0) {
+ padding_len++;
+ }
+
+ plaintext_pin.resize(plaintext_pin.size() - padding_len);
+ if (padding_len == 0 || plaintext_pin.size() < 4 ||
+ plaintext_pin.size() > 63) {
+ return CtapDeviceResponseCode::kCtap2ErrPinPolicyViolation;
+ }
+
+ state->pin = std::string(reinterpret_cast<const char*>(plaintext_pin.data()),
+ plaintext_pin.size());
+ state->retries = 8;
+
+ return CtapDeviceResponseCode::kSuccess;
+}
+
} // namespace
VirtualCtap2Device::VirtualCtap2Device()
- : VirtualFidoDevice(),
- device_info_(AuthenticatorGetInfoResponse({ProtocolVersion::kCtap},
- kDeviceAaguid)),
- weak_factory_(this) {}
+ : VirtualFidoDevice(), weak_factory_(this) {
+ device_info_ =
+ AuthenticatorGetInfoResponse({ProtocolVersion::kCtap}, kDeviceAaguid);
+}
-VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state)
- : VirtualFidoDevice(std::move(state)),
- device_info_(AuthenticatorGetInfoResponse({ProtocolVersion::kCtap},
- kDeviceAaguid)),
- weak_factory_(this) {}
+VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state,
+ bool enable_pin)
+ : VirtualFidoDevice(std::move(state)), weak_factory_(this) {
+ device_info_ =
+ AuthenticatorGetInfoResponse({ProtocolVersion::kCtap}, kDeviceAaguid);
+ if (enable_pin) {
+ AuthenticatorSupportedOptions options;
+ if (mutable_state()->pin.empty()) {
+ options.client_pin_availability = AuthenticatorSupportedOptions::
+ ClientPinAvailability::kSupportedButPinNotSet;
+ } else {
+ options.client_pin_availability = AuthenticatorSupportedOptions::
+ ClientPinAvailability::kSupportedAndPinSet;
+ }
+ device_info_->SetOptions(options);
+ }
+}
VirtualCtap2Device::~VirtualCtap2Device() = default;
@@ -304,6 +470,9 @@
case CtapRequestCommand::kAuthenticatorGetAssertion:
response_code = OnGetAssertion(request_bytes, &response_data);
break;
+ case CtapRequestCommand::kAuthenticatorClientPin:
+ response_code = OnPINCommand(request_bytes, &response_data);
+ break;
default:
break;
}
@@ -319,7 +488,7 @@
void VirtualCtap2Device::SetAuthenticatorSupportedOptions(
const AuthenticatorSupportedOptions& options) {
- device_info_.SetOptions(options);
+ device_info_->SetOptions(options);
}
CtapDeviceResponseCode VirtualCtap2Device::OnMakeCredential(
@@ -333,13 +502,14 @@
CtapMakeCredentialRequest request = std::get<0>(*request_and_hash);
CtapMakeCredentialRequest::ClientDataHash client_data_hash =
std::get<1>(*request_and_hash);
- const AuthenticatorSupportedOptions& options = device_info_.options();
+ const AuthenticatorSupportedOptions& options = device_info_->options();
bool user_verified;
const CtapDeviceResponseCode uv_error = CheckUserVerification(
true /* is makeCredential */, options, request.pin_auth(),
- request.pin_protocol(), request.user_verification(),
- mutable_state()->simulate_press_callback, &user_verified);
+ request.pin_protocol(), mutable_state()->pin_token, client_data_hash,
+ request.user_verification(), mutable_state()->simulate_press_callback,
+ &user_verified);
if (uv_error != CtapDeviceResponseCode::kSuccess) {
return uv_error;
}
@@ -451,13 +621,14 @@
CtapGetAssertionRequest request = std::get<0>(*request_and_hash);
CtapGetAssertionRequest::ClientDataHash client_data_hash =
std::get<1>(*request_and_hash);
- const AuthenticatorSupportedOptions& options = device_info_.options();
+ const AuthenticatorSupportedOptions& options = device_info_->options();
bool user_verified;
const CtapDeviceResponseCode uv_error = CheckUserVerification(
false /* not makeCredential */, options, request.pin_auth(),
- request.pin_protocol(), request.user_verification(),
- mutable_state()->simulate_press_callback, &user_verified);
+ request.pin_protocol(), mutable_state()->pin_token, client_data_hash,
+ request.user_verification(), mutable_state()->simulate_press_callback,
+ &user_verified);
if (uv_error != CtapDeviceResponseCode::kSuccess) {
return uv_error;
}
@@ -513,9 +684,180 @@
return CtapDeviceResponseCode::kSuccess;
}
+CtapDeviceResponseCode VirtualCtap2Device::OnPINCommand(
+ base::span<const uint8_t> request_bytes,
+ std::vector<uint8_t>* response) {
+ if (device_info_->options().client_pin_availability ==
+ AuthenticatorSupportedOptions::ClientPinAvailability::kNotSupported) {
+ return CtapDeviceResponseCode::kCtap1ErrInvalidCommand;
+ }
+
+ const auto& cbor_request = cbor::Reader::Read(request_bytes);
+ if (!cbor_request || !cbor_request->is_map()) {
+ return CtapDeviceResponseCode::kCtap2ErrCBORUnexpectedType;
+ }
+ const auto& request_map = cbor_request->GetMap();
+
+ const auto protocol_it = request_map.find(
+ cbor::Value(static_cast<int>(pin::RequestKey::kProtocol)));
+ if (protocol_it == request_map.end() || !protocol_it->second.is_unsigned()) {
+ return CtapDeviceResponseCode::kCtap2ErrCBORUnexpectedType;
+ }
+ if (protocol_it->second.GetUnsigned() != pin::kProtocolVersion) {
+ return CtapDeviceResponseCode::kCtap1ErrInvalidCommand;
+ }
+
+ const auto subcommand_it = request_map.find(
+ cbor::Value(static_cast<int>(pin::RequestKey::kSubcommand)));
+ if (subcommand_it == request_map.end() ||
+ !subcommand_it->second.is_unsigned()) {
+ return CtapDeviceResponseCode::kCtap2ErrCBORUnexpectedType;
+ }
+ const int64_t subcommand = subcommand_it->second.GetUnsigned();
+
+ cbor::Value::MapValue response_map;
+ switch (subcommand) {
+ case static_cast<int>(device::pin::Subcommand::kGetRetries):
+ response_map.emplace(static_cast<int>(pin::ResponseKey::kRetries),
+ mutable_state()->retries);
+ break;
+
+ case static_cast<int>(device::pin::Subcommand::kGetKeyAgreement): {
+ bssl::UniquePtr<EC_KEY> key(
+ EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+ CHECK(EC_KEY_generate_key(key.get()));
+ response_map.emplace(static_cast<int>(pin::ResponseKey::kKeyAgreement),
+ pin::EncodeCOSEPublicKey(key.get()));
+ mutable_state()->ecdh_key = std::move(key);
+ break;
+ }
+
+ case static_cast<int>(device::pin::Subcommand::kSetPIN): {
+ const auto encrypted_pin =
+ GetPINBytestring(request_map, pin::RequestKey::kNewPINEnc);
+ const auto pin_auth =
+ GetPINBytestring(request_map, pin::RequestKey::kPINAuth);
+ const auto peer_key =
+ GetPINKey(request_map, pin::RequestKey::kKeyAgreement);
+
+ if (!encrypted_pin || (encrypted_pin->size() % AES_BLOCK_SIZE) != 0 ||
+ !pin_auth || !peer_key) {
+ return CtapDeviceResponseCode::kCtap2ErrMissingParameter;
+ }
+
+ if (!mutable_state()->pin.empty()) {
+ return CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid;
+ }
+
+ uint8_t shared_key[SHA256_DIGEST_LENGTH];
+ if (!mutable_state()->ecdh_key) {
+ // kGetKeyAgreement should have been called first.
+ NOTREACHED();
+ return CtapDeviceResponseCode::kCtap2ErrPinTokenExpired;
+ }
+ pin::CalculateSharedKey(mutable_state()->ecdh_key.get(), peer_key->get(),
+ shared_key);
+
+ CtapDeviceResponseCode err =
+ SetPIN(mutable_state(), shared_key, *encrypted_pin, *pin_auth);
+ if (err != CtapDeviceResponseCode::kSuccess) {
+ return err;
+ };
+
+ AuthenticatorSupportedOptions options = device_info_->options();
+ options.client_pin_availability = AuthenticatorSupportedOptions::
+ ClientPinAvailability::kSupportedAndPinSet;
+ device_info_->SetOptions(options);
+
+ break;
+ }
+
+ case static_cast<int>(device::pin::Subcommand::kChangePIN): {
+ const auto encrypted_new_pin =
+ GetPINBytestring(request_map, pin::RequestKey::kNewPINEnc);
+ const auto encrypted_pin_hash =
+ GetPINBytestring(request_map, pin::RequestKey::kPINHashEnc);
+ const auto pin_auth =
+ GetPINBytestring(request_map, pin::RequestKey::kPINAuth);
+ const auto peer_key =
+ GetPINKey(request_map, pin::RequestKey::kKeyAgreement);
+
+ if (!encrypted_pin_hash || encrypted_pin_hash->size() != AES_BLOCK_SIZE ||
+ !encrypted_new_pin ||
+ (encrypted_new_pin->size() % AES_BLOCK_SIZE) != 0 || !pin_auth ||
+ !peer_key) {
+ return CtapDeviceResponseCode::kCtap2ErrMissingParameter;
+ }
+
+ uint8_t shared_key[SHA256_DIGEST_LENGTH];
+ if (!mutable_state()->ecdh_key) {
+ // kGetKeyAgreement should have been called first.
+ NOTREACHED();
+ return CtapDeviceResponseCode::kCtap2ErrPinTokenExpired;
+ }
+ pin::CalculateSharedKey(mutable_state()->ecdh_key.get(), peer_key->get(),
+ shared_key);
+
+ CtapDeviceResponseCode err =
+ ConfirmPresentedPIN(mutable_state(), shared_key, *encrypted_pin_hash);
+ if (err != CtapDeviceResponseCode::kSuccess) {
+ return err;
+ };
+
+ err = SetPIN(mutable_state(), shared_key, *encrypted_new_pin, *pin_auth);
+ if (err != CtapDeviceResponseCode::kSuccess) {
+ return err;
+ };
+
+ break;
+ }
+
+ case static_cast<int>(device::pin::Subcommand::kGetPINToken): {
+ const auto encrypted_pin_hash =
+ GetPINBytestring(request_map, pin::RequestKey::kPINHashEnc);
+ const auto peer_key =
+ GetPINKey(request_map, pin::RequestKey::kKeyAgreement);
+
+ if (!encrypted_pin_hash || encrypted_pin_hash->size() != AES_BLOCK_SIZE ||
+ !peer_key) {
+ return CtapDeviceResponseCode::kCtap2ErrMissingParameter;
+ }
+
+ uint8_t shared_key[SHA256_DIGEST_LENGTH];
+ if (!mutable_state()->ecdh_key) {
+ // kGetKeyAgreement should have been called first.
+ NOTREACHED();
+ return CtapDeviceResponseCode::kCtap2ErrPinTokenExpired;
+ }
+ pin::CalculateSharedKey(mutable_state()->ecdh_key.get(), peer_key->get(),
+ shared_key);
+
+ CtapDeviceResponseCode err =
+ ConfirmPresentedPIN(mutable_state(), shared_key, *encrypted_pin_hash);
+ if (err != CtapDeviceResponseCode::kSuccess) {
+ return err;
+ };
+
+ RAND_bytes(mutable_state()->pin_token,
+ sizeof(mutable_state()->pin_token));
+ uint8_t encrypted_pin_token[sizeof(mutable_state()->pin_token)];
+ pin::Encrypt(shared_key, mutable_state()->pin_token, encrypted_pin_token);
+ response_map.emplace(static_cast<int>(pin::ResponseKey::kPINToken),
+ base::span<const uint8_t>(encrypted_pin_token));
+ break;
+ }
+
+ default:
+ return CtapDeviceResponseCode::kCtap1ErrInvalidCommand;
+ }
+
+ *response = cbor::Writer::Write(cbor::Value(std::move(response_map))).value();
+ return CtapDeviceResponseCode::kSuccess;
+}
+
CtapDeviceResponseCode VirtualCtap2Device::OnAuthenticatorGetInfo(
std::vector<uint8_t>* response) const {
- *response = EncodeToCBOR(device_info_);
+ *response = EncodeToCBOR(*device_info_);
return CtapDeviceResponseCode::kSuccess;
}
@@ -672,7 +1014,7 @@
pin_protocol_it->second.GetUnsigned() >
std::numeric_limits<uint8_t>::max())
return base::nullopt;
- request.SetPinProtocol(pin_auth_it->second.GetUnsigned());
+ request.SetPinProtocol(pin_protocol_it->second.GetUnsigned());
}
return std::make_pair(std::move(request),
@@ -761,7 +1103,7 @@
pin_protocol_it->second.GetUnsigned() >
std::numeric_limits<uint8_t>::max())
return base::nullopt;
- request.SetPinProtocol(pin_auth_it->second.GetUnsigned());
+ request.SetPinProtocol(pin_protocol_it->second.GetUnsigned());
}
return std::make_pair(std::move(request),
diff --git a/device/fido/virtual_ctap2_device.h b/device/fido/virtual_ctap2_device.h
index 9925fb5..1745026 100644
--- a/device/fido/virtual_ctap2_device.h
+++ b/device/fido/virtual_ctap2_device.h
@@ -30,7 +30,7 @@
: public VirtualFidoDevice {
public:
VirtualCtap2Device();
- explicit VirtualCtap2Device(scoped_refptr<State> state);
+ explicit VirtualCtap2Device(scoped_refptr<State> state, bool enable_pin);
~VirtualCtap2Device() override;
// FidoDevice:
@@ -48,6 +48,9 @@
CtapDeviceResponseCode OnGetAssertion(base::span<const uint8_t> request,
std::vector<uint8_t>* response);
+ CtapDeviceResponseCode OnPINCommand(base::span<const uint8_t> request,
+ std::vector<uint8_t>* response);
+
CtapDeviceResponseCode OnAuthenticatorGetInfo(
std::vector<uint8_t>* response) const;
@@ -58,7 +61,6 @@
base::Optional<AttestedCredentialData> attested_credential_data,
base::Optional<cbor::Value> extensions);
- AuthenticatorGetInfoResponse device_info_;
base::WeakPtrFactory<FidoDevice> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(VirtualCtap2Device);
diff --git a/device/fido/virtual_fido_device.cc b/device/fido/virtual_fido_device.cc
index 4ce0786..38dfa9c 100644
--- a/device/fido/virtual_fido_device.cc
+++ b/device/fido/virtual_fido_device.cc
@@ -10,6 +10,7 @@
#include "crypto/ec_signature_creator.h"
#include "device/fido/fido_parsing_utils.h"
+#include "third_party/boringssl/src/include/openssl/ec_key.h"
namespace device {
diff --git a/device/fido/virtual_fido_device.h b/device/fido/virtual_fido_device.h
index c6cfb191..e24b3e2 100644
--- a/device/fido/virtual_fido_device.h
+++ b/device/fido/virtual_fido_device.h
@@ -23,6 +23,7 @@
#include "device/fido/fido_device.h"
#include "device/fido/fido_parsing_utils.h"
#include "net/cert/x509_util.h"
+#include "third_party/boringssl/src/include/openssl/base.h"
namespace crypto {
class ECPrivateKey;
@@ -89,6 +90,21 @@
// zero, in violation of the rules for self-attestation.
bool non_zero_aaguid_with_self_attestation = false;
+ // Number of PIN retries remaining.
+ int retries = 8;
+ // The number of failed PIN attempts since the token was "inserted".
+ int retries_since_insertion = 0;
+ // True if the token is soft-locked due to too many failed PIN attempts
+ // since "insertion".
+ bool soft_locked = false;
+ // The PIN for the device, or an empty string if no PIN is set.
+ std::string pin;
+ // The elliptic-curve key. (Not expected to be set externally.)
+ bssl::UniquePtr<EC_KEY> ecdh_key;
+ // The random PIN token that is returned as a placeholder for the PIN
+ // itself.
+ uint8_t pin_token[32];
+
FidoTransportProtocol transport =
FidoTransportProtocol::kUsbHumanInterfaceDevice;