OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "extensions/browser/api/cast_channel/cast_auth_util.h" |
| 6 |
| 7 #include <string> |
| 8 |
| 9 #include "base/macros.h" |
| 10 #include "base/test/scoped_feature_list.h" |
| 11 #include "base/time/time.h" |
| 12 #include "components/cast_certificate/cast_cert_validator.h" |
| 13 #include "components/cast_certificate/cast_cert_validator_test_helpers.h" |
| 14 #include "components/cast_certificate/cast_crl.h" |
| 15 #include "components/cast_certificate/proto/test_suite.pb.h" |
| 16 #include "extensions/common/api/cast_channel/cast_channel.pb.h" |
| 17 #include "net/cert/internal/trust_store_in_memory.h" |
| 18 #include "net/cert/x509_certificate.h" |
| 19 #include "testing/gtest/include/gtest/gtest.h" |
| 20 |
| 21 namespace extensions { |
| 22 namespace api { |
| 23 namespace cast_channel { |
| 24 namespace { |
| 25 |
| 26 class CastAuthUtilTest : public testing::Test { |
| 27 public: |
| 28 CastAuthUtilTest() {} |
| 29 ~CastAuthUtilTest() override {} |
| 30 |
| 31 void SetUp() override {} |
| 32 |
| 33 protected: |
| 34 static AuthResponse CreateAuthResponse(std::string* signed_data) { |
| 35 auto chain = cast_certificate::testing::ReadCertificateChainFromFile( |
| 36 "certificates/chromecast_gen1.pem"); |
| 37 CHECK(!chain.empty()); |
| 38 |
| 39 auto signature_data = cast_certificate::testing::ReadSignatureTestData( |
| 40 "signeddata/2ZZBG9_FA8FCA3EF91A.pem"); |
| 41 |
| 42 AuthResponse response; |
| 43 |
| 44 response.set_client_auth_certificate(chain[0]); |
| 45 for (size_t i = 1; i < chain.size(); ++i) |
| 46 response.add_intermediate_certificate(chain[i]); |
| 47 |
| 48 response.set_signature(signature_data.signature_sha1); |
| 49 *signed_data = signature_data.message; |
| 50 |
| 51 return response; |
| 52 } |
| 53 |
| 54 // Mangles a string by inverting the first byte. |
| 55 static void MangleString(std::string* str) { (*str)[0] = ~(*str)[0]; } |
| 56 }; |
| 57 |
| 58 // Note on expiration: VerifyCredentials() depends on the system clock. In |
| 59 // practice this shouldn't be a problem though since the certificate chain |
| 60 // being verified doesn't expire until 2032! |
| 61 TEST_F(CastAuthUtilTest, VerifySuccess) { |
| 62 std::string signed_data; |
| 63 AuthResponse auth_response = CreateAuthResponse(&signed_data); |
| 64 base::Time now = base::Time::Now(); |
| 65 AuthResult result = VerifyCredentialsForTest( |
| 66 auth_response, signed_data, cast_certificate::CRLPolicy::CRL_OPTIONAL, |
| 67 nullptr, nullptr, now); |
| 68 EXPECT_TRUE(result.success()); |
| 69 EXPECT_EQ(AuthResult::POLICY_NONE, result.channel_policies); |
| 70 } |
| 71 |
| 72 TEST_F(CastAuthUtilTest, VerifyBadCA) { |
| 73 std::string signed_data; |
| 74 AuthResponse auth_response = CreateAuthResponse(&signed_data); |
| 75 MangleString(auth_response.mutable_intermediate_certificate(0)); |
| 76 AuthResult result = VerifyCredentials(auth_response, signed_data); |
| 77 EXPECT_FALSE(result.success()); |
| 78 EXPECT_EQ(AuthResult::ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA, result.error_type); |
| 79 } |
| 80 |
| 81 TEST_F(CastAuthUtilTest, VerifyBadClientAuthCert) { |
| 82 std::string signed_data; |
| 83 AuthResponse auth_response = CreateAuthResponse(&signed_data); |
| 84 MangleString(auth_response.mutable_client_auth_certificate()); |
| 85 AuthResult result = VerifyCredentials(auth_response, signed_data); |
| 86 EXPECT_FALSE(result.success()); |
| 87 // TODO(eroman): Not quite right of an error. |
| 88 EXPECT_EQ(AuthResult::ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA, result.error_type); |
| 89 } |
| 90 |
| 91 TEST_F(CastAuthUtilTest, VerifyBadSignature) { |
| 92 std::string signed_data; |
| 93 AuthResponse auth_response = CreateAuthResponse(&signed_data); |
| 94 MangleString(auth_response.mutable_signature()); |
| 95 AuthResult result = VerifyCredentials(auth_response, signed_data); |
| 96 EXPECT_FALSE(result.success()); |
| 97 EXPECT_EQ(AuthResult::ERROR_SIGNED_BLOBS_MISMATCH, result.error_type); |
| 98 } |
| 99 |
| 100 TEST_F(CastAuthUtilTest, VerifyBadPeerCert) { |
| 101 std::string signed_data; |
| 102 AuthResponse auth_response = CreateAuthResponse(&signed_data); |
| 103 MangleString(&signed_data); |
| 104 AuthResult result = VerifyCredentials(auth_response, signed_data); |
| 105 EXPECT_FALSE(result.success()); |
| 106 EXPECT_EQ(AuthResult::ERROR_SIGNED_BLOBS_MISMATCH, result.error_type); |
| 107 } |
| 108 |
| 109 TEST_F(CastAuthUtilTest, VerifySenderNonceMatch) { |
| 110 base::test::ScopedFeatureList scoped_feature_list; |
| 111 scoped_feature_list.InitAndEnableFeature( |
| 112 base::Feature{"CastNonceEnforced", base::FEATURE_DISABLED_BY_DEFAULT}); |
| 113 AuthContext context = AuthContext::Create(); |
| 114 AuthResult result = context.VerifySenderNonce(context.nonce()); |
| 115 EXPECT_TRUE(result.success()); |
| 116 } |
| 117 |
| 118 TEST_F(CastAuthUtilTest, VerifySenderNonceMismatch) { |
| 119 base::test::ScopedFeatureList scoped_feature_list; |
| 120 scoped_feature_list.InitAndEnableFeature( |
| 121 base::Feature{"CastNonceEnforced", base::FEATURE_DISABLED_BY_DEFAULT}); |
| 122 AuthContext context = AuthContext::Create(); |
| 123 std::string received_nonce = "test2"; |
| 124 EXPECT_NE(received_nonce, context.nonce()); |
| 125 AuthResult result = context.VerifySenderNonce(received_nonce); |
| 126 EXPECT_FALSE(result.success()); |
| 127 EXPECT_EQ(AuthResult::ERROR_SENDER_NONCE_MISMATCH, result.error_type); |
| 128 } |
| 129 |
| 130 TEST_F(CastAuthUtilTest, VerifySenderNonceMissing) { |
| 131 base::test::ScopedFeatureList scoped_feature_list; |
| 132 scoped_feature_list.InitAndEnableFeature( |
| 133 base::Feature{"CastNonceEnforced", base::FEATURE_DISABLED_BY_DEFAULT}); |
| 134 AuthContext context = AuthContext::Create(); |
| 135 std::string received_nonce = ""; |
| 136 EXPECT_FALSE(context.nonce().empty()); |
| 137 AuthResult result = context.VerifySenderNonce(received_nonce); |
| 138 EXPECT_FALSE(result.success()); |
| 139 EXPECT_EQ(AuthResult::ERROR_SENDER_NONCE_MISMATCH, result.error_type); |
| 140 } |
| 141 |
| 142 TEST_F(CastAuthUtilTest, VerifyTLSCertificateSuccess) { |
| 143 auto tls_cert_der = cast_certificate::testing::ReadCertificateChainFromFile( |
| 144 "certificates/test_tls_cert.pem"); |
| 145 |
| 146 scoped_refptr<net::X509Certificate> tls_cert = |
| 147 net::X509Certificate::CreateFromBytes(tls_cert_der[0].data(), |
| 148 tls_cert_der[0].size()); |
| 149 std::string peer_cert_der; |
| 150 AuthResult result = |
| 151 VerifyTLSCertificate(*tls_cert, &peer_cert_der, tls_cert->valid_start()); |
| 152 EXPECT_TRUE(result.success()); |
| 153 } |
| 154 |
| 155 TEST_F(CastAuthUtilTest, VerifyTLSCertificateTooEarly) { |
| 156 auto tls_cert_der = cast_certificate::testing::ReadCertificateChainFromFile( |
| 157 "certificates/test_tls_cert.pem"); |
| 158 |
| 159 scoped_refptr<net::X509Certificate> tls_cert = |
| 160 net::X509Certificate::CreateFromBytes(tls_cert_der[0].data(), |
| 161 tls_cert_der[0].size()); |
| 162 std::string peer_cert_der; |
| 163 AuthResult result = VerifyTLSCertificate( |
| 164 *tls_cert, &peer_cert_der, |
| 165 tls_cert->valid_start() - base::TimeDelta::FromSeconds(1)); |
| 166 EXPECT_FALSE(result.success()); |
| 167 EXPECT_EQ(AuthResult::ERROR_TLS_CERT_VALID_START_DATE_IN_FUTURE, |
| 168 result.error_type); |
| 169 } |
| 170 |
| 171 TEST_F(CastAuthUtilTest, VerifyTLSCertificateTooLate) { |
| 172 auto tls_cert_der = cast_certificate::testing::ReadCertificateChainFromFile( |
| 173 "certificates/test_tls_cert.pem"); |
| 174 |
| 175 scoped_refptr<net::X509Certificate> tls_cert = |
| 176 net::X509Certificate::CreateFromBytes(tls_cert_der[0].data(), |
| 177 tls_cert_der[0].size()); |
| 178 std::string peer_cert_der; |
| 179 AuthResult result = VerifyTLSCertificate( |
| 180 *tls_cert, &peer_cert_der, |
| 181 tls_cert->valid_expiry() + base::TimeDelta::FromSeconds(2)); |
| 182 EXPECT_FALSE(result.success()); |
| 183 EXPECT_EQ(AuthResult::ERROR_TLS_CERT_EXPIRED, result.error_type); |
| 184 } |
| 185 |
| 186 // Indicates the expected result of test step's verification. |
| 187 enum TestStepResult { |
| 188 RESULT_SUCCESS, |
| 189 RESULT_FAIL, |
| 190 }; |
| 191 |
| 192 // Verifies that the certificate chain provided is not revoked according to |
| 193 // the provided Cast CRL at |verification_time|. |
| 194 // The provided CRL is verified at |verification_time|. |
| 195 // If |crl_required| is set, then a valid Cast CRL must be provided. |
| 196 // Otherwise, a missing CRL is be ignored. |
| 197 AuthResult TestVerifyRevocation( |
| 198 const std::vector<std::string>& certificate_chain, |
| 199 const std::string& crl_bundle, |
| 200 const base::Time& verification_time, |
| 201 bool crl_required, |
| 202 net::TrustStore* cast_trust_store, |
| 203 net::TrustStore* crl_trust_store) { |
| 204 AuthResponse response; |
| 205 |
| 206 if (certificate_chain.size() > 0) { |
| 207 response.set_client_auth_certificate(certificate_chain[0]); |
| 208 for (size_t i = 1; i < certificate_chain.size(); ++i) |
| 209 response.add_intermediate_certificate(certificate_chain[i]); |
| 210 } |
| 211 |
| 212 response.set_crl(crl_bundle); |
| 213 |
| 214 cast_certificate::CRLPolicy crl_policy = |
| 215 cast_certificate::CRLPolicy::CRL_REQUIRED; |
| 216 if (!crl_required && crl_bundle.empty()) |
| 217 crl_policy = cast_certificate::CRLPolicy::CRL_OPTIONAL; |
| 218 AuthResult result = |
| 219 VerifyCredentialsForTest(response, "", crl_policy, cast_trust_store, |
| 220 crl_trust_store, verification_time); |
| 221 // This test doesn't set the signature so it will just fail there. |
| 222 EXPECT_FALSE(result.success()); |
| 223 return result; |
| 224 } |
| 225 |
| 226 // Runs a single test case. |
| 227 bool RunTest(const cast_certificate::DeviceCertTest& test_case) { |
| 228 std::unique_ptr<net::TrustStore> crl_trust_store; |
| 229 std::unique_ptr<net::TrustStore> cast_trust_store; |
| 230 if (test_case.use_test_trust_anchors()) { |
| 231 crl_trust_store = cast_certificate::testing::CreateTrustStoreFromFile( |
| 232 "certificates/cast_crl_test_root_ca.pem"); |
| 233 cast_trust_store = cast_certificate::testing::CreateTrustStoreFromFile( |
| 234 "certificates/cast_test_root_ca.pem"); |
| 235 |
| 236 EXPECT_TRUE(crl_trust_store.get()); |
| 237 EXPECT_TRUE(cast_trust_store.get()); |
| 238 } |
| 239 |
| 240 std::vector<std::string> certificate_chain; |
| 241 for (auto const& cert : test_case.der_cert_path()) { |
| 242 certificate_chain.push_back(cert); |
| 243 } |
| 244 |
| 245 // CastAuthUtil verifies the CRL at the same time as the certificate. |
| 246 base::Time verification_time; |
| 247 uint64_t cert_verify_time = test_case.cert_verification_time_seconds(); |
| 248 if (cert_verify_time) { |
| 249 verification_time = cast_certificate::testing::ConvertUnixTimestampSeconds( |
| 250 cert_verify_time); |
| 251 } else { |
| 252 verification_time = cast_certificate::testing::ConvertUnixTimestampSeconds( |
| 253 test_case.crl_verification_time_seconds()); |
| 254 } |
| 255 |
| 256 std::string crl_bundle = test_case.crl_bundle(); |
| 257 AuthResult result; |
| 258 switch (test_case.expected_result()) { |
| 259 case cast_certificate::PATH_VERIFICATION_FAILED: |
| 260 result = TestVerifyRevocation( |
| 261 certificate_chain, crl_bundle, verification_time, false, |
| 262 cast_trust_store.get(), crl_trust_store.get()); |
| 263 EXPECT_EQ(result.error_type, |
| 264 AuthResult::ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA); |
| 265 return result.error_type == |
| 266 AuthResult::ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA; |
| 267 case cast_certificate::CRL_VERIFICATION_FAILED: |
| 268 // Fall-through intended. |
| 269 case cast_certificate::REVOCATION_CHECK_FAILED_WITHOUT_CRL: |
| 270 result = TestVerifyRevocation( |
| 271 certificate_chain, crl_bundle, verification_time, true, |
| 272 cast_trust_store.get(), crl_trust_store.get()); |
| 273 EXPECT_EQ(result.error_type, AuthResult::ERROR_CRL_INVALID); |
| 274 return result.error_type == AuthResult::ERROR_CRL_INVALID; |
| 275 case cast_certificate::CRL_EXPIRED_AFTER_INITIAL_VERIFICATION: |
| 276 // By-pass this test because CRL is always verified at the time the |
| 277 // certificate is verified. |
| 278 return true; |
| 279 case cast_certificate::REVOCATION_CHECK_FAILED: |
| 280 result = TestVerifyRevocation( |
| 281 certificate_chain, crl_bundle, verification_time, true, |
| 282 cast_trust_store.get(), crl_trust_store.get()); |
| 283 EXPECT_EQ(result.error_type, AuthResult::ERROR_CERT_REVOKED); |
| 284 return result.error_type == AuthResult::ERROR_CERT_REVOKED; |
| 285 case cast_certificate::SUCCESS: |
| 286 result = TestVerifyRevocation( |
| 287 certificate_chain, crl_bundle, verification_time, false, |
| 288 cast_trust_store.get(), crl_trust_store.get()); |
| 289 EXPECT_EQ(result.error_type, AuthResult::ERROR_SIGNED_BLOBS_MISMATCH); |
| 290 return result.error_type == AuthResult::ERROR_SIGNED_BLOBS_MISMATCH; |
| 291 case UNSPECIFIED: |
| 292 return false; |
| 293 } |
| 294 return false; |
| 295 } |
| 296 |
| 297 // Parses the provided test suite provided in wire-format proto. |
| 298 // Each test contains the inputs and the expected output. |
| 299 // To see the description of the test, execute the test. |
| 300 // These tests are generated by a test generator in google3. |
| 301 void RunTestSuite(const std::string& test_suite_file_name) { |
| 302 std::string testsuite_raw = |
| 303 cast_certificate::testing::ReadTestFileToString(test_suite_file_name); |
| 304 cast_certificate::DeviceCertTestSuite test_suite; |
| 305 EXPECT_TRUE(test_suite.ParseFromString(testsuite_raw)); |
| 306 uint16_t success = 0; |
| 307 uint16_t failed = 0; |
| 308 std::vector<std::string> failed_tests; |
| 309 |
| 310 for (auto const& test_case : test_suite.tests()) { |
| 311 LOG(INFO) << "[ RUN ] " << test_case.description(); |
| 312 bool result = RunTest(test_case); |
| 313 EXPECT_TRUE(result); |
| 314 if (!result) { |
| 315 LOG(INFO) << "[ FAILED ] " << test_case.description(); |
| 316 ++failed; |
| 317 failed_tests.push_back(test_case.description()); |
| 318 } else { |
| 319 LOG(INFO) << "[ PASSED ] " << test_case.description(); |
| 320 ++success; |
| 321 } |
| 322 } |
| 323 LOG(INFO) << "[ PASSED ] " << success << " test(s)."; |
| 324 if (failed) { |
| 325 LOG(INFO) << "[ FAILED ] " << failed << " test(s), listed below:"; |
| 326 for (const auto& failed_test : failed_tests) { |
| 327 LOG(INFO) << "[ FAILED ] " << failed_test; |
| 328 } |
| 329 } |
| 330 } |
| 331 |
| 332 TEST_F(CastAuthUtilTest, CRLTestSuite) { |
| 333 RunTestSuite("testsuite/testsuite1.pb"); |
| 334 } |
| 335 |
| 336 } // namespace |
| 337 } // namespace cast_channel |
| 338 } // namespace api |
| 339 } // namespace extensions |
OLD | NEW |