blob: a6e0a7ffef9cf027de294019b2a03701e19e45b5 [file] [log] [blame]
// Copyright 2017 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/p256_public_key.h"
#include <utility>
#include "components/cbor/writer.h"
#include "components/device_event_log/device_event_log.h"
#include "device/fido/cbor_extract.h"
#include "device/fido/fido_constants.h"
#include "device/fido/public_key.h"
#include "third_party/boringssl/src/include/openssl/bn.h"
#include "third_party/boringssl/src/include/openssl/bytestring.h"
#include "third_party/boringssl/src/include/openssl/ec.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
#include "third_party/boringssl/src/include/openssl/mem.h"
#include "third_party/boringssl/src/include/openssl/obj.h"
using device::cbor_extract::IntKey;
using device::cbor_extract::Is;
using device::cbor_extract::StepOrByte;
using device::cbor_extract::Stop;
namespace device {
// kFieldElementLength is the size of a P-256 field element. The field is
// GF(2^256 - 2^224 + 2^192 + 2^96 - 1) and thus an element is 256 bits, or 32
// bytes.
constexpr size_t kFieldElementLength = 32;
// kUncompressedPointLength is the size of an X9.62 uncompressed point over
// P-256. It's one byte of type information followed by two field elements (x
// and y).
constexpr size_t kUncompressedPointLength = 1 + 2 * kFieldElementLength;
namespace {
// DERFromEC_POINT returns the ASN.1, DER, SubjectPublicKeyInfo for a given
// elliptic-curve point.
static std::vector<uint8_t> DERFromEC_POINT(const EC_POINT* point) {
bssl::UniquePtr<EC_KEY> ec_key(
EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
CHECK(EC_KEY_set_public_key(ec_key.get(), point));
bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new());
CHECK(EVP_PKEY_assign_EC_KEY(pkey.get(), ec_key.release()));
bssl::ScopedCBB cbb;
uint8_t* der_bytes = nullptr;
size_t der_bytes_len = 0;
CHECK(CBB_init(cbb.get(), /* initial size */ 128) &&
EVP_marshal_public_key(cbb.get(), pkey.get()) &&
CBB_finish(cbb.get(), &der_bytes, &der_bytes_len));
std::vector<uint8_t> ret(der_bytes, der_bytes + der_bytes_len);
OPENSSL_free(der_bytes);
return ret;
}
} // namespace
// static
std::unique_ptr<PublicKey> P256PublicKey::ExtractFromU2fRegistrationResponse(
int32_t algorithm,
base::span<const uint8_t> u2f_data) {
// In a U2F registration response, there is first a reserved byte that must be
// ignored. Following that is the rest of the response.
if (u2f_data.size() < 1 + kUncompressedPointLength) {
return nullptr;
}
return ParseX962Uncompressed(algorithm,
u2f_data.subspan(1, kUncompressedPointLength));
}
// static
std::unique_ptr<PublicKey> P256PublicKey::ExtractFromCOSEKey(
int32_t algorithm,
base::span<const uint8_t> cbor_bytes,
const cbor::Value::MapValue& map) {
struct COSEKey {
const int64_t* kty;
const int64_t* crv;
const std::vector<uint8_t>* x;
const std::vector<uint8_t>* y;
} cose_key;
static constexpr cbor_extract::StepOrByte<COSEKey> kSteps[] = {
// clang-format off
ELEMENT(Is::kRequired, COSEKey, kty),
IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kKty)),
ELEMENT(Is::kRequired, COSEKey, crv),
IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kEllipticCurve)),
ELEMENT(Is::kRequired, COSEKey, x),
IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kEllipticX)),
ELEMENT(Is::kRequired, COSEKey, y),
IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kEllipticY)),
Stop<COSEKey>(),
// clang-format on
};
if (!cbor_extract::Extract<COSEKey>(&cose_key, kSteps, map) ||
*cose_key.kty != static_cast<int64_t>(CoseKeyTypes::kEC2) ||
*cose_key.crv != static_cast<int64_t>(CoseCurves::kP256) ||
cose_key.x->size() != kFieldElementLength ||
cose_key.y->size() != kFieldElementLength) {
return nullptr;
}
bssl::UniquePtr<BIGNUM> x_bn(BN_new());
bssl::UniquePtr<BIGNUM> y_bn(BN_new());
if (!BN_bin2bn(cose_key.x->data(), cose_key.x->size(), x_bn.get()) ||
!BN_bin2bn(cose_key.y->data(), cose_key.y->size(), y_bn.get())) {
return nullptr;
}
// Parse into an |EC_POINT| to perform the validity checks that BoringSSL
// does.
bssl::UniquePtr<EC_GROUP> p256(
EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
bssl::UniquePtr<EC_POINT> point(EC_POINT_new(p256.get()));
if (!EC_POINT_set_affine_coordinates_GFp(p256.get(), point.get(), x_bn.get(),
y_bn.get(), /*ctx=*/nullptr)) {
FIDO_LOG(ERROR) << "P-256 public key is not on curve";
return nullptr;
}
return std::make_unique<PublicKey>(algorithm, cbor_bytes,
DERFromEC_POINT(point.get()));
}
// static
std::unique_ptr<PublicKey> P256PublicKey::ParseX962Uncompressed(
int32_t algorithm,
base::span<const uint8_t> x962) {
// Parse into an |EC_POINT| to perform the validity checks that BoringSSL
// does.
bssl::UniquePtr<EC_GROUP> p256(
EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
bssl::UniquePtr<EC_POINT> point(EC_POINT_new(p256.get()));
if (x962.size() != kUncompressedPointLength ||
x962[0] != POINT_CONVERSION_UNCOMPRESSED ||
!EC_POINT_oct2point(p256.get(), point.get(), x962.data(), x962.size(),
/*ctx=*/nullptr)) {
FIDO_LOG(ERROR) << "P-256 public key is not on curve";
return nullptr;
}
base::span<const uint8_t, kFieldElementLength> x(&x962[1],
kFieldElementLength);
base::span<const uint8_t, kFieldElementLength> y(
&x962[1 + kFieldElementLength], kFieldElementLength);
cbor::Value::MapValue map;
map.emplace(static_cast<int>(CoseKeyKey::kKty),
static_cast<int64_t>(CoseKeyTypes::kEC2));
map.emplace(static_cast<int>(CoseKeyKey::kAlg), algorithm);
map.emplace(static_cast<int>(CoseKeyKey::kEllipticCurve),
static_cast<int64_t>(CoseCurves::kP256));
map.emplace(static_cast<int>(CoseKeyKey::kEllipticX), x);
map.emplace(static_cast<int>(CoseKeyKey::kEllipticY), y);
const std::vector<uint8_t> cbor_bytes(
std::move(cbor::Writer::Write(cbor::Value(std::move(map))).value()));
return std::make_unique<PublicKey>(algorithm, cbor_bytes,
DERFromEC_POINT(point.get()));
}
} // namespace device