// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. #include "certificate_provisioning.h" #include #include #include #include "crypto_session.h" #include "license_protocol.pb.h" #include "metrics_collections.h" #include "string_conversions.h" #include "string_format.h" #include "test_base.h" #include "wv_cdm_types.h" namespace { using video_widevine::DrmCertificate; using video_widevine::ProvisioningResponse; using video_widevine::SignedDrmCertificate; using video_widevine::SignedProvisioningMessage; const std::string kSignedDeviceCertificate = wvutil::a2bs_hex( "0A3D0802121B7769646576696E655F746573745F73657269616C5F6E756D62657218C09A0C" "28D2093A11746573742E7769646576696E652E636F6D6080B51812097369676E617475726" "5"); const std::string kSignedDeviceCertificateCreationTimeUnlimited = wvutil::a2bs_hex( "0A370802121B7769646576696E655F746573745F73657269616C5F6E756D6265721800" "28D2093A11746573742E7769646576696E652E636F6D12097369676E6174757265"); const std::string kSignedDeviceCertificateExpirationTimeInvalid = wvutil::a2bs_hex( "0A390802121B7769646576696E655F746573745F73657269616C5F6E756D62657218C0" "9A0C28D2093A11746573742E7769646576696E652E636F6D12097369676E617475726" "5"); const std::string kSignedDeviceCertificateExpirationTimeUnlimited = wvutil::a2bs_hex( "0A3B0802121B7769646576696E655F746573745F73657269616C5F6E756D62657218C0" "9A0C28D2093A11746573742E7769646576696E652E636F6D600012097369676E617475" "7265"); const std::string kSignedDeviceCertificateInvalid = wvutil::a2bs_hex( "76340802121B7769646576696E655F746573745F73657269616C5F6E756D62657228D2093A" "11746573742E7769646576696E652E636F6D12097369676E6174757265"); const std::string kSignedDeviceCertificateInvalidCertificateType = wvutil::a2bs_hex( "0A350801121B7769646576696E655F746573745F73657269616C5F6E756D62657228D2" "093A11746573742E7769646576696E652E636F6D12097369676E6174757265"); const std::string kSignedDeviceCertificateNoDrmCertificate = wvutil::a2bs_hex("12097369676E6174757265"); const std::string kSignedDeviceCertificateTimesInvalid = wvutil::a2bs_hex( "0A350802121B7769646576696E655F746573745F73657269616C5F6E756D62657228D2093A" "11746573742E7769646576696E652E636F6D12097369676E6174757265"); const int64_t kCreationTime = 200000; const int64_t kExpirationTime = 400000; const std::string kSerialNumber = "widevine_test_serial_number"; const uint32_t kSystemId = 1234; const uint32_t kNonce = 0x49e81305; const std::string kNonceString = "\x49\xe8\x13\x05"; const std::string kEmptyString; const std::string kFakeCoreMessage = wvutil::a2bs_hex("DEADBEEF"); const std::string kFakeProvisioningRequestSignature = "a very real provisioning request signature"; const std::string kFakeBuildInfo = "Mock Crypto Session - License Test"; const std::string kFakeAuthority = "a fake authority"; const std::string kOrigin = "an origin for device id"; const std::string kSpoid = "a spoid for identifying a device"; const std::string kFakeCertificateToken = "a testing certificate token"; const std::string kFakePublicKey = "a very real public key for a certificate"; const std::string kFakeSignature = "a very real certificate signature"; const std::string kWrappedPrivateKey = "a wrapped private key"; bool MakeSignedDrmCertificate(const std::string& public_key, const std::string& serial_number, uint32_t system_id, const std::string& signature, std::string* certificate) { DrmCertificate drm_certificate; drm_certificate.set_public_key(public_key); drm_certificate.set_serial_number(serial_number); drm_certificate.set_system_id(system_id); drm_certificate.set_type(DrmCertificate::DEVICE); drm_certificate.set_creation_time_seconds(kCreationTime); std::string serialized_drm_certificate; drm_certificate.SerializeToString(&serialized_drm_certificate); SignedDrmCertificate signed_drm_certificate; signed_drm_certificate.set_drm_certificate(serialized_drm_certificate); signed_drm_certificate.set_signature(signature); return signed_drm_certificate.SerializeToString(certificate); } bool MakeProvisioningResponseJson( const std::string& certificate, const std::string& nonce, ProvisioningResponse::ProvisioningStatus status, const std::string& signature, const std::string& core_message, std::string* response) { ProvisioningResponse provisioning_response; provisioning_response.set_device_certificate(certificate); provisioning_response.set_nonce(nonce); provisioning_response.set_status(status); std::string provisioning_response_string; if (!provisioning_response.SerializeToString(&provisioning_response_string)) { return false; } SignedProvisioningMessage signed_response; signed_response.set_message(provisioning_response_string); signed_response.set_signature(signature); signed_response.set_oemcrypto_core_message(core_message); signed_response.set_protocol_version(SignedProvisioningMessage::VERSION_1_1); signed_response.set_provisioning_type( SignedProvisioningMessage::DRM_REPROVISIONING); std::string signed_response_string; if (!signed_response.SerializeToString(&signed_response_string)) { return false; } std::string response_base64 = wvutil::Base64SafeEncode(signed_response_string); return wvutil::FormatString(response, "{ \"signedResponse\": \"%s\" }", response_base64.c_str()); } } // unnamed namespace namespace wvcdm { using ::testing::_; using ::testing::ByMove; using ::testing::DoAll; using ::testing::HasSubstr; using ::testing::NiceMock; using ::testing::NotNull; using ::testing::Return; using ::testing::ReturnArg; using ::testing::SaveArg; using ::testing::SetArgPointee; using ::testing::SetArgReferee; using ::testing::StrEq; using wvutil::File; class MockFile : public File { public: MockFile() {} ~MockFile() override {} MOCK_METHOD(ssize_t, Read, (char*, size_t), (override)); MOCK_METHOD(ssize_t, Write, (const char*, size_t), (override)); }; class MockFileSystem : public wvutil::FileSystem { public: MockFileSystem() {} ~MockFileSystem() override {} MOCK_METHOD(std::unique_ptr, Open, (const std::string&, int), (override)); }; class MockCryptoSession : public TestCryptoSession { public: MockCryptoSession(metrics::CryptoMetrics* metrics) : TestCryptoSession(metrics) {} MOCK_METHOD(CdmResponseType, Open, (wvcdm::RequestedSecurityLevel), (override)); // Usage Table Header. MOCK_METHOD(CdmResponseType, CreateUsageTableHeader, (wvcdm::RequestedSecurityLevel, UsageTableHeader*), (override)); MOCK_METHOD(CdmResponseType, LoadUsageTableHeader, (wvcdm::RequestedSecurityLevel, const UsageTableHeader&), (override)); MOCK_METHOD(CdmResponseType, ShrinkUsageTableHeader, (wvcdm::RequestedSecurityLevel, uint32_t, UsageTableHeader*), (override)); // Usage Entry. MOCK_METHOD(CdmResponseType, CreateUsageEntry, (UsageEntryIndex*), (override)); MOCK_METHOD(CdmResponseType, LoadUsageEntry, (UsageEntryIndex, const UsageEntry&), (override)); MOCK_METHOD(CdmResponseType, UpdateUsageEntry, (UsageTableHeader*, UsageEntry*), (override)); MOCK_METHOD(CdmResponseType, MoveUsageEntry, (UsageEntryIndex), (override)); MOCK_METHOD(bool, IsOpen, (), (override)); MOCK_METHOD(CdmClientTokenType, GetPreProvisionTokenType, (), (override)); MOCK_METHOD(CdmResponseType, GetProvisioningToken, (std::string*, std::string*), (override)); MOCK_METHOD(CdmResponseType, GenerateNonce, (uint32_t*), (override)); MOCK_METHOD(CdmResponseType, PrepareAndSignProvisioningRequest, (const std::string&, std::string*, std::string*, bool&, OEMCrypto_SignatureHashAlgorithm&), (override)); MOCK_METHOD(bool, GetSupportedCertificateTypes, (SupportedCertificateTypes*), (override)); MOCK_METHOD(bool, GetApiVersion, (uint32_t*), (override)); MOCK_METHOD(bool, GetResourceRatingTier, (uint32_t*), (override)); MOCK_METHOD(bool, GetBuildInformation, (std::string*), (override)); MOCK_METHOD(CdmSecurityLevel, GetSecurityLevel, (), (override)); MOCK_METHOD(CdmResponseType, LoadProvisioning, (const std::string&, const std::string&, const std::string&, const std::string&, std::string*), (override)); }; class TestStubCryptoSessionFactory : public CryptoSessionFactory { CryptoSession* MakeCryptoSession( metrics::CryptoMetrics* crypto_metrics) override { return new NiceMock(crypto_metrics); } }; class CertificateProvisioningTest : public WvCdmTestBase, public testing::WithParamInterface { public: void SetUp() override { WvCdmTestBase::SetUp(); CryptoSession::SetCryptoSessionFactory(new TestStubCryptoSessionFactory()); metrics_.reset(new metrics::CryptoMetrics()); certificate_provisioning_.reset( new CertificateProvisioning(metrics_.get())); GetCryptoSession()->pre_provision_token_type_ = GetParam(); } void TearDown() override {} protected: MockCryptoSession* GetCryptoSession() { return static_cast( certificate_provisioning_->crypto_session_.get()); } std::unique_ptr metrics_; std::unique_ptr certificate_provisioning_; }; void MockValidCryptoSession(MockCryptoSession* crypto_session, CdmClientTokenType token_type) { const uint32_t crypto_session_api_version = 18; const uint32_t resource_rating_tier = RESOURCE_RATING_TIER_LOW; ON_CALL(*crypto_session, IsOpen()).WillByDefault(Return(true)); ON_CALL(*crypto_session, Open(_)) .WillByDefault(Return(CdmResponseType(NO_ERROR))); ON_CALL(*crypto_session, GenerateNonce(NotNull())) .WillByDefault( DoAll(SetArgPointee<0>(kNonce), Return(CdmResponseType(NO_ERROR)))); ON_CALL(*crypto_session, GetApiVersion(NotNull())) .WillByDefault( DoAll(SetArgPointee<0>(crypto_session_api_version), Return(true))); ON_CALL(*crypto_session, GetResourceRatingTier(NotNull())) .WillByDefault( DoAll(SetArgPointee<0>(resource_rating_tier), Return(true))); ON_CALL(*crypto_session, GetBuildInformation(NotNull())) .WillByDefault(DoAll(SetArgPointee<0>(kFakeBuildInfo), Return(true))); ON_CALL(*crypto_session, GetSupportedCertificateTypes(NotNull())) .WillByDefault(Return(false)); ON_CALL(*crypto_session, GetPreProvisionTokenType()) .WillByDefault(Return(token_type)); } // Tests ExtractDeviceInfo failure scenarios // * invalid output parmeters // * invalid signed drm device certificate // * signed drm device certificate contains no drm certificate // * drm certificate has an invalid certificate type TEST_P(CertificateProvisioningTest, ExtractDeviceInfo_InvalidInput) { std::string serial_number; uint32_t system_id; EXPECT_FALSE(certificate_provisioning_->ExtractDeviceInfo( kSignedDeviceCertificate, nullptr, nullptr, nullptr, nullptr)); int64_t creation_time_seconds, expiration_time_seconds; EXPECT_FALSE(certificate_provisioning_->ExtractDeviceInfo( kSignedDeviceCertificateInvalid, &serial_number, &system_id, &creation_time_seconds, &expiration_time_seconds)); EXPECT_FALSE(certificate_provisioning_->ExtractDeviceInfo( kSignedDeviceCertificateNoDrmCertificate, &serial_number, &system_id, &creation_time_seconds, &expiration_time_seconds)); EXPECT_FALSE(certificate_provisioning_->ExtractDeviceInfo( kSignedDeviceCertificateInvalidCertificateType, &serial_number, &system_id, &creation_time_seconds, &expiration_time_seconds)); } // Tests ExtractDeviceInfo success scenarios // * able to extract both |serial_number| and |system_id| // * able to extract only |serial_number| // * able to extract only |system_id| TEST_P(CertificateProvisioningTest, ExtractDeviceInfo) { std::string serial_number; uint32_t system_id; int64_t creation_time_seconds, expiration_time_seconds; EXPECT_TRUE(certificate_provisioning_->ExtractDeviceInfo( kSignedDeviceCertificateTimesInvalid, &serial_number, &system_id, &creation_time_seconds, &expiration_time_seconds)); EXPECT_EQ(kSerialNumber, serial_number); EXPECT_EQ(kSystemId, system_id); EXPECT_EQ(INVALID_TIME, creation_time_seconds); EXPECT_EQ(INVALID_TIME, expiration_time_seconds); EXPECT_TRUE(certificate_provisioning_->ExtractDeviceInfo( kSignedDeviceCertificateTimesInvalid, nullptr, &system_id, nullptr, nullptr)); EXPECT_EQ(kSystemId, system_id); EXPECT_TRUE(certificate_provisioning_->ExtractDeviceInfo( kSignedDeviceCertificateTimesInvalid, &serial_number, nullptr, nullptr, nullptr)); EXPECT_EQ(kSerialNumber, serial_number); EXPECT_TRUE(certificate_provisioning_->ExtractDeviceInfo( kSignedDeviceCertificateCreationTimeUnlimited, &serial_number, &system_id, &creation_time_seconds, &expiration_time_seconds)); EXPECT_EQ(kSerialNumber, serial_number); EXPECT_EQ(kSystemId, system_id); EXPECT_EQ(UNLIMITED_DURATION, creation_time_seconds); EXPECT_EQ(INVALID_TIME, expiration_time_seconds); EXPECT_TRUE(certificate_provisioning_->ExtractDeviceInfo( kSignedDeviceCertificateExpirationTimeInvalid, &serial_number, &system_id, &creation_time_seconds, &expiration_time_seconds)); EXPECT_EQ(kSerialNumber, serial_number); EXPECT_EQ(kSystemId, system_id); EXPECT_EQ(kCreationTime, creation_time_seconds); EXPECT_EQ(INVALID_TIME, expiration_time_seconds); EXPECT_TRUE(certificate_provisioning_->ExtractDeviceInfo( kSignedDeviceCertificateExpirationTimeUnlimited, &serial_number, &system_id, &creation_time_seconds, &expiration_time_seconds)); EXPECT_EQ(kSerialNumber, serial_number); EXPECT_EQ(kSystemId, system_id); EXPECT_EQ(kCreationTime, creation_time_seconds); EXPECT_EQ(UNLIMITED_DURATION, expiration_time_seconds); EXPECT_TRUE(certificate_provisioning_->ExtractDeviceInfo( kSignedDeviceCertificate, &serial_number, &system_id, &creation_time_seconds, &expiration_time_seconds)); EXPECT_EQ(kSerialNumber, serial_number); EXPECT_EQ(kSystemId, system_id); EXPECT_EQ(kCreationTime, creation_time_seconds); EXPECT_EQ(kExpirationTime, expiration_time_seconds); EXPECT_TRUE(certificate_provisioning_->ExtractDeviceInfo( kSignedDeviceCertificate, &serial_number, &system_id, nullptr, &expiration_time_seconds)); EXPECT_EQ(kSerialNumber, serial_number); EXPECT_EQ(kSystemId, system_id); EXPECT_EQ(kExpirationTime, expiration_time_seconds); EXPECT_TRUE(certificate_provisioning_->ExtractDeviceInfo( kSignedDeviceCertificateExpirationTimeUnlimited, &serial_number, &system_id, &creation_time_seconds, nullptr)); EXPECT_EQ(kSerialNumber, serial_number); EXPECT_EQ(kSystemId, system_id); EXPECT_EQ(kCreationTime, creation_time_seconds); EXPECT_EQ(kExpirationTime, expiration_time_seconds); } TEST_P(CertificateProvisioningTest, ProvisioningRequestIsValid) { certificate_provisioning_->Init(""); MockFileSystem file_system; MockCryptoSession* crypto_session = GetCryptoSession(); MockValidCryptoSession(crypto_session, GetParam()); ON_CALL(*crypto_session, GetProvisioningToken(NotNull(), _)) .WillByDefault(DoAll(SetArgPointee<0>(kFakeCertificateToken), Return(CdmResponseType(NO_ERROR)))); EXPECT_CALL(*crypto_session, PrepareAndSignProvisioningRequest(_, NotNull(), NotNull(), _, _)) .WillOnce(DoAll(SetArgPointee<1>(kFakeCoreMessage), SetArgPointee<2>(kFakeProvisioningRequestSignature), SetArgReferee<3>(false), Return(CdmResponseType(NO_ERROR)))); CdmProvisioningRequest request; std::string default_url; EXPECT_EQ(NO_ERROR, certificate_provisioning_->GetProvisioningRequest( &file_system, kLevelDefault, kCertificateWidevine, kFakeAuthority, kOrigin, kSpoid, &request, &default_url)); } TEST_P(CertificateProvisioningTest, ProvisioningRequestFailsEmptySignature) { certificate_provisioning_->Init(""); MockFileSystem file_system; MockCryptoSession* crypto_session = GetCryptoSession(); MockValidCryptoSession(crypto_session, GetParam()); ON_CALL(*crypto_session, GetProvisioningToken(NotNull(), _)) .WillByDefault(DoAll(SetArgPointee<0>(kFakeCertificateToken), Return(CdmResponseType(NO_ERROR)))); EXPECT_CALL(*crypto_session, PrepareAndSignProvisioningRequest(_, NotNull(), NotNull(), _, _)) .WillOnce(DoAll(SetArgPointee<1>(kFakeCoreMessage), SetArgPointee<2>(kEmptyString), SetArgReferee<3>(false), Return(CdmResponseType(NO_ERROR)))); CdmProvisioningRequest request; std::string default_url; EXPECT_EQ(CERT_PROVISIONING_REQUEST_ERROR_4, certificate_provisioning_->GetProvisioningRequest( &file_system, kLevelDefault, kCertificateWidevine, kFakeAuthority, kOrigin, kSpoid, &request, &default_url)); } TEST_P(CertificateProvisioningTest, ProvisioningResponseFailsWithEmptyResponse) { certificate_provisioning_->Init(""); // Must set state if not generating request. certificate_provisioning_->SetStateForTesting( CertificateProvisioning::kDrmRequestSent); MockFileSystem file_system; std::string certificate; std::string wrapped_key; EXPECT_EQ(CERT_PROVISIONING_RESPONSE_ERROR_1, certificate_provisioning_->HandleProvisioningResponse( &file_system, /*response=*/"", &certificate, &wrapped_key)); } TEST_P(CertificateProvisioningTest, ProvisioningResponseFailsIfDeviceIsRevoked) { certificate_provisioning_->Init(""); // Must set state if not generating request. certificate_provisioning_->SetStateForTesting( CertificateProvisioning::kDrmRequestSent); MockFileSystem file_system; std::string response_certificate; std::string response; ASSERT_TRUE(MakeSignedDrmCertificate(kFakePublicKey, kSerialNumber, kSystemId, kFakeSignature, &response_certificate)); ASSERT_TRUE(MakeProvisioningResponseJson( response_certificate, kNonceString, ProvisioningResponse::REVOKED_DEVICE_CREDENTIALS, kFakeSignature, kFakeCoreMessage, &response)); std::string certificate; std::string wrapped_key; EXPECT_EQ(DEVICE_REVOKED, certificate_provisioning_->HandleProvisioningResponse( &file_system, response, &certificate, &wrapped_key)); } TEST_P(CertificateProvisioningTest, ProvisioningResponseSuccess) { certificate_provisioning_->Init(""); // Must set state if not generating request. certificate_provisioning_->SetStateForTesting( CertificateProvisioning::kDrmRequestSent); std::string expected_certificate; std::string response; ASSERT_TRUE(MakeSignedDrmCertificate(kFakePublicKey, kSerialNumber, kSystemId, kFakeSignature, &expected_certificate)); ASSERT_TRUE(MakeProvisioningResponseJson( expected_certificate, kNonceString, ProvisioningResponse::NO_ERROR, kFakeSignature, kFakeCoreMessage, &response)); MockCryptoSession* crypto_session = GetCryptoSession(); ON_CALL(*crypto_session, IsOpen()).WillByDefault(Return(true)); ON_CALL(*crypto_session, GetSecurityLevel()) .WillByDefault(Return(kSecurityLevelL3)); EXPECT_CALL(*crypto_session, LoadProvisioning) .Times(1) .WillOnce(DoAll(SetArgPointee<4>(kWrappedPrivateKey), Return(CdmResponseType(NO_ERROR)))); MockFile* file = new MockFile(); std::string stored_certificate; EXPECT_CALL(*file, Write(_, _)) .Times(1) .WillOnce(DoAll(SaveArg<0>(&stored_certificate), ReturnArg<1>())); MockFileSystem file_system; EXPECT_CALL(file_system, Open(HasSubstr(wvutil::kLegacyCertificateFileName), _)) .Times(1) .WillOnce(Return(ByMove(std::unique_ptr(file)))); std::string certificate; std::string wrapped_key; EXPECT_EQ(NO_ERROR, certificate_provisioning_->HandleProvisioningResponse( &file_system, response, &certificate, &wrapped_key)); EXPECT_NE(std::string::npos, stored_certificate.find(expected_certificate)); } INSTANTIATE_TEST_SUITE_P( CertificateProvisioningTests, CertificateProvisioningTest, testing::Values(kClientTokenKeybox, kClientTokenOemCert, kClientTokenDrmCertificateReprovisioning), [](const testing::TestParamInfo& param_type) { return CdmClientTokenTypeToString(param_type.param); }); } // namespace wvcdm