// 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 #include "base/memory/raw_ptr_exclusion.h" #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 DERFromEC_POINT(const EC_POINT* point) { bssl::UniquePtr ec_key( EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); CHECK(EC_KEY_set_public_key(ec_key.get(), point)); bssl::UniquePtr 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 ret(der_bytes, der_bytes + der_bytes_len); OPENSSL_free(der_bytes); return ret; } } // namespace // static std::unique_ptr P256PublicKey::ExtractFromU2fRegistrationResponse( int32_t algorithm, base::span 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 P256PublicKey::ExtractFromCOSEKey( int32_t algorithm, base::span cbor_bytes, const cbor::Value::MapValue& map) { struct COSEKey { // All the fields below are not a raw_ptr<,,,>, because ELEMENT() treats the // raw_ptr as a void*, skipping AddRef() call and causing a ref-counting // mismatch. RAW_PTR_EXCLUSION const int64_t* kty; RAW_PTR_EXCLUSION const int64_t* crv; RAW_PTR_EXCLUSION const std::vector* x; RAW_PTR_EXCLUSION const std::vector* y; } cose_key; static constexpr cbor_extract::StepOrByte kSteps[] = { // clang-format off ELEMENT(Is::kRequired, COSEKey, kty), IntKey(static_cast(CoseKeyKey::kKty)), ELEMENT(Is::kRequired, COSEKey, crv), IntKey(static_cast(CoseKeyKey::kEllipticCurve)), ELEMENT(Is::kRequired, COSEKey, x), IntKey(static_cast(CoseKeyKey::kEllipticX)), ELEMENT(Is::kRequired, COSEKey, y), IntKey(static_cast(CoseKeyKey::kEllipticY)), Stop(), // clang-format on }; if (!cbor_extract::Extract(&cose_key, kSteps, map) || *cose_key.kty != static_cast(CoseKeyTypes::kEC2) || *cose_key.crv != static_cast(CoseCurves::kP256) || cose_key.x->size() != kFieldElementLength || cose_key.y->size() != kFieldElementLength) { return nullptr; } bssl::UniquePtr x_bn(BN_new()); bssl::UniquePtr 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 p256( EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); bssl::UniquePtr 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(algorithm, cbor_bytes, DERFromEC_POINT(point.get())); } // static std::unique_ptr P256PublicKey::ParseX962Uncompressed( int32_t algorithm, base::span x962) { // Parse into an |EC_POINT| to perform the validity checks that BoringSSL // does. bssl::UniquePtr p256( EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); bssl::UniquePtr 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 x(&x962[1], kFieldElementLength); base::span y( &x962[1 + kFieldElementLength], kFieldElementLength); cbor::Value::MapValue map; map.emplace(static_cast(CoseKeyKey::kKty), static_cast(CoseKeyTypes::kEC2)); map.emplace(static_cast(CoseKeyKey::kAlg), algorithm); map.emplace(static_cast(CoseKeyKey::kEllipticCurve), static_cast(CoseCurves::kP256)); map.emplace(static_cast(CoseKeyKey::kEllipticX), x); map.emplace(static_cast(CoseKeyKey::kEllipticY), y); const std::vector cbor_bytes( std::move(cbor::Writer::Write(cbor::Value(std::move(map))).value())); return std::make_unique(algorithm, cbor_bytes, DERFromEC_POINT(point.get())); } } // namespace device