aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbtolsch <btolsch@chromium.org>2019-09-10 10:42:56 -0700
committerCommit Bot <commit-bot@chromium.org>2019-09-10 18:04:35 +0000
commit06a865967c70f8d8e473b0ed40be2fdda09bd887 (patch)
treebfd71a6e552d98dc4af5cef2918681cc41231be5
parent349c2aaf03b3aab25ff6a55b78f6da1d4cdceb64 (diff)
downloadopenscreen-06a865967c70f8d8e473b0ed40be2fdda09bd887.tar.gz
Add cast channel authentication utilities
This change imports cast_auth_util.cc from Chromium's sender component (//components/cast_channel). Bug: openscreen:59 Change-Id: I4eb3a66ae1e50dbe0435176a15d13b2c62d452f3 Reviewed-on: https://chromium-review.googlesource.com/c/openscreen/+/1772435 Commit-Queue: Brandon Tolsch <btolsch@chromium.org> Reviewed-by: Ryan Keane <rwkeane@google.com> Reviewed-by: Max Yakimakha <yakimakha@chromium.org>
-rw-r--r--BUILD.gn1
-rw-r--r--cast/common/certificate/BUILD.gn2
-rw-r--r--cast/common/certificate/cast_cert_validator.cc3
-rw-r--r--cast/common/certificate/cast_cert_validator_internal.cc83
-rw-r--r--cast/common/certificate/cast_cert_validator_internal.h6
-rw-r--r--cast/common/certificate/cast_cert_validator_unittest.cc49
-rw-r--r--cast/common/certificate/cast_crl.cc8
-rw-r--r--cast/common/certificate/cast_crl_unittest.cc101
-rw-r--r--cast/common/certificate/test_helpers.cc85
-rw-r--r--cast/common/certificate/test_helpers.h19
-rw-r--r--cast/common/certificate/types.cc70
-rw-r--r--cast/common/certificate/types.h4
-rw-r--r--cast/sender/DEPS2
-rw-r--r--cast/sender/channel/BUILD.gn30
-rw-r--r--cast/sender/channel/cast_auth_util.cc379
-rw-r--r--cast/sender/channel/cast_auth_util.h95
-rw-r--r--cast/sender/channel/cast_auth_util_unittest.cc472
-rw-r--r--cast/sender/channel/proto/BUILD.gn11
-rw-r--r--cast/sender/channel/proto/cast_channel.proto99
-rw-r--r--platform/base/error.cc32
-rw-r--r--platform/base/error.h18
-rw-r--r--test/data/cast/common/certificate/certificates/test_tls_cert.pem68
22 files changed, 1452 insertions, 185 deletions
diff --git a/BUILD.gn b/BUILD.gn
index 12bc3883..08fa796c 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -39,6 +39,7 @@ executable("openscreen_unittests") {
deps = [
"cast/common:mdns_unittests",
"cast/common/certificate:unittests",
+ "cast/sender/channel:unittests",
"osp:osp_unittests",
"osp/impl/discovery/mdns:mdns_unittests",
"osp/msgs:unittests",
diff --git a/cast/common/certificate/BUILD.gn b/cast/common/certificate/BUILD.gn
index 46449794..65000c5d 100644
--- a/cast/common/certificate/BUILD.gn
+++ b/cast/common/certificate/BUILD.gn
@@ -10,6 +10,8 @@ source_set("certificate") {
"cast_cert_validator_internal.h",
"cast_crl.cc",
"cast_crl.h",
+ "types.cc",
+ "types.h",
]
public_deps = [
"../../../third_party/boringssl",
diff --git a/cast/common/certificate/cast_cert_validator.cc b/cast/common/certificate/cast_cert_validator.cc
index fb7217ef..6fe95821 100644
--- a/cast/common/certificate/cast_cert_validator.cc
+++ b/cast/common/certificate/cast_cert_validator.cc
@@ -188,7 +188,8 @@ openscreen::Error VerifyDeviceCert(
return error;
}
- if (crl && !crl->CheckRevocation(result_path.path, time)) {
+ if (crl_policy == CRLPolicy::kCrlRequired &&
+ !crl->CheckRevocation(result_path.path, time)) {
return CastCertError::kErrCertsRevoked;
}
diff --git a/cast/common/certificate/cast_cert_validator_internal.cc b/cast/common/certificate/cast_cert_validator_internal.cc
index c704cf4c..39aff8a4 100644
--- a/cast/common/certificate/cast_cert_validator_internal.cc
+++ b/cast/common/certificate/cast_cert_validator_internal.cc
@@ -64,20 +64,9 @@ uint8_t ParseAsn1TimeDoubleDigit(ASN1_GENERALIZEDTIME* time, int index) {
}
CastCertError VerifyCertTime(X509* cert, const DateTime& time) {
- ASN1_GENERALIZEDTIME* not_before_asn1 = ASN1_TIME_to_generalizedtime(
- cert->cert_info->validity->notBefore, nullptr);
- ASN1_GENERALIZEDTIME* not_after_asn1 = ASN1_TIME_to_generalizedtime(
- cert->cert_info->validity->notAfter, nullptr);
- if (!not_before_asn1 || !not_after_asn1) {
- return CastCertError::kErrCertsVerifyGeneric;
- }
DateTime not_before;
DateTime not_after;
- bool times_valid = ParseAsn1GeneralizedTime(not_before_asn1, &not_before) &&
- ParseAsn1GeneralizedTime(not_after_asn1, &not_after);
- ASN1_GENERALIZEDTIME_free(not_before_asn1);
- ASN1_GENERALIZEDTIME_free(not_after_asn1);
- if (!times_valid) {
+ if (!GetCertValidTimeRange(cert, &not_before, &not_after)) {
return CastCertError::kErrCertsVerifyGeneric;
}
if ((time < not_before) || (not_after < time)) {
@@ -350,6 +339,23 @@ bool ParseAsn1GeneralizedTime(ASN1_GENERALIZEDTIME* time, DateTime* out) {
return true;
}
+bool GetCertValidTimeRange(X509* cert,
+ DateTime* not_before,
+ DateTime* not_after) {
+ ASN1_GENERALIZEDTIME* not_before_asn1 = ASN1_TIME_to_generalizedtime(
+ cert->cert_info->validity->notBefore, nullptr);
+ ASN1_GENERALIZEDTIME* not_after_asn1 = ASN1_TIME_to_generalizedtime(
+ cert->cert_info->validity->notAfter, nullptr);
+ if (!not_before_asn1 || !not_after_asn1) {
+ return false;
+ }
+ bool times_valid = ParseAsn1GeneralizedTime(not_before_asn1, not_before) &&
+ ParseAsn1GeneralizedTime(not_after_asn1, not_after);
+ ASN1_GENERALIZEDTIME_free(not_before_asn1);
+ ASN1_GENERALIZEDTIME_free(not_after_asn1);
+ return times_valid;
+}
+
bool VerifySignedData(const EVP_MD* digest,
EVP_PKEY* public_key,
const ConstDataSpan& data,
@@ -364,59 +370,6 @@ bool VerifySignedData(const EVP_MD* digest,
data.data, data.length) == 1);
}
-bool operator<(const DateTime& a, const DateTime& b) {
- if (a.year < b.year) {
- return true;
- } else if (a.year > b.year) {
- return false;
- }
- if (a.month < b.month) {
- return true;
- } else if (a.month > b.month) {
- return false;
- }
- if (a.day < b.day) {
- return true;
- } else if (a.day > b.day) {
- return false;
- }
- if (a.hour < b.hour) {
- return true;
- } else if (a.hour > b.hour) {
- return false;
- }
- if (a.minute < b.minute) {
- return true;
- } else if (a.minute > b.minute) {
- return false;
- }
- if (a.second < b.second) {
- return true;
- } else if (a.second > b.second) {
- return false;
- }
- return false;
-}
-
-bool ConvertTimeSeconds(uint64_t seconds, DateTime* time) {
- struct tm tm = {};
- time_t sec = static_cast<time_t>(seconds);
- OSP_DCHECK_GE(sec, 0);
- OSP_DCHECK_EQ(static_cast<uint64_t>(sec), seconds);
- if (!gmtime_r(&sec, &tm)) {
- return false;
- }
-
- time->second = tm.tm_sec;
- time->minute = tm.tm_min;
- time->hour = tm.tm_hour;
- time->day = tm.tm_mday;
- time->month = tm.tm_mon + 1;
- time->year = tm.tm_year + 1900;
-
- return true;
-}
-
openscreen::Error FindCertificatePath(const std::vector<std::string>& der_certs,
const DateTime& time,
CertificatePathResult* result_path,
diff --git a/cast/common/certificate/cast_cert_validator_internal.h b/cast/common/certificate/cast_cert_validator_internal.h
index ad8dafbc..1c127e72 100644
--- a/cast/common/certificate/cast_cert_validator_internal.h
+++ b/cast/common/certificate/cast_cert_validator_internal.h
@@ -34,12 +34,12 @@ bool VerifySignedData(const EVP_MD* digest,
const ConstDataSpan& data,
const ConstDataSpan& signature);
-bool operator<(const DateTime& a, const DateTime& b);
-bool ConvertTimeSeconds(uint64_t seconds, DateTime* time);
-
// Parses DateTime with additional restrictions laid out by RFC 5280
// 4.1.2.5.2.
bool ParseAsn1GeneralizedTime(ASN1_GENERALIZEDTIME* time, DateTime* out);
+bool GetCertValidTimeRange(X509* cert,
+ DateTime* not_before,
+ DateTime* not_after);
struct CertificatePathResult {
bssl::UniquePtr<X509> target_cert;
diff --git a/cast/common/certificate/cast_cert_validator_unittest.cc b/cast/common/certificate/cast_cert_validator_unittest.cc
index a4aa1962..7b0ee1d7 100644
--- a/cast/common/certificate/cast_cert_validator_unittest.cc
+++ b/cast/common/certificate/cast_cert_validator_unittest.cc
@@ -110,53 +110,16 @@ void RunTest(CastCertError expected_result,
// If valid signatures are known for this device certificate, test them.
if (!optional_signed_data_file_name.empty()) {
- FILE* fp = fopen(optional_signed_data_file_name.c_str(), "r");
- ASSERT_TRUE(fp);
- ConstDataSpan message = {};
- ConstDataSpan sha1 = {};
- ConstDataSpan sha256 = {};
- for (;;) {
- char* name;
- char* header;
- unsigned char* data;
- long length;
- if (PEM_read(fp, &name, &header, &data, &length) == 1) {
- if (strcmp(name, "MESSAGE") == 0) {
- ASSERT_FALSE(message.data);
- message.data = data;
- message.length = length;
- } else if (strcmp(name, "SIGNATURE SHA1") == 0) {
- ASSERT_FALSE(sha1.data);
- sha1.data = data;
- sha1.length = length;
- } else if (strcmp(name, "SIGNATURE SHA256") == 0) {
- ASSERT_FALSE(sha256.data);
- sha256.data = data;
- sha256.length = length;
- } else {
- OPENSSL_free(data);
- }
- OPENSSL_free(name);
- OPENSSL_free(header);
- } else {
- break;
- }
- }
- ASSERT_TRUE(message.data);
- ASSERT_TRUE(sha1.data);
- ASSERT_TRUE(sha256.data);
+ testing::SignatureTestData signatures =
+ testing::ReadSignatureTestData(optional_signed_data_file_name);
// Test verification of a valid SHA1 signature.
- EXPECT_TRUE(context->VerifySignatureOverData(sha1, message,
- DigestAlgorithm::kSha1));
+ EXPECT_TRUE(context->VerifySignatureOverData(
+ signatures.sha1, signatures.message, DigestAlgorithm::kSha1));
// Test verification of a valid SHA256 signature.
- EXPECT_TRUE(context->VerifySignatureOverData(sha256, message,
- DigestAlgorithm::kSha256));
-
- OPENSSL_free((uint8_t*)message.data);
- OPENSSL_free((uint8_t*)sha1.data);
- OPENSSL_free((uint8_t*)sha256.data);
+ EXPECT_TRUE(context->VerifySignatureOverData(
+ signatures.sha256, signatures.message, DigestAlgorithm::kSha256));
}
}
diff --git a/cast/common/certificate/cast_crl.cc b/cast/common/certificate/cast_crl.cc
index a2f05ea0..901f5e0e 100644
--- a/cast/common/certificate/cast_crl.cc
+++ b/cast/common/certificate/cast_crl.cc
@@ -92,11 +92,11 @@ bool VerifyCRL(const Crl& crl,
// Verify the CRL is still valid.
DateTime not_before;
- if (!ConvertTimeSeconds(tbs_crl.not_before_seconds(), &not_before)) {
+ if (!DateTimeFromSeconds(tbs_crl.not_before_seconds(), &not_before)) {
return false;
}
DateTime not_after;
- if (!ConvertTimeSeconds(tbs_crl.not_after_seconds(), &not_after)) {
+ if (!DateTimeFromSeconds(tbs_crl.not_after_seconds(), &not_after)) {
return false;
}
if ((time < not_before) || (not_after < time)) {
@@ -167,8 +167,8 @@ 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.
- ConvertTimeSeconds(tbs_crl.not_before_seconds(), &not_before_);
- ConvertTimeSeconds(tbs_crl.not_after_seconds(), &not_after_);
+ DateTimeFromSeconds(tbs_crl.not_before_seconds(), &not_before_);
+ DateTimeFromSeconds(tbs_crl.not_after_seconds(), &not_after_);
if (overall_not_after < not_after_) {
not_after_ = overall_not_after;
}
diff --git a/cast/common/certificate/cast_crl_unittest.cc b/cast/common/certificate/cast_crl_unittest.cc
index 2b17ea73..de3d922e 100644
--- a/cast/common/certificate/cast_crl_unittest.cc
+++ b/cast/common/certificate/cast_crl_unittest.cc
@@ -4,9 +4,6 @@
#include "cast/common/certificate/cast_crl.h"
-#include <fcntl.h>
-#include <unistd.h>
-
#include "cast/common/certificate/cast_cert_validator.h"
#include "cast/common/certificate/cast_cert_validator_internal.h"
#include "cast/common/certificate/proto/test_suite.pb.h"
@@ -88,35 +85,19 @@ bool TestVerifyRevocation(CastCertError expected_result,
return expected_result == result.code();
}
-TrustStore CreateTrustStoreFromPemFile(const std::string& filename) {
- TrustStore store;
-
- std::vector<std::string> certs =
- testing::ReadCertificatesFromPemFile(filename);
- for (const auto& der_cert : certs) {
- const uint8_t* data = (const uint8_t*)der_cert.data();
- store.certs.emplace_back(d2i_X509(nullptr, &data, der_cert.size()));
- }
- return store;
-}
-
#define TEST_DATA_PREFIX "test/data/cast/common/certificate/"
bool RunTest(const DeviceCertTest& test_case) {
- TrustStore crl_trust_store_local;
- TrustStore cast_trust_store_local;
- TrustStore* crl_trust_store = nullptr;
- TrustStore* cast_trust_store = nullptr;
+ std::unique_ptr<TrustStore> crl_trust_store;
+ std::unique_ptr<TrustStore> cast_trust_store;
if (test_case.use_test_trust_anchors()) {
- crl_trust_store_local = CreateTrustStoreFromPemFile(
+ crl_trust_store = testing::CreateTrustStoreFromPemFile(
TEST_DATA_PREFIX "certificates/cast_crl_test_root_ca.pem");
- cast_trust_store_local = CreateTrustStoreFromPemFile(
+ cast_trust_store = testing::CreateTrustStoreFromPemFile(
TEST_DATA_PREFIX "certificates/cast_test_root_ca.pem");
- crl_trust_store = &crl_trust_store_local;
- cast_trust_store = &cast_trust_store_local;
- EXPECT_FALSE(crl_trust_store_local.certs.empty());
- EXPECT_FALSE(cast_trust_store_local.certs.empty());
+ EXPECT_FALSE(crl_trust_store->certs.empty());
+ EXPECT_FALSE(cast_trust_store->certs.empty());
}
std::vector<std::string> der_cert_path;
@@ -125,12 +106,12 @@ bool RunTest(const DeviceCertTest& test_case) {
}
DateTime cert_verification_time;
- EXPECT_TRUE(ConvertTimeSeconds(test_case.cert_verification_time_seconds(),
- &cert_verification_time));
+ EXPECT_TRUE(DateTimeFromSeconds(test_case.cert_verification_time_seconds(),
+ &cert_verification_time));
uint64_t crl_verify_time = test_case.crl_verification_time_seconds();
DateTime crl_verification_time;
- EXPECT_TRUE(ConvertTimeSeconds(crl_verify_time, &crl_verification_time));
+ EXPECT_TRUE(DateTimeFromSeconds(crl_verify_time, &crl_verification_time));
if (crl_verify_time == 0) {
crl_verification_time = cert_verification_time;
}
@@ -139,73 +120,57 @@ bool RunTest(const DeviceCertTest& test_case) {
switch (test_case.expected_result()) {
case PATH_VERIFICATION_FAILED:
return TestVerifyCertificate(kResultFail, der_cert_path,
- cert_verification_time, cast_trust_store);
+ cert_verification_time,
+ cast_trust_store.get());
case CRL_VERIFICATION_FAILED:
return TestVerifyCRL(kResultFail, crl_bundle, crl_verification_time,
- crl_trust_store);
+ crl_trust_store.get());
case REVOCATION_CHECK_FAILED_WITHOUT_CRL:
return TestVerifyCertificate(kResultSuccess, der_cert_path,
- cert_verification_time, cast_trust_store) &&
+ cert_verification_time,
+ cast_trust_store.get()) &&
TestVerifyCRL(kResultFail, crl_bundle, crl_verification_time,
- crl_trust_store) &&
- TestVerifyRevocation(CastCertError::kErrCrlInvalid, der_cert_path,
- crl_bundle, crl_verification_time,
- cert_verification_time, true,
- cast_trust_store, crl_trust_store);
+ crl_trust_store.get()) &&
+ TestVerifyRevocation(
+ CastCertError::kErrCrlInvalid, der_cert_path, crl_bundle,
+ crl_verification_time, cert_verification_time, true,
+ cast_trust_store.get(), crl_trust_store.get());
case CRL_EXPIRED_AFTER_INITIAL_VERIFICATION: // fallthrough
case REVOCATION_CHECK_FAILED:
return TestVerifyCertificate(kResultSuccess, der_cert_path,
- cert_verification_time, cast_trust_store) &&
+ cert_verification_time,
+ cast_trust_store.get()) &&
TestVerifyCRL(kResultSuccess, crl_bundle, crl_verification_time,
- crl_trust_store) &&
- TestVerifyRevocation(CastCertError::kErrCertsRevoked,
- der_cert_path, crl_bundle,
- crl_verification_time, cert_verification_time,
- false, cast_trust_store, crl_trust_store);
+ crl_trust_store.get()) &&
+ TestVerifyRevocation(
+ CastCertError::kErrCertsRevoked, der_cert_path, crl_bundle,
+ crl_verification_time, cert_verification_time, true,
+ cast_trust_store.get(), crl_trust_store.get());
case SUCCESS:
return (crl_bundle.empty() ||
TestVerifyCRL(kResultSuccess, crl_bundle, crl_verification_time,
- crl_trust_store)) &&
+ crl_trust_store.get())) &&
TestVerifyCertificate(kResultSuccess, der_cert_path,
- cert_verification_time, cast_trust_store) &&
+ cert_verification_time,
+ cast_trust_store.get()) &&
TestVerifyRevocation(CastCertError::kNone, der_cert_path,
crl_bundle, crl_verification_time,
cert_verification_time, !crl_bundle.empty(),
- cast_trust_store, crl_trust_store);
+ cast_trust_store.get(),
+ crl_trust_store.get());
case UNSPECIFIED:
return false;
}
return false;
}
-std::string ReadEntireFileToString(const std::string& filename) {
- int fd = open(filename.c_str(), O_RDONLY);
- if (fd == -1) {
- return {};
- }
- off_t file_size = lseek(fd, 0, SEEK_END);
- lseek(fd, 0, SEEK_SET);
- std::string contents(file_size, 0);
- off_t bytes_read = 0;
- while (bytes_read < file_size) {
- int ret = read(fd, &contents[bytes_read], file_size - bytes_read);
- if (ret == -1 && errno != EINTR) {
- return {};
- } else {
- bytes_read += ret;
- }
- }
- close(fd);
-
- return contents;
-}
-
// Parses the provided test suite provided in wire-format proto.
// Each test contains the inputs and the expected output.
// To see the description of the test, execute the test.
// These tests are generated by a test generator in google3.
void RunTestSuite(const std::string& test_suite_file_name) {
- std::string testsuite_raw = ReadEntireFileToString(test_suite_file_name);
+ std::string testsuite_raw =
+ testing::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 d64f10c4..fa04a2e9 100644
--- a/cast/common/certificate/test_helpers.cc
+++ b/cast/common/certificate/test_helpers.cc
@@ -8,10 +8,35 @@
#include <stdio.h>
#include <string.h>
+#include "platform/api/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) {
+ 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;
+ }
+ }
+ fclose(file);
+
+ return contents;
+}
+
std::vector<std::string> ReadCertificatesFromPemFile(
const std::string& filename) {
FILE* fp = fopen(filename.c_str(), "r");
@@ -40,6 +65,66 @@ std::vector<std::string> ReadCertificatesFromPemFile(
return certs;
}
+SignatureTestData::SignatureTestData()
+ : message{nullptr, 0}, sha1{nullptr, 0}, sha256{nullptr, 0} {}
+
+SignatureTestData::~SignatureTestData() {
+ OPENSSL_free(const_cast<uint8_t*>(message.data));
+ OPENSSL_free(const_cast<uint8_t*>(sha1.data));
+ OPENSSL_free(const_cast<uint8_t*>(sha256.data));
+}
+
+SignatureTestData ReadSignatureTestData(const std::string& filename) {
+ FILE* fp = fopen(filename.c_str(), "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);
+ } else {
+ break;
+ }
+ }
+ OSP_DCHECK(result.message.data);
+ OSP_DCHECK(result.sha1.data);
+ OSP_DCHECK(result.sha256.data);
+
+ return result;
+}
+
+std::unique_ptr<TrustStore> CreateTrustStoreFromPemFile(
+ const std::string& filename) {
+ std::unique_ptr<TrustStore> store = std::make_unique<TrustStore>();
+
+ std::vector<std::string> certs =
+ testing::ReadCertificatesFromPemFile(filename);
+ for (const auto& der_cert : certs) {
+ const uint8_t* data = (const uint8_t*)der_cert.data();
+ store->certs.emplace_back(d2i_X509(nullptr, &data, der_cert.size()));
+ }
+ return store;
+}
+
} // namespace testing
} // namespace certificate
} // namespace cast
diff --git a/cast/common/certificate/test_helpers.h b/cast/common/certificate/test_helpers.h
index 75a3301f..ac3a136d 100644
--- a/cast/common/certificate/test_helpers.h
+++ b/cast/common/certificate/test_helpers.h
@@ -8,13 +8,32 @@
#include <string>
#include <vector>
+#include "cast/common/certificate/cast_cert_validator_internal.h"
+#include "cast/common/certificate/types.h"
+
namespace cast {
namespace certificate {
namespace testing {
+std::string ReadEntireFileToString(const std::string& filename);
std::vector<std::string> ReadCertificatesFromPemFile(
const std::string& filename);
+class SignatureTestData {
+ public:
+ SignatureTestData();
+ ~SignatureTestData();
+
+ ConstDataSpan message;
+ ConstDataSpan sha1;
+ ConstDataSpan sha256;
+};
+
+SignatureTestData ReadSignatureTestData(const std::string& filename);
+
+std::unique_ptr<TrustStore> CreateTrustStoreFromPemFile(
+ const std::string& filename);
+
} // namespace testing
} // namespace certificate
} // namespace cast
diff --git a/cast/common/certificate/types.cc b/cast/common/certificate/types.cc
new file mode 100644
index 00000000..82d51358
--- /dev/null
+++ b/cast/common/certificate/types.cc
@@ -0,0 +1,70 @@
+// 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/common/certificate/types.h"
+
+#include "platform/api/logging.h"
+
+namespace cast {
+namespace certificate {
+
+bool operator<(const DateTime& a, const DateTime& b) {
+ if (a.year < b.year) {
+ return true;
+ } else if (a.year > b.year) {
+ return false;
+ }
+ if (a.month < b.month) {
+ return true;
+ } else if (a.month > b.month) {
+ return false;
+ }
+ if (a.day < b.day) {
+ return true;
+ } else if (a.day > b.day) {
+ return false;
+ }
+ if (a.hour < b.hour) {
+ return true;
+ } else if (a.hour > b.hour) {
+ return false;
+ }
+ if (a.minute < b.minute) {
+ return true;
+ } else if (a.minute > b.minute) {
+ return false;
+ }
+ if (a.second < b.second) {
+ return true;
+ } else if (a.second > b.second) {
+ return false;
+ }
+ return false;
+}
+
+bool operator>(const DateTime& a, const DateTime& b) {
+ return (b < a);
+}
+
+bool DateTimeFromSeconds(uint64_t seconds, DateTime* time) {
+ struct tm tm = {};
+ time_t sec = static_cast<time_t>(seconds);
+ OSP_DCHECK_GE(sec, 0);
+ OSP_DCHECK_EQ(static_cast<uint64_t>(sec), seconds);
+ if (!gmtime_r(&sec, &tm)) {
+ return false;
+ }
+
+ time->second = tm.tm_sec;
+ time->minute = tm.tm_min;
+ time->hour = tm.tm_hour;
+ time->day = tm.tm_mday;
+ time->month = tm.tm_mon + 1;
+ time->year = tm.tm_year + 1900;
+
+ return true;
+}
+
+} // namespace certificate
+} // namespace cast
diff --git a/cast/common/certificate/types.h b/cast/common/certificate/types.h
index 2691c691..62fe45ed 100644
--- a/cast/common/certificate/types.h
+++ b/cast/common/certificate/types.h
@@ -24,6 +24,10 @@ struct DateTime {
uint8_t second;
};
+bool operator<(const DateTime& a, const DateTime& b);
+bool operator>(const DateTime& a, const DateTime& b);
+bool DateTimeFromSeconds(uint64_t seconds, DateTime* time);
+
} // namespace certificate
} // namespace cast
diff --git a/cast/sender/DEPS b/cast/sender/DEPS
index e386f11a..7ab7a51a 100644
--- a/cast/sender/DEPS
+++ b/cast/sender/DEPS
@@ -2,6 +2,6 @@
include_rules = [
# libcast sender code must not depend on the receiver.
- '+cast/common/public',
+ '+cast/common',
'+cast/sender'
]
diff --git a/cast/sender/channel/BUILD.gn b/cast/sender/channel/BUILD.gn
new file mode 100644
index 00000000..c383b6fa
--- /dev/null
+++ b/cast/sender/channel/BUILD.gn
@@ -0,0 +1,30 @@
+# 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 = [
+ "cast_auth_util.cc",
+ "cast_auth_util.h",
+ ]
+
+ deps = [
+ "../../../platform",
+ "proto",
+ ]
+}
+
+source_set("unittests") {
+ testonly = true
+ sources = [
+ "cast_auth_util_unittest.cc",
+ ]
+
+ deps = [
+ ":channel",
+ "../../../platform",
+ "../../../third_party/googletest:gtest",
+ "../../common/certificate/proto:unittest_proto",
+ "proto",
+ ]
+}
diff --git a/cast/sender/channel/cast_auth_util.cc b/cast/sender/channel/cast_auth_util.cc
new file mode 100644
index 00000000..ec9a889b
--- /dev/null
+++ b/cast/sender/channel/cast_auth_util.cc
@@ -0,0 +1,379 @@
+// 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/sender/channel/cast_auth_util.h"
+
+#include <openssl/rand.h>
+
+#include <vector>
+
+#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 "platform/api/logging.h"
+#include "platform/api/time.h"
+#include "platform/base/error.h"
+
+namespace cast {
+namespace channel {
+namespace {
+
+#define PARSE_ERROR_PREFIX "Failed to parse auth message: "
+
+// The maximum number of days a cert can live for.
+const int kMaxSelfSignedCertLifetimeInDays = 4;
+
+// The size of the nonce challenge in bytes.
+const int kNonceSizeInBytes = 16;
+
+// The number of hours after which a nonce is regenerated.
+long kNonceExpirationTimeInHours = 24;
+
+using CastCertError = openscreen::Error::Code;
+
+// Extracts an embedded DeviceAuthMessage payload from an auth challenge reply
+// message.
+openscreen::Error ParseAuthMessage(const CastMessage& challenge_reply,
+ DeviceAuthMessage* auth_message) {
+ if (challenge_reply.payload_type() != CastMessage_PayloadType_BINARY) {
+ return openscreen::Error(CastCertError::kCastV2WrongPayloadType,
+ PARSE_ERROR_PREFIX
+ "Wrong payload type in challenge reply");
+ }
+ if (!challenge_reply.has_payload_binary()) {
+ return openscreen::Error(
+ CastCertError::kCastV2NoPayload, PARSE_ERROR_PREFIX
+ "Payload type is binary but payload_binary field not set");
+ }
+ if (!auth_message->ParseFromString(challenge_reply.payload_binary())) {
+ return openscreen::Error(
+ CastCertError::kCastV2PayloadParsingFailed, PARSE_ERROR_PREFIX
+ "Cannot parse binary payload into DeviceAuthMessage");
+ }
+
+ if (auth_message->has_error()) {
+ std::stringstream ss;
+ ss << PARSE_ERROR_PREFIX "Auth message error: "
+ << auth_message->error().error_type();
+ return openscreen::Error(CastCertError::kCastV2MessageError, ss.str());
+ }
+ if (!auth_message->has_response()) {
+ return openscreen::Error(CastCertError::kCastV2NoResponse,
+ PARSE_ERROR_PREFIX
+ "Auth message has no response field");
+ }
+ return openscreen::Error::None();
+}
+
+class CastNonce {
+ public:
+ static CastNonce* GetInstance() {
+ static CastNonce* cast_nonce = new CastNonce();
+ return cast_nonce;
+ }
+
+ static const std::string& Get() {
+ GetInstance()->EnsureNonceTimely();
+ return GetInstance()->nonce_;
+ }
+
+ private:
+ CastNonce() : nonce_(kNonceSizeInBytes, 0) { GenerateNonce(); }
+ void GenerateNonce() {
+ OSP_CHECK_EQ(
+ RAND_bytes(reinterpret_cast<uint8_t*>(&nonce_[0]), kNonceSizeInBytes),
+ 1);
+ nonce_generation_time_ = openscreen::platform::GetWallTimeSinceUnixEpoch();
+ }
+
+ void EnsureNonceTimely() {
+ if (openscreen::platform::GetWallTimeSinceUnixEpoch() >
+ (nonce_generation_time_ +
+ std::chrono::hours(kNonceExpirationTimeInHours))) {
+ GenerateNonce();
+ }
+ }
+
+ // The nonce challenge to send to the Cast receiver.
+ // The nonce is updated daily.
+ std::string nonce_;
+ std::chrono::seconds nonce_generation_time_;
+};
+
+// Maps CastCertError from certificate verification to openscreen::Error.
+// If crl_required is set to false, all revocation related errors are ignored.
+openscreen::Error MapToOpenscreenError(CastCertError error, bool crl_required) {
+ switch (error) {
+ case CastCertError::kErrCertsMissing:
+ return openscreen::Error(CastCertError::kCastV2PeerCertEmpty,
+ "Failed to locate certificates.");
+ case CastCertError::kErrCertsParse:
+ return openscreen::Error(CastCertError::kErrCertsParse,
+ "Failed to parse certificates.");
+ case CastCertError::kErrCertsDateInvalid:
+ return openscreen::Error(CastCertError::kCastV2CertNotSignedByTrustedCa,
+ "Failed date validity check.");
+ case CastCertError::kErrCertsVerifyGeneric:
+ return openscreen::Error(
+ CastCertError::kCastV2CertNotSignedByTrustedCa,
+ "Failed with a generic certificate verification error.");
+ case CastCertError::kErrCertsRestrictions:
+ return openscreen::Error(CastCertError::kCastV2CertNotSignedByTrustedCa,
+ "Failed certificate restrictions.");
+ case CastCertError::kErrCrlInvalid:
+ // This error is only encountered if |crl_required| is true.
+ OSP_DCHECK(crl_required);
+ return openscreen::Error(CastCertError::kErrCrlInvalid,
+ "Failed to provide a valid CRL.");
+ case CastCertError::kErrCertsRevoked:
+ return openscreen::Error(CastCertError::kErrCertsRevoked,
+ "Failed certificate revocation check.");
+ case CastCertError::kNone:
+ return openscreen::Error::None();
+ default:
+ return openscreen::Error(CastCertError::kCastV2CertNotSignedByTrustedCa,
+ "Failed verifying cast device certificate.");
+ }
+ return openscreen::Error::None();
+}
+
+openscreen::Error VerifyAndMapDigestAlgorithm(
+ HashAlgorithm response_digest_algorithm,
+ certificate::DigestAlgorithm* digest_algorithm,
+ bool enforce_sha256_checking) {
+ switch (response_digest_algorithm) {
+ case SHA1:
+ if (enforce_sha256_checking) {
+ return openscreen::Error(CastCertError::kCastV2DigestUnsupported,
+ "Unsupported digest algorithm.");
+ }
+ *digest_algorithm = certificate::DigestAlgorithm::kSha1;
+ break;
+ case SHA256:
+ *digest_algorithm = certificate::DigestAlgorithm::kSha256;
+ break;
+ default:
+ return CastCertError::kCastV2DigestUnsupported;
+ }
+ return openscreen::Error::None();
+}
+
+} // namespace
+
+// static
+AuthContext AuthContext::Create() {
+ return AuthContext(CastNonce::Get());
+}
+
+AuthContext::AuthContext(const std::string& nonce) : nonce_(nonce) {}
+
+AuthContext::~AuthContext() {}
+
+openscreen::Error AuthContext::VerifySenderNonce(
+ const std::string& nonce_response,
+ bool enforce_nonce_checking) const {
+ if (nonce_ != nonce_response) {
+ if (enforce_nonce_checking) {
+ return openscreen::Error(CastCertError::kCastV2SenderNonceMismatch,
+ "Sender nonce mismatched.");
+ }
+ }
+ return openscreen::Error::None();
+}
+
+openscreen::Error VerifyTLSCertificateValidity(
+ X509* peer_cert,
+ std::chrono::seconds verification_time) {
+ // Ensure the peer cert is valid and doesn't have an excessive remaining
+ // lifetime. Although it is not verified as an X.509 certificate, the entire
+ // structure is signed by the AuthResponse, so the validity field from X.509
+ // is repurposed as this signature's expiration.
+ certificate::DateTime not_before;
+ certificate::DateTime not_after;
+ if (!certificate::GetCertValidTimeRange(peer_cert, &not_before, &not_after)) {
+ return openscreen::Error(CastCertError::kErrCertsParse, PARSE_ERROR_PREFIX
+ "Parsing validity fields failed.");
+ }
+
+ std::chrono::seconds lifetime_limit =
+ verification_time +
+ std::chrono::hours(24 * kMaxSelfSignedCertLifetimeInDays);
+ certificate::DateTime verification_time_exploded = {};
+ certificate::DateTime lifetime_limit_exploded = {};
+ OSP_CHECK(certificate::DateTimeFromSeconds(verification_time.count(),
+ &verification_time_exploded));
+ OSP_CHECK(certificate::DateTimeFromSeconds(lifetime_limit.count(),
+ &lifetime_limit_exploded));
+ if (verification_time_exploded < not_before) {
+ return openscreen::Error(
+ CastCertError::kCastV2TlsCertValidStartDateInFuture,
+ PARSE_ERROR_PREFIX "Certificate's valid start date is in the future.");
+ }
+ if (not_after < verification_time_exploded) {
+ return openscreen::Error(CastCertError::kCastV2TlsCertExpired,
+ PARSE_ERROR_PREFIX "Certificate has expired.");
+ }
+ if (lifetime_limit_exploded < not_after) {
+ return openscreen::Error(CastCertError::kCastV2TlsCertValidityPeriodTooLong,
+ PARSE_ERROR_PREFIX
+ "Peer cert lifetime is too long.");
+ }
+ return openscreen::Error::None();
+}
+
+ErrorOr<CastDeviceCertPolicy> AuthenticateChallengeReply(
+ const CastMessage& challenge_reply,
+ X509* peer_cert,
+ const AuthContext& auth_context) {
+ DeviceAuthMessage auth_message;
+ openscreen::Error result = ParseAuthMessage(challenge_reply, &auth_message);
+ if (!result.ok()) {
+ return result;
+ }
+
+ result = VerifyTLSCertificateValidity(
+ peer_cert, openscreen::platform::GetWallTimeSinceUnixEpoch());
+ if (!result.ok()) {
+ return result;
+ }
+
+ const AuthResponse& response = auth_message.response();
+ const std::string& nonce_response = response.sender_nonce();
+
+ result = auth_context.VerifySenderNonce(nonce_response);
+ if (!result.ok()) {
+ return result;
+ }
+
+ int len = i2d_X509(peer_cert, nullptr);
+ if (len <= 0) {
+ return openscreen::Error(CastCertError::kErrCertsParse,
+ "Serializing cert failed.");
+ }
+ std::string peer_cert_der(len, 0);
+ uint8_t* data = reinterpret_cast<uint8_t*>(&peer_cert_der[0]);
+ if (!i2d_X509(peer_cert, &data)) {
+ return openscreen::Error(CastCertError::kErrCertsParse,
+ "Serializing cert failed.");
+ }
+ size_t actual_size = data - reinterpret_cast<uint8_t*>(&peer_cert_der[0]);
+ OSP_DCHECK_EQ(actual_size, peer_cert_der.size());
+ peer_cert_der.resize(actual_size);
+
+ return VerifyCredentials(response, nonce_response + peer_cert_der);
+}
+
+// This function does the following
+//
+// * Verifies that the certificate chain |response.client_auth_certificate| +
+// |response.intermediate_certificate| is valid and chains to a trusted Cast
+// root. The list of trusted Cast roots can be overrided by providing a
+// non-nullptr |cast_trust_store|. The certificate is verified at
+// |verification_time|.
+//
+// * Verifies that none of the certificates in the chain are revoked based on
+// the CRL provided in the response |response.crl|. The CRL is verified to be
+// valid and its issuer certificate chains to a trusted Cast CRL root. The
+// list of trusted Cast CRL roots can be overrided by providing a non-nullptr
+// |crl_trust_store|. If |crl_policy| is kCrlOptional then the result of
+// revocation checking is ignored. The CRL is verified at |verification_time|.
+//
+// * Verifies that |response.signature| matches the signature of
+// |signature_input| by |response.client_auth_certificate|'s public key.
+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) {
+ if (response.signature().empty() && !signature_input.empty()) {
+ return openscreen::Error(CastCertError::kCastV2SignatureEmpty,
+ "Signature is empty.");
+ }
+
+ // Verify the certificate
+ std::unique_ptr<certificate::CertVerificationContext> verification_context;
+
+ // Build a single vector containing the certificate chain.
+ 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());
+
+ // Parse the CRL.
+ std::unique_ptr<certificate::CastCRL> crl;
+ if (!response.crl().empty()) {
+ crl = certificate::ParseAndVerifyCRL(response.crl(), verification_time,
+ crl_trust_store);
+ }
+
+ // Perform certificate verification.
+ certificate::CastDeviceCertPolicy device_policy;
+ openscreen::Error verify_result = certificate::VerifyDeviceCert(
+ cert_chain, verification_time, &verification_context, &device_policy,
+ crl.get(), crl_policy, cast_trust_store);
+
+ // Handle and report errors.
+ openscreen::Error result = MapToOpenscreenError(
+ verify_result.code(), crl_policy == certificate::CRLPolicy::kCrlRequired);
+ if (!result.ok()) {
+ return result;
+ }
+
+ // The certificate is verified at this point.
+ certificate::DigestAlgorithm digest_algorithm;
+ openscreen::Error digest_result = VerifyAndMapDigestAlgorithm(
+ response.hash_algorithm(), &digest_algorithm, enforce_sha256_checking);
+ if (!digest_result.ok()) {
+ return digest_result;
+ }
+
+ certificate::ConstDataSpan signature = {
+ reinterpret_cast<const uint8_t*>(response.signature().data()),
+ static_cast<uint32_t>(response.signature().size())};
+ certificate::ConstDataSpan siginput = {
+ reinterpret_cast<const uint8_t*>(signature_input.data()),
+ static_cast<uint32_t>(signature_input.size())};
+ if (!verification_context->VerifySignatureOverData(signature, siginput,
+ digest_algorithm)) {
+ return openscreen::Error(CastCertError::kCastV2SignedBlobsMismatch,
+ "Failed verifying signature over data.");
+ }
+
+ return device_policy;
+}
+
+ErrorOr<CastDeviceCertPolicy> VerifyCredentials(
+ const AuthResponse& response,
+ const std::string& signature_input,
+ bool enforce_revocation_checking,
+ bool enforce_sha256_checking) {
+ certificate::DateTime now = {};
+ OSP_CHECK(certificate::DateTimeFromSeconds(
+ openscreen::platform::GetWallTimeSinceUnixEpoch().count(), &now));
+ certificate::CRLPolicy policy = (enforce_revocation_checking)
+ ? certificate::CRLPolicy::kCrlRequired
+ : certificate::CRLPolicy::kCrlOptional;
+ return VerifyCredentialsImpl(response, signature_input, policy, nullptr,
+ nullptr, now, enforce_sha256_checking);
+}
+
+ErrorOr<CastDeviceCertPolicy> VerifyCredentialsForTest(
+ const AuthResponse& response,
+ const std::string& signature_input,
+ certificate::CRLPolicy crl_policy,
+ certificate::TrustStore* cast_trust_store,
+ certificate::TrustStore* crl_trust_store,
+ const certificate::DateTime& verification_time,
+ bool enforce_sha256_checking) {
+ return VerifyCredentialsImpl(response, signature_input, crl_policy,
+ cast_trust_store, crl_trust_store,
+ verification_time, enforce_sha256_checking);
+}
+
+} // namespace channel
+} // namespace cast
diff --git a/cast/sender/channel/cast_auth_util.h b/cast/sender/channel/cast_auth_util.h
new file mode 100644
index 00000000..35a9a704
--- /dev/null
+++ b/cast/sender/channel/cast_auth_util.h
@@ -0,0 +1,95 @@
+// 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_SENDER_CHANNEL_CAST_AUTH_UTIL_H_
+#define CAST_SENDER_CHANNEL_CAST_AUTH_UTIL_H_
+
+#include <openssl/x509.h>
+
+#include <string>
+
+#include "cast/common/certificate/cast_cert_validator.h"
+#include "cast/sender/channel/proto/cast_channel.pb.h"
+#include "platform/base/error.h"
+
+namespace cast {
+namespace certificate {
+enum class CRLPolicy;
+struct DateTime;
+struct TrustStore;
+} // namespace certificate
+} // namespace cast
+
+namespace cast {
+namespace channel {
+
+class AuthResponse;
+class CastMessage;
+
+template <typename T>
+using ErrorOr = openscreen::ErrorOr<T>;
+using CastDeviceCertPolicy = certificate::CastDeviceCertPolicy;
+
+class AuthContext {
+ public:
+ ~AuthContext();
+
+ // Get an auth challenge context.
+ // The same context must be used in the challenge and reply.
+ static AuthContext Create();
+
+ // Verifies the nonce received in the response is equivalent to the one sent.
+ // Returns success if |nonce_response| matches nonce_
+ openscreen::Error VerifySenderNonce(
+ const std::string& nonce_response,
+ bool enforce_nonce_checking = false) const;
+
+ // The nonce challenge.
+ const std::string& nonce() const { return nonce_; }
+
+ private:
+ explicit AuthContext(const std::string& nonce);
+
+ const std::string nonce_;
+};
+
+// Authenticates the given |challenge_reply|:
+// 1. Signature contained in the reply is valid.
+// 2. Certficate used to sign is rooted to a trusted CA.
+ErrorOr<CastDeviceCertPolicy> AuthenticateChallengeReply(
+ const CastMessage& challenge_reply,
+ X509* peer_cert,
+ const AuthContext& auth_context);
+
+// Performs a quick check of the TLS certificate for time validity requirements.
+openscreen::Error VerifyTLSCertificateValidity(
+ X509* peer_cert,
+ std::chrono::seconds verification_time);
+
+// Auth-library specific implementation of cryptographic signature verification
+// routines. Verifies that |response| contains a valid signature of
+// |signature_input|.
+ErrorOr<CastDeviceCertPolicy> VerifyCredentials(
+ const AuthResponse& response,
+ const std::string& signature_input,
+ bool enforce_revocation_checking = false,
+ bool enforce_sha256_checking = false);
+
+// Exposed for testing only.
+//
+// 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,
+ certificate::CRLPolicy crl_policy,
+ certificate::TrustStore* cast_trust_store,
+ certificate::TrustStore* crl_trust_store,
+ const certificate::DateTime& verification_time,
+ bool enforce_sha256_checking = false);
+
+} // namespace channel
+} // namespace cast
+
+#endif // CAST_SENDER_CHANNEL_CAST_AUTH_UTIL_H_
diff --git a/cast/sender/channel/cast_auth_util_unittest.cc b/cast/sender/channel/cast_auth_util_unittest.cc
new file mode 100644
index 00000000..b8c94d53
--- /dev/null
+++ b/cast/sender/channel/cast_auth_util_unittest.cc
@@ -0,0 +1,472 @@
+// 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/sender/channel/cast_auth_util.h"
+
+#include <string>
+
+#include "cast/common/certificate/cast_cert_validator.h"
+#include "cast/common/certificate/cast_crl.h"
+#include "cast/common/certificate/proto/test_suite.pb.h"
+#include "cast/common/certificate/test_helpers.h"
+#include "cast/sender/channel/proto/cast_channel.pb.h"
+#include "gtest/gtest.h"
+#include "platform/api/logging.h"
+#include "platform/api/time.h"
+
+namespace cast {
+namespace channel {
+namespace {
+
+using ErrorCode = openscreen::Error::Code;
+
+bool ConvertTimeSeconds(const certificate::DateTime& time, uint64_t* seconds) {
+ static constexpr uint64_t kDaysPerYear = 365;
+ static constexpr uint64_t kHoursPerDay = 24;
+ static constexpr uint64_t kMinutesPerHour = 60;
+ static constexpr uint64_t kSecondsPerMinute = 60;
+
+ static constexpr uint64_t kSecondsPerDay =
+ kSecondsPerMinute * kMinutesPerHour * kHoursPerDay;
+ static constexpr uint64_t kDaysPerQuadYear = 4 * kDaysPerYear + 1;
+ static constexpr uint64_t kDaysPerCentury =
+ kDaysPerQuadYear * 24 + kDaysPerYear * 4;
+ static constexpr uint64_t kDaysPerQuadCentury = 4 * kDaysPerCentury + 1;
+
+ static constexpr uint64_t kDaysPerMonth[] = {
+ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
+ };
+
+ bool is_leap_year =
+ (time.year % 4 == 0 && (time.year % 100 != 0 || time.year % 400 == 0));
+ if (time.year < 1970 || time.month < 1 || time.day < 1 ||
+ time.day > (kDaysPerMonth[time.month - 1] + is_leap_year) ||
+ time.month > 12 || time.hour > 23 || time.minute > 59 ||
+ time.second > 60) {
+ return false;
+ }
+ uint64_t result = 0;
+ uint64_t year = time.year - 1970;
+ uint64_t first_two_years = year >= 2;
+ result += first_two_years * 2 * kDaysPerYear * kSecondsPerDay;
+ year -= first_two_years * 2;
+
+ if (first_two_years) {
+ uint64_t twenty_eight_years = year >= 28;
+ result += twenty_eight_years * 7 * kDaysPerQuadYear * kSecondsPerDay;
+ year -= twenty_eight_years * 28;
+
+ if (twenty_eight_years) {
+ uint64_t quad_centuries = year / 400;
+ result += quad_centuries * kDaysPerQuadCentury * kSecondsPerDay;
+ year -= quad_centuries * 400;
+
+ uint64_t first_century = year >= 100;
+ result += first_century * (kDaysPerCentury + 1) * kSecondsPerDay;
+ year -= first_century * 100;
+
+ uint64_t centuries = year / 100;
+ result += centuries * kDaysPerCentury * kSecondsPerDay;
+ year -= centuries * 100;
+ }
+
+ uint64_t quad_years = year / 4;
+ result += quad_years * kDaysPerQuadYear * kSecondsPerDay;
+ year -= quad_years * 4;
+
+ uint64_t first_year = year >= 1;
+ result += first_year * (kDaysPerYear + 1) * kSecondsPerDay;
+ year -= first_year;
+
+ result += year * kDaysPerYear * kSecondsPerDay;
+ OSP_DCHECK_LE(year, 2);
+ }
+
+ for (int i = 0; i < time.month - 1; ++i) {
+ uint64_t days = kDaysPerMonth[i];
+ result += days * kSecondsPerDay;
+ }
+ if (time.month >= 3 && is_leap_year) {
+ result += kSecondsPerDay;
+ }
+ result += (time.day - 1) * kSecondsPerDay;
+ result += time.hour * kMinutesPerHour * kSecondsPerMinute;
+ result += time.minute * kSecondsPerMinute;
+ result += time.second;
+
+ *seconds = result;
+ return true;
+}
+
+#define TEST_DATA_PREFIX "test/data/cast/common/certificate/"
+
+class CastAuthUtilTest : public testing::Test {
+ public:
+ CastAuthUtilTest() {}
+ ~CastAuthUtilTest() override {}
+
+ void SetUp() override {}
+
+ protected:
+ static AuthResponse CreateAuthResponse(std::string* signed_data,
+ HashAlgorithm digest_algorithm) {
+ std::vector<std::string> chain =
+ certificate::testing::ReadCertificatesFromPemFile(
+ TEST_DATA_PREFIX "certificates/chromecast_gen1.pem");
+ OSP_CHECK(!chain.empty());
+
+ certificate::testing::SignatureTestData signatures =
+ certificate::testing::ReadSignatureTestData(
+ TEST_DATA_PREFIX "signeddata/2ZZBG9_FA8FCA3EF91A.pem");
+
+ AuthResponse response;
+
+ response.set_client_auth_certificate(chain[0]);
+ for (size_t i = 1; i < chain.size(); ++i)
+ response.add_intermediate_certificate(chain[i]);
+
+ response.set_hash_algorithm(digest_algorithm);
+ switch (digest_algorithm) {
+ case SHA1:
+ response.set_signature(
+ std::string(reinterpret_cast<const char*>(signatures.sha1.data),
+ signatures.sha1.length));
+ break;
+ case SHA256:
+ response.set_signature(
+ std::string(reinterpret_cast<const char*>(signatures.sha256.data),
+ signatures.sha256.length));
+ break;
+ }
+ signed_data->assign(reinterpret_cast<const char*>(signatures.message.data),
+ signatures.message.length);
+
+ return response;
+ }
+
+ // Mangles a string by inverting the first byte.
+ static void MangleString(std::string* str) { (*str)[0] = ~(*str)[0]; }
+};
+
+// Note on expiration: VerifyCredentials() depends on the system clock. In
+// practice this shouldn't be a problem though since the certificate chain
+// being verified doesn't expire until 2032.
+TEST_F(CastAuthUtilTest, VerifySuccess) {
+ std::string signed_data;
+ AuthResponse auth_response = CreateAuthResponse(&signed_data, SHA256);
+ certificate::DateTime now = {};
+ ASSERT_TRUE(certificate::DateTimeFromSeconds(
+ openscreen::platform::GetWallTimeSinceUnixEpoch().count(), &now));
+ ErrorOr<CastDeviceCertPolicy> result = VerifyCredentialsForTest(
+ auth_response, signed_data, certificate::CRLPolicy::kCrlOptional, nullptr,
+ nullptr, now);
+ EXPECT_TRUE(result);
+ EXPECT_EQ(certificate::CastDeviceCertPolicy::kUnrestricted, result.value());
+}
+
+TEST_F(CastAuthUtilTest, VerifyBadCA) {
+ std::string signed_data;
+ AuthResponse auth_response = CreateAuthResponse(&signed_data, SHA256);
+ MangleString(auth_response.mutable_intermediate_certificate(0));
+ ErrorOr<CastDeviceCertPolicy> result =
+ VerifyCredentials(auth_response, signed_data);
+ EXPECT_FALSE(result);
+ EXPECT_EQ(ErrorCode::kErrCertsParse, result.error().code());
+}
+
+TEST_F(CastAuthUtilTest, VerifyBadClientAuthCert) {
+ std::string signed_data;
+ AuthResponse auth_response = CreateAuthResponse(&signed_data, SHA256);
+ MangleString(auth_response.mutable_client_auth_certificate());
+ ErrorOr<CastDeviceCertPolicy> result =
+ VerifyCredentials(auth_response, signed_data);
+ EXPECT_FALSE(result);
+ EXPECT_EQ(ErrorCode::kErrCertsParse, result.error().code());
+}
+
+TEST_F(CastAuthUtilTest, VerifyBadSignature) {
+ std::string signed_data;
+ AuthResponse auth_response = CreateAuthResponse(&signed_data, SHA256);
+ MangleString(auth_response.mutable_signature());
+ ErrorOr<CastDeviceCertPolicy> result =
+ VerifyCredentials(auth_response, signed_data);
+ EXPECT_FALSE(result);
+ EXPECT_EQ(ErrorCode::kCastV2SignedBlobsMismatch, result.error().code());
+}
+
+TEST_F(CastAuthUtilTest, VerifyEmptySignature) {
+ std::string signed_data;
+ AuthResponse auth_response = CreateAuthResponse(&signed_data, SHA256);
+ auth_response.mutable_signature()->clear();
+ ErrorOr<CastDeviceCertPolicy> result =
+ VerifyCredentials(auth_response, signed_data);
+ EXPECT_FALSE(result);
+ EXPECT_EQ(ErrorCode::kCastV2SignatureEmpty, result.error().code());
+}
+
+TEST_F(CastAuthUtilTest, VerifyUnsupportedDigest) {
+ std::string signed_data;
+ AuthResponse auth_response = CreateAuthResponse(&signed_data, SHA1);
+ certificate::DateTime now = {};
+ ASSERT_TRUE(certificate::DateTimeFromSeconds(
+ openscreen::platform::GetWallTimeSinceUnixEpoch().count(), &now));
+ ErrorOr<CastDeviceCertPolicy> result = VerifyCredentialsForTest(
+ auth_response, signed_data, certificate::CRLPolicy::kCrlOptional, nullptr,
+ nullptr, now, true);
+ EXPECT_FALSE(result);
+ EXPECT_EQ(ErrorCode::kCastV2DigestUnsupported, result.error().code());
+}
+
+TEST_F(CastAuthUtilTest, VerifyBackwardsCompatibleDigest) {
+ std::string signed_data;
+ AuthResponse auth_response = CreateAuthResponse(&signed_data, SHA1);
+ certificate::DateTime now = {};
+ ASSERT_TRUE(certificate::DateTimeFromSeconds(
+ openscreen::platform::GetWallTimeSinceUnixEpoch().count(), &now));
+ ErrorOr<CastDeviceCertPolicy> result = VerifyCredentialsForTest(
+ auth_response, signed_data, certificate::CRLPolicy::kCrlOptional, nullptr,
+ nullptr, now);
+ EXPECT_TRUE(result);
+}
+
+TEST_F(CastAuthUtilTest, VerifyBadPeerCert) {
+ std::string signed_data;
+ AuthResponse auth_response = CreateAuthResponse(&signed_data, SHA256);
+ MangleString(&signed_data);
+ ErrorOr<CastDeviceCertPolicy> result =
+ VerifyCredentials(auth_response, signed_data);
+ EXPECT_FALSE(result);
+ EXPECT_EQ(ErrorCode::kCastV2SignedBlobsMismatch, result.error().code());
+}
+
+TEST_F(CastAuthUtilTest, VerifySenderNonceMatch) {
+ AuthContext context = AuthContext::Create();
+ ErrorOr<CastDeviceCertPolicy> result =
+ context.VerifySenderNonce(context.nonce(), true);
+ EXPECT_TRUE(result);
+}
+
+TEST_F(CastAuthUtilTest, VerifySenderNonceMismatch) {
+ AuthContext context = AuthContext::Create();
+ std::string received_nonce = "test2";
+ EXPECT_NE(received_nonce, context.nonce());
+ ErrorOr<CastDeviceCertPolicy> result =
+ context.VerifySenderNonce(received_nonce, true);
+ EXPECT_FALSE(result);
+ EXPECT_EQ(ErrorCode::kCastV2SenderNonceMismatch, result.error().code());
+}
+
+TEST_F(CastAuthUtilTest, VerifySenderNonceMissing) {
+ AuthContext context = AuthContext::Create();
+ std::string received_nonce;
+ EXPECT_FALSE(context.nonce().empty());
+ ErrorOr<CastDeviceCertPolicy> result =
+ context.VerifySenderNonce(received_nonce, true);
+ EXPECT_FALSE(result);
+ EXPECT_EQ(ErrorCode::kCastV2SenderNonceMismatch, result.error().code());
+}
+
+TEST_F(CastAuthUtilTest, VerifyTLSCertificateSuccess) {
+ std::vector<std::string> tls_cert_der =
+ certificate::testing::ReadCertificatesFromPemFile(
+ TEST_DATA_PREFIX "certificates/test_tls_cert.pem");
+ std::string& der_cert = tls_cert_der[0];
+ const uint8_t* data = (const uint8_t*)der_cert.data();
+ X509* tls_cert = d2i_X509(nullptr, &data, der_cert.size());
+ certificate::DateTime not_before;
+ certificate::DateTime not_after;
+ ASSERT_TRUE(
+ certificate::GetCertValidTimeRange(tls_cert, &not_before, &not_after));
+ uint64_t x;
+ ASSERT_TRUE(ConvertTimeSeconds(not_before, &x));
+ std::chrono::seconds s(x);
+
+ ErrorOr<CastDeviceCertPolicy> result =
+ VerifyTLSCertificateValidity(tls_cert, s);
+ EXPECT_TRUE(result);
+ X509_free(tls_cert);
+}
+
+TEST_F(CastAuthUtilTest, VerifyTLSCertificateTooEarly) {
+ std::vector<std::string> tls_cert_der =
+ certificate::testing::ReadCertificatesFromPemFile(
+ TEST_DATA_PREFIX "certificates/test_tls_cert.pem");
+ std::string& der_cert = tls_cert_der[0];
+ const uint8_t* data = (const uint8_t*)der_cert.data();
+ X509* tls_cert = d2i_X509(nullptr, &data, der_cert.size());
+ certificate::DateTime not_before;
+ certificate::DateTime not_after;
+ ASSERT_TRUE(
+ certificate::GetCertValidTimeRange(tls_cert, &not_before, &not_after));
+ uint64_t x;
+ ASSERT_TRUE(ConvertTimeSeconds(not_before, &x));
+ std::chrono::seconds s(x - 1);
+
+ ErrorOr<CastDeviceCertPolicy> result =
+ VerifyTLSCertificateValidity(tls_cert, s);
+ EXPECT_FALSE(result);
+ EXPECT_EQ(ErrorCode::kCastV2TlsCertValidStartDateInFuture,
+ result.error().code());
+ X509_free(tls_cert);
+}
+
+TEST_F(CastAuthUtilTest, VerifyTLSCertificateTooLate) {
+ std::vector<std::string> tls_cert_der =
+ certificate::testing::ReadCertificatesFromPemFile(
+ TEST_DATA_PREFIX "certificates/test_tls_cert.pem");
+ std::string& der_cert = tls_cert_der[0];
+ const uint8_t* data = (const uint8_t*)der_cert.data();
+ X509* tls_cert = d2i_X509(nullptr, &data, der_cert.size());
+ certificate::DateTime not_before;
+ certificate::DateTime not_after;
+ ASSERT_TRUE(
+ certificate::GetCertValidTimeRange(tls_cert, &not_before, &not_after));
+ uint64_t x;
+ ASSERT_TRUE(ConvertTimeSeconds(not_after, &x));
+ std::chrono::seconds s(x + 2);
+
+ ErrorOr<CastDeviceCertPolicy> result =
+ VerifyTLSCertificateValidity(tls_cert, s);
+ EXPECT_FALSE(result);
+ EXPECT_EQ(ErrorCode::kCastV2TlsCertExpired, result.error().code());
+ X509_free(tls_cert);
+}
+
+// Indicates the expected result of test step's verification.
+enum TestStepResult {
+ RESULT_SUCCESS,
+ RESULT_FAIL,
+};
+
+// Verifies that the certificate chain provided is not revoked according to
+// the provided Cast CRL at |verification_time|.
+// The provided CRL is verified at |verification_time|.
+// If |crl_required| is set, then a valid Cast CRL must be provided.
+// Otherwise, a missing CRL is be ignored.
+ErrorOr<CastDeviceCertPolicy> TestVerifyRevocation(
+ const std::vector<std::string>& certificate_chain,
+ const std::string& crl_bundle,
+ const certificate::DateTime& verification_time,
+ bool crl_required,
+ certificate::TrustStore* cast_trust_store,
+ certificate::TrustStore* crl_trust_store) {
+ AuthResponse response;
+
+ 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]);
+ }
+
+ response.set_crl(crl_bundle);
+
+ certificate::CRLPolicy crl_policy = certificate::CRLPolicy::kCrlRequired;
+ if (!crl_required && crl_bundle.empty())
+ crl_policy = certificate::CRLPolicy::kCrlOptional;
+ ErrorOr<CastDeviceCertPolicy> result =
+ VerifyCredentialsForTest(response, "", crl_policy, cast_trust_store,
+ crl_trust_store, verification_time);
+ // This test doesn't set the signature so it will just fail there.
+ EXPECT_FALSE(result);
+ return result;
+}
+
+// Runs a single test case.
+bool RunTest(const certificate::DeviceCertTest& test_case) {
+ std::unique_ptr<certificate::TrustStore> crl_trust_store;
+ std::unique_ptr<certificate::TrustStore> cast_trust_store;
+ if (test_case.use_test_trust_anchors()) {
+ crl_trust_store = certificate::testing::CreateTrustStoreFromPemFile(
+ TEST_DATA_PREFIX "certificates/cast_crl_test_root_ca.pem");
+ cast_trust_store = certificate::testing::CreateTrustStoreFromPemFile(
+ TEST_DATA_PREFIX "certificates/cast_test_root_ca.pem");
+
+ EXPECT_FALSE(crl_trust_store->certs.empty());
+ EXPECT_FALSE(cast_trust_store->certs.empty());
+ }
+
+ std::vector<std::string> certificate_chain;
+ for (auto const& cert : test_case.der_cert_path()) {
+ certificate_chain.push_back(cert);
+ }
+
+ // CastAuthUtil verifies the CRL at the same time as the certificate.
+ certificate::DateTime verification_time;
+ uint64_t cert_verify_time = test_case.cert_verification_time_seconds();
+ if (!cert_verify_time) {
+ cert_verify_time = test_case.crl_verification_time_seconds();
+ }
+ OSP_DCHECK(
+ certificate::DateTimeFromSeconds(cert_verify_time, &verification_time));
+
+ std::string crl_bundle = test_case.crl_bundle();
+ ErrorOr<CastDeviceCertPolicy> result(
+ certificate::CastDeviceCertPolicy::kUnrestricted);
+ switch (test_case.expected_result()) {
+ case certificate::PATH_VERIFICATION_FAILED:
+ result = TestVerifyRevocation(
+ certificate_chain, crl_bundle, verification_time, false,
+ cast_trust_store.get(), crl_trust_store.get());
+ EXPECT_EQ(result.error().code(),
+ ErrorCode::kCastV2CertNotSignedByTrustedCa);
+ return result.error().code() ==
+ ErrorCode::kCastV2CertNotSignedByTrustedCa;
+ case certificate::CRL_VERIFICATION_FAILED:
+ // Fall-through intended.
+ case certificate::REVOCATION_CHECK_FAILED_WITHOUT_CRL:
+ result = TestVerifyRevocation(
+ certificate_chain, crl_bundle, verification_time, true,
+ cast_trust_store.get(), crl_trust_store.get());
+ EXPECT_EQ(result.error().code(), ErrorCode::kErrCrlInvalid);
+ return result.error().code() == ErrorCode::kErrCrlInvalid;
+ case certificate::CRL_EXPIRED_AFTER_INITIAL_VERIFICATION:
+ // By-pass this test because CRL is always verified at the time the
+ // certificate is verified.
+ return true;
+ case certificate::REVOCATION_CHECK_FAILED:
+ result = TestVerifyRevocation(
+ certificate_chain, crl_bundle, verification_time, true,
+ cast_trust_store.get(), crl_trust_store.get());
+ EXPECT_EQ(result.error().code(), ErrorCode::kErrCertsRevoked);
+ return result.error().code() == ErrorCode::kErrCertsRevoked;
+ case certificate::SUCCESS:
+ result = TestVerifyRevocation(
+ certificate_chain, crl_bundle, verification_time, false,
+ cast_trust_store.get(), crl_trust_store.get());
+ EXPECT_EQ(result.error().code(), ErrorCode::kCastV2SignedBlobsMismatch);
+ return result.error().code() == ErrorCode::kCastV2SignedBlobsMismatch;
+ case certificate::UNSPECIFIED:
+ return false;
+ }
+ return false;
+}
+
+// Parses the provided test suite provided in wire-format proto.
+// Each test contains the inputs and the expected output.
+// To see the description of the test, execute the test.
+// 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);
+ certificate::DeviceCertTestSuite test_suite;
+ EXPECT_TRUE(test_suite.ParseFromString(testsuite_raw));
+ uint16_t successes = 0;
+
+ for (auto const& test_case : test_suite.tests()) {
+ bool result = RunTest(test_case);
+ EXPECT_TRUE(result) << test_case.description();
+ successes += result;
+ }
+ OSP_LOG_IF(ERROR, successes != test_suite.tests().size())
+ << "successes: " << successes
+ << ", failures: " << (test_suite.tests().size() - successes);
+}
+
+TEST_F(CastAuthUtilTest, CRLTestSuite) {
+ RunTestSuite("testsuite/testsuite1.pb");
+}
+
+} // namespace
+} // namespace channel
+} // namespace cast
diff --git a/cast/sender/channel/proto/BUILD.gn b/cast/sender/channel/proto/BUILD.gn
new file mode 100644
index 00000000..c3bfa439
--- /dev/null
+++ b/cast/sender/channel/proto/BUILD.gn
@@ -0,0 +1,11 @@
+# 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.
+
+import("//third_party/protobuf/proto_library.gni")
+
+proto_library("proto") {
+ sources = [
+ "cast_channel.proto",
+ ]
+}
diff --git a/cast/sender/channel/proto/cast_channel.proto b/cast/sender/channel/proto/cast_channel.proto
new file mode 100644
index 00000000..57c7b3f3
--- /dev/null
+++ b/cast/sender/channel/proto/cast_channel.proto
@@ -0,0 +1,99 @@
+// 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.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package cast.channel;
+
+message CastMessage {
+ // Always pass a version of the protocol for future compatibility
+ // requirements.
+ enum ProtocolVersion { CASTV2_1_0 = 0; }
+ required ProtocolVersion protocol_version = 1;
+
+ // source and destination ids identify the origin and destination of the
+ // message. They are used to route messages between endpoints that share a
+ // device-to-device channel.
+ //
+ // For messages between applications:
+ // - The sender application id is a unique identifier generated on behalf of
+ // the sender application.
+ // - The receiver id is always the the session id for the application.
+ //
+ // For messages to or from the sender or receiver platform, the special ids
+ // 'sender-0' and 'receiver-0' can be used.
+ //
+ // For messages intended for all endpoints using a given channel, the
+ // wildcard destination_id '*' can be used.
+ required string source_id = 2;
+ required string destination_id = 3;
+
+ // This is the core multiplexing key. All messages are sent on a namespace
+ // and endpoints sharing a channel listen on one or more namespaces. The
+ // namespace defines the protocol and semantics of the message.
+ required string namespace = 4;
+
+ // Encoding and payload info follows.
+
+ // What type of data do we have in this message.
+ enum PayloadType {
+ STRING = 0;
+ BINARY = 1;
+ }
+ required PayloadType payload_type = 5;
+
+ // Depending on payload_type, exactly one of the following optional fields
+ // will always be set.
+ optional string payload_utf8 = 6;
+ optional bytes payload_binary = 7;
+}
+
+enum SignatureAlgorithm {
+ UNSPECIFIED = 0;
+ RSASSA_PKCS1v15 = 1;
+ RSASSA_PSS = 2;
+}
+
+enum HashAlgorithm {
+ SHA1 = 0;
+ SHA256 = 1;
+}
+
+// Messages for authentication protocol between a sender and a receiver.
+message AuthChallenge {
+ optional SignatureAlgorithm signature_algorithm = 1
+ [default = RSASSA_PKCS1v15];
+ optional bytes sender_nonce = 2;
+ optional HashAlgorithm hash_algorithm = 3 [default = SHA1];
+}
+
+message AuthResponse {
+ required bytes signature = 1;
+ required bytes client_auth_certificate = 2;
+ repeated bytes intermediate_certificate = 3;
+ optional SignatureAlgorithm signature_algorithm = 4
+ [default = RSASSA_PKCS1v15];
+ optional bytes sender_nonce = 5;
+ optional HashAlgorithm hash_algorithm = 6 [default = SHA1];
+ optional bytes crl = 7;
+}
+
+message AuthError {
+ enum ErrorType {
+ INTERNAL_ERROR = 0;
+ NO_TLS = 1; // The underlying connection is not TLS
+ SIGNATURE_ALGORITHM_UNAVAILABLE = 2;
+ }
+ required ErrorType error_type = 1;
+}
+
+message DeviceAuthMessage {
+ // Request fields
+ optional AuthChallenge challenge = 1;
+ // Response fields
+ optional AuthResponse response = 2;
+ optional AuthError error = 3;
+}
diff --git a/platform/base/error.cc b/platform/base/error.cc
index 742240a3..8cb5d63d 100644
--- a/platform/base/error.cc
+++ b/platform/base/error.cc
@@ -158,6 +158,38 @@ std::ostream& operator<<(std::ostream& os, const Error::Code& code) {
return os << "Failure: OperationInvalid";
case Error::Code::kOperationCancelled:
return os << "Failure: OperationCancelled";
+ case Error::Code::kCastV2PeerCertEmpty:
+ return os << "Failure: kCastV2PeerCertEmpty";
+ case Error::Code::kCastV2WrongPayloadType:
+ return os << "Failure: kCastV2WrongPayloadType";
+ case Error::Code::kCastV2NoPayload:
+ return os << "Failure: kCastV2NoPayload";
+ case Error::Code::kCastV2PayloadParsingFailed:
+ return os << "Failure: kCastV2PayloadParsingFailed";
+ case Error::Code::kCastV2MessageError:
+ return os << "Failure: CastV2kMessageError";
+ case Error::Code::kCastV2NoResponse:
+ return os << "Failure: kCastV2NoResponse";
+ case Error::Code::kCastV2FingerprintNotFound:
+ return os << "Failure: kCastV2FingerprintNotFound";
+ case Error::Code::kCastV2CertNotSignedByTrustedCa:
+ return os << "Failure: kCastV2CertNotSignedByTrustedCa";
+ case Error::Code::kCastV2CannotExtractPublicKey:
+ return os << "Failure: kCastV2CannotExtractPublicKey";
+ case Error::Code::kCastV2SignedBlobsMismatch:
+ return os << "Failure: kCastV2SignedBlobsMismatch";
+ case Error::Code::kCastV2TlsCertValidityPeriodTooLong:
+ return os << "Failure: kCastV2TlsCertValidityPeriodTooLong";
+ case Error::Code::kCastV2TlsCertValidStartDateInFuture:
+ return os << "Failure: kCastV2TlsCertValidStartDateInFuture";
+ case Error::Code::kCastV2TlsCertExpired:
+ return os << "Failure: kCastV2TlsCertExpired";
+ case Error::Code::kCastV2SenderNonceMismatch:
+ return os << "Failure: kCastV2SenderNonceMismatch";
+ case Error::Code::kCastV2DigestUnsupported:
+ return os << "Failure: kCastV2DigestUnsupported";
+ case Error::Code::kCastV2SignatureEmpty:
+ return os << "Failure: kCastV2SignatureEmpty";
}
// Unused 'return' to get around failure on GCC.
diff --git a/platform/base/error.h b/platform/base/error.h
index f16da3a2..cfba0348 100644
--- a/platform/base/error.h
+++ b/platform/base/error.h
@@ -108,6 +108,24 @@ class Error {
// The pathlen constraint of the root certificate was exceeded.
kErrCertsPathlen,
+ // Cast authentication errors.
+ kCastV2PeerCertEmpty,
+ kCastV2WrongPayloadType,
+ kCastV2NoPayload,
+ kCastV2PayloadParsingFailed,
+ kCastV2MessageError,
+ kCastV2NoResponse,
+ kCastV2FingerprintNotFound,
+ kCastV2CertNotSignedByTrustedCa,
+ kCastV2CannotExtractPublicKey,
+ kCastV2SignedBlobsMismatch,
+ kCastV2TlsCertValidityPeriodTooLong,
+ kCastV2TlsCertValidStartDateInFuture,
+ kCastV2TlsCertExpired,
+ kCastV2SenderNonceMismatch,
+ kCastV2DigestUnsupported,
+ kCastV2SignatureEmpty,
+
// Generic errors.
kUnknownError,
kNotImplemented,
diff --git a/test/data/cast/common/certificate/certificates/test_tls_cert.pem b/test/data/cast/common/certificate/certificates/test_tls_cert.pem
new file mode 100644
index 00000000..9c8e9f3d
--- /dev/null
+++ b/test/data/cast/common/certificate/certificates/test_tls_cert.pem
@@ -0,0 +1,68 @@
+$ openssl x509 -text -noout < [CERTIFICATE]
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 103143085 (0x625d6ad)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: CN=97429b8b-af77-422a-2a02-ebafd920c3a8
+ Validity
+ Not Before: Mar 1 05:57:57 2017 GMT
+ Not After : Mar 3 05:57:57 2017 GMT
+ Subject: CN=97429b8b-af77-422a-2a02-ebafd920c3a8
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:bc:8c:0a:e9:63:c2:4b:26:74:19:5f:9b:33:00:
+ b0:de:6b:8e:af:a0:37:e8:aa:cc:9b:0e:d1:b6:e2:
+ 82:ac:eb:fe:2a:6a:2b:8d:cc:05:18:aa:5c:f4:b7:
+ 88:25:77:5d:80:6e:14:10:e9:e5:99:3f:4d:36:f2:
+ b5:44:6e:e3:94:fb:72:c1:48:53:09:f3:cd:70:85:
+ 6d:b6:b9:5e:34:60:fe:e2:b5:df:d5:a6:78:c8:52:
+ d6:62:07:f6:c1:6a:0d:f0:80:bb:6e:b3:82:82:1d:
+ 66:64:7c:85:7b:bf:aa:65:fb:18:8d:c0:2e:92:56:
+ 4d:50:ac:2b:39:a3:ce:5d:e5:29:32:76:0a:71:71:
+ a9:6f:68:4b:b5:0a:f5:5c:38:50:11:4c:35:01:12:
+ a7:2f:99:a7:a4:4f:35:04:18:7e:72:03:3f:30:26:
+ d0:ff:89:e5:ae:e8:21:c8:3b:b9:ca:b2:81:e7:56:
+ c6:06:09:16:5f:47:da:54:18:ad:4b:0f:9a:48:e9:
+ 22:1e:44:dc:00:78:61:2e:ef:ad:15:75:3c:d1:32:
+ 16:8d:fc:c4:e7:a9:43:56:21:ce:fd:d8:9b:73:b7:
+ 54:04:9e:9c:23:b7:07:0b:99:35:82:72:0f:dc:52:
+ bb:65:e0:34:04:63:4e:2e:85:88:d4:0a:b2:05:c2:
+ a4:7d
+ Exponent: 65537 (0x10001)
+ Signature Algorithm: sha256WithRSAEncryption
+ 06:ac:25:72:16:9c:39:86:f2:2e:27:d7:9f:3d:80:ae:bd:3e:
+ 8a:63:b7:c4:ba:d2:32:c3:15:40:38:dd:98:bd:27:a6:53:dd:
+ 66:29:3a:ca:50:df:f3:88:68:c1:bb:4a:41:08:b9:d7:c5:98:
+ 10:66:f0:6f:c5:50:f1:69:c5:37:63:ee:b9:db:b4:26:da:5e:
+ 43:f5:a5:66:8a:bf:5f:29:e2:dd:0a:d5:c0:51:7f:cb:07:f1:
+ 70:99:94:cd:77:cf:85:f2:ae:07:92:d1:f7:2d:e6:f3:2c:85:
+ e8:95:29:2f:b1:44:e2:8d:36:e7:0c:82:08:2c:d8:55:ea:a2:
+ 1b:34:9d:37:ed:68:9c:51:c4:3a:f9:42:b0:bd:cb:4f:9d:79:
+ 37:a5:38:ce:80:55:9a:5f:fc:d5:9a:3b:c2:4f:3b:38:37:cd:
+ aa:0f:96:3b:ea:74:b0:9c:d3:f2:ca:a0:90:61:3e:25:84:82:
+ aa:18:78:a0:78:d8:85:f0:45:1d:cf:fc:3b:f6:dd:fe:3b:ef:
+ 3c:be:59:9d:41:23:4d:2e:b1:64:57:8c:9f:68:c3:21:97:bc:
+ 40:14:85:02:e1:02:17:be:34:5c:e4:d5:5f:ca:4e:7a:30:fb:
+ 1e:99:b0:36:18:05:ad:f2:5a:6f:7c:7e:dc:49:43:20:63:da:
+ a8:84:2b:ed
+-----BEGIN CERTIFICATE-----
+MIIC2jCCAcKgAwIBAgIEBiXWrTANBgkqhkiG9w0BAQsFADAvMS0wKwYDVQQDDCQ5
+NzQyOWI4Yi1hZjc3LTQyMmEtMmEwMi1lYmFmZDkyMGMzYTgwHhcNMTcwMzAxMDU1
+NzU3WhcNMTcwMzAzMDU1NzU3WjAvMS0wKwYDVQQDDCQ5NzQyOWI4Yi1hZjc3LTQy
+MmEtMmEwMi1lYmFmZDkyMGMzYTgwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC8jArpY8JLJnQZX5szALDea46voDfoqsybDtG24oKs6/4qaiuNzAUYqlz0
+t4gld12AbhQQ6eWZP0028rVEbuOU+3LBSFMJ881whW22uV40YP7itd/VpnjIUtZi
+B/bBag3wgLtus4KCHWZkfIV7v6pl+xiNwC6SVk1QrCs5o85d5SkydgpxcalvaEu1
+CvVcOFARTDUBEqcvmaekTzUEGH5yAz8wJtD/ieWu6CHIO7nKsoHnVsYGCRZfR9pU
+GK1LD5pI6SIeRNwAeGEu760VdTzRMhaN/MTnqUNWIc792Jtzt1QEnpwjtwcLmTWC
+cg/cUrtl4DQEY04uhYjUCrIFwqR9AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAAas
+JXIWnDmG8i4n1589gK69Popjt8S60jLDFUA43Zi9J6ZT3WYpOspQ3/OIaMG7SkEI
+udfFmBBm8G/FUPFpxTdj7rnbtCbaXkP1pWaKv18p4t0K1cBRf8sH8XCZlM13z4Xy
+rgeS0fct5vMsheiVKS+xROKNNucMgggs2FXqohs0nTftaJxRxDr5QrC9y0+deTel
+OM6AVZpf/NWaO8JPOzg3zaoPljvqdLCc0/LKoJBhPiWEgqoYeKB42IXwRR3P/Dv2
+3f477zy+WZ1BI00usWRXjJ9owyGXvEAUhQLhAhe+NFzk1V/KTnow+x6ZsDYYBa3y
+Wm98ftxJQyBj2qiEK+0=
+-----END CERTIFICATE-----