blob: 1ef914523abd2bf093b061fb164ad174c822854e [file]
// 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 <algorithm>
#include <utility>
#include "base/containers/extend.h"
#include "base/containers/span_reader.h"
#include "base/containers/to_array.h"
#include "base/containers/to_vector.h"
#include "base/numerics/safe_math.h"
#include "components/cbor/reader.h"
#include "components/device_event_log/device_event_log.h"
#include "device/fido/public/fido_constants.h"
#include "device/fido/public_key.h"
namespace device {
// static
std::optional<std::pair<AttestedCredentialData, base::span<const uint8_t>>>
AttestedCredentialData::ConsumeFromCtapResponse(
base::span<const uint8_t> buffer) {
if (buffer.size() < kAaguidLength) {
return std::nullopt;
}
auto aaguid = buffer.first<kAaguidLength>();
buffer = buffer.subspan(kAaguidLength);
if (buffer.size() < kCredentialIdLengthLength) {
return std::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 std::nullopt;
}
auto credential_id = buffer.first(credential_id_length);
buffer = buffer.subspan(credential_id_length);
size_t public_key_byte_len;
std::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 std::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();
// 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
const auto alg_it =
public_key_map.find(cbor::Value(static_cast<int64_t>(CoseKeyKey::kAlg)));
if (alg_it == public_key_map.end() || !alg_it->second.is_integer()) {
FIDO_LOG(ERROR) << "Failed to parse COSE key algorithm";
return std::nullopt;
}
// kKty is required in COSE keys:
// https://tools.ietf.org/html/rfc8152#section-7
if (const auto kty_it = public_key_map.find(
cbor::Value(static_cast<int64_t>(CoseKeyKey::kKty)));
kty_it == public_key_map.end() || !kty_it->second.is_integer()) {
FIDO_LOG(ERROR) << "Failed to parse COSE key type";
return std::nullopt;
}
// In WebIDL, a |long| is an |int32_t|[1].
//
// [1] https://heycam.github.io/webidl/#idl-long
const int64_t algorithm64 = alg_it->second.GetInteger();
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 std::nullopt;
}
const int32_t algorithm = static_cast<int32_t>(algorithm64);
std::unique_ptr<PublicKey> public_key =
PublicKey::FromCOSEKey(algorithm, public_key_cbor_bytes, public_key_map);
if (!public_key) {
return std::nullopt;
}
return std::make_pair(
AttestedCredentialData(aaguid, credential_id_length_span,
base::ToVector(credential_id),
std::move(public_key)),
buffer);
}
// static
std::optional<AttestedCredentialData>
AttestedCredentialData::CreateFromU2fRegisterResponse(
base::span<const uint8_t> u2f_data,
std::unique_ptr<PublicKey> public_key) {
// TODO(crbug.com/41363164): 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.
base::SpanReader<const uint8_t> reader(u2f_data);
reader.Skip(kU2fKeyHandleLengthOffset);
uint8_t extracted_length;
if (!reader.ReadU8BigEndian(extracted_length)) {
return std::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};
// Extract the credential id (i.e. key handle).
std::optional<base::span<const uint8_t>> credential_id =
reader.Read(extracted_length);
if (!credential_id.has_value()) {
return std::nullopt;
}
return AttestedCredentialData(aaguid, credential_id_length,
base::ToVector(*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_bytes,
std::vector<uint8_t> credential_id,
std::unique_ptr<PublicKey> public_key)
: aaguid_(base::ToArray(aaguid)),
credential_id_length_(base::ToArray(credential_id_length_bytes)),
credential_id_(std::move(credential_id)),
public_key_(std::move(public_key)) {
const size_t credential_id_length =
(base::strict_cast<size_t>(credential_id_length_[0]) << 8) |
base::strict_cast<size_t>(credential_id_length_[1]);
CHECK_EQ(credential_id_length, credential_id_.size());
}
AttestedCredentialData::AttestedCredentialData(
base::span<const uint8_t, kAaguidLength> aaguid,
base::span<const uint8_t> credential_id,
std::unique_ptr<PublicKey> public_key)
: AttestedCredentialData(
aaguid,
std::array<uint8_t, kCredentialIdLengthLength>{
base::checked_cast<uint8_t>((0xff00 & credential_id.size()) >> 8),
base::checked_cast<uint8_t>(0xff & credential_id.size())},
base::ToVector(credential_id),
std::move(public_key)) {
CHECK_LE(credential_id.size(), 0xffffu);
}
AttestedCredentialData& AttestedCredentialData::operator=(
AttestedCredentialData&& other) = default;
AttestedCredentialData::~AttestedCredentialData() = default;
bool AttestedCredentialData::IsAaguidZero() const {
return std::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;
base::Extend(attestation_data, aaguid_);
base::Extend(attestation_data, credential_id_length_);
base::Extend(attestation_data, credential_id_);
base::Extend(attestation_data, public_key_->cose_key_bytes);
return attestation_data;
}
const PublicKey* AttestedCredentialData::public_key() const {
return public_key_.get();
}
} // namespace device