123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- // Copyright 2020 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.
- #ifndef DEVICE_FIDO_CABLE_V2_HANDSHAKE_H_
- #define DEVICE_FIDO_CABLE_V2_HANDSHAKE_H_
- #include <stdint.h>
- #include <array>
- #include <memory>
- #include "base/component_export.h"
- #include "base/containers/span.h"
- #include "base/strings/string_piece.h"
- #include "components/cbor/values.h"
- #include "device/fido/cable/cable_discovery_data.h"
- #include "device/fido/cable/noise.h"
- #include "device/fido/cable/v2_constants.h"
- #include "device/fido/fido_constants.h"
- #include "third_party/abseil-cpp/absl/types/optional.h"
- #include "third_party/boringssl/src/include/openssl/base.h"
- class GURL;
- namespace device {
- namespace cablev2 {
- namespace tunnelserver {
- // ToKnownDomainID creates a KnownDomainID from a raw 16-bit value, or returns
- // |nullopt| if the value maps to an assigned, but unknown, domain.
- COMPONENT_EXPORT(DEVICE_FIDO)
- absl::optional<KnownDomainID> ToKnownDomainID(uint16_t domain);
- // DecodeDomain converts a 16-bit tunnel server domain into a string in dotted
- // form.
- COMPONENT_EXPORT(DEVICE_FIDO)
- std::string DecodeDomain(KnownDomainID domain);
- // GetNewTunnelURL converts a tunnel server domain and a tunnel ID, into a
- // WebSockets-based URL for creating a new tunnel.
- COMPONENT_EXPORT(DEVICE_FIDO)
- GURL GetNewTunnelURL(KnownDomainID domain, base::span<const uint8_t, 16> id);
- // GetConnectURL converts a tunnel server domain, a routing-ID, and a tunnel ID,
- // into a WebSockets-based URL for connecting to an existing tunnel.
- COMPONENT_EXPORT(DEVICE_FIDO)
- GURL GetConnectURL(KnownDomainID domain,
- std::array<uint8_t, kRoutingIdSize> routing_id,
- base::span<const uint8_t, 16> id);
- // GetContactURL gets a URL for contacting a previously-paired authenticator.
- // The |tunnel_server| is assumed to be a valid domain name and should have been
- // taken from a previous call to |DecodeDomain|.
- COMPONENT_EXPORT(DEVICE_FIDO)
- GURL GetContactURL(const std::string& tunnel_server,
- base::span<const uint8_t> contact_id);
- } // namespace tunnelserver
- namespace eid {
- // Encrypt turns an EID into a BLE advert payload by encrypting and
- // authenticating with |key|.
- COMPONENT_EXPORT(DEVICE_FIDO)
- std::array<uint8_t, kAdvertSize> Encrypt(
- const CableEidArray& eid,
- base::span<const uint8_t, kEIDKeySize> key);
- // Decrypt turns a BLE advert payload into a plaintext EID (suitable for passing
- // to |ToComponents|) by decrypting with |key|. It ensures that the encoded
- // tunnel server domain is recognised.
- COMPONENT_EXPORT(DEVICE_FIDO)
- absl::optional<CableEidArray> Decrypt(
- const std::array<uint8_t, kAdvertSize>& advert,
- base::span<const uint8_t, kEIDKeySize> key);
- // TODO(agl): this could probably be a class.
- // Components contains the parts of a decrypted EID.
- struct COMPONENT_EXPORT(DEVICE_FIDO) Components {
- Components();
- Components(const Components&);
- ~Components();
- tunnelserver::KnownDomainID tunnel_server_domain;
- std::array<uint8_t, kRoutingIdSize> routing_id;
- std::array<uint8_t, kNonceSize> nonce;
- };
- // FromComponents constructs a valid EID from the given components. The result
- // will produce a non-nullopt value if given to |ToComponents|.
- COMPONENT_EXPORT(DEVICE_FIDO)
- CableEidArray FromComponents(const Components& components);
- // ToComponents explodes a decrypted EID into its components. It's the
- // inverse of |FromComponents|. This will CHECK if the |eid| array is invalid;
- // eids from |Decrypt| are always valid.
- COMPONENT_EXPORT(DEVICE_FIDO)
- Components ToComponents(const CableEidArray& eid);
- } // namespace eid
- namespace qr {
- // Components contains the parsed elements of a QR code.
- struct COMPONENT_EXPORT(DEVICE_FIDO) Components {
- std::array<uint8_t, device::kP256X962Length> peer_identity;
- std::array<uint8_t, 16> secret;
- // num_known_domains is the number of registered tunnel server domains known
- // to the device showing the QR code. Authenticators can use this to fallback
- // to a hashed domain if their registered domain isn't going to work with this
- // client.
- int64_t num_known_domains = 0;
- // supports_linking is true if the device showing the QR code supports storing
- // and later using linking information. If this is false or absent, an
- // authenticator may wish to avoid bothering the user about linking.
- absl::optional<bool> supports_linking;
- // request_type contains the hinted type of the request. This can
- // be used to guide UI ahead of receiving the actual request. This defaults to
- // `kGetAssertion` if not present or if the value in the QR code is unknown.
- FidoRequestType request_type = FidoRequestType::kGetAssertion;
- };
- COMPONENT_EXPORT(DEVICE_FIDO)
- absl::optional<Components> Parse(const std::string& qr_url);
- // Encode returns the contents of a QR code that represents |qr_key|.
- COMPONENT_EXPORT(DEVICE_FIDO)
- std::string Encode(base::span<const uint8_t, kQRKeySize> qr_key,
- FidoRequestType request_type);
- // BytesToDigits returns a base-10 encoding of |in|.
- COMPONENT_EXPORT(DEVICE_FIDO)
- std::string BytesToDigits(base::span<const uint8_t> in);
- // DigitsToBytes reverses the actions of |BytesToDigits|.
- COMPONENT_EXPORT(DEVICE_FIDO)
- absl::optional<std::vector<uint8_t>> DigitsToBytes(base::StringPiece in);
- } // namespace qr
- namespace sync {
- // IDNow returns the current pairing ID for Sync. This is a very rough
- // timestamp.
- COMPONENT_EXPORT(DEVICE_FIDO) uint32_t IDNow();
- // IDIsValid returns true iff |candidate| is an acceptable pairing ID. This
- // determination is based on the current time since Sync pairing IDs are
- // timestamps in disguise.
- COMPONENT_EXPORT(DEVICE_FIDO) bool IDIsValid(uint32_t candidate);
- } // namespace sync
- // DerivedValueType enumerates the different types of values that might be
- // derived in caBLEv2 from some secret. The values this this enum are protocol
- // constants and thus must not change over time.
- enum class DerivedValueType : uint32_t {
- kEIDKey = 1,
- kTunnelID = 2,
- kPSK = 3,
- kPairedSecret = 4,
- kIdentityKeySeed = 5,
- kPerContactIDSecret = 6,
- };
- namespace internal {
- COMPONENT_EXPORT(DEVICE_FIDO)
- void Derive(uint8_t* out,
- size_t out_len,
- base::span<const uint8_t> secret,
- base::span<const uint8_t> nonce,
- DerivedValueType type);
- } // namespace internal
- // RequestTypeToString maps |request_type| to either "ga" (for getAssertion) or
- // "mc" (for makeCredential). These strings are encoded in the QR code and
- // client payload to give the phone an early hint about the type of request.
- // This lets it craft better UI.
- COMPONENT_EXPORT(DEVICE_FIDO)
- const char* RequestTypeToString(FidoRequestType request_type);
- // RequestTypeFromString performs the inverse of `RequestTypeToString`. If the
- // value of `s` is unknown, `kGetAssertion` is returned.
- COMPONENT_EXPORT(DEVICE_FIDO)
- FidoRequestType RequestTypeFromString(const std::string& s);
- // Derive derives a sub-secret from a secret and nonce. It is not possible to
- // learn anything about |secret| from the value of the sub-secret, assuming that
- // |secret| has sufficient size to prevent full enumeration of the
- // possibilities.
- template <size_t N>
- std::array<uint8_t, N> Derive(base::span<const uint8_t> secret,
- base::span<const uint8_t> nonce,
- DerivedValueType type) {
- std::array<uint8_t, N> ret;
- internal::Derive(ret.data(), N, secret, nonce, type);
- return ret;
- }
- // IdentityKey returns a P-256 private key derived from |root_secret|.
- COMPONENT_EXPORT(DEVICE_FIDO)
- bssl::UniquePtr<EC_KEY> IdentityKey(base::span<const uint8_t, 32> root_secret);
- // EncodePaddedCBORMap encodes the given map and pads it to
- // |kPostHandshakeMsgPaddingGranularity| bytes in such a way that
- // |DecodePaddedCBORMap| can decode it. The padding is done on the assumption
- // that the returned bytes will be encrypted and the encoded size of the map
- // should be hidden. The function can fail if the CBOR encoding fails or,
- // somehow, the size overflows.
- COMPONENT_EXPORT(DEVICE_FIDO)
- absl::optional<std::vector<uint8_t>> EncodePaddedCBORMap(
- cbor::Value::MapValue map);
- // DecodePaddedCBORMap unpads and decodes a CBOR map as produced by
- // |EncodePaddedCBORMap|.
- COMPONENT_EXPORT(DEVICE_FIDO)
- absl::optional<cbor::Value> DecodePaddedCBORMap(
- base::span<const uint8_t> input);
- // Crypter handles the post-handshake encryption of CTAP2 messages.
- class COMPONENT_EXPORT(DEVICE_FIDO) Crypter {
- public:
- Crypter(base::span<const uint8_t, 32> read_key,
- base::span<const uint8_t, 32> write_key);
- ~Crypter();
- // Encrypt encrypts |message_to_encrypt| and overrides it with the
- // ciphertext. It returns true on success and false on error.
- bool Encrypt(std::vector<uint8_t>* message_to_encrypt);
- // Decrypt decrypts |ciphertext|, which was received as the payload of a
- // message with the given command, and writes the plaintext to
- // |out_plaintext|. It returns true on success and false on error.
- //
- // (In practice, command must always be |kMsg|. But passing it here makes it
- // less likely that other code will forget to check that.)
- bool Decrypt(base::span<const uint8_t> ciphertext,
- std::vector<uint8_t>* out_plaintext);
- // Encrypt and decrypt with big-endian nonces and no additional data. This
- // is the format in the spec and that we want to transition to.
- void UseNewConstruction();
- // IsCounterpartyOfForTesting returns true if |other| is the mirror-image of
- // this object. (I.e. read/write keys are equal but swapped.)
- bool IsCounterpartyOfForTesting(const Crypter& other) const;
- bool& GetNewConstructionFlagForTesting();
- private:
- const std::array<uint8_t, 32> read_key_, write_key_;
- uint32_t read_sequence_num_ = 0;
- uint32_t write_sequence_num_ = 0;
- bool new_construction_ = false;
- };
- // HandshakeHash is the hashed transcript of a handshake. This can be used as a
- // channel-binding value. See
- // http://www.noiseprotocol.org/noise.html#channel-binding.
- using HandshakeHash = std::array<uint8_t, 32>;
- // HandshakeResult is the output of the handshaking process on both sides: a
- // |Crypter| that can encrypt and decrypt future messages on the connection, and
- // the handshake hash that can be used to tie signatures to the connection.
- using HandshakeResult =
- absl::optional<std::pair<std::unique_ptr<Crypter>, HandshakeHash>>;
- // HandshakeInitiator starts a caBLE v2 handshake and processes the single
- // response message from the other party. The handshake is always initiated from
- // the desktop.
- class COMPONENT_EXPORT(DEVICE_FIDO) HandshakeInitiator {
- public:
- HandshakeInitiator(
- // psk is derived from the connection nonce and either QR-code secrets
- // pairing secrets.
- base::span<const uint8_t, 32> psk,
- // peer_identity, if not nullopt, specifies that this is a paired
- // handshake and then contains a P-256 public key for the peer. Otherwise
- // this is a QR handshake.
- absl::optional<base::span<const uint8_t, kP256X962Length>> peer_identity,
- // identity_seed, if not nullopt, specifies that this is a QR handshake
- // and contains the seed for QR key for this client. identity_seed must be
- // provided iff |peer_identity| is not.
- absl::optional<base::span<const uint8_t, kQRSeedSize>> identity_seed);
- ~HandshakeInitiator();
- // BuildInitialMessage returns the handshake message to send to the peer to
- // start a handshake.
- std::vector<uint8_t> BuildInitialMessage();
- // ProcessResponse processes the handshake response from the peer. If
- // successful it returns a |Crypter| for protecting future messages on the
- // connection and a handshake transcript for signing over if needed.
- HandshakeResult ProcessResponse(base::span<const uint8_t> response);
- private:
- Noise noise_;
- std::array<uint8_t, 32> psk_;
- absl::optional<std::array<uint8_t, kP256X962Length>> peer_identity_;
- bssl::UniquePtr<EC_KEY> local_identity_;
- bssl::UniquePtr<EC_KEY> ephemeral_key_;
- };
- // RespondToHandshake responds to a caBLE v2 handshake started by a peer. Since
- // the desktop speaks first in caBLE, this is called by the phone.
- COMPONENT_EXPORT(DEVICE_FIDO)
- HandshakeResult RespondToHandshake(
- // psk is derived from the connection nonce and either QR-code secrets or
- // pairing secrets.
- base::span<const uint8_t, 32> psk,
- // identity, if not nullptr, specifies that this is a paired handshake and
- // contains the phone's private key.
- bssl::UniquePtr<EC_KEY> identity,
- // peer_identity, which must be non-nullopt iff |identity| is nullptr,
- // contains the peer's public key as taken from the QR code.
- absl::optional<base::span<const uint8_t, kP256X962Length>> peer_identity,
- // in contains the initial handshake message from the peer.
- base::span<const uint8_t> in,
- // out_response is set to the response handshake message, if successful.
- std::vector<uint8_t>* out_response);
- // VerifyPairingSignature checks that |signature| is a valid signature of
- // |handshake_hash| by |peer_public_key_x962|. This is used by a phone to prove
- // possession of |peer_public_key_x962| since the |handshake_hash| encloses
- // random values generated by the desktop and thus is a fresh value.
- COMPONENT_EXPORT(DEVICE_FIDO)
- bool VerifyPairingSignature(
- base::span<const uint8_t, kQRSeedSize> identity_seed,
- base::span<const uint8_t, kP256X962Length> peer_public_key_x962,
- base::span<const uint8_t, std::tuple_size<HandshakeHash>::value>
- handshake_hash,
- base::span<const uint8_t> signature);
- // CalculatePairingSignature generates a value that will satisfy
- // |VerifyPairingSignature|.
- COMPONENT_EXPORT(DEVICE_FIDO)
- std::vector<uint8_t> CalculatePairingSignature(
- const EC_KEY* identity_key,
- base::span<const uint8_t, kP256X962Length> peer_public_key_x962,
- base::span<const uint8_t, std::tuple_size<HandshakeHash>::value>
- handshake_hash);
- } // namespace cablev2
- } // namespace device
- #endif // DEVICE_FIDO_CABLE_V2_HANDSHAKE_H_
|