diff --git a/libwvdrmengine/cdm/core/include/certificate_provisioning.h b/libwvdrmengine/cdm/core/include/certificate_provisioning.h index 097a8006..05c0cad5 100644 --- a/libwvdrmengine/cdm/core/include/certificate_provisioning.h +++ b/libwvdrmengine/cdm/core/include/certificate_provisioning.h @@ -86,7 +86,8 @@ class CertificateProvisioning { std::string* default_url); CdmResponseType FillEncryptedClientId( const std::string& client_token, - video_widevine::ProvisioningRequest& provisioning_request); + video_widevine::ProvisioningRequest& provisioning_request, + const ServiceCertificate& service_certificate); CdmResponseType HandleProvisioning40Response( wvutil::FileSystem* file_system, const std::string& response_message); diff --git a/libwvdrmengine/cdm/core/include/device_files.h b/libwvdrmengine/cdm/core/include/device_files.h index d23a5d45..8b3926ef 100644 --- a/libwvdrmengine/cdm/core/include/device_files.h +++ b/libwvdrmengine/cdm/core/include/device_files.h @@ -5,6 +5,7 @@ #ifndef WVCDM_CORE_DEVICE_FILES_H_ #define WVCDM_CORE_DEVICE_FILES_H_ +#include #include #include #include @@ -369,6 +370,7 @@ class DeviceFiles { #endif static std::set reserved_license_ids_; + static std::mutex reserved_license_ids_mutex_; wvutil::FileSystem* file_system_; CdmSecurityLevel security_level_; diff --git a/libwvdrmengine/cdm/core/include/service_certificate.h b/libwvdrmengine/cdm/core/include/service_certificate.h index 7bd80b26..ced4f4c1 100644 --- a/libwvdrmengine/cdm/core/include/service_certificate.h +++ b/libwvdrmengine/cdm/core/include/service_certificate.h @@ -38,8 +38,8 @@ class ServiceCertificate { const std::string& provider_id() const { return provider_id_; } // Verify the signature for a message. - virtual CdmResponseType VerifySignedMessage(const std::string& message, - const std::string& signature); + virtual CdmResponseType VerifySignedMessage( + const std::string& message, const std::string& signature) const; // Encrypt the ClientIdentification message for a provisioning or // licensing request. Encryption is performed using the current @@ -50,7 +50,7 @@ class ServiceCertificate { virtual CdmResponseType EncryptClientId( CryptoSession* crypto_session, const video_widevine::ClientIdentification* clear_client_id, - video_widevine::EncryptedClientIdentification* encrypted_client_id); + video_widevine::EncryptedClientIdentification* encrypted_client_id) const; // Helper methods static bool GetRequest(CdmKeyMessage* request); @@ -63,7 +63,7 @@ class ServiceCertificate { // string to contain the decrypted data on return, and may not be null. // returns NO_ERROR if successful or an appropriate error code otherwise. virtual CdmResponseType EncryptRsaOaep(const std::string& plaintext, - std::string* ciphertext); + std::string* ciphertext) const; // Track whether object holds valid certificate bool has_certificate_; diff --git a/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp b/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp index 77e20747..2f2f2339 100644 --- a/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp +++ b/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp @@ -218,7 +218,8 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal( // Prepare device provisioning request. ProvisioningRequest provisioning_request; - status = FillEncryptedClientId(/*client_token=*/"", provisioning_request); + status = FillEncryptedClientId(/*client_token=*/"", provisioning_request, + *service_certificate_); if (status != NO_ERROR) return status; uint32_t nonce; @@ -339,24 +340,33 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal( } } + CdmResponseType status = NO_ERROR; if (stored_oem_cert.empty()) { // This is the first stage provisioning. default_url->assign(kProvisioningServerUrl + kProv40FirstStageServerUrlSuffix); + + // First-stage provisioning always uses the WV production service cert for + // encryption. + ServiceCertificate wv_service_cert; + status = wv_service_cert.Init(kCpProductionServiceCertificate); + if (status != NO_ERROR) return status; + + // Since |stored_oem_cert| is empty, the client identification token will be + // retrieved from OEMCrypto, which is the BCC in this case. + status = FillEncryptedClientId(stored_oem_cert, provisioning_request, + wv_service_cert); + if (status != NO_ERROR) return status; } else { // This is the second stage provisioning. default_url->assign(kProvisioningServerUrl); + // Since |stored_oem_cert| is non-empty, it will be used as the client + // identification token. + status = FillEncryptedClientId(stored_oem_cert, provisioning_request, + *service_certificate_); + if (status != NO_ERROR) return status; } - // If this is the first stage, |stored_oem_cert| remains empty. In this case, - // the client identification token will be retrieved from OEMCrypto, which is - // the BCC in this case. - // If this is the second stage, |stored_oem_cert| is non-empty and will be - // used as the client identification token. - CdmResponseType status = - FillEncryptedClientId(stored_oem_cert, provisioning_request); - if (status != NO_ERROR) return status; - std::string public_key; std::string public_key_signature; provisioning_40_wrapped_private_key_.clear(); @@ -396,8 +406,8 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal( } CdmResponseType CertificateProvisioning::FillEncryptedClientId( - const std::string& client_token, - ProvisioningRequest& provisioning_request) { + const std::string& client_token, ProvisioningRequest& provisioning_request, + const ServiceCertificate& service_certificate) { if (!crypto_session_->IsOpen()) { return UNKNOWN_ERROR; } @@ -412,13 +422,13 @@ CdmResponseType CertificateProvisioning::FillEncryptedClientId( status = id.Prepare(app_parameter, kEmptyString, &client_id); if (status != NO_ERROR) return status; - if (!service_certificate_->has_certificate()) { + if (!service_certificate.has_certificate()) { LOGE("Service certificate not staged"); return CERT_PROVISIONING_EMPTY_SERVICE_CERTIFICATE; } // Encrypt client identification - return service_certificate_->EncryptClientId( + return service_certificate.EncryptClientId( crypto_session_.get(), &client_id, provisioning_request.mutable_encrypted_client_id()); } diff --git a/libwvdrmengine/cdm/core/src/device_files.cpp b/libwvdrmengine/cdm/core/src/device_files.cpp index 2602171b..e18fac0a 100644 --- a/libwvdrmengine/cdm/core/src/device_files.cpp +++ b/libwvdrmengine/cdm/core/src/device_files.cpp @@ -87,6 +87,8 @@ using video_widevine_client::sdk:: } namespace wvcdm { +using UniqueLock = std::unique_lock; + namespace { const char kEmptyFileName[] = ""; const char kFalse[] = "false"; @@ -364,6 +366,7 @@ const char* DeviceFiles::ResponseTypeToString(ResponseType type) { // static std::set DeviceFiles::reserved_license_ids_; +std::mutex DeviceFiles::reserved_license_ids_mutex_; DeviceFiles::DeviceFiles(wvutil::FileSystem* file_system) : file_system_(file_system), @@ -847,6 +850,7 @@ bool DeviceFiles::StoreLicense(const CdmLicenseData& license_data, std::string serialized_file; file.SerializeToString(&serialized_file); + UniqueLock lock(reserved_license_ids_mutex_); reserved_license_ids_.erase(license_data.key_set_id); *result = StoreFileWithHash(license_data.key_set_id + kLicenseFileNameExt, serialized_file); @@ -984,18 +988,21 @@ bool DeviceFiles::DeleteAllFiles() { bool DeviceFiles::LicenseExists(const std::string& key_set_id) { RETURN_FALSE_IF_UNINITIALIZED(); + UniqueLock lock(reserved_license_ids_mutex_); return reserved_license_ids_.count(key_set_id) || FileExists(key_set_id + kLicenseFileNameExt); } bool DeviceFiles::ReserveLicenseId(const std::string& key_set_id) { RETURN_FALSE_IF_UNINITIALIZED(); + UniqueLock lock(reserved_license_ids_mutex_); reserved_license_ids_.insert(key_set_id); return true; } bool DeviceFiles::UnreserveLicenseId(const std::string& key_set_id) { RETURN_FALSE_IF_UNINITIALIZED(); + UniqueLock lock(reserved_license_ids_mutex_); reserved_license_ids_.erase(key_set_id); return true; } diff --git a/libwvdrmengine/cdm/core/src/service_certificate.cpp b/libwvdrmengine/cdm/core/src/service_certificate.cpp index 47625a7a..2d2947bf 100644 --- a/libwvdrmengine/cdm/core/src/service_certificate.cpp +++ b/libwvdrmengine/cdm/core/src/service_certificate.cpp @@ -206,7 +206,7 @@ CdmResponseType ServiceCertificate::Init(const std::string& certificate) { } CdmResponseType ServiceCertificate::VerifySignedMessage( - const std::string& message, const std::string& signature) { + const std::string& message, const std::string& signature) const { if (!public_key_) { LOGE("Service certificate not set"); return DEVICE_CERTIFICATE_ERROR_4; @@ -218,8 +218,8 @@ CdmResponseType ServiceCertificate::VerifySignedMessage( return NO_ERROR; } -CdmResponseType ServiceCertificate::EncryptRsaOaep(const std::string& plaintext, - std::string* ciphertext) { +CdmResponseType ServiceCertificate::EncryptRsaOaep( + const std::string& plaintext, std::string* ciphertext) const { if (!public_key_) { LOGE("Service certificate not set"); return DEVICE_CERTIFICATE_ERROR_4; @@ -233,7 +233,7 @@ CdmResponseType ServiceCertificate::EncryptRsaOaep(const std::string& plaintext, CdmResponseType ServiceCertificate::EncryptClientId( CryptoSession* crypto_session, const ClientIdentification* clear_client_id, - EncryptedClientIdentification* encrypted_client_id) { + EncryptedClientIdentification* encrypted_client_id) const { encrypted_client_id->set_provider_id(provider_id_); encrypted_client_id->set_service_certificate_serial_number(serial_number_); diff --git a/libwvdrmengine/cdm/include/wv_content_decryption_module.h b/libwvdrmengine/cdm/include/wv_content_decryption_module.h index 511a88bf..bc6145f8 100644 --- a/libwvdrmengine/cdm/include/wv_content_decryption_module.h +++ b/libwvdrmengine/cdm/include/wv_content_decryption_module.h @@ -92,7 +92,7 @@ class WvContentDecryptionModule : public android::RefBase, public TimerHandler { CdmProvisioningRequest* request, std::string* default_url); virtual CdmResponseType HandleProvisioningResponse( - const CdmIdentifier& identifier, CdmProvisioningResponse& response, + const CdmIdentifier& identifier, const CdmProvisioningResponse& response, RequestedSecurityLevel requested_security_level, std::string* cert, std::string* wrapped_key); diff --git a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp index 2ff88768..a0f6edae 100644 --- a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp +++ b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp @@ -227,7 +227,7 @@ CdmResponseType WvContentDecryptionModule::GetProvisioningRequest( } CdmResponseType WvContentDecryptionModule::HandleProvisioningResponse( - const CdmIdentifier& identifier, CdmProvisioningResponse& response, + const CdmIdentifier& identifier, const CdmProvisioningResponse& response, RequestedSecurityLevel requested_security_level, std::string* cert, std::string* wrapped_key) { CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier); diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index 21cff53d..b960cbcb 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -52,7 +52,11 @@ namespace { // HTTP response codes. const int kHttpOk = 200; -const int kDrmCertificateExpiryPeriod = 150; + +// For tests checking functionality of expiring. +constexpr uint32_t kDrmCertificateExpiryPeriod = 120; +constexpr uint32_t kDrmCertificateExpirySleepPeriod = + kDrmCertificateExpiryPeriod + 30; const wvcdm::CdmIdentifier kExampleIdentifier = { wvcdm::EMPTY_SPOID, "com.example", "com.example", 7, 9}; @@ -1868,11 +1872,26 @@ class WvCdmRequestLicenseTest : public WvCdmTestBase { // Post a request and extract the signed provisioning message from // the HTTP response. - std::string GetCertRequestResponse(const std::string& server_url) { + // Setting a non-zero value to |duration_seconds| will request a + // limited duration DRM certificate. + std::string GetCertRequestResponse(const std::string& server_url, + uint32_t duration_seconds = 0) { // Use secure connection and chunk transfer coding. - UrlRequest url_request(server_url); + std::string actual_url = server_url; + if (duration_seconds > 0) { + const auto query_start = actual_url.find('?'); + if (query_start == std::string::npos) { + actual_url.push_back('?'); + } else { + actual_url.push_back('&'); + } + actual_url.append("options.expiration_delta_seconds="); + actual_url.append(std::to_string(duration_seconds)); + } + + UrlRequest url_request(actual_url); EXPECT_TRUE(url_request.is_connected()) - << "Fail to connect to " << server_url; + << "Fail to connect to " << actual_url; url_request.PostCertRequestInQueryString(key_msg_); std::string message; EXPECT_TRUE(url_request.GetResponse(&message)); @@ -2573,6 +2592,10 @@ TEST_F(WvCdmRequestLicenseTest, ProvisioningRevocationTest) { } } +// Test checks that the CDM correctly reports provisioning status when +// a certificate has expired. It is expected that the CDM reports that +// the specified CDM engine is provisioned only during the certificate's +// validity period. TEST_F(WvCdmRequestLicenseTest, ProvisioningWithExpiringCertTest) { EXPECT_EQ(NO_ERROR, decryptor_->Unprovision(kSecurityLevelL1, kExampleIdentifier)); @@ -2584,7 +2607,7 @@ TEST_F(WvCdmRequestLicenseTest, ProvisioningWithExpiringCertTest) { // Provision std::string provisioning_server; - CdmCertificateType cert_type = kCertificateWidevine; + const CdmCertificateType cert_type = kCertificateWidevine; std::string cert_authority, cert, wrapped_key; EXPECT_EQ(wvcdm::NO_ERROR, decryptor_->GetProvisioningRequest( @@ -2592,18 +2615,19 @@ TEST_F(WvCdmRequestLicenseTest, ProvisioningWithExpiringCertTest) { kEmptyServiceCertificate, kLevelDefault, &key_msg_, &provisioning_server)); - std::string response = GetCertRequestResponse(config.provisioning_server()); - EXPECT_NE(0, static_cast(response.size())); + const std::string response = GetCertRequestResponse( + config.provisioning_server(), kDrmCertificateExpiryPeriod); + EXPECT_FALSE(response.empty()) << "Failed to get DRM provisioning response"; EXPECT_EQ(wvcdm::NO_ERROR, decryptor_->HandleProvisioningResponse( kExampleIdentifier, response, kLevelDefault, &cert, &wrapped_key)); - EXPECT_EQ(0, static_cast(cert.size())); - EXPECT_EQ(0, static_cast(wrapped_key.size())); - decryptor_->CloseSession(session_id_); + EXPECT_TRUE(cert.empty()) << "Widevine certs should not be returned"; + EXPECT_TRUE(wrapped_key.empty()) + << "Keys from Widevine certs should not be returned"; // Make sure it is provisioned, then wait for certificate expiry period EXPECT_TRUE(IsProvisioned(kExampleIdentifier, kLevelDefault)); - sleep(kDrmCertificateExpiryPeriod); + sleep(kDrmCertificateExpirySleepPeriod); // Verify that it is no longer provisioned after the certificate expires EXPECT_FALSE(IsProvisioned(kExampleIdentifier, kLevelDefault)); @@ -2781,6 +2805,8 @@ TEST_F(WvCdmRequestLicenseTest, AddStreamingKeyTest) { decryptor_->CloseSession(session_id_); } +// Test checks that the CDM does not allow key request generations +// if the DRM certificate is expired. TEST_F(WvCdmRequestLicenseTest, StreamingWithExpiringCertTest) { EXPECT_EQ(NO_ERROR, decryptor_->Unprovision(kSecurityLevelL1, kExampleIdentifier)); @@ -2800,13 +2826,15 @@ TEST_F(WvCdmRequestLicenseTest, StreamingWithExpiringCertTest) { kEmptyServiceCertificate, kLevelDefault, &key_msg_, &provisioning_server)); - std::string response = GetCertRequestResponse(config.provisioning_server()); - EXPECT_NE(0, static_cast(response.size())); + const std::string response = GetCertRequestResponse( + config.provisioning_server(), kDrmCertificateExpiryPeriod); + EXPECT_FALSE(response.empty()) << "Failed to get DRM provisioning response"; EXPECT_EQ(wvcdm::NO_ERROR, decryptor_->HandleProvisioningResponse( kExampleIdentifier, response, kLevelDefault, &cert, &wrapped_key)); - EXPECT_EQ(0, static_cast(cert.size())); - EXPECT_EQ(0, static_cast(wrapped_key.size())); + EXPECT_TRUE(cert.empty()) << "Widevine certs should not be returned"; + EXPECT_TRUE(wrapped_key.empty()) + << "Keys from Widevine certs should not be returned"; EXPECT_TRUE(IsProvisioned(kExampleIdentifier, kLevelDefault)); @@ -2821,6 +2849,17 @@ TEST_F(WvCdmRequestLicenseTest, StreamingWithExpiringCertTest) { nullptr); VerifyKeyRequestResponse(config_.license_server(), config_.client_auth()); decryptor_->CloseSession(session_id_); + + sleep(kDrmCertificateExpirySleepPeriod); + + // Fetch another stream license, after expiry. + EXPECT_EQ(NO_ERROR, + decryptor_->OpenSession(config_.key_system(), nullptr, + kExampleIdentifier, nullptr, &session_id_)); + GenerateKeyRequest(NEED_PROVISIONING, ISO_BMFF_VIDEO_MIME_TYPE, + binary_key_id(), app_parameters, kLicenseTypeStreaming, + kExampleIdentifier, nullptr); + decryptor_->CloseSession(session_id_); } TEST_F(WvCdmRequestLicenseTest, AddKeyOfflineTest) { @@ -2864,6 +2903,8 @@ TEST_F(WvCdmRequestLicenseTest, RestoreOfflineKeyTest) { decryptor_->CloseSession(session_id_); } +// Test checks that the CDM allows reloading of previously acquired offline +// licenses even if the DRM certificate has expired. TEST_F(WvCdmRequestLicenseTest, RestoreOfflineKeysWithExpiringCertTest) { EXPECT_EQ(NO_ERROR, decryptor_->Unprovision(kSecurityLevelL1, kExampleIdentifier)); @@ -2875,7 +2916,7 @@ TEST_F(WvCdmRequestLicenseTest, RestoreOfflineKeysWithExpiringCertTest) { // Provision std::string provisioning_server; - CdmCertificateType cert_type = kCertificateWidevine; + const CdmCertificateType cert_type = kCertificateWidevine; std::string cert_authority, cert, wrapped_key; EXPECT_EQ(wvcdm::NO_ERROR, decryptor_->GetProvisioningRequest( @@ -2883,13 +2924,15 @@ TEST_F(WvCdmRequestLicenseTest, RestoreOfflineKeysWithExpiringCertTest) { kEmptyServiceCertificate, kLevelDefault, &key_msg_, &provisioning_server)); - std::string response = GetCertRequestResponse(config.provisioning_server()); - EXPECT_NE(0, static_cast(response.size())); + const std::string response = GetCertRequestResponse( + config.provisioning_server(), kDrmCertificateExpiryPeriod); + EXPECT_FALSE(response.empty()) << "Failed to get DRM provisioning response"; EXPECT_EQ(wvcdm::NO_ERROR, decryptor_->HandleProvisioningResponse( kExampleIdentifier, response, kLevelDefault, &cert, &wrapped_key)); - EXPECT_EQ(0, static_cast(cert.size())); - EXPECT_EQ(0, static_cast(wrapped_key.size())); + EXPECT_TRUE(cert.empty()) << "Widevine certs should not be returned"; + EXPECT_TRUE(wrapped_key.empty()) + << "Keys from Widevine certs should not be returned"; EXPECT_TRUE(IsProvisioned(kExampleIdentifier, kLevelDefault)); @@ -2908,19 +2951,20 @@ TEST_F(WvCdmRequestLicenseTest, RestoreOfflineKeysWithExpiringCertTest) { nullptr); VerifyKeyRequestResponse(config_.license_server(), client_auth); - CdmKeySetId key_set_id = key_set_id_; + const CdmKeySetId key_set_id = key_set_id_; EXPECT_FALSE(key_set_id_.empty()); decryptor_->CloseSession(session_id_); session_id_.clear(); // Wait till certificate expires - sleep(kDrmCertificateExpiryPeriod); + sleep(kDrmCertificateExpirySleepPeriod); - // Make sure the certificate has expired and the device is not provisioned + // Make sure the certificate has expired and the device is not provisioned. EXPECT_FALSE(IsProvisioned(kExampleIdentifier, kLevelDefault)); - // Restore offline license + // Restore offline license. Key restoration should not be prevented + // by the expired DRM certificate. decryptor_->OpenSession(config_.key_system(), nullptr, kExampleIdentifier, nullptr, &session_id_); EXPECT_EQ(wvcdm::KEY_ADDED, decryptor_->RestoreKey(session_id_, key_set_id)); diff --git a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp index 8e567b32..b74ad900 100644 --- a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp +++ b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp @@ -194,7 +194,7 @@ class MockCDM : public WvContentDecryptionModule { std::string*), (override)); MOCK_METHOD(CdmResponseType, HandleProvisioningResponse, - (const CdmIdentifier&, CdmProvisioningResponse&, + (const CdmIdentifier&, const CdmProvisioningResponse&, wvcdm::RequestedSecurityLevel, std::string*, std::string*), (override)); MOCK_METHOD(CdmResponseType, Unprovision, (CdmSecurityLevel,