| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef DEVICE_FIDO_VIRTUAL_FIDO_DEVICE_H_ |
| #define DEVICE_FIDO_VIRTUAL_FIDO_DEVICE_H_ |
| |
| #include <stdint.h> |
| |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/component_export.h" |
| #include "base/containers/span.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "device/fido/fido_constants.h" |
| #include "device/fido/fido_device.h" |
| #include "device/fido/fido_parsing_utils.h" |
| #include "device/fido/large_blob.h" |
| #include "device/fido/public_key_credential_descriptor.h" |
| #include "device/fido/public_key_credential_rp_entity.h" |
| #include "device/fido/public_key_credential_user_entity.h" |
| #include "net/cert/x509_util.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/boringssl/src/include/openssl/base.h" |
| |
| namespace crypto { |
| class ECPrivateKey; |
| } |
| |
| namespace device { |
| |
| struct PublicKey; |
| |
| constexpr size_t kMaxPinRetries = 8; |
| |
| constexpr size_t kMaxUvRetries = 5; |
| |
| class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice { |
| public: |
| // PrivateKey abstracts over the private key types supported by the virtual |
| // authenticator. |
| class COMPONENT_EXPORT(DEVICE_FIDO) PrivateKey { |
| public: |
| // FromPKCS8 attempts to parse |pkcs8_private_key| as an ASN.1, DER, PKCS#8 |
| // private key of a supported type and returns a |PrivateKey| instance |
| // representing that key. |
| static absl::optional<std::unique_ptr<PrivateKey>> FromPKCS8( |
| base::span<const uint8_t> pkcs8_private_key); |
| |
| // FreshP256Key returns a randomly generated P-256 PrivateKey. |
| static std::unique_ptr<PrivateKey> FreshP256Key(); |
| |
| // FreshRSAKey returns a randomly generated RSA PrivateKey. |
| static std::unique_ptr<PrivateKey> FreshRSAKey(); |
| |
| // FreshEd25519Key returns a randomly generated Ed25519 PrivateKey. |
| static std::unique_ptr<PrivateKey> FreshEd25519Key(); |
| |
| // FreshInvalidForTestingKey returns a dummy |PrivateKey| with a special |
| // algorithm number that is used to test that unknown public keys are |
| // handled correctly. |
| static std::unique_ptr<PrivateKey> FreshInvalidForTestingKey(); |
| |
| virtual ~PrivateKey(); |
| |
| // Sign returns a signature over |message|. |
| virtual std::vector<uint8_t> Sign(base::span<const uint8_t> message) = 0; |
| |
| // GetX962PublicKey returns the elliptic-curve public key encoded in X9.62 |
| // format. Only elliptic-curve based private keys can be represented in this |
| // format and calling this function on other types of keys will crash. |
| virtual std::vector<uint8_t> GetX962PublicKey() const; |
| |
| // GetPKCS8PrivateKey returns the private key encoded in ASN.1, DER, PKCS#8 |
| // format. |
| virtual std::vector<uint8_t> GetPKCS8PrivateKey() const = 0; |
| |
| virtual std::unique_ptr<PublicKey> GetPublicKey() const = 0; |
| }; |
| |
| // Encapsulates information corresponding to one registered key on the virtual |
| // authenticator device. |
| struct COMPONENT_EXPORT(DEVICE_FIDO) RegistrationData { |
| RegistrationData(); |
| explicit RegistrationData(const std::string& rp_id); |
| RegistrationData( |
| std::unique_ptr<PrivateKey> private_key, |
| base::span<const uint8_t, kRpIdHashLength> application_parameter, |
| uint32_t counter); |
| |
| RegistrationData(RegistrationData&& data); |
| RegistrationData& operator=(RegistrationData&& other); |
| |
| RegistrationData(const RegistrationData&) = delete; |
| RegistrationData& operator=(const RegistrationData&) = delete; |
| |
| ~RegistrationData(); |
| |
| std::unique_ptr<PrivateKey> private_key = PrivateKey::FreshP256Key(); |
| std::array<uint8_t, kRpIdHashLength> application_parameter; |
| uint32_t counter = 0; |
| bool is_resident = false; |
| // is_u2f is true if the credential was created via a U2F interface. |
| bool is_u2f = false; |
| device::CredProtect protection = device::CredProtect::kUVOptional; |
| |
| // user is only valid if |is_resident| is true. |
| absl::optional<device::PublicKeyCredentialUserEntity> user; |
| // rp is only valid if |is_resident| is true. |
| absl::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. |
| absl::optional<std::pair<std::array<uint8_t, 32>, std::array<uint8_t, 32>>> |
| hmac_key; |
| |
| absl::optional<std::array<uint8_t, 32>> large_blob_key; |
| absl::optional<std::vector<uint8_t>> cred_blob; |
| |
| // device_bound_key contains the optional device-bound key for this |
| // credential, thus simulating a multi-device credential. |
| absl::optional<std::unique_ptr<PrivateKey>> device_key; |
| }; |
| |
| // Stores the state of the device. Since |U2fDevice| objects only persist for |
| // the lifetime of a single request, keeping state in an external object is |
| // necessary in order to provide continuity between requests. |
| class COMPONENT_EXPORT(DEVICE_FIDO) State : public base::RefCounted<State> { |
| public: |
| using RegistrationsMap = std::map<std::vector<uint8_t>, |
| RegistrationData, |
| fido_parsing_utils::RangeLess>; |
| using SimulatePressCallback = |
| base::RepeatingCallback<bool(VirtualFidoDevice*)>; |
| |
| State(); |
| |
| State(const State&) = delete; |
| State& operator=(const State&) = delete; |
| |
| // The common name in the attestation certificate. |
| std::string attestation_cert_common_name; |
| |
| // The common name in the attestation certificate if individual attestation |
| // is requested. |
| std::string individual_attestation_cert_common_name; |
| |
| // Registered keys. Keyed on key handle (a.k.a. "credential ID"). |
| RegistrationsMap registrations; |
| |
| // If set, this callback is called whenever a "press" is required. Returning |
| // `true` will simulate a press and continue the request, returning `false` |
| // simulates the user not pressing the device and leaves the request idle. |
| SimulatePressCallback simulate_press_callback; |
| |
| // If true, causes the response from the device to be invalid. |
| bool simulate_invalid_response = false; |
| |
| // If true, return a packed self-attestation rather than a generated |
| // certificate. This only has an effect for a CTAP2 device as |
| // self-attestation is not defined for CTAP1. |
| bool self_attestation = false; |
| |
| // Only valid if |self_attestation| is true. Causes the AAGUID to be non- |
| // zero, in violation of the rules for self-attestation. |
| bool non_zero_aaguid_with_self_attestation = false; |
| |
| // u2f_invalid_signature causes the signature in an assertion response to be |
| // invalid. (U2F only.) |
| bool u2f_invalid_signature = false; |
| |
| // u2f_invalid_public_key causes the public key in a registration response |
| // to be invalid. (U2F only.) |
| bool u2f_invalid_public_key = false; |
| |
| // Number of PIN retries remaining. |
| int pin_retries = kMaxPinRetries; |
| // The number of failed PIN attempts since the token was "inserted". |
| int pin_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]; |
| // The permissions parameter for |pin_token|. |
| uint8_t pin_uv_token_permissions = 0; |
| // The permissions RPID for |pin_token|. |
| absl::optional<std::string> pin_uv_token_rpid; |
| // If true, fail all PinUvAuthToken requests until a new PIN is set. |
| bool force_pin_change = false; |
| // The minimum PIN length as unicode code points. |
| uint32_t min_pin_length = kMinPinLength; |
| |
| // Number of internal UV retries remaining. |
| int uv_retries = kMaxUvRetries; |
| |
| // Whether a device with internal-UV support has fingerprints enrolled. |
| bool fingerprints_enrolled = false; |
| |
| // Whether a device with bio enrollment support has been provisioned. |
| bool bio_enrollment_provisioned = false; |
| |
| // Current template ID being enrolled, if any. |
| absl::optional<uint8_t> bio_current_template_id; |
| |
| // Number of remaining samples in current enrollment. |
| uint8_t bio_remaining_samples = 4; |
| |
| // Backing storage for enrollments and their friendly names. |
| std::map<uint8_t, std::string> bio_templates; |
| |
| // Whether the next authenticatorBioEnrollment command with a |
| // enrollCaptureNextSample subCommand should return a |
| // CTAP2_ENROLL_FEEDBACK_TOO_HIGH response. Will be reset to false upon |
| // returning the error. |
| bool bio_enrollment_next_sample_error = false; |
| |
| // Whether the next authenticatorBioEnrollment command with a |
| // enrollCaptureNextSample subCommand should return a |
| // CTAP2_ENROLL_FEEDBACK_NO_USER_ACTIVITY response. Will be reset to false |
| // upon returning the error. |
| bool bio_enrollment_next_sample_timeout = false; |
| |
| // allow_list_history contains the allow_list values that have been seen in |
| // assertion requests. This is for tests to confirm that the expected |
| // sequence of requests was sent. |
| std::vector<std::vector<PublicKeyCredentialDescriptor>> allow_list_history; |
| |
| // exclude_list_history contains the exclude_list values that have been seen |
| // in registration requests. This is for tests to confirm that the expected |
| // sequence of requests was sent. |
| std::vector<std::vector<PublicKeyCredentialDescriptor>> |
| exclude_list_history; |
| |
| // |cancel_response_code| is the response code the authenticator will return |
| // when cancelling a pending request. Normally authenticators return |
| // CTAP2_ERR_KEEP_ALIVE_CANCEL, but some authenticators incorrectly return |
| // other codes. |
| CtapDeviceResponseCode cancel_response_code = |
| CtapDeviceResponseCode::kCtap2ErrKeepAliveCancel; |
| |
| // The large-blob array. |
| std::vector<uint8_t> large_blob; |
| |
| FidoTransportProtocol transport = |
| FidoTransportProtocol::kUsbHumanInterfaceDevice; |
| |
| // transact_callback contains the outstanding callback in the event that |
| // |simulate_press_callback| returned false. This can be used to inject a |
| // response after simulating an unsatisfied touch for CTAP2 authenticators. |
| FidoDevice::DeviceCallback transact_callback; |
| |
| // device_id_override can be used to inject a return value for `GetId()` in |
| // unit tests where a stable device identifier is required. |
| absl::optional<std::string> device_id_override; |
| |
| // Adds a new credential to the authenticator. Returns true on success, |
| // false if there already exists a credential with the given ID. |
| bool InjectRegistration(base::span<const uint8_t> credential_id, |
| RegistrationData registration); |
| |
| // Adds a registration for the specified credential ID with the application |
| // parameter set to be valid for the given relying party ID (which would |
| // typically be a domain, e.g. "example.com"). |
| // |
| // Returns true on success. Will fail if there already exists a credential |
| // with the given ID or if private-key generation fails. |
| bool InjectRegistration(base::span<const uint8_t> credential_id, |
| const std::string& relying_party_id); |
| |
| // Adds a resident credential with the specified values. |
| // Returns false if there already exists a resident credential for the same |
| // (RP ID, user ID) pair, or for the same credential ID. Otherwise returns |
| // true. |
| bool InjectResidentKey(base::span<const uint8_t> credential_id, |
| device::PublicKeyCredentialRpEntity rp, |
| device::PublicKeyCredentialUserEntity user, |
| int32_t signature_counter, |
| std::unique_ptr<PrivateKey> private_key); |
| |
| // Adds a resident credential with the specified values, creating a new |
| // private key. |
| // Returns false if there already exists a resident credential for the same |
| // (RP ID, user ID) pair, or for the same credential ID. Otherwise returns |
| // true. |
| bool InjectResidentKey(base::span<const uint8_t> credential_id, |
| device::PublicKeyCredentialRpEntity rp, |
| device::PublicKeyCredentialUserEntity user); |
| |
| // Version of InjectResidentKey that takes values for constructing an RP and |
| // user entity. |
| bool InjectResidentKey(base::span<const uint8_t> credential_id, |
| const std::string& relying_party_id, |
| base::span<const uint8_t> user_id, |
| absl::optional<std::string> user_name, |
| absl::optional<std::string> user_display_name); |
| |
| // Returns the large blob associated with the credential, if any. |
| absl::optional<LargeBlob> GetLargeBlob(const RegistrationData& credential); |
| |
| // Injects a large blob for the credential. If the credential already has an |
| // associated large blob, replaces it. If the |large_blob| is malformed, |
| // completely replaces its contents. |
| void InjectLargeBlob(RegistrationData* credential, LargeBlob blob); |
| |
| // Clears all large blobs resetting |large_blob| to its default value. |
| void ClearLargeBlobs(); |
| |
| private: |
| friend class base::RefCounted<State>; |
| ~State(); |
| }; |
| |
| // Constructs an object with ephemeral state. In order to have the state of |
| // the device persist between operations, use the constructor that takes a |
| // scoped_refptr<State>. |
| VirtualFidoDevice(); |
| |
| // Constructs an object that will read from, and write to, |state|. |
| explicit VirtualFidoDevice(scoped_refptr<State> state); |
| |
| VirtualFidoDevice(const VirtualFidoDevice&) = delete; |
| VirtualFidoDevice& operator=(const VirtualFidoDevice&) = delete; |
| |
| ~VirtualFidoDevice() override; |
| |
| State* mutable_state() const { return state_.get(); } |
| |
| // FidoDevice: |
| std::string GetId() const override; |
| |
| protected: |
| static std::vector<uint8_t> GetAttestationKey(); |
| |
| scoped_refptr<State> NewReferenceToState() const { return state_; } |
| |
| static bool Sign(crypto::ECPrivateKey* private_key, |
| base::span<const uint8_t> sign_buffer, |
| std::vector<uint8_t>* signature); |
| |
| // Constructs certificate encoded in X.509 format to be used for packed |
| // attestation statement and FIDO-U2F attestation statement. |
| // https://w3c.github.io/webauthn/#defined-attestation-formats |
| absl::optional<std::vector<uint8_t>> GenerateAttestationCertificate( |
| bool individual_attestation_requested, |
| bool include_transports) const; |
| |
| void StoreNewKey(base::span<const uint8_t> key_handle, |
| VirtualFidoDevice::RegistrationData registration_data); |
| |
| RegistrationData* FindRegistrationData( |
| base::span<const uint8_t> key_handle, |
| base::span<const uint8_t, kRpIdHashLength> application_parameter); |
| |
| // Simulates flashing the device for a press and potentially receiving one. |
| // Returns true if the "user" pressed the device (and the request must |
| // continue) or false if the user didn't, and the request must be dropped. |
| // Internally calls |state_->simulate_press_callback|, so |this| may be |
| // destroyed after calling this method, in which case it will return false. |
| bool SimulatePress(); |
| |
| // FidoDevice: |
| void TryWink(base::OnceClosure cb) override; |
| FidoTransportProtocol DeviceTransport() const override; |
| |
| private: |
| static std::string MakeVirtualFidoDeviceId(); |
| |
| const std::string id_ = MakeVirtualFidoDeviceId(); |
| scoped_refptr<State> state_ = base::MakeRefCounted<State>(); |
| }; |
| |
| } // namespace device |
| |
| #endif // DEVICE_FIDO_VIRTUAL_FIDO_DEVICE_H_ |