blob: c3e8fa3f55f1dddfd5faebabab178b8021c95064 [file] [log] [blame]
// Copyright 2018 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.
#include "device/fido/cable/fido_cable_handshake_handler.h"
#include <algorithm>
#include <tuple>
#include <utility>
#include "base/bind.h"
#include "base/containers/span.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/cbor/reader.h"
#include "components/cbor/values.h"
#include "components/cbor/writer.h"
#include "components/device_event_log/device_event_log.h"
#include "crypto/aead.h"
#include "crypto/hkdf.h"
#include "crypto/hmac.h"
#include "crypto/random.h"
#include "device/fido/cable/fido_cable_device.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
#include "third_party/boringssl/src/include/openssl/digest.h"
#include "third_party/boringssl/src/include/openssl/ec_key.h"
#include "third_party/boringssl/src/include/openssl/ecdh.h"
#include "third_party/boringssl/src/include/openssl/hkdf.h"
#include "third_party/boringssl/src/include/openssl/obj.h"
#include "third_party/boringssl/src/include/openssl/sha.h"
namespace device {
namespace {
// Length of CBOR encoded authenticator hello message concatenated with
// 16 byte message authentication code.
constexpr size_t kCableAuthenticatorHandshakeMessageSize = 66;
// Length of CBOR encoded client hello message concatenated with 16 byte message
// authenticator code.
constexpr size_t kClientHelloMessageSize = 58;
constexpr size_t kCableHandshakeMacMessageSize = 16;
// Derives key of size 32 bytes using HDKF algorithm.
// See https://tools.ietf.org/html/rfc5869 for details.
std::string GenerateKey(base::StringPiece secret,
base::StringPiece salt,
base::StringPiece info) {
return crypto::HkdfSha256(secret, salt, info, 32);
}
base::Optional<std::array<uint8_t, kClientHelloMessageSize>>
ConstructHandshakeMessage(base::StringPiece handshake_key,
base::span<const uint8_t, 16> client_random_nonce) {
cbor::Value::MapValue map;
map.emplace(0, kCableClientHelloMessage);
map.emplace(1, client_random_nonce);
auto client_hello = cbor::Writer::Write(cbor::Value(std::move(map)));
DCHECK(client_hello);
crypto::HMAC hmac(crypto::HMAC::SHA256);
if (!hmac.Init(handshake_key))
return base::nullopt;
std::array<uint8_t, 32> client_hello_mac;
if (!hmac.Sign(fido_parsing_utils::ConvertToStringPiece(*client_hello),
client_hello_mac.data(), client_hello_mac.size())) {
return base::nullopt;
}
DCHECK_EQ(kClientHelloMessageSize,
client_hello->size() + kCableHandshakeMacMessageSize);
std::array<uint8_t, kClientHelloMessageSize> handshake_message;
std::copy(client_hello->begin(), client_hello->end(),
handshake_message.begin());
std::copy(client_hello_mac.begin(),
client_hello_mac.begin() + kCableHandshakeMacMessageSize,
handshake_message.begin() + client_hello->size());
return handshake_message;
}
} // namespace
FidoCableHandshakeHandler::~FidoCableHandshakeHandler() {}
FidoCableV1HandshakeHandler::FidoCableV1HandshakeHandler(
FidoCableDevice* cable_device,
base::span<const uint8_t, 8> nonce,
base::span<const uint8_t, 32> session_pre_key)
: cable_device_(cable_device),
nonce_(fido_parsing_utils::Materialize(nonce)),
session_pre_key_(fido_parsing_utils::Materialize(session_pre_key)),
handshake_key_(GenerateKey(
fido_parsing_utils::ConvertToStringPiece(session_pre_key_),
fido_parsing_utils::ConvertToStringPiece(nonce_),
kCableHandshakeKeyInfo)) {
crypto::RandBytes(client_session_random_.data(),
client_session_random_.size());
}
FidoCableV1HandshakeHandler::~FidoCableV1HandshakeHandler() = default;
void FidoCableV1HandshakeHandler::InitiateCableHandshake(
FidoDevice::DeviceCallback callback) {
auto handshake_message =
ConstructHandshakeMessage(handshake_key_, client_session_random_);
if (!handshake_message) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), base::nullopt));
return;
}
FIDO_LOG(DEBUG) << "Sending the caBLE handshake message";
cable_device_->SendHandshakeMessage(
fido_parsing_utils::Materialize(*handshake_message), std::move(callback));
}
bool FidoCableV1HandshakeHandler::ValidateAuthenticatorHandshakeMessage(
base::span<const uint8_t> response) {
crypto::HMAC hmac(crypto::HMAC::SHA256);
if (!hmac.Init(handshake_key_))
return false;
if (response.size() != kCableAuthenticatorHandshakeMessageSize) {
return false;
}
const auto authenticator_hello = response.first(
kCableAuthenticatorHandshakeMessageSize - kCableHandshakeMacMessageSize);
if (!hmac.VerifyTruncated(
fido_parsing_utils::ConvertToStringPiece(authenticator_hello),
fido_parsing_utils::ConvertToStringPiece(
response.subspan(authenticator_hello.size())))) {
return false;
}
const auto authenticator_hello_cbor = cbor::Reader::Read(authenticator_hello);
if (!authenticator_hello_cbor || !authenticator_hello_cbor->is_map() ||
authenticator_hello_cbor->GetMap().size() != 2) {
return false;
}
const auto authenticator_hello_msg =
authenticator_hello_cbor->GetMap().find(cbor::Value(0));
if (authenticator_hello_msg == authenticator_hello_cbor->GetMap().end() ||
!authenticator_hello_msg->second.is_string() ||
authenticator_hello_msg->second.GetString() !=
kCableAuthenticatorHelloMessage) {
return false;
}
const auto authenticator_random_nonce =
authenticator_hello_cbor->GetMap().find(cbor::Value(1));
if (authenticator_random_nonce == authenticator_hello_cbor->GetMap().end() ||
!authenticator_random_nonce->second.is_bytestring() ||
authenticator_random_nonce->second.GetBytestring().size() != 16) {
return false;
}
cable_device_->SetEncryptionData(
GetEncryptionKeyAfterSuccessfulHandshake(base::make_span<16>(
authenticator_random_nonce->second.GetBytestring())),
nonce_);
return true;
}
std::string
FidoCableV1HandshakeHandler::GetEncryptionKeyAfterSuccessfulHandshake(
base::span<const uint8_t, 16> authenticator_random_nonce) const {
std::vector<uint8_t> nonce_message;
fido_parsing_utils::Append(&nonce_message, nonce_);
fido_parsing_utils::Append(&nonce_message, client_session_random_);
fido_parsing_utils::Append(&nonce_message, authenticator_random_nonce);
return GenerateKey(
fido_parsing_utils::ConvertToStringPiece(session_pre_key_),
fido_parsing_utils::ConvertToStringPiece(
fido_parsing_utils::CreateSHA256Hash(
fido_parsing_utils::ConvertToStringPiece(nonce_message))),
kCableDeviceEncryptionKeyInfo);
}
FidoCableV2HandshakeHandler::FidoCableV2HandshakeHandler(
FidoCableDevice* cable_device,
base::span<const uint8_t, 32> session_pre_key)
: cable_device_(cable_device),
session_pre_key_(fido_parsing_utils::Materialize(session_pre_key)) {}
FidoCableV2HandshakeHandler::~FidoCableV2HandshakeHandler() {}
namespace {
// P256PointSize is the number of bytes in an X9.62 encoding of a P-256 point.
constexpr size_t P256PointSize = 65;
// HKDF2 implements the functions with the same name from Noise[1], specialized
// to the case where |num_outputs| is two.
//
// [1] https://www.noiseprotocol.org/noise.html#hash-functions
std::tuple<std::array<uint8_t, 32>, std::array<uint8_t, 32>> HKDF2(
base::span<const uint8_t, 32> ck,
base::span<const uint8_t> ikm) {
uint8_t output[32 * 2];
HKDF(output, sizeof(output), EVP_sha256(), ikm.data(), ikm.size(), ck.data(),
ck.size(), /*salt=*/nullptr, 0);
std::array<uint8_t, 32> a, b;
memcpy(a.data(), &output[0], 32);
memcpy(b.data(), &output[32], 32);
return std::make_tuple(a, b);
}
// HKDF3 implements the functions with the same name from Noise[1], specialized
// to the case where |num_outputs| is three.
//
// [1] https://www.noiseprotocol.org/noise.html#hash-functions
std::tuple<std::array<uint8_t, 32>,
std::array<uint8_t, 32>,
std::array<uint8_t, 32>>
HKDF3(base::span<const uint8_t, 32> ck, base::span<const uint8_t> ikm) {
uint8_t output[32 * 3];
HKDF(output, sizeof(output), EVP_sha256(), ikm.data(), ikm.size(), ck.data(),
ck.size(), /*salt=*/nullptr, 0);
std::array<uint8_t, 32> a, b, c;
memcpy(a.data(), &output[0], 32);
memcpy(b.data(), &output[32], 32);
memcpy(c.data(), &output[64], 32);
return std::make_tuple(a, b, c);
}
} // namespace
void FidoCableV2HandshakeHandler::InitiateCableHandshake(
FidoDevice::DeviceCallback callback) {
// See https://www.noiseprotocol.org/noise.html#the-handshakestate-object
static const char kProtocolName[] = "Noise_NNpsk0_P256_AESGCM_SHA256";
static_assert(sizeof(kProtocolName) == crypto::kSHA256Length,
"name may need padding if not HASHLEN bytes long");
static_assert(
std::tuple_size<decltype(chaining_key_)>::value == crypto::kSHA256Length,
"chaining_key_ is wrong size");
static_assert(std::tuple_size<decltype(h_)>::value == crypto::kSHA256Length,
"h_ is wrong size");
memcpy(chaining_key_.data(), kProtocolName, sizeof(kProtocolName));
h_ = chaining_key_;
static const uint8_t kPrologue[] = "caBLE QR code handshake";
MixHash(kPrologue);
MixKeyAndHash(session_pre_key_);
ephemeral_key_.reset(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
CHECK(EC_KEY_generate_key(ephemeral_key_.get()));
uint8_t ephemeral_key_public_bytes[P256PointSize];
CHECK_EQ(sizeof(ephemeral_key_public_bytes),
EC_POINT_point2oct(
EC_KEY_get0_group(ephemeral_key_.get()),
EC_KEY_get0_public_key(ephemeral_key_.get()),
POINT_CONVERSION_UNCOMPRESSED, ephemeral_key_public_bytes,
sizeof(ephemeral_key_public_bytes), /*ctx=*/nullptr));
MixHash(ephemeral_key_public_bytes);
MixKey(ephemeral_key_public_bytes);
std::vector<uint8_t> ciphertext = Encrypt(base::span<const uint8_t>());
MixHash(ciphertext);
std::vector<uint8_t> handshake_message;
handshake_message.reserve(sizeof(ephemeral_key_public_bytes) +
ciphertext.size());
handshake_message.insert(
handshake_message.end(), ephemeral_key_public_bytes,
ephemeral_key_public_bytes + sizeof(ephemeral_key_public_bytes));
handshake_message.insert(handshake_message.end(), ciphertext.begin(),
ciphertext.end());
cable_device_->SendHandshakeMessage(std::move(handshake_message),
std::move(callback));
}
bool FidoCableV2HandshakeHandler::ValidateAuthenticatorHandshakeMessage(
base::span<const uint8_t> response) {
if (response.size() < P256PointSize) {
return false;
}
auto peer_point_bytes = response.subspan(0, P256PointSize);
auto ciphertext = response.subspan(P256PointSize);
bssl::UniquePtr<EC_POINT> peer_point(
EC_POINT_new(EC_KEY_get0_group(ephemeral_key_.get())));
uint8_t shared_key[32];
if (!EC_POINT_oct2point(EC_KEY_get0_group(ephemeral_key_.get()),
peer_point.get(), peer_point_bytes.data(),
peer_point_bytes.size(), /*ctx=*/nullptr) ||
!ECDH_compute_key(shared_key, sizeof(shared_key), peer_point.get(),
ephemeral_key_.get(), /*kdf=*/nullptr)) {
return false;
}
MixHash(peer_point_bytes);
MixKey(peer_point_bytes);
MixKey(shared_key);
auto maybe_plaintext = Decrypt(ciphertext);
if (!maybe_plaintext || !maybe_plaintext->empty()) {
return false;
}
// Here the spec says to do MixHash(ciphertext), but there are no more
// handshake messages so that's moot.
// MixHash(ciphertext);
std::array<uint8_t, 32> key1, unused_key2;
std::tie(key1, unused_key2) =
HKDF2(chaining_key_, base::span<const uint8_t>());
uint8_t zero_nonce[8] = {0};
cable_device_->SetEncryptionData(
std::string(reinterpret_cast<const char*>(key1.data()), key1.size()),
zero_nonce);
return true;
}
void FidoCableV2HandshakeHandler::MixHash(base::span<const uint8_t> in) {
// See https://www.noiseprotocol.org/noise.html#the-symmetricstate-object
SHA256_CTX ctx;
SHA256_Init(&ctx);
SHA256_Update(&ctx, h_.data(), h_.size());
SHA256_Update(&ctx, in.data(), in.size());
SHA256_Final(h_.data(), &ctx);
}
void FidoCableV2HandshakeHandler::MixKey(base::span<const uint8_t> ikm) {
// See https://www.noiseprotocol.org/noise.html#the-symmetricstate-object
std::array<uint8_t, 32> temp_k;
std::tie(chaining_key_, temp_k) = HKDF2(chaining_key_, ikm);
InitializeKey(temp_k);
}
void FidoCableV2HandshakeHandler::MixKeyAndHash(base::span<const uint8_t> ikm) {
// See https://www.noiseprotocol.org/noise.html#the-symmetricstate-object
std::array<uint8_t, 32> temp_h, temp_k;
std::tie(chaining_key_, temp_h, temp_k) = HKDF3(chaining_key_, ikm);
MixHash(temp_h);
InitializeKey(temp_k);
}
void FidoCableV2HandshakeHandler::InitializeKey(
base::span<const uint8_t, 32> key) {
// See https://www.noiseprotocol.org/noise.html#the-cipherstate-object
DCHECK_EQ(symmetric_key_.size(), key.size());
memcpy(symmetric_key_.data(), key.data(), symmetric_key_.size());
symmetric_nonce_ = 0;
}
std::vector<uint8_t> FidoCableV2HandshakeHandler::Encrypt(
base::span<const uint8_t> plaintext) {
uint8_t nonce[12] = {0};
nonce[8] = symmetric_nonce_ >> 24;
nonce[9] = symmetric_nonce_ >> 16;
nonce[10] = symmetric_nonce_ >> 8;
nonce[11] = symmetric_nonce_;
symmetric_nonce_++;
crypto::Aead aead(crypto::Aead::AES_256_GCM);
aead.Init(symmetric_key_);
return aead.Seal(base::span<const uint8_t>(), nonce, h_);
}
base::Optional<std::vector<uint8_t>> FidoCableV2HandshakeHandler::Decrypt(
base::span<const uint8_t> ciphertext) {
uint8_t nonce[12] = {0};
nonce[8] = symmetric_nonce_ >> 24;
nonce[9] = symmetric_nonce_ >> 16;
nonce[10] = symmetric_nonce_ >> 8;
nonce[11] = symmetric_nonce_;
symmetric_nonce_++;
crypto::Aead aead(crypto::Aead::AES_256_GCM);
aead.Init(symmetric_key_);
return aead.Open(ciphertext, nonce, h_);
}
} // namespace device