aboutsummaryrefslogtreecommitdiff
path: root/cast
diff options
context:
space:
mode:
Diffstat (limited to 'cast')
-rw-r--r--cast/common/BUILD.gn5
-rw-r--r--cast/common/certificate/cast_crl.cc40
-rw-r--r--cast/common/certificate/cast_crl_unittest.cc3
-rw-r--r--cast/common/certificate/test_helpers.cc128
-rw-r--r--cast/common/certificate/test_helpers.h11
-rw-r--r--cast/common/certificate/types.cc17
-rw-r--r--cast/common/certificate/types.h5
-rw-r--r--cast/common/channel/connection_namespace_handler_unittest.cc11
-rw-r--r--cast/common/channel/proto/cast_channel.proto2
-rw-r--r--cast/common/channel/test/mock_socket_error_handler.h27
-rw-r--r--cast/common/channel/virtual_connection_router_unittest.cc12
-rw-r--r--cast/receiver/BUILD.gn59
-rw-r--r--cast/receiver/DEPS2
-rw-r--r--cast/receiver/channel/device_auth_namespace_handler.cc140
-rw-r--r--cast/receiver/channel/device_auth_namespace_handler.h57
-rw-r--r--cast/receiver/channel/device_auth_namespace_handler_unittest.cc204
-rw-r--r--cast/receiver/channel/receiver_socket_factory.cc50
-rw-r--r--cast/receiver/channel/receiver_socket_factory.h55
-rw-r--r--cast/receiver/channel/testing/device_auth_test_helpers.cc51
-rw-r--r--cast/receiver/channel/testing/device_auth_test_helpers.h46
-rw-r--r--cast/sender/BUILD.gn3
-rw-r--r--cast/sender/channel/cast_auth_util.cc56
-rw-r--r--cast/sender/channel/cast_auth_util.h19
-rw-r--r--cast/sender/channel/cast_auth_util_unittest.cc15
-rw-r--r--cast/streaming/receiver_session_unittest.cc2
-rw-r--r--cast/test/BUILD.gn38
-rw-r--r--cast/test/DEPS5
-rw-r--r--cast/test/README.md28
-rw-r--r--cast/test/device_auth_test.cc208
-rw-r--r--cast/test/make_crl_tests.cc248
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(), &not_before_);
DateTimeFromSeconds(tbs_crl.not_after_seconds(), &not_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();
+}