diff options
Diffstat (limited to 'cast')
30 files changed, 1406 insertions, 141 deletions
diff --git a/cast/common/BUILD.gn b/cast/common/BUILD.gn index 5e3227da..51a3b653 100644 --- a/cast/common/BUILD.gn +++ b/cast/common/BUILD.gn @@ -73,16 +73,20 @@ source_set("discovery") { source_set("test_helpers") { testonly = true + + # TODO(btolsch): Move to testing/ folders. sources = [ "certificate/test_helpers.cc", "certificate/test_helpers.h", "channel/test/fake_cast_socket.h", "channel/test/mock_cast_message_handler.h", + "channel/test/mock_socket_error_handler.h", ] public_deps = [ ":certificate", ":channel", "../../platform:test", + "../../third_party/abseil", "../../third_party/boringssl", "../../third_party/googletest:gmock", ] @@ -110,6 +114,7 @@ source_set("unittests") { ":channel", ":test_helpers", "../../platform", + "../../testing/util", "../../third_party/boringssl", "../../third_party/googletest:gmock", "../../third_party/googletest:gtest", diff --git a/cast/common/certificate/cast_crl.cc b/cast/common/certificate/cast_crl.cc index 5935e1e8..587fdb55 100644 --- a/cast/common/certificate/cast_crl.cc +++ b/cast/common/certificate/cast_crl.cc @@ -13,6 +13,7 @@ #include "cast/common/certificate/cast_cert_validator_internal.h" #include "cast/common/certificate/proto/revocation.pb.h" #include "platform/base/macros.h" +#include "util/crypto/certificate_utils.h" #include "util/crypto/sha2.h" #include "util/logging.h" @@ -134,39 +135,12 @@ bool VerifyCRL(const Crl& crl, return true; } -std::string GetSpkiTlv(X509* cert) { - int len = i2d_X509_PUBKEY(cert->cert_info->key, nullptr); - if (len <= 0) { - return {}; - } - std::string x(len, 0); - uint8_t* data = reinterpret_cast<uint8_t*>(&x[0]); - if (!i2d_X509_PUBKEY(cert->cert_info->key, &data)) { - return {}; - } - size_t actual_size = data - reinterpret_cast<uint8_t*>(&x[0]); - OSP_DCHECK_EQ(actual_size, x.size()); - x.resize(actual_size); - return x; -} - -bool ParseDerUint64(ASN1_INTEGER* asn1int, uint64_t* result) { - if (asn1int->length > 8 || asn1int->length == 0) { - return false; - } - *result = 0; - for (int i = 0; i < asn1int->length; ++i) { - *result = (*result << 8) | asn1int->data[i]; - } - return true; -} - } // namespace CastCRL::CastCRL(const TbsCrl& tbs_crl, const DateTime& overall_not_after) { // Parse the validity information. - // Assume ConvertTimeSeconds will succeed. Successful call to VerifyCRL - // means that these calls were successful. + // Assume DateTimeFromSeconds will succeed. Successful call to VerifyCRL means + // that these calls were successful. DateTimeFromSeconds(tbs_crl.not_before_seconds(), ¬_before_); DateTimeFromSeconds(tbs_crl.not_after_seconds(), ¬_after_); if (overall_not_after < not_after_) { @@ -205,7 +179,7 @@ bool CastCRL::CheckRevocation(const std::vector<X509*>& trusted_chain, // Check revocation. This loop iterates over both certificates AND then the // trust anchor after exhausting the certs. for (size_t i = 0; i < trusted_chain.size(); ++i) { - std::string spki_tlv = GetSpkiTlv(trusted_chain[i]); + std::string spki_tlv = openscreen::GetSpkiTlv(trusted_chain[i]); if (spki_tlv.empty()) { return false; } @@ -226,10 +200,12 @@ bool CastCRL::CheckRevocation(const std::vector<X509*>& trusted_chain, // Only Google generated device certificates will be revoked by range. // These will always be less than 64 bits in length. - if (!ParseDerUint64(subordinate->cert_info->serialNumber, - &serial_number)) { + openscreen::ErrorOr<uint64_t> maybe_serial = + openscreen::ParseDerUint64(subordinate->cert_info->serialNumber); + if (!maybe_serial) { continue; } + serial_number = maybe_serial.value(); for (const auto& revoked_serial : issuer_iter->second) { if (revoked_serial.first_serial <= serial_number && revoked_serial.last_serial >= serial_number) { diff --git a/cast/common/certificate/cast_crl_unittest.cc b/cast/common/certificate/cast_crl_unittest.cc index 4e3a94d8..b6660537 100644 --- a/cast/common/certificate/cast_crl_unittest.cc +++ b/cast/common/certificate/cast_crl_unittest.cc @@ -9,6 +9,7 @@ #include "cast/common/certificate/proto/test_suite.pb.h" #include "cast/common/certificate/test_helpers.h" #include "gtest/gtest.h" +#include "testing/util/read_file.h" #include "util/logging.h" namespace cast { @@ -170,7 +171,7 @@ bool RunTest(const DeviceCertTest& test_case) { // These tests are generated by a test generator in google3. void RunTestSuite(const std::string& test_suite_file_name) { std::string testsuite_raw = - testing::ReadEntireFileToString(test_suite_file_name); + openscreen::ReadEntireFileToString(test_suite_file_name); ASSERT_FALSE(testsuite_raw.empty()); DeviceCertTestSuite test_suite; ASSERT_TRUE(test_suite.ParseFromString(testsuite_raw)); diff --git a/cast/common/certificate/test_helpers.cc b/cast/common/certificate/test_helpers.cc index 41aef74f..3ede95e4 100644 --- a/cast/common/certificate/test_helpers.cc +++ b/cast/common/certificate/test_helpers.cc @@ -4,65 +4,69 @@ #include "cast/common/certificate/test_helpers.h" +#include <openssl/bytestring.h> #include <openssl/pem.h> +#include <openssl/rsa.h> #include <stdio.h> #include <string.h> +#include "absl/strings/match.h" #include "util/logging.h" namespace cast { namespace certificate { namespace testing { -std::string ReadEntireFileToString(const std::string& filename) { - FILE* file = fopen(filename.c_str(), "r"); - if (file == nullptr) { +std::vector<std::string> ReadCertificatesFromPemFile( + absl::string_view filename) { + FILE* fp = fopen(filename.data(), "r"); + if (!fp) { return {}; } - fseek(file, 0, SEEK_END); - long file_size = ftell(file); - fseek(file, 0, SEEK_SET); - std::string contents(file_size, 0); - int bytes_read = 0; - while (bytes_read < file_size) { - size_t ret = fread(&contents[bytes_read], 1, file_size - bytes_read, file); - if (ret == 0 && ferror(file)) { - return {}; - } else { - bytes_read += ret; + std::vector<std::string> certs; + char* name; + char* header; + unsigned char* data; + long length; + while (PEM_read(fp, &name, &header, &data, &length) == 1) { + if (absl::StartsWith(name, "CERTIFICATE")) { + certs.emplace_back((char*)data, length); } + OPENSSL_free(name); + OPENSSL_free(header); + OPENSSL_free(data); } - fclose(file); - - return contents; + fclose(fp); + return certs; } -std::vector<std::string> ReadCertificatesFromPemFile( - const std::string& filename) { - FILE* fp = fopen(filename.c_str(), "r"); +bssl::UniquePtr<EVP_PKEY> ReadKeyFromPemFile(absl::string_view filename) { + FILE* fp = fopen(filename.data(), "r"); if (!fp) { - return {}; + return nullptr; } - std::vector<std::string> certs; -#define STRCMP_LITERAL(s, l) strncmp(s, l, sizeof(l)) - for (;;) { - char* name; - char* header; - unsigned char* data; - long length; - if (PEM_read(fp, &name, &header, &data, &length) == 1) { - if (STRCMP_LITERAL(name, "CERTIFICATE") == 0) { - certs.emplace_back((char*)data, length); + bssl::UniquePtr<EVP_PKEY> pkey; + char* name; + char* header; + unsigned char* data; + long length; + while (PEM_read(fp, &name, &header, &data, &length) == 1) { + if (absl::StartsWith(name, "RSA PRIVATE KEY")) { + OSP_DCHECK(!pkey); + CBS cbs; + CBS_init(&cbs, data, length); + RSA* rsa = RSA_parse_private_key(&cbs); + if (rsa) { + pkey.reset(EVP_PKEY_new()); + EVP_PKEY_assign_RSA(pkey.get(), rsa); } - OPENSSL_free(name); - OPENSSL_free(header); - OPENSSL_free(data); - } else { - break; } + OPENSSL_free(name); + OPENSSL_free(header); + OPENSSL_free(data); } fclose(fp); - return certs; + return pkey; } SignatureTestData::SignatureTestData() @@ -74,36 +78,32 @@ SignatureTestData::~SignatureTestData() { OPENSSL_free(const_cast<uint8_t*>(sha256.data)); } -SignatureTestData ReadSignatureTestData(const std::string& filename) { - FILE* fp = fopen(filename.c_str(), "r"); +SignatureTestData ReadSignatureTestData(absl::string_view filename) { + FILE* fp = fopen(filename.data(), "r"); OSP_DCHECK(fp); SignatureTestData result = {}; - for (;;) { - char* name; - char* header; - unsigned char* data; - long length; - if (PEM_read(fp, &name, &header, &data, &length) == 1) { - if (strcmp(name, "MESSAGE") == 0) { - OSP_DCHECK(!result.message.data); - result.message.data = data; - result.message.length = length; - } else if (strcmp(name, "SIGNATURE SHA1") == 0) { - OSP_DCHECK(!result.sha1.data); - result.sha1.data = data; - result.sha1.length = length; - } else if (strcmp(name, "SIGNATURE SHA256") == 0) { - OSP_DCHECK(!result.sha256.data); - result.sha256.data = data; - result.sha256.length = length; - } else { - OPENSSL_free(data); - } - OPENSSL_free(name); - OPENSSL_free(header); + char* name; + char* header; + unsigned char* data; + long length; + while (PEM_read(fp, &name, &header, &data, &length) == 1) { + if (strcmp(name, "MESSAGE") == 0) { + OSP_DCHECK(!result.message.data); + result.message.data = data; + result.message.length = length; + } else if (strcmp(name, "SIGNATURE SHA1") == 0) { + OSP_DCHECK(!result.sha1.data); + result.sha1.data = data; + result.sha1.length = length; + } else if (strcmp(name, "SIGNATURE SHA256") == 0) { + OSP_DCHECK(!result.sha256.data); + result.sha256.data = data; + result.sha256.length = length; } else { - break; + OPENSSL_free(data); } + OPENSSL_free(name); + OPENSSL_free(header); } OSP_DCHECK(result.message.data); OSP_DCHECK(result.sha1.data); @@ -113,7 +113,7 @@ SignatureTestData ReadSignatureTestData(const std::string& filename) { } std::unique_ptr<TrustStore> CreateTrustStoreFromPemFile( - const std::string& filename) { + absl::string_view filename) { std::unique_ptr<TrustStore> store = std::make_unique<TrustStore>(); std::vector<std::string> certs = diff --git a/cast/common/certificate/test_helpers.h b/cast/common/certificate/test_helpers.h index ac3a136d..558e60e8 100644 --- a/cast/common/certificate/test_helpers.h +++ b/cast/common/certificate/test_helpers.h @@ -5,9 +5,12 @@ #ifndef CAST_COMMON_CERTIFICATE_TEST_HELPERS_H_ #define CAST_COMMON_CERTIFICATE_TEST_HELPERS_H_ +#include <openssl/evp.h> + #include <string> #include <vector> +#include "absl/strings/string_view.h" #include "cast/common/certificate/cast_cert_validator_internal.h" #include "cast/common/certificate/types.h" @@ -15,9 +18,9 @@ namespace cast { namespace certificate { namespace testing { -std::string ReadEntireFileToString(const std::string& filename); std::vector<std::string> ReadCertificatesFromPemFile( - const std::string& filename); + absl::string_view filename); +bssl::UniquePtr<EVP_PKEY> ReadKeyFromPemFile(absl::string_view filename); class SignatureTestData { public: @@ -29,10 +32,10 @@ class SignatureTestData { ConstDataSpan sha256; }; -SignatureTestData ReadSignatureTestData(const std::string& filename); +SignatureTestData ReadSignatureTestData(absl::string_view filename); std::unique_ptr<TrustStore> CreateTrustStoreFromPemFile( - const std::string& filename); + absl::string_view filename); } // namespace testing } // namespace certificate diff --git a/cast/common/certificate/types.cc b/cast/common/certificate/types.cc index 297fbffc..a6f297a2 100644 --- a/cast/common/certificate/types.cc +++ b/cast/common/certificate/types.cc @@ -66,5 +66,22 @@ bool DateTimeFromSeconds(uint64_t seconds, DateTime* time) { return true; } +static_assert(sizeof(time_t) >= 4, "Can't avoid overflow with < 32-bits"); + +std::chrono::seconds DateTimeToSeconds(const DateTime& time) { + OSP_DCHECK_GE(time.month, 1); + OSP_DCHECK_GE(time.year, 1900); + // NOTE: Guard against overflow if time_t is 32-bit. + OSP_DCHECK(sizeof(time_t) >= 8 || time.year < 2038) << time.year; + struct tm tm = {}; + tm.tm_sec = time.second; + tm.tm_min = time.minute; + tm.tm_hour = time.hour; + tm.tm_mday = time.day; + tm.tm_mon = time.month - 1; + tm.tm_year = time.year - 1900; + return std::chrono::seconds(mktime(&tm)); +} + } // namespace certificate } // namespace cast diff --git a/cast/common/certificate/types.h b/cast/common/certificate/types.h index 62fe45ed..40c83276 100644 --- a/cast/common/certificate/types.h +++ b/cast/common/certificate/types.h @@ -7,6 +7,8 @@ #include <stdint.h> +#include <chrono> + namespace cast { namespace certificate { @@ -28,6 +30,9 @@ bool operator<(const DateTime& a, const DateTime& b); bool operator>(const DateTime& a, const DateTime& b); bool DateTimeFromSeconds(uint64_t seconds, DateTime* time); +// |time| is assumed to be valid. +std::chrono::seconds DateTimeToSeconds(const DateTime& time); + } // namespace certificate } // namespace cast diff --git a/cast/common/channel/connection_namespace_handler_unittest.cc b/cast/common/channel/connection_namespace_handler_unittest.cc index 2970ee2f..ce7ea3f6 100644 --- a/cast/common/channel/connection_namespace_handler_unittest.cc +++ b/cast/common/channel/connection_namespace_handler_unittest.cc @@ -7,6 +7,7 @@ #include "cast/common/channel/cast_socket.h" #include "cast/common/channel/message_util.h" #include "cast/common/channel/test/fake_cast_socket.h" +#include "cast/common/channel/test/mock_socket_error_handler.h" #include "cast/common/channel/virtual_connection.h" #include "cast/common/channel/virtual_connection_manager.h" #include "cast/common/channel/virtual_connection_router.h" @@ -26,16 +27,6 @@ using ::testing::NiceMock; using openscreen::ErrorOr; -class MockSocketErrorHandler - : public VirtualConnectionRouter::SocketErrorHandler { - public: - MOCK_METHOD(void, OnClose, (CastSocket * socket), (override)); - MOCK_METHOD(void, - OnError, - (CastSocket * socket, openscreen::Error error), - (override)); -}; - class MockVirtualConnectionPolicy : public ConnectionNamespaceHandler::VirtualConnectionPolicy { public: diff --git a/cast/common/channel/proto/cast_channel.proto b/cast/common/channel/proto/cast_channel.proto index 3714e360..968acf5c 100644 --- a/cast/common/channel/proto/cast_channel.proto +++ b/cast/common/channel/proto/cast_channel.proto @@ -92,7 +92,7 @@ message AuthChallenge { message AuthResponse { required bytes signature = 1; required bytes client_auth_certificate = 2; - repeated bytes intermediate_certificate = 3; + repeated bytes intermediate_certificates = 3; optional SignatureAlgorithm signature_algorithm = 4 [default = RSASSA_PKCS1v15]; optional bytes sender_nonce = 5; diff --git a/cast/common/channel/test/mock_socket_error_handler.h b/cast/common/channel/test/mock_socket_error_handler.h new file mode 100644 index 00000000..0e32f8c9 --- /dev/null +++ b/cast/common/channel/test/mock_socket_error_handler.h @@ -0,0 +1,27 @@ +// Copyright 2019 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. + +#ifndef CAST_COMMON_CHANNEL_TEST_MOCK_SOCKET_ERROR_HANDLER_H_ +#define CAST_COMMON_CHANNEL_TEST_MOCK_SOCKET_ERROR_HANDLER_H_ + +#include "cast/common/channel/virtual_connection_router.h" +#include "gmock/gmock.h" +#include "platform/base/error.h" + +using openscreen::Error; + +namespace cast { +namespace channel { + +class MockSocketErrorHandler + : public VirtualConnectionRouter::SocketErrorHandler { + public: + MOCK_METHOD(void, OnClose, (CastSocket * socket), (override)); + MOCK_METHOD(void, OnError, (CastSocket * socket, Error error), (override)); +}; + +} // namespace channel +} // namespace cast + +#endif // CAST_COMMON_CHANNEL_TEST_MOCK_SOCKET_ERROR_HANDLER_H_ diff --git a/cast/common/channel/virtual_connection_router_unittest.cc b/cast/common/channel/virtual_connection_router_unittest.cc index b5915330..40394805 100644 --- a/cast/common/channel/virtual_connection_router_unittest.cc +++ b/cast/common/channel/virtual_connection_router_unittest.cc @@ -8,8 +8,8 @@ #include "cast/common/channel/proto/cast_channel.pb.h" #include "cast/common/channel/test/fake_cast_socket.h" #include "cast/common/channel/test/mock_cast_message_handler.h" +#include "cast/common/channel/test/mock_socket_error_handler.h" #include "cast/common/channel/virtual_connection_manager.h" -#include "gmock/gmock.h" #include "gtest/gtest.h" namespace cast { @@ -19,16 +19,6 @@ namespace { using ::testing::_; using ::testing::Invoke; -class MockSocketErrorHandler - : public VirtualConnectionRouter::SocketErrorHandler { - public: - MOCK_METHOD(void, OnClose, (CastSocket * socket), (override)); - MOCK_METHOD(void, - OnError, - (CastSocket * socket, openscreen::Error error), - (override)); -}; - class VirtualConnectionRouterTest : public ::testing::Test { public: void SetUp() override { diff --git a/cast/receiver/BUILD.gn b/cast/receiver/BUILD.gn new file mode 100644 index 00000000..e0a62eba --- /dev/null +++ b/cast/receiver/BUILD.gn @@ -0,0 +1,59 @@ +# Copyright 2019 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. + +source_set("channel") { + sources = [ + "channel/device_auth_namespace_handler.cc", + "channel/device_auth_namespace_handler.h", + "channel/receiver_socket_factory.cc", + "channel/receiver_socket_factory.h", + ] + + public_deps = [ + "../../platform", + "../../third_party/abseil", + "../../third_party/boringssl", + "../common:channel", + ] + + deps = [ + "../../util", + "../common:certificate", + "../common/channel/proto:channel_proto", + ] +} + +source_set("test_helpers") { + testonly = true + sources = [ + "channel/testing/device_auth_test_helpers.cc", + "channel/testing/device_auth_test_helpers.h", + ] + + public_deps = [ + ":channel", + "../../third_party/boringssl", + "../common:test_helpers", + ] + deps = [ + "../../third_party/googletest:gtest", + ] +} + +source_set("unittests") { + testonly = true + sources = [ + "channel/device_auth_namespace_handler_unittest.cc", + ] + + deps = [ + ":channel", + ":test_helpers", + "../../testing/util", + "../../third_party/googletest:gmock", + "../../third_party/googletest:gtest", + "../common:channel", + "../common/channel/proto:channel_proto", + ] +} diff --git a/cast/receiver/DEPS b/cast/receiver/DEPS index 7bdadde7..a2def1b0 100644 --- a/cast/receiver/DEPS +++ b/cast/receiver/DEPS @@ -2,6 +2,6 @@ include_rules = [ # libcast receiver code must not depend on the sender. - '+cast/common/public', + '+cast/common', '+cast/receiver' ] diff --git a/cast/receiver/channel/device_auth_namespace_handler.cc b/cast/receiver/channel/device_auth_namespace_handler.cc new file mode 100644 index 00000000..84fa217a --- /dev/null +++ b/cast/receiver/channel/device_auth_namespace_handler.cc @@ -0,0 +1,140 @@ +// Copyright 2019 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 "cast/receiver/channel/device_auth_namespace_handler.h" + +#include <openssl/evp.h> + +#include "cast/common/certificate/cast_cert_validator.h" +#include "cast/common/channel/message_util.h" +#include "cast/common/channel/proto/cast_channel.pb.h" +#include "cast/common/channel/virtual_connection.h" +#include "cast/common/channel/virtual_connection_router.h" +#include "platform/base/tls_credentials.h" +#include "util/crypto/digest_sign.h" + +namespace cast { +namespace channel { +namespace { + +CastMessage GenerateErrorMessage(AuthError::ErrorType error_type) { + DeviceAuthMessage message; + AuthError* error = message.mutable_error(); + error->set_error_type(error_type); + std::string payload; + message.SerializeToString(&payload); + + CastMessage response; + response.set_protocol_version(CastMessage_ProtocolVersion_CASTV2_1_0); + response.set_namespace_(kAuthNamespace); + response.set_payload_type(CastMessage_PayloadType_BINARY); + response.set_payload_binary(std::move(payload)); + return response; +} + +} // namespace + +DeviceAuthNamespaceHandler::DeviceAuthNamespaceHandler( + CredentialsProvider* creds_provider) + : creds_provider_(creds_provider) {} + +DeviceAuthNamespaceHandler::~DeviceAuthNamespaceHandler() = default; + +void DeviceAuthNamespaceHandler::OnMessage(VirtualConnectionRouter* router, + CastSocket* socket, + CastMessage&& message) { + if (message.payload_type() != CastMessage_PayloadType_BINARY) { + return; + } + const std::string& payload = message.payload_binary(); + DeviceAuthMessage device_auth_message; + if (!device_auth_message.ParseFromArray(payload.data(), payload.length())) { + // TODO(btolsch): Consider all of these cases for future error reporting + // mechanism. + return; + } + + if (!device_auth_message.has_challenge()) { + return; + } + + if (device_auth_message.has_response() || device_auth_message.has_error()) { + return; + } + + const VirtualConnection virtual_conn{ + message.destination_id(), message.source_id(), socket->socket_id()}; + const AuthChallenge& challenge = device_auth_message.challenge(); + const SignatureAlgorithm sig_alg = challenge.signature_algorithm(); + HashAlgorithm hash_alg = challenge.hash_algorithm(); + // TODO(btolsch): Reconsider supporting SHA1 after further metrics + // investigation. + if ((sig_alg != UNSPECIFIED && sig_alg != RSASSA_PKCS1v15) || + (hash_alg != SHA1 && hash_alg != SHA256)) { + router->SendMessage( + virtual_conn, + GenerateErrorMessage(AuthError::SIGNATURE_ALGORITHM_UNAVAILABLE)); + return; + } + const EVP_MD* digest = hash_alg == SHA256 ? EVP_sha256() : EVP_sha1(); + + const absl::Span<const uint8_t> tls_cert_der = + creds_provider_->GetCurrentTlsCertAsDer(); + const DeviceCredentials& device_creds = + creds_provider_->GetCurrentDeviceCredentials(); + if (tls_cert_der.empty() || device_creds.certs.empty() || + !device_creds.private_key) { + // TODO(btolsch): Add this to future error reporting. + router->SendMessage(virtual_conn, + GenerateErrorMessage(AuthError::INTERNAL_ERROR)); + return; + } + + std::unique_ptr<AuthResponse> auth_response(new AuthResponse()); + auth_response->set_client_auth_certificate(device_creds.certs[0]); + for (auto it = device_creds.certs.begin() + 1; it != device_creds.certs.end(); + ++it) { + auth_response->add_intermediate_certificates(*it); + } + auth_response->set_signature_algorithm(RSASSA_PKCS1v15); + auth_response->set_hash_algorithm(hash_alg); + std::string sender_nonce; + if (challenge.has_sender_nonce()) { + sender_nonce = challenge.sender_nonce(); + auth_response->set_sender_nonce(sender_nonce); + } + + auth_response->set_crl(device_creds.serialized_crl); + + std::vector<uint8_t> to_be_signed; + to_be_signed.reserve(sender_nonce.size() + tls_cert_der.size()); + to_be_signed.insert(to_be_signed.end(), sender_nonce.begin(), + sender_nonce.end()); + to_be_signed.insert(to_be_signed.end(), tls_cert_der.begin(), + tls_cert_der.end()); + + openscreen::ErrorOr<std::string> signature = openscreen::SignData( + digest, device_creds.private_key.get(), to_be_signed); + if (!signature) { + router->SendMessage(virtual_conn, + GenerateErrorMessage(AuthError::INTERNAL_ERROR)); + return; + } + auth_response->set_signature(std::move(signature.value())); + + DeviceAuthMessage response_auth_message; + response_auth_message.set_allocated_response(auth_response.release()); + + std::string response_string; + response_auth_message.SerializeToString(&response_string); + CastMessage response; + response.set_protocol_version(CastMessage_ProtocolVersion_CASTV2_1_0); + response.set_namespace_(kAuthNamespace); + response.set_payload_type(CastMessage_PayloadType_BINARY); + response.set_payload_binary(std::move(response_string)); + router->SendMessage(virtual_conn, std::move(response)); +} + +} // namespace channel +} // namespace cast diff --git a/cast/receiver/channel/device_auth_namespace_handler.h b/cast/receiver/channel/device_auth_namespace_handler.h new file mode 100644 index 00000000..e5e867cd --- /dev/null +++ b/cast/receiver/channel/device_auth_namespace_handler.h @@ -0,0 +1,57 @@ +// Copyright 2019 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. + +#ifndef CAST_RECEIVER_CHANNEL_DEVICE_AUTH_NAMESPACE_HANDLER_H_ +#define CAST_RECEIVER_CHANNEL_DEVICE_AUTH_NAMESPACE_HANDLER_H_ + +#include <openssl/evp.h> + +#include <string> +#include <vector> + +#include "absl/types/span.h" +#include "cast/common/channel/cast_message_handler.h" + +namespace cast { +namespace channel { + +struct DeviceCredentials { + // The device's certificate chain in DER form, where |certs[0]| is the + // device's certificate and |certs[certs.size()-1]| is the last intermediate + // before a Cast root certificate. + std::vector<std::string> certs; + + // The device's private key that corresponds to the certificate in |certs[0]|. + bssl::UniquePtr<EVP_PKEY> private_key; + + // If non-empty, this contains a serialized CrlBundle protobuf. This may be + // used by the sender as part of verifying |certs|. + std::string serialized_crl; +}; + +class DeviceAuthNamespaceHandler final : public CastMessageHandler { + public: + class CredentialsProvider { + public: + virtual absl::Span<const uint8_t> GetCurrentTlsCertAsDer() = 0; + virtual const DeviceCredentials& GetCurrentDeviceCredentials() = 0; + }; + + // |creds_provider| must outlive |this|. + explicit DeviceAuthNamespaceHandler(CredentialsProvider* creds_provider); + ~DeviceAuthNamespaceHandler(); + + // CastMessageHandler overrides. + void OnMessage(VirtualConnectionRouter* router, + CastSocket* socket, + CastMessage&& message) override; + + private: + CredentialsProvider* const creds_provider_; +}; + +} // namespace channel +} // namespace cast + +#endif // CAST_RECEIVER_CHANNEL_DEVICE_AUTH_NAMESPACE_HANDLER_H_ diff --git a/cast/receiver/channel/device_auth_namespace_handler_unittest.cc b/cast/receiver/channel/device_auth_namespace_handler_unittest.cc new file mode 100644 index 00000000..13d8104d --- /dev/null +++ b/cast/receiver/channel/device_auth_namespace_handler_unittest.cc @@ -0,0 +1,204 @@ +// Copyright 2019 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 "cast/receiver/channel/device_auth_namespace_handler.h" + +#include "cast/common/channel/cast_socket.h" +#include "cast/common/channel/message_util.h" +#include "cast/common/channel/proto/cast_channel.pb.h" +#include "cast/common/channel/test/fake_cast_socket.h" +#include "cast/common/channel/test/mock_socket_error_handler.h" +#include "cast/common/channel/virtual_connection_manager.h" +#include "cast/common/channel/virtual_connection_router.h" +#include "cast/receiver/channel/testing/device_auth_test_helpers.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "testing/util/read_file.h" + +namespace cast { +namespace channel { +namespace { + +using ::testing::_; +using ::testing::ElementsAreArray; +using ::testing::Invoke; + +class DeviceAuthNamespaceHandlerTest : public ::testing::Test { + public: + void SetUp() override { + socket_ = fake_cast_socket_pair_.socket.get(); + router_.TakeSocket(&mock_error_handler_, + std::move(fake_cast_socket_pair_.socket)); + router_.AddHandlerForLocalId(kPlatformReceiverId, &auth_handler_); + } + + protected: + FakeCastSocketPair fake_cast_socket_pair_; + MockSocketErrorHandler mock_error_handler_; + CastSocket* socket_; + + StaticCredentialsProvider creds_; + VirtualConnectionManager manager_; + VirtualConnectionRouter router_{&manager_}; + DeviceAuthNamespaceHandler auth_handler_{&creds_}; +}; + +} // namespace + +#define TEST_DATA_PREFIX OPENSCREEN_TEST_DATA_DIR "cast/receiver/channel/" + +// The tests in this file use a pre-recorded AuthChallenge as input and a +// matching pre-recorded AuthResponse for verification. This is to make it +// easier to keep sender and receiver code separate, because the code that would +// really generate an AuthChallenge and verify an AuthResponse is under +// //cast/sender. The pre-recorded messages come from an integration test which +// _does_ properly call both sender and receiver sides, but can optionally +// record the messages for use in these unit tests. That test is currently +// under //cast/test. See //cast/test/README.md for more information. +// +// The tests generally follow this procedure: +// 1. Read a fake device certificate chain + TLS certificate from disk. +// 2. Read a pre-recorded CastMessage proto containing an AuthChallenge. +// 3. Send this CastMessage over a CastSocket to a DeviceAuthNamespaceHandler. +// 4. Catch the CastMessage response and check that it has an AuthResponse. +// 5. Check the AuthResponse against another pre-recorded protobuf. + +TEST_F(DeviceAuthNamespaceHandlerTest, AuthResponse) { + InitStaticCredentialsFromFiles( + &creds_, nullptr, nullptr, TEST_DATA_PREFIX "device_key.pem", + TEST_DATA_PREFIX "device_chain.pem", TEST_DATA_PREFIX "device_tls.pem"); + + // Send an auth challenge. |auth_handler_| will automatically respond via + // |router_| and we will catch the result in |challenge_reply|. + CastMessage auth_challenge; + const std::string auth_challenge_string = + openscreen::ReadEntireFileToString(TEST_DATA_PREFIX "auth_challenge.pb"); + ASSERT_TRUE(auth_challenge.ParseFromString(auth_challenge_string)); + + CastMessage challenge_reply; + EXPECT_CALL(fake_cast_socket_pair_.mock_peer_client, OnMessage(_, _)) + .WillOnce( + Invoke([&challenge_reply](CastSocket* socket, CastMessage message) { + challenge_reply = std::move(message); + })); + fake_cast_socket_pair_.peer_socket->SendMessage(std::move(auth_challenge)); + + const std::string auth_response_string = + openscreen::ReadEntireFileToString(TEST_DATA_PREFIX "auth_response.pb"); + AuthResponse expected_auth_response; + ASSERT_TRUE(expected_auth_response.ParseFromString(auth_response_string)); + + DeviceAuthMessage auth_message; + ASSERT_EQ(challenge_reply.payload_type(), CastMessage_PayloadType_BINARY); + ASSERT_TRUE(auth_message.ParseFromString(challenge_reply.payload_binary())); + ASSERT_TRUE(auth_message.has_response()); + ASSERT_FALSE(auth_message.has_challenge()); + ASSERT_FALSE(auth_message.has_error()); + const AuthResponse& auth_response = auth_message.response(); + + EXPECT_EQ(expected_auth_response.signature(), auth_response.signature()); + EXPECT_EQ(expected_auth_response.client_auth_certificate(), + auth_response.client_auth_certificate()); + EXPECT_EQ(expected_auth_response.signature_algorithm(), + auth_response.signature_algorithm()); + EXPECT_EQ(expected_auth_response.sender_nonce(), + auth_response.sender_nonce()); + EXPECT_EQ(expected_auth_response.hash_algorithm(), + auth_response.hash_algorithm()); + EXPECT_EQ(expected_auth_response.crl(), auth_response.crl()); + EXPECT_THAT( + auth_response.intermediate_certificates(), + ElementsAreArray(expected_auth_response.intermediate_certificates())); +} + +TEST_F(DeviceAuthNamespaceHandlerTest, BadNonce) { + InitStaticCredentialsFromFiles( + &creds_, nullptr, nullptr, TEST_DATA_PREFIX "device_key.pem", + TEST_DATA_PREFIX "device_chain.pem", TEST_DATA_PREFIX "device_tls.pem"); + + // Send an auth challenge. |auth_handler_| will automatically respond via + // |router_| and we will catch the result in |challenge_reply|. + CastMessage auth_challenge; + const std::string auth_challenge_string = + openscreen::ReadEntireFileToString(TEST_DATA_PREFIX "auth_challenge.pb"); + ASSERT_TRUE(auth_challenge.ParseFromString(auth_challenge_string)); + + // Change the nonce to be different from what was used to record the correct + // response originally. + DeviceAuthMessage msg; + ASSERT_EQ(auth_challenge.payload_type(), CastMessage_PayloadType_BINARY); + ASSERT_TRUE(msg.ParseFromString(auth_challenge.payload_binary())); + ASSERT_TRUE(msg.has_challenge()); + std::string* nonce = msg.mutable_challenge()->mutable_sender_nonce(); + (*nonce)[0] = ~(*nonce)[0]; + std::string new_payload; + ASSERT_TRUE(msg.SerializeToString(&new_payload)); + auth_challenge.set_payload_binary(new_payload); + + CastMessage challenge_reply; + EXPECT_CALL(fake_cast_socket_pair_.mock_peer_client, OnMessage(_, _)) + .WillOnce( + Invoke([&challenge_reply](CastSocket* socket, CastMessage message) { + challenge_reply = std::move(message); + })); + fake_cast_socket_pair_.peer_socket->SendMessage(std::move(auth_challenge)); + + const std::string auth_response_string = + openscreen::ReadEntireFileToString(TEST_DATA_PREFIX "auth_response.pb"); + AuthResponse expected_auth_response; + ASSERT_TRUE(expected_auth_response.ParseFromString(auth_response_string)); + + DeviceAuthMessage auth_message; + ASSERT_EQ(challenge_reply.payload_type(), CastMessage_PayloadType_BINARY); + ASSERT_TRUE(auth_message.ParseFromString(challenge_reply.payload_binary())); + ASSERT_TRUE(auth_message.has_response()); + ASSERT_FALSE(auth_message.has_challenge()); + ASSERT_FALSE(auth_message.has_error()); + const AuthResponse& auth_response = auth_message.response(); + + // NOTE: This is the ultimate result of the nonce-mismatch. + EXPECT_NE(expected_auth_response.signature(), auth_response.signature()); +} + +TEST_F(DeviceAuthNamespaceHandlerTest, UnsupportedSignatureAlgorithm) { + InitStaticCredentialsFromFiles( + &creds_, nullptr, nullptr, TEST_DATA_PREFIX "device_key.pem", + TEST_DATA_PREFIX "device_chain.pem", TEST_DATA_PREFIX "device_tls.pem"); + + // Send an auth challenge. |auth_handler_| will automatically respond via + // |router_| and we will catch the result in |challenge_reply|. + CastMessage auth_challenge; + const std::string auth_challenge_string = + openscreen::ReadEntireFileToString(TEST_DATA_PREFIX "auth_challenge.pb"); + ASSERT_TRUE(auth_challenge.ParseFromString(auth_challenge_string)); + + // Change the signature algorithm an unsupported value. + DeviceAuthMessage msg; + ASSERT_EQ(auth_challenge.payload_type(), CastMessage_PayloadType_BINARY); + ASSERT_TRUE(msg.ParseFromString(auth_challenge.payload_binary())); + ASSERT_TRUE(msg.has_challenge()); + msg.mutable_challenge()->set_signature_algorithm( + SignatureAlgorithm::RSASSA_PSS); + std::string new_payload; + ASSERT_TRUE(msg.SerializeToString(&new_payload)); + auth_challenge.set_payload_binary(new_payload); + + CastMessage challenge_reply; + EXPECT_CALL(fake_cast_socket_pair_.mock_peer_client, OnMessage(_, _)) + .WillOnce( + Invoke([&challenge_reply](CastSocket* socket, CastMessage message) { + challenge_reply = std::move(message); + })); + fake_cast_socket_pair_.peer_socket->SendMessage(std::move(auth_challenge)); + + DeviceAuthMessage auth_message; + ASSERT_EQ(challenge_reply.payload_type(), CastMessage_PayloadType_BINARY); + ASSERT_TRUE(auth_message.ParseFromString(challenge_reply.payload_binary())); + ASSERT_FALSE(auth_message.has_response()); + ASSERT_FALSE(auth_message.has_challenge()); + ASSERT_TRUE(auth_message.has_error()); +} + +} // namespace channel +} // namespace cast diff --git a/cast/receiver/channel/receiver_socket_factory.cc b/cast/receiver/channel/receiver_socket_factory.cc new file mode 100644 index 00000000..a555717a --- /dev/null +++ b/cast/receiver/channel/receiver_socket_factory.cc @@ -0,0 +1,50 @@ +// Copyright 2019 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 "cast/receiver/channel/receiver_socket_factory.h" + +#include "util/logging.h" + +namespace cast { +namespace channel { + +ReceiverSocketFactory::ReceiverSocketFactory(Client* client, + CastSocket::Client* socket_client) + : client_(client), socket_client_(socket_client) { + OSP_DCHECK(client); + OSP_DCHECK(socket_client); +} + +ReceiverSocketFactory::~ReceiverSocketFactory() = default; + +void ReceiverSocketFactory::OnAccepted( + TlsConnectionFactory* factory, + std::vector<uint8_t> der_x509_peer_cert, + std::unique_ptr<TlsConnection> connection) { + IPEndpoint endpoint = connection->GetRemoteEndpoint(); + auto socket = std::make_unique<CastSocket>(std::move(connection), + socket_client_, GetNextSocketId()); + client_->OnConnected(this, endpoint, std::move(socket)); +} + +void ReceiverSocketFactory::OnConnected( + TlsConnectionFactory* factory, + std::vector<uint8_t> der_x509_peer_cert, + std::unique_ptr<TlsConnection> connection) { + OSP_NOTREACHED() << "This factory is accept-only."; +} + +void ReceiverSocketFactory::OnConnectionFailed( + TlsConnectionFactory* factory, + const IPEndpoint& remote_address) { + OSP_DVLOG << "Receiving connection from endpoint failed: " << remote_address; +} + +void ReceiverSocketFactory::OnError(TlsConnectionFactory* factory, + Error error) { + client_->OnError(this, error); +} + +} // namespace channel +} // namespace cast diff --git a/cast/receiver/channel/receiver_socket_factory.h b/cast/receiver/channel/receiver_socket_factory.h new file mode 100644 index 00000000..573da4d0 --- /dev/null +++ b/cast/receiver/channel/receiver_socket_factory.h @@ -0,0 +1,55 @@ +// Copyright 2019 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. + +#ifndef CAST_RECEIVER_CHANNEL_RECEIVER_SOCKET_FACTORY_H_ +#define CAST_RECEIVER_CHANNEL_RECEIVER_SOCKET_FACTORY_H_ + +#include <vector> + +#include "cast/common/channel/cast_socket.h" +#include "platform/api/tls_connection_factory.h" +#include "platform/base/ip_address.h" + +namespace cast { +namespace channel { + +using openscreen::Error; +using openscreen::IPEndpoint; +using openscreen::TlsConnection; +using openscreen::TlsConnectionFactory; + +class ReceiverSocketFactory final : public TlsConnectionFactory::Client { + public: + class Client { + public: + virtual void OnConnected(ReceiverSocketFactory* factory, + const IPEndpoint& endpoint, + std::unique_ptr<CastSocket> socket) = 0; + virtual void OnError(ReceiverSocketFactory* factory, Error error) = 0; + }; + + // |client| and |socket_client| must outlive |this|. + ReceiverSocketFactory(Client* client, CastSocket::Client* socket_client); + ~ReceiverSocketFactory(); + + // TlsConnectionFactory::Client overrides. + void OnAccepted(TlsConnectionFactory* factory, + std::vector<uint8_t> der_x509_peer_cert, + std::unique_ptr<TlsConnection> connection) override; + void OnConnected(TlsConnectionFactory* factory, + std::vector<uint8_t> der_x509_peer_cert, + std::unique_ptr<TlsConnection> connection) override; + void OnConnectionFailed(TlsConnectionFactory* factory, + const IPEndpoint& remote_address) override; + void OnError(TlsConnectionFactory* factory, Error error) override; + + private: + Client* const client_; + CastSocket::Client* const socket_client_; +}; + +} // namespace channel +} // namespace cast + +#endif // CAST_RECEIVER_CHANNEL_RECEIVER_SOCKET_FACTORY_H_ diff --git a/cast/receiver/channel/testing/device_auth_test_helpers.cc b/cast/receiver/channel/testing/device_auth_test_helpers.cc new file mode 100644 index 00000000..dace8429 --- /dev/null +++ b/cast/receiver/channel/testing/device_auth_test_helpers.cc @@ -0,0 +1,51 @@ +// Copyright 2019 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 "cast/receiver/channel/testing/device_auth_test_helpers.h" + +#include "gtest/gtest.h" + +namespace cast { +namespace channel { + +void InitStaticCredentialsFromFiles(StaticCredentialsProvider* creds, + bssl::UniquePtr<X509>* parsed_cert, + certificate::TrustStore* fake_trust_store, + absl::string_view privkey_filename, + absl::string_view chain_filename, + absl::string_view tls_filename) { + auto private_key = certificate::testing::ReadKeyFromPemFile(privkey_filename); + ASSERT_TRUE(private_key); + std::vector<std::string> certs = + certificate::testing::ReadCertificatesFromPemFile(chain_filename); + ASSERT_GT(certs.size(), 1u); + + // Use the root of the chain as the trust store for the test. + auto* data = reinterpret_cast<const uint8_t*>(certs.back().data()); + auto fake_root = + bssl::UniquePtr<X509>(d2i_X509(nullptr, &data, certs.back().size())); + ASSERT_TRUE(fake_root); + certs.pop_back(); + if (fake_trust_store) { + fake_trust_store->certs.emplace_back(fake_root.release()); + } + + creds->device_creds = DeviceCredentials{ + std::move(certs), std::move(private_key), std::string()}; + + const std::vector<std::string> tls_cert = + certificate::testing::ReadCertificatesFromPemFile(tls_filename); + ASSERT_EQ(tls_cert.size(), 1u); + data = reinterpret_cast<const uint8_t*>(tls_cert[0].data()); + if (parsed_cert) { + *parsed_cert = + bssl::UniquePtr<X509>(d2i_X509(nullptr, &data, tls_cert[0].size())); + ASSERT_TRUE(*parsed_cert); + } + const auto* begin = reinterpret_cast<const uint8_t*>(tls_cert[0].data()); + creds->tls_cert_der.assign(begin, begin + tls_cert[0].size()); +} + +} // namespace channel +} // namespace cast diff --git a/cast/receiver/channel/testing/device_auth_test_helpers.h b/cast/receiver/channel/testing/device_auth_test_helpers.h new file mode 100644 index 00000000..81f8266b --- /dev/null +++ b/cast/receiver/channel/testing/device_auth_test_helpers.h @@ -0,0 +1,46 @@ +// Copyright 2019 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. + +#ifndef CAST_RECEIVER_CHANNEL_TESTING_DEVICE_AUTH_TEST_HELPERS_H_ +#define CAST_RECEIVER_CHANNEL_TESTING_DEVICE_AUTH_TEST_HELPERS_H_ + +#include <openssl/x509.h> + +#include <vector> + +#include "absl/strings/string_view.h" +#include "cast/common/certificate/test_helpers.h" +#include "cast/receiver/channel/device_auth_namespace_handler.h" + +namespace cast { +namespace channel { + +class StaticCredentialsProvider final + : public DeviceAuthNamespaceHandler::CredentialsProvider { + public: + StaticCredentialsProvider() = default; + ~StaticCredentialsProvider() = default; + + absl::Span<const uint8_t> GetCurrentTlsCertAsDer() override { + return absl::Span<uint8_t>(tls_cert_der); + } + const DeviceCredentials& GetCurrentDeviceCredentials() override { + return device_creds; + } + + DeviceCredentials device_creds; + std::vector<uint8_t> tls_cert_der; +}; + +void InitStaticCredentialsFromFiles(StaticCredentialsProvider* creds, + bssl::UniquePtr<X509>* parsed_cert, + certificate::TrustStore* fake_trust_store, + absl::string_view privkey_filename, + absl::string_view chain_filename, + absl::string_view tls_filename); + +} // namespace channel +} // namespace cast + +#endif // CAST_RECEIVER_CHANNEL_TESTING_DEVICE_AUTH_TEST_HELPERS_H_ diff --git a/cast/sender/BUILD.gn b/cast/sender/BUILD.gn index f42d555c..ae36bf27 100644 --- a/cast/sender/BUILD.gn +++ b/cast/sender/BUILD.gn @@ -22,6 +22,8 @@ source_set("channel") { public_deps = [ "../../platform", "../../third_party/boringssl", + "../common:certificate", + "../common:channel", ] } @@ -34,6 +36,7 @@ source_set("unittests") { deps = [ ":channel", "../../platform", + "../../testing/util", "../../third_party/googletest:gtest", "../common/certificate/proto:certificate_proto", "../common/certificate/proto:certificate_unittest_proto", diff --git a/cast/sender/channel/cast_auth_util.cc b/cast/sender/channel/cast_auth_util.cc index 90d63f77..44a75b98 100644 --- a/cast/sender/channel/cast_auth_util.cc +++ b/cast/sender/channel/cast_auth_util.cc @@ -11,6 +11,7 @@ #include "cast/common/certificate/cast_cert_validator.h" #include "cast/common/certificate/cast_cert_validator_internal.h" #include "cast/common/certificate/cast_crl.h" +#include "cast/common/channel/proto/cast_channel.pb.h" #include "platform/api/time.h" #include "platform/base/error.h" #include "util/logging.h" @@ -222,18 +223,31 @@ openscreen::Error VerifyTLSCertificateValidity( return openscreen::Error::None(); } -ErrorOr<CastDeviceCertPolicy> AuthenticateChallengeReply( +ErrorOr<CastDeviceCertPolicy> VerifyCredentialsImpl( + const AuthResponse& response, + const std::string& signature_input, + const certificate::CRLPolicy& crl_policy, + certificate::TrustStore* cast_trust_store, + certificate::TrustStore* crl_trust_store, + const certificate::DateTime& verification_time, + bool enforce_sha256_checking); + +ErrorOr<CastDeviceCertPolicy> AuthenticateChallengeReplyImpl( const CastMessage& challenge_reply, X509* peer_cert, - const AuthContext& auth_context) { + const AuthContext& auth_context, + const certificate::CRLPolicy& crl_policy, + certificate::TrustStore* cast_trust_store, + certificate::TrustStore* crl_trust_store, + const certificate::DateTime& verification_time) { DeviceAuthMessage auth_message; openscreen::Error result = ParseAuthMessage(challenge_reply, &auth_message); if (!result.ok()) { return result; } - result = VerifyTLSCertificateValidity( - peer_cert, openscreen::GetWallTimeSinceUnixEpoch()); + result = VerifyTLSCertificateValidity(peer_cert, + DateTimeToSeconds(verification_time)); if (!result.ok()) { return result; } @@ -261,7 +275,35 @@ ErrorOr<CastDeviceCertPolicy> AuthenticateChallengeReply( OSP_DCHECK_EQ(actual_size, peer_cert_der.size()); peer_cert_der.resize(actual_size); - return VerifyCredentials(response, nonce_response + peer_cert_der); + return VerifyCredentialsImpl(response, nonce_response + peer_cert_der, + crl_policy, cast_trust_store, crl_trust_store, + verification_time, false); +} + +ErrorOr<CastDeviceCertPolicy> AuthenticateChallengeReply( + const CastMessage& challenge_reply, + X509* peer_cert, + const AuthContext& auth_context) { + certificate::DateTime now = {}; + OSP_CHECK(certificate::DateTimeFromSeconds( + openscreen::GetWallTimeSinceUnixEpoch().count(), &now)); + certificate::CRLPolicy policy = certificate::CRLPolicy::kCrlOptional; + return AuthenticateChallengeReplyImpl( + challenge_reply, peer_cert, auth_context, policy, + /* cast_trust_store */ nullptr, /* crl_trust_store */ nullptr, now); +} + +ErrorOr<CastDeviceCertPolicy> AuthenticateChallengeReplyForTest( + const CastMessage& challenge_reply, + X509* peer_cert, + const AuthContext& auth_context, + certificate::CRLPolicy crl_policy, + certificate::TrustStore* cast_trust_store, + certificate::TrustStore* crl_trust_store, + const certificate::DateTime& verification_time) { + return AuthenticateChallengeReplyImpl( + challenge_reply, peer_cert, auth_context, crl_policy, cast_trust_store, + crl_trust_store, verification_time); } // This function does the following @@ -301,8 +343,8 @@ ErrorOr<CastDeviceCertPolicy> VerifyCredentialsImpl( std::vector<std::string> cert_chain; cert_chain.push_back(response.client_auth_certificate()); cert_chain.insert(cert_chain.end(), - response.intermediate_certificate().begin(), - response.intermediate_certificate().end()); + response.intermediate_certificates().begin(), + response.intermediate_certificates().end()); // Parse the CRL. std::unique_ptr<certificate::CastCRL> crl; diff --git a/cast/sender/channel/cast_auth_util.h b/cast/sender/channel/cast_auth_util.h index 35f1d028..b3ea452f 100644 --- a/cast/sender/channel/cast_auth_util.h +++ b/cast/sender/channel/cast_auth_util.h @@ -7,10 +7,10 @@ #include <openssl/x509.h> +#include <chrono> #include <string> #include "cast/common/certificate/cast_cert_validator.h" -#include "cast/common/channel/proto/cast_channel.pb.h" #include "platform/base/error.h" namespace cast { @@ -61,6 +61,19 @@ ErrorOr<CastDeviceCertPolicy> AuthenticateChallengeReply( X509* peer_cert, const AuthContext& auth_context); +// Exposed for testing only. +// +// Overloaded version of AuthenticateChallengeReply that allows modifying the +// crl policy, trust stores, and verification times. +ErrorOr<CastDeviceCertPolicy> AuthenticateChallengeReplyForTest( + const CastMessage& challenge_reply, + X509* peer_cert, + const AuthContext& auth_context, + certificate::CRLPolicy crl_policy, + certificate::TrustStore* cast_trust_store, + certificate::TrustStore* crl_trust_store, + const certificate::DateTime& verification_time); + // Performs a quick check of the TLS certificate for time validity requirements. openscreen::Error VerifyTLSCertificateValidity( X509* peer_cert, @@ -77,8 +90,8 @@ ErrorOr<CastDeviceCertPolicy> VerifyCredentials( // Exposed for testing only. // -// Overloaded version of VerifyCredentials that allows modifying -// the crl policy, trust stores, and verification times. +// Overloaded version of VerifyCredentials that allows modifying the crl policy, +// trust stores, and verification times. ErrorOr<CastDeviceCertPolicy> VerifyCredentialsForTest( const AuthResponse& response, const std::string& signature_input, diff --git a/cast/sender/channel/cast_auth_util_unittest.cc b/cast/sender/channel/cast_auth_util_unittest.cc index b8a8e90c..a4b341e2 100644 --- a/cast/sender/channel/cast_auth_util_unittest.cc +++ b/cast/sender/channel/cast_auth_util_unittest.cc @@ -13,6 +13,7 @@ #include "cast/common/channel/proto/cast_channel.pb.h" #include "gtest/gtest.h" #include "platform/api/time.h" +#include "testing/util/read_file.h" #include "util/logging.h" namespace cast { @@ -123,8 +124,9 @@ class CastAuthUtilTest : public testing::Test { AuthResponse response; response.set_client_auth_certificate(chain[0]); - for (size_t i = 1; i < chain.size(); ++i) - response.add_intermediate_certificate(chain[i]); + for (size_t i = 1; i < chain.size(); ++i) { + response.add_intermediate_certificates(chain[i]); + } response.set_hash_algorithm(digest_algorithm); switch (digest_algorithm) { @@ -168,7 +170,7 @@ TEST_F(CastAuthUtilTest, VerifySuccess) { TEST_F(CastAuthUtilTest, VerifyBadCA) { std::string signed_data; AuthResponse auth_response = CreateAuthResponse(&signed_data, SHA256); - MangleString(auth_response.mutable_intermediate_certificate(0)); + MangleString(auth_response.mutable_intermediate_certificates(0)); ErrorOr<CastDeviceCertPolicy> result = VerifyCredentials(auth_response, signed_data); EXPECT_FALSE(result); @@ -354,8 +356,9 @@ ErrorOr<CastDeviceCertPolicy> TestVerifyRevocation( if (certificate_chain.size() > 0) { response.set_client_auth_certificate(certificate_chain[0]); - for (size_t i = 1; i < certificate_chain.size(); ++i) - response.add_intermediate_certificate(certificate_chain[i]); + for (size_t i = 1; i < certificate_chain.size(); ++i) { + response.add_intermediate_certificates(certificate_chain[i]); + } } response.set_crl(crl_bundle); @@ -447,7 +450,7 @@ bool RunTest(const certificate::DeviceCertTest& test_case) { // These tests are generated by a test generator in google3. void RunTestSuite(const std::string& test_suite_file_name) { std::string testsuite_raw = - certificate::testing::ReadEntireFileToString(test_suite_file_name); + openscreen::ReadEntireFileToString(test_suite_file_name); certificate::DeviceCertTestSuite test_suite; EXPECT_TRUE(test_suite.ParseFromString(testsuite_raw)); uint16_t successes = 0; diff --git a/cast/streaming/receiver_session_unittest.cc b/cast/streaming/receiver_session_unittest.cc index 4716a6a3..ba775a88 100644 --- a/cast/streaming/receiver_session_unittest.cc +++ b/cast/streaming/receiver_session_unittest.cc @@ -222,7 +222,7 @@ void ExpectIsErrorAnswerMessage( const Json::Value& error = message["error"]; EXPECT_TRUE(error.isObject()); - EXPECT_EQ(83, error["code"].asInt()); + EXPECT_GT(error["code"].asInt(), 0); EXPECT_EQ("", error["description"].asString()); } diff --git a/cast/test/BUILD.gn b/cast/test/BUILD.gn new file mode 100644 index 00000000..ef2eb3a8 --- /dev/null +++ b/cast/test/BUILD.gn @@ -0,0 +1,38 @@ +# Copyright 2019 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. + +source_set("unittests") { + testonly = true + sources = [ + "device_auth_test.cc", + ] + + deps = [ + "../../testing/util", + "../../third_party/googletest:gmock", + "../../third_party/googletest:gtest", + "../common:channel", + "../common:test_helpers", + "../common/channel/proto:channel_proto", + "../receiver:channel", + "../receiver:test_helpers", + "../sender:channel", + ] +} + +if (is_posix) { + executable("make_crl_tests") { + testonly = true + sources = [ + "make_crl_tests.cc", + ] + + deps = [ + "../../third_party/boringssl", + "../../util", + "../common:test_helpers", + "../common/certificate/proto:certificate_proto", + ] + } +} diff --git a/cast/test/DEPS b/cast/test/DEPS new file mode 100644 index 00000000..e43aeddb --- /dev/null +++ b/cast/test/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + '+cast/common', + '+cast/receiver', + '+cast/sender', +] diff --git a/cast/test/README.md b/cast/test/README.md new file mode 100644 index 00000000..adf17d9a --- /dev/null +++ b/cast/test/README.md @@ -0,0 +1,28 @@ +# Cast Integration Tests + +This file contains notes about the integration tests under this directory, +including how they tie into other systems or tests if relevant. + +## Device Authentication + +The tests in `device_auth_test.cc` verify sender and receiver authentication +code against each other to ensure we are at least self-consistent. These tests +encompass successful device authentication, authentication errors, +authenticating with a revocation list, and various revocation list failures. + +In order to enforce sender and receiver code separation, these tests can also +record the protobuf data they generate for use in unit tests. For example, a +`CastMessage` containing an `AuthChallenge` from sender code can be used as +fixed input to receiver code. Currently, only receiver code uses this kind of +data because the sender code just uses existing test data imported from +Chromium. + +New test data may need to be generated if a bug is found in either sender or +receiver code or if new test certificates need to be used. To generate new +data, build and run `make_crl_tests` and run this specific integration test: +``` bash +$ out/Debug/openscreen_unittests --gtest_filter=DeviceAuthTest.MANUAL_SerializeTestData +``` +Note that this test will not run without being exactly named in the filter. The +paths to which they will write are fixed and are the same as from where the +tests expect to read. diff --git a/cast/test/device_auth_test.cc b/cast/test/device_auth_test.cc new file mode 100644 index 00000000..3d30e43b --- /dev/null +++ b/cast/test/device_auth_test.cc @@ -0,0 +1,208 @@ +// Copyright 2019 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 <stdio.h> + +#include "cast/common/certificate/test_helpers.h" +#include "cast/common/channel/cast_socket.h" +#include "cast/common/channel/proto/cast_channel.pb.h" +#include "cast/common/channel/test/fake_cast_socket.h" +#include "cast/common/channel/test/mock_socket_error_handler.h" +#include "cast/common/channel/virtual_connection_manager.h" +#include "cast/common/channel/virtual_connection_router.h" +#include "cast/receiver/channel/device_auth_namespace_handler.h" +#include "cast/receiver/channel/testing/device_auth_test_helpers.h" +#include "cast/sender/channel/cast_auth_util.h" +#include "cast/sender/channel/message_util.h" +#include "gtest/gtest.h" +#include "testing/util/read_file.h" + +namespace cast { +namespace channel { +namespace { + +using ::testing::_; +using ::testing::Invoke; + +using openscreen::ErrorOr; + +#define TEST_DATA_PREFIX OPENSCREEN_TEST_DATA_DIR "cast/receiver/channel/" + +class DeviceAuthTest : public ::testing::Test { + public: + void SetUp() override { + socket_ = fake_cast_socket_pair_.socket.get(); + router_.TakeSocket(&mock_error_handler_, + std::move(fake_cast_socket_pair_.socket)); + router_.AddHandlerForLocalId(kPlatformReceiverId, &auth_handler_); + } + + protected: + void RunAuthTest(std::string serialized_crl, + certificate::TrustStore* fake_crl_trust_store, + bool should_succeed = true, + bool record_this_test = false) { + bssl::UniquePtr<X509> parsed_cert; + certificate::TrustStore fake_trust_store; + InitStaticCredentialsFromFiles(&creds_, &parsed_cert, &fake_trust_store, + TEST_DATA_PREFIX "device_key.pem", + TEST_DATA_PREFIX "device_chain.pem", + TEST_DATA_PREFIX "device_tls.pem"); + creds_.device_creds.serialized_crl = std::move(serialized_crl); + + // Send an auth challenge. |auth_handler_| will automatically respond + // via |router_| and we will catch the result in |challenge_reply|. + AuthContext auth_context = AuthContext::Create(); + CastMessage auth_challenge = CreateAuthChallengeMessage(auth_context); + if (record_this_test) { + std::string output; + DeviceAuthMessage auth_message; + ASSERT_EQ(auth_challenge.payload_type(), CastMessage_PayloadType_BINARY); + ASSERT_TRUE( + auth_message.ParseFromString(auth_challenge.payload_binary())); + ASSERT_TRUE(auth_message.has_challenge()); + ASSERT_FALSE(auth_message.has_response()); + ASSERT_FALSE(auth_message.has_error()); + ASSERT_TRUE(auth_challenge.SerializeToString(&output)); + + FILE* fd = fopen(TEST_DATA_PREFIX "auth_challenge.pb", "wb"); + ASSERT_TRUE(fd); + ASSERT_EQ(fwrite(output.data(), 1, output.size(), fd), output.size()); + fclose(fd); + } + CastMessage challenge_reply; + EXPECT_CALL(fake_cast_socket_pair_.mock_peer_client, OnMessage(_, _)) + .WillOnce( + Invoke([&challenge_reply](CastSocket* socket, CastMessage message) { + challenge_reply = std::move(message); + })); + fake_cast_socket_pair_.peer_socket->SendMessage(std::move(auth_challenge)); + + if (record_this_test) { + std::string output; + DeviceAuthMessage auth_message; + ASSERT_EQ(challenge_reply.payload_type(), CastMessage_PayloadType_BINARY); + ASSERT_TRUE( + auth_message.ParseFromString(challenge_reply.payload_binary())); + ASSERT_TRUE(auth_message.has_response()); + ASSERT_FALSE(auth_message.has_challenge()); + ASSERT_FALSE(auth_message.has_error()); + ASSERT_TRUE(auth_message.response().SerializeToString(&output)); + + FILE* fd = fopen(TEST_DATA_PREFIX "auth_response.pb", "wb"); + ASSERT_TRUE(fd); + ASSERT_EQ(fwrite(output.data(), 1, output.size(), fd), output.size()); + fclose(fd); + } + + certificate::DateTime December2019 = {}; + December2019.year = 2019; + December2019.month = 12; + December2019.day = 17; + const ErrorOr<CastDeviceCertPolicy> error_or_policy = + AuthenticateChallengeReplyForTest( + challenge_reply, parsed_cert.get(), auth_context, + fake_crl_trust_store ? certificate::CRLPolicy::kCrlRequired + : certificate::CRLPolicy::kCrlOptional, + &fake_trust_store, fake_crl_trust_store, December2019); + EXPECT_EQ(error_or_policy.is_value(), should_succeed); + } + + FakeCastSocketPair fake_cast_socket_pair_; + MockSocketErrorHandler mock_error_handler_; + CastSocket* socket_; + + StaticCredentialsProvider creds_; + VirtualConnectionManager manager_; + VirtualConnectionRouter router_{&manager_}; + DeviceAuthNamespaceHandler auth_handler_{&creds_}; +}; + +} // namespace + +TEST_F(DeviceAuthTest, MANUAL_SerializeTestData) { + if (::testing::GTEST_FLAG(filter) == + "DeviceAuthTest.MANUAL_SerializeTestData") { + RunAuthTest(std::string(), nullptr, true, true); + } +} + +TEST_F(DeviceAuthTest, AuthIntegration) { + RunAuthTest(std::string(), nullptr); +} + +TEST_F(DeviceAuthTest, GoodCrl) { + std::unique_ptr<certificate::TrustStore> fake_crl_trust_store = + certificate::testing::CreateTrustStoreFromPemFile(TEST_DATA_PREFIX + "crl_root.pem"); + RunAuthTest( + openscreen::ReadEntireFileToString(TEST_DATA_PREFIX "good_crl.pb"), + fake_crl_trust_store.get()); +} + +TEST_F(DeviceAuthTest, InvalidCrlTime) { + std::unique_ptr<certificate::TrustStore> fake_crl_trust_store = + certificate::testing::CreateTrustStoreFromPemFile(TEST_DATA_PREFIX + "crl_root.pem"); + RunAuthTest(openscreen::ReadEntireFileToString(TEST_DATA_PREFIX + "invalid_time_crl.pb"), + fake_crl_trust_store.get(), false); +} + +TEST_F(DeviceAuthTest, IssuerRevoked) { + std::unique_ptr<certificate::TrustStore> fake_crl_trust_store = + certificate::testing::CreateTrustStoreFromPemFile(TEST_DATA_PREFIX + "crl_root.pem"); + RunAuthTest(openscreen::ReadEntireFileToString(TEST_DATA_PREFIX + "issuer_revoked_crl.pb"), + fake_crl_trust_store.get(), false); +} + +TEST_F(DeviceAuthTest, DeviceRevoked) { + std::unique_ptr<certificate::TrustStore> fake_crl_trust_store = + certificate::testing::CreateTrustStoreFromPemFile(TEST_DATA_PREFIX + "crl_root.pem"); + RunAuthTest(openscreen::ReadEntireFileToString(TEST_DATA_PREFIX + "device_revoked_crl.pb"), + fake_crl_trust_store.get(), false); +} + +TEST_F(DeviceAuthTest, IssuerSerialRevoked) { + std::unique_ptr<certificate::TrustStore> fake_crl_trust_store = + certificate::testing::CreateTrustStoreFromPemFile(TEST_DATA_PREFIX + "crl_root.pem"); + RunAuthTest(openscreen::ReadEntireFileToString( + TEST_DATA_PREFIX "issuer_serial_revoked_crl.pb"), + fake_crl_trust_store.get(), false); +} + +TEST_F(DeviceAuthTest, DeviceSerialRevoked) { + std::unique_ptr<certificate::TrustStore> fake_crl_trust_store = + certificate::testing::CreateTrustStoreFromPemFile(TEST_DATA_PREFIX + "crl_root.pem"); + RunAuthTest(openscreen::ReadEntireFileToString( + TEST_DATA_PREFIX "device_serial_revoked_crl.pb"), + fake_crl_trust_store.get(), false); +} + +TEST_F(DeviceAuthTest, BadCrlSignerCert) { + std::unique_ptr<certificate::TrustStore> fake_crl_trust_store = + certificate::testing::CreateTrustStoreFromPemFile(TEST_DATA_PREFIX + "crl_root.pem"); + RunAuthTest(openscreen::ReadEntireFileToString(TEST_DATA_PREFIX + "bad_signer_cert_crl.pb"), + fake_crl_trust_store.get(), false); +} + +TEST_F(DeviceAuthTest, BadCrlSignature) { + std::unique_ptr<certificate::TrustStore> fake_crl_trust_store = + certificate::testing::CreateTrustStoreFromPemFile(TEST_DATA_PREFIX + "crl_root.pem"); + RunAuthTest(openscreen::ReadEntireFileToString(TEST_DATA_PREFIX + "bad_signature_crl.pb"), + fake_crl_trust_store.get(), false); +} + +} // namespace channel +} // namespace cast diff --git a/cast/test/make_crl_tests.cc b/cast/test/make_crl_tests.cc new file mode 100644 index 00000000..edbe1afd --- /dev/null +++ b/cast/test/make_crl_tests.cc @@ -0,0 +1,248 @@ +// Copyright 2019 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 <fcntl.h> +#include <unistd.h> + +#include "cast/common/certificate/proto/revocation.pb.h" +#include "cast/common/certificate/test_helpers.h" +#include "cast/common/certificate/types.h" +#include "util/crypto/certificate_utils.h" +#include "util/crypto/digest_sign.h" +#include "util/crypto/sha2.h" +#include "util/logging.h" + +#define TEST_DATA_PREFIX OPENSCREEN_TEST_DATA_DIR "cast/receiver/channel/" + +namespace cast { +namespace { + +using certificate::ConstDataSpan; +using certificate::DateTime; +using openscreen::Error; +using openscreen::ErrorOr; + +std::string* AddRevokedPublicKeyHash(certificate::TbsCrl* tbs_crl, X509* cert) { + std::string* pubkey_hash = tbs_crl->add_revoked_public_key_hashes(); + std::string pubkey_spki = openscreen::GetSpkiTlv(cert); + ErrorOr<std::string> hash_value = openscreen::SHA256HashString(pubkey_spki); + OSP_DCHECK(hash_value.is_value()); + *pubkey_hash = std::move(hash_value.value()); + return pubkey_hash; +} + +void AddSerialNumberRange(certificate::TbsCrl* tbs_crl, + X509* issuer, + uint64_t first, + uint64_t last) { + certificate::SerialNumberRange* serial_range = + tbs_crl->add_revoked_serial_number_ranges(); + std::string issuer_spki = openscreen::GetSpkiTlv(issuer); + ErrorOr<std::string> issuer_hash = openscreen::SHA256HashString(issuer_spki); + OSP_DCHECK(issuer_hash.is_value()); + serial_range->set_issuer_public_key_hash(std::move(issuer_hash.value())); + serial_range->set_first_serial_number(first); + serial_range->set_last_serial_number(last); +} + +certificate::TbsCrl MakeTbsCrl(uint64_t not_before, + uint64_t not_after, + X509* device_cert, + X509* inter_cert) { + certificate::TbsCrl tbs_crl; + tbs_crl.set_version(0); + tbs_crl.set_not_before_seconds(not_before); + tbs_crl.set_not_after_seconds(not_after); + + // NOTE: By default, include a hash which should not match any included certs. + std::string* pubkey_hash = AddRevokedPublicKeyHash(&tbs_crl, device_cert); + (*pubkey_hash)[0] ^= 0xff; + + // NOTE: Include default serial number range at device-level, which should not + // include any of our certs. + ErrorOr<uint64_t> maybe_serial = + openscreen::ParseDerUint64(device_cert->cert_info->serialNumber); + OSP_DCHECK(maybe_serial); + uint64_t serial = maybe_serial.value(); + OSP_DCHECK_LE(serial, UINT64_MAX - 200); + AddSerialNumberRange(&tbs_crl, inter_cert, serial + 100, serial + 200); + + return tbs_crl; +} + +// Pack into a CrlBundle and sign with |crl_inter_key|. |crl_inter_der| must be +// directly signed by a Cast CRL root CA (possibly distinct from Cast root CA). +void PackCrlIntoFile(const char* filename, + const certificate::TbsCrl& tbs_crl, + const std::string& crl_inter_der, + EVP_PKEY* crl_inter_key) { + certificate::CrlBundle crl_bundle; + certificate::Crl* crl = crl_bundle.add_crls(); + std::string* tbs_crl_serial = crl->mutable_tbs_crl(); + tbs_crl.SerializeToString(tbs_crl_serial); + crl->set_signer_cert(crl_inter_der); + ErrorOr<std::string> signature = openscreen::SignData( + EVP_sha256(), crl_inter_key, + absl::Span<const uint8_t>{ + reinterpret_cast<const uint8_t*>(tbs_crl_serial->data()), + tbs_crl_serial->size()}); + OSP_DCHECK(signature); + crl->set_signature(std::move(signature.value())); + + std::string output; + crl_bundle.SerializeToString(&output); + int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); + OSP_DCHECK_GE(fd, 0); + OSP_DCHECK_EQ(write(fd, output.data(), output.size()), (int)output.size()); + close(fd); +} + +int CastMain() { + bssl::UniquePtr<EVP_PKEY> inter_key = + certificate::testing::ReadKeyFromPemFile(TEST_DATA_PREFIX + "inter_key.pem"); + bssl::UniquePtr<EVP_PKEY> crl_inter_key = + certificate::testing::ReadKeyFromPemFile(TEST_DATA_PREFIX + "crl_inter_key.pem"); + OSP_DCHECK(inter_key); + OSP_DCHECK(crl_inter_key); + + std::vector<std::string> chain_der = + certificate::testing::ReadCertificatesFromPemFile(TEST_DATA_PREFIX + "device_chain.pem"); + std::vector<std::string> crl_inter_der = + certificate::testing::ReadCertificatesFromPemFile(TEST_DATA_PREFIX + "crl_inter.pem"); + OSP_DCHECK_EQ(chain_der.size(), 3u); + OSP_DCHECK_EQ(crl_inter_der.size(), 1u); + + std::string& device_der = chain_der[0]; + std::string& inter_der = chain_der[1]; + std::string& root_der = chain_der[2]; + + auto* data = reinterpret_cast<const uint8_t*>(device_der.data()); + bssl::UniquePtr<X509> device_cert{ + d2i_X509(nullptr, &data, device_der.size())}; + data = reinterpret_cast<const uint8_t*>(inter_der.data()); + bssl::UniquePtr<X509> inter_cert{d2i_X509(nullptr, &data, inter_der.size())}; + data = reinterpret_cast<const uint8_t*>(root_der.data()); + bssl::UniquePtr<X509> root_cert{d2i_X509(nullptr, &data, root_der.size())}; + data = reinterpret_cast<const uint8_t*>(crl_inter_der[0].data()); + bssl::UniquePtr<X509> crl_inter_cert{ + d2i_X509(nullptr, &data, crl_inter_der[0].size())}; + OSP_DCHECK(device_cert); + OSP_DCHECK(inter_cert); + OSP_DCHECK(root_cert); + OSP_DCHECK(crl_inter_cert); + + // NOTE: CRL where everything should pass. + DateTime july2019 = {}; + july2019.month = 7; + july2019.year = 2019; + july2019.day = 16; + DateTime july2020 = {}; + july2020.month = 7; + july2020.year = 2020; + july2020.day = 23; + std::chrono::seconds not_before = certificate::DateTimeToSeconds(july2019); + std::chrono::seconds not_after = certificate::DateTimeToSeconds(july2020); + certificate::TbsCrl tbs_crl = + MakeTbsCrl(not_before.count(), not_after.count(), device_cert.get(), + inter_cert.get()); + PackCrlIntoFile(TEST_DATA_PREFIX "good_crl.pb", tbs_crl, crl_inter_der[0], + crl_inter_key.get()); + + // NOTE: CRL used outside its valid time range. + { + DateTime august2019 = {}; + august2019.month = 8; + august2019.year = 2019; + august2019.day = 16; + std::chrono::seconds not_after = certificate::DateTimeToSeconds(august2019); + certificate::TbsCrl tbs_crl = + MakeTbsCrl(not_before.count(), not_after.count(), device_cert.get(), + inter_cert.get()); + PackCrlIntoFile(TEST_DATA_PREFIX "invalid_time_crl.pb", tbs_crl, + crl_inter_der[0], crl_inter_key.get()); + } + + // NOTE: Device's issuer revoked. + { + certificate::TbsCrl tbs_crl = + MakeTbsCrl(not_before.count(), not_after.count(), device_cert.get(), + inter_cert.get()); + AddRevokedPublicKeyHash(&tbs_crl, inter_cert.get()); + PackCrlIntoFile(TEST_DATA_PREFIX "issuer_revoked_crl.pb", tbs_crl, + crl_inter_der[0], crl_inter_key.get()); + } + + // NOTE: Device revoked. + { + certificate::TbsCrl tbs_crl = + MakeTbsCrl(not_before.count(), not_after.count(), device_cert.get(), + inter_cert.get()); + AddRevokedPublicKeyHash(&tbs_crl, device_cert.get()); + PackCrlIntoFile(TEST_DATA_PREFIX "device_revoked_crl.pb", tbs_crl, + crl_inter_der[0], crl_inter_key.get()); + } + + // NOTE: Issuer serial revoked. + { + certificate::TbsCrl tbs_crl = + MakeTbsCrl(not_before.count(), not_after.count(), device_cert.get(), + inter_cert.get()); + ErrorOr<uint64_t> maybe_serial = + openscreen::ParseDerUint64(inter_cert->cert_info->serialNumber); + OSP_DCHECK(maybe_serial); + uint64_t serial = maybe_serial.value(); + OSP_DCHECK_GE(serial, 10); + OSP_DCHECK_LE(serial, UINT64_MAX - 20); + AddSerialNumberRange(&tbs_crl, root_cert.get(), serial - 10, serial + 20); + PackCrlIntoFile(TEST_DATA_PREFIX "issuer_serial_revoked_crl.pb", tbs_crl, + crl_inter_der[0], crl_inter_key.get()); + } + + // NOTE: Device serial revoked. + { + certificate::TbsCrl tbs_crl = + MakeTbsCrl(not_before.count(), not_after.count(), device_cert.get(), + inter_cert.get()); + ErrorOr<uint64_t> maybe_serial = + openscreen::ParseDerUint64(device_cert->cert_info->serialNumber); + OSP_DCHECK(maybe_serial); + uint64_t serial = maybe_serial.value(); + OSP_DCHECK_GE(serial, 10); + OSP_DCHECK_LE(serial, UINT64_MAX - 20); + AddSerialNumberRange(&tbs_crl, inter_cert.get(), serial - 10, serial + 20); + PackCrlIntoFile(TEST_DATA_PREFIX "device_serial_revoked_crl.pb", tbs_crl, + crl_inter_der[0], crl_inter_key.get()); + } + + // NOTE: Bad |signer_cert| used for Crl (not issued by Cast CRL root). + { + certificate::TbsCrl tbs_crl = + MakeTbsCrl(not_before.count(), not_after.count(), device_cert.get(), + inter_cert.get()); + PackCrlIntoFile(TEST_DATA_PREFIX "bad_signer_cert_crl.pb", tbs_crl, + inter_der, inter_key.get()); + } + + // NOTE: Mismatched key for signature in Crl (just looks like bad signature). + { + certificate::TbsCrl tbs_crl = + MakeTbsCrl(not_before.count(), not_after.count(), device_cert.get(), + inter_cert.get()); + PackCrlIntoFile(TEST_DATA_PREFIX "bad_signature_crl.pb", tbs_crl, + crl_inter_der[0], inter_key.get()); + } + + return 0; +} + +} // namespace +} // namespace cast + +int main() { + return cast::CastMain(); +} |