blob: d5cc282075d03e0657e8618257cf3d60f8504de2 [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.
#include "device/fido/pin_internal.h"
#include <string>
#include <utility>
#include "base/i18n/char_iterator.h"
#include "base/no_destructor.h"
#include "base/strings/string_util.h"
#include "crypto/random.h"
#include "third_party/boringssl/src/include/openssl/aes.h"
#include "third_party/boringssl/src/include/openssl/bn.h"
#include "third_party/boringssl/src/include/openssl/ec.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/evp.h"
#include "third_party/boringssl/src/include/openssl/hkdf.h"
#include "third_party/boringssl/src/include/openssl/hmac.h"
#include "third_party/boringssl/src/include/openssl/mem.h"
#include "third_party/boringssl/src/include/openssl/sha.h"
namespace device {
namespace pin {
base::Optional<bssl::UniquePtr<EC_POINT>> PointFromKeyAgreementResponse(
const EC_GROUP* group,
const KeyAgreementResponse& response) {
bssl::UniquePtr<EC_POINT> ret(EC_POINT_new(group));
bssl::UniquePtr<BIGNUM> x_bn(BN_new()), y_bn(BN_new());
BN_bin2bn(response.x, sizeof(response.x), x_bn.get());
BN_bin2bn(response.y, sizeof(response.y), y_bn.get());
const bool on_curve =
EC_POINT_set_affine_coordinates_GFp(group, ret.get(), x_bn.get(),
y_bn.get(), nullptr /* ctx */) == 1;
if (!on_curve) {
return base::nullopt;
}
return ret;
}
// ProtocolV1 implements CTAP2.1 PIN/UV Auth Protocol One (6.5.10).
class ProtocolV1 : public Protocol {
private:
static constexpr size_t kSharedKeySize = 32u;
static constexpr size_t kSignatureSize = 16u;
std::array<uint8_t, kP256X962Length> Encapsulate(
const KeyAgreementResponse& peers_key,
std::vector<uint8_t>* out_shared_key) const override {
bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
CHECK(EC_KEY_generate_key(key.get()));
base::Optional<bssl::UniquePtr<EC_POINT>> peers_point =
PointFromKeyAgreementResponse(EC_KEY_get0_group(key.get()), peers_key);
*out_shared_key = CalculateSharedKey(key.get(), peers_point->get());
// KeyAgreementResponse parsing ensures that the point is on the curve.
DCHECK(peers_point);
std::array<uint8_t, kP256X962Length> x962;
CHECK_EQ(x962.size(),
EC_POINT_point2oct(EC_KEY_get0_group(key.get()),
EC_KEY_get0_public_key(key.get()),
POINT_CONVERSION_UNCOMPRESSED, x962.data(),
x962.size(), nullptr /* BN_CTX */));
return x962;
}
std::vector<uint8_t> Encrypt(
base::span<const uint8_t> shared_key,
base::span<const uint8_t> plaintext) const override {
DCHECK_EQ(plaintext.size() % AES_BLOCK_SIZE, 0u);
DCHECK_EQ(shared_key.size(), kSharedKeySize);
std::vector<uint8_t> ciphertext(plaintext.size());
EVP_CIPHER_CTX aes_ctx;
EVP_CIPHER_CTX_init(&aes_ctx);
const uint8_t kZeroIV[AES_BLOCK_SIZE] = {};
CHECK(EVP_EncryptInit_ex(&aes_ctx, EVP_aes_256_cbc(), nullptr,
shared_key.data(), kZeroIV));
CHECK(EVP_CIPHER_CTX_set_padding(&aes_ctx, 0 /* no padding */));
CHECK(EVP_Cipher(&aes_ctx, ciphertext.data(), plaintext.data(),
plaintext.size()));
EVP_CIPHER_CTX_cleanup(&aes_ctx);
return ciphertext;
}
std::vector<uint8_t> Decrypt(
base::span<const uint8_t> shared_key,
base::span<const uint8_t> ciphertext) const override {
DCHECK_EQ(ciphertext.size() % AES_BLOCK_SIZE, 0u);
DCHECK_EQ(shared_key.size(), kSharedKeySize);
std::vector<uint8_t> plaintext(ciphertext.size());
EVP_CIPHER_CTX aes_ctx;
EVP_CIPHER_CTX_init(&aes_ctx);
const uint8_t kZeroIV[AES_BLOCK_SIZE] = {};
CHECK(EVP_DecryptInit_ex(&aes_ctx, EVP_aes_256_cbc(), nullptr,
shared_key.data(), kZeroIV));
CHECK(EVP_CIPHER_CTX_set_padding(&aes_ctx, 0 /* no padding */));
CHECK(EVP_Cipher(&aes_ctx, plaintext.data(), ciphertext.data(),
ciphertext.size()));
EVP_CIPHER_CTX_cleanup(&aes_ctx);
return plaintext;
}
std::vector<uint8_t> Authenticate(
base::span<const uint8_t> key,
base::span<const uint8_t> data) const override {
// Authenticate can be invoked with the shared secret or with a PIN/UV Auth
// Token. In CTAP2.1, V1 tokens are fixed at 16 or 32 bytes. But in CTAP2.0
// they may be any multiple of 16 bytes. We don't know the CTAP version, so
// only enforce the latter.
static_assert(kSharedKeySize == 32u, "");
DCHECK_EQ(key.size() % AES_BLOCK_SIZE, 0u);
std::vector<uint8_t> pin_auth(SHA256_DIGEST_LENGTH);
unsigned hmac_bytes;
CHECK(HMAC(EVP_sha256(), key.data(), key.size(), data.data(), data.size(),
pin_auth.data(), &hmac_bytes));
DCHECK_EQ(pin_auth.size(), static_cast<size_t>(hmac_bytes));
pin_auth.resize(kSignatureSize);
return pin_auth;
}
bool Verify(base::span<const uint8_t> key,
base::span<const uint8_t> data,
base::span<const uint8_t> signature) const override {
if (signature.size() != kSignatureSize) {
return false;
}
const std::vector<uint8_t> computed_signature = Authenticate(key, data);
CHECK_EQ(computed_signature.size(), kSignatureSize);
return CRYPTO_memcmp(signature.data(), computed_signature.data(),
kSignatureSize) == 0;
}
std::vector<uint8_t> CalculateSharedKey(
const EC_KEY* key,
const EC_POINT* peers_key) const override {
std::vector<uint8_t> shared_key(SHA256_DIGEST_LENGTH);
CHECK_EQ(static_cast<int>(SHA256_DIGEST_LENGTH),
ECDH_compute_key(shared_key.data(), shared_key.size(), peers_key,
key, SHA256KDF));
return shared_key;
}
static void* SHA256KDF(const void* in,
size_t in_len,
void* out,
size_t* out_len) {
DCHECK_GE(*out_len, static_cast<size_t>(SHA256_DIGEST_LENGTH));
SHA256(reinterpret_cast<const uint8_t*>(in), in_len,
reinterpret_cast<uint8_t*>(out));
*out_len = SHA256_DIGEST_LENGTH;
return out;
}
};
// ProtocolV2 implements CTAP2.1 PIN/UV Auth Protocol Two (6.5.11).
class ProtocolV2 : public ProtocolV1 {
private:
static constexpr size_t kAESKeyLength = 32;
static constexpr size_t kHMACKeyLength = 32;
static constexpr size_t kSharedKeyLength = kAESKeyLength + kHMACKeyLength;
static constexpr size_t kPINUVAuthTokenLength = 32;
static constexpr size_t kSignatureSize = SHA256_DIGEST_LENGTH;
// GetHMACSubKey returns the HMAC-key portion of the shared secret.
static base::span<const uint8_t, kHMACKeyLength> GetHMACSubKey(
base::span<const uint8_t, kSharedKeyLength> shared_key) {
CHECK_EQ(shared_key.size(), kSharedKeyLength);
return base::make_span<kHMACKeyLength>(
shared_key.subspan(0, kHMACKeyLength));
}
// GetAESSubKey returns the HMAC-key portion of the shared secret.
static base::span<const uint8_t, kAESKeyLength> GetAESSubKey(
base::span<const uint8_t, kSharedKeyLength> shared_key) {
return base::make_span<kAESKeyLength>(shared_key.subspan(kHMACKeyLength));
}
std::vector<uint8_t> Encrypt(
base::span<const uint8_t> shared_key,
base::span<const uint8_t> plaintext) const override {
DCHECK_EQ(plaintext.size() % AES_BLOCK_SIZE, 0u);
const base::span<const uint8_t, kAESKeyLength> aes_key =
GetAESSubKey(base::make_span<kSharedKeyLength>(shared_key));
std::vector<uint8_t> result(AES_BLOCK_SIZE + plaintext.size());
const base::span<uint8_t> iv =
base::make_span(result).subspan(0, AES_BLOCK_SIZE);
const base::span<uint8_t> ciphertext =
base::make_span(result).subspan(AES_BLOCK_SIZE);
crypto::RandBytes(iv);
EVP_CIPHER_CTX aes_ctx;
EVP_CIPHER_CTX_init(&aes_ctx);
CHECK(EVP_EncryptInit_ex(&aes_ctx, EVP_aes_256_cbc(), nullptr,
aes_key.data(), iv.data()));
CHECK(EVP_CIPHER_CTX_set_padding(&aes_ctx, 0 /* no padding */));
CHECK(EVP_Cipher(&aes_ctx, ciphertext.data(), plaintext.data(),
plaintext.size()));
EVP_CIPHER_CTX_cleanup(&aes_ctx);
return result;
}
std::vector<uint8_t> Decrypt(base::span<const uint8_t> shared_key,
base::span<const uint8_t> input) const override {
DCHECK_EQ(input.size() % AES_BLOCK_SIZE, 0u);
const base::span<const uint8_t, kAESKeyLength> aes_key =
GetAESSubKey(base::make_span<kSharedKeyLength>(shared_key));
const base::span<const uint8_t> iv = input.subspan(0, AES_BLOCK_SIZE);
const base::span<const uint8_t> ciphertext = input.subspan(AES_BLOCK_SIZE);
std::vector<uint8_t> plaintext(ciphertext.size());
EVP_CIPHER_CTX aes_ctx;
EVP_CIPHER_CTX_init(&aes_ctx);
CHECK(EVP_DecryptInit_ex(&aes_ctx, EVP_aes_256_cbc(), nullptr,
aes_key.data(), iv.data()));
CHECK(EVP_CIPHER_CTX_set_padding(&aes_ctx, 0 /* no padding */));
CHECK(EVP_Cipher(&aes_ctx, plaintext.data(), ciphertext.data(),
ciphertext.size()));
EVP_CIPHER_CTX_cleanup(&aes_ctx);
return plaintext;
}
std::vector<uint8_t> Authenticate(
base::span<const uint8_t> key,
base::span<const uint8_t> data) const override {
// Authenticate can be invoked with the shared secret or with a PIN/UV Auth
// Token, which is fixed at 32 bytes in V2.
DCHECK(key.size() == kSharedKeyLength ||
key.size() == kPINUVAuthTokenLength);
const base::span<const uint8_t, kHMACKeyLength> hmac_key =
(key.size() == kSharedKeyLength
? GetHMACSubKey(base::make_span<kSharedKeyLength>(key))
: base::make_span<kPINUVAuthTokenLength>(key));
std::vector<uint8_t> pin_auth(SHA256_DIGEST_LENGTH);
unsigned hmac_bytes;
CHECK(HMAC(EVP_sha256(), hmac_key.data(), hmac_key.size(), data.data(),
data.size(), pin_auth.data(), &hmac_bytes));
DCHECK_EQ(pin_auth.size(), static_cast<size_t>(hmac_bytes));
return pin_auth;
}
bool Verify(base::span<const uint8_t> key,
base::span<const uint8_t> data,
base::span<const uint8_t> signature) const override {
if (signature.size() != kSignatureSize) {
return false;
}
const std::vector<uint8_t> computed_signature = Authenticate(key, data);
CHECK_EQ(computed_signature.size(), kSignatureSize);
return CRYPTO_memcmp(signature.data(), computed_signature.data(),
kSignatureSize) == 0;
}
std::vector<uint8_t> CalculateSharedKey(
const EC_KEY* key,
const EC_POINT* peers_key) const override {
std::vector<uint8_t> shared_key(kSharedKeyLength);
CHECK_EQ(static_cast<int>(kSharedKeyLength),
ECDH_compute_key(shared_key.data(), shared_key.size(), peers_key,
key, KDF));
return shared_key;
}
static void* KDF(const void* in, size_t in_len, void* out, size_t* out_len) {
static_assert(kSharedKeyLength == 2 * SHA256_DIGEST_LENGTH, "");
DCHECK_GE(*out_len, static_cast<size_t>(kSharedKeyLength));
auto hmac_key_out =
base::make_span(reinterpret_cast<uint8_t*>(out), SHA256_DIGEST_LENGTH);
auto aes_key_out =
base::make_span(reinterpret_cast<uint8_t*>(out) + SHA256_DIGEST_LENGTH,
SHA256_DIGEST_LENGTH);
constexpr uint8_t kHMACKeyInfo[] = "CTAP2 HMAC key";
constexpr uint8_t kAESKeyInfo[] = "CTAP2 AES key";
constexpr uint8_t kZeroSalt[32] = {};
CHECK(HKDF(hmac_key_out.data(), hmac_key_out.size(), EVP_sha256(),
reinterpret_cast<const uint8_t*>(in), in_len, kZeroSalt,
sizeof(kZeroSalt), kHMACKeyInfo, sizeof(kHMACKeyInfo) - 1));
CHECK(HKDF(aes_key_out.data(), aes_key_out.size(), EVP_sha256(),
reinterpret_cast<const uint8_t*>(in), in_len, kZeroSalt,
sizeof(kZeroSalt), kAESKeyInfo, sizeof(kAESKeyInfo) - 1));
*out_len = kSharedKeyLength;
return out;
}
};
// static
const Protocol& ProtocolVersion(PINUVAuthProtocol protocol) {
static const base::NoDestructor<ProtocolV1> kProtocolV1;
static const base::NoDestructor<ProtocolV2> kProtocolV2;
switch (protocol) {
case PINUVAuthProtocol::kV1:
return *kProtocolV1;
case PINUVAuthProtocol::kV2:
return *kProtocolV2;
}
}
} // namespace pin
} // namespace device