blob: 402bb6f3d3f1cf48a4512c241013da803f122296 [file] [log] [blame]
// 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_