blob: fb7e7ffb454c432aedc1ec48f6fec94e11cd1f65 [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 "device/fido/virtual_ctap2_device.h"
#include <memory>
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "components/cbor/reader.h"
#include "device/fido/attestation_statement.h"
#include "device/fido/authenticator_get_assertion_response.h"
#include "device/fido/device_response_converter.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/fido_test_data.h"
#include "device/fido/test_callback_receiver.h"
#include "net/cert/asn1_util.h"
#include "net/cert/x509_certificate.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace device {
namespace {
using TestCallbackReceiver =
test::ValueCallbackReceiver<absl::optional<std::vector<uint8_t>>>;
void SendCommand(VirtualCtap2Device* device,
base::span<const uint8_t> command,
FidoDevice::DeviceCallback callback = base::DoNothing()) {
device->DeviceTransact(fido_parsing_utils::Materialize(command),
std::move(callback));
}
// DecodeCBOR parses a CBOR structure, ignoring the first byte of |in|, which is
// assumed to be a CTAP2 status byte.
absl::optional<cbor::Value> DecodeCBOR(base::span<const uint8_t> in) {
CHECK(!in.empty());
return cbor::Reader::Read(in.subspan(1));
}
} // namespace
class VirtualCtap2DeviceTest : public ::testing::Test {
protected:
void MakeDevice() { device_ = std::make_unique<VirtualCtap2Device>(); }
void MakeSelfDestructingDevice() {
MakeDevice();
device_->mutable_state()->simulate_press_callback =
base::BindLambdaForTesting([&](VirtualFidoDevice* _) {
device_.reset();
return true;
});
}
std::unique_ptr<VirtualCtap2Device> device_;
base::test::TaskEnvironment task_environment_;
};
TEST_F(VirtualCtap2DeviceTest, ParseMakeCredentialRequestForVirtualCtapKey) {
const auto& cbor_request = cbor::Reader::Read(
base::make_span(test_data::kCtapMakeCredentialRequest).subspan(1));
ASSERT_TRUE(cbor_request);
ASSERT_TRUE(cbor_request->is_map());
const absl::optional<CtapMakeCredentialRequest> request =
CtapMakeCredentialRequest::Parse(cbor_request->GetMap());
ASSERT_TRUE(request);
EXPECT_THAT(request->client_data_hash,
::testing::ElementsAreArray(test_data::kClientDataHash));
EXPECT_EQ(test_data::kRelyingPartyId, request->rp.id);
EXPECT_EQ("Acme", request->rp.name);
EXPECT_THAT(request->user.id,
::testing::ElementsAreArray(test_data::kUserId));
ASSERT_TRUE(request->user.name);
EXPECT_EQ("johnpsmith@example.com", *request->user.name);
ASSERT_TRUE(request->user.display_name);
EXPECT_EQ("John P. Smith", *request->user.display_name);
ASSERT_TRUE(request->user.icon_url);
EXPECT_EQ("https://pics.acme.com/00/p/aBjjjpqPb.png",
request->user.icon_url->spec());
ASSERT_EQ(2u,
request->public_key_credential_params.public_key_credential_params()
.size());
EXPECT_EQ(-7,
request->public_key_credential_params.public_key_credential_params()
.at(0)
.algorithm);
EXPECT_EQ(257,
request->public_key_credential_params.public_key_credential_params()
.at(1)
.algorithm);
EXPECT_EQ(UserVerificationRequirement::kRequired, request->user_verification);
EXPECT_TRUE(request->resident_key_required);
}
TEST_F(VirtualCtap2DeviceTest, ParseGetAssertionRequestForVirtualCtapKey) {
constexpr uint8_t kAllowedCredentialOne[] = {
0xf2, 0x20, 0x06, 0xde, 0x4f, 0x90, 0x5a, 0xf6, 0x8a, 0x43, 0x94,
0x2f, 0x02, 0x4f, 0x2a, 0x5e, 0xce, 0x60, 0x3d, 0x9c, 0x6d, 0x4b,
0x3d, 0xf8, 0xbe, 0x08, 0xed, 0x01, 0xfc, 0x44, 0x26, 0x46, 0xd0,
0x34, 0x85, 0x8a, 0xc7, 0x5b, 0xed, 0x3f, 0xd5, 0x80, 0xbf, 0x98,
0x08, 0xd9, 0x4f, 0xcb, 0xee, 0x82, 0xb9, 0xb2, 0xef, 0x66, 0x77,
0xaf, 0x0a, 0xdc, 0xc3, 0x58, 0x52, 0xea, 0x6b, 0x9e};
constexpr uint8_t kAllowedCredentialTwo[] = {
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03};
const auto& cbor_request = cbor::Reader::Read(
base::make_span(test_data::kTestComplexCtapGetAssertionRequest)
.subspan(1));
ASSERT_TRUE(cbor_request);
ASSERT_TRUE(cbor_request->is_map());
const absl::optional<CtapGetAssertionRequest> request =
CtapGetAssertionRequest::Parse(cbor_request->GetMap());
EXPECT_THAT(request->client_data_hash,
::testing::ElementsAreArray(test_data::kClientDataHash));
EXPECT_EQ(test_data::kRelyingPartyId, request->rp_id);
EXPECT_EQ(UserVerificationRequirement::kRequired, request->user_verification);
EXPECT_FALSE(request->user_presence_required);
ASSERT_EQ(2u, request->allow_list.size());
EXPECT_THAT(request->allow_list.at(0).id(),
::testing::ElementsAreArray(kAllowedCredentialOne));
EXPECT_THAT(request->allow_list.at(1).id(),
::testing::ElementsAreArray(kAllowedCredentialTwo));
}
// Tests that destroying the virtual device from the |simulate_press_callback|
// does not crash.
TEST_F(VirtualCtap2DeviceTest, DestroyInsideSimulatePressCallback) {
MakeSelfDestructingDevice();
SendCommand(device_.get(), test_data::kCtapSimpleMakeCredentialRequest);
ASSERT_FALSE(device_);
MakeSelfDestructingDevice();
SendCommand(device_.get(), test_data::kCtapGetAssertionRequest);
ASSERT_FALSE(device_);
}
// Tests that the attestation certificate returned on MakeCredential is valid.
// See
// https://w3c.github.io/webauthn/#sctn-packed-attestation-cert-requirements
TEST_F(VirtualCtap2DeviceTest, AttestationCertificateIsValid) {
MakeDevice();
TestCallbackReceiver callback_receiver;
SendCommand(device_.get(), test_data::kCtapSimpleMakeCredentialRequest,
callback_receiver.callback());
callback_receiver.WaitForCallback();
absl::optional<cbor::Value> cbor = DecodeCBOR(*callback_receiver.value());
ASSERT_TRUE(cbor);
absl::optional<AuthenticatorMakeCredentialResponse> response =
ReadCTAPMakeCredentialResponse(
FidoTransportProtocol::kUsbHumanInterfaceDevice, std::move(cbor));
ASSERT_TRUE(response);
const AttestationStatement& attestation =
response->attestation_object().attestation_statement();
EXPECT_FALSE(attestation.IsSelfAttestation());
EXPECT_FALSE(
attestation.IsAttestationCertificateInappropriatelyIdentifying());
base::span<const uint8_t> cert_bytes = *attestation.GetLeafCertificate();
scoped_refptr<net::X509Certificate> cert =
net::X509Certificate::CreateFromBytes(cert_bytes);
ASSERT_TRUE(cert);
const auto& subject = cert->subject();
EXPECT_EQ("Batch Certificate", subject.common_name);
EXPECT_EQ("US", subject.country_name);
EXPECT_THAT(subject.organization_names, testing::ElementsAre("Chromium"));
EXPECT_THAT(subject.organization_unit_names,
testing::ElementsAre("Authenticator Attestation"));
base::Time now = base::Time::Now();
EXPECT_LT(cert->valid_start(), now);
EXPECT_GT(cert->valid_expiry(), now);
bool present;
bool critical;
base::StringPiece contents;
ASSERT_TRUE(net::asn1::ExtractExtensionFromDERCert(
net::x509_util::CryptoBufferAsStringPiece(cert->cert_buffer()),
base::StringPiece("\x55\x1d\x13"), &present, &critical, &contents));
EXPECT_TRUE(present);
EXPECT_TRUE(critical);
EXPECT_EQ(base::StringPiece("\x30\x00", 2), contents);
}
} // namespace device