blob: a199eb89d4a6b227cabb2c88c704407c3fc4079d [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "device/fido/attested_credential_data.h"
#include <utility>
#include "base/memory/raw_ptr_exclusion.h"
#include "base/numerics/safe_math.h"
#include "base/ranges/algorithm.h"
#include "components/cbor/reader.h"
#include "components/device_event_log/device_event_log.h"
#include "device/fido/cbor_extract.h"
#include "device/fido/ed25519_public_key.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/p256_public_key.h"
#include "device/fido/public_key.h"
#include "device/fido/rsa_public_key.h"
using device::cbor_extract::IntKey;
using device::cbor_extract::Is;
using device::cbor_extract::StepOrByte;
using device::cbor_extract::Stop;
namespace device {
// static
absl::optional<std::pair<AttestedCredentialData, base::span<const uint8_t>>>
AttestedCredentialData::ConsumeFromCtapResponse(
base::span<const uint8_t> buffer) {
if (buffer.size() < kAaguidLength)
return absl::nullopt;
auto aaguid = buffer.first<kAaguidLength>();
buffer = buffer.subspan(kAaguidLength);
if (buffer.size() < kCredentialIdLengthLength)
return absl::nullopt;
auto credential_id_length_span = buffer.first<kCredentialIdLengthLength>();
const size_t credential_id_length =
(base::strict_cast<size_t>(credential_id_length_span[0]) << 8) |
base::strict_cast<size_t>(credential_id_length_span[1]);
buffer = buffer.subspan(kCredentialIdLengthLength);
if (buffer.size() < credential_id_length)
return absl::nullopt;
auto credential_id = buffer.first(credential_id_length);
buffer = buffer.subspan(credential_id_length);
size_t public_key_byte_len;
absl::optional<cbor::Value> public_key_cbor =
cbor::Reader::Read(buffer, &public_key_byte_len);
if (!public_key_cbor || !public_key_cbor->is_map()) {
FIDO_LOG(ERROR) << "CBOR error in COSE public key";
return absl::nullopt;
}
const base::span<const uint8_t> public_key_cbor_bytes(
buffer.first(public_key_byte_len));
buffer = buffer.subspan(public_key_byte_len);
const cbor::Value::MapValue& public_key_map = public_key_cbor->GetMap();
struct COSEKey {
// Both fields below are not a raw_ptr<int64_t>, because ELEMENT() treats
// the raw_ptr<T> as a void*, skipping AddRef() call and causing a
// ref-counting mismatch.
RAW_PTR_EXCLUSION const int64_t* alg;
RAW_PTR_EXCLUSION const int64_t* kty;
} cose_key;
static constexpr cbor_extract::StepOrByte<COSEKey> kSteps[] = {
// clang-format off
// kAlg is required to be present. See
// https://www.w3.org/TR/webauthn/#credentialpublickey
// COSE allows it to be a string or an integer. However, WebAuthn defines
// COSEAlgorithmIdentifier to be a long[1], thus only integer-based
// algorithms can be negotiated.
//
// [1] https://www.w3.org/TR/webauthn/#alg-identifier
ELEMENT(Is::kRequired, COSEKey, alg),
IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kAlg)),
// kKty is required in COSE keys:
// https://tools.ietf.org/html/rfc8152#section-7
ELEMENT(Is::kRequired, COSEKey, kty),
IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kKty)),
Stop<COSEKey>(),
// clang-format on
};
if (!cbor_extract::Extract<COSEKey>(&cose_key, kSteps, public_key_map)) {
FIDO_LOG(ERROR) << "Failed to parse COSE key";
return absl::nullopt;
}
// In WebIDL, a |long| is an |int32_t|[1].
//
// [1] https://heycam.github.io/webidl/#idl-long
const int64_t algorithm64 = *cose_key.alg;
if (algorithm64 > std::numeric_limits<int32_t>::max() ||
algorithm64 < std::numeric_limits<int32_t>::min()) {
FIDO_LOG(ERROR) << "COSE algorithm in public key is out of range";
return absl::nullopt;
}
const int32_t algorithm = static_cast<int32_t>(algorithm64);
const int64_t key_type = *cose_key.kty;
std::unique_ptr<PublicKey> public_key;
if (key_type == static_cast<int64_t>(CoseKeyTypes::kEC2) ||
key_type == static_cast<int64_t>(CoseKeyTypes::kOKP)) {
auto curve = public_key_map.find(
cbor::Value(static_cast<int64_t>(CoseKeyKey::kEllipticCurve)));
if (curve == public_key_map.end() || !curve->second.is_integer()) {
return absl::nullopt;
}
const int64_t curve_id = curve->second.GetInteger();
if (key_type == static_cast<int64_t>(CoseKeyTypes::kEC2) &&
curve_id == static_cast<int64_t>(CoseCurves::kP256)) {
auto p256_key = P256PublicKey::ExtractFromCOSEKey(
algorithm, public_key_cbor_bytes, public_key_map);
if (!p256_key) {
FIDO_LOG(ERROR) << "Invalid P-256 public key";
return absl::nullopt;
}
public_key = std::move(p256_key);
} else if (key_type == static_cast<int64_t>(CoseKeyTypes::kOKP) &&
curve_id == static_cast<int64_t>(CoseCurves::kEd25519)) {
auto ed25519_key = Ed25519PublicKey::ExtractFromCOSEKey(
algorithm, public_key_cbor_bytes, public_key_map);
if (!ed25519_key) {
FIDO_LOG(ERROR) << "Invalid Ed25519 public key";
return absl::nullopt;
}
public_key = std::move(ed25519_key);
}
} else if (key_type == static_cast<int64_t>(CoseKeyTypes::kRSA)) {
auto rsa_key = RSAPublicKey::ExtractFromCOSEKey(
algorithm, public_key_cbor_bytes, public_key_map);
if (!rsa_key) {
FIDO_LOG(ERROR) << "Invalid RSA public key";
return absl::nullopt;
}
public_key = std::move(rsa_key);
}
if (!public_key) {
public_key = std::make_unique<PublicKey>(algorithm, public_key_cbor_bytes,
absl::nullopt);
}
return std::make_pair(
AttestedCredentialData(aaguid, credential_id_length_span,
fido_parsing_utils::Materialize(credential_id),
std::move(public_key)),
buffer);
}
// static
absl::optional<AttestedCredentialData>
AttestedCredentialData::CreateFromU2fRegisterResponse(
base::span<const uint8_t> u2f_data,
std::unique_ptr<PublicKey> public_key) {
// TODO(crbug/799075): Introduce a CredentialID class to do this extraction.
// Extract the length of the credential (i.e. of the U2FResponse key
// handle). Length is big endian.
std::vector<uint8_t> extracted_length =
fido_parsing_utils::Extract(u2f_data, kU2fKeyHandleLengthOffset, 1);
if (extracted_length.empty()) {
return absl::nullopt;
}
// For U2F register request, device AAGUID is set to zeros.
std::array<uint8_t, kAaguidLength> aaguid = {};
// Note that U2F responses only use one byte for length.
std::array<uint8_t, kCredentialIdLengthLength> credential_id_length = {
0, extracted_length[0]};
// Extract the credential id (i.e. key handle).
std::vector<uint8_t> credential_id = fido_parsing_utils::Extract(
u2f_data, kU2fKeyHandleOffset,
base::strict_cast<size_t>(credential_id_length[1]));
if (credential_id.empty()) {
return absl::nullopt;
}
return AttestedCredentialData(aaguid, credential_id_length,
std::move(credential_id),
std::move(public_key));
}
AttestedCredentialData::AttestedCredentialData(AttestedCredentialData&& other) =
default;
AttestedCredentialData::AttestedCredentialData(
base::span<const uint8_t, kAaguidLength> aaguid,
base::span<const uint8_t, kCredentialIdLengthLength> credential_id_length,
std::vector<uint8_t> credential_id,
std::unique_ptr<PublicKey> public_key)
: aaguid_(fido_parsing_utils::Materialize(aaguid)),
credential_id_length_(
fido_parsing_utils::Materialize(credential_id_length)),
credential_id_(std::move(credential_id)),
public_key_(std::move(public_key)) {}
AttestedCredentialData& AttestedCredentialData::operator=(
AttestedCredentialData&& other) = default;
AttestedCredentialData::~AttestedCredentialData() = default;
bool AttestedCredentialData::IsAaguidZero() const {
return base::ranges::all_of(aaguid_, [](uint8_t v) { return v == 0; });
}
bool AttestedCredentialData::DeleteAaguid() {
if (IsAaguidZero()) {
return false;
}
std::fill(aaguid_.begin(), aaguid_.end(), 0);
return true;
}
std::vector<uint8_t> AttestedCredentialData::SerializeAsBytes() const {
std::vector<uint8_t> attestation_data;
fido_parsing_utils::Append(&attestation_data, aaguid_);
fido_parsing_utils::Append(&attestation_data, credential_id_length_);
fido_parsing_utils::Append(&attestation_data, credential_id_);
fido_parsing_utils::Append(&attestation_data, public_key_->cose_key_bytes);
return attestation_data;
}
const PublicKey* AttestedCredentialData::public_key() const {
return public_key_.get();
}
} // namespace device