| // 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 structures to implement the CTAP2 PIN protocol, version |
| // one. See |
| // https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#authenticatorClientPIN |
| |
| #ifndef DEVICE_FIDO_PIN_H_ |
| #define DEVICE_FIDO_PIN_H_ |
| |
| #include <stdint.h> |
| |
| #include <array> |
| #include <string> |
| #include <vector> |
| |
| #include "base/component_export.h" |
| #include "base/containers/span.h" |
| #include "base/optional.h" |
| #include "components/cbor/values.h" |
| #include "device/fido/fido_constants.h" |
| |
| namespace device { |
| namespace pin { |
| |
| // The reason we are prompting for a new PIN. |
| enum class PINEntryReason { |
| // Indicates a new PIN is being set. |
| kSet, |
| |
| // The existing PIN must be changed before using this authenticator. |
| kChange, |
| |
| // The existing PIN is being collected to prove user verification. |
| kChallenge |
| }; |
| |
| // The errors that may prompt asking for a PIN. |
| enum class PINEntryError { |
| // No error has occurred. |
| kNoError, |
| |
| // Internal UV is locked, so we are falling back to PIN. |
| kInternalUvLocked, |
| |
| // The PIN the user entered does not match the authenticator PIN. |
| kWrongPIN, |
| |
| // The new PIN the user entered is too short. |
| kTooShort, |
| |
| // The new PIN the user entered contains invalid characters. |
| kInvalidCharacters, |
| |
| // The new PIN the user entered is the same as the currently set PIN. |
| kSameAsCurrentPIN, |
| }; |
| |
| // Permission list flags. See |
| // https://drafts.fidoalliance.org/fido-2/stable-links-to-latest/fido-client-to-authenticator-protocol.html#permissions |
| enum class Permissions : uint8_t { |
| kMakeCredential = 0x01, |
| kGetAssertion = 0x02, |
| kCredentialManagement = 0x04, |
| kBioEnrollment = 0x08, |
| kLargeBlobWrite = 0x10, |
| }; |
| |
| // Some commands that validate PinUvAuthTokens include this padding to ensure a |
| // PinUvAuthParam cannot be reused across different commands. |
| constexpr std::array<uint8_t, 32> kPinUvAuthTokenSafetyPadding = { |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; |
| |
| // Validates |pin|, returning |kNoError| if valid or an appropriate error code |
| // otherwise. |
| COMPONENT_EXPORT(DEVICE_FIDO) |
| PINEntryError ValidatePIN( |
| const std::string& pin, |
| uint32_t min_pin_length = kMinPinLength, |
| base::Optional<std::string> current_pin = base::nullopt); |
| |
| // Like |ValidatePIN| above but takes a wide string. |
| COMPONENT_EXPORT(DEVICE_FIDO) |
| PINEntryError ValidatePIN( |
| const base::string16& pin16, |
| uint32_t min_pin_length = kMinPinLength, |
| base::Optional<std::string> current_pin = base::nullopt); |
| |
| // kMinBytes is the minimum number of *bytes* of PIN data that a CTAP2 device |
| // will accept. Since the PIN is UTF-8 encoded, this could be a single code |
| // point. However, the platform is supposed to additionally enforce a 4 |
| // *character* minimum |
| constexpr size_t kMinBytes = 4; |
| // kMaxBytes is the maximum number of bytes of PIN data that a CTAP2 device will |
| // accept. |
| constexpr size_t kMaxBytes = 63; |
| |
| // EncodeCOSEPublicKey converts an X9.62 public key to a COSE structure. |
| COMPONENT_EXPORT(DEVICE_FIDO) |
| cbor::Value::MapValue EncodeCOSEPublicKey( |
| base::span<const uint8_t, kP256X962Length> x962); |
| |
| // PinRetriesRequest asks an authenticator for the number of remaining PIN |
| // attempts before the device is locked. |
| struct PinRetriesRequest { |
| PINUVAuthProtocol protocol; |
| }; |
| |
| // UVRetriesRequest asks an authenticator for the number of internal user |
| // verification attempts before the feature is locked. |
| struct UvRetriesRequest { |
| PINUVAuthProtocol protocol; |
| }; |
| |
| // RetriesResponse reflects an authenticator's response to a |PinRetriesRequest| |
| // or a |UvRetriesRequest|. |
| struct RetriesResponse { |
| static base::Optional<RetriesResponse> ParsePinRetries( |
| const base::Optional<cbor::Value>& cbor); |
| |
| static base::Optional<RetriesResponse> ParseUvRetries( |
| const base::Optional<cbor::Value>& cbor); |
| |
| // retries is the number of PIN attempts remaining before the authenticator |
| // locks. |
| int retries; |
| |
| private: |
| static base::Optional<RetriesResponse> Parse( |
| const base::Optional<cbor::Value>& cbor, |
| const int retries_key); |
| |
| RetriesResponse(); |
| }; |
| |
| // KeyAgreementRequest asks an authenticator for an ephemeral ECDH key for |
| // encrypting PIN material in future requests. |
| struct KeyAgreementRequest { |
| PINUVAuthProtocol protocol; |
| }; |
| |
| // KeyAgreementResponse reflects an authenticator's response to a |
| // |KeyAgreementRequest| and is also used as representation of the |
| // authenticator's ephemeral key. |
| struct COMPONENT_EXPORT(DEVICE_FIDO) KeyAgreementResponse { |
| static base::Optional<KeyAgreementResponse> Parse( |
| const base::Optional<cbor::Value>& cbor); |
| static base::Optional<KeyAgreementResponse> ParseFromCOSE( |
| 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 |
| // that this is a valid point on the curve. |
| uint8_t x[32], y[32]; |
| |
| private: |
| KeyAgreementResponse(); |
| }; |
| |
| // SetRequest sets an initial PIN on an authenticator. (This is distinct from |
| // changing a PIN.) |
| class SetRequest { |
| public: |
| // IsValid(pin) must be true. |
| SetRequest(PINUVAuthProtocol protocol, |
| const std::string& pin, |
| const KeyAgreementResponse& peer_key); |
| |
| friend std::pair<CtapRequestCommand, base::Optional<cbor::Value>> |
| AsCTAPRequestValuePair(const SetRequest&); |
| |
| private: |
| const PINUVAuthProtocol protocol_; |
| const KeyAgreementResponse peer_key_; |
| uint8_t pin_[kMaxBytes + 1]; |
| }; |
| |
| struct EmptyResponse { |
| static base::Optional<EmptyResponse> Parse( |
| const base::Optional<cbor::Value>& cbor); |
| }; |
| |
| // ChangeRequest changes the PIN on an authenticator that already has a PIN set. |
| // (This is distinct from setting an initial PIN.) |
| class ChangeRequest { |
| public: |
| // IsValid(new_pin) must be true. |
| ChangeRequest(PINUVAuthProtocol protocol, |
| const std::string& old_pin, |
| const std::string& new_pin, |
| const KeyAgreementResponse& peer_key); |
| |
| friend std::pair<CtapRequestCommand, base::Optional<cbor::Value>> |
| AsCTAPRequestValuePair(const ChangeRequest&); |
| |
| private: |
| const PINUVAuthProtocol protocol_; |
| const KeyAgreementResponse peer_key_; |
| uint8_t old_pin_hash_[16]; |
| uint8_t new_pin_[kMaxBytes + 1]; |
| }; |
| |
| // ResetRequest resets an authenticator, which should invalidate all |
| // credentials and clear any configured PIN. This is not strictly a |
| // PIN-related command, but is generally used to reset a PIN and so is |
| // included here. |
| struct ResetRequest {}; |
| |
| using ResetResponse = EmptyResponse; |
| |
| // TokenRequest requests a pin-token from an authenticator. These tokens can be |
| // used to show user-verification in other operations, e.g. when getting an |
| // assertion. |
| class TokenRequest { |
| public: |
| TokenRequest(const TokenRequest&) = delete; |
| |
| // shared_key returns the shared ECDH key that was used to encrypt the PIN. |
| // This is needed to decrypt the response. |
| const std::vector<uint8_t>& shared_key() const; |
| |
| protected: |
| TokenRequest(TokenRequest&&); |
| TokenRequest(PINUVAuthProtocol protocol, |
| const KeyAgreementResponse& peer_key); |
| ~TokenRequest(); |
| |
| const PINUVAuthProtocol protocol_; |
| std::vector<uint8_t> shared_key_; |
| std::array<uint8_t, kP256X962Length> public_key_; |
| }; |
| |
| class PinTokenRequest : public TokenRequest { |
| public: |
| PinTokenRequest(PINUVAuthProtocol protocol, |
| const std::string& pin, |
| const KeyAgreementResponse& peer_key); |
| PinTokenRequest(PinTokenRequest&&); |
| PinTokenRequest(const PinTokenRequest&) = delete; |
| virtual ~PinTokenRequest(); |
| |
| friend std::pair<CtapRequestCommand, base::Optional<cbor::Value>> |
| AsCTAPRequestValuePair(const PinTokenRequest&); |
| |
| protected: |
| uint8_t pin_hash_[16]; |
| }; |
| |
| class PinTokenWithPermissionsRequest : public PinTokenRequest { |
| public: |
| PinTokenWithPermissionsRequest(PINUVAuthProtocol protocol, |
| const std::string& pin, |
| const KeyAgreementResponse& peer_key, |
| base::span<const pin::Permissions> permissions, |
| const base::Optional<std::string> rp_id); |
| PinTokenWithPermissionsRequest(PinTokenWithPermissionsRequest&&); |
| PinTokenWithPermissionsRequest(const PinTokenWithPermissionsRequest&) = |
| delete; |
| ~PinTokenWithPermissionsRequest() override; |
| |
| friend std::pair<CtapRequestCommand, base::Optional<cbor::Value>> |
| AsCTAPRequestValuePair(const PinTokenWithPermissionsRequest&); |
| |
| private: |
| uint8_t permissions_; |
| base::Optional<std::string> rp_id_; |
| }; |
| |
| class UvTokenRequest : public TokenRequest { |
| public: |
| UvTokenRequest(PINUVAuthProtocol protocol, |
| const KeyAgreementResponse& peer_key, |
| base::Optional<std::string> rp_id, |
| base::span<const pin::Permissions> permissions); |
| UvTokenRequest(UvTokenRequest&&); |
| UvTokenRequest(const UvTokenRequest&) = delete; |
| virtual ~UvTokenRequest(); |
| |
| friend std::pair<CtapRequestCommand, base::Optional<cbor::Value>> |
| AsCTAPRequestValuePair(const UvTokenRequest&); |
| |
| private: |
| base::Optional<std::string> rp_id_; |
| uint8_t permissions_; |
| }; |
| |
| class HMACSecretRequest { |
| public: |
| HMACSecretRequest(PINUVAuthProtocol protocol, |
| const KeyAgreementResponse& peer_key, |
| base::span<const uint8_t, 32> salt1, |
| const base::Optional<std::array<uint8_t, 32>>& salt2); |
| HMACSecretRequest(const HMACSecretRequest&); |
| ~HMACSecretRequest(); |
| HMACSecretRequest& operator=(const HMACSecretRequest&); |
| |
| base::Optional<std::vector<uint8_t>> Decrypt( |
| base::span<const uint8_t> ciphertext); |
| |
| private: |
| const PINUVAuthProtocol protocol_; |
| std::vector<uint8_t> shared_key_; |
| |
| public: |
| const std::array<uint8_t, kP256X962Length> public_key_x962; |
| const std::vector<uint8_t> encrypted_salts; |
| const std::vector<uint8_t> salts_auth; |
| }; |
| |
| // TokenResponse represents the response to a pin-token request. In order to |
| // decrypt a response, the shared key from the request is needed. Once a pin- |
| // token has been decrypted, it can be used to calculate the pinAuth parameters |
| // needed to show user-verification in future operations. |
| class COMPONENT_EXPORT(DEVICE_FIDO) TokenResponse { |
| public: |
| ~TokenResponse(); |
| TokenResponse(const TokenResponse&); |
| TokenResponse& operator=(const TokenResponse&); |
| |
| static base::Optional<TokenResponse> Parse( |
| PINUVAuthProtocol protocol, |
| base::span<const uint8_t> shared_key, |
| const base::Optional<cbor::Value>& cbor); |
| |
| std::pair<PINUVAuthProtocol, std::vector<uint8_t>> PinAuth( |
| base::span<const uint8_t> client_data_hash) const; |
| |
| PINUVAuthProtocol protocol() const { return protocol_; } |
| const std::vector<uint8_t>& token_for_testing() const { return token_; } |
| |
| private: |
| explicit TokenResponse(PINUVAuthProtocol protocol); |
| |
| PINUVAuthProtocol protocol_; |
| std::vector<uint8_t> token_; |
| }; |
| |
| std::pair<CtapRequestCommand, base::Optional<cbor::Value>> |
| AsCTAPRequestValuePair(const PinRetriesRequest&); |
| |
| std::pair<CtapRequestCommand, base::Optional<cbor::Value>> |
| AsCTAPRequestValuePair(const UvRetriesRequest&); |
| |
| std::pair<CtapRequestCommand, base::Optional<cbor::Value>> |
| AsCTAPRequestValuePair(const KeyAgreementRequest&); |
| |
| std::pair<CtapRequestCommand, base::Optional<cbor::Value>> |
| AsCTAPRequestValuePair(const SetRequest&); |
| |
| std::pair<CtapRequestCommand, base::Optional<cbor::Value>> |
| AsCTAPRequestValuePair(const ChangeRequest&); |
| |
| std::pair<CtapRequestCommand, base::Optional<cbor::Value>> |
| AsCTAPRequestValuePair(const ResetRequest&); |
| |
| std::pair<CtapRequestCommand, base::Optional<cbor::Value>> |
| AsCTAPRequestValuePair(const TokenRequest&); |
| |
| } // namespace pin |
| |
| } // namespace device |
| |
| #endif // DEVICE_FIDO_PIN_H_ |