diff --git a/libwvdrmengine/cdm/core/include/certificate_provisioning.h b/libwvdrmengine/cdm/core/include/certificate_provisioning.h index 34918fb4..5ca9e317 100644 --- a/libwvdrmengine/cdm/core/include/certificate_provisioning.h +++ b/libwvdrmengine/cdm/core/include/certificate_provisioning.h @@ -73,6 +73,9 @@ class CertificateProvisioning { static void GetProvisioningServerUrl(std::string* default_url); private: +#if defined(UNIT_TEST) + friend class CertificateProvisioningTest; +#endif CdmResponseType GetProvisioningRequestInternal( wvutil::FileSystem* file_system, RequestedSecurityLevel requested_security_level, diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 24660014..435ad89b 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -62,7 +62,7 @@ OEMCryptoResult WrapIfNecessary(OEMCryptoResult ret_value) { return ret_value; } } #define RETURN_IF_NOT_OPEN(ret_value) \ - if (!open_) { \ + if (!IsOpen()) { \ LOGE("Crypto session is not open"); \ return WrapIfNecessary(ret_value); \ } diff --git a/libwvdrmengine/cdm/core/test/certificate_provisioning_unittest.cpp b/libwvdrmengine/cdm/core/test/certificate_provisioning_unittest.cpp index 9f80b2be..ee96b60e 100644 --- a/libwvdrmengine/cdm/core/test/certificate_provisioning_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/certificate_provisioning_unittest.cpp @@ -10,12 +10,20 @@ #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" @@ -50,11 +58,108 @@ 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::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) @@ -78,17 +183,39 @@ class MockCryptoSession : public TestCryptoSession { 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&, + std::string*), + (override)); }; class TestStubCryptoSessionFactory : public CryptoSessionFactory { CryptoSession* MakeCryptoSession( metrics::CryptoMetrics* crypto_metrics) override { - return new MockCryptoSession(crypto_metrics); + return new NiceMock(crypto_metrics); } }; -class CertificateProvisioningTest : public WvCdmTestBase { - protected: +class CertificateProvisioningTest + : public WvCdmTestBase, + public testing::WithParamInterface { + public: void SetUp() override { WvCdmTestBase::SetUp(); CryptoSession::SetCryptoSessionFactory(new TestStubCryptoSessionFactory()); @@ -96,20 +223,52 @@ class CertificateProvisioningTest : public WvCdmTestBase { 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_F(CertificateProvisioningTest, ExtractDeviceInfo_InvalidInput) { +TEST_P(CertificateProvisioningTest, ExtractDeviceInfo_InvalidInput) { std::string serial_number; uint32_t system_id; @@ -134,7 +293,7 @@ TEST_F(CertificateProvisioningTest, ExtractDeviceInfo_InvalidInput) { // * able to extract both |serial_number| and |system_id| // * able to extract only |serial_number| // * able to extract only |system_id| -TEST_F(CertificateProvisioningTest, ExtractDeviceInfo) { +TEST_P(CertificateProvisioningTest, ExtractDeviceInfo) { std::string serial_number; uint32_t system_id; int64_t creation_time_seconds, expiration_time_seconds; @@ -205,4 +364,128 @@ TEST_F(CertificateProvisioningTest, ExtractDeviceInfo) { 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(""); + + 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(""); + + 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(""); + 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<3>(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(StrEq(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, + // TODO: b/305093063 - Add Drm Reprovisioning to Values once implemented. + testing::Values(kClientTokenKeybox, kClientTokenOemCert), + [](const testing::TestParamInfo& + param_type) { + return CdmClientTokenTypeToString(param_type.param); + }); + } // namespace wvcdm