blob: ea9723e5c684f4a4c8372e664699018bbf51d5d1 [file] [log] [blame]
// Copyright 2020 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 <ostream>
#include "device/fido/large_blob.h"
#include "base/containers/span.h"
#include "components/cbor/reader.h"
#include "components/cbor/writer.h"
#include "crypto/aead.h"
#include "crypto/random.h"
#include "crypto/sha2.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/pin.h"
namespace device {
namespace {
// The number of bytes the large blob validation hash is truncated to.
constexpr size_t kTruncatedHashBytes = 16;
constexpr std::array<uint8_t, 4> kLargeBlobADPrefix = {'b', 'l', 'o', 'b'};
constexpr size_t kAssociatedDataLength = kLargeBlobADPrefix.size() + 8;
std::array<uint8_t, kAssociatedDataLength> GenerateLargeBlobAdditionalData(
size_t size) {
std::array<uint8_t, kAssociatedDataLength> additional_data;
const std::array<uint8_t, 8>& size_array =
fido_parsing_utils::Uint64LittleEndian(size);
std::copy(kLargeBlobADPrefix.begin(), kLargeBlobADPrefix.end(),
additional_data.begin());
std::copy(size_array.begin(), size_array.end(),
additional_data.begin() + kLargeBlobADPrefix.size());
return additional_data;
}
} // namespace
LargeBlob::LargeBlob(std::vector<uint8_t> compressed_data,
uint64_t original_size)
: compressed_data(std::move(compressed_data)),
original_size(original_size) {}
LargeBlob::~LargeBlob() = default;
LargeBlob::LargeBlob(const LargeBlob&) = default;
LargeBlob& LargeBlob::operator=(const LargeBlob&) = default;
LargeBlob::LargeBlob(LargeBlob&&) = default;
LargeBlob& LargeBlob::operator=(LargeBlob&&) = default;
bool LargeBlob::operator==(const LargeBlob& other) const {
return other.compressed_data == compressed_data &&
other.original_size == original_size;
}
LargeBlobArrayFragment::LargeBlobArrayFragment(const std::vector<uint8_t> bytes,
const size_t offset)
: bytes(std::move(bytes)), offset(offset) {}
LargeBlobArrayFragment::~LargeBlobArrayFragment() = default;
LargeBlobArrayFragment::LargeBlobArrayFragment(LargeBlobArrayFragment&&) =
default;
bool VerifyLargeBlobArrayIntegrity(base::span<const uint8_t> large_blob_array) {
if (large_blob_array.size() <= kTruncatedHashBytes) {
return false;
}
const size_t trail_offset = large_blob_array.size() - kTruncatedHashBytes;
std::array<uint8_t, crypto::kSHA256Length> large_blob_hash =
crypto::SHA256Hash(large_blob_array.subspan(0, trail_offset));
base::span<const uint8_t> large_blob_trail =
large_blob_array.subspan(trail_offset);
return std::equal(large_blob_hash.begin(),
large_blob_hash.begin() + kTruncatedHashBytes,
large_blob_trail.begin(), large_blob_trail.end());
}
// static
LargeBlobsRequest LargeBlobsRequest::ForRead(size_t bytes, size_t offset) {
DCHECK_GT(bytes, 0u);
LargeBlobsRequest request;
request.get_ = bytes;
request.offset_ = offset;
return request;
}
// static
LargeBlobsRequest LargeBlobsRequest::ForWrite(LargeBlobArrayFragment fragment,
size_t length) {
LargeBlobsRequest request;
if (fragment.offset == 0) {
request.length_ = length;
}
request.offset_ = fragment.offset;
request.set_ = std::move(fragment.bytes);
return request;
}
LargeBlobsRequest::LargeBlobsRequest() = default;
LargeBlobsRequest::LargeBlobsRequest(LargeBlobsRequest&& other) = default;
LargeBlobsRequest::~LargeBlobsRequest() = default;
void LargeBlobsRequest::SetPinParam(
const pin::TokenResponse& pin_uv_auth_token) {
DCHECK(set_) << "SetPinParam should only be used for write requests";
std::vector<uint8_t> pin_auth(pin::kPinUvAuthTokenSafetyPadding.begin(),
pin::kPinUvAuthTokenSafetyPadding.end());
pin_auth.insert(pin_auth.end(), kLargeBlobPinPrefix.begin(),
kLargeBlobPinPrefix.end());
const std::array<uint8_t, 4> offset_array =
fido_parsing_utils::Uint32LittleEndian(offset_);
pin_auth.insert(pin_auth.end(), offset_array.begin(), offset_array.end());
std::array<uint8_t, crypto::kSHA256Length> set_hash =
crypto::SHA256Hash(*set_);
pin_auth.insert(pin_auth.end(), set_hash.begin(), set_hash.end());
std::tie(pin_uv_auth_protocol_, pin_uv_auth_param_) =
pin_uv_auth_token.PinAuth(pin_auth);
}
// static
absl::optional<LargeBlobsResponse> LargeBlobsResponse::ParseForRead(
const size_t bytes_to_read,
const absl::optional<cbor::Value>& cbor_response) {
if (!cbor_response || !cbor_response->is_map()) {
return absl::nullopt;
}
const cbor::Value::MapValue& map = cbor_response->GetMap();
auto it =
map.find(cbor::Value(static_cast<int>(LargeBlobsResponseKey::kConfig)));
if (it == map.end() || !it->second.is_bytestring()) {
return absl::nullopt;
}
const std::vector<uint8_t>& config = it->second.GetBytestring();
if (config.size() > bytes_to_read) {
return absl::nullopt;
}
return LargeBlobsResponse(std::move(config));
}
// static
absl::optional<LargeBlobsResponse> LargeBlobsResponse::ParseForWrite(
const absl::optional<cbor::Value>& cbor_response) {
// For writing, we expect an empty response.
if (cbor_response) {
return absl::nullopt;
}
return LargeBlobsResponse();
}
LargeBlobsResponse::LargeBlobsResponse(
absl::optional<std::vector<uint8_t>> config)
: config_(std::move(config)) {}
LargeBlobsResponse::LargeBlobsResponse(LargeBlobsResponse&& other) = default;
LargeBlobsResponse& LargeBlobsResponse::operator=(LargeBlobsResponse&& other) =
default;
LargeBlobsResponse::~LargeBlobsResponse() = default;
std::pair<CtapRequestCommand, absl::optional<cbor::Value>>
AsCTAPRequestValuePair(const LargeBlobsRequest& request) {
cbor::Value::MapValue map;
if (request.get_) {
map.emplace(static_cast<int>(LargeBlobsRequestKey::kGet), *request.get_);
}
if (request.set_) {
map.emplace(static_cast<int>(LargeBlobsRequestKey::kSet), *request.set_);
}
map.emplace(static_cast<int>(LargeBlobsRequestKey::kOffset), request.offset_);
if (request.length_) {
map.emplace(static_cast<int>(LargeBlobsRequestKey::kLength),
*request.length_);
}
if (request.pin_uv_auth_param_) {
map.emplace(static_cast<int>(LargeBlobsRequestKey::kPinUvAuthParam),
*request.pin_uv_auth_param_);
}
if (request.pin_uv_auth_protocol_) {
map.emplace(static_cast<int>(LargeBlobsRequestKey::kPinUvAuthProtocol),
static_cast<uint8_t>(*request.pin_uv_auth_protocol_));
}
return std::make_pair(CtapRequestCommand::kAuthenticatorLargeBlobs,
cbor::Value(std::move(map)));
}
// static.
absl::optional<LargeBlobData> LargeBlobData::Parse(const cbor::Value& value) {
if (!value.is_map()) {
return absl::nullopt;
}
const cbor::Value::MapValue& map = value.GetMap();
auto ciphertext_it =
map.find(cbor::Value(static_cast<int>(LargeBlobDataKeys::kCiphertext)));
if (ciphertext_it == map.end() || !ciphertext_it->second.is_bytestring()) {
return absl::nullopt;
}
auto nonce_it =
map.find(cbor::Value(static_cast<int>(LargeBlobDataKeys::kNonce)));
if (nonce_it == map.end() || !nonce_it->second.is_bytestring() ||
nonce_it->second.GetBytestring().size() != kLargeBlobArrayNonceLength) {
return absl::nullopt;
}
auto orig_size_it =
map.find(cbor::Value(static_cast<int>(LargeBlobDataKeys::kOrigSize)));
if (orig_size_it == map.end() || !orig_size_it->second.is_unsigned()) {
return absl::nullopt;
}
return LargeBlobData(ciphertext_it->second.GetBytestring(),
base::make_span<kLargeBlobArrayNonceLength>(
nonce_it->second.GetBytestring()),
orig_size_it->second.GetUnsigned());
}
LargeBlobData::LargeBlobData(
std::vector<uint8_t> ciphertext,
base::span<const uint8_t, kLargeBlobArrayNonceLength> nonce,
int64_t orig_size)
: ciphertext_(std::move(ciphertext)), orig_size_(std::move(orig_size)) {
std::copy(nonce.begin(), nonce.end(), nonce_.begin());
}
LargeBlobData::LargeBlobData(LargeBlobKey key, LargeBlob large_blob)
: orig_size_(large_blob.original_size) {
crypto::Aead aead(crypto::Aead::AeadAlgorithm::AES_256_GCM);
aead.Init(key);
crypto::RandBytes(nonce_);
ciphertext_ = aead.Seal(large_blob.compressed_data, nonce_,
GenerateLargeBlobAdditionalData(orig_size_));
}
LargeBlobData::LargeBlobData(LargeBlobData&&) = default;
LargeBlobData& LargeBlobData::operator=(LargeBlobData&&) = default;
LargeBlobData::~LargeBlobData() = default;
bool LargeBlobData::operator==(const LargeBlobData& other) const {
return ciphertext_ == other.ciphertext_ && nonce_ == other.nonce_ &&
orig_size_ == other.orig_size_;
}
absl::optional<LargeBlob> LargeBlobData::Decrypt(LargeBlobKey key) const {
crypto::Aead aead(crypto::Aead::AeadAlgorithm::AES_256_GCM);
aead.Init(key);
absl::optional<std::vector<uint8_t>> compressed_data = aead.Open(
ciphertext_, nonce_, GenerateLargeBlobAdditionalData(orig_size_));
if (!compressed_data) {
return absl::nullopt;
}
return LargeBlob(*compressed_data, orig_size_);
}
cbor::Value::MapValue LargeBlobData::AsCBOR() const {
cbor::Value::MapValue map;
map.emplace(static_cast<int>(LargeBlobDataKeys::kCiphertext), ciphertext_);
map.emplace(static_cast<int>(LargeBlobDataKeys::kNonce), nonce_);
map.emplace(static_cast<int>(LargeBlobDataKeys::kOrigSize), orig_size_);
return map;
}
LargeBlobArrayReader::LargeBlobArrayReader() = default;
LargeBlobArrayReader::LargeBlobArrayReader(LargeBlobArrayReader&&) = default;
LargeBlobArrayReader::~LargeBlobArrayReader() = default;
void LargeBlobArrayReader::Append(const std::vector<uint8_t>& fragment) {
bytes_.insert(bytes_.end(), fragment.begin(), fragment.end());
}
absl::optional<std::vector<LargeBlobData>> LargeBlobArrayReader::Materialize() {
if (!VerifyLargeBlobArrayIntegrity(bytes_)) {
return absl::nullopt;
}
base::span<const uint8_t> cbor_bytes =
base::make_span(bytes_.data(), bytes_.size() - kTruncatedHashBytes);
absl::optional<cbor::Value> cbor = cbor::Reader::Read(cbor_bytes);
if (!cbor || !cbor->is_array()) {
return absl::nullopt;
}
std::vector<LargeBlobData> large_blob_array;
const cbor::Value::ArrayValue& array = cbor->GetArray();
for (const cbor::Value& value : array) {
absl::optional<LargeBlobData> large_blob_data = LargeBlobData::Parse(value);
if (!large_blob_data) {
continue;
}
large_blob_array.emplace_back(std::move(*large_blob_data));
}
return large_blob_array;
}
LargeBlobArrayWriter::LargeBlobArrayWriter(
const std::vector<LargeBlobData>& large_blob_array) {
cbor::Value::ArrayValue array;
for (const LargeBlobData& large_blob_data : large_blob_array) {
array.emplace_back(large_blob_data.AsCBOR());
}
bytes_ = *cbor::Writer::Write(cbor::Value(array));
std::array<uint8_t, crypto::kSHA256Length> large_blob_hash =
crypto::SHA256Hash(bytes_);
bytes_.insert(bytes_.end(), large_blob_hash.begin(),
large_blob_hash.begin() + kTruncatedHashBytes);
DCHECK(VerifyLargeBlobArrayIntegrity(bytes_));
}
LargeBlobArrayWriter::LargeBlobArrayWriter(LargeBlobArrayWriter&&) = default;
LargeBlobArrayWriter::~LargeBlobArrayWriter() = default;
LargeBlobArrayFragment LargeBlobArrayWriter::Pop(size_t length) {
CHECK(has_remaining_fragments());
length = std::min(length, bytes_.size() - offset_);
LargeBlobArrayFragment fragment{
fido_parsing_utils::Materialize(
base::make_span(bytes_.data() + offset_, length)),
offset_};
offset_ += length;
return fragment;
}
} // namespace device