diff --git a/libwvdrmengine/Android.mk b/libwvdrmengine/Android.mk index 6f258625..31f2e751 100644 --- a/libwvdrmengine/Android.mk +++ b/libwvdrmengine/Android.mk @@ -168,6 +168,7 @@ LOCAL_STATIC_LIBRARIES := \ libwvdrmcryptoplugin \ libwvdrmdrmplugin \ libwvlevel3 \ + libwv_odk \ LOCAL_SHARED_LIBRARIES := \ libbase \ @@ -229,6 +230,7 @@ LOCAL_STATIC_LIBRARIES := \ libwvdrmcryptoplugin_hidl \ libwvdrmdrmplugin_hidl \ libwvlevel3 \ + libwv_odk \ LOCAL_SHARED_LIBRARIES := \ android.hardware.drm@1.0 \ diff --git a/libwvdrmengine/build_and_run_all_unit_tests.sh b/libwvdrmengine/build_and_run_all_unit_tests.sh index 29ec0e7f..ca0918cc 100755 --- a/libwvdrmengine/build_and_run_all_unit_tests.sh +++ b/libwvdrmengine/build_and_run_all_unit_tests.sh @@ -57,6 +57,7 @@ WV_TEST_TARGETS="base64_test \ license_unittest \ metrics_collections_unittest \ oemcrypto_test \ + odk_test \ policy_engine_constraints_unittest \ policy_engine_unittest \ request_license_test \ diff --git a/libwvdrmengine/cdm/Android.bp b/libwvdrmengine/cdm/Android.bp index b952b1a1..ae262960 100644 --- a/libwvdrmengine/cdm/Android.bp +++ b/libwvdrmengine/cdm/Android.bp @@ -8,7 +8,8 @@ METRICS_SRC_DIR = "metrics/src" cc_library_static { name: "libcdm", - cflags: ["-DDYNAMIC_ADAPTER"], + cflags: ["-DDYNAMIC_ADAPTER", + "-DTEST_OEMCRYPTO_V15"], include_dirs: [ "vendor/widevine/libwvdrmengine/cdm/core/include", @@ -16,6 +17,7 @@ cc_library_static { "vendor/widevine/libwvdrmengine/cdm/util/include", "vendor/widevine/libwvdrmengine/cdm/include", "vendor/widevine/libwvdrmengine/oemcrypto/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/include", "external/jsmn", "external/protobuf/src", ], diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index abc9445a..28949cc7 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -195,9 +195,6 @@ class CdmEngine { // system. This will force the device to reprovision itself. virtual CdmResponseType Unprovision(CdmSecurityLevel security_level); - // Delete OEMCrypto usage tables. Used by Unprovision(). - virtual CdmResponseType DeleteUsageTable(CdmSecurityLevel security_level); - // Return the list of key_set_ids stored on the current (origin-specific) // file system. virtual CdmResponseType ListStoredLicenses( @@ -364,7 +361,6 @@ class CdmEngine { const CdmSessionId* forced_session_id, CdmSessionId* session_id); - void DeleteAllUsageReportsUponFactoryReset(); bool ValidateKeySystem(const CdmKeySystem& key_system); CdmResponseType GetUsageInfo(const std::string& app_id, SecurityLevel requested_security_level, @@ -396,7 +392,10 @@ class CdmEngine { Clock clock_; std::string spoid_; - // usage related variables + // Usage related variables + // Used to isolate a single active usage information license. Loading, + // creating or releasing a different usage licenses through the engine + // API will release the handle to previously active secure stop license. std::unique_ptr usage_session_; std::unique_ptr usage_property_set_; int64_t last_usage_information_update_time_; diff --git a/libwvdrmengine/cdm/core/include/cdm_session.h b/libwvdrmengine/cdm/core/include/cdm_session.h index a5c585ac..1211e474 100644 --- a/libwvdrmengine/cdm/core/include/cdm_session.h +++ b/libwvdrmengine/cdm/core/include/cdm_session.h @@ -142,10 +142,6 @@ class CdmSession { } virtual CdmSecurityLevel GetSecurityLevel() { return security_level_; } - // Delete usage information for the list of tokens, |provider_session_tokens|. - virtual CdmResponseType DeleteMultipleUsageInformation( - const std::vector& provider_session_tokens); - virtual CdmResponseType UpdateUsageTableInformation(); virtual CdmResponseType UpdateUsageEntryInformation(); virtual bool is_initial_usage_update() { return is_initial_usage_update_; } diff --git a/libwvdrmengine/cdm/core/include/certificate_provisioning.h b/libwvdrmengine/cdm/core/include/certificate_provisioning.h index 75ec75b6..6f00c5f8 100644 --- a/libwvdrmengine/cdm/core/include/certificate_provisioning.h +++ b/libwvdrmengine/cdm/core/include/certificate_provisioning.h @@ -48,6 +48,8 @@ class CertificateProvisioning { FileSystem* file_system, const CdmProvisioningResponse& response, std::string* cert, std::string* wrapped_key); + bool supports_core_messages() const { return supports_core_messages_; } + // Helper methods // Extract serial number and system ID from a DRM Device certificate. @@ -73,6 +75,13 @@ class CertificateProvisioning { CdmCertificateType cert_type_; std::unique_ptr service_certificate_; + // Indicates whether OEMCrypto supports core messages, and whether the + // CDM should expect a core message in the response. This is primarly + // used to distinguish between v16+ OEMCrypto or and earlier version. + // Assume core messages are supported, and check if OEMCrypto populates + // the core message field when calling PrepAndSignProvisioningRequest(). + bool supports_core_messages_ = true; + CORE_DISALLOW_COPY_AND_ASSIGN(CertificateProvisioning); }; diff --git a/libwvdrmengine/cdm/core/include/content_key_session.h b/libwvdrmengine/cdm/core/include/content_key_session.h index fc3f3570..9961c3b3 100644 --- a/libwvdrmengine/cdm/core/include/content_key_session.h +++ b/libwvdrmengine/cdm/core/include/content_key_session.h @@ -51,8 +51,8 @@ class ContentKeySession : public KeySession { // Decrypt for ContentKeySession OEMCryptoResult Decrypt( const CdmDecryptionParameters& params, - OEMCrypto_DestBufferDesc& buffer_descriptor, - OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) override; + OEMCrypto_DestBufferDesc& buffer_descriptor, size_t additional_offset, + const OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) override; protected: virtual OEMCryptoResult LoadKeysAsLicenseType( diff --git a/libwvdrmengine/cdm/core/include/crypto_session.h b/libwvdrmengine/cdm/core/include/crypto_session.h index 7f6a094b..7f6595e3 100644 --- a/libwvdrmengine/cdm/core/include/crypto_session.h +++ b/libwvdrmengine/cdm/core/include/crypto_session.h @@ -75,6 +75,8 @@ class CryptoSession { virtual CdmSecurityLevel GetSecurityLevel(SecurityLevel requested_level); virtual bool GetApiVersion(uint32_t* version); virtual bool GetApiVersion(SecurityLevel requested_level, uint32_t* version); + virtual bool GetApiMinorVersion(SecurityLevel requested_level, + uint32_t* minor_version); virtual CdmResponseType GetInternalDeviceUniqueId(std::string* device_id); virtual CdmResponseType GetExternalDeviceUniqueId(std::string* device_id); @@ -89,13 +91,15 @@ class CryptoSession { virtual bool IsOpen() { return open_; } virtual CryptoSessionId oec_session_id() { return oec_session_id_; } - // Key request/response + // All request/responses virtual const std::string& request_id() { return request_id_; } - virtual CdmResponseType PrepareRequest(const std::string& key_deriv_message, - bool is_provisioning, - std::string* signature); - virtual CdmResponseType PrepareRenewalRequest(const std::string& message, - std::string* signature); + virtual CdmResponseType GenerateNonce(uint32_t* nonce); + + // License request/responses + virtual CdmResponseType PrepareAndSignLicenseRequest( + const std::string& message, std::string* core_message, + std::string* signature); + // V15 licenses. virtual CdmResponseType LoadKeys(const std::string& message, const std::string& signature, const std::string& mac_key_iv, @@ -104,52 +108,45 @@ class CryptoSession { const std::string& provider_session_token, const std::string& srm_requirement, CdmLicenseKeyType key_type); - virtual CdmResponseType LoadEntitledContentKeys( - const std::vector& key_array); - virtual CdmResponseType LoadCertificatePrivateKey(std::string& wrapped_key); + // V16 licenses. + virtual CdmResponseType LoadLicense(const std::string& signed_message, + const std::string& core_message, + const std::string& signature); + + // Renewal request/responses + virtual CdmResponseType PrepareAndSignRenewalRequest( + const std::string& message, std::string* core_message, + std::string* signature); + // V15 licenses. virtual CdmResponseType RefreshKeys(const std::string& message, const std::string& signature, - int num_keys, const CryptoKey* key_array); - virtual CdmResponseType GenerateNonce(uint32_t* nonce); + const std::vector& key_array); + // V16 licenses. + virtual CdmResponseType LoadRenewal(const std::string& signed_message, + const std::string& core_message, + const std::string& signature); + + // Entitled content Keys. + virtual CdmResponseType LoadEntitledContentKeys( + const std::vector& key_array); + + // Provisioning request/responses virtual CdmResponseType GenerateDerivedKeys(const std::string& message); virtual CdmResponseType GenerateDerivedKeys(const std::string& message, const std::string& session_key); - virtual CdmResponseType RewrapCertificate(const std::string& signed_message, - const std::string& signature, - const std::string& nonce, - const std::string& private_key, - const std::string& iv, - const std::string& wrapping_key, - std::string* wrapped_private_key); + virtual CdmResponseType PrepareAndSignProvisioningRequest( + const std::string& message, std::string* core_message, + std::string* signature); + virtual CdmResponseType LoadProvisioning(const std::string& signed_message, + const std::string& core_message, + const std::string& signature, + std::string* wrapped_private_key); + virtual CdmResponseType LoadCertificatePrivateKey( + const std::string& wrapped_key); // Media data path virtual CdmResponseType Decrypt(const CdmDecryptionParameters& params); - // Usage related methods - // The overloaded method with |security_level| may be called without a - // preceding call to Open. The other method must call Open first. - virtual bool UsageInformationSupport(bool* has_support); - virtual bool UsageInformationSupport(SecurityLevel security_level, - bool* has_support); - virtual CdmResponseType UpdateUsageInformation(); // only for OEMCrypto v9-12 - virtual CdmResponseType DeactivateUsageInformation( - const std::string& provider_session_token); - virtual CdmResponseType GenerateUsageReport( - const std::string& provider_session_token, std::string* usage_report, - UsageDurationStatus* usage_duration_status, - int64_t* seconds_since_started, int64_t* seconds_since_last_played); - virtual CdmResponseType ReleaseUsageInformation( - const std::string& message, const std::string& signature, - const std::string& provider_session_token); - // Delete a usage information for a single token. This does not require - // a signed message from the server. - virtual CdmResponseType DeleteUsageInformation( - const std::string& provider_session_token); - // Delete usage information for a list of tokens. This does not require - // a signed message from the server. - virtual CdmResponseType DeleteMultipleUsageInformation( - const std::vector& provider_session_tokens); - virtual CdmResponseType DeleteAllUsageReports(); virtual bool IsAntiRollbackHwPresent(); // The overloaded methods with |security_level| may be called without a @@ -178,6 +175,9 @@ class CryptoSession { std::string* info); virtual bool GetBuildInformation(std::string* info); + virtual bool GetMaximumUsageTableEntries(SecurityLevel security_level, + size_t* number_of_entries); + virtual bool GetDecryptHashSupport(SecurityLevel security_level, uint32_t* hash_support); @@ -205,32 +205,49 @@ class CryptoSession { CdmSigningAlgorithm algorithm, const std::string& signature); - // Usage table header and usage entry related methods + // Usage table API related methods. + // Used to manipulate the CDM managed usage table header & entries, + // delegating calls to OEMCrypto. + + // Usage support. + virtual CdmResponseType GetUsageSupportType(CdmUsageSupportType* type); + + // The overloaded method with |security_level| may be called without a + // preceding call to Open. The other method must call Open first. + virtual bool UsageInformationSupport(bool* has_support); + virtual bool UsageInformationSupport(SecurityLevel security_level, + bool* has_support); + + // Usage report. + virtual CdmResponseType DeactivateUsageInformation( + const std::string& provider_session_token); + virtual CdmResponseType GenerateUsageReport( + const std::string& provider_session_token, std::string* usage_report, + UsageDurationStatus* usage_duration_status, + int64_t* seconds_since_started, int64_t* seconds_since_last_played); + + // Usage table header. virtual UsageTableHeader* GetUsageTableHeader() { return usage_table_header_; } - virtual CdmResponseType GetUsageSupportType(CdmUsageSupportType* type); + virtual CdmResponseType CreateUsageTableHeader( CdmUsageTableHeader* usage_table_header); virtual CdmResponseType LoadUsageTableHeader( const CdmUsageTableHeader& usage_table_header); + + // Usage entry. virtual CdmResponseType CreateUsageEntry(uint32_t* entry_number); virtual CdmResponseType LoadUsageEntry(uint32_t entry_number, const CdmUsageEntry& usage_entry); virtual CdmResponseType UpdateUsageEntry( CdmUsageTableHeader* usage_table_header, CdmUsageEntry* usage_entry); + + // Adjust usage entries in usage table header. virtual CdmResponseType ShrinkUsageTableHeader( uint32_t new_entry_count, CdmUsageTableHeader* usage_table_header); virtual CdmResponseType MoveUsageEntry(uint32_t new_entry_number); - virtual bool CreateOldUsageEntry(uint64_t time_since_license_received, - uint64_t time_since_first_decrypt, - uint64_t time_since_last_decrypt, - UsageDurationStatus status, - const std::string& server_mac_key, - const std::string& client_mac_key, - const std::string& provider_session_token); - virtual CdmResponseType CopyOldUsageEntry( - const std::string& provider_session_token); + virtual bool GetAnalogOutputCapabilities(bool* can_support_output, bool* can_disable_output, bool* can_support_cgms_a); @@ -271,27 +288,11 @@ class CryptoSession { static bool ExtractSystemIdFromOemCert(const std::string& oem_cert, uint32_t* system_id); CdmResponseType GetSystemIdInternal(uint32_t* system_id); - CdmResponseType GenerateSignature(const std::string& message, - std::string* signature); CdmResponseType GenerateRsaSignature(const std::string& message, std::string* signature); bool SetDestinationBufferType(); - CdmResponseType RewrapDeviceRSAKey(const std::string& message, - const std::string& signature, - const std::string& nonce, - const std::string& enc_rsa_key, - const std::string& rsa_key_iv, - std::string* wrapped_rsa_key); - - CdmResponseType RewrapDeviceRSAKey30(const std::string& message, - const std::string& nonce, - const std::string& private_key, - const std::string& iv, - const std::string& wrapping_key, - std::string* wrapped_private_key); - CdmResponseType SelectKey(const std::string& key_id, CdmCipherMode cipher_mode); @@ -336,7 +337,7 @@ class CryptoSession { // Initialization & Termination | WithOecWriteLock() // Property | WithOecReadLock() // Session Initialization | WithOecWriteLock() - // Usage Table | WithOecWriteLock() + // Usage Table Header & Entries | WithOecWriteLock() // Session | WithOecSessionLock() // // Note that accessing |key_session_| often accesses the OEMCrypto session, so @@ -400,7 +401,6 @@ class CryptoSession { bool is_destination_buffer_type_valid_; SecurityLevel requested_security_level_; - bool is_usage_support_type_valid_; CdmUsageSupportType usage_support_type_; UsageTableHeader* usage_table_header_; static UsageTableHeader* usage_table_header_l1_; diff --git a/libwvdrmengine/cdm/core/include/initialization_data.h b/libwvdrmengine/cdm/core/include/initialization_data.h index 90c37fe1..1cca1b3b 100644 --- a/libwvdrmengine/cdm/core/include/initialization_data.h +++ b/libwvdrmengine/cdm/core/include/initialization_data.h @@ -35,6 +35,11 @@ class InitializationData { std::vector ExtractWrappedKeys() const; bool contains_entitled_keys() const { return contains_entitled_keys_; } + // Dump information to the logs for debugging. + void DumpToLogs() const; + + // The static system ID for PSSH boxes indicating Widevine content. + static const std::string WidevineSystemID(); private: bool SelectWidevinePssh(const CdmInitData& init_data, diff --git a/libwvdrmengine/cdm/core/include/key_session.h b/libwvdrmengine/cdm/core/include/key_session.h index 52e98136..92cb140b 100644 --- a/libwvdrmengine/cdm/core/include/key_session.h +++ b/libwvdrmengine/cdm/core/include/key_session.h @@ -40,8 +40,8 @@ class KeySession { CdmCipherMode cipher_mode) = 0; virtual OEMCryptoResult Decrypt( const CdmDecryptionParameters& params, - OEMCrypto_DestBufferDesc& buffer_descriptor, - OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) = 0; + OEMCrypto_DestBufferDesc& buffer_descriptor, size_t additional_offset, + const OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) = 0; protected: metrics::CryptoMetrics* metrics_; diff --git a/libwvdrmengine/cdm/core/include/license.h b/libwvdrmengine/cdm/core/include/license.h index c96e69a8..95c307e4 100644 --- a/libwvdrmengine/cdm/core/include/license.h +++ b/libwvdrmengine/cdm/core/include/license.h @@ -80,7 +80,11 @@ class CdmLicense { return provider_session_token_; } - virtual bool is_offline() { return is_offline_; } + virtual bool is_offline() const { return is_offline_; } + + virtual bool supports_core_messages() const { + return supports_core_messages_; + } virtual const VersionInfo& GetServiceVersion() { return latest_service_version_; @@ -105,18 +109,18 @@ class CdmLicense { video_widevine::LicenseRequest* license_request); CdmResponseType HandleContentKeyResponse( - const std::string& msg, const std::string& signature, - const std::string& mac_key_iv, const std::string& mac_key, - const std::vector& key_array, + const std::string& msg, const std::string& core_message, + const std::string& signature, const std::string& mac_key_iv, + const std::string& mac_key, const std::vector& key_array, const video_widevine::License& license); // HandleEntitlementKeyResponse loads the entitlement keys in |key_array| into // the crypto session. In addition, it also extracts content keys from // |wrapped_keys_| and loads them for use. CdmResponseType HandleEntitlementKeyResponse( - const std::string& msg, const std::string& signature, - const std::string& mac_key_iv, const std::string& mac_key, - const std::vector& key_array, + const std::string& msg, const std::string& core_message, + const std::string& signature, const std::string& mac_key_iv, + const std::string& mac_key, const std::vector& key_array, const video_widevine::License& license); CdmResponseType HandleNewEntitledKeys( @@ -139,6 +143,11 @@ class CdmLicense { std::string provider_session_token_; bool renew_with_client_id_; bool is_offline_; + // Indicates whether the license contains / supports OEMCrypto-level + // support for core messages. If the original license was created before + // upgrading from V15, or if the licensing server is still running V15, + // then the license does not support core messages. + bool supports_core_messages_; // Associated with ClientIdentification encryption bool use_privacy_mode_; diff --git a/libwvdrmengine/cdm/core/include/oemcrypto_adapter.h b/libwvdrmengine/cdm/core/include/oemcrypto_adapter.h index bf7ab4fd..d39fbb37 100644 --- a/libwvdrmengine/cdm/core/include/oemcrypto_adapter.h +++ b/libwvdrmengine/cdm/core/include/oemcrypto_adapter.h @@ -23,6 +23,7 @@ OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, size_t* idLength, OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData, size_t* keyDataLength, SecurityLevel level); uint32_t OEMCrypto_APIVersion(SecurityLevel level); +uint32_t OEMCrypto_MinorAPIVersion(SecurityLevel level); const char* OEMCrypto_SecurityLevel(SecurityLevel level); OEMCryptoResult OEMCrypto_GetHDCPCapability(SecurityLevel level, OEMCrypto_HDCP_Capability* current, @@ -47,15 +48,14 @@ OEMCryptoResult OEMCrypto_ShrinkUsageTableHeader(SecurityLevel level, uint32_t new_table_size, uint8_t* header_buffer, size_t* header_buffer_length); -OEMCryptoResult OEMCrypto_CreateOldUsageEntry( - SecurityLevel level, uint64_t time_since_license_received, - uint64_t time_since_first_decrypt, uint64_t time_since_last_decrypt, - OEMCrypto_Usage_Entry_Status status, uint8_t* server_mac_key, - uint8_t* client_mac_key, const uint8_t* pst, size_t pst_length); uint32_t OEMCrypto_GetAnalogOutputFlags(SecurityLevel level); const char* OEMCrypto_BuildInformation(SecurityLevel level); uint32_t OEMCrypto_ResourceRatingTier(SecurityLevel level); uint32_t OEMCrypto_SupportsDecryptHash(SecurityLevel level); +size_t OEMCrypto_MaximumUsageTableHeaderSize(SecurityLevel level); +OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(uint8_t* public_cert, + size_t* public_cert_length, + SecurityLevel level); } // namespace wvcdm /* The following functions are deprecated in OEMCrypto v13. They are defined @@ -104,18 +104,8 @@ OEMCryptoResult OEMCrypto_LoadKeys_Back_Compat( OEMCrypto_Substring pst, OEMCrypto_Substring srm_restriction_data, OEMCrypto_LicenseType license_type, OEMCryptoCipherMode* cipher_modes); -OEMCryptoResult OEMCrypto_UpdateUsageTable(); - OEMCryptoResult OEMCrypto_DeactivateUsageEntry_V12(const uint8_t* pst, size_t pst_length); -OEMCryptoResult OEMCrypto_DeleteUsageEntry( - OEMCrypto_SESSION session, const uint8_t* pst, size_t pst_length, - const uint8_t* message, size_t message_length, const uint8_t* signature, - size_t signature_length); - -OEMCryptoResult OEMCrypto_ForceDeleteUsageEntry(const uint8_t* pst, - size_t pst_length); - typedef struct { const uint8_t* entitlement_key_id; size_t entitlement_key_id_length; @@ -150,10 +140,9 @@ OEMCryptoResult OEMCrypto_RefreshKeys_V14( const uint8_t* signature, size_t signature_length, size_t num_keys, const OEMCrypto_KeyRefreshObject_V14* key_array); -OEMCryptoResult OEMCrypto_CopyBuffer_V14(const uint8_t* data_addr, - size_t data_length, - OEMCrypto_DestBufferDesc* out_buffer, - uint8_t subsample_flags); +OEMCryptoResult OEMCrypto_CopyBuffer_V14( + const uint8_t* data_addr, size_t data_length, + OEMCrypto_DestBufferDesc* out_buffer_descriptor, uint8_t subsample_flags); } // extern "C" diff --git a/libwvdrmengine/cdm/core/include/usage_table_header.h b/libwvdrmengine/cdm/core/include/usage_table_header.h index 8d6ec30e..78136f1b 100644 --- a/libwvdrmengine/cdm/core/include/usage_table_header.h +++ b/libwvdrmengine/cdm/core/include/usage_table_header.h @@ -124,17 +124,8 @@ class UsageTableHeader { CdmResponseType Shrink(metrics::CryptoMetrics* metrics, uint32_t number_of_usage_entries_to_delete); - CdmResponseType UpgradeFromUsageTable(DeviceFiles* handle, - metrics::CryptoMetrics* metrics); - bool UpgradeLicensesFromUsageTable(DeviceFiles* handle, - metrics::CryptoMetrics* metrics); - bool UpgradeUsageInfoFromUsageTable(DeviceFiles* handle, - metrics::CryptoMetrics* metrics); - virtual bool is_inited() { return is_inited_; } - virtual bool CreateDummyOldUsageEntry(CryptoSession* crypto_session); - // Performs and LRU upgrade on all loaded CdmUsageEntryInfo from a // device file that had not yet been upgraded to use the LRU data. virtual bool LruUpgradeAllUsageEntries(); diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_constants.h b/libwvdrmengine/cdm/core/include/wv_cdm_constants.h index 6864aeb3..36fd81d9 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_constants.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_constants.h @@ -26,10 +26,17 @@ static const size_t CERTIFICATE_DATA_SIZE = 4 * 1024; // (NaN in JS translates to 0 in unix timestamp). static const int64_t NEVER_EXPIRES = 0; +// This is the lower limit. For OEMCrypto v16+ one can query and find how many +// are supported +static constexpr size_t kMinimumUsageTableEntriesSupported = 200; + // Resource rating tiers static const uint32_t RESOURCE_RATING_TIER_LOW = 1u; static const uint32_t RESOURCE_RATING_TIER_MEDIUM = 2u; static const uint32_t RESOURCE_RATING_TIER_HIGH = 3u; +static const uint32_t RESOURCE_RATING_TIER_VERY_HIGH = 4u; +static const uint32_t RESOURCE_RATING_TIER_MIN = RESOURCE_RATING_TIER_LOW; +static const uint32_t RESOURCE_RATING_TIER_MAX = RESOURCE_RATING_TIER_VERY_HIGH; // OEMCrypto features by version static const uint32_t OEM_CRYPTO_API_VERSION_SUPPORTS_RESOURCE_RATING_TIER = 15; @@ -82,6 +89,10 @@ static const std::string QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION = "OemCryptoBuildInformation"; static const std::string QUERY_KEY_DECRYPT_HASH_SUPPORT = "DecryptHashSupport"; static const std::string QUERY_KEY_PROVISIONING_MODEL = "ProvisioningModel"; +static const std::string QUERY_KEY_MAX_USAGE_TABLE_ENTRIES = + "MaxNumberOfUsageTableEntries"; +static const std::string QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION = + "OemCryptoApiMinorVersion"; static const std::string QUERY_VALUE_TRUE = "True"; static const std::string QUERY_VALUE_FALSE = "False"; diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_types.h b/libwvdrmengine/cdm/core/include/wv_cdm_types.h index aa770a8d..a5ef69a4 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_types.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_types.h @@ -72,8 +72,8 @@ enum CdmResponseType { CERT_PROVISIONING_RESPONSE_ERROR_2 = 19, CERT_PROVISIONING_RESPONSE_ERROR_3 = 20, CERT_PROVISIONING_RESPONSE_ERROR_4 = 21, - CERT_PROVISIONING_RESPONSE_ERROR_5 = 22, - CERT_PROVISIONING_RESPONSE_ERROR_6 = 23, + /* previously CERT_PROVISIONING_RESPONSE_ERROR_5 = 22 */ + /* previously CERT_PROVISIONING_RESPONSE_ERROR_6 = 23 */ CERT_PROVISIONING_RESPONSE_ERROR_7 = 24, CERT_PROVISIONING_RESPONSE_ERROR_8 = 25, /* previously CRYPTO_SESSION_OPEN_ERROR_1 = 26 */ @@ -341,7 +341,7 @@ enum CdmResponseType { NO_MATCHING_ENTITLEMENT_KEY = 287, LOAD_ENTITLED_CONTENT_KEYS_ERROR = 288, GET_PROVISIONING_METHOD_ERROR = 289, - SESSION_NOT_FOUND_17 = 290, + INVALID_SESSION_2 = 290, SESSION_NOT_FOUND_18 = 291, NO_CONTENT_KEY_3 = 292, DEVICE_CANNOT_REPROVISION = 293, @@ -396,11 +396,14 @@ enum CdmResponseType { NOT_IMPLEMENTED_ERROR = 342, GET_SRM_VERSION_ERROR = 343, REWRAP_DEVICE_RSA_KEY_ERROR = 344, - REWRAP_DEVICE_RSA_KEY_30_ERROR = 345, + LOAD_PROVISIONING_ERROR = 345, INVALID_SRM_LIST = 346, KEYSET_ID_NOT_FOUND_4 = 347, SESSION_NOT_FOUND_22 = 348, USAGE_INVALID_PARAMETERS_2 = 349, + CORE_MESSAGE_NOT_FOUND = 350, + LOAD_LICENSE_ERROR = 351, + LOAD_RENEWAL_ERROR = 352, // Don't forget to add new values to // * core/test/test_printers.cpp. // * android/include/mapErrors-inl.h @@ -482,15 +485,12 @@ enum CdmClientTokenType { // kNonSecureUsageSupport - TEE does not provide any support for usage // information. -// kUsageTableSupport - TEE persists usage information securely in a fixed -// size table, commonly 50 entries. (OEMCrypto v9+) // kUsageEntrySupport - usage information (table headers and entries) are // persisted in non-secure storage but are loaded and unloaded from // the TEE during use (OEMCrypto v13+) // kUnknownUsageSupport - usage support type is uninitialized or unavailable enum CdmUsageSupportType { kNonSecureUsageSupport, - kUsageTableSupport, kUsageEntrySupport, kUnknownUsageSupport, }; diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index 9ecbfe80..bab060ec 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -27,7 +27,6 @@ namespace { const uint64_t kReleaseSessionTimeToLive = 60; // seconds const uint32_t kUpdateUsageInformationPeriod = 60; // seconds -const size_t kUsageReportsPerRequest = 1; wvcdm::CdmOfflineLicenseState MapDeviceFilesLicenseState( wvcdm::DeviceFiles::LicenseState state) { @@ -571,7 +570,7 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, return NO_ERROR; } else if (query_token == QUERY_KEY_USAGE_SUPPORT) { bool supports_usage_reporting; - bool got_info = crypto_session->UsageInformationSupport( + const bool got_info = crypto_session->UsageInformationSupport( security_level, &supports_usage_reporting); if (!got_info) { @@ -698,6 +697,24 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, break; } return NO_ERROR; + } else if (query_token == QUERY_KEY_MAX_USAGE_TABLE_ENTRIES) { + size_t max_number_of_usage_entries; + if (!crypto_session->GetMaximumUsageTableEntries( + security_level, &max_number_of_usage_entries)) { + LOGW("GetMaxUsageTableEntries failed"); + return UNKNOWN_ERROR; + } + *query_response = std::to_string(max_number_of_usage_entries); + } else if (query_token == QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION) { + uint32_t api_minor_version; + if (!crypto_session->GetApiMinorVersion(security_level, + &api_minor_version)) { + LOGW("GetApiMinorVersion failed"); + return UNKNOWN_ERROR; + } + + *query_response = std::to_string(api_minor_version); + return NO_ERROR; } M_TIME(status = crypto_session->Open(security_level), @@ -804,9 +821,6 @@ CdmResponseType CdmEngine::QueryKeyAllowedUsage(const std::string& key_id, CdmKeyAllowedUsage* key_usage) { LOGI("Querying allowed key useage (all sessions): key_id = %s", key_id.c_str()); - CdmResponseType sts; - CdmKeyAllowedUsage found_in_this_session; - bool found = false; if (!key_usage) { LOGE("No response destination"); return PARAMETER_NULL; @@ -816,9 +830,12 @@ CdmResponseType CdmEngine::QueryKeyAllowedUsage(const std::string& key_id, CdmSessionList sessions; session_map_.GetSessionList(sessions); + bool found = false; for (CdmSessionList::iterator iter = sessions.begin(); iter != sessions.end(); ++iter) { - sts = (*iter)->QueryKeyAllowedUsage(key_id, &found_in_this_session); + CdmKeyAllowedUsage found_in_this_session; + CdmResponseType sts = + (*iter)->QueryKeyAllowedUsage(key_id, &found_in_this_session); if (sts == NO_ERROR) { if (found) { // Found another key. If usage settings do not match, fail. @@ -890,8 +907,7 @@ CdmResponseType CdmEngine::GetProvisioningRequest( return INVALID_PROVISIONING_REQUEST_PARAM_2; } - DeleteAllUsageReportsUponFactoryReset(); - + // TODO(b/141705730): Remove usage entries on provisioning. if (!cert_provisioning_) { cert_provisioning_.reset( new CertificateProvisioning(metrics_->GetCryptoMetrics())); @@ -1004,6 +1020,8 @@ CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) { return UNPROVISION_ERROR_1; } + // TODO(b/141705730): Remove usage entries during unprovisioning. + if (!file_system_->IsGlobal()) { if (!handle.RemoveCertificate()) { LOGE("Unable to delete certificate"); @@ -1015,34 +1033,10 @@ CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) { LOGE("Unable to delete files"); return UNPROVISION_ERROR_3; } - return DeleteUsageTable(security_level); + return NO_ERROR; } } -CdmResponseType CdmEngine::DeleteUsageTable(CdmSecurityLevel security_level) { - LOGI("Deleting usage table: security_level = %d", - static_cast(security_level)); - std::unique_ptr crypto_session( - CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics())); - CdmResponseType status; - M_TIME(status = crypto_session->Open( - security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault), - metrics_->GetCryptoMetrics(), crypto_session_open_, status, - security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault); - if (NO_ERROR != status) { - LOGE("Error opening crypto session: status = %d", static_cast(status)); - return UNPROVISION_ERROR_4; - } - status = crypto_session->DeleteAllUsageReports(); - metrics_->GetCryptoMetrics() - ->crypto_session_delete_all_usage_reports_.Increment(status); - if (status != NO_ERROR) { - LOGE("Error deleteing usage reports: status = %d", - static_cast(status)); - } - return status; -} - CdmResponseType CdmEngine::ListStoredLicenses( CdmSecurityLevel security_level, std::vector* key_set_ids) { DeviceFiles handle(file_system_); @@ -1195,9 +1189,6 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, return GET_USAGE_INFO_ERROR_1; } - CdmKeyMessage license_request; - CdmKeyResponse license_response; - std::string usage_entry; DeviceFiles::CdmUsageData usage_data; if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), ssid, &usage_data)) { @@ -1206,7 +1197,7 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, usage_session_.reset(new CdmSession(file_system_, metrics_->AddSession())); status = usage_session_->Init(usage_property_set_.get()); if (NO_ERROR != status) { - LOGE("Session init error"); + LOGE("Session init error: status = %d", static_cast(status)); return status; } if (!handle.Reset(usage_session_->GetSecurityLevel())) { @@ -1280,6 +1271,11 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, CdmUsageInfo* usage_info) { LOGI("Getting usage info: app_id = %s, security_level = %d", app_id.c_str(), static_cast(requested_security_level)); + if (!usage_info) { + LOGE("No usage info destination"); + return PARAMETER_NULL; + } + if (!usage_property_set_) { usage_property_set_.reset(new UsagePropertySet()); } @@ -1307,20 +1303,15 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, return GET_USAGE_INFO_ERROR_4; } - if (!usage_info) { - LOGE("No usage info destination"); - return PARAMETER_NULL; - } - if (0 == usage_data.size()) { - usage_info->resize(0); + if (usage_data.empty()) { + usage_info->clear(); return NO_ERROR; } - usage_info->resize(kUsageReportsPerRequest); - - size_t index = CdmRandom::RandomInRange(usage_data.size() - 1); + const size_t index = CdmRandom::RandomInRange(usage_data.size() - 1); status = usage_session_->RestoreUsageSession(usage_data[index], error_detail); if (KEY_ADDED != status) { + // TODO(b/141704872): Make multiple attempts. LOGE("RestoreUsageSession failed: index = %zu, status = %d", index, static_cast(status)); usage_info->clear(); @@ -1367,64 +1358,42 @@ CdmResponseType CdmEngine::RemoveAllUsageInfo( usage_session_.reset(new CdmSession(file_system_, metrics_->AddSession())); usage_session_->Init(usage_property_set_.get()); - switch (usage_session_->get_usage_support_type()) { - case kUsageEntrySupport: { - std::vector usage_data; - // Retrieve all usage information but delete only one before - // refetching. This is because deleting the usage entry - // might cause other entries to be shifted and information updated. - do { - if (!handle.RetrieveUsageInfo( - DeviceFiles::GetUsageInfoFileName(app_id), &usage_data)) { - LOGW("Failed to retrieve usage info"); - break; - } - - if (usage_data.empty()) break; - - CdmResponseType res = usage_session_->DeleteUsageEntry( - usage_data[0].usage_entry_number); - - if (res != NO_ERROR) { - LOGW("Failed to delete usage entry: status = %d", - static_cast(res)); - break; - } - - if (!handle.DeleteUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), - usage_data[0].provider_session_token)) { - LOGW("Failed to delete usage info"); - break; - } - } while (!usage_data.empty()); - - std::vector provider_session_tokens; - if (!handle.DeleteAllUsageInfoForApp( - DeviceFiles::GetUsageInfoFileName(app_id), - &provider_session_tokens)) { - status = REMOVE_ALL_USAGE_INFO_ERROR_5; + if (usage_session_->get_usage_support_type() == kUsageEntrySupport) { + std::vector usage_data; + // Retrieve all usage information but delete only one before + // refetching. This is because deleting the usage entry + // might cause other entries to be shifted and information updated. + do { + if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), + &usage_data)) { + LOGW("Failed to retrieve usage info"); + break; } - break; - } - case kUsageTableSupport: { - std::vector provider_session_tokens; - if (!handle.DeleteAllUsageInfoForApp( - DeviceFiles::GetUsageInfoFileName(app_id), - &provider_session_tokens)) { - LOGE("Failed to delete secure stops: cdm_security_level = %d", - static_cast(cdm_security_level)); - status = REMOVE_ALL_USAGE_INFO_ERROR_1; - } else { - CdmResponseType status2 = - usage_session_->DeleteMultipleUsageInformation( - provider_session_tokens); - if (status2 != NO_ERROR) status = status2; + + if (usage_data.empty()) break; + + CdmResponseType res = + usage_session_->DeleteUsageEntry(usage_data[0].usage_entry_number); + + if (res != NO_ERROR) { + LOGW("Failed to delete usage entry: status = %d", + static_cast(res)); + break; } - break; + + if (!handle.DeleteUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), + usage_data[0].provider_session_token)) { + LOGW("Failed to delete usage info"); + break; + } + } while (!usage_data.empty()); + + std::vector provider_session_tokens; + if (!handle.DeleteAllUsageInfoForApp( + DeviceFiles::GetUsageInfoFileName(app_id), + &provider_session_tokens)) { + status = REMOVE_ALL_USAGE_INFO_ERROR_5; } - default: - // Ignore - break; } } usage_session_.reset(); @@ -1479,38 +1448,14 @@ CdmResponseType CdmEngine::RemoveUsageInfo( continue; } - switch (usage_session_->get_usage_support_type()) { - case kUsageEntrySupport: { - status = usage_session_->DeleteUsageEntry(usage_entry_number); - - if (!handle.DeleteUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), - provider_session_token)) { - status = REMOVE_USAGE_INFO_ERROR_1; - } - usage_session_.reset(); - return status; + if (usage_session_->get_usage_support_type() == kUsageEntrySupport) { + status = usage_session_->DeleteUsageEntry(usage_entry_number); + if (!handle.DeleteUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), + provider_session_token)) { + status = REMOVE_USAGE_INFO_ERROR_1; } - case kUsageTableSupport: { - std::vector provider_session_tokens; - handle.DeleteUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), - provider_session_token); - std::unique_ptr crypto_session( - CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics())); - status = crypto_session->Open(static_cast(j) == - kSecurityLevelL3 - ? kLevel3 - : kLevelDefault); - if (status == NO_ERROR) { - crypto_session->UpdateUsageInformation(); - status = - crypto_session->DeleteUsageInformation(provider_session_token); - crypto_session->UpdateUsageInformation(); - } - return status; - } - default: - // Ignore - break; + usage_session_.reset(); + return status; } } else { LOGE("Failed to initialize L%d device files", j); @@ -1530,6 +1475,7 @@ CdmResponseType CdmEngine::ReleaseUsageInfo( } CdmResponseType status = usage_session_->ReleaseKey(message); + // Q: Should this only be reset if release key was successful? usage_session_.reset(); if (NO_ERROR != status) { LOGE("ReleaseKey failed: status = %d", status); @@ -1908,36 +1854,15 @@ void CdmEngine::OnTimerEvent() { if (is_usage_update_needed && (usage_update_period_expired || is_initial_usage_update)) { - bool has_usage_been_updated = false; - // Session list may have changed. Rebuild. session_map_.GetSessionList(sessions); for (CdmSessionList::iterator iter = sessions.begin(); iter != sessions.end(); ++iter) { (*iter)->reset_usage_flags(); - switch ((*iter)->get_usage_support_type()) { - case kUsageEntrySupport: - if ((*iter)->has_provider_session_token()) { - (*iter)->UpdateUsageEntryInformation(); - } - break; - case kUsageTableSupport: - if (!has_usage_been_updated) { - // usage is updated for all sessions so this needs to be - // called only once per update usage information period - CdmResponseType status = (*iter)->UpdateUsageTableInformation(); - if (NO_ERROR != status) { - LOGW("UpdateUsageTableInformation failed: status = %d", - static_cast(status)); - } else { - has_usage_been_updated = true; - } - } - break; - default: - // Ignore - break; + if ((*iter)->get_usage_support_type() == kUsageEntrySupport && + (*iter)->has_provider_session_token()) { + (*iter)->UpdateUsageEntryInformation(); } } } @@ -2004,38 +1929,4 @@ void CdmEngine::CloseExpiredReleaseSessions() { } } -void CdmEngine::DeleteAllUsageReportsUponFactoryReset() { - std::string device_base_path_level1 = ""; - std::string device_base_path_level3 = ""; - Properties::GetDeviceFilesBasePath(kSecurityLevelL1, - &device_base_path_level1); - Properties::GetDeviceFilesBasePath(kSecurityLevelL3, - &device_base_path_level3); - - if (!file_system_->Exists(device_base_path_level1) && - !file_system_->Exists(device_base_path_level3)) { - std::unique_ptr crypto_session( - CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics())); - CdmResponseType status; - M_TIME(status = crypto_session->Open( - cert_provisioning_requested_security_level_), - metrics_->GetCryptoMetrics(), crypto_session_open_, status, - cert_provisioning_requested_security_level_); - if (NO_ERROR == status) { - status = crypto_session->DeleteAllUsageReports(); - metrics_->GetCryptoMetrics() - ->crypto_session_delete_all_usage_reports_.Increment(status); - if (NO_ERROR != status) { - LOGW("Failed to delete usage reports: status = %d", - static_cast(status)); - } - } else { - LOGW( - "Failed to open crypto session: status = %d\n" - "Usage reports are not removed after factory reset", - static_cast(status)); - } - } -} - } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index f1c19ac3..a8cc9011 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -894,18 +894,11 @@ CdmResponseType CdmSession::StoreLicense() { usage_entry_number_)) { LOGE("Unable to store usage info"); // Usage info file is corrupt. Delete current usage entry and file. - switch (usage_support_type_) { - case kUsageEntrySupport: - DeleteUsageEntry(usage_entry_number_); - break; - case kUsageTableSupport: - crypto_session_->DeleteUsageInformation(provider_session_token); - crypto_session_->UpdateUsageInformation(); - break; - default: - LOGW("Unexpected usage support type: %d", - static_cast(usage_support_type_)); - break; + if (usage_support_type_ == kUsageEntrySupport) { + DeleteUsageEntry(usage_entry_number_); + } else { + LOGW("Unexpected usage support type: %d", + static_cast(usage_support_type_)); } std::vector provider_session_tokens; file_handle_->DeleteAllUsageInfoForApp( @@ -1006,41 +999,6 @@ void CdmSession::GetApplicationId(std::string* app_id) { } } -CdmResponseType CdmSession::DeleteMultipleUsageInformation( - const std::vector& provider_session_tokens) { - CdmUsageSupportType usage_support_type; - CdmResponseType sts = - crypto_session_->GetUsageSupportType(&usage_support_type); - if (sts == NO_ERROR && usage_support_type == kUsageTableSupport) { - for (size_t i = 0; i < provider_session_tokens.size(); ++i) { - crypto_session_->DeactivateUsageInformation(provider_session_tokens[i]); - UpdateUsageTableInformation(); - } - } - - if (sts == NO_ERROR) { - sts = crypto_session_->DeleteMultipleUsageInformation( - provider_session_tokens); - crypto_metrics_->crypto_session_delete_multiple_usage_information_ - .Increment(sts); - } - return sts; -} - -CdmResponseType CdmSession::UpdateUsageTableInformation() { - CdmUsageSupportType usage_support_type; - CdmResponseType sts = - crypto_session_->GetUsageSupportType(&usage_support_type); - - if (sts == NO_ERROR && usage_support_type == kUsageTableSupport) { - M_TIME(sts = crypto_session_->UpdateUsageInformation(), crypto_metrics_, - crypto_session_update_usage_information_, sts); - return sts; - } - - return NO_ERROR; // Ignore -} - CdmResponseType CdmSession::UpdateUsageEntryInformation() { if (usage_support_type_ != kUsageEntrySupport || !has_provider_session_token() || usage_table_header_ == nullptr) { diff --git a/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp b/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp index dd6febfb..a669ac0a 100644 --- a/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp +++ b/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp @@ -174,9 +174,9 @@ CdmResponseType CertificateProvisioning::SetSpoidParameter( SignedProvisioningMessage::ProtocolVersion CertificateProvisioning::GetProtocolVersion() { if (crypto_session_->GetPreProvisionTokenType() == kClientTokenOemCert) - return SignedProvisioningMessage::VERSION_3; + return SignedProvisioningMessage::PROVISIONING_30; else - return SignedProvisioningMessage::VERSION_2; + return SignedProvisioningMessage::PROVISIONING_20; } /* @@ -191,8 +191,9 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest( const std::string& cert_authority, const std::string& origin, const std::string& spoid, CdmProvisioningRequest* request, std::string* default_url) { - if (!default_url) { - LOGE("Output parameter |default_url| is not provided"); + if (!request || !default_url) { + LOGE("Output parameter |%s| is not provided", + request ? "default_url" : "request"); return CERT_PROVISIONING_REQUEST_ERROR_1; } @@ -212,11 +213,10 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest( status = id.Init(crypto_session_.get()); if (status != NO_ERROR) return status; - video_widevine::ClientIdentification* client_id = - provisioning_request.mutable_client_id(); + video_widevine::ClientIdentification client_id; CdmAppParameterMap app_parameter; - status = id.Prepare(app_parameter, kEmptyString, client_id); + status = id.Prepare(app_parameter, kEmptyString, &client_id); if (status != NO_ERROR) return status; if (!service_certificate_->has_certificate()) { @@ -228,8 +228,7 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest( EncryptedClientIdentification* encrypted_client_id = provisioning_request.mutable_encrypted_client_id(); status = service_certificate_->EncryptClientId( - crypto_session_.get(), client_id, encrypted_client_id); - provisioning_request.clear_client_id(); + crypto_session_.get(), &client_id, encrypted_client_id); uint32_t nonce; status = crypto_session_->GenerateNonce(&nonce); @@ -271,9 +270,10 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest( provisioning_request.SerializeToString(&serialized_message); // Derives signing and encryption keys and constructs signature. + std::string core_message; std::string request_signature; - status = crypto_session_->PrepareRequest(serialized_message, true, - &request_signature); + status = crypto_session_->PrepareAndSignProvisioningRequest( + serialized_message, &core_message, &request_signature); if (status != NO_ERROR) { LOGE("Failed to prepare provisioning request: status = %d", @@ -290,6 +290,12 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest( signed_provisioning_msg.set_message(serialized_message); signed_provisioning_msg.set_signature(request_signature); signed_provisioning_msg.set_protocol_version(GetProtocolVersion()); + if (core_message.empty()) { + // OEMCrypto does not support core messages. + supports_core_messages_ = false; + } else { + signed_provisioning_msg.set_oemcrypto_core_message(core_message); + } std::string serialized_request; signed_provisioning_msg.SerializeToString(&serialized_request); @@ -298,9 +304,9 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest( // Return request as web-safe base64 string std::vector request_vector(serialized_request.begin(), serialized_request.end()); - request->assign(Base64SafeEncodeNoPad(request_vector)); + *request = Base64SafeEncodeNoPad(request_vector); } else { - request->swap(serialized_request); + *request = std::move(serialized_request); } return NO_ERROR; } @@ -353,66 +359,59 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( error = true; } + if (supports_core_messages() && + !signed_response.has_oemcrypto_core_message()) { + LOGE("Signed response does not have core message"); + error = true; + } else if (!supports_core_messages() && + signed_response.has_oemcrypto_core_message()) { + const std::string& core_message = signed_response.oemcrypto_core_message(); + // This case should not occur. However, the CDM will let OEMCrypto + // fail. + LOGW( + "Received unexpected core message in provisioning request: " + "core_message_size = %zu", + core_message.size()); + } + if (error) return CERT_PROVISIONING_RESPONSE_ERROR_3; const std::string& signed_message = signed_response.message(); const std::string& signature = signed_response.signature(); - ProvisioningResponse provisioning_response; + const std::string core_message = + supports_core_messages() ? signed_response.oemcrypto_core_message() + : std::string(); + ProvisioningResponse provisioning_response; if (!provisioning_response.ParseFromString(signed_message)) { LOGE("Failed to parse provisioning response"); return CERT_PROVISIONING_RESPONSE_ERROR_4; } - if (!provisioning_response.has_device_rsa_key()) { - LOGE("Provisioning response does not have RSA key"); - return CERT_PROVISIONING_RESPONSE_ERROR_5; - } - - // If Provisioning 3.0 (OEM Cert provisioned), verify that the - // message is properly signed. - if (crypto_session_->GetPreProvisionTokenType() == kClientTokenOemCert) { - if (service_certificate_->VerifySignedMessage(signed_message, signature) != - NO_ERROR) { - // TODO(b/69562876): if the cert is bad, request a new one. - LOGE("Provisioning response message not properly signed"); - return CERT_PROVISIONING_RESPONSE_ERROR_6; - } - } - - const std::string& new_private_key = provisioning_response.device_rsa_key(); - const std::string& nonce = provisioning_response.nonce(); - const std::string& iv = provisioning_response.device_rsa_key_iv(); - - const std::string& wrapping_key = (provisioning_response.has_wrapping_key()) - ? provisioning_response.wrapping_key() - : std::string(); - std::string wrapped_private_key; - - CdmResponseType status = crypto_session_->RewrapCertificate( - signed_message, signature, nonce, new_private_key, iv, wrapping_key, - &wrapped_private_key); + const CdmResponseType status = crypto_session_->LoadProvisioning( + signed_message, core_message, signature, &wrapped_private_key); if (status != NO_ERROR) { - LOGE("RewrapCertificate failed: status = %d", static_cast(status)); + LOGE("LoadProvisioning failed: status = %d", static_cast(status)); return status; } - CdmSecurityLevel security_level = crypto_session_->GetSecurityLevel(); + const CdmSecurityLevel security_level = crypto_session_->GetSecurityLevel(); crypto_session_->Close(); + // This is the entire certificate (SignedDrmDeviceCertificate). + const std::string& device_certificate = + provisioning_response.device_certificate(); + if (cert_type_ == kCertificateX509) { - *cert = provisioning_response.device_certificate(); + *cert = device_certificate; *wrapped_key = wrapped_private_key; return NO_ERROR; } - // This is the entire certificate (SignedDrmDeviceCertificate). - // This will be stored to the device as the final step in the device - // provisioning process. - const std::string& device_certificate = - provisioning_response.device_certificate(); + // The certificate will be stored to the device as the final step in + // the device provisioning process. DeviceFiles handle(file_system); if (!handle.Init(security_level)) { diff --git a/libwvdrmengine/cdm/core/src/content_key_session.cpp b/libwvdrmengine/cdm/core/src/content_key_session.cpp index 0f2940fe..84411a9e 100644 --- a/libwvdrmengine/cdm/core/src/content_key_session.cpp +++ b/libwvdrmengine/cdm/core/src/content_key_session.cpp @@ -107,13 +107,32 @@ OEMCryptoResult ContentKeySession::SelectKey(const std::string& key_id, // Decrypt for ContentKeySession OEMCryptoResult ContentKeySession::Decrypt( const CdmDecryptionParameters& params, - OEMCrypto_DestBufferDesc& buffer_descriptor, - OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) { + OEMCrypto_DestBufferDesc& buffer_descriptor, size_t additional_offset, + const OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) { + // TODO(b/135285640): fix this. At the moment, all this does is send one + // subsample. No checks are in place. + OEMCryptoResult sts; - M_TIME(sts = OEMCrypto_DecryptCENC( - oec_session_id_, params.encrypt_buffer, params.encrypt_length, - params.is_encrypted, &(*params.iv).front(), params.block_offset, - &buffer_descriptor, &pattern_descriptor, params.subsample_flags), + OEMCrypto_SampleDescription sample; // Just one for now. + OEMCrypto_SubSampleDescription subsample; + sample.buffers.input_data = params.encrypt_buffer + additional_offset; + sample.buffers.input_data_length = params.encrypt_length; + sample.buffers.output_descriptor = buffer_descriptor; + memcpy(sample.iv, params.iv->data(), KEY_IV_SIZE); + sample.subsamples = &subsample; + sample.subsamples_length = 1; + if (params.is_encrypted) { + subsample.num_bytes_clear = 0; + subsample.num_bytes_encrypted = params.encrypt_length; + } else { + subsample.num_bytes_clear = params.encrypt_length; + subsample.num_bytes_encrypted = 0; + } + subsample.subsample_flags = params.subsample_flags; + subsample.block_offset = params.block_offset; + + M_TIME(sts = OEMCrypto_DecryptCENC(oec_session_id_, &sample, 1, + &pattern_descriptor), metrics_, oemcrypto_decrypt_cenc_, sts, metrics::Pow2Bucket(params.encrypt_length)); return sts; diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 5a4657bf..f7658dbc 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -47,23 +47,13 @@ return ret_value; \ } +namespace wvcdm { + namespace { - -// Encode unsigned integer into a big endian formatted string -std::string EncodeUint32(unsigned int u) { - std::string s; - s.append(1, (u >> 24) & 0xFF); - s.append(1, (u >> 16) & 0xFF); - s.append(1, (u >> 8) & 0xFF); - s.append(1, (u >> 0) & 0xFF); - return s; -} - const uint32_t kRsaSignatureLength = 256; // TODO(b/117112392): adjust chunk size based on resource rating. const size_t kMaximumChunkSize = 100 * 1024; // 100 KiB const size_t kEstimatedInitialUsageTableHeader = 40; -const size_t kOemCryptoApiVersionSupportsBigUsageTables = 13; // Ability to switch cipher modes in SelectKey() was introduced in this // OEMCrypto version const size_t kOemCryptoApiVersionSupportsSwitchingCipherMode = 14; @@ -71,9 +61,37 @@ const size_t kOemCryptoApiVersionSupportsSwitchingCipherMode = 14; // Constants and utility objects relating to OEM Certificates const char* const kWidevineSystemIdExtensionOid = "1.3.6.1.4.1.11129.4.1.1"; +const std::string kStringNotAvailable = "NA"; + +// This maps a few common OEMCryptoResult to CdmResponseType. Many mappings +// are not universal but are OEMCrypto method specific. Those will be +// specified in the CryptoSession method rather than here. +CdmResponseType MapOEMCryptoResult(OEMCryptoResult result, + CdmResponseType default_status, + const char* crypto_session_method) { + if (result != OEMCrypto_SUCCESS) { + LOGE("Mapping OEMCrypto result: crypto_session_method = %s, result = %d", + crypto_session_method ? crypto_session_method : "N/A", + static_cast(result)); + } + + switch (result) { + case OEMCrypto_SUCCESS: + return NO_ERROR; + case OEMCrypto_ERROR_NOT_IMPLEMENTED: + return NOT_IMPLEMENTED_ERROR; + case OEMCrypto_ERROR_TOO_MANY_SESSIONS: + return INSUFFICIENT_CRYPTO_RESOURCES; + case OEMCrypto_ERROR_SESSION_LOST_STATE: + return SESSION_LOST_STATE_ERROR; + case OEMCrypto_ERROR_SYSTEM_INVALIDATED: + return SYSTEM_INVALIDATED_ERROR; + default: + return default_status; + } +} } // namespace -namespace wvcdm { shared_mutex CryptoSession::static_field_mutex_; shared_mutex CryptoSession::oem_crypto_mutex_; bool CryptoSession::initialized_ = false; @@ -145,45 +163,11 @@ void GenerateEncryptContext(const std::string& input_context, deriv_context->append(EncodeUint32(kEncryptionKeySizeBits)); } -OEMCrypto_LicenseType OEMCryptoLicenseType(CdmLicenseKeyType cdm_license_type) { - return cdm_license_type == kLicenseKeyTypeContent - ? OEMCrypto_ContentLicense - : OEMCrypto_EntitlementLicense; -} - OEMCryptoCipherMode ToOEMCryptoCipherMode(CdmCipherMode cipher_mode) { return cipher_mode == kCipherModeCtr ? OEMCrypto_CipherMode_CTR : OEMCrypto_CipherMode_CBC; } -// This maps a few common OEMCryptoResult to CdmResponseType. Many mappings -// are not universal but are OEMCrypto method specific. Those will be -// specified in the CryptoSession method rather than here. -CdmResponseType MapOEMCryptoResult(OEMCryptoResult result, - CdmResponseType default_status, - const char* crypto_session_method) { - if (result != OEMCrypto_SUCCESS) { - LOGE("Mapping OEMCrypto result: crypto_session_method = %s, result = %d", - crypto_session_method ? crypto_session_method : "N/A", - static_cast(result)); - } - - switch (result) { - case OEMCrypto_SUCCESS: - return NO_ERROR; - case OEMCrypto_ERROR_NOT_IMPLEMENTED: - return NOT_IMPLEMENTED_ERROR; - case OEMCrypto_ERROR_TOO_MANY_SESSIONS: - return INSUFFICIENT_CRYPTO_RESOURCES; - case OEMCrypto_ERROR_SESSION_LOST_STATE: - return SESSION_LOST_STATE_ERROR; - case OEMCrypto_ERROR_SYSTEM_INVALIDATED: - return SYSTEM_INVALIDATED_ERROR; - default: - return default_status; - } -} - CryptoSession::CryptoSession(metrics::CryptoMetrics* metrics) : metrics_(metrics), system_id_(-1), @@ -192,7 +176,6 @@ CryptoSession::CryptoSession(metrics::CryptoMetrics* metrics) update_usage_table_after_close_session_(false), is_destination_buffer_type_valid_(false), requested_security_level_(kLevelDefault), - is_usage_support_type_valid_(false), usage_support_type_(kUnknownUsageSupport), usage_table_header_(nullptr), cipher_mode_(kCipherModeCtr), @@ -241,6 +224,7 @@ CdmResponseType CryptoSession::GetProvisioningMethod( void CryptoSession::Init() { LOGV("Initializing crypto session"); + bool initialized = false; WithStaticFieldWriteLock("Init", [&] { session_count_ += 1; if (!initialized_) { @@ -261,8 +245,32 @@ void CryptoSession::Init() { return; } initialized_ = true; + initialized = true; } }); + + if (initialized) { + uint32_t version; + std::string api_version = + CryptoSession::GetApiVersion(kLevelDefault, &version) + ? std::to_string(version) + : kStringNotAvailable; + std::string api_minor_version = + CryptoSession::GetApiMinorVersion(kLevelDefault, &version) + ? std::to_string(version) + : kStringNotAvailable; + LOGD("OEMCrypto version (default security level): %s.%s", + api_version.c_str(), api_minor_version.c_str()); + + api_version = CryptoSession::GetApiVersion(kLevel3, &version) + ? std::to_string(version) + : kStringNotAvailable; + api_minor_version = CryptoSession::GetApiMinorVersion(kLevel3, &version) + ? std::to_string(version) + : kStringNotAvailable; + LOGD("OEMCrypto version (L3 security level): %s.%s", api_version.c_str(), + api_minor_version.c_str()); + } } void CryptoSession::Terminate() { @@ -326,14 +334,23 @@ CdmResponseType CryptoSession::GetTokenFromOemCert(std::string* token) { token->assign(oem_token_); return NO_ERROR; } + + // TODO(b/141655126): This function can be optimized to not load private + // key when it isn't needed. + status = OEMCrypto_LoadOEMPrivateKey(oec_session_id_); + if (status != OEMCrypto_SUCCESS) { + return MapOEMCryptoResult(status, GET_TOKEN_FROM_OEM_CERT_ERROR, + "GetTokenFromOemCert"); + } + std::string temp_buffer(CERTIFICATE_DATA_SIZE, '\0'); bool retrying = false; while (true) { size_t buf_size = temp_buffer.size(); uint8_t* buf = reinterpret_cast(&temp_buffer[0]); WithOecSessionLock("GetTokenFromOemCert", [&] { - status = - OEMCrypto_GetOEMPublicCertificate(oec_session_id_, buf, &buf_size); + status = OEMCrypto_GetOEMPublicCertificate(buf, &buf_size, + requested_security_level_); }); metrics_->oemcrypto_get_oem_public_certificate_.Increment(status); @@ -511,6 +528,25 @@ bool CryptoSession::GetApiVersion(SecurityLevel security_level, return true; } +bool CryptoSession::GetApiMinorVersion(SecurityLevel security_level, + uint32_t* minor_version) { + LOGV("Getting API minor version: security_level = %d", + static_cast(security_level)); + if (!minor_version) { + LOGE("Output parameter |minor_version| not provided"); + return false; + } + RETURN_IF_UNINITIALIZED(false); + + WithOecReadLock("GetApiMinorVersion", [&] { + *minor_version = OEMCrypto_MinorAPIVersion(security_level); + }); + // Record the minor version into the metrics. + metrics_->oemcrypto_minor_api_version_.Record(*minor_version); + + return true; +} + bool CryptoSession::GetSystemId(uint32_t* system_id) { RETURN_IF_NULL(system_id, false); RETURN_IF_UNINITIALIZED(false); @@ -758,39 +794,63 @@ void CryptoSession::Close() { if (!open_) return; OEMCryptoResult close_sts; - bool update_usage_table = false; WithOecWriteLock( "Close", [&] { close_sts = OEMCrypto_CloseSession(oec_session_id_); }); metrics_->oemcrypto_close_session_.Increment(close_sts); if (OEMCrypto_SUCCESS == close_sts) open_ = false; - update_usage_table = update_usage_table_after_close_session_; - if (close_sts == OEMCrypto_SUCCESS && update_usage_table && - usage_support_type_ == kUsageTableSupport) { - UpdateUsageInformation(); - } } -CdmResponseType CryptoSession::PrepareRequest(const std::string& message, - bool is_provisioning, - std::string* signature) { +CdmResponseType CryptoSession::PrepareAndSignLicenseRequest( + const std::string& message, std::string* core_message, + std::string* signature) { + LOGV("Preparing and signing license request: id = %u", oec_session_id_); RETURN_IF_NULL(signature, PARAMETER_NULL); + RETURN_IF_NULL(core_message, PARAMETER_NULL); - if (is_provisioning && (pre_provision_token_type_ == kClientTokenKeybox)) { - CdmResponseType status = GenerateDerivedKeys(message); + OEMCryptoResult sts; + size_t signature_length = 0; + size_t core_message_length = 0; + // First call is intended to determine the required size of the + // output buffers. + WithOecSessionLock("PrepareAndSignLicenseRequest", [&] { + M_TIME( + sts = OEMCrypto_PrepAndSignLicenseRequest( + oec_session_id_, + reinterpret_cast(const_cast(message.data())), + message.size(), &core_message_length, nullptr, &signature_length), + metrics_, oemcrypto_prep_and_sign_license_request_, sts); + }); - if (status != NO_ERROR) return status; - - return GenerateSignature(message, signature); - } else { - return GenerateRsaSignature(message, signature); + if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { + return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR, + "PrepareAndSignLicenseRequest"); } -} -CdmResponseType CryptoSession::PrepareRenewalRequest(const std::string& message, - std::string* signature) { - RETURN_IF_NULL(signature, PARAMETER_NULL); + // Resize. + core_message->resize(core_message_length); + signature->resize(signature_length); - return GenerateSignature(message, signature); + std::string combined_message = *core_message + message; + WithOecSessionLock("PrepareAndSignLicenseRequest", [&] { + M_TIME(sts = OEMCrypto_PrepAndSignLicenseRequest( + oec_session_id_, + reinterpret_cast( + const_cast(combined_message.data())), + combined_message.size(), &core_message_length, + reinterpret_cast(const_cast(signature->data())), + &signature_length), + metrics_, oemcrypto_prep_and_sign_license_request_, sts); + }); + + if (OEMCrypto_SUCCESS == sts) { + signature->resize(signature_length); + *core_message = std::move(combined_message); + // Truncate combined message to only contain the core message. + core_message->resize(core_message_length); + return NO_ERROR; + } + return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR, + "PrepareAndSignLicenseRequest"); } CdmResponseType CryptoSession::LoadKeys( @@ -799,8 +859,7 @@ CdmResponseType CryptoSession::LoadKeys( const std::vector& keys, const std::string& provider_session_token, const std::string& srm_requirement, CdmLicenseKeyType key_type) { - CdmResponseType result = KEY_ADDED; - + LOGV("Loading keys: id = %u", oec_session_id_); OEMCryptoResult sts; WithOecSessionLock("LoadKeys", [&] { if (key_type == kLicenseKeyTypeEntitlement && @@ -822,28 +881,225 @@ CdmResponseType CryptoSession::LoadKeys( case OEMCrypto_SUCCESS: if (!provider_session_token.empty()) update_usage_table_after_close_session_ = true; - result = KEY_ADDED; - break; + return KEY_ADDED; case OEMCrypto_ERROR_TOO_MANY_KEYS: - result = INSUFFICIENT_CRYPTO_RESOURCES_4; - break; + return INSUFFICIENT_CRYPTO_RESOURCES_4; case OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE: // Handle vendor specific error return NEED_PROVISIONING; - case OEMCrypto_ERROR_SESSION_LOST_STATE: - return SESSION_LOST_STATE_ERROR; - case OEMCrypto_ERROR_SYSTEM_INVALIDATED: - return SYSTEM_INVALIDATED_ERROR; default: - result = LOAD_KEY_ERROR; break; } + return MapOEMCryptoResult(sts, LOAD_KEY_ERROR, "LoadKeys"); +} - if (!provider_session_token.empty() && - usage_support_type_ == kUsageTableSupport) { - UpdateUsageInformation(); +CdmResponseType CryptoSession::LoadLicense(const std::string& signed_message, + const std::string& core_message, + const std::string& signature) { + LOGV("Loading license: id = %u", oec_session_id_); + const std::string combined_message = core_message + signed_message; + OEMCryptoResult sts; + WithOecSessionLock("LoadLicense", [&] { + M_TIME(sts = OEMCrypto_LoadLicense( + oec_session_id_, + reinterpret_cast(combined_message.data()), + combined_message.size(), core_message.size(), + reinterpret_cast(signature.data()), + signature.size()), + metrics_, oemcrypto_load_license_, sts); + }); + + switch (sts) { + case OEMCrypto_SUCCESS: + return KEY_ADDED; + case OEMCrypto_ERROR_BUFFER_TOO_LARGE: + LOGE("LoadLicense buffer too large: size = %zu", combined_message.size()); + return LOAD_LICENSE_ERROR; + case OEMCrypto_ERROR_TOO_MANY_KEYS: + LOGE("Too many keys in license"); + return INSUFFICIENT_CRYPTO_RESOURCES_4; + default: + break; } - return result; + return MapOEMCryptoResult(sts, LOAD_LICENSE_ERROR, "LoadLicense"); +} + +CdmResponseType CryptoSession::PrepareAndSignRenewalRequest( + const std::string& message, std::string* core_message, + std::string* signature) { + LOGV("Preparing and signing renewal request: id = %u", oec_session_id_); + if (signature == nullptr) { + LOGE("Output parameter |signature| not provided"); + return PARAMETER_NULL; + } + if (core_message == nullptr) { + LOGE("Output parameter |core_message| not provided"); + return PARAMETER_NULL; + } + + OEMCryptoResult sts; + size_t signature_length = 0; + size_t core_message_length = 0; + // First call is intended to determine the required size of the + // output buffers. + WithOecSessionLock("PrepareAndSignRenewalRequest", [&] { + M_TIME( + sts = OEMCrypto_PrepAndSignRenewalRequest( + oec_session_id_, + reinterpret_cast(const_cast(message.data())), + message.size(), &core_message_length, nullptr, &signature_length), + metrics_, oemcrypto_prep_and_sign_renewal_request_, sts); + }); + + if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { + return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR, + "PrepareAndSignRenewalRequest"); + } + + // Resize. + core_message->resize(core_message_length); + signature->resize(signature_length); + + std::string combined_message = *core_message + message; + WithOecSessionLock("PrepareAndSignRenewalRequest", [&] { + M_TIME(sts = OEMCrypto_PrepAndSignRenewalRequest( + oec_session_id_, + reinterpret_cast( + const_cast(combined_message.data())), + combined_message.size(), &core_message_length, + reinterpret_cast(const_cast(signature->data())), + &signature_length), + metrics_, oemcrypto_prep_and_sign_renewal_request_, sts); + }); + + if (OEMCrypto_SUCCESS == sts) { + signature->resize(signature_length); + *core_message = std::move(combined_message); + // Truncate combined message to only contain the core message. + core_message->resize(core_message_length); + return NO_ERROR; + } + return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR, + "PrepareAndSignRenewalRequest"); +} + +CdmResponseType CryptoSession::RefreshKeys( + const std::string& message, const std::string& signature, + const std::vector& key_array) { + const uint8_t* msg = reinterpret_cast(message.data()); + std::vector load_key_array(key_array.size()); + for (size_t i = 0; i < key_array.size(); ++i) { + const CryptoKey* ki = &key_array[i]; + OEMCrypto_KeyRefreshObject* ko = &load_key_array[i]; + ko->key_id = GetSubstring(message, ki->key_id()); + bool has_key_control = ki->HasKeyControl(); + ko->key_control_iv = + GetSubstring(message, ki->key_control_iv(), !has_key_control); + ko->key_control = + GetSubstring(message, ki->key_control(), !has_key_control); + } + LOGV("Refreshing keys: id = %u", oec_session_id_); + OEMCryptoResult refresh_sts; + WithOecSessionLock("RefreshKeys", [&] { + M_TIME(refresh_sts = OEMCrypto_RefreshKeys( + oec_session_id_, msg, message.size(), + reinterpret_cast(signature.data()), + signature.size(), key_array.size(), &load_key_array[0]), + metrics_, oemcrypto_refresh_keys_, refresh_sts); + }); + + if (refresh_sts == OEMCrypto_SUCCESS) return KEY_ADDED; + + return MapOEMCryptoResult(refresh_sts, REFRESH_KEYS_ERROR, "RefreshKeys"); +} + +CdmResponseType CryptoSession::LoadRenewal(const std::string& signed_message, + const std::string& core_message, + const std::string& signature) { + LOGV("Loading license renewal: id = %u", oec_session_id_); + const std::string combined_message = core_message + signed_message; + OEMCryptoResult sts; + WithOecSessionLock("LoadRenewal", [&] { + M_TIME(sts = OEMCrypto_LoadRenewal( + oec_session_id_, + reinterpret_cast(combined_message.data()), + combined_message.size(), core_message.size(), + reinterpret_cast(signature.data()), + signature.size()), + metrics_, oemcrypto_load_renewal_, sts); + }); + if (sts == OEMCrypto_SUCCESS) { + return KEY_ADDED; + } + if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) { + LOGE("Buffer too large: size = %zu", combined_message.size()); + return LOAD_RENEWAL_ERROR; + } + return MapOEMCryptoResult(sts, LOAD_RENEWAL_ERROR, "LoadRenewal"); +} + +CdmResponseType CryptoSession::PrepareAndSignProvisioningRequest( + const std::string& message, std::string* core_message, + std::string* signature) { + LOGV("Preparing and signing provisioning request: id = %u", oec_session_id_); + if (signature == nullptr) { + LOGE("Output parameter |signature| not provided"); + return PARAMETER_NULL; + } + if (core_message == nullptr) { + LOGE("Output parameter |core_message| not provided"); + return PARAMETER_NULL; + } + + if (pre_provision_token_type_ == kClientTokenKeybox) { + const CdmResponseType status = GenerateDerivedKeys(message); + if (status != NO_ERROR) return status; + } + + OEMCryptoResult sts; + size_t signature_length = 0; + size_t core_message_length = 0; + // First call is intended to determine the required size of the + // output buffers. + WithOecSessionLock("PrepareAndSignProvisioningRequest", [&] { + M_TIME( + sts = OEMCrypto_PrepAndSignProvisioningRequest( + oec_session_id_, + reinterpret_cast(const_cast(message.data())), + message.size(), &core_message_length, nullptr, &signature_length), + metrics_, oemcrypto_prep_and_sign_provisioning_request_, sts); + }); + + if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { + return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR, + "PrepareAndSignProvisioningRequest"); + } + + // Resize. + core_message->resize(core_message_length); + signature->resize(signature_length); + + std::string combined_message = *core_message + message; + WithOecSessionLock("PrepareAndSignProvisioningRequest", [&] { + M_TIME(sts = OEMCrypto_PrepAndSignProvisioningRequest( + oec_session_id_, + reinterpret_cast( + const_cast(combined_message.data())), + combined_message.size(), &core_message_length, + reinterpret_cast(const_cast(signature->data())), + &signature_length), + metrics_, oemcrypto_prep_and_sign_provisioning_request_, sts); + }); + + if (OEMCrypto_SUCCESS == sts) { + signature->resize(signature_length); + *core_message = std::move(combined_message); + // Truncate combined message to only contain the core message. + core_message->resize(core_message_length); + return NO_ERROR; + } + return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR, + "PrepareAndSignProvisioningRequest"); } CdmResponseType CryptoSession::LoadEntitledContentKeys( @@ -872,7 +1128,8 @@ CdmResponseType CryptoSession::LoadEntitledContentKeys( } CdmResponseType CryptoSession::LoadCertificatePrivateKey( - std::string& wrapped_key) { + const std::string& wrapped_key) { + // TODO(b/141655126): Getting the OEM Cert no longer loads the private key. // Call OEMCrypto_GetOEMPublicCertificate before OEMCrypto_LoadDeviceRSAKey // so it caches the OEMCrypto Public Key and then throw away result std::string temp_buffer(CERTIFICATE_DATA_SIZE, '\0'); @@ -882,8 +1139,8 @@ CdmResponseType CryptoSession::LoadCertificatePrivateKey( WithOecSessionLock( "LoadCertificatePrivateKey() calling OEMCrypto_GetOEMPublicCertificate", [&] { - sts = - OEMCrypto_GetOEMPublicCertificate(oec_session_id_, buf, &buf_size); + sts = OEMCrypto_GetOEMPublicCertificate(buf, &buf_size, + requested_security_level_); }); metrics_->oemcrypto_get_oem_public_certificate_.Increment(sts); @@ -901,37 +1158,7 @@ CdmResponseType CryptoSession::LoadCertificatePrivateKey( "LoadCertificatePrivateKey"); } -CdmResponseType CryptoSession::RefreshKeys(const std::string& message, - const std::string& signature, - int num_keys, - const CryptoKey* key_array) { - const uint8_t* msg = reinterpret_cast(message.data()); - std::vector load_key_array(num_keys); - for (int i = 0; i < num_keys; ++i) { - const CryptoKey* ki = &key_array[i]; - OEMCrypto_KeyRefreshObject* ko = &load_key_array[i]; - ko->key_id = GetSubstring(message, ki->key_id()); - bool has_key_control = ki->HasKeyControl(); - ko->key_control_iv = - GetSubstring(message, ki->key_control_iv(), !has_key_control); - ko->key_control = - GetSubstring(message, ki->key_control(), !has_key_control); - } - LOGV("Refreshing keys: id = %u", oec_session_id_); - OEMCryptoResult refresh_sts; - WithOecSessionLock("RefreshKeys", [&] { - M_TIME(refresh_sts = OEMCrypto_RefreshKeys( - oec_session_id_, msg, message.size(), - reinterpret_cast(signature.data()), - signature.size(), num_keys, &load_key_array[0]), - metrics_, oemcrypto_refresh_keys_, refresh_sts); - }); - - if (refresh_sts == OEMCrypto_SUCCESS) return KEY_ADDED; - - return MapOEMCryptoResult(refresh_sts, REFRESH_KEYS_ERROR, "RefreshKeys"); -} - +// Private. CdmResponseType CryptoSession::SelectKey(const std::string& key_id, CdmCipherMode cipher_mode) { OEMCryptoResult sts; @@ -990,43 +1217,6 @@ CdmResponseType CryptoSession::GenerateDerivedKeys( "GenerateDerivedKeys"); } -CdmResponseType CryptoSession::GenerateSignature(const std::string& message, - std::string* signature) { - LOGV("Generating signature: id = %u", oec_session_id_); - RETURN_IF_NULL(signature, PARAMETER_NULL); - - OEMCryptoResult sts; - size_t length = signature->size(); - - // At most two attempts. - // The first attempt may fail due to buffer too short - for (int i = 0; i < 2; ++i) { - WithOecSessionLock("GenerateSignature", [&] { - M_TIME( - sts = OEMCrypto_GenerateSignature( - oec_session_id_, reinterpret_cast(message.data()), - message.size(), - reinterpret_cast(const_cast(signature->data())), - &length), - metrics_, oemcrypto_generate_signature_, sts, - metrics::Pow2Bucket(length)); - }); - if (OEMCrypto_SUCCESS == sts) { - // Trim signature buffer and done - signature->resize(length); - return NO_ERROR; - } - if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { - break; - } - - // Retry with proper-sized signature buffer - signature->resize(length); - } - - return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR, "GenerateSignature"); -} - CdmResponseType CryptoSession::GenerateRsaSignature(const std::string& message, std::string* signature) { LOGV("Generating RSA signature: id = %u", oec_session_id_); @@ -1086,13 +1276,14 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) { buffer_descriptor.buffer.clear.address = static_cast(params.decrypt_buffer) + params.decrypt_buffer_offset; - buffer_descriptor.buffer.clear.max_length = + buffer_descriptor.buffer.clear.address_length = params.decrypt_buffer_length - params.decrypt_buffer_offset; break; case OEMCrypto_BufferType_Secure: buffer_descriptor.buffer.secure.handle = params.decrypt_buffer; buffer_descriptor.buffer.secure.offset = params.decrypt_buffer_offset; - buffer_descriptor.buffer.secure.max_length = params.decrypt_buffer_length; + buffer_descriptor.buffer.secure.handle_length = + params.decrypt_buffer_length; break; case OEMCrypto_BufferType_Direct: buffer_descriptor.type = OEMCrypto_BufferType_Direct; @@ -1128,7 +1319,6 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) { OEMCrypto_CENCEncryptPatternDesc pattern_descriptor; pattern_descriptor.encrypt = params.pattern_descriptor.encrypt_blocks; pattern_descriptor.skip = params.pattern_descriptor.skip_blocks; - pattern_descriptor.offset = 0; // Deprecated field // Check if key needs to be selected if (params.is_encrypted) { CdmResponseType result = SelectKey(*params.key_id, params.cipher_mode); @@ -1136,8 +1326,8 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) { } WithOecSessionLock("Decrypt() calling key_session_->Decrypt()", [&] { - sts = - key_session_->Decrypt(params, buffer_descriptor, pattern_descriptor); + sts = key_session_->Decrypt(params, buffer_descriptor, 0, + pattern_descriptor); }); if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) { @@ -1168,7 +1358,7 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) { case OEMCrypto_ERROR_KEY_EXPIRED: return NEED_KEY; case OEMCrypto_ERROR_INVALID_SESSION: - return SESSION_NOT_FOUND_17; + return INVALID_SESSION_2; case OEMCrypto_ERROR_DECRYPT_FAILED: case OEMCrypto_ERROR_UNKNOWN_FAILURE: return DECRYPT_ERROR; @@ -1204,27 +1394,6 @@ bool CryptoSession::UsageInformationSupport(SecurityLevel security_level, return true; } -CdmResponseType CryptoSession::UpdateUsageInformation() { - LOGV("Updating usage information: id = %u", oec_session_id_); - RETURN_IF_UNINITIALIZED(UNKNOWN_ERROR); - - if (usage_table_header_ != nullptr) { - LOGV("Deprecated for OEMCrypto v13+"); - return NO_ERROR; - } - - OEMCryptoResult status; - WithOecWriteLock("UpdateUsageInformation", - [&] { status = OEMCrypto_UpdateUsageTable(); }); - metrics_->oemcrypto_update_usage_table_.Increment(status); - if (status != OEMCrypto_SUCCESS) { - LOGE("OEMCrypto_UpdateUsageTable failed: status = %d", - static_cast(status)); - return UNKNOWN_ERROR; - } - return NO_ERROR; -} - CdmResponseType CryptoSession::DeactivateUsageInformation( const std::string& provider_session_token) { LOGV("Deactivating usage information: id = %u", oec_session_id_); @@ -1354,93 +1523,6 @@ CdmResponseType CryptoSession::GenerateUsageReport( return NO_ERROR; } -CdmResponseType CryptoSession::ReleaseUsageInformation( - const std::string& message, const std::string& signature, - const std::string& provider_session_token) { - LOGV("Releasing usage information: id = %u", oec_session_id_); - if (usage_table_header_ != nullptr) { - LOGW("Releasing usage information is deprecated for OEMCrypto v13+"); - return NO_ERROR; - } - - const uint8_t* msg = reinterpret_cast(message.data()); - const uint8_t* sig = reinterpret_cast(signature.data()); - const uint8_t* pst = msg + GetOffset(message, provider_session_token); - - OEMCryptoResult status; - WithOecWriteLock("ReleaseUsageInformation", [&] { - status = OEMCrypto_DeleteUsageEntry( - oec_session_id_, pst, provider_session_token.length(), msg, - message.length(), sig, signature.length()); - }); - metrics_->oemcrypto_delete_usage_entry_.Increment(status); - - if (OEMCrypto_SUCCESS != status) { - LOGE("OEMCrypto_DeleteUsageEntry failed: status = %d", - static_cast(status)); - return UNKNOWN_ERROR; - } - - if (usage_support_type_ == kUsageTableSupport) UpdateUsageInformation(); - return NO_ERROR; -} - -CdmResponseType CryptoSession::DeleteUsageInformation( - const std::string& provider_session_token) { - CdmResponseType response = NO_ERROR; - LOGV("Deleting usage information: id = %u", oec_session_id_); - OEMCryptoResult status; - WithOecWriteLock("DeleteUsageInformation", [&] { - status = OEMCrypto_ForceDeleteUsageEntry( - reinterpret_cast(provider_session_token.c_str()), - provider_session_token.length()); - }); - metrics_->oemcrypto_force_delete_usage_entry_.Increment(status); - if (OEMCrypto_SUCCESS != status) { - LOGE("OEMCrypto_ForceDeleteUsageEntry failed: status = %d", - static_cast(status)); - response = UNKNOWN_ERROR; - } - if (usage_support_type_ == kUsageTableSupport) UpdateUsageInformation(); - return response; -} - -CdmResponseType CryptoSession::DeleteMultipleUsageInformation( - const std::vector& provider_session_tokens) { - LOGV("Deleting multiple usage information: id = %u", oec_session_id_); - CdmResponseType response = NO_ERROR; - WithOecWriteLock("DeleteMultipleUsageInformation", [&] { - for (size_t i = 0; i < provider_session_tokens.size(); ++i) { - OEMCryptoResult status = OEMCrypto_ForceDeleteUsageEntry( - reinterpret_cast(provider_session_tokens[i].c_str()), - provider_session_tokens[i].length()); - metrics_->oemcrypto_force_delete_usage_entry_.Increment(status); - if (OEMCrypto_SUCCESS != status) { - LOGW("OEMCrypto_ForceDeleteUsageEntry failed: index = %zu, status = %d", - i, static_cast(status)); - response = UNKNOWN_ERROR; - } - } - }); - if (usage_support_type_ == kUsageTableSupport) UpdateUsageInformation(); - return response; -} - -CdmResponseType CryptoSession::DeleteAllUsageReports() { - LOGV("Deleting all usage reports"); - OEMCryptoResult status; - WithOecWriteLock("DeleteAllUsageReports", - [&] { status = OEMCrypto_DeleteOldUsageTable(); }); - metrics_->oemcrypto_delete_usage_table_.Increment(status); - if (OEMCrypto_SUCCESS != status) { - LOGE("OEMCrypto_DeleteOldUsageTable failed: status = %d", - static_cast(status)); - } - - if (usage_support_type_ == kUsageTableSupport) UpdateUsageInformation(); - return NO_ERROR; -} - bool CryptoSession::IsAntiRollbackHwPresent() { bool is_present; WithOecReadLock("IsAntiRollbackHwPresent", [&] { @@ -1477,131 +1559,50 @@ bool CryptoSession::SetDestinationBufferType() { return true; } -CdmResponseType CryptoSession::RewrapCertificate( - const std::string& signed_message, const std::string& signature, - const std::string& nonce, const std::string& private_key, - const std::string& iv, const std::string& wrapping_key, - std::string* wrapped_private_key) { - LOGV("Rewrapping certificate: id = %u", oec_session_id_); - - if (pre_provision_token_type_ == kClientTokenKeybox) { - return RewrapDeviceRSAKey(signed_message, signature, nonce, private_key, iv, - wrapped_private_key); - - } else if (pre_provision_token_type_ == kClientTokenOemCert) { - return RewrapDeviceRSAKey30(signed_message, nonce, private_key, iv, - wrapping_key, wrapped_private_key); - - } else { - LOGE("Bad pre-provision type: %d, id = %u", - static_cast(pre_provision_token_type_), oec_session_id_); - return UNKNOWN_CLIENT_TOKEN_TYPE; - } -} - -CdmResponseType CryptoSession::RewrapDeviceRSAKey( - const std::string& message, const std::string& signature, - const std::string& nonce, const std::string& enc_rsa_key, - const std::string& rsa_key_iv, std::string* wrapped_rsa_key) { - LOGV("Rewrapping device RSA key: id = %u", oec_session_id_); - - const uint8_t* signed_msg = reinterpret_cast(message.data()); - const uint8_t* msg_rsa_key = nullptr; - const uint8_t* msg_rsa_key_iv = nullptr; - const uint32_t* msg_nonce = nullptr; - if (enc_rsa_key.size() >= MAC_KEY_SIZE && rsa_key_iv.size() >= KEY_IV_SIZE) { - msg_rsa_key = signed_msg + GetOffset(message, enc_rsa_key); - msg_rsa_key_iv = signed_msg + GetOffset(message, rsa_key_iv); - msg_nonce = reinterpret_cast(signed_msg + - GetOffset(message, nonce)); +CdmResponseType CryptoSession::LoadProvisioning( + const std::string& signed_message, const std::string& core_message, + const std::string& signature, std::string* wrapped_private_key) { + LOGV("Loading provisioning certificate: id = %u", oec_session_id_); + if (wrapped_private_key == nullptr) { + LOGE("Missing wrapped |wrapped_private_key|"); + return PARAMETER_NULL; } - // Gets wrapped_rsa_key_length by passing nullptr as uint8_t* wrapped_rsa_key - // and 0 as wrapped_rsa_key_length. - size_t wrapped_rsa_key_length = 0; - OEMCryptoResult status; - WithOecSessionLock("RewrapDeviceRSAKey Attempt 1", [&] { - M_TIME(status = OEMCrypto_RewrapDeviceRSAKey( - oec_session_id_, signed_msg, message.size(), - reinterpret_cast(signature.data()), - signature.size(), msg_nonce, msg_rsa_key, enc_rsa_key.size(), - msg_rsa_key_iv, nullptr, &wrapped_rsa_key_length), - metrics_, oemcrypto_rewrap_device_rsa_key_, status); - }); - - if (status != OEMCrypto_ERROR_SHORT_BUFFER) { - return MapOEMCryptoResult(status, REWRAP_DEVICE_RSA_KEY_ERROR, - "RewrapDeviceRSAKey"); - } - - wrapped_rsa_key->resize(wrapped_rsa_key_length); - WithOecSessionLock("RewrapDeviceRSAKey Attempt 2", [&] { - M_TIME( - status = OEMCrypto_RewrapDeviceRSAKey( - oec_session_id_, signed_msg, message.size(), - reinterpret_cast(signature.data()), - signature.size(), msg_nonce, msg_rsa_key, enc_rsa_key.size(), - msg_rsa_key_iv, reinterpret_cast(&(*wrapped_rsa_key)[0]), - &wrapped_rsa_key_length), - metrics_, oemcrypto_rewrap_device_rsa_key_, status); - }); - - wrapped_rsa_key->resize(wrapped_rsa_key_length); - - return MapOEMCryptoResult(status, REWRAP_DEVICE_RSA_KEY_ERROR, - "RewrapDeviceRSAKey"); -} - -CdmResponseType CryptoSession::RewrapDeviceRSAKey30( - const std::string& message, const std::string& nonce, - const std::string& private_key, const std::string& iv, - const std::string& wrapping_key, std::string* wrapped_private_key) { - LOGV("Rewrapping Device RSA key 30: id = %u", oec_session_id_); - - const uint8_t* signed_msg = reinterpret_cast(message.data()); - const uint8_t* msg_private_key = nullptr; - const uint8_t* msg_iv = nullptr; - const uint32_t* msg_nonce = nullptr; - const uint8_t* msg_wrapping_key = nullptr; - if (private_key.size() >= MAC_KEY_SIZE && iv.size() >= KEY_IV_SIZE) { - msg_private_key = signed_msg + GetOffset(message, private_key); - msg_iv = signed_msg + GetOffset(message, iv); - msg_nonce = reinterpret_cast(signed_msg + - GetOffset(message, nonce)); - msg_wrapping_key = signed_msg + GetOffset(message, wrapping_key); - } - - // Gets wrapped_rsa_key_length by passing nullptr as uint8_t* wrapped_rsa_key - // and 0 as wrapped_rsa_key_length. + const std::string combined_message = core_message + signed_message; + // Round 1, get the size of the wrapped private key buffer. size_t wrapped_private_key_length = 0; OEMCryptoResult status; - WithOecSessionLock("RewrapDeviceRSAKey30 Attempt 1", [&] { - M_TIME(status = OEMCrypto_RewrapDeviceRSAKey30( - oec_session_id_, msg_nonce, msg_wrapping_key, - wrapping_key.size(), msg_private_key, private_key.size(), msg_iv, - nullptr, &wrapped_private_key_length), - metrics_, oemcrypto_rewrap_device_rsa_key_30_, status); + WithOecSessionLock("LoadProvisioning Attempt 1", [&] { + M_TIME(status = OEMCrypto_LoadProvisioning( + oec_session_id_, + reinterpret_cast(combined_message.data()), + combined_message.size(), core_message.size(), + reinterpret_cast(signature.data()), + signature.size(), nullptr, &wrapped_private_key_length), + metrics_, oemcrypto_load_provisioning_, status); }); if (status != OEMCrypto_ERROR_SHORT_BUFFER) { - return MapOEMCryptoResult(status, REWRAP_DEVICE_RSA_KEY_30_ERROR, - "RewrapDeviceRSAKey30"); + return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR, + "LoadProvisioning"); } wrapped_private_key->resize(wrapped_private_key_length); - WithOecSessionLock("RewrapDeviceRSAKey30 Attempt 2", [&] { - M_TIME(status = OEMCrypto_RewrapDeviceRSAKey30( - oec_session_id_, msg_nonce, msg_wrapping_key, - wrapping_key.size(), msg_private_key, private_key.size(), msg_iv, - reinterpret_cast(&(*wrapped_private_key)[0]), + + WithOecSessionLock("LoadProvisioning Attempt 2", [&] { + M_TIME(status = OEMCrypto_LoadProvisioning( + oec_session_id_, + reinterpret_cast(combined_message.data()), + combined_message.size(), core_message.size(), + reinterpret_cast(signature.data()), + signature.size(), + reinterpret_cast(&wrapped_private_key->front()), &wrapped_private_key_length), - metrics_, oemcrypto_rewrap_device_rsa_key_30_, status); + metrics_, oemcrypto_load_provisioning_, status); }); - wrapped_private_key->resize(wrapped_private_key_length); - - return MapOEMCryptoResult(status, REWRAP_DEVICE_RSA_KEY_30_ERROR, - "RewrapDeviceRSAKey30"); + return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR, + "LoadProvisioning"); } CdmResponseType CryptoSession::GetHdcpCapabilities(HdcpCapability* current, @@ -1775,7 +1776,7 @@ bool CryptoSession::GetResourceRatingTier(SecurityLevel security_level, *tier = OEMCrypto_ResourceRatingTier(security_level); metrics_->oemcrypto_resource_rating_tier_.Record(*tier); }); - if (*tier < RESOURCE_RATING_TIER_LOW || *tier > RESOURCE_RATING_TIER_HIGH) { + if (*tier < RESOURCE_RATING_TIER_MIN || *tier > RESOURCE_RATING_TIER_MAX) { uint32_t api_version; if (GetApiVersion(security_level, &api_version)) { if (api_version >= OEM_CRYPTO_API_VERSION_SUPPORTS_RESOURCE_RATING_TIER) { @@ -1812,6 +1813,25 @@ bool CryptoSession::GetBuildInformation(SecurityLevel security_level, return true; } +bool CryptoSession::GetMaximumUsageTableEntries(SecurityLevel security_level, + size_t* number_of_entries) { + LOGV("Getting maximum usage table entries: security_level = %d", + static_cast(security_level)); + RETURN_IF_UNINITIALIZED(false); + if (number_of_entries == nullptr) { + LOGE("Output parameter |number_of_entries| not provided"); + return false; + } + WithOecReadLock("GetMaxUsageTableEntries", [&] { + *number_of_entries = OEMCrypto_MaximumUsageTableHeaderSize(security_level); + }); + // Record the number of entries into the metrics. + metrics_->oemcrypto_maximum_usage_table_header_size_.Record( + *number_of_entries); + + return *number_of_entries >= kMinimumUsageTableEntriesSupported; +} + bool CryptoSession::GetDecryptHashSupport(SecurityLevel security_level, uint32_t* decrypt_hash_support) { LOGV("Checking if decrypt hash is supported"); @@ -2144,13 +2164,11 @@ CdmResponseType CryptoSession::GetUsageSupportType( if (!has_support) { *usage_support_type = usage_support_type_ = kNonSecureUsageSupport; - return NO_ERROR; + } else { + // As of v16, all supported version of OEMCrypto provide usage entry + // support or no usage info support. + *usage_support_type = usage_support_type_ = kUsageEntrySupport; } - - *usage_support_type = usage_support_type_ = - (api_version_ >= kOemCryptoApiVersionSupportsBigUsageTables) - ? kUsageEntrySupport - : kUsageTableSupport; return NO_ERROR; } @@ -2395,76 +2413,6 @@ CdmResponseType CryptoSession::MoveUsageEntry(uint32_t new_entry_number) { "MoveUsageEntry"); } -bool CryptoSession::CreateOldUsageEntry( - uint64_t time_since_license_received, uint64_t time_since_first_decrypt, - uint64_t time_since_last_decrypt, UsageDurationStatus usage_duration_status, - const std::string& server_mac_key, const std::string& client_mac_key, - const std::string& provider_session_token) { - LOGV("Creating old usage entry"); - - if (server_mac_key.size() < MAC_KEY_SIZE) { - LOGE("Invalid server MAC key size: size = %zu", server_mac_key.size()); - return false; - } - if (client_mac_key.size() < MAC_KEY_SIZE) { - LOGE("Invalid client MAC key size: size = %zu", client_mac_key.size()); - return false; - } - - OEMCrypto_Usage_Entry_Status status = kUnused; - switch (usage_duration_status) { - case kUsageDurationsInvalid: - status = kUnused; - break; - case kUsageDurationPlaybackNotBegun: - status = kInactiveUnused; - break; - case kUsageDurationsValid: - status = kActive; - break; - default: - LOGE("Unrecognized usage duration status: %d", - static_cast(usage_duration_status)); - return false; - } - - OEMCryptoResult result; - WithOecWriteLock("CreateOldUsageEntry", [&] { - result = OEMCrypto_CreateOldUsageEntry( - requested_security_level_, time_since_license_received, - time_since_first_decrypt, time_since_last_decrypt, status, - reinterpret_cast(const_cast(server_mac_key.data())), - reinterpret_cast(const_cast(client_mac_key.data())), - reinterpret_cast(provider_session_token.data()), - provider_session_token.size()); - metrics_->oemcrypto_create_old_usage_entry_.Increment(result); - }); - - if (result != OEMCrypto_SUCCESS) { - LOGE("OEMCrypto_CreateOldUsageEntry failed: status = %d", - static_cast(result)); - return false; - } - return true; -} - -CdmResponseType CryptoSession::CopyOldUsageEntry( - const std::string& provider_session_token) { - LOGV("Copying old usage entry: id = %u", oec_session_id_); - - OEMCryptoResult result; - WithOecWriteLock("CopyOldUsageEntry", [&] { - result = OEMCrypto_CopyOldUsageEntry( - oec_session_id_, - reinterpret_cast(provider_session_token.data()), - provider_session_token.size()); - metrics_->oemcrypto_copy_old_usage_entry_.Increment(result); - }); - - return MapOEMCryptoResult(result, COPY_OLD_USAGE_ENTRY_UNKNOWN_ERROR, - "CopyOldUsageEntry"); -} - bool CryptoSession::GetAnalogOutputCapabilities(bool* can_support_output, bool* can_disable_output, bool* can_support_cgms_a) { @@ -2534,7 +2482,7 @@ OEMCryptoResult CryptoSession::CopyBufferInChunks( switch (buffer_descriptor.type) { case OEMCrypto_BufferType_Clear: buffer_descriptor.buffer.clear.address += additional_offset; - buffer_descriptor.buffer.clear.max_length -= additional_offset; + buffer_descriptor.buffer.clear.address_length -= additional_offset; break; case OEMCrypto_BufferType_Secure: buffer_descriptor.buffer.secure.offset += additional_offset; @@ -2603,7 +2551,7 @@ OEMCryptoResult CryptoSession::DecryptInChunks( switch (buffer_descriptor.type) { case OEMCrypto_BufferType_Clear: buffer_descriptor.buffer.clear.address += additional_offset; - buffer_descriptor.buffer.clear.max_length -= additional_offset; + buffer_descriptor.buffer.clear.address_length -= additional_offset; break; case OEMCrypto_BufferType_Secure: buffer_descriptor.buffer.secure.offset += additional_offset; @@ -2624,16 +2572,13 @@ OEMCryptoResult CryptoSession::DecryptInChunks( // pattern length long, which is also guaranteed to be an exact number // of AES blocks long. OEMCryptoResult sts; + sts = OEMCrypto_ERROR_NOT_IMPLEMENTED; WithOecSessionLock("DecryptInChunks", [&] { - M_TIME( - sts = OEMCrypto_DecryptCENC( - oec_session_id_, params.encrypt_buffer + additional_offset, - chunk_size, params.is_encrypted, &iv.front(), params.block_offset, - &buffer_descriptor, &pattern_descriptor, subsample_flags), - metrics_, oemcrypto_decrypt_cenc_, sts, - metrics::Pow2Bucket(chunk_size)); + M_TIME(sts = key_session_->Decrypt(params, buffer_descriptor, + additional_offset, pattern_descriptor), + metrics_, oemcrypto_decrypt_cenc_, sts, + metrics::Pow2Bucket(chunk_size)); }); - if (sts != OEMCrypto_SUCCESS) { return sts; } diff --git a/libwvdrmengine/cdm/core/src/initialization_data.cpp b/libwvdrmengine/cdm/core/src/initialization_data.cpp index 31db68e7..cfcc28f5 100644 --- a/libwvdrmengine/cdm/core/src/initialization_data.cpp +++ b/libwvdrmengine/cdm/core/src/initialization_data.cpp @@ -89,6 +89,11 @@ InitializationData::InitializationData(const std::string& type, } } +const std::string InitializationData::WidevineSystemID() { + return std::string(reinterpret_cast(kWidevineSystemId), + sizeof(kWidevineSystemId)); +} + std::vector InitializationData::ExtractWrappedKeys() const { std::vector keys; @@ -671,4 +676,64 @@ bool InitializationData::DetectEntitlementPreference( return std::stoul(oec_version_string) >= 14; } +void InitializationData::DumpToLogs() const { + if (!is_supported()) { + LOGD("InitData: Not supported"); + } + if (!IsEmpty()) { + LOGD("InitData: Empty"); + } + std::string type_info = type(); + if (is_cenc()) type_info += " (cenc)"; + if (is_hls()) type_info += " (hls)"; + if (is_webm()) type_info += " (webm)"; + if (is_audio()) type_info += " (audio)"; + LOGD("InitData: type = %s", type_info.c_str()); + + video_widevine::WidevinePsshData pssh; + if (!pssh.ParseFromString(data())) { + LOGD("InitData: invalid pssh: %s", b2a_hex(data()).c_str()); + return; + } + if (pssh.has_content_id()) { + LOGD("InitData: content_id = '%s'", pssh.content_id().c_str()); + } + if (pssh.has_protection_scheme()) { + uint32_t scheme = pssh.protection_scheme(); + LOGD("InitData: Protection Scheme: %c%c%c%c", (scheme >> 24) & 0xFF, + (scheme >> 16) & 0xFF, (scheme >> 8) & 0xFF, (scheme >> 0) & 0xFF); + } + switch (pssh.type()) { + case video_widevine::WidevinePsshData_Type_SINGLE: + // Don't bother printing. + break; + case video_widevine::WidevinePsshData_Type_ENTITLEMENT: + LOGD("InitData: Entitlement License"); + break; + case video_widevine::WidevinePsshData_Type_ENTITLED_KEY: + LOGD("InitData: Entitled Key"); + break; + default: + LOGE("Undefine pssh type: %d", pssh.type()); + break; + } + if (pssh.has_crypto_period_index()) + LOGD("InitData: Crypto Period Index %u", pssh.crypto_period_index()); + if (pssh.has_crypto_period_seconds()) + LOGD("InitData: Crypto Period seconds %u", pssh.crypto_period_seconds()); + if (pssh.has_key_sequence()) + LOGD("InitData: Key Sequence %u", pssh.key_sequence()); + + for (int i = 0; i < pssh.key_ids_size(); i++) { + LOGD("InitData: key_id %d: %s", i, b2a_hex(pssh.key_ids(i)).c_str()); + } + + for (int i = 0; i < pssh.entitled_keys_size(); i++) { + video_widevine::WidevinePsshData_EntitledKey key = pssh.entitled_keys(i); + LOGD("InitData: entitlement_key_id %d: %s -> %s", i, + b2a_hex(key.entitlement_key_id()).c_str(), + b2a_hex(key.key_id()).c_str()); + } +} + } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/src/license.cpp b/libwvdrmengine/cdm/core/src/license.cpp index 98ac86d0..6dbc3e82 100644 --- a/libwvdrmengine/cdm/core/src/license.cpp +++ b/libwvdrmengine/cdm/core/src/license.cpp @@ -53,7 +53,8 @@ using video_widevine::LicenseRequest_ContentIdentification_WebmDeprecated; using video_widevine::SignedDrmDeviceCertificate; using video_widevine::SignedMessage; -static std::vector ExtractEntitlementKeys(const License& license) { +namespace { +std::vector ExtractEntitlementKeys(const License& license) { std::vector key_array; for (int i = 0; i < license.key_size(); ++i) { @@ -108,7 +109,7 @@ static std::vector ExtractEntitlementKeys(const License& license) { return key_array; } -static std::vector ExtractContentKeys(const License& license) { +std::vector ExtractContentKeys(const License& license) { std::vector key_array; // Extract content key(s) @@ -174,6 +175,7 @@ static std::vector ExtractContentKeys(const License& license) { return key_array; } +} // namespace CdmLicense::CdmLicense(const CdmSessionId& session_id) : crypto_session_(nullptr), @@ -182,6 +184,7 @@ CdmLicense::CdmLicense(const CdmSessionId& session_id) initialized_(false), renew_with_client_id_(false), is_offline_(false), + supports_core_messages_(true), use_privacy_mode_(false), clock_(new Clock()), license_key_type_(kLicenseKeyTypeContent) {} @@ -193,6 +196,7 @@ CdmLicense::CdmLicense(const CdmSessionId& session_id, Clock* clock) initialized_(false), renew_with_client_id_(false), is_offline_(false), + supports_core_messages_(true), use_privacy_mode_(false), license_key_type_(kLicenseKeyTypeContent) { clock_.reset(clock); @@ -336,10 +340,12 @@ CdmResponseType CdmLicense::PrepareKeyRequest( key_request_ = serialized_license_req; - // Derive signing and encryption keys and construct signature. + // Derive signing and encryption keys and construct core message and + // signature. + std::string core_message; std::string license_request_signature; - status = crypto_session_->PrepareRequest(serialized_license_req, false, - &license_request_signature); + status = crypto_session_->PrepareAndSignLicenseRequest( + serialized_license_req, &core_message, &license_request_signature); if (status != NO_ERROR) { signed_request->clear(); @@ -357,6 +363,7 @@ CdmResponseType CdmLicense::PrepareKeyRequest( signed_message.set_type(SignedMessage::LICENSE_REQUEST); signed_message.set_signature(license_request_signature); signed_message.set_msg(serialized_license_req); + signed_message.set_oemcrypto_core_message(core_message); signed_message.SerializeToString(signed_request); @@ -474,10 +481,11 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest( std::string serialized_license_req; license_request.SerializeToString(&serialized_license_req); - // Construct signature. + // Construct signature and core message. + std::string core_message; std::string license_request_signature; - status = crypto_session_->PrepareRenewalRequest(serialized_license_req, - &license_request_signature); + status = crypto_session_->PrepareAndSignRenewalRequest( + serialized_license_req, &core_message, &license_request_signature); if (status != NO_ERROR) return status; if (license_request_signature.empty()) { @@ -490,6 +498,11 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest( signed_message.set_type(SignedMessage::LICENSE_REQUEST); signed_message.set_signature(license_request_signature); signed_message.set_msg(serialized_license_req); + if (supports_core_messages()) { + // Only include the |core_message| in renewal requests if it is + // already known that the license is v16. + signed_message.set_oemcrypto_core_message(core_message); + } signed_message.SerializeToString(signed_request); *server_url = server_url_; @@ -543,8 +556,23 @@ CdmResponseType CdmLicense::HandleKeyResponse( return LICENSE_RESPONSE_NOT_SIGNED; } + // Check that the server returned a |core_message|. If missing, then + // the server is assumed to operate as V15. This will imply that the + // |signature| field in the respones does not include a core message + // either. + if (!signed_response.has_oemcrypto_core_message()) { + supports_core_messages_ = false; + } + + const std::string& signed_message = signed_response.msg(); + const std::string core_message = + signed_response.has_oemcrypto_core_message() + ? signed_response.oemcrypto_core_message() + : std::string(); + const std::string& signature = signed_response.signature(); + License license; - if (!license.ParseFromString(signed_response.msg())) { + if (!license.ParseFromString(signed_message)) { LOGE("Unable to parse license response"); return LICENSE_RESPONSE_PARSE_ERROR_1; } @@ -581,6 +609,11 @@ CdmResponseType CdmLicense::HandleKeyResponse( } } + // A license may contain several types of keys (content, entitlement, + // signing, key control and operator sessions); however, it should not + // contain both entitlement keys and content keys. To determine the + // overall type of the license, we check for the existence of either + // type of keys. If both are present, we default to entitlement keys. CdmLicenseKeyType key_type = kLicenseKeyTypeEntitlement; std::vector key_array = ExtractEntitlementKeys(license); if (key_array.empty()) { @@ -629,13 +662,12 @@ CdmResponseType CdmLicense::HandleKeyResponse( CdmResponseType resp = NO_CONTENT_KEY; if (kLicenseKeyTypeEntitlement == key_type) { - resp = HandleEntitlementKeyResponse(signed_response.msg(), - signed_response.signature(), mac_key_iv, - mac_keys, key_array, license); + resp = + HandleEntitlementKeyResponse(signed_message, core_message, signature, + mac_key_iv, mac_keys, key_array, license); } else if (kLicenseKeyTypeContent == key_type) { - resp = HandleContentKeyResponse(signed_response.msg(), - signed_response.signature(), mac_key_iv, - mac_keys, key_array, license); + resp = HandleContentKeyResponse(signed_message, core_message, signature, + mac_key_iv, mac_keys, key_array, license); } return resp; } @@ -668,13 +700,29 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( return INVALID_LICENSE_TYPE; } + // At this point of the license life-cycle (handling a renewal or + // release), we should already know if the license is v15 or not. + // If license is v16, then there should be a |core_message| + // present; otherwise there might have beeen some tampering with the + // request or response. + if (supports_core_messages() && + !signed_response.has_oemcrypto_core_message()) { + LOGE("Renewal response is missing |core_message| field"); + return CORE_MESSAGE_NOT_FOUND; + } + if (!signed_response.has_signature()) { LOGE("Update key response is missing signature"); return SIGNATURE_NOT_FOUND; } + const std::string& signed_message = signed_response.msg(); + const std::string core_message = + supports_core_messages() ? signed_response.oemcrypto_core_message() + : std::string(); + const std::string& signature = signed_response.signature(); License license; - if (!license.ParseFromString(signed_response.msg())) { + if (!license.ParseFromString(signed_message)) { LOGE("Unable to parse license from signed message"); return LICENSE_RESPONSE_PARSE_ERROR_3; } @@ -690,12 +738,8 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( if (!is_renewal) { if (!license.id().has_provider_session_token()) return KEY_ADDED; - provider_session_token_ = license.id().provider_session_token(); - CdmResponseType status = crypto_session_->ReleaseUsageInformation( - signed_response.msg(), signed_response.signature(), - provider_session_token_); - return (NO_ERROR == status) ? KEY_ADDED : status; + return KEY_ADDED; } if (license.policy().has_renewal_server_url() && @@ -703,11 +747,14 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( server_url_ = license.policy().renewal_server_url(); } - std::vector key_array = ExtractContentKeys(license); - - CdmResponseType status = crypto_session_->RefreshKeys( - signed_response.msg(), signed_response.signature(), key_array.size(), - &key_array[0]); + CdmResponseType status; + if (supports_core_messages()) { + status = + crypto_session_->LoadRenewal(signed_message, core_message, signature); + } else { + std::vector key_array = ExtractContentKeys(license); + status = crypto_session_->RefreshKeys(signed_message, signature, key_array); + } if (status == KEY_ADDED) { policy_engine_->UpdateLicense(license); @@ -750,6 +797,12 @@ CdmResponseType CdmLicense::RestoreOfflineLicense( return INVALID_LICENSE_REQUEST_TYPE_1; } + if (!signed_request.has_oemcrypto_core_message()) { + // Pre V16 license did not include |core_message| components. + // The license response is checked by HandleKeyResponse(). + supports_core_messages_ = false; + } + key_request_ = signed_request.msg(); CdmResponseType sts = HandleKeyResponse(license_response); @@ -827,6 +880,11 @@ CdmResponseType CdmLicense::RestoreLicenseForRelease( return INVALID_LICENSE_REQUEST_TYPE_2; } + if (!signed_request.has_oemcrypto_core_message()) { + // Pre V16 license did not include |core_message| components. + supports_core_messages_ = false; + } + key_request_ = signed_request.msg(); SignedMessage signed_response; @@ -847,6 +905,13 @@ CdmResponseType CdmLicense::RestoreLicenseForRelease( return SIGNATURE_NOT_FOUND_2; } + if (!signed_response.has_oemcrypto_core_message()) { + // Possible that the request contains a |core_message|, but the + // response does not. This would occur if the licensing server + // is v15. + supports_core_messages_ = false; + } + License license; if (!license.ParseFromString(signed_response.msg())) { LOGE("Failed to parse license response"); @@ -1016,17 +1081,22 @@ CdmResponseType CdmLicense::PrepareContentId( } CdmResponseType CdmLicense::HandleContentKeyResponse( - const std::string& msg, const std::string& signature, - const std::string& mac_key_iv, const std::string& mac_key, - const std::vector& key_array, + const std::string& msg, const std::string& core_message, + const std::string& signature, const std::string& mac_key_iv, + const std::string& mac_key, const std::vector& key_array, const video_widevine::License& license) { if (key_array.empty()) { LOGE("No content keys provided"); return NO_CONTENT_KEY; } - CdmResponseType resp = crypto_session_->LoadKeys( - msg, signature, mac_key_iv, mac_key, key_array, provider_session_token_, - license.srm_requirement(), kLicenseKeyTypeContent); + CdmResponseType resp; + if (supports_core_messages()) { + resp = crypto_session_->LoadLicense(msg, core_message, signature); + } else { + resp = crypto_session_->LoadKeys( + msg, signature, mac_key_iv, mac_key, key_array, provider_session_token_, + license.srm_requirement(), kLicenseKeyTypeContent); + } if (KEY_ADDED == resp) { loaded_keys_.clear(); @@ -1040,17 +1110,23 @@ CdmResponseType CdmLicense::HandleContentKeyResponse( } CdmResponseType CdmLicense::HandleEntitlementKeyResponse( - const std::string& msg, const std::string& signature, - const std::string& mac_key_iv, const std::string& mac_key, - const std::vector& key_array, + const std::string& msg, const std::string& core_message, + const std::string& signature, const std::string& mac_key_iv, + const std::string& mac_key, const std::vector& key_array, const video_widevine::License& license) { if (key_array.empty()) { LOGE("No entitlement keys provided"); return NO_CONTENT_KEY; } - CdmResponseType resp = crypto_session_->LoadKeys( - msg, signature, mac_key_iv, mac_key, key_array, provider_session_token_, - license.srm_requirement(), kLicenseKeyTypeEntitlement); + CdmResponseType resp; + if (supports_core_messages()) { + resp = crypto_session_->LoadLicense(msg, core_message, signature); + } else { + resp = crypto_session_->LoadKeys( + msg, signature, mac_key_iv, mac_key, key_array, provider_session_token_, + license.srm_requirement(), kLicenseKeyTypeEntitlement); + } + if (KEY_ADDED != resp) { return resp; } diff --git a/libwvdrmengine/cdm/core/src/license_protocol.proto b/libwvdrmengine/cdm/core/src/license_protocol.proto index a9b0c719..d932e7bb 100644 --- a/libwvdrmengine/cdm/core/src/license_protocol.proto +++ b/libwvdrmengine/cdm/core/src/license_protocol.proto @@ -4,10 +4,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine Master // License Agreement. -// -// Description: -// Definitions of the protocol buffer messages used in the Widevine license -// exchange protocol. syntax = "proto2"; @@ -18,6 +14,31 @@ option optimize_for = LITE_RUNTIME; option java_package = "com.google.video.widevine.protos"; +// ---------------------------------------------------------------------------- +// remote_attestation.proto +// ---------------------------------------------------------------------------- +// Description of section: +// Remote attestation is used by ChromeOS device to authenticate itself +// to Widevine services for both licensing and keybox provisioning. + +message RemoteAttestation { + // Encrypted ClientIdentification message containing the device remote + // attestation certificate. Required. + optional EncryptedClientIdentification certificate = 1; + // Bytes of salt which were added to the remote attestation challenge prior to + // signing it. Required. + optional bytes salt = 2; + // Signed remote attestation challenge + salt. Required. + optional bytes signature = 3; +} + +// ---------------------------------------------------------------------------- +// license_protocol.proto +// ---------------------------------------------------------------------------- +// Description of section: +// Definitions of the protocol buffer messages used in the Widevine license +// exchange protocol, described in Widevine license exchange protocol document + enum LicenseType { STREAMING = 1; OFFLINE = 2; @@ -190,6 +211,8 @@ message License { optional HdcpSrmRule hdcp_srm_rule = 3 [default = HDCP_SRM_RULE_NONE]; // Optional requirement to indicate analog output is not allowed. optional bool disable_analog_output = 4 [default = false]; + // Optional requirement to indicate digital output is not allowed. + optional bool disable_digital_output = 5 [default = false]; } message VideoResolutionConstraint { @@ -274,6 +297,7 @@ message License { enum ProtocolVersion { VERSION_2_0 = 20; VERSION_2_1 = 21; + VERSION_2_2 = 22; } message LicenseRequest { @@ -379,21 +403,12 @@ message MetricData { repeated TypeValue metric_data = 2; } -message RemoteAttestation { - // Encrypted ClientIdentification message containing the device remote - // attestation certificate. Required. - optional EncryptedClientIdentification certificate = 1; - // Bytes of salt which were added to the remote attestation challenge prior to - // signing it. Required. - optional bytes salt = 2; - // Signed remote attestation challenge + salt. Required. - optional bytes signature = 3; -} - message VersionInfo { - // License SDK version reported by the Widevine License SDK. + // License SDK version reported by the Widevine License SDK. This field + // is populated automatically by the SDK. optional string license_sdk_version = 1; - // Version of the service hosting the license SDK. + // Version of the service hosting the license SDK. This field is optional. + // It may be provided by the hosting service. optional string license_service_version = 2; } @@ -407,11 +422,29 @@ message SignedMessage { SUB_LICENSE = 6; CAS_LICENSE_REQUEST = 7; CAS_LICENSE = 8; + EXTERNAL_LICENSE_REQUEST = 9; + EXTERNAL_LICENSE = 10; + } + + enum SessionKeyType { + UNDEFINED = 0; + WRAPPED_AES_KEY = 1; + EPHERMERAL_ECC_PUBLIC_KEY = 2; } optional MessageType type = 1; optional bytes msg = 2; + // Required field that contains the signature of the bytes of msg. + // For license requests, the signing algorithm is determined by the + // certificate contained in the request. + // For license responses, the signing algorithm is HMAC with signing key based + // on |session_key|. optional bytes signature = 3; + // If populated, the contents of this field will be signaled by the + // |session_key_type| type. If the |session_key_type| is WRAPPED_AES_KEY the + // key is the bytes of an encrypted AES key. If the |session_key_type| is + // EPHERMERAL_ECC_PUBLIC_KEY the field contains the bytes of an RFC5208 ASN1 + // serialized ECC public key. optional bytes session_key = 4; // Remote attestation data which will be present in the initial license // request for ChromeOS client devices operating in verified mode. Remote @@ -422,41 +455,18 @@ message SignedMessage { // Version information from the SDK and license service. This information is // provided in the license response. optional VersionInfo service_version_info = 7; -} -message GroupKeys { - enum GroupLicenseVersion { - GROUP_LICENSE_VERSION_1 = 0; - GROUP_LICENSE_VERSION_2 = 1; - } - - message GroupKeyData { - // Required track type. This indicates the track type to which this key - // belongs. - optional string track_type = 1; - // A required signed message. The message body contains a serialized group - // msg. - optional bytes key = 2; - } - - // Optional key container array used in group licensing V1. This is not used - // in V2. - repeated License.KeyContainer key = 1 [deprecated = true]; - - // Byte string that identifies the group to which this license material - // belongs. - optional bytes group_id = 2; - - // Required version id beginning with version 2. If not present version 1 - // should be assumed. - optional GroupLicenseVersion version = 3 [default = GROUP_LICENSE_VERSION_1]; - // Optional key container array for group licensing V2. - repeated GroupKeyData key_data = 4; + // Optional field that contains the algorithm type used to generate the + // session_key and signature in a LICENSE message. + optional SessionKeyType session_key_type = 8 [default = WRAPPED_AES_KEY]; + // The core message is the simple serialization of fields used by OEMCrypto. + // This field was introduced in OEMCrypto API v16. + optional bytes oemcrypto_core_message = 9; } // ---------------------------------------------------------------------------- // certificate_provisioning.proto // ---------------------------------------------------------------------------- -// Description: +// Description of section: // Public protocol buffer definitions for Widevine Device Certificate // Provisioning protocol. @@ -466,6 +476,7 @@ message ProvisioningOptions { enum CertificateType { WIDEVINE_DRM = 0; // Default. The original certificate type. X509 = 1; // X.509 certificate. + WIDEVINE_KEYBOX = 2; } optional CertificateType certificate_type = 1 [default = WIDEVINE_DRM]; @@ -474,36 +485,69 @@ message ProvisioningOptions { // authority for signing the generated certificate. This is required if the // certificate type is X509. optional string certificate_authority = 2; + // System ID for OTA keybox provisioning. Requires device secure boot. + optional uint32 system_id = 3; } // Provisioning request sent by client devices to provisioning service. message ProvisioningRequest { - //oneof clear_or_encrypted_client_id { + message EncryptedSessionKeys { + message SessionKeys { + // 16 bytes encryption key generated by client, used by the server to: + // (1) AES-128-CBC decrypt encrypted_client_id in + // EncryptedClientIdentification which is in RemoteAttestation + // (2) AES-128-CBC encrypt device_key to be returned in + // ProvisioningResponse. + optional bytes encryption_key = 1; + // 32 bytes mac key generated by client, used by server to sign + // the ProvisioningResponse. + optional bytes mac_key = 2; + } + // Serial number of certificate which was used to encrypt the session keys. + // Required. + optional bytes certificate_serial_number = 1; + // Serialized, encrypted session keys. Required. + optional bytes encrypted_session_keys = 2; + } + oneof clear_or_encrypted_client_id { // Device root of trust and other client identification. Required. - optional ClientIdentification client_id = 1; - optional EncryptedClientIdentification encrypted_client_id = 5; - //} + ClientIdentification client_id = 1; + EncryptedClientIdentification encrypted_client_id = 5; + } // Nonce value used to prevent replay attacks. Required. optional bytes nonce = 2; // Options for type of certificate to generate. Optional. optional ProvisioningOptions options = 3; - //oneof spoid_param { + oneof spoid_param { // Stable identifier, unique for each device + application (or origin). // To be deprecated. - optional bytes stable_id = 4; + bytes stable_id = 4; // Service provider ID from the service certificate's provider_id field. // Preferred parameter. - optional bytes provider_id = 6; + bytes provider_id = 6; // Client-generated stable per-origin identifier to be copied directly - // to the client certificater serial number. - optional bytes spoid = 7; - //} + // to the client certificate serial number. + bytes spoid = 7; + } + // SessionKeys encrypted using a service cert public key. + // Required for keybox provisioning. + optional EncryptedSessionKeys encrypted_session_keys = 8; } // Provisioning response sent by the provisioning server to client devices. // This message is used for both regular Widevine DRM certificates and for // application-specific X.509 certificates. message ProvisioningResponse { + message OtaKeybox { + // Iv used along with SessionKeys.encryption_key for encrypting device key. + optional bytes device_key_encryption_iv = 1; + // Device key component of the keybox, encrypted using the + // SessionKeys.encryption_key in the request and |device_key_encryption_iv| + // above. + optional bytes encrypted_device_key = 2; + // Device CA token component of the keybox. + optional bytes device_ca_token = 3; + } // AES-128 encrypted device private RSA key. PKCS#1 ASN.1 DER-encoded. // Required. For X.509 certificates, the private RSA key may also include // a prefix as specified by private_key_prefix in the X509CertificateMetadata @@ -512,7 +556,7 @@ message ProvisioningResponse { // Initialization vector used to encrypt device_rsa_key. Required. optional bytes device_rsa_key_iv = 2; // For Widevine DRM certificates, this contains the serialized - // SignedDrmDeviceCertificate. For X.509 certificates, this contains the PEM + // SignedDrmCertificate. For X.509 certificates, this contains the PEM // encoded X.509 certificate. Required. optional bytes device_certificate = 3; // Nonce value matching nonce in ProvisioningRequest. Required. @@ -521,28 +565,76 @@ message ProvisioningResponse { // provisioned device. Encrypted with the device OEM public key using // RSA-OAEP. optional bytes wrapping_key = 5; + // Only populated in OTA keybox provisioning response. + optional OtaKeybox ota_keybox = 6; +} + +// Protocol-specific context data used to hold the state of the server in +// stateful provisioning protocols. For more information, please refer to +// "Widevine DRM Provisioning using Third-Part and Stateful Protocols". +message ProvisioningContext { + // Serialized ProvisioningContextKeyData. Required. + optional bytes key_data = 1; + // Protocol-dependent context data, encrypted with key and IV in key_data. + // Required. + optional bytes context_data = 2; +} + +message SignedProvisioningContext { + // ProvisioningContext in bytes. + optional bytes provisioning_context = 1; + // RSASSA-PSS signature of provisioning_context. Signed with service private + // key. + optional bytes signature = 2; +} + +// Cryptographic tokens to be used for ProvisioningContext. +message ProvisioningContextKeyData { + // Encryption key, usually 32 bytes used for AES-256-CBC. Required. + optional bytes encryption_key = 1; + // Encryption IV, 16 bytes. Required. + optional bytes encryption_iv = 2; } // Serialized ProvisioningRequest or ProvisioningResponse signed with // The message authentication key. message SignedProvisioningMessage { enum ProtocolVersion { - VERSION_2 = 2; // Keybox factory-provisioned devices. - VERSION_3 = 3; // OEM certificate factory-provisioned devices. + SERVICE_CERTIFICATE_REQUEST = 1; // Service certificate request. + PROVISIONING_20 = 2; // Keybox factory-provisioned devices. + PROVISIONING_30 = 3; // OEM certificate factory-provisioned devices. + ARCPP_PROVISIONING = 4; // ChromeOS/Arc++ devices. + INTEL_SIGMA_101 = 101; // Intel Sigma 1.0.1 protocol. } - // Serialized ProvisioningRequest or ProvisioningResponse. Required. + // Serialized protobuf message for the corresponding protocol and stage of + // the provisioning exchange. ProvisioningRequest or ProvisioningResponse + // in the case of Provisioning 2.0, 3.0 and ARCPP_PROVISIONING. Required. optional bytes message = 1; - // HMAC-SHA256 (Keybox) or RSASSA-PSS (OEM) signature of message. Required. + // HMAC-SHA256 (Keybox) or RSASSA-PSS (OEM) signature of message. Required + // for provisioning 2.0 and 3.0. For ARCPP_PROVISIONING, only used in + // response. optional bytes signature = 2; // Version number of provisioning protocol. - optional ProtocolVersion protocol_version = 3 [default = VERSION_2]; + optional ProtocolVersion protocol_version = 3 [default = PROVISIONING_20]; + // Protocol-specific context / state information for multiple-exchange, + // stateful provisioing protocols. Optional. + optional SignedProvisioningContext signed_provisioning_context = 4; + // Remote attestation data to authenticate that the ChromeOS client device + // is operating in verified mode. Remote attestation challenge data is + // |message| field above. Required for ARCPP_PROVISIONING request. + // It contains signature of |message|. + optional RemoteAttestation remote_attestation = 5; + // The core message is the simple serialization of fields used by OEMCrypto. + // This field was introduced in OEMCrypto API v16. The core message format is + // documented in the "Widevine Core Message Serialization". + optional bytes oemcrypto_core_message = 6; } // ---------------------------------------------------------------------------- // client_identification.proto // ---------------------------------------------------------------------------- -// Description: +// Description of section: // ClientIdentification messages used by provisioning and license protocols. // ClientIdentification message used to authenticate the client device. @@ -575,6 +667,9 @@ message ClientIdentification { enum CertificateKeyType { RSA_2048 = 0; RSA_3072 = 1; + ECC_SECP256R1 = 2; + ECC_SECP384R1 = 3; + ECC_SECP521R1 = 4; } enum AnalogOutputCapabilities { @@ -612,6 +707,11 @@ message ClientIdentification { optional uint32 resource_rating_tier = 12 [default = 0]; } + message ClientCredentials { + optional TokenType type = 1 [default = KEYBOX]; + optional bytes token = 2; + } + // Type of factory-provisioned device root of trust. Optional. optional TokenType type = 1 [default = KEYBOX]; // Factory-provisioned device root of trust. Required. @@ -627,6 +727,8 @@ message ClientIdentification { optional ClientCapabilities client_capabilities = 6; // Serialized VmpData message. Optional. optional bytes vmp_data = 7; + // Optional field that may contain additional provisioning credentials. + repeated ClientCredentials device_credentials = 8; } // EncryptedClientIdentification message used to hold ClientIdentification @@ -650,7 +752,7 @@ message EncryptedClientIdentification { // ---------------------------------------------------------------------------- // device_certificate.proto // ---------------------------------------------------------------------------- -// Description: +// Description of section: // Device certificate and certificate status list format definitions. // DRM certificate definition for user devices, intermediate, service, and root @@ -685,8 +787,18 @@ message DrmDeviceCertificate { optional string provider_id = 7; } -// Contains DRM and OEM certificate status and device information for a -// specific system ID. +// DeviceCertificate signed with intermediate or root certificate private key. +message SignedDrmDeviceCertificate { + // Serialized certificate. Required. + optional bytes drm_certificate = 1; + // Signature of certificate. Signed with root or intermediate + // certificate specified below. Required. + optional bytes signature = 2; + // SignedDrmDeviceCertificate used to sign this certificate. + optional SignedDrmDeviceCertificate signer = 3; +} + +// Contains the status of the root or an intermediate DeviceCertificate. message DeviceCertificateStatus { enum Status { VALID = 0; @@ -701,9 +813,6 @@ message DeviceCertificateStatus { // Device model information about the device to which the intermediate // certificate(s) correspond. optional ProvisionedDeviceInfo device_info = 4; - // Serial number of the OEM X.509 intermediate certificate for this type - // of device. Present only if the device is OEM-provisioned. - optional bytes oem_serial_number = 5; } // List of DeviceCertificateStatus. Used to propagate certificate revocation @@ -727,7 +836,7 @@ message SignedCertificateStatusList { // ---------------------------------------------------------------------------- // provisioned_device_info.proto // ---------------------------------------------------------------------------- -// Description: +// Description of section: // Provisioned device info format definitions. // Contains device model information for a provisioned device. @@ -740,52 +849,71 @@ message ProvisionedDeviceInfo { LEVEL_3 = 3; } + // Widevine initial provisioning / bootstrapping method. DRM certificates are + // required for retrieving licenses, so if a DRM certificate is not initially + // provisioned, then the provisioned credentials will be used to provision + // a DRM certificate via the Widevine Provisioning Service. + enum ProvisioningMethod { + // Don't use this. + PROVISIONING_METHOD_UNSPECIFIED = 0; + // Factory-provisioned device-unique keybox. + FACTORY_KEYBOX = 1; + // Factory-provisioned device-unique OEM certificate. + FACTORY_OEM_DEVICE_CERTIFICATE = 2; + // Factory-provisioned model-group OEM certificate. + FACTORY_OEM_GROUP_CERTIFICATE = 3; + // Factory-provisioned model-group DRM certificate (Level-3 "baked in"). + FACTORY_DRM_GROUP_CERTIFICATE = 4; + // OTA-provisioned keybox (Level-1 ARC++). + OTA_KEYBOX = 5; + // OTA-provisioned device-unique OEM certificate. + OTA_OEM_DEVICE_CERTIFICATE = 6; + // OTA-provisioned model-group OEM certificate. + OTA_OEM_GROUP_CERTIFICATE = 7; + // OTA-provisioned device-unique DRM certificate (Bedrock). + OTA_DRM_DEVICE_CERTIFICATE = 8; + } + // Represents additional devices that are associated with the device. These + // are devices that have the systemID, but a different 'manufacturer'/'model' + // etc. + message ModelInfo { + // Represents the device manufacturer. Typically, this will be Philips, LG, + // Sharp, etc. + optional string manufacturer = 1; + // Model of the device. + optional string model = 2; + } // Widevine system ID for the device. Mandatory. optional uint32 system_id = 1; // Name of system-on-a-chip. Optional. optional string soc = 2; - // Name of manufacturer. Optional. + // First registered manufacturer. Optional. optional string manufacturer = 3; - // Manufacturer's model name. Matches "brand" in device metadata. Optional. + // First registered manufacturer's model name. Matches "brand" in device + // metadata. Optional. optional string model = 4; - // Type of device (Phone, Tablet, TV, etc). + // First registered type of device (Phone, Tablet, TV, etc). optional string device_type = 5; - // Device model year. Optional. + // First registered device model year. Optional. optional uint32 model_year = 6; // Widevine-defined security level. Optional. optional WvSecurityLevel security_level = 7 [default = LEVEL_UNSPECIFIED]; // True if the certificate corresponds to a test (non production) device. // Optional. optional bool test_device = 8 [default = false]; + // Indicates the type of device root of trust which was factory provisioned. + optional ProvisioningMethod provisioning_method = 9; + // A list of ModelInfo using the same system_id. + repeated ModelInfo model_info = 10; } // ---------------------------------------------------------------------------- // widevine_pssh.proto // ---------------------------------------------------------------------------- -// Description: +// Description of section: // Public protocol buffer definitions for Widevine Cenc Header // protocol. -// Each SubLicense message represents a single content key. These keys can be -// added to Widevine CENC initialization data to support both content grouping -// and key rotation. -message SubLicense { - // Required. The key_id of a SUB_SESSION_KEY received in the master license. - // SUB_SESSION_KEY is defined in the Widevine License Protocol. - optional string sub_session_key_id = 1; - - // Required. The key_msg contains the bytes of a serialized SignedMessage - // proto. Internally the message field will contain a serialized KeyContainer - // holding a single content key. - optional bytes key_msg = 2; - - // TODO(jfore): There is some uncertainty about including the current group in - // a license. This may change. - // Byte string that identifies the group to which this this content - // belongs. - optional bytes group_id = 13; -} - message WidevinePsshData { enum Type { SINGLE = 0; // Single PSSH to be used to retrieve content keys. @@ -802,6 +930,8 @@ message WidevinePsshData { optional bytes key = 3; // IV used for wrapping |key|. Required. optional bytes iv = 4; + // Size of entitlement key used for wrapping |key|. + optional uint32 entitlement_key_size_bytes = 5 [default = 32]; } // Entitlement or content key IDs. Can onnly present in SINGLE or ENTITLEMENT @@ -856,6 +986,12 @@ message WidevinePsshData { // PSSHs of type ENTITLED_KEY. repeated EntitledKey entitled_keys = 14; + // Video feature identifier, which is used in conjunction with |content_id| + // to determine the set of keys to be returned in the license. Cannot be + // present in conjunction with |key_ids|. + // Current values are "HDR". + optional string video_feature = 15; + //////////////////////////// Deprecated Fields //////////////////////////// enum Algorithm { UNENCRYPTED = 0; @@ -867,15 +1003,3 @@ message WidevinePsshData { optional string policy = 6 [deprecated = true]; optional bytes grouped_license = 8 [deprecated = true]; } - -// Signed device certificate definition. -// DrmDeviceCertificate signed by a higher (CA) DRM certificate. -message SignedDrmDeviceCertificate { - // Serialized certificate. Required. - optional bytes drm_certificate = 1; - // Signature of certificate. Signed with root or intermediate - // certificate specified below. Required. - optional bytes signature = 2; - // SignedDrmDeviceCertificate used to sign this certificate. - optional SignedDrmDeviceCertificate signer = 3; -} diff --git a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp index 8c893611..6d6baea2 100644 --- a/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp +++ b/libwvdrmengine/cdm/core/src/oemcrypto_adapter_dynamic.cpp @@ -30,12 +30,15 @@ #include "file_store.h" #include "level3.h" +#include "license_protocol.pb.h" #include "log.h" #include "metrics_collections.h" +#include "odk.h" #include "properties.h" #include "wv_cdm_constants.h" using namespace wvoec3; +using video_widevine::ProvisioningResponse; using wvcdm::kLevel3; using wvcdm::kLevelDefault; @@ -51,10 +54,14 @@ typedef OEMCryptoResult (*L1_Initialize_t)(void); typedef OEMCryptoResult (*L1_Terminate_t)(void); typedef OEMCryptoResult (*L1_OpenSession_t)(OEMCrypto_SESSION* session); typedef OEMCryptoResult (*L1_CloseSession_t)(OEMCrypto_SESSION session); -typedef OEMCryptoResult (*L1_GenerateDerivedKeys_t)( +typedef OEMCryptoResult (*L1_GenerateDerivedKeys_V15_t)( OEMCrypto_SESSION session, const uint8_t* mac_key_context, uint32_t mac_key_context_length, const uint8_t* enc_key_context, uint32_t enc_key_context_length); +typedef OEMCryptoResult (*L1_GenerateDerivedKeys_t)( + OEMCrypto_SESSION session, const uint8_t* mac_key_context, + size_t mac_key_context_length, const uint8_t* enc_key_context, + size_t enc_key_context_length); typedef OEMCryptoResult (*L1_GenerateNonce_t)(OEMCrypto_SESSION session, uint32_t* nonce); typedef OEMCryptoResult (*L1_GenerateSignature_t)(OEMCrypto_SESSION session, @@ -62,6 +69,21 @@ typedef OEMCryptoResult (*L1_GenerateSignature_t)(OEMCrypto_SESSION session, size_t message_length, uint8_t* signature, size_t* signature_length); +typedef OEMCryptoResult (*L1_PrepAndSignLicenseRequest_t)( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_length, uint8_t* signature, size_t* signature_length); +typedef OEMCryptoResult (*L1_PrepAndSignRenewalRequest_t)( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_length, uint8_t* signature, size_t* signature_length); +typedef OEMCryptoResult (*L1_PrepAndSignProvisioningRequest_t)( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_length, uint8_t* signature, size_t* signature_length); +typedef OEMCryptoResult (*L1_LoadLicense_t)(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); typedef OEMCryptoResult (*L1_LoadKeys_t)( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, const uint8_t* signature, size_t signature_length, @@ -101,10 +123,17 @@ typedef OEMCryptoResult (*L1_LoadKeys_V8_t)( const OEMCrypto_KeyObject_V10* key_array); typedef OEMCryptoResult (*L1_LoadEntitledContentKeys_t)( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, - size_t num_keys, const OEMCrypto_EntitledContentKeyObject* key_array); + size_t key_array_length, + const OEMCrypto_EntitledContentKeyObject* key_array); typedef OEMCryptoResult (*L1_LoadEntitledContentKeys_V14_t)( OEMCrypto_SESSION session, size_t num_keys, const OEMCrypto_EntitledContentKeyObject_V14* key_array); +typedef OEMCryptoResult (*L1_LoadRenewal_t)(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); typedef OEMCryptoResult (*L1_RefreshKeys_t)( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, const uint8_t* signature, size_t signature_length, size_t num_keys, @@ -126,20 +155,25 @@ typedef OEMCryptoResult (*L1_SelectKey_V13_t)(const OEMCrypto_SESSION session, typedef OEMCryptoResult (*L1_DecryptCTR_V10_t)( OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, bool is_encrypted, const uint8_t* iv, size_t offset, - const OEMCrypto_DestBufferDesc* out_buffer, uint8_t subsample_flags); -typedef OEMCryptoResult (*L1_DecryptCENC_t)( + const OEMCrypto_DestBufferDesc* out_buffer_descriptor, + uint8_t subsample_flags); +typedef OEMCryptoResult (*L1_DecryptCENC_V15_t)( OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, bool is_encrypted, const uint8_t* iv, size_t offset, - OEMCrypto_DestBufferDesc* out_buffer, - const OEMCrypto_CENCEncryptPatternDesc* pattern, uint8_t subsample_flags); + OEMCrypto_DestBufferDesc* out_buffer_descriptor, + const OEMCrypto_CENCEncryptPatternDesc_V15* pattern, + uint8_t subsample_flags); +typedef OEMCryptoResult (*L1_DecryptCENC_t)( + OEMCrypto_SESSION session, const OEMCrypto_SampleDescription* samples, + size_t samples_length, const OEMCrypto_CENCEncryptPatternDesc* pattern); typedef OEMCryptoResult (*L1_CopyBuffer_V14_t)( const uint8_t* data_addr, size_t data_length, - OEMCrypto_DestBufferDesc* out_buffer, uint8_t subsample_flags); -typedef OEMCryptoResult (*L1_CopyBuffer_t)(OEMCrypto_SESSION session, - const uint8_t* data_addr, - size_t data_length, - OEMCrypto_DestBufferDesc* out_buffer, - uint8_t subsample_flags); + const OEMCrypto_DestBufferDesc* out_buffer_descriptor, + uint8_t subsample_flags); +typedef OEMCryptoResult (*L1_CopyBuffer_t)( + OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, + const OEMCrypto_DestBufferDesc* out_buffer_descriptor, + uint8_t subsample_flags); typedef OEMCryptoResult (*L1_WrapKeybox_t)(const uint8_t* keybox, size_t keyBoxLength, uint8_t* wrappedKeybox, @@ -216,17 +250,13 @@ typedef OEMCryptoResult (*L1_ReportUsage_t)(OEMCrypto_SESSION session, const uint8_t* pst, size_t pst_length, uint8_t* buffer, size_t* buffer_length); -typedef OEMCryptoResult (*L1_DeleteUsageEntry_t)( - OEMCrypto_SESSION session, const uint8_t* pst, size_t pst_length, - const uint8_t* message, size_t message_length, const uint8_t* signature, - size_t signature_length); -typedef OEMCryptoResult (*L1_ForceDeleteUsageEntry_t)(const uint8_t* pst, - size_t pst_length); -typedef OEMCryptoResult (*L1_DeleteOldUsageTable_t)(); typedef OEMCrypto_ProvisioningMethod (*L1_GetProvisioningMethod_t)(); -typedef OEMCryptoResult (*L1_GetOEMPublicCertificate_t)( +typedef OEMCryptoResult (*L1_GetOEMPublicCertificate_V15_t)( OEMCrypto_SESSION session, uint8_t* public_cert, size_t* public_cert_length); +typedef OEMCryptoResult (*L1_GetOEMPublicCertificate_t)( + uint8_t* public_cert, size_t* public_cert_length); +typedef OEMCryptoResult (*L1_LoadOEMPrivateKey_t)(OEMCrypto_SESSION session); typedef OEMCryptoResult (*L1_RewrapDeviceRSAKey30_t)( OEMCrypto_SESSION session, const uint32_t* nonce, const uint8_t* encrypted_message_key, size_t encrypted_message_key_length, @@ -262,14 +292,6 @@ typedef OEMCryptoResult (*L1_ShrinkUsageTableHeader_t)( size_t* header_buffer_length); typedef OEMCryptoResult (*L1_MoveEntry_t)(OEMCrypto_SESSION session, uint32_t new_index); -typedef OEMCryptoResult (*L1_CopyOldUsageEntry_t)(OEMCrypto_SESSION session, - const uint8_t* pst, - size_t pst_length); -typedef OEMCryptoResult (*L1_CreateOldUsageEntry_t)( - uint64_t time_since_license_received, uint64_t time_since_first_decrypt, - uint64_t time_since_last_decrypt, OEMCrypto_Usage_Entry_Status status, - uint8_t* server_mac_key, uint8_t* client_mac_key, const uint8_t* pst, - size_t pst_length); typedef uint32_t (*L1_GetAnalogOutputFlags_t)(void); typedef const char* (*L1_BuildInformation_t)(void); typedef uint32_t (*L1_ResourceRatingTier_t)(void); @@ -280,6 +302,19 @@ typedef OEMCryptoResult (*L1_SetDecryptHash_t)(OEMCrypto_SESSION session, size_t hash_length); typedef OEMCryptoResult (*L1_GetHashErrorCode_t)(OEMCrypto_SESSION session, uint32_t* failed_frame_number); +typedef OEMCryptoResult (*L1_AllocateSecureBuffer_t)( + OEMCrypto_SESSION session, size_t buffer_size, + OEMCrypto_DestBufferDesc* output_descriptor, int* secure_fd); +typedef OEMCryptoResult (*L1_FreeSecureBuffer_t)( + OEMCrypto_SESSION session, OEMCrypto_DestBufferDesc* output_descriptor, + int secure_fd); +typedef size_t (*L1_MaximumUsageTableHeaderSize_t)(); +typedef OEMCryptoResult (*L1_LoadProvisioning_t)( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t core_message_length, const uint8_t* signature, + size_t signature_length, uint8_t* wrapped_private_key, + size_t* wrapped_private_key_length); +typedef uint32_t (*L1_MinorAPIVersion_t)(); struct FunctionPointers { uint32_t version; @@ -288,15 +323,23 @@ struct FunctionPointers { L1_Terminate_t Terminate; L1_OpenSession_t OpenSession; L1_CloseSession_t CloseSession; + L1_GenerateDerivedKeys_V15_t GenerateDerivedKeys_V15; L1_GenerateDerivedKeys_t GenerateDerivedKeys; L1_GenerateNonce_t GenerateNonce; L1_GenerateSignature_t GenerateSignature; + L1_PrepAndSignLicenseRequest_t PrepAndSignLicenseRequest; + L1_PrepAndSignRenewalRequest_t PrepAndSignRenewalRequest; + L1_PrepAndSignProvisioningRequest_t PrepAndSignProvisioningRequest; + L1_LoadLicense_t LoadLicense; L1_LoadKeys_t LoadKeys; L1_LoadEntitledContentKeys_t LoadEntitledContentKeys; + L1_LoadEntitledContentKeys_V14_t LoadEntitledContentKeys_V14; + L1_LoadRenewal_t LoadRenewal; L1_RefreshKeys_t RefreshKeys; L1_QueryKeyControl_t QueryKeyControl; L1_SelectKey_t SelectKey; L1_DecryptCTR_V10_t DecryptCTR_V10; + L1_DecryptCENC_V15_t DecryptCENC_V15; L1_DecryptCENC_t DecryptCENC; L1_CopyBuffer_V14_t CopyBuffer_V14; L1_CopyBuffer_t CopyBuffer; @@ -327,11 +370,10 @@ struct FunctionPointers { L1_UpdateUsageTable_t UpdateUsageTable; L1_DeactivateUsageEntry_t DeactivateUsageEntry; L1_ReportUsage_t ReportUsage; - L1_DeleteUsageEntry_t DeleteUsageEntry; - L1_ForceDeleteUsageEntry_t ForceDeleteUsageEntry; - L1_DeleteOldUsageTable_t DeleteOldUsageTable; L1_GetProvisioningMethod_t GetProvisioningMethod; + L1_GetOEMPublicCertificate_V15_t GetOEMPublicCertificate_V15; L1_GetOEMPublicCertificate_t GetOEMPublicCertificate; + L1_LoadOEMPrivateKey_t LoadOEMPrivateKey; L1_RewrapDeviceRSAKey30_t RewrapDeviceRSAKey30; L1_SupportedCertificates_t SupportedCertificates; L1_IsSRMUpdateSupported_t IsSRMUpdateSupported; @@ -345,14 +387,17 @@ struct FunctionPointers { L1_UpdateUsageEntry_t UpdateUsageEntry; L1_ShrinkUsageTableHeader_t ShrinkUsageTableHeader; L1_MoveEntry_t MoveEntry; - L1_CopyOldUsageEntry_t CopyOldUsageEntry; - L1_CreateOldUsageEntry_t CreateOldUsageEntry; L1_GetAnalogOutputFlags_t GetAnalogOutputFlags; L1_BuildInformation_t BuildInformation; L1_ResourceRatingTier_t ResourceRatingTier; L1_SupportsDecryptHash_t SupportsDecryptHash; L1_SetDecryptHash_t SetDecryptHash; L1_GetHashErrorCode_t GetHashErrorCode; + L1_AllocateSecureBuffer_t AllocateSecureBuffer; + L1_FreeSecureBuffer_t FreeSecureBuffer; + L1_MaximumUsageTableHeaderSize_t MaximumUsageTableHeaderSize; + L1_LoadProvisioning_t LoadProvisioning; + L1_MinorAPIVersion_t MinorAPIVersion; L1_LoadKeys_V8_t LoadKeys_V8; L1_GenerateRSASignature_V8_t GenerateRSASignature_V8; @@ -364,10 +409,19 @@ struct FunctionPointers { L1_LoadKeys_V14_t LoadKeys_V14; L1_SelectKey_V13_t SelectKey_V13; L1_LoadTestKeybox_V13_t LoadTestKeybox_V13; - L1_LoadEntitledContentKeys_V14_t LoadEntitledContentKeys_V14; L1_RefreshKeys_V14_t RefreshKeys_V14; }; +size_t GetOffset(const std::string& message, const std::string& field) { + size_t pos = message.find(field); + if (pos == std::string::npos) { + LOGE("Cannot find the |field| offset in message: field = %s", + field.c_str()); + pos = 0; + } + return pos; +} + // The WatchDog looks after a worker thread that is trying to initialize L3. // Once in a rare while, the L3 init does not finish and eats up CPU cycles. // If that happens, the watchdog thread will give up and return an error. @@ -558,14 +612,26 @@ class WatchDog { struct LevelSession { FunctionPointers* fcn; OEMCrypto_SESSION session; - LevelSession() : fcn(0), session(0){}; + // For backwards compatibility, we need to remember the session's nonce + // so that we can pass it to the ODK library. + uint32_t nonce; + LevelSession() : fcn(0), session(0), nonce(0) {} }; #define QUOTE_DEFINE(A) #A #define QUOTE(A) QUOTE_DEFINE(A) // This macro looks up a function name, but only if the API version is in the // specified range. -#define LOOKUP(min, max, Name, Function) \ +#define LOOKUP(min, max, Name, Function) \ + level1_.Name = (L1_##Name##_t)dlsym(level1_library_, QUOTE(Function)); \ + if (!level1_.Name) { \ + if ((level1_.version >= min) && (level1_.version <= max)) { \ + LOGW("Could not load L1 %s.", QUOTE(Function)); \ + } \ + } +// TODO(b/144660718): This is the old version of the LOOKUP macro. If we fix +// backwards compat, then we can, and probably should, go back to this. +#define LOOKUP_WITH_ERROR_CHECKING(min, max, Name, Function) \ if ((level1_.version >= min) && (level1_.version <= max)) { \ level1_.Name = (L1_##Name##_t)dlsym(level1_library_, QUOTE(Function)); \ if (!level1_.Name) { \ @@ -680,7 +746,7 @@ class Adapter { } level1_valid_ = true; const uint32_t kMinimumVersion = 8; - const uint32_t kMaximumVersion = 15; + const uint32_t kMaximumVersion = 16; level1_.version = kMinimumVersion; LOOKUP_ALL(8, Initialize, OEMCrypto_Initialize); LOOKUP_ALL(8, APIVersion, OEMCrypto_APIVersion); @@ -722,23 +788,23 @@ class Adapter { LOOKUP_ALL( 8, CloseSession, OEMCrypto_CloseSession); LOOKUP(10, 14, CopyBuffer_V14, OEMCrypto_CopyBuffer_V14); LOOKUP_ALL(15, CopyBuffer, OEMCrypto_CopyBuffer); - LOOKUP_ALL(13, CopyOldUsageEntry, OEMCrypto_CopyOldUsageEntry); LOOKUP_ALL(13, CreateNewUsageEntry, OEMCrypto_CreateNewUsageEntry); - LOOKUP_ALL(13, CreateOldUsageEntry, OEMCrypto_CreateOldUsageEntry); LOOKUP_ALL(13, CreateUsageTableHeader, OEMCrypto_CreateUsageTableHeader); LOOKUP( 9, 12, DeactivateUsageEntry_V12, OEMCrypto_DeactivateUsageEntry_V12); LOOKUP_ALL(13, DeactivateUsageEntry, OEMCrypto_DeactivateUsageEntry); LOOKUP( 8, 10, DecryptCTR_V10, OEMCrypto_DecryptCTR_V10); - LOOKUP_ALL(11, DecryptCENC, OEMCrypto_DecryptCENC); - LOOKUP_ALL( 9, DeleteOldUsageTable, OEMCrypto_DeleteOldUsageTable); - LOOKUP( 9, 12, DeleteUsageEntry, OEMCrypto_DeleteUsageEntry); + LOOKUP(11, 15, DecryptCENC_V15, OEMCrypto_DecryptCENC_V15); + LOOKUP_ALL(16, DecryptCENC, OEMCrypto_DecryptCENC); LOOKUP_ALL( 8, DeriveKeysFromSessionKey, OEMCrypto_DeriveKeysFromSessionKey); - LOOKUP(10, 12, ForceDeleteUsageEntry, OEMCrypto_ForceDeleteUsageEntry); - LOOKUP_ALL( 8, GenerateDerivedKeys, OEMCrypto_GenerateDerivedKeys); + LOOKUP( 8, 15, GenerateDerivedKeys_V15, OEMCrypto_GenerateDerivedKeys_V15); + LOOKUP_ALL(16, GenerateDerivedKeys, OEMCrypto_GenerateDerivedKeys); LOOKUP_ALL( 8, GenerateNonce, OEMCrypto_GenerateNonce); LOOKUP( 8, 8, GenerateRSASignature_V8, OEMCrypto_GenerateRSASignature_V8); LOOKUP_ALL( 9, GenerateRSASignature, OEMCrypto_GenerateRSASignature); - LOOKUP_ALL( 8, GenerateSignature, OEMCrypto_GenerateSignature); + LOOKUP( 8, 15, GenerateSignature, OEMCrypto_GenerateSignature); + LOOKUP_ALL(16, PrepAndSignLicenseRequest, OEMCrypto_PrepAndSignLicenseRequest); + LOOKUP_ALL(16, PrepAndSignRenewalRequest, OEMCrypto_PrepAndSignRenewalRequest); + LOOKUP_ALL(16, PrepAndSignProvisioningRequest, OEMCrypto_PrepAndSignProvisioningRequest); LOOKUP_ALL( 8, Generic_Decrypt, OEMCrypto_Generic_Decrypt); LOOKUP_ALL( 8, Generic_Encrypt, OEMCrypto_Generic_Encrypt); LOOKUP_ALL( 8, Generic_Sign, OEMCrypto_Generic_Sign); @@ -753,7 +819,9 @@ class Adapter { LOOKUP_ALL( 8, GetKeyData, OEMCrypto_GetKeyData); LOOKUP_ALL(10, GetMaxNumberOfSessions, OEMCrypto_GetMaxNumberOfSessions); LOOKUP_ALL(10, GetNumberOfOpenSessions, OEMCrypto_GetNumberOfOpenSessions); - LOOKUP_ALL(12, GetOEMPublicCertificate, OEMCrypto_GetOEMPublicCertificate); + LOOKUP(12, 15, GetOEMPublicCertificate_V15, OEMCrypto_GetOEMPublicCertificate_V15); + LOOKUP_ALL(16, GetOEMPublicCertificate, OEMCrypto_GetOEMPublicCertificate); + LOOKUP_ALL(16, LoadOEMPrivateKey, OEMCrypto_LoadOEMPrivateKey); LOOKUP_ALL(12, GetProvisioningMethod, OEMCrypto_GetProvisioningMethod); LOOKUP_ALL( 8, GetRandom, OEMCrypto_GetRandom); LOOKUP_ALL( 8, InstallKeyboxOrOEMCert, OEMCrypto_InstallKeyboxOrOEMCert); @@ -767,6 +835,7 @@ class Adapter { LOOKUP(13, 13, LoadKeys_V13, OEMCrypto_LoadKeys_V13); LOOKUP(14, 14, LoadKeys_V14, OEMCrypto_LoadKeys_V14); LOOKUP_ALL(15, LoadKeys, OEMCrypto_LoadKeys); + LOOKUP_ALL(16, LoadLicense, OEMCrypto_LoadLicense); LOOKUP(14, 14, LoadEntitledContentKeys_V14,OEMCrypto_LoadEntitledContentKeys_V14); LOOKUP_ALL(15, LoadEntitledContentKeys, OEMCrypto_LoadEntitledContentKeys); LOOKUP_ALL(13, LoadSRM, OEMCrypto_LoadSRM); @@ -778,6 +847,7 @@ class Adapter { LOOKUP_ALL(13, MoveEntry, OEMCrypto_MoveEntry); LOOKUP_ALL( 8, OpenSession, OEMCrypto_OpenSession); LOOKUP_ALL(10, QueryKeyControl, OEMCrypto_QueryKeyControl); + LOOKUP_ALL(16, LoadRenewal, OEMCrypto_LoadRenewal); LOOKUP( 8, 14, RefreshKeys_V14, OEMCrypto_RefreshKeys_V14); LOOKUP_ALL(15, RefreshKeys, OEMCrypto_RefreshKeys); LOOKUP_ALL(13, RemoveSRM, OEMCrypto_RemoveSRM); @@ -797,6 +867,11 @@ class Adapter { LOOKUP_ALL(15, SupportsDecryptHash, OEMCrypto_SupportsDecryptHash); LOOKUP_ALL(15, SetDecryptHash, OEMCrypto_SetDecryptHash); LOOKUP_ALL(15, GetHashErrorCode, OEMCrypto_GetHashErrorCode); + LOOKUP_ALL(16, AllocateSecureBuffer, OEMCrypto_AllocateSecureBuffer); + LOOKUP_ALL(16, FreeSecureBuffer, OEMCrypto_FreeSecureBuffer); + LOOKUP_ALL(16, MaximumUsageTableHeaderSize, OEMCrypto_MaximumUsageTableHeaderSize); + LOOKUP_ALL(16, LoadProvisioning, OEMCrypto_LoadProvisioning); + LOOKUP_ALL(16, MinorAPIVersion, OEMCrypto_MinorAPIVersion); // clang-format on // TODO(119830252): make the code below available to a static adapter. @@ -882,15 +957,20 @@ class Adapter { level3_.Terminate = Level3_Terminate; level3_.OpenSession = Level3_OpenSession; level3_.CloseSession = Level3_CloseSession; - level3_.GenerateDerivedKeys = Level3_GenerateDerivedKeys; + level3_.GenerateDerivedKeys_V15 = Level3_GenerateDerivedKeys; level3_.GenerateNonce = Level3_GenerateNonce; level3_.GenerateSignature = Level3_GenerateSignature; level3_.LoadKeys = Level3_LoadKeys; + // TODO(b/139814713): implement V16 DecryptCENC for Haystack L3 + // level3_.LoadLicense = Level3_LoadLicense; level3_.LoadEntitledContentKeys = Level3_LoadEntitledContentKeys; + // TODO(b/139814713): fix this. + // level3_.LoadRenewal = Level3_LoadRenewal; level3_.RefreshKeys = Level3_RefreshKeys; level3_.QueryKeyControl = Level3_QueryKeyControl; level3_.SelectKey = Level3_SelectKey; - level3_.DecryptCENC = Level3_DecryptCENC; + // TODO(b/139814713): implement V16 DecryptCENC for Haystack L3 + level3_.DecryptCENC_V15 = Level3_DecryptCENC; level3_.CopyBuffer = Level3_CopyBuffer; level3_.WrapKeybox = Level3_WrapKeyboxOrOEMCert; level3_.InstallKeyboxOrOEMCert = Level3_InstallKeyboxOrOEMCert; @@ -905,6 +985,7 @@ class Adapter { level3_.GenerateRSASignature = Level3_GenerateRSASignature; level3_.DeriveKeysFromSessionKey = Level3_DeriveKeysFromSessionKey; level3_.APIVersion = Level3_APIVersion; + level3_.MinorAPIVersion = nullptr; level3_.SecurityPatchLevel = Level3_SecurityPatchLevel; level3_.SecurityLevel = Level3_SecurityLevel; level3_.GetHDCPCapability = Level3_GetHDCPCapability; @@ -921,9 +1002,8 @@ class Adapter { level3_.Generic_Verify = Level3_Generic_Verify; level3_.DeactivateUsageEntry = Level3_DeactivateUsageEntry; level3_.ReportUsage = Level3_ReportUsage; - level3_.DeleteOldUsageTable = Level3_DeleteOldUsageTable; level3_.GetProvisioningMethod = Level3_GetProvisioningMethod; - level3_.GetOEMPublicCertificate = Level3_GetOEMPublicCertificate; + level3_.GetOEMPublicCertificate_V15 = Level3_GetOEMPublicCertificate; level3_.RewrapDeviceRSAKey30 = Level3_RewrapDeviceRSAKey30; level3_.SupportedCertificates = Level3_SupportedCertificates; level3_.IsSRMUpdateSupported = Level3_IsSRMUpdateSupported; @@ -937,11 +1017,10 @@ class Adapter { level3_.UpdateUsageEntry = Level3_UpdateUsageEntry; level3_.ShrinkUsageTableHeader = Level3_ShrinkUsageTableHeader; level3_.MoveEntry = Level3_MoveEntry; - level3_.CopyOldUsageEntry = Level3_CopyOldUsageEntry; - level3_.CreateOldUsageEntry = Level3_CreateOldUsageEntry; level3_.SupportsDecryptHash = Level3_SupportsDecryptHash; level3_.SetDecryptHash = Level3_SetDecryptHash; level3_.GetHashErrorCode = Level3_GetHashErrorCode; + level3_.LoadProvisioning = nullptr; // clang-format on level3_.version = Level3_APIVersion(); @@ -1100,6 +1179,14 @@ uint32_t OEMCrypto_APIVersion(SecurityLevel level) { return fcn->APIVersion(); } +uint32_t OEMCrypto_MinorAPIVersion(SecurityLevel level) { + if (!gAdapter) return 0; + const FunctionPointers* fcn = gAdapter->GetFunctionPointers(level); + if (!fcn) return 0; + if (fcn->MinorAPIVersion == nullptr) return 0; + return fcn->MinorAPIVersion(); +} + uint8_t OEMCrypto_Security_Patch_Level(SecurityLevel level) { if (!gAdapter) return 0; const FunctionPointers* fcn = gAdapter->GetFunctionPointers(level); @@ -1179,6 +1266,15 @@ uint32_t OEMCrypto_SupportsDecryptHash(SecurityLevel level) { return fcn->SupportsDecryptHash(); } +size_t OEMCrypto_MaximumUsageTableHeaderSize(SecurityLevel level) { + if (!gAdapter.get()) return 0; + const FunctionPointers* fcn = gAdapter->GetFunctionPointers(level); + if (!fcn) return 0; + const size_t historic_value = 200u; + if (fcn->MaximumUsageTableHeaderSize == nullptr) return historic_value; + return fcn->MaximumUsageTableHeaderSize(); +} + bool OEMCrypto_SupportsUsageTable(SecurityLevel level) { if (!gAdapter) return false; const FunctionPointers* fcn = gAdapter->GetFunctionPointers(level); @@ -1214,7 +1310,12 @@ OEMCryptoResult OEMCrypto_GetMaxNumberOfSessions(SecurityLevel level, if (fcn->version < 10) return OEMCrypto_ERROR_NOT_IMPLEMENTED; if (fcn->GetMaxNumberOfSessions == nullptr) return OEMCrypto_ERROR_NOT_IMPLEMENTED; - return fcn->GetMaxNumberOfSessions(maximum); + OEMCryptoResult result = fcn->GetMaxNumberOfSessions(maximum); + if (fcn->version < 16 && result == OEMCrypto_SUCCESS && *maximum > 1) { + // Reserve one session to use in GetOEMPublicCertificate. + *maximum -= 1; + } + return result; } uint32_t OEMCrypto_SupportedCertificates(SecurityLevel level) { @@ -1266,21 +1367,25 @@ OEMCryptoResult OEMCrypto_ShrinkUsageTableHeader(SecurityLevel level, header_buffer_length); } -OEMCryptoResult OEMCrypto_CreateOldUsageEntry( - SecurityLevel level, uint64_t time_since_license_received, - uint64_t time_since_first_decrypt, uint64_t time_since_last_decrypt, - OEMCrypto_Usage_Entry_Status status, uint8_t* server_mac_key, - uint8_t* client_mac_key, const uint8_t* pst, size_t pst_length) { +OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(uint8_t* public_cert, + size_t* public_cert_length, + SecurityLevel level) { if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; const FunctionPointers* fcn = gAdapter->GetFunctionPointers(level); if (!fcn) return OEMCrypto_ERROR_INVALID_SESSION; - if (fcn->version < 13) return OEMCrypto_ERROR_NOT_IMPLEMENTED; - if (fcn->CreateOldUsageEntry == nullptr) - return OEMCrypto_ERROR_NOT_IMPLEMENTED; - return fcn->CreateOldUsageEntry( - time_since_license_received, time_since_first_decrypt, - time_since_last_decrypt, status, server_mac_key, client_mac_key, pst, - pst_length); + if (fcn->GetOEMPublicCertificate != nullptr) + return fcn->GetOEMPublicCertificate(public_cert, public_cert_length); + if (fcn->GetOEMPublicCertificate_V15 != nullptr) { + OEMCrypto_SESSION session; + OEMCryptoResult res = fcn->OpenSession(&session); + if (res != OEMCrypto_SUCCESS) return res; + res = fcn->GetOEMPublicCertificate_V15(session, public_cert, + public_cert_length); + // Ignore error from close. Error from main function is more important. + fcn->CloseSession(session); + return res; + } + return OEMCrypto_ERROR_NOT_IMPLEMENTED; } } // namespace wvcdm @@ -1319,13 +1424,27 @@ extern "C" OEMCryptoResult OEMCrypto_CloseSession(OEMCrypto_SESSION session) { extern "C" OEMCryptoResult OEMCrypto_GenerateDerivedKeys( OEMCrypto_SESSION session, const uint8_t* mac_key_context, - uint32_t mac_key_context_length, const uint8_t* enc_key_context, - uint32_t enc_key_context_length) { - if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + size_t mac_key_context_length, const uint8_t* enc_key_context, + size_t enc_key_context_length) { + if (!gAdapter.get()) return OEMCrypto_ERROR_UNKNOWN_FAILURE; LevelSession pair = gAdapter->GetSession(session); if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION; - if (pair.fcn->GenerateDerivedKeys == nullptr) - return OEMCrypto_ERROR_NOT_IMPLEMENTED; + if (pair.fcn->GenerateDerivedKeys == nullptr) { + if (pair.fcn->GenerateDerivedKeys_V15 == nullptr) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + // A sanity check for buffer sizes. Fits in a uint32_t. To be extra sane, + // we'll check for an int32_t. + const uint32_t kMaxMessage = 0x7FFFFFFF; + if (mac_key_context_length > kMaxMessage || + enc_key_context_length > kMaxMessage) { + return OEMCrypto_ERROR_BUFFER_TOO_LARGE; + } + return pair.fcn->GenerateDerivedKeys_V15( + pair.session, mac_key_context, + static_cast(mac_key_context_length), enc_key_context, + static_cast(enc_key_context_length)); + } return pair.fcn->GenerateDerivedKeys(pair.session, mac_key_context, mac_key_context_length, enc_key_context, enc_key_context_length); @@ -1338,19 +1457,84 @@ extern "C" OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session, if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION; if (pair.fcn->GenerateNonce == nullptr) return OEMCrypto_ERROR_NOT_IMPLEMENTED; - return pair.fcn->GenerateNonce(pair.session, nonce); + OEMCryptoResult result = pair.fcn->GenerateNonce(pair.session, nonce); + if (result == OEMCrypto_SUCCESS && nonce != nullptr) pair.nonce = *nonce; + return result; } -extern "C" OEMCryptoResult OEMCrypto_GenerateSignature( - OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, - uint8_t* signature, size_t* signature_length) { - if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; +extern "C" OEMCryptoResult OEMCrypto_PrepAndSignLicenseRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_length, uint8_t* signature, size_t* signature_length) { + if (!gAdapter.get()) return OEMCrypto_ERROR_UNKNOWN_FAILURE; LevelSession pair = gAdapter->GetSession(session); if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION; - if (pair.fcn->GenerateSignature == nullptr) + if (pair.fcn->PrepAndSignLicenseRequest != nullptr) { + return pair.fcn->PrepAndSignLicenseRequest( + pair.session, message, message_length, core_message_length, signature, + signature_length); + } + if (pair.fcn->GenerateRSASignature != nullptr) { + if (core_message_length == nullptr) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + *core_message_length = 0; + return pair.fcn->GenerateRSASignature(pair.session, message, message_length, + signature, signature_length, + kSign_RSASSA_PSS); + } + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +extern "C" OEMCryptoResult OEMCrypto_PrepAndSignRenewalRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_length, uint8_t* signature, size_t* signature_length) { + if (!gAdapter.get()) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + LevelSession pair = gAdapter->GetSession(session); + if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION; + if (pair.fcn->PrepAndSignRenewalRequest != nullptr) { + return pair.fcn->PrepAndSignRenewalRequest( + pair.session, message, message_length, core_message_length, signature, + signature_length); + } + if (pair.fcn->GenerateSignature != nullptr) { + if (core_message_length == nullptr) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + *core_message_length = 0; + return pair.fcn->GenerateSignature(pair.session, message, message_length, + signature, signature_length); + } + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +extern "C" OEMCryptoResult OEMCrypto_PrepAndSignProvisioningRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_length, uint8_t* signature, size_t* signature_length) { + if (!gAdapter.get()) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + LevelSession pair = gAdapter->GetSession(session); + if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION; + if (pair.fcn->PrepAndSignProvisioningRequest != nullptr) { + return pair.fcn->PrepAndSignProvisioningRequest( + pair.session, message, message_length, core_message_length, signature, + signature_length); + } + if (pair.fcn->GetProvisioningMethod == nullptr) return OEMCrypto_ERROR_NOT_IMPLEMENTED; - return pair.fcn->GenerateSignature(pair.session, message, message_length, - signature, signature_length); + OEMCrypto_ProvisioningMethod method = pair.fcn->GetProvisioningMethod(); + if (core_message_length == nullptr) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + *core_message_length = 0; + if (method == OEMCrypto_Keybox) { + if (pair.fcn->GenerateSignature != nullptr) { + return pair.fcn->GenerateSignature(pair.session, message, message_length, + signature, signature_length); + } + } else if (method == OEMCrypto_OEMCertificate) { + if (pair.fcn->GenerateRSASignature != nullptr) { + return pair.fcn->GenerateRSASignature(pair.session, message, + message_length, signature, + signature_length, kSign_RSASSA_PSS); + } + } else { + LOGE("Unsupported provisioning = %d", method); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + return OEMCrypto_ERROR_NOT_IMPLEMENTED; } extern "C" uint32_t OEMCrypto_GetAnalogOutputFlags() { @@ -1365,7 +1549,7 @@ extern "C" uint32_t OEMCrypto_ResourceRatingTier() { return OEMCrypto_ResourceRatingTier(kLevelDefault); } -// Used for backwards compatibility. If the length is 0, this denotes a NULL +// Used for backwards compatibility. If the length is 0, this denotes a null // pointer for OEMCrypto v15. const uint8_t* PointerOrNull(const uint8_t* pointer, size_t length) { return length ? pointer : nullptr; @@ -1524,6 +1708,25 @@ extern "C" OEMCryptoResult OEMCrypto_LoadKeys_Back_Compat( } } +extern "C" OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length) { + if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + LevelSession pair = gAdapter->GetSession(session); + if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION; + if (pair.fcn->LoadLicense == nullptr) { + // The CDM layer will handle backwards compatibility by calling LoadKeys for + // licenses that are pre-v16. + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + return pair.fcn->LoadLicense(pair.session, message, message_length, + core_message_length, signature, + signature_length); +} + /* The CDM layer should use OEMCrypto_LoadKeys_Back_Compat instead. This is * used by unit tests, and forces cipher mode to CTR when used with an older * oemcrypto. @@ -1679,18 +1882,20 @@ extern "C" OEMCryptoResult OEMCrypto_LoadKeys( extern "C" OEMCryptoResult OEMCrypto_LoadEntitledContentKeys( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, - size_t num_keys, const OEMCrypto_EntitledContentKeyObject* key_array) { - if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + size_t key_array_length, + const OEMCrypto_EntitledContentKeyObject* key_array) { + if (!gAdapter.get()) return OEMCrypto_ERROR_UNKNOWN_FAILURE; LevelSession pair = gAdapter->GetSession(session); if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION; if (pair.fcn->version < 14) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; - } else if (pair.fcn->version < 15) { + } + if (pair.fcn->version < 15) { if (pair.fcn->LoadEntitledContentKeys_V14 == nullptr) return OEMCrypto_ERROR_NOT_IMPLEMENTED; std::vector ecko_array_v14( - num_keys); - for (size_t i = 0; i < num_keys; i++) { + key_array_length); + for (size_t i = 0; i < key_array_length; i++) { ecko_array_v14[i].entitlement_key_id = PointerOrNull(message + key_array[i].entitlement_key_id.offset, key_array[i].entitlement_key_id.length); @@ -1711,15 +1916,34 @@ extern "C" OEMCryptoResult OEMCrypto_LoadEntitledContentKeys( key_array[i].content_key_data.length; } OEMCrypto_EntitledContentKeyObject_V14* ecko_array_v14_ptr = nullptr; - if (num_keys > 0) ecko_array_v14_ptr = &ecko_array_v14[0]; - return pair.fcn->LoadEntitledContentKeys_V14(pair.session, num_keys, + if (key_array_length > 0) ecko_array_v14_ptr = ecko_array_v14.data(); + return pair.fcn->LoadEntitledContentKeys_V14(pair.session, key_array_length, ecko_array_v14_ptr); - } else { - if (pair.fcn->LoadEntitledContentKeys == nullptr) - return OEMCrypto_ERROR_NOT_IMPLEMENTED; - return pair.fcn->LoadEntitledContentKeys( - pair.session, message, message_length, num_keys, key_array); } + if (pair.fcn->LoadEntitledContentKeys == nullptr) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + return pair.fcn->LoadEntitledContentKeys( + pair.session, message, message_length, key_array_length, key_array); +} + +extern "C" OEMCryptoResult OEMCrypto_LoadRenewal(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length) { + if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + LevelSession pair = gAdapter->GetSession(session); + if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION; + if (pair.fcn->LoadLicense == nullptr) { + // The CDM layer will handle backwards compatibility by calling RefreshKeys + // for licenses that are pre-v16. + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + return pair.fcn->LoadRenewal(pair.session, message, message_length, + core_message_length, signature, + signature_length); } extern "C" OEMCryptoResult OEMCrypto_RefreshKeys( @@ -1745,7 +1969,7 @@ extern "C" OEMCryptoResult OEMCrypto_RefreshKeys( key_array[i].key_control.length); } OEMCrypto_KeyRefreshObject_V14* kro_array_v14_ptr = nullptr; - if (num_keys > 0) kro_array_v14_ptr = &kro_array_v14[0]; + if (num_keys > 0) kro_array_v14_ptr = kro_array_v14.data(); return pair.fcn->RefreshKeys_V14(pair.session, message, message_length, signature, signature_length, num_keys, kro_array_v14_ptr); @@ -1786,43 +2010,66 @@ extern "C" OEMCryptoResult OEMCrypto_SelectKey( } extern "C" OEMCryptoResult OEMCrypto_DecryptCENC( - OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, - bool is_encrypted, const uint8_t* iv, size_t offset, - OEMCrypto_DestBufferDesc* out_buffer, - const OEMCrypto_CENCEncryptPatternDesc* pattern, uint8_t subsample_flags) { + OEMCrypto_SESSION session, const OEMCrypto_SampleDescription* samples, + size_t samples_length, const OEMCrypto_CENCEncryptPatternDesc* pattern) { if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; LevelSession pair = gAdapter->GetSession(session); if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION; - if (pair.fcn->version < 11) { - if (pair.fcn->DecryptCTR_V10 == nullptr) - return OEMCrypto_ERROR_NOT_IMPLEMENTED; - return pair.fcn->DecryptCTR_V10(pair.session, data_addr, data_length, - is_encrypted, iv, offset, out_buffer, - subsample_flags); + if (pair.fcn->DecryptCENC != nullptr) { + return pair.fcn->DecryptCENC(pair.session, samples, samples_length, + pattern); + } else if (pair.fcn->DecryptCENC_V15 != nullptr) { + // Reject any calls that contain more subsamples than the v11 API supports, + // which is exactly 1 sample containing exactly 1 subsample containing only + // clear or encrypted data. If there is less than this, the call is + // malformed, so return OEMCrypto_ERROR_INVALID_CONTEXT. If there is more, + // v16 guarantees the CDM will break the samples down until they fit the v11 + // API if we keep returning OEMCrypto_ERROR_BUFFER_TOO_LARGE. + if (samples_length < 1) return OEMCrypto_ERROR_INVALID_CONTEXT; + if (samples_length > 1) return OEMCrypto_ERROR_BUFFER_TOO_LARGE; + const OEMCrypto_SampleDescription& sample = samples[0]; + if (sample.subsamples_length < 1) return OEMCrypto_ERROR_INVALID_CONTEXT; + if (sample.subsamples_length > 1) return OEMCrypto_ERROR_BUFFER_TOO_LARGE; + const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0]; + const bool is_clear = subsample.num_bytes_clear > 0; + const bool is_encrypted = subsample.num_bytes_encrypted > 0; + if (!(is_clear || is_encrypted)) return OEMCrypto_ERROR_INVALID_CONTEXT; + if (is_clear && is_encrypted) return OEMCrypto_ERROR_BUFFER_TOO_LARGE; + + // We must make a mutable defensive copy because v11 accidentally made + // this non-const. + OEMCrypto_DestBufferDesc dest_buffer = sample.buffers.output_descriptor; + // The definition of a pattern was different in v15. + OEMCrypto_CENCEncryptPatternDesc_V15 pattern_v15 = { + pattern->encrypt, pattern->skip, + 0 // Always zero. Pattern offsets don't actually exist. + }; + return pair.fcn->DecryptCENC_V15(pair.session, sample.buffers.input_data, + sample.buffers.input_data_length, + is_encrypted, &sample.iv[0], + subsample.block_offset, &dest_buffer, + &pattern_v15, subsample.subsample_flags); } else { - if (pair.fcn->DecryptCENC == nullptr) - return OEMCrypto_ERROR_NOT_IMPLEMENTED; - return pair.fcn->DecryptCENC(pair.session, data_addr, data_length, - is_encrypted, iv, offset, out_buffer, pattern, - subsample_flags); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; } } extern "C" OEMCryptoResult OEMCrypto_CopyBuffer( OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, - OEMCrypto_DestBufferDesc* out_buffer, uint8_t subsample_flags) { + const OEMCrypto_DestBufferDesc* out_buffer_descriptor, + uint8_t subsample_flags) { if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; LevelSession pair = gAdapter->GetSession(session); if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION; if (pair.fcn->version < 15) { if (pair.fcn->CopyBuffer_V14 == nullptr) return OEMCrypto_ERROR_NOT_IMPLEMENTED; - return pair.fcn->CopyBuffer_V14(data_addr, data_length, out_buffer, - subsample_flags); + return pair.fcn->CopyBuffer_V14(data_addr, data_length, + out_buffer_descriptor, subsample_flags); } if (pair.fcn->CopyBuffer == nullptr) return OEMCrypto_ERROR_NOT_IMPLEMENTED; - return pair.fcn->CopyBuffer(session, data_addr, data_length, out_buffer, - subsample_flags); + return pair.fcn->CopyBuffer(session, data_addr, data_length, + out_buffer_descriptor, subsample_flags); } extern "C" OEMCryptoResult OEMCrypto_WrapKeybox(const uint8_t* keybox, @@ -1850,12 +2097,14 @@ extern "C" OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t* buffer, const FunctionPointers* fcn = gAdapter->GetFunctionPointers(kLevelDefault); if (!fcn) return OEMCrypto_ERROR_INVALID_SESSION; if (fcn->version < 10) return OEMCrypto_ERROR_NOT_IMPLEMENTED; + if (fcn->LoadTestKeybox != nullptr) { + return fcn->LoadTestKeybox(buffer, length); + } if (fcn->LoadTestKeybox_V13 != nullptr) { // Old versions might use wrong keybox, but this is the best we can do. return fcn->LoadTestKeybox_V13(); } - if (fcn->LoadTestKeybox == nullptr) return OEMCrypto_ERROR_NOT_IMPLEMENTED; - return fcn->LoadTestKeybox(buffer, length); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; } extern "C" OEMCryptoResult OEMCrypto_IsKeyboxOrOEMCertValid() { @@ -1867,16 +2116,34 @@ extern "C" OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod() { } extern "C" OEMCryptoResult OEMCrypto_GetOEMPublicCertificate( - OEMCrypto_SESSION session, uint8_t* public_cert, - size_t* public_cert_length) { + uint8_t* public_cert, size_t* public_cert_length) { + return OEMCrypto_GetOEMPublicCertificate(public_cert, public_cert_length, + kLevelDefault); +} + +extern "C" OEMCryptoResult OEMCrypto_LoadOEMPrivateKey( + OEMCrypto_SESSION session) { if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; LevelSession pair = gAdapter->GetSession(session); if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION; if (pair.fcn->version < 12) return OEMCrypto_ERROR_NOT_IMPLEMENTED; - if (pair.fcn->GetOEMPublicCertificate == nullptr) + if (pair.fcn->version < 16) { + if (pair.fcn->GetOEMPublicCertificate_V15 == nullptr) + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + std::vector cert; + size_t public_cert_length = 0; + OEMCryptoResult result = pair.fcn->GetOEMPublicCertificate_V15( + pair.session, cert.data(), &public_cert_length); + if (result == OEMCrypto_ERROR_SHORT_BUFFER) { + cert.resize(public_cert_length); + result = pair.fcn->GetOEMPublicCertificate_V15(pair.session, cert.data(), + &public_cert_length); + } + return result; + } + if (pair.fcn->LoadOEMPrivateKey == nullptr) return OEMCrypto_ERROR_NOT_IMPLEMENTED; - return pair.fcn->GetOEMPublicCertificate(pair.session, public_cert, - public_cert_length); + return pair.fcn->LoadOEMPrivateKey(pair.session); } extern "C" OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, @@ -1898,6 +2165,68 @@ extern "C" OEMCryptoResult OEMCrypto_GetRandom(uint8_t* randomData, return fcn->GetRandom(randomData, dataLength); } +extern "C" OEMCryptoResult OEMCrypto_LoadProvisioning( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t core_message_length, const uint8_t* signature, + size_t signature_length, uint8_t* wrapped_private_key, + size_t* wrapped_private_key_length) { + if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + LevelSession pair = gAdapter->GetSession(session); + if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION; + if (pair.fcn->LoadProvisioning != nullptr) { + return pair.fcn->LoadProvisioning( + pair.session, message, message_length, core_message_length, signature, + signature_length, wrapped_private_key, wrapped_private_key_length); + } + if (pair.fcn->GetProvisioningMethod == nullptr) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + const OEMCrypto_ProvisioningMethod method = pair.fcn->GetProvisioningMethod(); + + const std::string signed_message( + reinterpret_cast(message), + reinterpret_cast(message + message_length)); + ProvisioningResponse provisioning_response; + if (!provisioning_response.ParseFromString(signed_message)) { + LOGE("Failed to parse signed provisioining response"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const std::string& enc_rsa_key = provisioning_response.device_rsa_key(); + const std::string& iv = provisioning_response.device_rsa_key_iv(); + const std::string& nonce = provisioning_response.nonce(); + const uint8_t* msg_rsa_key = message + GetOffset(signed_message, enc_rsa_key); + const uint8_t* msg_rsa_key_iv = message + GetOffset(signed_message, iv); + const uint32_t* msg_nonce = reinterpret_cast( + message + GetOffset(signed_message, nonce)); + if (method == OEMCrypto_Keybox) { + if (pair.fcn->RewrapDeviceRSAKey == nullptr) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + return pair.fcn->RewrapDeviceRSAKey( + pair.session, message, message_length, signature, signature_length, + msg_nonce, msg_rsa_key, enc_rsa_key.size(), msg_rsa_key_iv, + wrapped_private_key, wrapped_private_key_length); + } else if (method == OEMCrypto_OEMCertificate) { + if (pair.fcn->RewrapDeviceRSAKey30 == nullptr) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + const std::string& wrapping_key = (provisioning_response.has_wrapping_key()) + ? provisioning_response.wrapping_key() + : std::string(); + const uint8_t* msg_wrapping_key = + message + GetOffset(signed_message, wrapping_key); + return pair.fcn->RewrapDeviceRSAKey30( + pair.session, msg_nonce, msg_wrapping_key, wrapping_key.size(), + msg_rsa_key, enc_rsa_key.size(), msg_rsa_key_iv, wrapped_private_key, + wrapped_private_key_length); + } else { // Otherwise, provisioning method does not support LoadProvisioning. + LOGE("Backwards compatibility code invalid: %d", method); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } +} + +// This function is still used by the unit tests when driving a v15 OEMCrypto. +// This is needed on Android because some devices have not upgraded to v16. extern "C" OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( OEMCrypto_SESSION session, const uint32_t* nonce, const uint8_t* encrypted_message_key, size_t encrypted_message_key_length, @@ -1916,6 +2245,8 @@ extern "C" OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( wrapped_rsa_key_length); } +// This function is still used by the unit tests when driving a v15 OEMCrypto. +// This is needed on Android because some devices have not upgraded to v16. extern "C" OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, const uint8_t* signature, size_t signature_length, const uint32_t* nonce, @@ -1994,6 +2325,10 @@ extern "C" uint32_t OEMCrypto_APIVersion() { return OEMCrypto_APIVersion(kLevelDefault); } +extern "C" uint32_t OEMCrypto_MinorAPIVersion() { + return OEMCrypto_MinorAPIVersion(kLevelDefault); +} + extern "C" uint8_t OEMCrypto_Security_Patch_Level() { return OEMCrypto_Security_Patch_Level(kLevelDefault); } @@ -2103,17 +2438,6 @@ extern "C" OEMCryptoResult OEMCrypto_Generic_Verify( algorithm, signature, signature_length); } -extern "C" OEMCryptoResult OEMCrypto_UpdateUsageTable() { - if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; - const FunctionPointers* fcn1 = gAdapter->GetFunctionPointers(kLevelDefault); - OEMCryptoResult sts = OEMCrypto_ERROR_NOT_IMPLEMENTED; - if ((fcn1 != nullptr) && (fcn1->UpdateUsageTable != nullptr) && - (fcn1->version > 8) && (fcn1->version < 13)) { - sts = fcn1->UpdateUsageTable(); - } - return sts; -} - extern "C" OEMCryptoResult OEMCrypto_DeactivateUsageEntry( OEMCrypto_SESSION session, const uint8_t* pst, size_t pst_length) { if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; @@ -2186,57 +2510,6 @@ extern "C" OEMCryptoResult OEMCrypto_ReportUsage(OEMCrypto_SESSION session, } } -extern "C" OEMCryptoResult OEMCrypto_DeleteUsageEntry( - OEMCrypto_SESSION session, const uint8_t* pst, size_t pst_length, - const uint8_t* message, size_t message_length, const uint8_t* signature, - size_t signature_length) { - if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; - LevelSession pair = gAdapter->GetSession(session); - if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION; - if (pair.fcn->version > 8 && pair.fcn->version < 13) { - return pair.fcn->DeleteUsageEntry(pair.session, pst, pst_length, message, - message_length, signature, - signature_length); - } else { - return OEMCrypto_ERROR_NOT_IMPLEMENTED; - } -} - -extern "C" OEMCryptoResult OEMCrypto_ForceDeleteUsageEntry(const uint8_t* pst, - size_t pst_length) { - if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; - const FunctionPointers* fcn1 = gAdapter->GetFunctionPointers(kLevelDefault); - const FunctionPointers* fcn3 = gAdapter->GetFunctionPointers(kLevel3); - OEMCryptoResult sts = OEMCrypto_ERROR_NOT_IMPLEMENTED; - if (fcn3 && (fcn3->version > 9) && (fcn3->version < 13) && - fcn3->ForceDeleteUsageEntry != nullptr) { - sts = fcn3->ForceDeleteUsageEntry(pst, pst_length); - } - if (fcn1 && fcn1 != fcn3 && (fcn1->version > 9) && (fcn1->version < 13) && - (fcn1->ForceDeleteUsageEntry != nullptr)) { - OEMCryptoResult sts1 = fcn1->ForceDeleteUsageEntry(pst, pst_length); - if ((sts != OEMCrypto_SUCCESS) && (sts1 == OEMCrypto_SUCCESS)) { - sts = sts1; - } - } - return sts; -} - -extern "C" OEMCryptoResult OEMCrypto_DeleteOldUsageTable() { - if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; - const FunctionPointers* fcn1 = gAdapter->GetFunctionPointers(kLevelDefault); - const FunctionPointers* fcn3 = gAdapter->GetFunctionPointers(kLevel3); - OEMCryptoResult sts = OEMCrypto_ERROR_NOT_IMPLEMENTED; - if (fcn3 && (fcn3->version > 8) && (fcn3->DeleteOldUsageTable != nullptr)) { - sts = fcn3->DeleteOldUsageTable(); - } - if (fcn1 && fcn1 != fcn3 && (fcn1->version > 8) && - (fcn1->DeleteOldUsageTable != nullptr)) { - sts = fcn1->DeleteOldUsageTable(); - } - return sts; -} - extern "C" bool OEMCrypto_IsSRMUpdateSupported() { if (!gAdapter) return false; // Level 3 can't load an SRM, so this just checkes Level 1. @@ -2346,32 +2619,14 @@ extern "C" OEMCryptoResult OEMCrypto_MoveEntry(OEMCrypto_SESSION session, return pair.fcn->MoveEntry(pair.session, new_index); } -extern "C" OEMCryptoResult OEMCrypto_CopyOldUsageEntry( - OEMCrypto_SESSION session, const uint8_t* pst, size_t pst_length) { - if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; - LevelSession pair = gAdapter->GetSession(session); - if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION; - if (pair.fcn->version < 13) return OEMCrypto_ERROR_NOT_IMPLEMENTED; - if (pair.fcn->CopyOldUsageEntry == nullptr) - return OEMCrypto_ERROR_NOT_IMPLEMENTED; - return pair.fcn->CopyOldUsageEntry(pair.session, pst, pst_length); -} - -extern "C" OEMCryptoResult OEMCrypto_CreateOldUsageEntry( - uint64_t time_since_license_received, uint64_t time_since_first_decrypt, - uint64_t time_since_last_decrypt, OEMCrypto_Usage_Entry_Status status, - uint8_t* server_mac_key, uint8_t* client_mac_key, const uint8_t* pst, - size_t pst_length) { - return OEMCrypto_CreateOldUsageEntry( - kLevelDefault, time_since_license_received, time_since_first_decrypt, - time_since_last_decrypt, status, server_mac_key, client_mac_key, pst, - pst_length); -} - extern "C" uint32_t OEMCrypto_SupportsDecryptHash() { return OEMCrypto_SupportsDecryptHash(kLevelDefault); } +extern "C" size_t OEMCrypto_MaximumUsageTableHeaderSize() { + return OEMCrypto_MaximumUsageTableHeaderSize(kLevelDefault); +} + extern "C" OEMCryptoResult OEMCrypto_SetDecryptHash(OEMCrypto_SESSION session, uint32_t frame_number, const uint8_t* hash, @@ -2396,3 +2651,28 @@ extern "C" OEMCryptoResult OEMCrypto_GetHashErrorCode( } return pair.fcn->GetHashErrorCode(pair.session, failed_frame_number); } + +extern "C" OEMCryptoResult OEMCrypto_AllocateSecureBuffer( + OEMCrypto_SESSION session, size_t buffer_size, + OEMCrypto_DestBufferDesc* output_descriptor, int* secure_fd) { + if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + LevelSession pair = gAdapter->GetSession(session); + if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION; + if (pair.fcn->AllocateSecureBuffer == nullptr) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + return pair.fcn->AllocateSecureBuffer(pair.session, buffer_size, + output_descriptor, secure_fd); +} + +extern "C" OEMCryptoResult OEMCrypto_FreeSecureBuffer( + OEMCrypto_SESSION session, OEMCrypto_DestBufferDesc* output_descriptor, + int secure_fd) { + if (!gAdapter) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + LevelSession pair = gAdapter->GetSession(session); + if (!pair.fcn) return OEMCrypto_ERROR_INVALID_SESSION; + if (pair.fcn->FreeSecureBuffer == nullptr) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + return pair.fcn->FreeSecureBuffer(pair.session, output_descriptor, secure_fd); +} diff --git a/libwvdrmengine/cdm/core/src/service_certificate.cpp b/libwvdrmengine/cdm/core/src/service_certificate.cpp index 4dc17288..55ab57c4 100644 --- a/libwvdrmengine/cdm/core/src/service_certificate.cpp +++ b/libwvdrmengine/cdm/core/src/service_certificate.cpp @@ -163,11 +163,17 @@ CdmResponseType ServiceCertificate::Init(const std::string& certificate) { LOGE("Failed to parse signed service certificate"); return DEVICE_CERTIFICATE_ERROR_2; } + +#ifdef ACCEPT_TEST_CERT +# pragma message "(TEST ONLY): Ignoring service cert's signature." +#else if (!root_key.VerifySignature(signed_service_cert.drm_certificate(), signed_service_cert.signature())) { LOGE("Failed to verify service certificate signature"); return DEVICE_CERTIFICATE_ERROR_3; } +#endif + DrmDeviceCertificate service_cert; if (!service_cert.ParseFromString(signed_service_cert.drm_certificate())) { LOGE("Failed to parse service certificate"); diff --git a/libwvdrmengine/cdm/core/src/usage_table_header.cpp b/libwvdrmengine/cdm/core/src/usage_table_header.cpp index 1b1fffd7..43e91629 100644 --- a/libwvdrmengine/cdm/core/src/usage_table_header.cpp +++ b/libwvdrmengine/cdm/core/src/usage_table_header.cpp @@ -17,11 +17,7 @@ namespace wvcdm { namespace { std::string kEmptyString; size_t kMaxCryptoRetries = 3; -size_t kMinUsageEntriesSupported = 200; wvcdm::CdmKeySetId kDummyKeySetId = "DummyKsid"; -uint64_t kOldUsageEntryTimeSinceLicenseReceived = 0; -uint64_t kOldUsageEntryTimeSinceFirstDecrypt = 0; -uint64_t kOldUsageEntryTimeSinceLastDecrypt = 0; std::string kOldUsageEntryServerMacKey(wvcdm::MAC_KEY_SIZE, 0); std::string kOldUsageEntryClientMacKey(wvcdm::MAC_KEY_SIZE, 0); std::string kOldUsageEntryPoviderSessionToken = @@ -197,7 +193,7 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, // minimum capacity (>200), we need to make sure we can still add and // remove entries. If not, clear files/data and recreate usage header table. if (status == NO_ERROR && lru_success) { - if (usage_entry_info_.size() > kMinUsageEntriesSupported) { + if (usage_entry_info_.size() > kMinimumUsageTableEntriesSupported) { uint32_t temporary_usage_entry_number; // Create a new temporary usage entry, close the session and then @@ -251,9 +247,6 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, status = crypto_session->CreateUsageTableHeader(&usage_table_header_); if (status != NO_ERROR) return false; file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); - - UpgradeFromUsageTable(file_handle_.get(), metrics); - file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); } is_inited_ = true; @@ -750,179 +743,6 @@ CdmResponseType UsageTableHeader::Shrink( return NO_ERROR; } -CdmResponseType UsageTableHeader::UpgradeFromUsageTable( - DeviceFiles* handle, metrics::CryptoMetrics* metrics) { - UpgradeLicensesFromUsageTable(handle, metrics); - UpgradeUsageInfoFromUsageTable(handle, metrics); - return NO_ERROR; -} - -bool UsageTableHeader::UpgradeLicensesFromUsageTable( - DeviceFiles* handle, metrics::CryptoMetrics* metrics) { - // Fetch the key set IDs for each offline license. For each license - // * retrieve the provider session token, - // * load the old usage table by creating a new dummy entry - // * copy over the entry from the old usage table and - // * update the usage header table and entry numbers - // * save the usage table header and store the usage entry number and - // usage entry along with the license to persistent memory - std::vector key_set_ids; - if (!handle->ListLicenses(&key_set_ids)) { - LOGE("Unable to retrieve list of licenses"); - return false; - } - - for (size_t i = 0; i < key_set_ids.size(); ++i) { - DeviceFiles::CdmLicenseData license_data; - DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError; - - if (!handle->RetrieveLicense(key_set_ids[i], &license_data, - &sub_error_code)) { - LOGW("Failed to retrieve license: key_set_index = %zu, status = %d", i, - static_cast(sub_error_code)); - continue; - } - - std::string provider_session_token; - if (!CdmLicense::ExtractProviderSessionToken(license_data.license, - &provider_session_token)) { - LOGW("Failed to extract provider session token: key_set_index = %zu", i); - continue; - } - - if (provider_session_token.empty()) continue; - - std::unique_ptr crypto_session( - CryptoSession::MakeCryptoSession(metrics)); - CdmResponseType status = crypto_session->Open(requested_security_level_); - - if (status != NO_ERROR) continue; - - // We create this entry since OEMCrypto_CopyOldUsageEntry needs the old - // usage table to be loaded in order to copy an entry. - if (!CreateDummyOldUsageEntry(crypto_session.get())) continue; - - status = AddEntry(crypto_session.get(), true /* persistent license */, - key_set_ids[i], kEmptyString, license_data.license, - &license_data.usage_entry_number); - - if (status != NO_ERROR) continue; - - status = crypto_session->CopyOldUsageEntry(provider_session_token); - - if (status != NO_ERROR) { - crypto_session->Close(); - Shrink(metrics, 1); - continue; - } - - status = UpdateEntry(license_data.usage_entry_number, crypto_session.get(), - &license_data.usage_entry); - - if (status != NO_ERROR) { - crypto_session->Close(); - Shrink(metrics, 1); - continue; - } - - if (!handle->StoreLicense(license_data, &sub_error_code)) { - LOGW("Failed to store license: key_set_index = %zu, status = %d", i, - static_cast(sub_error_code)); - continue; - } - } - - return NO_ERROR; -} - -bool UsageTableHeader::UpgradeUsageInfoFromUsageTable( - DeviceFiles* handle, metrics::CryptoMetrics* metrics) { - // Fetch all usage files. For each file retrieve all the usage info records - // within the file. For each piece of usage information - // * load the old usage table by creating a new dummy entry - // * copy over the entry from the old usage table and - // * update the usage header table and entry numbers - // * save the usage table header - // * once done processing all the usage records from a file, save the usage - // information to persistent memory along with usage entry number and usage - // entry. - std::vector usage_info_file_names; - if (!handle->ListUsageInfoFiles(&usage_info_file_names)) { - LOGE("Unable to retrieve list of usage info file names"); - return false; - } - - for (size_t i = 0; i < usage_info_file_names.size(); ++i) { - std::vector usage_data; - if (!handle->RetrieveUsageInfo(usage_info_file_names[i], &usage_data)) { - LOGW("Failed to retrieve usage records: usage_info_file_name = %s", - usage_info_file_names[i].c_str()); - continue; - } - - for (size_t j = 0; j < usage_data.size(); ++j) { - if (usage_data[j].provider_session_token.empty()) { - LOGW("Provider session ID is empty"); - continue; - } - - std::unique_ptr crypto_session( - CryptoSession::MakeCryptoSession(metrics)); - CdmResponseType status = crypto_session->Open(requested_security_level_); - - if (status != NO_ERROR) continue; - - // We create this entry since OEMCrypto_CopyOldUsageEntry needs the old - // usage table to be loaded in order to copy an entry. - if (!CreateDummyOldUsageEntry(crypto_session.get())) continue; - - // TODO(rfrias): We need to fill in the app id, but it is hashed - // and we have no way to extract. Use the hased filename instead? - status = - AddEntry(crypto_session.get(), false /* usage info */, - usage_data[j].key_set_id, usage_info_file_names[i], - usage_data[j].license, &(usage_data[j].usage_entry_number)); - - if (status != NO_ERROR) continue; - - status = crypto_session->CopyOldUsageEntry( - usage_data[j].provider_session_token); - - if (status != NO_ERROR) { - crypto_session->Close(); - Shrink(metrics, 1); - continue; - } - - status = UpdateEntry(usage_data[j].usage_entry_number, - crypto_session.get(), &(usage_data[j].usage_entry)); - - if (status != NO_ERROR) { - crypto_session->Close(); - Shrink(metrics, 1); - continue; - } - } - - if (!handle->StoreUsageInfo(usage_info_file_names[i], usage_data)) { - LOGE("Failed to store upgraded usage records: usage_info_file_name = %s", - usage_info_file_names[i].c_str()); - continue; - } - } - - return NO_ERROR; -} - -// TODO(fredgc): remove when b/65730828 is addressed -bool UsageTableHeader::CreateDummyOldUsageEntry(CryptoSession* crypto_session) { - return crypto_session->CreateOldUsageEntry( - kOldUsageEntryTimeSinceLicenseReceived, - kOldUsageEntryTimeSinceFirstDecrypt, kOldUsageEntryTimeSinceLastDecrypt, - CryptoSession::kUsageDurationsInvalid, kOldUsageEntryServerMacKey, - kOldUsageEntryClientMacKey, kOldUsageEntryPoviderSessionToken); -} - // Test only method. void UsageTableHeader::DeleteEntryForTest(uint32_t usage_entry_number) { LOGV("Deleting entry for test: usage_entry_number = %u", usage_entry_number); diff --git a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp index 4873eef9..5d24e264 100644 --- a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp +++ b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp @@ -177,6 +177,7 @@ class WvCdmEngineTest : public WvCdmEnginePreProvTest { CdmKeySetId key_set_id; InitializationData init_data(init_data_type_string, key_id); + if (g_cutoff >= LOG_DEBUG) init_data.DumpToLogs(); CdmKeyRequest key_request; diff --git a/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp b/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp index e732983d..f0ac0731 100644 --- a/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/cdm_session_unittest.cpp @@ -142,7 +142,7 @@ class MockCryptoSession : public TestCryptoSession { MOCK_METHOD0(GetSecurityLevel, CdmSecurityLevel()); MOCK_METHOD0(Open, CdmResponseType()); MOCK_METHOD1(Open, CdmResponseType(SecurityLevel)); - MOCK_METHOD1(LoadCertificatePrivateKey, CdmResponseType(std::string&)); + MOCK_METHOD1(LoadCertificatePrivateKey, CdmResponseType(const std::string&)); MOCK_METHOD0(DeleteAllUsageReports, CdmResponseType()); MOCK_METHOD1(GetUsageSupportType, CdmResponseType(CdmUsageSupportType* type)); MOCK_METHOD0(GetUsageTableHeader, UsageTableHeader*()); diff --git a/libwvdrmengine/cdm/core/test/fake_provisioning_server.cpp b/libwvdrmengine/cdm/core/test/fake_provisioning_server.cpp new file mode 100644 index 00000000..d8d725a7 --- /dev/null +++ b/libwvdrmengine/cdm/core/test/fake_provisioning_server.cpp @@ -0,0 +1,362 @@ +// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "fake_provisioning_server.h" + +#include "core_message_deserialize.h" +#include "core_message_serialize.h" +#include "core_message_serialize_proto.h" +#include "crypto_session.h" +#include "license_protocol.pb.h" +#include "log.h" +#include "oec_session_util.h" +#include "oec_test_data.h" +#include "privacy_crypto.h" +#include "service_certificate.h" +#include "string_conversions.h" + +// TODO: refactor oec_session so that these are not needed. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace wvcdm { +namespace { +// This is a sample RSA private key, it pairs with the public fake service +// certificate below. +// From file test_rsa_key_2_carmichael.pk8 in team shared drive. Size is 1216. +const std::string kPrivateKeyFakeServiceCert = a2bs_hex( + "308204bc020100300d06092a864886f70d0101010500048204a6308204a2020100028201" + "0100a700366065dcbd545a2a40b4e1159458114f9458dddea71f3c2ce08809296157675e" + "567eee278f59349a2aaa9db44efaa76ad4c97a53c14e9fe334f73db7c910474f28da3fce" + "317bfd0610ebf7be92f9affb3e68daee1a644cf329f2739e39d8f66fd8b28082718eb5a4" + "f2c23ecd0acab604cd9a138b54735425548cbe987a67addab34eb3fa82a84a6798565754" + "71cd127feda301c06a8b24039688be97662abc53c98306515a88651318e43aed6bf1615b" + "4cc81ef4c2ae085e2d5ff8127fa2fcbb211830dafe40fb01ca2e370ecedd768782460b3a" + "778fc072072c7f9d1e865bed2729df039762ef44d35b3ddb9c5e1b7b39b40b6d046bbbbb" + "2c5fcfb37a050203010001028201000af94a1972881b4ed82fef999332da51212e1406f4" + "e9651cf9d4cf1a5153cd48338c30eddd536f2982f9e074deb11301888fce14c13b90b7cc" + "6cdf35a1f21a3dbe19d70ae46775bbfa87f403b57f69e40b6adc928254641a942de46340" + "b2b4856bc834baa21430471aeb906230434402c70c30c07fa947aede682792aa1195f56f" + "fc198b49a0779dc6135d73ff45a24c3bf3e12dd7c470e26c37994c7aa927f83ad6fdc5d8" + "fa2d0e714b857ececb1c7971bdff63036b5868e014ca5e85fdd0b7e06814ff2c8222268a" + "3fbfb02a90ffc772fc66513e519f82680ef3657488abb7e5975f0f3ee53abca4a150dd5c" + "944b0c7071484ed0ec468fdfa29afed8351a2f02818100cf738cbe6d452d0c0b5d5c6c75" + "78cc3548b698f1b964608c43eb85ab04b67d1b717506e2da84682e7f4ce373b4de514bb6" + "51867bd0e64df3d1cf1afe7f3a83bab3e1ff541393d79c2780b71e649ef7322b4629f7f8" + "186cf74abe4bee96908fa216226acc48067463437f2722443c2d3b62f11cb42733852660" + "4816cbeff8cd3702818100ce15436e4b0ff93f87c3414597b149c2192387e4241c64e528" + "cb431014140e19cbbbdbfd119d1768786d6170633aa1b3f3a75b0effb76111549199e591" + "322deb3fd83ef7d4cbd2a341c1eec69213eb7f4258f4d0b2741d8e8746cd14b816adb5bd" + "0d6c955a16bfe953dafbed835167a955ab54029520a6681753a8ea43e5b0a3028180679c" + "32833957ff73b089648bd6f00a2de2af301c2a97f3909aab9b0b1b4379a0a73de7be8d9c" + "ebdbad40dda90080b8e1b3a16c2592e433b2beeb4d74265f37439c6c17760a812082a148" + "2c2d45dc0f624332bbeb5941f9ca58ce4a665354c828101e087116d802714158d456ccf5" + "b131a3ed008509bf3595412940198335246902818055100bcc3ba9753d16e1ae50766394" + "494cad10cb47687cf0e5dcb86aab8ef79f082c1b8aa2b98fceec5e61a8cd1c87604ac31a" + "5fdf8726c6cb7c69e48b01065922fa344b81873c036d020a77e615d8cfa768266cfa2bd9" + "835a2d0c3b701cd448bea70ad9bedcc30c2133b366ff1c1bc89676e86f4474bc9b1c7dc8" + "ac21a86e370281802c7cad1e75f6691de7a6ca747d67c8652866c443a6bd4057aeb7652c" + "52f9e4c7817b56a3d20de83370cf0684b34e4450756196864bb62badf0ad57d0370d1d35" + "50cb69223929b93ad329230260f7ab3040da8e4d457026f4a20dd0645d473c18f4d45295" + "00ae846b47b23c82d37253de722cf7c12236d91856fe392833e0db03"); + +// This is a fake service certificate. +// From the team shared drive file +// oem-7913-leaf-and-intermediate-certs-test-key-2-carmichael.p7b, size 2353. +const std::string kPublicFakeServiceCert = a2bs_hex( + "3082092d06092a864886f70d010702a082091e3082091a0201013100300f06092a864886" + "f70d010701a0020400a08208fe3082037130820259a003020102021100c28d2022828b9e" + "639d15892ca98fd95d300d06092a864886f70d01010b0500306b310b3009060355040613" + "025553310b300906035504080c0257413111300f06035504070c084b69726b6c616e6431" + "0f300d060355040a0c06476f6f676c653111300f060355040b0c085769646576696e6531" + "18301606035504030c0f73797374656d2069643a2037393133301e170d31383031313131" + "33323632325a170d3338303130363133323632325a30653112301006035504030c093739" + "31332d6c656166310b3009060355040613025553310b300906035504080c025741311130" + "0f06035504070c084b69726b6c616e64310f300d060355040a0c06476f6f676c65311130" + "0f060355040b0c085769646576696e6530820122300d06092a864886f70d010101050003" + "82010f003082010a0282010100a700366065dcbd545a2a40b4e1159458114f9458dddea7" + "1f3c2ce08809296157675e567eee278f59349a2aaa9db44efaa76ad4c97a53c14e9fe334" + "f73db7c910474f28da3fce317bfd0610ebf7be92f9affb3e68daee1a644cf329f2739e39" + "d8f66fd8b28082718eb5a4f2c23ecd0acab604cd9a138b54735425548cbe987a67addab3" + "4eb3fa82a84a679856575471cd127feda301c06a8b24039688be97662abc53c98306515a" + "88651318e43aed6bf1615b4cc81ef4c2ae085e2d5ff8127fa2fcbb211830dafe40fb01ca" + "2e370ecedd768782460b3a778fc072072c7f9d1e865bed2729df039762ef44d35b3ddb9c" + "5e1b7b39b40b6d046bbbbb2c5fcfb37a050203010001a31630143012060a2b06010401d6" + "79040101040402021ee9300d06092a864886f70d01010b050003820101008895eccd8ba7" + "51da7481a539621a0e2ede3c37eaad7cee9b268ee2d634cdb770babfa0a3feb34bbcf41c" + "726681d50933780c6121a8f1e2c9e283c21902f2e8ab17363a0b20af0fae2e7368ac15ee" + "9cc092037e9563aaad159643203be59b1fca02baf0077680d7a31aebc8db037b4356e596" + "6b86fe08588a84bde94718eeb2a8057bf0fdaab985cd7a0e6b6c9fc675d22afe5bf3b731" + "6cace3009fe7dde381c136c31c5fdff2c35efa5532d85ca8e5ccb64ae9e2cc3844074659" + "348479f9ee3c4b4890ab73b0a192c3d6838781ca1281d65df76f7a355e4f02668a478882" + "abf0121db9753b7ba83615efa8120e53b4837853c052aea60aa053dc1c1522dd17983082" + "05853082036da003020102021003b1f758df1de325000b103dd5e6e464300d06092a8648" + "86f70d01010b0500307e310b30090603550406130255533113301106035504080c0a5761" + "7368696e67746f6e3111300f06035504070c084b69726b6c616e64310f300d060355040a" + "0c06476f6f676c653111300f060355040b0c085769646576696e65312330210603550403" + "0c1a7769646576696e652e636f6d2f6f656d2d726f6f742d70726f64301e170d31373131" + "31383031313333355a170d3237313131383031313331335a306b310b3009060355040613" + "025553310b300906035504080c0257413111300f06035504070c084b69726b6c616e6431" + "0f300d060355040a0c06476f6f676c653111300f060355040b0c085769646576696e6531" + "18301606035504030c0f73797374656d2069643a203739313330820122300d06092a8648" + "86f70d01010105000382010f003082010a0282010100aec871ae080c06062d817ca98bb3" + "d666e4f6085e5a75e874617a88ca85140d58a409196c60c9ad911cbf04b34710637f0258" + "c21ebdcc0777aa7e14a8c201cde84660536f2fda172d4d9d0e5db55095aeab6e43e3b000" + "12b405824a2b14630d1f0612aae19de7badae3fc7c6c73ae56f8abf7519331ef8fe4b601" + "2ceb7be4d8b3ea70378905a9515772989ea846dbeb7a382b2fc027b7c2e19a17dff5d69c" + "d58cb86642d5041e7c364c1e3e45514d417222533df4577c6c33342445df84874aa6cb7c" + "03a3aa8e2d8201278774821abc0f7669abe04e70be37fcc82c91174fd5263b7b90b52d64" + "baf7d28ab48f389d8ebae75c52f10ab8c01bb6b1707e475994590203010001a382011030" + "82010c30120603551d130101ff040830060101ff020100300e0603551d0f0101ff040403" + "020204301d0603551d0e041604144bcbdfaa02de8dc3e7e585db2e8abe756b8a67583081" + "b20603551d230481aa3081a78014049466aaf96189b6dbb5f713383d6284b8180a8fa181" + "83a48180307e310b30090603550406130255533113301106035504080c0a57617368696e" + "67746f6e3111300f06035504070c084b69726b6c616e64310f300d060355040a0c06476f" + "6f676c653111300f060355040b0c085769646576696e653123302106035504030c1a7769" + "646576696e652e636f6d2f6f656d2d726f6f742d70726f64820900df86053101be9a9a30" + "12060a2b06010401d679040101040402021ee9300d06092a864886f70d01010b05000382" + "020100613f2f43e4be6634ef9206e988ba6a1d4f545a97b175d793f845c6839236fd55a9" + "210bdcf6ae11dc622144bd041d582c03f8e4e21ebae6dd19dd56fdce06735f941eb603db" + "3d7babab72647bde7d4dcf7ef09129c17713c26f80ab7aa8ceb01c2ac59cfb0be59f9c1b" + "c94b58df9618f7676789a4e91448acfa9d862aeb752c2bbf637dc74e7ead392db47c07a5" + "5ae83ad4f50c4ff3a29c3c32ed9d4b4905bc1fa013e6dd827906313bc697ec8daa4fef14" + "3c21f672b20942c774feef70bde98541300bb36b590c0f1175d4bbb1dfb1dfb3fab33a43" + "177d8a82aea207f88351fb16fb64b646dabe322bc0ee782a84a9540af92d6165dea59766" + "7902f89717e2d49f9eacccae999a0304bb45feb2f580babfdd24e5e61e5d36a5870cdf60" + "816fb75fb91fca753c1a63b0ebe695860daea6c92a94f1d0be75c8f807d788ffecf9cd49" + "c6fe4d7f441ed8afa9722798e25a08ea55d3b3eadc7669511001467d33949c94effe761c" + "c6d715533e8d3d299a586af1759eea1b4cf04776acc6a2324440dffeff9df4e2c2faa15f" + "2e66e997cb27266e53e4e8862cead3696c614ffec1c98b05926f4796cef033fa7c78249b" + "d78d36563786bc725af9b9b093f0817810f2b0c279915ecfbc8cf2320ff72d30d813774f" + "789e408de63a98b2aa134d2549346c809e1903dbcdf5b154741b673c46ac3e5da2d91383" + "30eb823b06ab3c397dd0683100"); + +// This is a private RSA key that is paired with the DRM certificate below. +const std::string kPrivateKeySampleDRMCert = a2bs_hex( + "308204BC020100300D06092A864886F70D0101010500048204A6308204A202010002820101" + "00E68EAD7C67ED983A72C89BC55054D26821C3399702E7906B77C7E09AE607D40B0013484B" + "0C557A810E19A814B4F14D55E60456EE21BC19F29EFFDA416BC9CBF0CE2C684E5A44F77008" + "038E0666EE7995166381E00F7EB2C38845F5CD2B790D35570D07D7EE225ED94A5D9421EE60" + "BCD075ADFBA71F4E7363DB5E0B1E05352F6945A3A1F0E41899A3BF1DE322FEECCCD577225B" + "CB79550D0014343EA64312EB32919B6E8D6EC1E349FA54074D829E80C8CE7E1E713AB50917" + "A4AC98C898A85F7C267417D35180CF1BF3E6CABE04C8AC6422F36137FAD800A83EABB8C970" + "C4248435F1B4C9C659865864BC714AF4D9A425F28626BEF9DB3A42510135FF2B69213F0203" + "010001028201000C5D9CAA6E7C8CCC9DB96AB9637C9928629F30E88B8C55EF9DA607C2E711" + "866AEC9F1C22824FD75932A367A36CAD0083D9E963AC33FCFDBB4891DA67E5DB15E81D76BE" + "456D8C03656BD89CF674F0D76E8A9BDDAC61C85ED823E7F4AE0365E3B277AFC83AE997C854" + "892B89B5642EA611DC2DEFB05FFA7A2FE1E5225D82D3FE6DE1F1B3DE0A5386E1F75FE21E20" + "E38A517FDFF1598BF9C2210EDDED222D9C6045494015E7FDC76084E57D2A58184E8F84153D" + "BD1CC5E0C2FB84085EEAA42A046205AA1B0BDC30DCF077941AC001C03BEE1E6F3F8E0D6A95" + "1C9672B54C11F3BBAB5F9F18816F32A7E3A1F1755CDEA30C2805D21C90891B6B1CC9601B0B" + "DE8601A902818100F41622BA193DF0D70D76FB47D38BFA711D52530DDC438B7F4A88685B60" + "B5D4A1937E8B63EC6FF4EA650BF129A7649A2A5376622AE2D5B4AD2064FC9E0A05C28D0510" + "412DDF5660BB008C4340965B80CC0A9CCA559B783179B6ECA6952999A86B8CECCE1582C3E0" + "E603E357D2FF59181EA3E3A25361C4E287DEB192132A4C2E2D02818100F1CF7ECBB795E456" + "D5C976E512EE63C25C79A3FAE9CF4B8C838950B69E97373819E838266678CD11DF45E9E9B0" + "A53183D79C6E1713A9F47A51E39DFC730EA76B3B0460E1A8E1BC932A84875610F79267C81F" + "F794E1FC80A8687AFE3120CC6A3EBAC1735FDFF20FE7809879F205F7C933845FC6B8D18DD7" + "88031B3E8B19155C9B028180510FCCE6AB1D640FB79C0D25B47EE7648B8D5CA1DCC5DDDD1F" + "5E9FF1C0F382334AED9AD34BA17EE01D40D30DB756F4D01BB9D42E53F90F30F3F235E73282" + "E932B63CC8B8B8545279A85BECB5D5797C13C76E7CCFE37B0E4B52D1D31CF49CE04F1F9541" + "77E95EAE2115A779F24BF545CA5F39691E71F8D616B3819B769BF482DD028180491A86E5C5" + "B1BE1F76707ACE5443D7CAF9B4189C11B586CC8B33A7401E7FEEC4BA2857595C9F66B7E17D" + "3C7356E10A3026ADF72668DE77B7C72BFE26450E8814C5F9D3E444EF41D868013AFD0D121B" + "A3DE7FB394C221593010AE264CE9F282A8464397C2C36C65DC822716AED19910ADCF763918" + "C4D991F05FA80BE77784DAC30281800C5FDD98BB2EF766B9D43784CC2FC22FC27D6F41BDCA" + "9A4EFB0197FFD599C3528B31E42B12BBA3788019C9B546F66B22C8AE91BAB5B4D86FF46C2F" + "6AF3D579C834A301AE61E0C01CA914D8C4DAA3AE0DA92ABEC1122A78055E8B257D0658E080" + "BC7C1184BDDA1133EB9951A1E80F2A4F2DC01A01A6035337DADE95E7B77394CB"); + +// This is a DRM certificate that was intercepted from a provisioning response +// from the production server to a device with the test keybox. +const std::string kPublicSampleDRMCert = a2bs_hex( + "0ABC02080212107CB49F987A635E1E0A52184694582D6E18A2C99EEC05228E023082010A02" + "82010100E68EAD7C67ED983A72C89BC55054D26821C3399702E7906B77C7E09AE607D40B00" + "13484B0C557A810E19A814B4F14D55E60456EE21BC19F29EFFDA416BC9CBF0CE2C684E5A44" + "F77008038E0666EE7995166381E00F7EB2C38845F5CD2B790D35570D07D7EE225ED94A5D94" + "21EE60BCD075ADFBA71F4E7363DB5E0B1E05352F6945A3A1F0E41899A3BF1DE322FEECCCD5" + "77225BCB79550D0014343EA64312EB32919B6E8D6EC1E349FA54074D829E80C8CE7E1E713A" + "B50917A4AC98C898A85F7C267417D35180CF1BF3E6CABE04C8AC6422F36137FAD800A83EAB" + "B8C970C4248435F1B4C9C659865864BC714AF4D9A425F28626BEF9DB3A42510135FF2B6921" + "3F020301000128E83D3A0C7769646576696E652E636F6D1280028F1B043BBFFF8B126F970C" + "9F0722C19DCDDD9A02146A8527249DC7CB78FEC38AF92935B37003B3B47D492A94E8787D40" + "86C6E754D4EB77A8C92F721A6516D7F91A308E8CE2EC4712C4FAA3ADB3482A63D816B10902" + "245D96B38D61F5D0053FC0302B338763F9CB1F3E843BDDE2B520770872F844E8C6BD8C611A" + "FA81EA5542BF0312335FC13ACC0679747BBC2F44459CDAA7241B119BE0158A0A90A2567C8B" + "096D733727E22B0CA24E248A94649F1F4D80CF588115E6675DF01C0192A4D0C5E6FCE326E4" + "2368DE6BF572D97D1AF140AC7DECDF8DC41A0276437D52AFDA2F7A6E76DA2B4466EBC63AA9" + "EA827DAC355B815D306A431CEEDF1D9C62E478E33B4CC41AB4050AAE020801121065802C9B" + "625E5A319C33DC1CB7C3C6D418E3A5BDD005228E023082010A0282010100B80502043C2A8A" + "0FD8D25C613E1E3E3B5E349F332F04516A7510D38021A5629B9AA027AEAD3C759B7AFE70BE" + "D65F3DF6860FF5EB60B983A3FFA33FDE06F3B73014DFC845AB371C6600562E9D904F842B8B" + "A4A5D9200FFA3ED45D705520A5C372A889F9E314386234C6897AE655851FCD9ADB4EF9126C" + "78386EA93BCB25BA3EC475C55C608E771C763AB02506F9B07252D6ABF7EA64B1EBDE7B95C6" + "407690533BD6890B9274C16066F74FC401EA355F0A02106814D49BF0C89E6E1F8DB2A47841" + "CD0DAD793296A107C36223404F2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A038BD3" + "E4BBBAE4391BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F020301000128E83D1280" + "037E06581A019184AB572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D68009B" + "1C4644F9B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB03B2DA7" + "B68302FAA6DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A91C051C" + "A280A86669710A1AD7A44BF9218027460DF694E2E9270396DF221963F21EE6AA220A5EE4A4" + "D0FEB3D53EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56F7416420E05E05" + "9F9734A96BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667B33B8114C76A8838C4" + "23D824A50B23251A088136D6E8F475299D2AFD46CEA51B5CBDF789A572125CD24FBB813B38" + "7A10CD2A30E3447634AB3408F96B9CF3D98896D405F3F540D9C57962760FCD177CDD101EB8" + "A4148B9C29CED5EAD645A95B698F1CDC6E1DB6678B85074186080D68D13CD37E07B16DE370" + "CD9AFB9B25564A73A30E2AF8085EA37D310C474F0E67AC00CA992A5296FAEDAD7AA06ECD79" + "0F1E3D426558FA98383E3CD2ED4830"); +} // namespace + +FakeProvisioningServer::FakeProvisioningServer() { + // Generate a service certificate that can convince the CDM we are a real + // provisioning server. it only works if the CDM is compiled with the symbol + // ACCEPT_TEST_CERT defined. + video_widevine::DrmDeviceCertificate cert; + cert.set_type(video_widevine::DrmDeviceCertificate_CertificateType_SERVICE); + + cert.set_public_key(kPublicFakeServiceCert); + cert.set_serial_number("Serial Number 007"); + cert.set_provider_id("Unit Test Provider"); + + std::string serialized_cert; + cert.SerializeToString(&serialized_cert); + + video_widevine::SignedDrmDeviceCertificate signed_cert; + signed_cert.set_drm_certificate(serialized_cert); + signed_cert.SerializeToString(&service_certificate_); +} + +bool FakeProvisioningServer::MakeResponse( + const std::string& serialized_signed_request, std::string* json_response) { + if (json_response == nullptr) { + LOGE("json_response is jullptr."); + return false; + } + json_response->clear(); + + // First, parse the signed request and the request. + video_widevine::SignedProvisioningMessage signed_request; + if (!signed_request.ParseFromString(serialized_signed_request)) { + LOGE("Failed to parse signed provisioning request"); + return false; + } + std::string serialized_message = signed_request.message(); + video_widevine::ProvisioningRequest provisioning_request; + if (!provisioning_request.ParseFromString(serialized_message)) { + LOGE("Failed to parse provisioning request"); + return false; + } + std::string request_signature = signed_request.signature(); + LOGD("Requested certificate type %s", + provisioning_request.options().certificate_type() == + video_widevine::ProvisioningOptions::WIDEVINE_DRM + ? "WIDEVINE_DRM" + : "X509"); + + video_widevine::SignedProvisioningMessage::ProtocolVersion version = + signed_request.protocol_version(); + LOGD("Request uses protocol version: %d", version); + if (version != video_widevine::SignedProvisioningMessage::PROVISIONING_20) { + LOGE("Fake provisioning server only handles Keyboxes"); + return false; + } + + // Next, we derive the keys from the keybox device key. This is Provisioning + // 2.0 specific. + // TODO(b/141438127): Add support for provisioing 3.0. + std::string mac_context; + GenerateMacContext(serialized_message, &mac_context); + std::vector mac_context_v(mac_context.begin(), mac_context.end()); + std::string enc_context; + GenerateEncryptContext(serialized_message, &enc_context); + std::vector enc_context_v(enc_context.begin(), enc_context.end()); + wvoec::KeyDeriver key_deriver; + // Not only is this Prov 2.0 specific, it assumes the device is using the + // standard test keybox. + key_deriver.DeriveKeys(wvoec::kTestKeybox.device_key_, mac_context_v, + enc_context_v); + + // Create a structure to hold the RSA private key. This is used by the key + // deriver to encrypt the key. + wvoec::RSAPrivateKeyMessage clear_rsa_key; + if (sizeof(clear_rsa_key.rsa_key) < kPrivateKeySampleDRMCert.size()) { + LOGE("Private key too big"); + return false; + } + memcpy(clear_rsa_key.rsa_key, kPrivateKeySampleDRMCert.data(), + kPrivateKeySampleDRMCert.size()); + clear_rsa_key.rsa_key_length = kPrivateKeySampleDRMCert.size(); + wvoec::RSAPrivateKeyMessage encrypted_rsa_key; + key_deriver.PadAndEncryptProvisioningMessage(&clear_rsa_key, + &encrypted_rsa_key); + + // Fill out the provisioning response with the public DRM cert, and the + // encrypted private RSA key. + video_widevine::ProvisioningResponse provisioning_response; + provisioning_response.set_device_certificate(kPublicSampleDRMCert); + provisioning_response.set_device_rsa_key(std::string( + encrypted_rsa_key.rsa_key, + encrypted_rsa_key.rsa_key + encrypted_rsa_key.rsa_key_length)); + provisioning_response.set_device_rsa_key_iv(std::string( + encrypted_rsa_key.rsa_key_iv, + encrypted_rsa_key.rsa_key_iv + sizeof(encrypted_rsa_key.rsa_key_iv))); + provisioning_response.set_nonce(provisioning_request.nonce()); + + // Sign the response. + video_widevine::SignedProvisioningMessage signed_response; + signed_response.set_protocol_version(signed_request.protocol_version()); + std::string message; + provisioning_response.SerializeToString(&message); + signed_response.set_message(message); + + if (signed_request.has_oemcrypto_core_message()) { + // If the request has a core message, then the response should also have a + // core message. + std::string core_request = signed_request.oemcrypto_core_message(); + oemcrypto_core_message::ODK_ProvisioningRequest core_request_data; + if (!oemcrypto_core_message::deserialize:: + CoreProvisioningRequestFromMessage(core_request, + &core_request_data)) { + LOGE("Core request did not parse."); + return false; + } + std::string core_response; + oemcrypto_core_message::serialize::CreateCoreProvisioningResponseFromProto( + message, core_request_data, &core_response); + signed_response.set_oemcrypto_core_message(core_response); + // Also, the signature should be over the concatenation of the core message + // and the message body. This is done to ensure that neither the message + // body, nor the oemcrypto core message are tampered with. See "Widevine + // Core Message Serialization" for details. + message = core_response + message; + } + + std::vector signature_v; + key_deriver.ServerSignBuffer(reinterpret_cast(message.data()), + message.size(), &signature_v); + std::string signature(signature_v.begin(), signature_v.end()); + signed_response.set_signature(signature); + + // Finally, base 64 encode and surround with just enough fake json to fool the + // CDM. + std::string response_data; + signed_response.SerializeToString(&response_data); + std::vector response_data_v(response_data.begin(), + response_data.end()); + static const std::string json_start = "{ \"signedResponse\": \""; + static const std::string json_end = "\" }"; + *json_response = json_start + Base64SafeEncode(response_data_v) + json_end; + return true; +} + +} // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/fake_provisioning_server.h b/libwvdrmengine/cdm/core/test/fake_provisioning_server.h new file mode 100644 index 00000000..77dbeb8b --- /dev/null +++ b/libwvdrmengine/cdm/core/test/fake_provisioning_server.h @@ -0,0 +1,32 @@ +// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef WVCDM_CORE_FAKE_PROVISIONING_SERVER_H_ +#define WVCDM_CORE_FAKE_PROVISIONING_SERVER_H_ + +#include + +#include "config_test_env.h" + +namespace wvcdm { + +// A fake provisioning server. This can be used to generate custom made +// provisioning responses. +class FakeProvisioningServer { + public: + FakeProvisioningServer(); + + // Get the service certificate for this server. + const std::string& service_certificate() { return service_certificate_; } + // Make a response for this request. Returns true on success. + bool MakeResponse(const std::string& serialized_signed_request, + std::string* json_response); + + private: + std::string service_certificate_; +}; + +} // namespace wvcdm + +#endif // WVCDM_CORE_FAKE_PROVISIONING_SERVER_H_ diff --git a/libwvdrmengine/cdm/core/test/license_unittest.cpp b/libwvdrmengine/cdm/core/test/license_unittest.cpp index c87d3373..57e44796 100644 --- a/libwvdrmengine/cdm/core/test/license_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/license_unittest.cpp @@ -12,6 +12,7 @@ #include "crypto_session.h" #include "initialization_data.h" #include "license.h" +#include "mock_clock.h" #include "policy_engine.h" #include "properties.h" #include "service_certificate.h" @@ -112,6 +113,7 @@ const std::string kLicenseRequestSignature = a2bs_hex( "8D24103EB15C63C227A0D57A9D90F5A409D2D55147EE10A35AE291D2D725C7F161FF827221" "9AE18B91516E0CDD0B581590DDDEA2A2527E2C9ABA273629B586A9D22D451A827E332CFC3E" "9BEDB6CF3D8713F9E11675DF1F5DB9038DBBECAB9D1683F8722CAF6E18EC8C04AEE5"); +const std::string kFakeCoreMessage = a2bs_hex("DEADBEEF"); const CryptoSession::SupportedCertificateTypes kDefaultSupportedCertTypes = { true, true, true}; @@ -142,8 +144,8 @@ class MockCryptoSession : public TestCryptoSession { MOCK_METHOD1(GetSupportedCertificateTypes, bool(SupportedCertificateTypes*)); MOCK_METHOD1(GetApiVersion, bool(uint32_t*)); MOCK_METHOD1(GenerateNonce, CdmResponseType(uint32_t*)); - MOCK_METHOD3(PrepareRequest, - CdmResponseType(const std::string&, bool, std::string*)); + MOCK_METHOD3(PrepareAndSignLicenseRequest, + CdmResponseType(const std::string&, std::string*, std::string*)); MOCK_METHOD1(LoadEntitledContentKeys, CdmResponseType(const std::vector& key_array)); MOCK_METHOD1(GetResourceRatingTier, bool(uint32_t*)); @@ -158,11 +160,6 @@ class MockPolicyEngine : public PolicyEngine { void(const std::vector&)); }; -class MockClock : public Clock { - public: - MOCK_METHOD0(GetCurrentTime, int64_t()); -}; - class MockInitializationData : public InitializationData { public: MockInitializationData(const std::string& type, const CdmInitData& data) @@ -326,9 +323,11 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidation) { EXPECT_CALL(*clock_, GetCurrentTime()).WillOnce(Return(kLicenseStartTime)); EXPECT_CALL(*crypto_session_, GenerateNonce(NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kNonce), Return(NO_ERROR))); - EXPECT_CALL(*crypto_session_, PrepareRequest(_, Eq(false), NotNull())) - .WillOnce( - DoAll(SetArgPointee<2>(kLicenseRequestSignature), Return(NO_ERROR))); + EXPECT_CALL(*crypto_session_, + PrepareAndSignLicenseRequest(_, NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(kFakeCoreMessage), + SetArgPointee<2>(kLicenseRequestSignature), + Return(NO_ERROR))); CreateCdmLicense(); EXPECT_TRUE(cdm_license_->Init(kToken, kClientTokenDrmCert, kEmptyString, @@ -353,6 +352,8 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidation) { EXPECT_TRUE(std::equal(signed_message.signature().begin(), signed_message.signature().end(), kLicenseRequestSignature.begin())); + EXPECT_TRUE(signed_message.has_oemcrypto_core_message()); + EXPECT_EQ(kFakeCoreMessage, signed_message.oemcrypto_core_message()); EXPECT_TRUE(!signed_message.msg().empty()); // Verify license request @@ -447,9 +448,11 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidationV15) { EXPECT_CALL(*clock_, GetCurrentTime()).WillOnce(Return(kLicenseStartTime)); EXPECT_CALL(*crypto_session_, GenerateNonce(NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kNonce), Return(NO_ERROR))); - EXPECT_CALL(*crypto_session_, PrepareRequest(_, Eq(false), NotNull())) - .WillOnce( - DoAll(SetArgPointee<2>(kLicenseRequestSignature), Return(NO_ERROR))); + EXPECT_CALL(*crypto_session_, + PrepareAndSignLicenseRequest(_, NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(kFakeCoreMessage), + SetArgPointee<2>(kLicenseRequestSignature), + Return(NO_ERROR))); CreateCdmLicense(); EXPECT_TRUE(cdm_license_->Init(kToken, kClientTokenDrmCert, kEmptyString, @@ -474,6 +477,8 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidationV15) { EXPECT_TRUE(std::equal(signed_message.signature().begin(), signed_message.signature().end(), kLicenseRequestSignature.begin())); + EXPECT_TRUE(signed_message.has_oemcrypto_core_message()); + EXPECT_EQ(kFakeCoreMessage, signed_message.oemcrypto_core_message()); EXPECT_TRUE(!signed_message.msg().empty()); // Verify license request diff --git a/libwvdrmengine/cdm/core/test/test_base.cpp b/libwvdrmengine/cdm/core/test/test_base.cpp index a2ab575a..6ee43e6d 100644 --- a/libwvdrmengine/cdm/core/test/test_base.cpp +++ b/libwvdrmengine/cdm/core/test/test_base.cpp @@ -1,8 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine Master // License Agreement. -// This file adds some print methods so that when unit tests fail, the -// will print the name of an enumeration instead of the numeric value. #include "test_base.h" @@ -17,7 +15,9 @@ #include #include "cdm_engine.h" +#include "clock.h" #include "crypto_session.h" +#include "fake_provisioning_server.h" #include "file_store.h" #include "license.h" #include "log.h" @@ -26,6 +26,7 @@ #include "platform.h" #include "properties.h" #include "test_printers.h" +#include "test_sleep.h" #include "url_request.h" using wvcdm::metrics::EngineMetrics; @@ -98,6 +99,11 @@ void show_menu(const char* prog_name, const std::string& extra_help_text) { << " in the url" << std::endl << std::endl; + std::cout << " --fake_sleep" << std::endl; + std::cout << " Use a fake clock to sleep for duration tests. This cannot" + << " be used with a real OEMCrypto." << std::endl + << std::endl; + std::cout << extra_help_text << std::endl; } @@ -201,7 +207,7 @@ CdmResponseType TestCryptoSession::GenerateNonce(uint32_t* nonce) { for (int i = 0; status != NO_ERROR; i++) { LOGV("Recovering from nonce flood."); if (i > 2) return status; - sleep(1); + TestSleep::Sleep(1); status = CryptoSession::GenerateNonce(nonce); } return NO_ERROR; @@ -273,104 +279,127 @@ void WvCdmTestBase::Provision() { CdmSessionId session_id; FileSystem file_system; - // TODO(fredgc): provision for different SPOIDs. - CdmEngine cdm_engine(&file_system, - std::shared_ptr(new EngineMetrics)); + if (config_.provisioning_server() == "fake") { + LOGD("Using fake provisioning server."); - CdmResponseType result = cdm_engine.GetProvisioningRequest( - cert_type, cert_authority, config_.provisioning_service_certificate(), - &prov_request, &provisioning_server_url); - ASSERT_EQ(NO_ERROR, result); - - if (binary_provisioning_) { - binary_prov_request = prov_request; - prov_request = std::string(Base64SafeEncodeNoPad(std::vector( - binary_prov_request.begin(), binary_prov_request.end()))); - } - - LOGV("Provisioning request: req = %s", prov_request.c_str()); - - // Ignore URL provided by CdmEngine. Use ours, as configured - // for test vs. production server. - provisioning_server_url.assign(config_.provisioning_server()); - - // TODO(b/139361531): Remove loop once provisioning service is stable. - std::string http_message; - size_t attempt_num = 0; - bool provision_success = false; - do { - if (attempt_num > 0) { - // Sleep between attempts. - std::this_thread::sleep_for(std::chrono::seconds(1)); + CdmEngine cdm_engine(&file_system, + std::shared_ptr(new EngineMetrics)); + FakeProvisioningServer server; + CdmResponseType result = cdm_engine.GetProvisioningRequest( + cert_type, cert_authority, server.service_certificate(), &prov_request, + &provisioning_server_url); + ASSERT_EQ(NO_ERROR, result); + if (!binary_provisioning_) { + std::vector prov_request_v = Base64SafeDecode(prov_request); + prov_request = std::string(prov_request_v.begin(), prov_request_v.end()); } - ++attempt_num; + std::string response; + ASSERT_TRUE(server.MakeResponse(prov_request, &response)) + << "Fake provisioning server could not provision"; + result = + cdm_engine.HandleProvisioningResponse(response, &cert, &wrapped_key); + EXPECT_EQ(NO_ERROR, result); + } else { + // TODO(fredgc): provision for different SPOIDs. + CdmEngine cdm_engine(&file_system, + std::shared_ptr(new EngineMetrics)); - // Make request. - UrlRequest url_request(provisioning_server_url); - if (!url_request.is_connected()) { - LOGE("Failed to connect to provisioning server: url = %s", - provisioning_server_url.c_str()); - continue; - } - url_request.PostCertRequestInQueryString(prov_request); - - // Receive and parse response. - if (!url_request.GetResponse(&http_message)) { - LOGE("Failed to get provisioning response"); - continue; - } - - LOGV("http_message: \n%s\n", http_message.c_str()); + CdmResponseType result = cdm_engine.GetProvisioningRequest( + cert_type, cert_authority, config_.provisioning_service_certificate(), + &prov_request, &provisioning_server_url); + ASSERT_EQ(NO_ERROR, result); if (binary_provisioning_) { - // extract provisioning response from received message - // Extracts signed response from JSON string, result is serialized - // protobuf. - static const std::string kMessageStart = "\"signedResponse\": \""; - static const std::string kMessageEnd = "\""; - std::string protobuf_response; - if (!ExtractSignedMessage(http_message, kMessageStart, kMessageEnd, - &protobuf_response)) { - LOGE("Failed to extract signed serialized response from JSON response"); - continue; - } - - LOGV("Extracted response message: \n%s\n", protobuf_response.c_str()); - - // base64 decode response to yield binary protobuf - std::vector response_vec(Base64SafeDecode(protobuf_response)); - if (response_vec.empty() && !protobuf_response.empty()) { - LOGE("Failed to decode base64 of response: response = %s", - protobuf_response.c_str()); - continue; - } - - std::string binary_protobuf_response(response_vec.begin(), - response_vec.end()); - - if (cdm_engine.HandleProvisioningResponse(binary_protobuf_response, &cert, - &wrapped_key) != NO_ERROR) { - LOGE("Failed to handle provisioning response"); - continue; - } - } else { - if (cdm_engine.HandleProvisioningResponse(http_message, &cert, - &wrapped_key) != NO_ERROR) { - LOGE("Failed to handle binary provisioning response"); - continue; - } + binary_prov_request = prov_request; + prov_request = std::string(Base64SafeEncodeNoPad(std::vector( + binary_prov_request.begin(), binary_prov_request.end()))); } - provision_success = true; - } while (attempt_num <= kDefaultMaxProvisioningAttempts && - !provision_success); - if (attempt_num > 1) { - LOGW("Provisioning request failed at least once: attempts = %zu", - attempt_num); + LOGV("Provisioning request: req = %s", prov_request.c_str()); + + // Ignore URL provided by CdmEngine. Use ours, as configured + // for test vs. production server. + provisioning_server_url.assign(config_.provisioning_server()); + + // TODO(b/139361531): Remove loop once provisioning service is stable. + std::string http_message; + size_t attempt_num = 0; + bool provision_success = false; + do { + if (attempt_num > 0) { + // Sleep between attempts. + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + ++attempt_num; + + // Make request. + UrlRequest url_request(provisioning_server_url); + if (!url_request.is_connected()) { + LOGE("Failed to connect to provisioning server: url = %s", + provisioning_server_url.c_str()); + continue; + } + url_request.PostCertRequestInQueryString(prov_request); + + // Receive and parse response. + if (!url_request.GetResponse(&http_message)) { + LOGE("Failed to get provisioning response"); + continue; + } + + LOGV("http_message: \n%s\n", http_message.c_str()); + + if (binary_provisioning_) { + // extract provisioning response from received message + // Extracts signed response from JSON string, result is serialized + // protobuf. + static const std::string kMessageStart = "\"signedResponse\": \""; + static const std::string kMessageEnd = "\""; + std::string protobuf_response; + if (!ExtractSignedMessage(http_message, kMessageStart, kMessageEnd, + &protobuf_response)) { + LOGE( + "Failed to extract signed serialized response from JSON " + "response"); + continue; + } + + LOGV("Extracted response message: \n%s\n", protobuf_response.c_str()); + + // base64 decode response to yield binary protobuf + std::vector response_vec(Base64SafeDecode(protobuf_response)); + if (response_vec.empty() && !protobuf_response.empty()) { + LOGE("Failed to decode base64 of response: response = %s", + protobuf_response.c_str()); + continue; + } + + std::string binary_protobuf_response(response_vec.begin(), + response_vec.end()); + + if (cdm_engine.HandleProvisioningResponse( + binary_protobuf_response, &cert, &wrapped_key) != NO_ERROR) { + LOGE("Failed to handle provisioning response"); + continue; + } + } else { + if (cdm_engine.HandleProvisioningResponse(http_message, &cert, + &wrapped_key) != NO_ERROR) { + LOGE("Failed to handle binary provisioning response"); + continue; + } + } + provision_success = true; + } while (attempt_num <= kDefaultMaxProvisioningAttempts && + !provision_success); + + if (attempt_num > 1) { + LOGW("Provisioning request failed at least once: attempts = %zu", + attempt_num); + } + ASSERT_TRUE(provision_success) + << "Failed to provision: message = " << http_message; } - - ASSERT_TRUE(provision_success) - << "Failed to provision: message = " << http_message; } // TODO(fredgc): Replace this with a pre-defined DRM certificate. We could do @@ -399,7 +428,6 @@ bool WvCdmTestBase::Initialize(int argc, const char* const argv[], const std::string& extra_help_text) { Properties::Init(); bool is_cast_receiver = false; - bool force_load_test_keybox = false; // TODO(fredgc): obsolete. remove. bool filter_tests = true; bool show_usage = false; int verbosity = 0; @@ -413,6 +441,11 @@ bool WvCdmTestBase::Initialize(int argc, const char* const argv[], filter_tests = false; } else if (arg == "--cast") { is_cast_receiver = true; + } else if (arg == "--fake_sleep") { + wvcdm::TestSleep::set_real_sleep(false); + } else if (arg.find("--gtest") == 0) { + // gtest arguments will be passed to gtest by the main program. + continue; } else { const auto index = arg.find('='); if (index == std::string::npos) { @@ -465,14 +498,17 @@ bool WvCdmTestBase::Initialize(int argc, const char* const argv[], // Displays server url, port and key Id being used std::cout << std::endl; - std::cout << "Default Server: " << default_config_.license_server() + std::cout << "Default Provisioning Server: " + << default_config_.provisioning_server() << std::endl; + std::cout << "Default License Server: " << default_config_.license_server() << std::endl; std::cout << "Default KeyID: " << default_config_.key_id() << std::endl << std::endl; // Figure out which tests are appropriate for OEMCrypto, based on features // supported. - wvoec::global_features.Initialize(is_cast_receiver, force_load_test_keybox); + wvoec::global_features.Initialize(); + wvoec::global_features.set_cast_receiver(is_cast_receiver); // If the user requests --no_filter, we don't change the filter, otherwise, we // filter out features that are not supported. if (filter_tests) { @@ -715,4 +751,26 @@ bool TestLicenseHolder::DeriveKey(const std::vector& key, return true; } +std::string MakePSSH(const video_widevine::WidevinePsshData& header) { + std::string data; + header.SerializeToString(&data); + return MakePSSH(data); +} + +std::string MakePSSH(const std::string& serialized_header) { + const uint32_t version = 0; + const std::string system_id = InitializationData::WidevineSystemID(); + size_t system_id_size = system_id.size(); + size_t data_size = serialized_header.size(); + size_t atom_size = data_size + system_id_size + 4 * 4; + + std::string pssh = EncodeUint32(atom_size); + pssh.append("pssh"); + pssh.append(EncodeUint32(version)); + pssh.append(system_id); + pssh.append(EncodeUint32(data_size)); + pssh.append(serialized_header); + return pssh; +} + } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/test_base.h b/libwvdrmengine/cdm/core/test/test_base.h index dd0a84e5..162869a5 100644 --- a/libwvdrmengine/cdm/core/test/test_base.h +++ b/libwvdrmengine/cdm/core/test/test_base.h @@ -147,6 +147,12 @@ class TestLicenseHolder { std::vector session_key_; }; +// Given a PSSH data structure, this makes a PSSH string for use in +// generating a license request. +std::string MakePSSH(const video_widevine::WidevinePsshData& header); +// Given a serialized PSSH data, this generates a full PSSH string. +std::string MakePSSH(const std::string& serialized_header); + } // namespace wvcdm #endif // WVCDM_CORE_TEST_BASE_H_ diff --git a/libwvdrmengine/cdm/core/test/test_main.cpp b/libwvdrmengine/cdm/core/test/test_main.cpp index 92c99b9d..5f74aa71 100644 --- a/libwvdrmengine/cdm/core/test/test_main.cpp +++ b/libwvdrmengine/cdm/core/test/test_main.cpp @@ -11,7 +11,7 @@ #include "test_base.h" int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); if (!wvcdm::WvCdmTestBase::Initialize(argc, argv)) return 0; + ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } diff --git a/libwvdrmengine/cdm/core/test/test_printers.cpp b/libwvdrmengine/cdm/core/test/test_printers.cpp index cb883b42..ae4cc820 100644 --- a/libwvdrmengine/cdm/core/test/test_printers.cpp +++ b/libwvdrmengine/cdm/core/test/test_printers.cpp @@ -56,12 +56,6 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case CERT_PROVISIONING_RESPONSE_ERROR_4: *os << "CERT_PROVISIONING_RESPONSE_ERROR_4"; break; - case CERT_PROVISIONING_RESPONSE_ERROR_5: - *os << "CERT_PROVISIONING_RESPONSE_ERROR_5"; - break; - case CERT_PROVISIONING_RESPONSE_ERROR_6: - *os << "CERT_PROVISIONING_RESPONSE_ERROR_6"; - break; case CERT_PROVISIONING_RESPONSE_ERROR_7: *os << "CERT_PROVISIONING_RESPONSE_ERROR_7"; break; @@ -89,6 +83,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case COPY_OLD_USAGE_ENTRY_UNKNOWN_ERROR: *os << "COPY_OLD_USAGE_ENTRY_UNKNOWN_ERROR"; break; + case CORE_MESSAGE_NOT_FOUND: + *os << "CORE_MESSAGE_NOT_FOUND"; + break; case CREATE_USAGE_ENTRY_UNKNOWN_ERROR: *os << "CREATE_USAGE_ENTRY_UNKNOWN_ERROR"; break; @@ -395,6 +392,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case INVALID_SESSION_1: *os << "INVALID_SESSION_1"; break; + case INVALID_SESSION_2: + *os << "INVALID_SESSION_2"; + break; case INVALID_SESSION_ID: *os << "INVALID_SESSION_ID"; break; @@ -533,6 +533,15 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case LOAD_KEY_ERROR: *os << "LOAD_KEY_ERROR"; break; + case LOAD_LICENSE_ERROR: + *os << "LOAD_LICENSE_ERROR"; + break; + case LOAD_PROVISIONING_ERROR: + *os << "LOAD_PROVISIONING_ERROR"; + break; + case LOAD_RENEWAL_ERROR: + *os << "LOAD_RENEWAL_ERROR"; + break; case LOAD_SRM_ERROR: *os << "LOAD_SRM_ERROR"; break; @@ -710,9 +719,6 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case RESTORE_OFFLINE_LICENSE_ERROR_2: *os << "RESTORE_OFFLINE_LICENSE_ERROR_2"; break; - case REWRAP_DEVICE_RSA_KEY_30_ERROR: - *os << "REWRAP_DEVICE_RSA_KEY_30_ERROR"; - break; case REWRAP_DEVICE_RSA_KEY_ERROR: *os << "REWRAP_DEVICE_RSA_KEY_ERROR"; break; @@ -779,9 +785,6 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case SESSION_NOT_FOUND_16: *os << "SESSION_NOT_FOUND_16"; break; - case SESSION_NOT_FOUND_17: - *os << "SESSION_NOT_FOUND_17"; - break; case SESSION_NOT_FOUND_18: *os << "SESSION_NOT_FOUND_18"; break; @@ -1141,6 +1144,34 @@ void PrintTo(const enum OEMCryptoResult& value, ::std::ostream* os) { case OEMCrypto_ERROR_SYSTEM_INVALIDATED: *os << "SYSTEM_INVALIDATED"; break; + case OEMCrypto_ERROR_LICENSE_RELOAD: + *os << "LICENSE_RELOAD"; + break; + case OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES: + *os << "MULTIPLE_USAGE_ENTRIES"; + break; + case OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION: + *os << "MIXED_OUTPUT_PROTECTION"; + break; + // ODK Values. + case ODK_ERROR_CORE_MESSAGE: + *os << "CORE_MESSAGE"; + break; + case ODK_SET_TIMER: + *os << "SET_TIMER"; + break; + case ODK_DISABLE_TIMER: + *os << "DISABLE_TIMER"; + break; + case ODK_TIMER_EXPIRED: + *os << "TIMER_EXPIRED"; + break; + case ODK_UNSUPPORTED_API: + *os << "UNSUPPORTED_API"; + break; + case ODK_STALE_RENEWAL: + *os << "STALE_RENEWAL"; + break; } } diff --git a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp index 94910e63..a9c65866 100644 --- a/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/usage_table_header_unittest.cpp @@ -144,28 +144,28 @@ const size_t kUsageInfoFileArraySize = ArraySize(kUsageInfoFileArray); std::vector kUsageInfoFileList; const DeviceFiles::CdmUsageData kCdmUsageData1 = { - /* provider_session_token = */ "provider_session_token_1", - /* license_request = */ "license_request_1", - /* license = */ "license_1", - /* key_set_id = */ "key_set_id_1", - /* usage_entry = */ "usage_entry_1", - /* usage_entry_number = */ 0, + /* provider_session_token = */ "provider_session_token_1", + /* license_request = */ "license_request_1", + /* license = */ "license_1", + /* key_set_id = */ "key_set_id_1", + /* usage_entry = */ "usage_entry_1", + /* usage_entry_number = */ 0, }; const DeviceFiles::CdmUsageData kCdmUsageData2 = { - /* provider_session_token = */ "provider_session_token_2", - /* license_request = */ "license_request_2", - /* license = */ "license_2", - /* key_set_id = */ "key_set_id_2", - /* usage_entry = */ "usage_entry_2", - /* usage_entry_number = */ 0, + /* provider_session_token = */ "provider_session_token_2", + /* license_request = */ "license_request_2", + /* license = */ "license_2", + /* key_set_id = */ "key_set_id_2", + /* usage_entry = */ "usage_entry_2", + /* usage_entry_number = */ 0, }; const DeviceFiles::CdmUsageData kCdmUsageData3 = { - /* provider_session_token = */ "provider_session_token_3", - /* license_request = */ "license_request_3", - /* license = */ "license_3", - /* key_set_id = */ "key_set_id_3", - /* usage_entry = */ "usage_entry_3", - /* usage_entry_number = */ 0, + /* provider_session_token = */ "provider_session_token_3", + /* license_request = */ "license_request_3", + /* license = */ "license_3", + /* key_set_id = */ "key_set_id_3", + /* usage_entry = */ "usage_entry_3", + /* usage_entry_number = */ 0, }; const std::vector kEmptyUsageInfoUsageDataList; @@ -340,8 +340,8 @@ void InitVectorConstants() { } } -void ToVector(std::vector& vec, const CdmUsageEntryInfo* arr, - size_t total_size) { +void ToVector(std::vector& vec, + const CdmUsageEntryInfo* arr, size_t total_size) { size_t max = total_size / sizeof(CdmUsageEntryInfo); vec.clear(); for (size_t i = 0; i < max; i++) { @@ -410,7 +410,8 @@ class MockDeviceFiles : public DeviceFiles { const std::string&, const CdmUsageEntry&, uint32_t)); MOCK_METHOD2(RetrieveUsageInfo, bool(const std::string&, std::vector*)); - MOCK_METHOD1(ListLicenses, bool(std::vector* key_set_ids)); + MOCK_METHOD1(ListLicenses, + bool(std::vector* key_set_ids)); MOCK_METHOD1(ListUsageInfoFiles, bool(std::vector* usage_info_files)); @@ -438,10 +439,10 @@ class MockCryptoSession : public TestCryptoSession { // Partial mock of the UsageTableHeader. This is to test when dependency // exist on internal methods which would require complex expectations class MockUsageTableHeader : public UsageTableHeader { - public: - MockUsageTableHeader() : UsageTableHeader() {} - MOCK_METHOD3(DeleteEntry, CdmResponseType(uint32_t, DeviceFiles*, - metrics::CryptoMetrics*)); + public: + MockUsageTableHeader() : UsageTableHeader() {} + MOCK_METHOD3(DeleteEntry, CdmResponseType(uint32_t, DeviceFiles*, + metrics::CryptoMetrics*)); }; } // namespace @@ -468,7 +469,9 @@ using ::testing::UnorderedElementsAreArray; class UsageTableHeaderTest : public WvCdmTestBase { public: - static void SetUpTestCase() { InitVectorConstants(); } + static void SetUpTestCase() { + InitVectorConstants(); + } // Useful when UsageTableHeader is mocked void DeleteEntry(uint32_t usage_entry_number, DeviceFiles*, @@ -563,7 +566,10 @@ class UsageTableHeaderInitializationTest : public UsageTableHeaderTest, public ::testing::WithParamInterface { public: - static void SetUpTestCase() { InitVectorConstants(); } + static void SetUpTestCase() { + InitVectorConstants(); + } + }; TEST_P(UsageTableHeaderInitializationTest, CreateUsageTableHeader) { @@ -572,18 +578,12 @@ TEST_P(UsageTableHeaderInitializationTest, CreateUsageTableHeader) { .WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), SetArgPointee<1>(kEmptyUsageEntryInfoVector), SetArgPointee<2>(false), Return(false))); - EXPECT_CALL(*device_files_, ListLicenses(NotNull())) - .WillOnce(DoAll(SetArgPointee<0>(kEmptyLicenseList), Return(false))); - EXPECT_CALL(*device_files_, ListUsageInfoFiles(NotNull())) - .WillOnce( - DoAll(SetArgPointee<0>(kEmptyUsageInfoFilesList), Return(false))); EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) .WillOnce( DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader, kEmptyUsageEntryInfoVector)) - .Times(2) - .WillRepeatedly(Return(true)); + .WillOnce(Return(true)); EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); } @@ -594,27 +594,15 @@ TEST_P(UsageTableHeaderInitializationTest, Upgrade_UnableToRetrieveLicenses) { .WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), SetArgPointee<1>(kEmptyUsageEntryInfoVector), SetArgPointee<2>(false), Return(false))); - EXPECT_CALL(*device_files_, ListLicenses(NotNull())) - .WillOnce(DoAll(SetArgPointee<0>(kLicenseList), Return(true))); - EXPECT_CALL(*device_files_, ListUsageInfoFiles(NotNull())) - .WillOnce( - DoAll(SetArgPointee<0>(kEmptyUsageInfoFilesList), Return(false))); EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) .WillOnce( DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR))); // TODO: Why not being called? - // EXPECT_CALL(*device_files_, DeleteAllLicenses()).WillOnce(Return(true)); + //EXPECT_CALL(*device_files_, DeleteAllLicenses()).WillOnce(Return(true)); EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader, kEmptyUsageEntryInfoVector)) - .Times(2) - .WillRepeatedly(Return(true)); + .WillOnce(Return(true)); - for (size_t i = 0; i < kLicenseList.size(); ++i) { - device_files_->DeleteLicense(kLicenseList[i]); - EXPECT_CALL(*device_files_, - RetrieveLicense(kLicenseList[i], NotNull(), NotNull())) - .WillOnce(Return(false)); - } EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); } @@ -624,24 +612,12 @@ TEST_P(UsageTableHeaderInitializationTest, Upgrade_UnableToRetrieveUsageInfo) { .WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), SetArgPointee<1>(kEmptyUsageEntryInfoVector), SetArgPointee<2>(false), Return(false))); - EXPECT_CALL(*device_files_, ListLicenses(NotNull())) - .WillOnce(DoAll(SetArgPointee<0>(kEmptyLicenseList), Return(false))); - EXPECT_CALL(*device_files_, ListUsageInfoFiles(NotNull())) - .WillOnce(DoAll(SetArgPointee<0>(kUsageInfoFileList), Return(true))); EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) .WillOnce( DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader, kEmptyUsageEntryInfoVector)) - .Times(2) - .WillRepeatedly(Return(true)); - for (size_t i = 0; i < kUsageInfoFileList.size(); ++i) { - EXPECT_CALL(*device_files_, - RetrieveUsageInfo(kUsageInfoFileList[i], NotNull())) - .WillOnce(DoAll(SetArgPointee<1>(kEmptyUsageInfoUsageDataList), - Return(false))); - } - + .WillOnce(Return(true)); EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); } @@ -681,8 +657,8 @@ TEST_P(UsageTableHeaderInitializationTest, SecurityLevel security_level = (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; - EXPECT_CALL(*crypto_session_, Open(security_level)) - .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, + Open(security_level)).WillOnce(Return(NO_ERROR)); EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader)) .WillOnce(Return(NO_ERROR)); EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) @@ -729,20 +705,22 @@ TEST_P(UsageTableHeaderInitializationTest, // Expectations for AddEntry uint32_t expect_usage_entry_number = k201UsageEntryInfoVector.size(); EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) - .WillOnce( - DoAll(SetArgPointee<0>(expect_usage_entry_number), Return(NO_ERROR))); - EXPECT_CALL(*device_files_, - StoreUsageTableInfo(kUsageTableHeader, usage_entries_202)) + .WillOnce(DoAll(SetArgPointee<0>(expect_usage_entry_number), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kUsageTableHeader, + usage_entries_202)) .WillOnce(Return(true)); // Expectations for DeleteEntry SecurityLevel security_level = (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; - EXPECT_CALL(*crypto_session_, Open(security_level)) + EXPECT_CALL(*crypto_session_, + Open(security_level)) .Times(2) .WillRepeatedly(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader(usage_entries_202.size() - 1, NotNull())) + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader(usage_entries_202.size() - 1, NotNull())) .WillOnce(Return(SHRINK_USAGE_TABLER_HEADER_UNKNOWN_ERROR)); EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); @@ -764,26 +742,29 @@ TEST_P(UsageTableHeaderInitializationTest, // Expectations for AddEntry uint32_t expect_usage_entry_number = k201UsageEntryInfoVector.size(); EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) - .WillOnce( - DoAll(SetArgPointee<0>(expect_usage_entry_number), Return(NO_ERROR))); - EXPECT_CALL(*device_files_, - StoreUsageTableInfo(kUsageTableHeader, usage_entries_202)) + .WillOnce(DoAll(SetArgPointee<0>(expect_usage_entry_number), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kUsageTableHeader, + usage_entries_202)) .WillOnce(Return(true)); // Expectations for DeleteEntry SecurityLevel security_level = (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; - EXPECT_CALL(*crypto_session_, Open(security_level)) + EXPECT_CALL(*crypto_session_, + Open(security_level)) .Times(2) .WillRepeatedly(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader(usage_entries_202.size() - 1, NotNull())) + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader(usage_entries_202.size() - 1, NotNull())) .WillOnce( DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, - StoreUsageTableInfo(kAnotherUsageTableHeader, - SizeIs(k201UsageEntryInfoVector.size()))) + StoreUsageTableInfo( + kAnotherUsageTableHeader, + SizeIs(k201UsageEntryInfoVector.size()))) .WillOnce(Return(true)); EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); @@ -946,8 +927,9 @@ TEST_F(UsageTableHeaderTest, EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) .WillOnce(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)) - .WillOnce(DoAll(SetArgPointee<0>(expected_usage_entry_number), - Return(NO_ERROR))); + .WillOnce( + DoAll(SetArgPointee<0>(expected_usage_entry_number), + Return(NO_ERROR))); EXPECT_CALL(*device_files_, StoreUsageTableInfo(kUsageTableHeader, _)) .WillOnce(DoAll(SaveArg<1>(&final_usage_entries), Return(true))); @@ -1007,8 +989,9 @@ TEST_F(UsageTableHeaderTest, EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) .WillOnce(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)) .WillOnce(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)) - .WillOnce(DoAll(SetArgPointee<0>(expected_usage_entry_number), - Return(NO_ERROR))); + .WillOnce( + DoAll(SetArgPointee<0>(expected_usage_entry_number), + Return(NO_ERROR))); EXPECT_CALL(*device_files_, StoreUsageTableInfo(kUsageTableHeader, _)) .WillOnce(DoAll(SaveArg<1>(&final_usage_entries), Return(true))); @@ -1175,8 +1158,9 @@ TEST_F(UsageTableHeaderTest, EXPECT_CALL(*mock_usage_table_header, DeleteEntry(_, device_files_, NotNull())) .Times(1) - .WillRepeatedly(DoAll(Invoke(this, &UsageTableHeaderTest::DeleteEntry), - Return(NO_ERROR))); + .WillRepeatedly( + DoAll(Invoke(this, &UsageTableHeaderTest::DeleteEntry), + Return(NO_ERROR))); EXPECT_CALL(*crypto_session_, LoadUsageEntry(usage_entry_number_to_load, usage_entry_to_load)) @@ -1184,9 +1168,11 @@ TEST_F(UsageTableHeaderTest, .WillOnce(Return(NO_ERROR)); // Now invoke the method under test - EXPECT_EQ(NO_ERROR, mock_usage_table_header->LoadEntry( - crypto_session_, usage_entry_to_load, - usage_entry_number_to_load)); + EXPECT_EQ(NO_ERROR, + mock_usage_table_header->LoadEntry( + crypto_session_, + usage_entry_to_load, + usage_entry_number_to_load)); } TEST_F(UsageTableHeaderTest, @@ -1205,8 +1191,9 @@ TEST_F(UsageTableHeaderTest, EXPECT_CALL(*mock_usage_table_header, DeleteEntry(_, device_files_, NotNull())) .Times(2) - .WillRepeatedly(DoAll(Invoke(this, &UsageTableHeaderTest::DeleteEntry), - Return(NO_ERROR))); + .WillRepeatedly( + DoAll(Invoke(this, &UsageTableHeaderTest::DeleteEntry), + Return(NO_ERROR))); EXPECT_CALL(*crypto_session_, LoadUsageEntry(usage_entry_number_to_load, usage_entry_to_load)) @@ -1215,9 +1202,11 @@ TEST_F(UsageTableHeaderTest, .WillOnce(Return(NO_ERROR)); // Now invoke the method under test - EXPECT_EQ(NO_ERROR, mock_usage_table_header->LoadEntry( - crypto_session_, usage_entry_to_load, - usage_entry_number_to_load)); + EXPECT_EQ(NO_ERROR, + mock_usage_table_header->LoadEntry( + crypto_session_, + usage_entry_to_load, + usage_entry_number_to_load)); } TEST_F(UsageTableHeaderTest, LoadEntry_LoadUsageEntryFailsThrice) { @@ -1235,8 +1224,9 @@ TEST_F(UsageTableHeaderTest, LoadEntry_LoadUsageEntryFailsThrice) { EXPECT_CALL(*mock_usage_table_header, DeleteEntry(_, device_files_, NotNull())) .Times(3) - .WillRepeatedly(DoAll(Invoke(this, &UsageTableHeaderTest::DeleteEntry), - Return(NO_ERROR))); + .WillRepeatedly( + DoAll(Invoke(this, &UsageTableHeaderTest::DeleteEntry), + Return(NO_ERROR))); EXPECT_CALL(*crypto_session_, LoadUsageEntry(usage_entry_number_to_load, usage_entry_to_load)) @@ -1244,10 +1234,11 @@ TEST_F(UsageTableHeaderTest, LoadEntry_LoadUsageEntryFailsThrice) { .WillRepeatedly(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)); // Now invoke the method under test - EXPECT_EQ( - INSUFFICIENT_CRYPTO_RESOURCES_3, - mock_usage_table_header->LoadEntry(crypto_session_, usage_entry_to_load, - usage_entry_number_to_load)); + EXPECT_EQ(INSUFFICIENT_CRYPTO_RESOURCES_3, + mock_usage_table_header->LoadEntry( + crypto_session_, + usage_entry_to_load, + usage_entry_number_to_load)); } TEST_F(UsageTableHeaderTest, DeleteEntry_InvalidUsageEntryNumber) { @@ -1365,7 +1356,7 @@ TEST_F(UsageTableHeaderTest, DeleteEntry_LastOfflineEntry) { // # of usage entries 4 3 TEST_F(UsageTableHeaderTest, DeleteEntry_LastSecureStopEntry) { std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { + const CdmUsageEntryInfo usage_entry_info_array[] = { kUsageEntryInfoOfflineLicense1, kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoSecureStop2}; ToVector(usage_entry_info_vector, usage_entry_info_array, diff --git a/libwvdrmengine/cdm/metrics/include/metrics_collections.h b/libwvdrmengine/cdm/metrics/include/metrics_collections.h index 8ae7ed9d..66389233 100644 --- a/libwvdrmengine/cdm/metrics/include/metrics_collections.h +++ b/libwvdrmengine/cdm/metrics/include/metrics_collections.h @@ -279,6 +279,20 @@ class CryptoMetrics { CounterMetric oemcrypto_set_decrypt_hash_; ValueMetric oemcrypto_resource_rating_tier_; + EventMetric + oemcrypto_prep_and_sign_license_request_; + EventMetric + oemcrypto_prep_and_sign_renewal_request_; + EventMetric + oemcrypto_prep_and_sign_provisioning_request_; + EventMetric + oemcrypto_load_license_; + EventMetric + oemcrypto_load_renewal_; + EventMetric + oemcrypto_load_provisioning_; + ValueMetric oemcrypto_minor_api_version_; + ValueMetric oemcrypto_maximum_usage_table_header_size_; }; // This class contains session-scoped metrics. All properties and diff --git a/libwvdrmengine/cdm/metrics/src/metrics.proto b/libwvdrmengine/cdm/metrics/src/metrics.proto index 45991c3f..c220746c 100644 --- a/libwvdrmengine/cdm/metrics/src/metrics.proto +++ b/libwvdrmengine/cdm/metrics/src/metrics.proto @@ -93,7 +93,7 @@ message WvCdmMetrics { // This contains metrics that were captured at the CryptoSession level. These // include CryptoSession metrics and most OEMCrypto metrics. - // next id: 77 + // next id: 85 message CryptoMetrics { // Crypto Session Metrics. optional ValueMetric crypto_session_security_level = 1; @@ -179,6 +179,19 @@ message WvCdmMetrics { optional ValueMetric oemcrypto_set_sandbox = 70; repeated CounterMetric oemcrypto_set_decrypt_hash = 71; optional ValueMetric oemcrypto_resource_rating_tier = 72; + // TODO(b/142684157): Remove this comment before closing bug. + // OemCrypto V16 metrics start at 77 (4 new metrics pending review). + repeated DistributionMetric + oemcrypto_prep_and_sign_license_request_time_us = 77; + repeated DistributionMetric + oemcrypto_prep_and_sign_renewal_request_time_us = 78; + repeated DistributionMetric + oemcrypto_prep_and_sign_provisioning_request_time_us = 79; + repeated DistributionMetric oemcrypto_load_license_time_us = 80; + repeated DistributionMetric oemcrypto_load_renewal_time_us = 81; + repeated DistributionMetric oemcrypto_load_provisioning_time_us = 82; + optional ValueMetric oemcrypto_minor_api_version = 83; + optional ValueMetric oemcrypto_maximum_usage_table_header_size = 84; } // This contains metrics that were captured within a CdmSession. This contains diff --git a/libwvdrmengine/cdm/metrics/src/metrics_collections.cpp b/libwvdrmengine/cdm/metrics/src/metrics_collections.cpp index 3f70a3c8..956266fa 100644 --- a/libwvdrmengine/cdm/metrics/src/metrics_collections.cpp +++ b/libwvdrmengine/cdm/metrics/src/metrics_collections.cpp @@ -194,6 +194,25 @@ void CryptoMetrics::Serialize(WvCdmMetrics::CryptoMetrics *crypto_metrics) crypto_metrics->mutable_oemcrypto_set_decrypt_hash()); crypto_metrics->set_allocated_oemcrypto_resource_rating_tier( oemcrypto_resource_rating_tier_.ToProto()); + oemcrypto_prep_and_sign_license_request_.ToProto( + crypto_metrics + ->mutable_oemcrypto_prep_and_sign_license_request_time_us()); + oemcrypto_prep_and_sign_renewal_request_.ToProto( + crypto_metrics + ->mutable_oemcrypto_prep_and_sign_renewal_request_time_us()); + oemcrypto_prep_and_sign_provisioning_request_.ToProto( + crypto_metrics + ->mutable_oemcrypto_prep_and_sign_provisioning_request_time_us()); + oemcrypto_load_license_.ToProto( + crypto_metrics->mutable_oemcrypto_load_license_time_us()); + oemcrypto_load_renewal_.ToProto( + crypto_metrics->mutable_oemcrypto_load_renewal_time_us()); + oemcrypto_load_provisioning_.ToProto( + crypto_metrics->mutable_oemcrypto_load_provisioning_time_us()); + crypto_metrics->set_allocated_oemcrypto_minor_api_version( + oemcrypto_minor_api_version_.ToProto()); + crypto_metrics->set_allocated_oemcrypto_maximum_usage_table_header_size( + oemcrypto_maximum_usage_table_header_size_.ToProto()); } SessionMetrics::SessionMetrics() : session_id_(""), completed_(false) {} diff --git a/libwvdrmengine/cdm/metrics/test/metrics_collections_unittest.cpp b/libwvdrmengine/cdm/metrics/test/metrics_collections_unittest.cpp index 24dcb428..527bff45 100644 --- a/libwvdrmengine/cdm/metrics/test/metrics_collections_unittest.cpp +++ b/libwvdrmengine/cdm/metrics/test/metrics_collections_unittest.cpp @@ -439,6 +439,8 @@ TEST_F(CryptoMetricsTest, AllCryptoMetrics) { crypto_metrics.oemcrypto_set_decrypt_hash_ .Increment(OEMCrypto_ERROR_INIT_FAILED); crypto_metrics.oemcrypto_resource_rating_tier_.Record(123); + crypto_metrics.oemcrypto_minor_api_version_.Record(234); + crypto_metrics.oemcrypto_maximum_usage_table_header_size_.Record(321); WvCdmMetrics::CryptoMetrics actual; crypto_metrics.Serialize(&actual); @@ -527,6 +529,9 @@ TEST_F(CryptoMetricsTest, AllCryptoMetrics) { EXPECT_EQ("sandbox", actual.oemcrypto_set_sandbox().string_value()); EXPECT_GT(actual.oemcrypto_set_decrypt_hash_size(), 0); EXPECT_EQ(123, actual.oemcrypto_resource_rating_tier().int_value()); + EXPECT_EQ(234, actual.oemcrypto_minor_api_version().int_value()); + EXPECT_EQ(321, + actual.oemcrypto_maximum_usage_table_header_size().int_value()); } } // namespace metrics diff --git a/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp b/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp index dbe85ebc..fd038f42 100644 --- a/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp +++ b/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp @@ -181,14 +181,6 @@ bool StringToInt64(const std::string& input, int64_t* output) { } // namespace -// GTest requires PrintTo to be in the same namespace as the thing it prints, -// which is std::vector in this case. -namespace std { -void PrintTo(const vector& value, ostream* os) { - *os << wvcdm::b2a_hex(value); -} -} // namespace std - using ::testing::Contains; using ::testing::Pair; using ::testing::StrNe; diff --git a/libwvdrmengine/cdm/test/cdm_feature_test.sh b/libwvdrmengine/cdm/test/cdm_feature_test.sh index bddb3a17..bec93214 100644 --- a/libwvdrmengine/cdm/test/cdm_feature_test.sh +++ b/libwvdrmengine/cdm/test/cdm_feature_test.sh @@ -85,7 +85,7 @@ write_options_txt_file() fi echo "security_patch_level 1" >> $HOST_OPTIONS_PATH - echo "max_buffer_size 0" >> $HOST_OPTIONS_PATH + echo "max_subsample_size 0" >> $HOST_OPTIONS_PATH if [ "$use_keybox" == "$NO" ]; then echo "use_keybox 0" >> $HOST_OPTIONS_PATH diff --git a/libwvdrmengine/cdm/test/integration-test.mk b/libwvdrmengine/cdm/test/integration-test.mk index a8feaf29..d9301caf 100644 --- a/libwvdrmengine/cdm/test/integration-test.mk +++ b/libwvdrmengine/cdm/test/integration-test.mk @@ -12,13 +12,17 @@ LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := \ $(test_main) \ $(test_src_dir)/$(test_name).cpp \ - ../../oemcrypto/test//oec_device_features.cpp \ ../core/test/config_test_env.cpp \ + ../core/test/fake_provisioning_server.cpp \ ../core/test/http_socket.cpp \ ../core/test/license_request.cpp \ ../core/test/test_base.cpp \ ../core/test/test_printers.cpp \ - ../core/test/url_request.cpp + ../core/test/url_request.cpp \ + ../../oemcrypto/test/oec_device_features.cpp \ + ../../oemcrypto/test/oec_key_deriver.cpp \ + ../../oemcrypto/test/oec_session_util.cpp \ + ../util/test/test_sleep.cpp \ LOCAL_C_INCLUDES := \ vendor/widevine/libwvdrmengine/android/cdm/test \ @@ -27,8 +31,11 @@ LOCAL_C_INCLUDES := \ vendor/widevine/libwvdrmengine/cdm/include \ vendor/widevine/libwvdrmengine/cdm/metrics/include \ vendor/widevine/libwvdrmengine/cdm/util/include \ + vendor/widevine/libwvdrmengine/cdm/util/test \ vendor/widevine/libwvdrmengine/oemcrypto/include \ vendor/widevine/libwvdrmengine/oemcrypto/test \ + vendor/widevine/libwvdrmengine/oemcrypto/odk/include \ + vendor/widevine/libwvdrmengine/oemcrypto/odk/kdo/include \ LOCAL_C_INCLUDES += external/protobuf/src @@ -40,6 +47,8 @@ LOCAL_STATIC_LIBRARIES := \ libgmock \ libgtest \ libwvlevel3 \ + libwv_kdo \ + libwv_odk \ LOCAL_SHARED_LIBRARIES := \ libbase \ diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index fcd993df..c618eeb3 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -63,9 +63,6 @@ const wvcdm::CdmIdentifier kAlternateCdmIdentifier2 = { const std::string kEmptyServiceCertificate; const std::string kComma = ","; -// From OEMCrypto v15.2 onwards, we require the nonce table to be size 4. -const size_t kNonceTableSize = 4; - // Protobuf generated classes using video_widevine::LicenseIdentification; using video_widevine::LicenseRequest_ContentIdentification; @@ -2356,22 +2353,14 @@ TEST_F(WvCdmRequestLicenseTest, ProvisioningInterspersedRetryTest) { // test this for API versions before that if they use OEM certificates. std::vector key_msgs; + // TODO(b/135288420): There can be only one request per OEMCrypto session. + // This code must change. wvcdm::CdmResponseType first_request_error; - if (provisioning_model == wvcdm::QUERY_VALUE_KEYBOX) { - // For keyboxes we use derived keys as part of the provisioning request. - // These get updated each request, therefore any request before the latest - // fails, so we only need 2 requests. - key_msgs.resize(2); - first_request_error = wvcdm::REWRAP_DEVICE_RSA_KEY_ERROR; - } else { - // For OEM certificates, we don't use derived keys, so any request is - // valid as long as its corresponding nonce is contained in the nonce - // table. This is why we need the size to be fixed. - // We need kNonceTableSize + 1 requests here to check that the first - // of these fails and the one after that succeeds. - key_msgs.resize(kNonceTableSize + 1); - first_request_error = wvcdm::REWRAP_DEVICE_RSA_KEY_30_ERROR; - } + // For keyboxes we use derived keys as part of the provisioning request. + // These get updated each request, therefore any request before the latest + // fails, so we only need 2 requests. + key_msgs.resize(2); + first_request_error = wvcdm::REWRAP_DEVICE_RSA_KEY_ERROR; for (size_t i = 0; i < key_msgs.size(); i++) { EXPECT_EQ( @@ -4941,6 +4930,32 @@ TEST_F(WvCdmRequestLicenseTest, QueryStatus) { // These are the only valid values for Android devices. EXPECT_TRUE(value == wvcdm::QUERY_VALUE_KEYBOX || value == wvcdm::QUERY_VALUE_OEM_CERTIFICATE); + + EXPECT_EQ( + wvcdm::NO_ERROR, + decryptor_->QueryStatus( + kLevelDefault, wvcdm::QUERY_KEY_MAX_USAGE_TABLE_ENTRIES, &value)); + + ss.clear(); + ss.str(value); + uint32_t max_usage_table_entries; + ss >> max_usage_table_entries; + ASSERT_FALSE(ss.fail()); + EXPECT_TRUE(ss.eof()); + EXPECT_LE(200u, max_usage_table_entries); + + EXPECT_EQ( + wvcdm::NO_ERROR, + decryptor_->QueryStatus( + kLevelDefault, wvcdm::QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION, &value)); + + ss.clear(); + ss.str(value); + uint32_t api_minor_version; + ss >> api_minor_version; + ASSERT_FALSE(ss.fail()); + EXPECT_TRUE(ss.eof()); + EXPECT_LE(0u, api_minor_version); } TEST_F(WvCdmRequestLicenseTest, QueryStatusL3) { @@ -5048,6 +5063,28 @@ TEST_F(WvCdmRequestLicenseTest, QueryStatusL3) { // These are the only valid values for Android devices. EXPECT_TRUE(value == wvcdm::QUERY_VALUE_KEYBOX || value == wvcdm::QUERY_VALUE_OEM_CERTIFICATE); + + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_->QueryStatus( + kLevel3, wvcdm::QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION, &value)); + ss.clear(); + ss.str(value); + uint32_t api_minor_version; + ss >> api_minor_version; + ASSERT_FALSE(ss.fail()); + EXPECT_TRUE(ss.eof()); + EXPECT_LE(0u, api_minor_version); + + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_->QueryStatus( + kLevel3, wvcdm::QUERY_KEY_MAX_USAGE_TABLE_ENTRIES, &value)); + ss.clear(); + ss.str(value); + uint32_t max_usage_table_entries; + ss >> max_usage_table_entries; + ASSERT_FALSE(ss.fail()); + EXPECT_TRUE(ss.eof()); + EXPECT_LE(200u, max_usage_table_entries); } TEST_F(WvCdmRequestLicenseTest, QueryOemCryptoSessionId) { diff --git a/libwvdrmengine/cdm/test/unit-test.mk b/libwvdrmengine/cdm/test/unit-test.mk index 8b2319b6..cf51670e 100644 --- a/libwvdrmengine/cdm/test/unit-test.mk +++ b/libwvdrmengine/cdm/test/unit-test.mk @@ -11,13 +11,15 @@ LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := \ $(test_src_dir)/$(test_name).cpp \ - ../../oemcrypto/test/oec_device_features.cpp \ ../core/test/config_test_env.cpp \ + ../core/test/fake_provisioning_server.cpp \ ../core/test/http_socket.cpp \ ../core/test/license_request.cpp \ ../core/test/test_base.cpp \ ../core/test/test_printers.cpp \ - ../core/test/url_request.cpp + ../core/test/url_request.cpp \ + ../../oemcrypto/test/oec_device_features.cpp \ + ../util/test/test_sleep.cpp \ LOCAL_C_INCLUDES := \ vendor/widevine/libwvdrmengine/android/cdm/test \ @@ -26,8 +28,11 @@ LOCAL_C_INCLUDES := \ vendor/widevine/libwvdrmengine/cdm/include \ vendor/widevine/libwvdrmengine/cdm/metrics/include \ vendor/widevine/libwvdrmengine/cdm/util/include \ + vendor/widevine/libwvdrmengine/cdm/util/test \ vendor/widevine/libwvdrmengine/oemcrypto/include \ vendor/widevine/libwvdrmengine/oemcrypto/test \ + vendor/widevine/libwvdrmengine/oemcrypto/odk/include \ + vendor/widevine/libwvdrmengine/oemcrypto/odk/kdo/include \ LOCAL_C_INCLUDES += external/protobuf/src diff --git a/libwvdrmengine/cdm/util/include/string_conversions.h b/libwvdrmengine/cdm/util/include/string_conversions.h index c5448a8f..cb6c9ee6 100644 --- a/libwvdrmengine/cdm/util/include/string_conversions.h +++ b/libwvdrmengine/cdm/util/include/string_conversions.h @@ -36,6 +36,8 @@ CORE_UTIL_EXPORT std::string IntToString(int value); CORE_UTIL_EXPORT int64_t htonll64(int64_t x); CORE_UTIL_EXPORT inline int64_t ntohll64(int64_t x) { return htonll64(x); } CORE_UTIL_EXPORT std::string BytesToString(const uint8_t* bytes, unsigned size); +// Encode unsigned integer into a big endian formatted string +CORE_UTIL_EXPORT std::string EncodeUint32(unsigned int u); } // namespace wvcdm diff --git a/libwvdrmengine/cdm/util/src/string_conversions.cpp b/libwvdrmengine/cdm/util/src/string_conversions.cpp index b2566e65..b9b49798 100644 --- a/libwvdrmengine/cdm/util/src/string_conversions.cpp +++ b/libwvdrmengine/cdm/util/src/string_conversions.cpp @@ -87,10 +87,12 @@ std::string a2bs_hex(const std::string& byte) { } std::string b2a_hex(const std::vector& byte) { - return HexEncode(&byte[0], byte.size()); + if (byte.empty()) return ""; + return HexEncode(byte.data(), byte.size()); } std::string b2a_hex(const std::string& byte) { + if (byte.empty()) return ""; return HexEncode(reinterpret_cast(byte.data()), byte.length()); } @@ -251,7 +253,7 @@ std::vector Base64SafeDecode(const std::string& b64_input) { std::string HexEncode(const uint8_t* in_buffer, unsigned int size) { static const char kHexChars[] = "0123456789ABCDEF"; - + if (size == 0) return ""; // Each input byte creates two output hex characters. std::string out_buffer(size * 2, '\0'); @@ -298,4 +300,14 @@ std::string BytesToString(const uint8_t* bytes, unsigned size) { return std::string(char_bytes, char_bytes + size); } +// Encode unsigned integer into a big endian formatted string +std::string EncodeUint32(unsigned int u) { + std::string s; + s.append(1, (u >> 24) & 0xFF); + s.append(1, (u >> 16) & 0xFF); + s.append(1, (u >> 8) & 0xFF); + s.append(1, (u >> 0) & 0xFF); + return s; +} + } // namespace wvcdm diff --git a/libwvdrmengine/cdm/util/test/test_clock.cpp b/libwvdrmengine/cdm/util/test/test_clock.cpp new file mode 100644 index 00000000..86e90179 --- /dev/null +++ b/libwvdrmengine/cdm/util/test/test_clock.cpp @@ -0,0 +1,40 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Clock - A fake clock just for running tests. + +#include "clock.h" + +#include + +#include "test_sleep.h" + +namespace wvcdm { + +namespace { +// A fake clock that only advances when TestSleep::Sleep is called. +class FakeClock : public wvcdm::TestSleep::CallBack { + public: + FakeClock() { + auto now = std::chrono::steady_clock().now(); + now_ = now.time_since_epoch() / std::chrono::milliseconds(1); + TestSleep::set_callback(this); + } + ~FakeClock() { TestSleep::set_callback(nullptr); } + void ElapseTime(int64_t milliseconds) { now_ += milliseconds; } + + int64_t now() const { return now_; } + + private: + int64_t now_; +}; + +FakeClock* g_fake_clock = nullptr; +} // namespace + +// On devices running a fake OEMCrypto, we can use a fake sleep and fake time. +int64_t Clock::GetCurrentTime() { + if (g_fake_clock == nullptr) g_fake_clock = new FakeClock(); + return g_fake_clock->now() / 1000; +} + +} // namespace wvcdm diff --git a/libwvdrmengine/cdm/util/test/test_sleep.cpp b/libwvdrmengine/cdm/util/test/test_sleep.cpp new file mode 100644 index 00000000..2140c9ee --- /dev/null +++ b/libwvdrmengine/cdm/util/test/test_sleep.cpp @@ -0,0 +1,44 @@ +// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "test_sleep.h" + +#include + +#include + +#include "clock.h" + +namespace wvcdm { + +bool TestSleep::real_sleep_ = true; +TestSleep::CallBack* TestSleep::callback_ = nullptr; + +void TestSleep::Sleep(unsigned int seconds) { + int64_t milliseconds = 1000 * seconds; + if (real_sleep_) { + // This next bit of logic is to avoid slow drift apart of the real clock and + // the fake clock. We compute how far from the real clock has advanced in + // total since the start, and then compare to a running total of sleep + // calls. We sleep for approximately x second, and then advance the clock by + // the amount of time that has actually passed. + static auto start_real = std::chrono::steady_clock().now(); + static int64_t fake_clock = 0; + sleep(seconds); + auto now_real = std::chrono::steady_clock().now(); + int64_t total_real = (now_real - start_real) / std::chrono::milliseconds(1); + // We want to advance the fake clock by the difference between the real + // clock, and the previous value on the fake clock. + milliseconds = total_real - fake_clock; + fake_clock += milliseconds; + } + if (callback_ != nullptr) callback_->ElapseTime(milliseconds); +} + +void TestSleep::SyncFakeClock() { + // Syncing can be done by sleeping 0 seconds. + Sleep(0); +} + +} // namespace wvcdm diff --git a/libwvdrmengine/cdm/util/test/test_sleep.h b/libwvdrmengine/cdm/util/test/test_sleep.h new file mode 100644 index 00000000..4f2dcf52 --- /dev/null +++ b/libwvdrmengine/cdm/util/test/test_sleep.h @@ -0,0 +1,49 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. +// +// TestSleep - Controls sleep and clock adjustment during tests. +// +#ifndef WVCDM_UTIL_TEST_SLEEP_H_ +#define WVCDM_UTIL_TEST_SLEEP_H_ + +#include + +namespace wvcdm { + +class TestSleep { + public: + // The callback is called when the clock should be advanced. + class CallBack { + public: + virtual void ElapseTime(int64_t milliseconds) = 0; + + protected: + virtual ~CallBack(){}; + }; + + // If real_sleep_ is true, then this sleeps for |seconds| of time. + // If the callback exists, this calls the callback. + static void Sleep(unsigned int seconds); + + // If we are using a real clock and a fake clock, then the real clock advances + // a little while we are doing work, but the fake one only advances when we + // sleep. This function advances the fake clock to be in sync with the real + // clock. This function should be called to prevent a slow flaky test from + // failing due to this drift. + static void SyncFakeClock(); + + static void set_real_sleep(bool real_sleep) { real_sleep_ = real_sleep; } + + static void set_callback(CallBack* callback) { callback_ = callback; } + + private: + // Controls if the test sleep should use real sleep. + static bool real_sleep_; + // Called when the clock should advance. + static CallBack* callback_; +}; + +} // namespace wvcdm + +#endif // WVCDM_UTIL_TEST_SLEEP_H_ diff --git a/libwvdrmengine/docs/License_Duration_and_Renewal.pdf b/libwvdrmengine/docs/License_Duration_and_Renewal.pdf new file mode 100644 index 00000000..832960b3 Binary files /dev/null and b/libwvdrmengine/docs/License_Duration_and_Renewal.pdf differ diff --git a/libwvdrmengine/docs/OEMCrypto_State_Diagrams.pdf b/libwvdrmengine/docs/OEMCrypto_State_Diagrams.pdf new file mode 100644 index 00000000..5425839d Binary files /dev/null and b/libwvdrmengine/docs/OEMCrypto_State_Diagrams.pdf differ diff --git a/libwvdrmengine/docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v14.pdf b/libwvdrmengine/docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v14.pdf deleted file mode 100644 index 99396c78..00000000 Binary files a/libwvdrmengine/docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v14.pdf and /dev/null differ diff --git a/libwvdrmengine/docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v16.pdf b/libwvdrmengine/docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v16.pdf new file mode 100644 index 00000000..90e78a42 Binary files /dev/null and b/libwvdrmengine/docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v16.pdf differ diff --git a/libwvdrmengine/docs/Widevine_Core_Message_Serialization.pdf b/libwvdrmengine/docs/Widevine_Core_Message_Serialization.pdf new file mode 100644 index 00000000..47bca117 Binary files /dev/null and b/libwvdrmengine/docs/Widevine_Core_Message_Serialization.pdf differ diff --git a/libwvdrmengine/docs/Widevine_Modular_DRM_Version_16_Delta.pdf b/libwvdrmengine/docs/Widevine_Modular_DRM_Version_16_Delta.pdf new file mode 100644 index 00000000..d75e0733 Binary files /dev/null and b/libwvdrmengine/docs/Widevine_Modular_DRM_Version_16_Delta.pdf differ diff --git a/libwvdrmengine/docs/Widevine_OEMCrypto_Version_Compatibility.pdf b/libwvdrmengine/docs/Widevine_OEMCrypto_Version_Compatibility.pdf index 301fb66b..b18975e5 100644 Binary files a/libwvdrmengine/docs/Widevine_OEMCrypto_Version_Compatibility.pdf and b/libwvdrmengine/docs/Widevine_OEMCrypto_Version_Compatibility.pdf differ diff --git a/libwvdrmengine/include/WVErrors.h b/libwvdrmengine/include/WVErrors.h index 352890c8..62a5276a 100644 --- a/libwvdrmengine/include/WVErrors.h +++ b/libwvdrmengine/include/WVErrors.h @@ -35,8 +35,6 @@ enum { kCertProvisioningResponseError2 = ERROR_DRM_VENDOR_MIN + 17, kCertProvisioningResponseError3 = ERROR_DRM_VENDOR_MIN + 18, kCertProvisioningResponseError4 = ERROR_DRM_VENDOR_MIN + 19, - kCertProvisioningResponseError5 = ERROR_DRM_VENDOR_MIN + 20, - kCertProvisioningResponseError6 = ERROR_DRM_VENDOR_MIN + 21, kCertProvisioningResponseError7 = ERROR_DRM_VENDOR_MIN + 22, kCertProvisioningResponseError8 = ERROR_DRM_VENDOR_MIN + 23, kDeviceCertificateError1 = ERROR_DRM_VENDOR_MIN + 30, @@ -282,10 +280,15 @@ enum { kSignatureNotFound2 = ERROR_DRM_VENDOR_MIN + 297, kSessionKeysNotFound2 = ERROR_DRM_VENDOR_MIN + 298, kUsageInvalidParameters2 = ERROR_DRM_VENDOR_MIN + 299, + kCoreMessageNotFound = ERROR_DRM_VENDOR_MIN + 300, + kInvalidSession2 = ERROR_DRM_VENDOR_MIN + 301, + kLoadProvisioningError = ERROR_DRM_VENDOR_MIN + 302, + kLoadLicenseError = ERROR_DRM_VENDOR_MIN + 303, + kLoadRenewalError = ERROR_DRM_VENDOR_MIN + 304, // This should always follow the last error code. // The offset value should be updated each time a new error code is added. - kErrorWVDrmMaxErrorUsed = ERROR_DRM_VENDOR_MIN + 299, + kErrorWVDrmMaxErrorUsed = ERROR_DRM_VENDOR_MIN + 304, // Used by crypto test mode kErrorTestMode = ERROR_DRM_VENDOR_MAX, diff --git a/libwvdrmengine/include/mapErrors-inl.h b/libwvdrmengine/include/mapErrors-inl.h index c37b39a9..32a6b388 100644 --- a/libwvdrmengine/include/mapErrors-inl.h +++ b/libwvdrmengine/include/mapErrors-inl.h @@ -59,7 +59,6 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) { case wvcdm::SESSION_NOT_FOUND_8: case wvcdm::SESSION_NOT_FOUND_9: case wvcdm::SESSION_NOT_FOUND_10: - case wvcdm::SESSION_NOT_FOUND_17: case wvcdm::SESSION_NOT_FOUND_18: case wvcdm::SESSION_NOT_FOUND_19: case wvcdm::SESSION_NOT_FOUND_20: @@ -95,10 +94,6 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) { return kCertProvisioningResponseError3; case wvcdm::CERT_PROVISIONING_RESPONSE_ERROR_4: return kCertProvisioningResponseError4; - case wvcdm::CERT_PROVISIONING_RESPONSE_ERROR_5: - return kCertProvisioningResponseError5; - case wvcdm::CERT_PROVISIONING_RESPONSE_ERROR_6: - return kCertProvisioningResponseError6; case wvcdm::CERT_PROVISIONING_RESPONSE_ERROR_7: return kCertProvisioningResponseError7; case wvcdm::CERT_PROVISIONING_RESPONSE_ERROR_8: @@ -117,6 +112,8 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) { return kClientIdRsaInitError; case wvcdm::COPY_OLD_USAGE_ENTRY_UNKNOWN_ERROR: return kCopyOldUsageEntryUnknownError; + case wvcdm::CORE_MESSAGE_NOT_FOUND: + return kCoreMessageNotFound; case wvcdm::CREATE_USAGE_ENTRY_UNKNOWN_ERROR: return kCreateUsageEntryUnknownError; case wvcdm::CREATE_USAGE_TABLE_ERROR: @@ -285,6 +282,8 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) { return kInvalidQueryKey; case wvcdm::INVALID_SESSION_1: return kInvalidSession1; + case wvcdm::INVALID_SESSION_2: + return kInvalidSession2; case wvcdm::INVALID_SESSION_ID: return kInvalidSessionId; case wvcdm::INVALID_USAGE_ENTRY_NUMBER_MODIFICATION: @@ -363,6 +362,12 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) { return kLoadEntitledContentKeysError; case wvcdm::LOAD_KEY_ERROR: return kLoadKeyError; + case wvcdm::LOAD_LICENSE_ERROR: + return kLoadLicenseError; + case wvcdm::LOAD_PROVISIONING_ERROR: + return kLoadProvisioningError; + case wvcdm::LOAD_RENEWAL_ERROR: + return kLoadRenewalError; case wvcdm::LOAD_SYSTEM_ID_ERROR: return kLoadSystemIdError; case wvcdm::LOAD_USAGE_ENTRY_GENERATION_SKEW: @@ -581,7 +586,6 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) { case wvcdm::NOT_IMPLEMENTED_ERROR: case wvcdm::GET_SRM_VERSION_ERROR: case wvcdm::REWRAP_DEVICE_RSA_KEY_ERROR: - case wvcdm::REWRAP_DEVICE_RSA_KEY_30_ERROR: case wvcdm::SERVICE_CERTIFICATE_PROVIDER_ID_EMPTY: case wvcdm::INVALID_SRM_LIST: return android::ERROR_DRM_UNKNOWN; diff --git a/libwvdrmengine/include_hidl/HidlTypes.h b/libwvdrmengine/include_hidl/HidlTypes.h index b785e5de..f6371307 100644 --- a/libwvdrmengine/include_hidl/HidlTypes.h +++ b/libwvdrmengine/include_hidl/HidlTypes.h @@ -9,9 +9,9 @@ #include #include -#include #include #include +#include #include #include #include diff --git a/libwvdrmengine/include_hidl/mapErrors-inl.h b/libwvdrmengine/include_hidl/mapErrors-inl.h index 76302033..88ed84dd 100644 --- a/libwvdrmengine/include_hidl/mapErrors-inl.h +++ b/libwvdrmengine/include_hidl/mapErrors-inl.h @@ -60,7 +60,6 @@ static Status mapCdmResponseType(wvcdm::CdmResponseType res) { case wvcdm::SESSION_NOT_FOUND_8: case wvcdm::SESSION_NOT_FOUND_9: case wvcdm::SESSION_NOT_FOUND_10: - case wvcdm::SESSION_NOT_FOUND_17: case wvcdm::SESSION_NOT_FOUND_18: case wvcdm::SESSION_NOT_FOUND_19: case wvcdm::SESSION_NOT_FOUND_20: @@ -94,8 +93,6 @@ static Status mapCdmResponseType(wvcdm::CdmResponseType res) { case wvcdm::CERT_PROVISIONING_RESPONSE_ERROR_2: case wvcdm::CERT_PROVISIONING_RESPONSE_ERROR_3: case wvcdm::CERT_PROVISIONING_RESPONSE_ERROR_4: - case wvcdm::CERT_PROVISIONING_RESPONSE_ERROR_5: - case wvcdm::CERT_PROVISIONING_RESPONSE_ERROR_6: case wvcdm::CERT_PROVISIONING_RESPONSE_ERROR_7: case wvcdm::CERT_PROVISIONING_RESPONSE_ERROR_8: case wvcdm::DEVICE_CERTIFICATE_ERROR_1: @@ -300,6 +297,7 @@ static Status mapCdmResponseType(wvcdm::CdmResponseType res) { case wvcdm::NOT_AN_ENTITLEMENT_SESSION: case wvcdm::LOAD_ENTITLED_CONTENT_KEYS_ERROR: case wvcdm::GET_PROVISIONING_METHOD_ERROR: + case wvcdm::INVALID_SESSION_2: case wvcdm::DEVICE_CANNOT_REPROVISION: case wvcdm::SET_DECRYPT_HASH_ERROR: case wvcdm::GET_DECRYPT_HASH_ERROR: @@ -347,9 +345,12 @@ static Status mapCdmResponseType(wvcdm::CdmResponseType res) { case wvcdm::NOT_IMPLEMENTED_ERROR: case wvcdm::GET_SRM_VERSION_ERROR: case wvcdm::REWRAP_DEVICE_RSA_KEY_ERROR: - case wvcdm::REWRAP_DEVICE_RSA_KEY_30_ERROR: case wvcdm::INVALID_SRM_LIST: case wvcdm::USAGE_INVALID_PARAMETERS_2: + case wvcdm::CORE_MESSAGE_NOT_FOUND: + case wvcdm::LOAD_PROVISIONING_ERROR: + case wvcdm::LOAD_LICENSE_ERROR: + case wvcdm::LOAD_RENEWAL_ERROR: ALOGW("Returns UNKNOWN error for legacy status: %d", res); return Status::ERROR_DRM_UNKNOWN; diff --git a/libwvdrmengine/level3/arm/libl3oemcrypto.cpp b/libwvdrmengine/level3/arm/libl3oemcrypto.cpp index 8451e15d..1da430a3 100644 --- a/libwvdrmengine/level3/arm/libl3oemcrypto.cpp +++ b/libwvdrmengine/level3/arm/libl3oemcrypto.cpp @@ -73910,7 +73910,7 @@ OEMCryptoResult wvoec3::Level3_DecryptCENC( OEMCrypto_SESSION gzztxhua, const uint8_t *axlkrrjt, size_t awcdllnm, bool qevbsltb, const uint8_t *fgennygy, size_t utvhnuzn, OEMCrypto_DestBufferDesc *wltfdyuo, - const OEMCrypto_CENCEncryptPatternDesc *lgvdezvr, uint8_t ppwymned) { + const OEMCrypto_CENCEncryptPatternDesc_V15 *lgvdezvr, uint8_t ppwymned) { { xcolgugy(17) = axlkrrjt == NULL; if (!xcolgugy(17)) goto pdafwmiy; @@ -73933,7 +73933,7 @@ OEMCryptoResult wvoec3::Level3_DecryptCENC( hgyqhsew:; } { - xcolgugy(20) = wltfdyuo->buffer.clear.max_length < awcdllnm; + xcolgugy(20) = wltfdyuo->buffer.clear.address_length < awcdllnm; if (!xcolgugy(20)) goto omrqtrlh; { return OEMCrypto_ERROR_SHORT_BUFFER; } @@ -74732,11 +74732,12 @@ OEMCryptoResult wvoec3::Level3_LoadEntitledContentKeys( lxzuelar->zuakffjp(); return ioejheit(36); } -OEMCryptoResult wvoec3::Level3_CopyBuffer(OEMCrypto_SESSION twwgmjzm, - const uint8_t *xulijeqc, - size_t fjybderx, - OEMCrypto_DestBufferDesc *fgqcdcbc, - uint8_t ebyjfmye) { +OEMCryptoResult wvoec3::Level3_CopyBuffer( + OEMCrypto_SESSION twwgmjzm, + const uint8_t *xulijeqc, + size_t fjybderx, + const OEMCrypto_DestBufferDesc *fgqcdcbc, + uint8_t ebyjfmye) { { xcolgugy(18) = xulijeqc == NULL; if (!xcolgugy(18)) goto eczushjv; @@ -74759,7 +74760,7 @@ OEMCryptoResult wvoec3::Level3_CopyBuffer(OEMCrypto_SESSION twwgmjzm, xbrocpwq:; } { - xcolgugy(12) = fgqcdcbc->buffer.clear.max_length < fjybderx; + xcolgugy(12) = fgqcdcbc->buffer.clear.address_length < fjybderx; if (!xcolgugy(12)) goto ucfxarbp; { return OEMCrypto_ERROR_SHORT_BUFFER; } diff --git a/libwvdrmengine/level3/arm64/libl3oemcrypto.cpp b/libwvdrmengine/level3/arm64/libl3oemcrypto.cpp index b92b6c09..218122a3 100644 --- a/libwvdrmengine/level3/arm64/libl3oemcrypto.cpp +++ b/libwvdrmengine/level3/arm64/libl3oemcrypto.cpp @@ -73231,7 +73231,7 @@ OEMCryptoResult wvoec3::Level3_DecryptCENC( OEMCrypto_SESSION bipyzplp, const uint8_t *lfkabxqr, size_t roxiuexd, bool vtqxsjqc, const uint8_t *cpkfbvpg, size_t vqwhxqnf, OEMCrypto_DestBufferDesc *zlzjtdeg, - const OEMCrypto_CENCEncryptPatternDesc *cmlkamtw, uint8_t ayhkgzyq) { + const OEMCrypto_CENCEncryptPatternDesc_V15 *cmlkamtw, uint8_t ayhkgzyq) { { wuoaphwn(17) = lfkabxqr == NULL; if (!wuoaphwn(17)) goto ugxxsboy; @@ -73254,7 +73254,7 @@ OEMCryptoResult wvoec3::Level3_DecryptCENC( iijwwbgh:; } { - wuoaphwn(20) = zlzjtdeg->buffer.clear.max_length < roxiuexd; + wuoaphwn(20) = zlzjtdeg->buffer.clear.address_length < roxiuexd; if (!wuoaphwn(20)) goto mtqcubqj; { return OEMCrypto_ERROR_SHORT_BUFFER; } @@ -74053,11 +74053,12 @@ OEMCryptoResult wvoec3::Level3_LoadEntitledContentKeys( ktsakhyf->wrdqmhex(); return moekauvv(36); } -OEMCryptoResult wvoec3::Level3_CopyBuffer(OEMCrypto_SESSION hfxzlvyc, - const uint8_t *jmtmhwxd, - size_t ykyhrrwo, - OEMCrypto_DestBufferDesc *hukbtckl, - uint8_t bknrnxwi) { +OEMCryptoResult wvoec3::Level3_CopyBuffer( + OEMCrypto_SESSION hfxzlvyc, + const uint8_t *jmtmhwxd, + size_t ykyhrrwo, + const OEMCrypto_DestBufferDesc *hukbtckl, + uint8_t bknrnxwi) { { wuoaphwn(4) = jmtmhwxd == NULL; if (!wuoaphwn(4)) goto hwtqzvkk; @@ -74080,7 +74081,7 @@ OEMCryptoResult wvoec3::Level3_CopyBuffer(OEMCrypto_SESSION hfxzlvyc, xmhuqhto:; } { - wuoaphwn(24) = hukbtckl->buffer.clear.max_length < ykyhrrwo; + wuoaphwn(24) = hukbtckl->buffer.clear.address_length < ykyhrrwo; if (!wuoaphwn(24)) goto adfcsnxe; { return OEMCrypto_ERROR_SHORT_BUFFER; } diff --git a/libwvdrmengine/level3/mips/libl3oemcrypto.cpp b/libwvdrmengine/level3/mips/libl3oemcrypto.cpp index d46d889d..2ab9e567 100644 --- a/libwvdrmengine/level3/mips/libl3oemcrypto.cpp +++ b/libwvdrmengine/level3/mips/libl3oemcrypto.cpp @@ -80057,7 +80057,7 @@ OEMCryptoResult wvoec3::Level3_DecryptCENC( OEMCrypto_SESSION ucnmyrep, const uint8_t *wikbwygf, size_t ujdfduzu, bool hfwfltaq, const uint8_t *cdeliuxd, size_t tnmgokdp, OEMCrypto_DestBufferDesc *ddynklqg, - const OEMCrypto_CENCEncryptPatternDesc *aeblrbva, uint8_t qdlbwshm) { + const OEMCrypto_CENCEncryptPatternDesc_V15 *aeblrbva, uint8_t qdlbwshm) { { npdoreif(18) = wikbwygf == NULL; if (!npdoreif(18)) goto eogaqgsk; @@ -80080,7 +80080,7 @@ OEMCryptoResult wvoec3::Level3_DecryptCENC( uxplkoth:; } { - npdoreif(21) = ddynklqg->buffer.clear.max_length < ujdfduzu; + npdoreif(21) = ddynklqg->buffer.clear.address_length < ujdfduzu; if (!npdoreif(21)) goto uqlzzpyg; { return OEMCrypto_ERROR_SHORT_BUFFER; } @@ -80879,11 +80879,12 @@ OEMCryptoResult wvoec3::Level3_LoadEntitledContentKeys( gpkbzadf->eshllukq(); return bgpwwfll(36); } -OEMCryptoResult wvoec3::Level3_CopyBuffer(OEMCrypto_SESSION mzggrxbc, - const uint8_t *bojucqbb, - size_t nkvgpnma, - OEMCrypto_DestBufferDesc *ammjkpfm, - uint8_t uoryadaw) { +OEMCryptoResult wvoec3::Level3_CopyBuffer( + OEMCrypto_SESSION mzggrxbc, + const uint8_t *bojucqbb, + size_t nkvgpnma, + const OEMCrypto_DestBufferDesc *ammjkpfm, + uint8_t uoryadaw) { { npdoreif(0) = bojucqbb == NULL; if (!npdoreif(0)) goto wcdhsrne; @@ -80906,7 +80907,7 @@ OEMCryptoResult wvoec3::Level3_CopyBuffer(OEMCrypto_SESSION mzggrxbc, lpyolhwr:; } { - npdoreif(2) = ammjkpfm->buffer.clear.max_length < nkvgpnma; + npdoreif(2) = ammjkpfm->buffer.clear.address_length < nkvgpnma; if (!npdoreif(2)) goto nfpjpsgd; { return OEMCrypto_ERROR_SHORT_BUFFER; } diff --git a/libwvdrmengine/level3/mips64/libl3oemcrypto.cpp b/libwvdrmengine/level3/mips64/libl3oemcrypto.cpp index 4411d9f1..7b939a6c 100644 --- a/libwvdrmengine/level3/mips64/libl3oemcrypto.cpp +++ b/libwvdrmengine/level3/mips64/libl3oemcrypto.cpp @@ -75480,7 +75480,7 @@ OEMCryptoResult wvoec3::Level3_DecryptCENC( OEMCrypto_SESSION hcjkesec, const uint8_t *njpolpvy, size_t djpojdil, bool lndcajpr, const uint8_t *nhwsayer, size_t tqgdmzkm, OEMCrypto_DestBufferDesc *ogqapwlf, - const OEMCrypto_CENCEncryptPatternDesc *yyynpdag, uint8_t ljvnrnlg) { + const OEMCrypto_CENCEncryptPatternDesc_V15 *yyynpdag, uint8_t ljvnrnlg) { { wxcfbvtr(17) = njpolpvy == NULL; if (!wxcfbvtr(17)) goto lfifouhb; @@ -75503,7 +75503,7 @@ OEMCryptoResult wvoec3::Level3_DecryptCENC( qvxdjgpm:; } { - wxcfbvtr(20) = ogqapwlf->buffer.clear.max_length < djpojdil; + wxcfbvtr(20) = ogqapwlf->buffer.clear.address_length < djpojdil; if (!wxcfbvtr(20)) goto skqgtazd; { return OEMCrypto_ERROR_SHORT_BUFFER; } @@ -76302,11 +76302,12 @@ OEMCryptoResult wvoec3::Level3_LoadEntitledContentKeys( ukmpmhnq->mxlqakvi(); return tyvuoedf(36); } -OEMCryptoResult wvoec3::Level3_CopyBuffer(OEMCrypto_SESSION upfmvyjb, - const uint8_t *wuwagiiq, - size_t tigggpmp, - OEMCrypto_DestBufferDesc *nuxgfgsn, - uint8_t xcdgdruw) { +OEMCryptoResult wvoec3::Level3_CopyBuffer( + OEMCrypto_SESSION upfmvyjb, + const uint8_t *wuwagiiq, + size_t tigggpmp, + const OEMCrypto_DestBufferDesc *nuxgfgsn, + uint8_t xcdgdruw) { { wxcfbvtr(18) = wuwagiiq == NULL; if (!wxcfbvtr(18)) goto fvwugtua; @@ -76329,7 +76330,7 @@ OEMCryptoResult wvoec3::Level3_CopyBuffer(OEMCrypto_SESSION upfmvyjb, yyoawneb:; } { - wxcfbvtr(20) = nuxgfgsn->buffer.clear.max_length < tigggpmp; + wxcfbvtr(20) = nuxgfgsn->buffer.clear.address_length < tigggpmp; if (!wxcfbvtr(20)) goto kbsudokb; { return OEMCrypto_ERROR_SHORT_BUFFER; } diff --git a/libwvdrmengine/level3/x86/libl3oemcrypto.cpp b/libwvdrmengine/level3/x86/libl3oemcrypto.cpp index 9bfab69d..e71a282d 100644 --- a/libwvdrmengine/level3/x86/libl3oemcrypto.cpp +++ b/libwvdrmengine/level3/x86/libl3oemcrypto.cpp @@ -68491,7 +68491,7 @@ OEMCryptoResult wvoec3::Level3_DecryptCENC( OEMCrypto_SESSION jcrkonan, const uint8_t *hhveywzp, size_t yxvwomnu, bool jtytpqro, const uint8_t *wkzubynq, size_t nkpelpma, OEMCrypto_DestBufferDesc *poqthycc, - const OEMCrypto_CENCEncryptPatternDesc *dfesckrs, uint8_t hcyarflg) { + const OEMCrypto_CENCEncryptPatternDesc_V15 *dfesckrs, uint8_t hcyarflg) { { ffweumqb(17) = hhveywzp == NULL; if (!ffweumqb(17)) goto wopjiuji; @@ -68514,7 +68514,7 @@ OEMCryptoResult wvoec3::Level3_DecryptCENC( wpdcpxqc:; } { - ffweumqb(20) = poqthycc->buffer.clear.max_length < yxvwomnu; + ffweumqb(20) = poqthycc->buffer.clear.address_length < yxvwomnu; if (!ffweumqb(20)) goto qdgtguem; { return OEMCrypto_ERROR_SHORT_BUFFER; } @@ -69313,11 +69313,12 @@ OEMCryptoResult wvoec3::Level3_LoadEntitledContentKeys( gvhkuwen->emtzfubn(); return yknatbfo(36); } -OEMCryptoResult wvoec3::Level3_CopyBuffer(OEMCrypto_SESSION rcllrvtu, - const uint8_t *gdwyybvl, - size_t fdvjchqd, - OEMCrypto_DestBufferDesc *romvkqbu, - uint8_t oaxlmvji) { +OEMCryptoResult wvoec3::Level3_CopyBuffer( + OEMCrypto_SESSION rcllrvtu, + const uint8_t *gdwyybvl, + size_t fdvjchqd, + const OEMCrypto_DestBufferDesc *romvkqbu, + uint8_t oaxlmvji) { { ffweumqb(0) = gdwyybvl == NULL; if (!ffweumqb(0)) goto dgjozwzo; @@ -69340,7 +69341,7 @@ OEMCryptoResult wvoec3::Level3_CopyBuffer(OEMCrypto_SESSION rcllrvtu, ruwwnscq:; } { - ffweumqb(14) = romvkqbu->buffer.clear.max_length < fdvjchqd; + ffweumqb(14) = romvkqbu->buffer.clear.address_length < fdvjchqd; if (!ffweumqb(14)) goto lgxdswag; { return OEMCrypto_ERROR_SHORT_BUFFER; } diff --git a/libwvdrmengine/level3/x86_64/libl3oemcrypto.cpp b/libwvdrmengine/level3/x86_64/libl3oemcrypto.cpp index fab1970f..96e42962 100644 --- a/libwvdrmengine/level3/x86_64/libl3oemcrypto.cpp +++ b/libwvdrmengine/level3/x86_64/libl3oemcrypto.cpp @@ -64917,7 +64917,7 @@ OEMCryptoResult wvoec3::Level3_DecryptCENC( OEMCrypto_SESSION ddtjvbdq, const uint8_t *lvhsesuk, size_t lxfceryb, bool uibjohfb, const uint8_t *wxuirukl, size_t ouvolqlc, OEMCrypto_DestBufferDesc *ncsqbesl, - const OEMCrypto_CENCEncryptPatternDesc *yqrlrghj, uint8_t wcdnagse) { + const OEMCrypto_CENCEncryptPatternDesc_V15 *yqrlrghj, uint8_t wcdnagse) { { mundzuuz(16) = lvhsesuk == NULL; if (!mundzuuz(16)) goto dmdzhbcq; @@ -64940,7 +64940,7 @@ OEMCryptoResult wvoec3::Level3_DecryptCENC( hrqfgtgz:; } { - mundzuuz(19) = ncsqbesl->buffer.clear.max_length < lxfceryb; + mundzuuz(19) = ncsqbesl->buffer.clear.address_length < lxfceryb; if (!mundzuuz(19)) goto qdbaawum; { return OEMCrypto_ERROR_SHORT_BUFFER; } @@ -65739,11 +65739,12 @@ OEMCryptoResult wvoec3::Level3_LoadEntitledContentKeys( dzaoanhf->rofponet(); return vvwoenhi(36); } -OEMCryptoResult wvoec3::Level3_CopyBuffer(OEMCrypto_SESSION uzysqwty, - const uint8_t *bqkkehez, - size_t jukuudyt, - OEMCrypto_DestBufferDesc *nipfbcgm, - uint8_t zewgnbta) { +OEMCryptoResult wvoec3::Level3_CopyBuffer( + OEMCrypto_SESSION uzysqwty, + const uint8_t *bqkkehez, + size_t jukuudyt, + const OEMCrypto_DestBufferDesc *nipfbcgm, + uint8_t zewgnbta) { { mundzuuz(2) = bqkkehez == NULL; if (!mundzuuz(2)) goto klnbkmjs; @@ -65766,7 +65767,7 @@ OEMCryptoResult wvoec3::Level3_CopyBuffer(OEMCrypto_SESSION uzysqwty, asmsppma:; } { - mundzuuz(21) = nipfbcgm->buffer.clear.max_length < jukuudyt; + mundzuuz(21) = nipfbcgm->buffer.clear.address_length < jukuudyt; if (!mundzuuz(21)) goto oswphilv; { return OEMCrypto_ERROR_SHORT_BUFFER; } diff --git a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp index c31decd4..dad5e5bb 100644 --- a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp @@ -11,8 +11,8 @@ #include "WVDrmPlugin.h" #include -#include #include +#include #include #include @@ -536,6 +536,10 @@ status_t WVDrmPlugin::getPropertyString(const String8& name, mCDM->GetDecryptHashError(mDecryptHashSessionId, &hash_error_string); value = hash_error_string.c_str(); return mapCdmResponseType(res); + } else if (name == "maxUsageEntriesSupported") { + return queryProperty(QUERY_KEY_MAX_USAGE_TABLE_ENTRIES, value); + } else if (name == "oemCryptoApiMinorVersion") { + return queryProperty(QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION, value); } else { ALOGE("App requested unknown string property %s", name.string()); return android::ERROR_DRM_CANNOT_HANDLE; diff --git a/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp index d988447c..528b0f2e 100644 --- a/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp @@ -14,6 +14,8 @@ #include "WVDrmPlugin.h" +#include "HidlTypes.h" +#include "TypeConvert.h" #include "android-base/macros.h" #include "hidl_metrics_adapter.h" #include "mapErrors-inl.h" @@ -21,8 +23,6 @@ #include "metrics.pb.h" #include "openssl/sha.h" #include "wv_cdm_constants.h" -#include "TypeConvert.h" -#include "HidlTypes.h" namespace { @@ -1244,6 +1244,10 @@ Return WVDrmPlugin::getPropertyString(const hidl_string& propertyName, } else if (name == "decryptHashError") { status = mapCdmResponseType( mCDM->GetDecryptHashError(mDecryptHashSessionId, &value)); + } else if (name == "maxUsageEntriesSupported") { + status = queryProperty(wvcdm::QUERY_KEY_MAX_USAGE_TABLE_ENTRIES, value); + } else if (name == "oemCryptoApiMinorVersion") { + status = queryProperty(wvcdm::QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION, value); } else { ALOGE("App requested unknown string property %s", name.c_str()); status = Status::ERROR_DRM_CANNOT_HANDLE; diff --git a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h index fffb93a9..f50ce0eb 100644 --- a/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h +++ b/libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h @@ -23,110 +23,80 @@ #include #include +#include "OEMCryptoCENCCommon.h" + #ifdef __cplusplus extern "C" { #endif typedef uint32_t OEMCrypto_SESSION; -typedef enum OEMCryptoResult { - OEMCrypto_SUCCESS = 0, - OEMCrypto_ERROR_INIT_FAILED = 1, - OEMCrypto_ERROR_TERMINATE_FAILED = 2, - OEMCrypto_ERROR_OPEN_FAILURE = 3, - OEMCrypto_ERROR_CLOSE_FAILURE = 4, - OEMCrypto_ERROR_ENTER_SECURE_PLAYBACK_FAILED = 5, // deprecated - OEMCrypto_ERROR_EXIT_SECURE_PLAYBACK_FAILED = 6, // deprecated - OEMCrypto_ERROR_SHORT_BUFFER = 7, - OEMCrypto_ERROR_NO_DEVICE_KEY = 8, // no keybox device key. - OEMCrypto_ERROR_NO_ASSET_KEY = 9, - OEMCrypto_ERROR_KEYBOX_INVALID = 10, - OEMCrypto_ERROR_NO_KEYDATA = 11, - OEMCrypto_ERROR_NO_CW = 12, - OEMCrypto_ERROR_DECRYPT_FAILED = 13, - OEMCrypto_ERROR_WRITE_KEYBOX = 14, - OEMCrypto_ERROR_WRAP_KEYBOX = 15, - OEMCrypto_ERROR_BAD_MAGIC = 16, - OEMCrypto_ERROR_BAD_CRC = 17, - OEMCrypto_ERROR_NO_DEVICEID = 18, - OEMCrypto_ERROR_RNG_FAILED = 19, - OEMCrypto_ERROR_RNG_NOT_SUPPORTED = 20, - OEMCrypto_ERROR_SETUP = 21, - OEMCrypto_ERROR_OPEN_SESSION_FAILED = 22, - OEMCrypto_ERROR_CLOSE_SESSION_FAILED = 23, - OEMCrypto_ERROR_INVALID_SESSION = 24, - OEMCrypto_ERROR_NOT_IMPLEMENTED = 25, - OEMCrypto_ERROR_NO_CONTENT_KEY = 26, - OEMCrypto_ERROR_CONTROL_INVALID = 27, - OEMCrypto_ERROR_UNKNOWN_FAILURE = 28, - OEMCrypto_ERROR_INVALID_CONTEXT = 29, - OEMCrypto_ERROR_SIGNATURE_FAILURE = 30, - OEMCrypto_ERROR_TOO_MANY_SESSIONS = 31, - OEMCrypto_ERROR_INVALID_NONCE = 32, - OEMCrypto_ERROR_TOO_MANY_KEYS = 33, - OEMCrypto_ERROR_DEVICE_NOT_RSA_PROVISIONED = 34, - OEMCrypto_ERROR_INVALID_RSA_KEY = 35, - OEMCrypto_ERROR_KEY_EXPIRED = 36, - OEMCrypto_ERROR_INSUFFICIENT_RESOURCES = 37, - OEMCrypto_ERROR_INSUFFICIENT_HDCP = 38, - OEMCrypto_ERROR_BUFFER_TOO_LARGE = 39, - OEMCrypto_WARNING_GENERATION_SKEW = 40, // Warning, not an error. - OEMCrypto_ERROR_GENERATION_SKEW = 41, - OEMCrypto_LOCAL_DISPLAY_ONLY = 42, // Info, not an error. - OEMCrypto_ERROR_ANALOG_OUTPUT = 43, - OEMCrypto_ERROR_WRONG_PST = 44, - OEMCrypto_ERROR_WRONG_KEYS = 45, - OEMCrypto_ERROR_MISSING_MASTER = 46, - OEMCrypto_ERROR_LICENSE_INACTIVE = 47, - OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE = 48, - OEMCrypto_ERROR_ENTRY_IN_USE = 49, - OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE = 50, // Reserved. Do not use. - OEMCrypto_KEY_NOT_LOADED = 51, // obsolete. use error 26. - OEMCrypto_KEY_NOT_ENTITLED = 52, - OEMCrypto_ERROR_BAD_HASH = 53, - OEMCrypto_ERROR_OUTPUT_TOO_LARGE = 54, - OEMCrypto_ERROR_SESSION_LOST_STATE = 55, - OEMCrypto_ERROR_SYSTEM_INVALIDATED = 56, -} OEMCryptoResult; +/* + * The memory referenced by OEMCrypto_SharedMemory* is safe to be placed in + * shared memory. The only data that should be placed into shared + * memory is the contents of input/output buffers, i.e. data that will + * not introduce security vulnerabilities if it is subject to + * modification while being accessed. + */ +typedef uint8_t OEMCrypto_SharedMemory; /* - * OEMCrypto_DestBufferDesc - * Describes the type and access information for the memory to receive - * decrypted data. + * OEMCrypto_DestBufferDesc Structure * - * The OEMCrypto API supports a range of client device architectures. - * Different architectures have different methods for acquiring and securing - * buffers that will hold portions of the audio or video stream after - * decryption. Three basic strategies are recognized for handling decrypted - * stream data: - * 1. Return the decrypted data in the clear into normal user memory - * (ClearBuffer). The caller uses normal memory allocation methods to - * acquire a buffer, and supplies the memory address of the buffer in the - * descriptor. - * 2. Place the decrypted data into protected memory (SecureBuffer). The - * caller uses a platform-specific method to acquire the protected buffer - * and a user-memory handle that references it. The handle is supplied - * to the decrypt call in the descriptor. If the buffer is filled with - * several OEMCrypto calls, the same handle will be used, and the offset - * will be incremented to indicate where the next write should take place. - * 3. Place the decrypted data directly into the audio or video decoder fifo - * (Direct). The caller will use platform-specific methods to initialize - * the fifo and the decoders. The decrypted stream data is not accessible - * to the caller. + * Description: + * This structure is used as parameters in the OEMCrypto_DecryptCENC and + * OEMCrypto_CopyBuffer functions. This describes the type and access + * information for the memory to receive decrypted data. * - * Specific fields are as follows: + * The OEMCrypto API supports a range of client device architectures. + * Different architectures have different methods for acquiring and securing + * buffers that will hold portions of the audio or video stream after + * decryption. Three basic strategies are recognized for handling decrypted + * stream data: * - * (type == OEMCrypto_BufferType_Clear) - * address - Address of start of user memory buffer. - * max_length - Size of user memory buffer. - * (type == OEMCrypto_BufferType_Secure) - * buffer - handle to a platform-specific secure buffer. - * max_length - Size of platform-specific secure buffer. - * offset - offset from beginning of buffer to which OEMCrypto should write. - * (type == OEMCrypto_BufferType_Direct) - * is_video - If true, decrypted bytes are routed to the video - * decoder. If false, decrypted bytes are routed to the - * audio decoder. + * 1. Return the decrypted data in the clear into normal user memory + * (ClearBuffer). The caller uses normal memory allocation methods to + * acquire a buffer, and supplies the memory address of the buffer in + * the descriptor. + * 2. Place the decrypted data into protected memory (SecureBuffer). The + * caller uses a platform-specific method to acquire the protected + * buffer and a user-memory handle that references it. The handle is + * supplied to the decrypt call in the descriptor. If the buffer is + * filled with several OEMCrypto calls, the same handle will be used, + * and the offset will be incremented to indicate where the next write + * should take place. + * 3. Place the decrypted data directly into the audio or video decoder + * fifo (Direct). The caller will use platform-specific methods to + * initialize the fifo and the decoders. The decrypted stream data is + * not accessible to the caller. This is used on some platforms only. + * + * Fields: + * [in] type: A tag that indicates which variant of the union is valid for + * this instance of the structure. + * [variant] clear: This variant is valid when the type is + * OEMCrypto_BufferType_Clear. This OEMCrypto_DestBufferDesc indicates output + * should be written to a clear buffer. + * [in] address: A pointer to the address in memory to begin writing output. + * [in] address_length: The length of the buffer that is available to contain + * output. + * [variant] secure: This variant is valid when the type is + * OEMCrypto_BufferType_Secure. This OEMCrypto_DestBufferDesc indicates + * output should be written to a secure buffer. The decrypted output must + * never leave the secure area until it is output from the device. + * [in] handle: An opaque handle to a secure buffer. The meaning of this + * handle is platform-specific. + * [in] handle_length: The length of the data contained in the secure buffer. + * [in] offset: An offset indicating where in the secure buffer to start + * writing data. + * [variant] direct: This variant is valid when the type is + * OEMCrypto_BufferType_Direct. This OEMCrypto_DestBufferDesc indicates + * output should be written directly to the decoder. + * [in] is_video: A flag indicating if the data is video and should be sent + * to the video decoder. If this is false, the data can be assumed to be + * audio and sent to the audio decoder. + * + * Version: + * This struct changed in API version 16. */ typedef enum OEMCryptoBufferType { OEMCrypto_BufferType_Clear, @@ -138,12 +108,12 @@ typedef struct { OEMCryptoBufferType type; union { struct { // type == OEMCrypto_BufferType_Clear - uint8_t* address; - size_t max_length; + OEMCrypto_SharedMemory* address; + size_t address_length; } clear; struct { // type == OEMCrypto_BufferType_Secure void* handle; - size_t max_length; + size_t handle_length; size_t offset; } secure; struct { // type == OEMCrypto_BufferType_Direct @@ -152,6 +122,116 @@ typedef struct { } buffer; } OEMCrypto_DestBufferDesc; +/* + * OEMCrypto_InputOutputPair Structure + * + * Description: + * This structure is used as parameters in the OEMCrypto_DecryptCENC function. + * + * Fields: + * [in] input_data: An unaligned pointer to this sample from the stream. + * [in] input_data_length: The length of this sample in the stream, in bytes. + * [in] output: A caller-owned descriptor that specifies the handling of the + * decrypted byte stream. See OEMCrypto_DestbufferDesc for details. + * + * Version: + * This struct changed in API version 16. + */ +typedef struct { + const OEMCrypto_SharedMemory* input_data; // source for encrypted data. + size_t input_data_length; // length of encrypted data. + OEMCrypto_DestBufferDesc output_descriptor; // destination for clear data. +} OEMCrypto_InputOutputPair; + +/* + * OEMCrypto_SubSampleDescription Structure + * + * Description: + * This structure is used as parameters in the OEMCrypto_DecryptCENC + * function. In the DASH specification, a sample is composed of multiple + * samples, and each subsample is composed of two regions. The first region + * is clear unprotected data. We also call this clear data or unencrypted + * data. Immediately following the clear region is the protected region. The + * protected region is encrypted or encrypted with a pattern. The pattern and + * number of bytes that are encrypted in the protected region is discussed in + * this document when we talk about the function OEMCryptoDecryptCENC. For + * historic reasons, this document also calls the protected region the + * encrypted region. + * + * Fields: + * [in] num_bytes_clear: The number of unprotected bytes in this subsample. + * The clear bytes come before the encrypted bytes. + * [in] num_bytes_encrypted: The number of protected bytes in this subsample. + * The protected bytes come after the clear bytes. + * [in] subsample_flags: bitwise flags indicating if this is the first, + * middle, or last subsample in a sample. 1 = first subsample, 2 = last + * subsample, 3 = both first and last subsample, 0 = neither first nor last + * subsample. + * [in] block_offset: This will only be non-zero for the 'cenc' scheme. If it + * is non-zero, the decryption block boundary is different from the start of + * the data. block_offset should be subtracted from data to compute the + * starting address of the first decrypted block. The bytes between the + * decryption block start address and data are discarded after decryption. It + * does not adjust the beginning of the source or destination data. This + * parameter satisfies 0 <= block_offset < 16. + * + * Version: + * This struct changed in API version 16. + */ +typedef struct { + size_t num_bytes_clear; + size_t num_bytes_encrypted; + uint8_t subsample_flags; // is this the first/last subsample in a sample? + size_t block_offset; // used for CTR "cenc" mode only. +} OEMCrypto_SubSampleDescription; + +#define OEMCrypto_FirstSubsample 1 +#define OEMCrypto_LastSubsample 2 + +/* + * OEMCrypto_SampleDescription Structure + * + * Description: + * This structure is used as parameters in the OEMCrypto_DecryptCENC function. + * + * Fields: + * [in] buffers: A structure containing information about the input and + * output buffers. + * [in] iv: A 16-byte array containing the IV for the initial subsample of + * the sample. + * [in] subsamples: A caller-owned array of OEMCrypto_SubSampleDescription + * structures. Each entry in this array describes one subsample in the sample. + * [in] subsamples_length: The length of the array pointed to by the + * subsamples parameter. + * + * Version: + * This struct changed in API version 16. + */ +typedef struct { + OEMCrypto_InputOutputPair buffers; // The source and destination buffers. + uint8_t iv[16]; // The IV for the initial subsample. + const OEMCrypto_SubSampleDescription* subsamples; // subsamples array. + size_t subsamples_length; // the number of subsamples in the sample. +} OEMCrypto_SampleDescription; + +/* + * OEMCrypto_CENCEncryptPatternDesc Structure + * + * Description: + * This structure is used as parameters in the OEMCrypto_DecryptCENC function. + * + * Fields: + * [in] encrypt: The number of 16-byte crypto blocks to encrypt. + * [in] skip: The number of 16-byte crypto blocks to leave in the clear. + * + * Version: + * This struct changed in API version 16. + */ +typedef struct { + size_t encrypt; // number of 16 byte blocks to decrypt. + size_t skip; // number of 16 byte blocks to leave in clear. +} OEMCrypto_CENCEncryptPatternDesc; + /** OEMCryptoCipherMode is used in SelectKey to prepare a key for either CTR * decryption or CBC decryption. */ @@ -160,66 +240,6 @@ typedef enum OEMCryptoCipherMode { OEMCrypto_CipherMode_CBC, } OEMCryptoCipherMode; -/** OEMCrypto_LicenseType is used in LoadKeys to indicate if the key objects - * are for content keys, or for entitlement keys. - */ -typedef enum OEMCrypto_LicenseType { - OEMCrypto_ContentLicense = 0, - OEMCrypto_EntitlementLicense = 1 -} OEMCrypto_LicenseType; - -/* - * OEMCrypto_Substring - * - * Used to indicate a substring of a signed message in OEMCrypto_LoadKeys and - * other functions which must verify that a parameter is contained within a - * signed message. - */ -typedef struct { - size_t offset; - size_t length; -} OEMCrypto_Substring; - -/* - * OEMCrypto_KeyObject - * Points to the relevant fields for a content key. The fields are extracted - * from the License Response message offered to OEMCrypto_LoadKeys(). Each - * field points to one of the components of the key. Key data, key control, - * and both IV fields are 128 bits (16 bytes): - * key_id - the unique id of this key. - * key_id_length - the size of key_id. OEMCrypto may assume this is at - * most 16. However, OEMCrypto shall correctly handle key id lengths - * from 1 to 16 bytes. - * key_data_iv - the IV for performing AES-128-CBC decryption of the - * key_data field. - * key_data - the key data. It is encrypted (AES-128-CBC) with the - * session's derived encrypt key and the key_data_iv. - * key_control_iv - the IV for performing AES-128-CBC decryption of the - * key_control field. - * key_control - the key control block. It is encrypted (AES-128-CBC) with - * the content key from the key_data field. - * - * The memory for the OEMCrypto_KeyObject fields is allocated and freed - * by the caller of OEMCrypto_LoadKeys(). - */ -typedef struct { - OEMCrypto_Substring key_id; - OEMCrypto_Substring key_data_iv; - OEMCrypto_Substring key_data; - OEMCrypto_Substring key_control_iv; - OEMCrypto_Substring key_control; -} OEMCrypto_KeyObject; - -/* - * SRM_Restriction_Data - * - * Structure passed into LoadKeys to specify required SRM version. - */ -typedef struct { - uint8_t verification[8]; // must be "HDCPDATA" - uint32_t minimum_srm_version; // version number in network byte order. -} SRM_Restriction_Data; - /* * OEMCrypto_EntitledContentKeyObject * Contains encrypted content key data for loading into the sessions keytable. @@ -242,6 +262,7 @@ typedef struct { /* * OEMCrypto_KeyRefreshObject + * This structure is being deprecated. It is only used for legacy licenses. * Points to the relevant fields for renewing a content key. The fields are * extracted from the License Renewal Response message offered to * OEMCrypto_RefreshKeys(). Each field points to one of the components of @@ -275,34 +296,6 @@ typedef enum OEMCrypto_Algorithm { OEMCrypto_HMAC_SHA256 = 1, } OEMCrypto_Algorithm; -/* - * Flags indicating data endpoints in OEMCrypto_DecryptCENC. - */ -#define OEMCrypto_FirstSubsample 1 -#define OEMCrypto_LastSubsample 2 - -/* OEMCrypto_CENCEncryptPatternDesc - * This is used in OEMCrypto_DecryptCENC to indicate the encrypt/skip pattern - * used, as specified in the CENC standard. - */ -typedef struct { - size_t encrypt; // number of 16 byte blocks to decrypt. - size_t skip; // number of 16 byte blocks to leave in clear. - size_t offset; // offset into the pattern in blocks for this call. -} OEMCrypto_CENCEncryptPatternDesc; - -/* - * OEMCrypto_Usage_Entry_Status. - * Valid values for status in the usage table. - */ -typedef enum OEMCrypto_Usage_Entry_Status { - kUnused = 0, - kActive = 1, - kInactive = 2, // Deprecated. Used kInactiveUsed or kInactiveUnused. - kInactiveUsed = 3, - kInactiveUnused = 4, -} OEMCrypto_Usage_Entry_Status; - /* * OEMCrypto_PST_Report is used to report an entry from the Usage Table. * @@ -367,11 +360,14 @@ typedef enum OEMCrypto_ProvisioningMethod { } OEMCrypto_ProvisioningMethod; /* - * Flags indicating RSA keys supported. + * Flags indicating public/private key types supported. */ #define OEMCrypto_Supports_RSA_2048bit 0x1 #define OEMCrypto_Supports_RSA_3072bit 0x2 #define OEMCrypto_Supports_RSA_CAST 0x10 +#define OEMCrypto_Supports_ECC_secp256r1 0x100 +#define OEMCrypto_Supports_ECC_secp384r1 0x200 +#define OEMCrypto_Supports_ECC_secp521r1 0x400 /* * Flags indicating full decrypt path hash supported. @@ -393,6 +389,7 @@ typedef enum OEMCrypto_ProvisioningMethod { /* * Obfuscation Renames. */ +// clang-format off #define OEMCrypto_Initialize _oecc01 #define OEMCrypto_Terminate _oecc02 #define OEMCrypto_InstallKeybox _oecc03 @@ -413,7 +410,7 @@ typedef enum OEMCrypto_ProvisioningMethod { #define OEMCrypto_OpenSession _oecc09 #define OEMCrypto_CloseSession _oecc10 #define OEMCrypto_DecryptCTR_V10 _oecc11 -#define OEMCrypto_GenerateDerivedKeys _oecc12 +#define OEMCrypto_GenerateDerivedKeys_V15 _oecc12 #define OEMCrypto_GenerateSignature _oecc13 #define OEMCrypto_GenerateNonce _oecc14 #define OEMCrypto_LoadKeys_V8 _oecc15 @@ -449,9 +446,9 @@ typedef enum OEMCrypto_ProvisioningMethod { #define OEMCrypto_LoadTestRSAKey _oecc45 #define OEMCrypto_Security_Patch_Level _oecc46 #define OEMCrypto_LoadKeys_V11_or_V12 _oecc47 -#define OEMCrypto_DecryptCENC _oecc48 +#define OEMCrypto_DecryptCENC_V15 _oecc48 #define OEMCrypto_GetProvisioningMethod _oecc49 -#define OEMCrypto_GetOEMPublicCertificate _oecc50 +#define OEMCrypto_GetOEMPublicCertificate_V15 _oecc50 #define OEMCrypto_RewrapDeviceRSAKey30 _oecc51 #define OEMCrypto_SupportedCertificates _oecc52 #define OEMCrypto_IsSRMUpdateSupported _oecc53 @@ -485,6 +482,20 @@ typedef enum OEMCrypto_ProvisioningMethod { #define OEMCrypto_RefreshKeys _oecc91 #define OEMCrypto_LoadEntitledContentKeys _oecc92 #define OEMCrypto_CopyBuffer _oecc93 +#define OEMCrypto_MaximumUsageTableHeaderSize _oecc94 +#define OEMCrypto_GenerateDerivedKeys _oecc95 +#define OEMCrypto_PrepAndSignLicenseRequest _oecc96 +#define OEMCrypto_PrepAndSignRenewalRequest _oecc97 +#define OEMCrypto_PrepAndSignProvisioningRequest _oecc98 +#define OEMCrypto_LoadLicense _oecc99 +#define OEMCrypto_LoadRenewal _oecc101 +#define OEMCrypto_LoadProvisioning _oecc102 +#define OEMCrypto_LoadOEMPrivateKey _oecc103 +#define OEMCrypto_GetOEMPublicCertificate _oecc104 +#define OEMCrypto_DecryptCENC _oecc105 +#define OEMCrypto_LoadDRMPrivateKey _oecc107 +#define OEMCrypto_MinorAPIVersion _oecc108 +// clang-format on /* * OEMCrypto_SetSandbox @@ -510,7 +521,7 @@ typedef enum OEMCrypto_ProvisioningMethod { * * Parameters: * [in] sandbox_id: a short string unique to the current sandbox. - * [in] sandobx_id_length: length of sandbox_id. + * [in] sandbox_id_length: length of sandbox_id. * * Returns: * OEMCrypto_SUCCESS success @@ -585,6 +596,11 @@ OEMCryptoResult OEMCrypto_Terminate(void); * session, and return a session handle that identifies that session in * future calls. * + * This function shall call ODK_InitializeSessionValues to initialize the + * session's clock values, timer values, and nonce values. + * ODK_InitializeSessionValues is described in the document "License Duration + * and Renewal", to initialize the sessions clock values. + * * Parameters: * [out] session: an opaque handle that the crypto firmware uses to identify * the session. @@ -602,7 +618,7 @@ OEMCryptoResult OEMCrypto_Terminate(void); * on the OEMCrypto system. * * Version: - * This method changed in API version 5. + * This method changed in API version 16. */ OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session); @@ -683,8 +699,8 @@ OEMCryptoResult OEMCrypto_CloseSession(OEMCrypto_SESSION session); * OEMCrypto_ERROR_SYSTEM_INVALIDATED * * Buffer Sizes: - * OEMCrypto shall support mac_key_context and enc_key_context sizes of at - * least 8 KiB. + * OEMCrypto shall support mac_key_context and enc_key_context sizes as + * described in the section OEMCrypto_ResourceRatingTier. * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffers are * too large. * @@ -698,11 +714,11 @@ OEMCryptoResult OEMCrypto_CloseSession(OEMCrypto_SESSION session); * Version: * This method changed in API version 12. */ -OEMCryptoResult OEMCrypto_GenerateDerivedKeys(OEMCrypto_SESSION session, - const uint8_t* mac_key_context, - uint32_t mac_key_context_length, - const uint8_t* enc_key_context, - uint32_t enc_key_context_length); +OEMCryptoResult OEMCrypto_GenerateDerivedKeys( + OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* mac_key_context, + size_t mac_key_context_length, + const OEMCrypto_SharedMemory* enc_key_context, + size_t enc_key_context_length); /* * OEMCrypto_DeriveKeysFromSessionKey @@ -715,12 +731,19 @@ OEMCryptoResult OEMCrypto_GenerateDerivedKeys(OEMCrypto_SESSION session, * This function is similar to OEMCrypto_GenerateDerivedKeys, except that it * uses a session key to generate the secondary keys instead of the Widevine * Keybox device key. These three keys will be stored in secure memory until - * the next call to LoadKeys. The session key is passed in encrypted by the - * device RSA public key, and must be decrypted with the RSA private key - * before use. + * the next call to LoadLicense or LoadProvisioning. + * + * If the session's private key is an RSA key, then the session key is passed + * in encrypted by the device RSA public key as the derivation_key, and must + * be decrypted with the RSA private key before use. + * + * If the sesion's private key is an ECC key, then the session key is the + * SHA256 of the shared secret key calculated by ECDH between the device's + * ECC private key and the derivation_key. See the document "OEMCrypto + * Elliptic Curve Support" for details. * * Once the enc_key and mac_keys have been generated, all calls to LoadKeys - * and RefreshKeys proceed in the same manner for license requests using RSA + * or LoadLicense proceed in the same manner for license requests using RSA * or using a Widevine keybox token. * * Verification: @@ -731,9 +754,9 @@ OEMCryptoResult OEMCrypto_GenerateDerivedKeys(OEMCrypto_SESSION session, * * Parameters: * [in] session: handle for the session to be used. - * [in] enc_session_key: session key, encrypted with the public RSA key (from + * [in] derivation_key: session key, encrypted with the public RSA key (from * the DRM certifcate) using RSA-OAEP. - * n_key_l[in] enc_sessioength: length of session_key, in bytes. + * [in] derivation_key_length: length of derivation_key, in bytes. * [in] mac_key_context: pointer to memory containing context data for * computing the HMAC generation key. * [in] mac_key_context_length: length of the HMAC key context data, in bytes. @@ -762,8 +785,8 @@ OEMCryptoResult OEMCrypto_GenerateDerivedKeys(OEMCrypto_SESSION session, * OEMCrypto_ERROR_SYSTEM_INVALIDATED * * Buffer Sizes: - * OEMCrypto shall support mac_key_context and enc_key_context sizes of at - * least 8 KiB. + * OEMCrypto shall support mac_key_context and enc_key_context sizes as + * described in the section OEMCrypto_ResourceRatingTier. * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffers are * too large. * @@ -775,12 +798,13 @@ OEMCryptoResult OEMCrypto_GenerateDerivedKeys(OEMCrypto_SESSION session, * session, and a read lock on the OEMCrypto system. * * Version: - * This method changed in API version 12. + * This method changed in API version 16. */ OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey( - OEMCrypto_SESSION session, const uint8_t* enc_session_key, - size_t enc_session_key_length, const uint8_t* mac_key_context, - size_t mac_key_context_length, const uint8_t* enc_key_context, + OEMCrypto_SESSION session, const uint8_t* derivation_key, + size_t derivation_key_length, const OEMCrypto_SharedMemory* mac_key_context, + size_t mac_key_context_length, + const OEMCrypto_SharedMemory* enc_key_context, size_t enc_key_context_length); /* @@ -788,21 +812,26 @@ OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey( * * Description: * Generates a 32-bit nonce to detect possible replay attack on the key - * control block. The nonce is stored in secure memory and will be used for - * the next call to LoadKeys. + * control block. The nonce is stored in secure memory and will be used in + * the license or provisioning request. * * Because the nonce will be used to prevent replay attacks, it is desirable * that a rogue application cannot rapidly call this function until a - * repeated nonce is created randomly. With this in mind, if more than 20 - * nonces are requested within one second, OEMCrypto will return an error - * after the 20th and not generate any more nonces for the rest of the - * second. After an error, if the application waits at least one second - * before requesting more nonces, then OEMCrypto will reset the error - * condition and generate valid nonces again. + * repeated nonce is created randomly. This is called a nonce flood. With + * this in mind, if more than 200 nonces are requested within one second, + * OEMCrypto will return an error after the 200th and not generate any more + * nonces for the rest of the second. After an error, if the application + * waits at least one second before requesting more nonces, then OEMCrypto + * will reset the error condition and generate valid nonces again. * - * To prevent Birthday Paradox attacks, OEMCrypto shall verify that the value - * generated is not in this session's nonce table, and that it is not in the - * nonce table of any other session. + * The nonce should be stored in the sessions ODK_NonceValue field by calling + * the function ODK_SetNonceValue(&nonce_values, nonce). The ODK functions + * are documented in "Widevine Core Message Serialization". + * + * This function shall only be called at most once per open session. It shall + * only be called before signing either a provisioning request or a license + * request. If an attempt is made to generate a nonce while in the wrong + * state, an error of OEMCrypto_ERROR_INVALID_CONTEXT is returned. * * Parameters: * [in] session: handle for the session to be used. @@ -826,26 +855,122 @@ OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey( * on the OEMCrypto system. * * Version: - * This method changed in API version 5. + * This method changed in API version 16. */ OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session, uint32_t* nonce); /* - * OEMCrypto_GenerateSignature + * OEMCrypto_PrepAndSignLicenseRequest * * Description: - * Generates a HMAC-SHA256 signature using the mac_key[client] for license - * request signing under the license server protocol for CENC. + * OEMCrypto will use ODK_PrepareCoreLicenseRequest to prepare the core + * message. If it returns OEMCrypto_SUCCESS, then OEMCrypto shall sign the + * the message body using the DRM certificate's private key. If it returns an + * error, the error should be returned by OEMCrypto to the CDM layer. + * ODK_PrepareCoreLicenseRequest is described in the document "Widevine Core + * Message Serialization". + * + * The message body is the buffer starting at message + core_message_size, + * and with length message_length-core_message_size. The reason OEMCrypto + * only signs the message body and not the entire message is to allow a v16 + * device to request a license from a v15 license server. + * + * If the session's private RSA key has an "allowed_schemes" bit field, then + * it must be 0x1 (RSASSA-PSS with SHA1). If not, then an error of + * OEMCrypto_ERROR_SIGNATURE_FAILURE shall be returned. + * + * OEMCrypto shall compute a hash of the core license request. The core + * license request is the buffer starting at message and with length + * core_message_size. The has will be saved with the session and verified + * that it matches a hash in the license response. + * + * OEMCrypto shall also call the function ODK_InitializeClockValues, + * described in the document "License Duration and Renewal", to initialize + * the sessions clock values. + * + * Refer to the Signing Messages Sent to a Server section above for more + * details about the signature algorithm. + * + * NOTE: if signature pointer is null and/or input signature_length is zero, + * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * signature_length to the size needed to receive the output signature. + * + * Parameters: + * [in/out] message: Pointer to memory for the entire message. Modified by + * OEMCrypto via the ODK library. + * [in] message_length: length of the entire message buffer. + * [in/out] core_message_size: length of the core message at the beginning of + * the message. (in) size of buffer reserved for the core message, in + * bytes. (out) actual length of the core message, in bytes. + * [out] signature: pointer to memory to receive the computed signature. + * [in/out] signature_length: (in) length of the signature buffer, in bytes. + * (out) actual length of the signature, in bytes. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_SHORT_BUFFER if signature buffer is not large enough to + * hold the signature. + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_SIGNATURE_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * Buffer Sizes: + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier. + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * Threading: + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * Version: + * This method changed in API version 16. + */ +OEMCryptoResult OEMCrypto_PrepAndSignLicenseRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, size_t* signature_length); + +/* + * OEMCrypto_PrepAndSignRenewalRequest + * + * Description: + * OEMCrypto will use ODK_PrepareCoreRenewalRequest, as described in the + * document "Widevine Core Message Serialization", to prepare the core + * message. + * + * If it returns an error, the error should be returned by OEMCrypto to the + * CDM layer. If it returns OEMCrypto_SUCCESS, then OEMCrypto computes the + * signature using the renewal mac key which was delivered in the license via + * LoadLicense. + * + * If nonce_values.api_level is 16, then OEMCrypto shall compute the + * signature of the entire message using the session's client renewal mac + * key. The entire message is the buffer starting at message with length + * message_length. + * + * If nonce_values.api_major_version is 15, then OEMCrypto shall compute the + * signature of the message body using the session's client renewal mac key. + * The message body is the buffer starting at message+core_message_size with + * length message_length-core_message_size. If the session has not had a + * license loaded, it will use the usage entries client mac key to sign the + * message body. + * + * This function generates a HMAC-SHA256 signature using the mac_key[client] + * for license request signing under the license server protocol for CENC. * * The key used for signing should be the mac_key[client] that was generated - * for this session or loaded for this session by the most recent successful - * call to any one of + * for this session or loaded for this session by OEMCrypto_LoadKeys, + * OEMCrypto_LoadLicense, or OEMCrypto_LoadUsageEntry. * - * - OEMCrypto_GenerateDerivedKeys, - * - OEMCrypto_DeriveKeysFromSessionKey, - * - OEMCrypto_LoadKeys, or - * - OEMCrypto_LoadUsageEntry. * Refer to the Signing Messages Sent to a Server section above for more * details. * @@ -854,18 +979,20 @@ OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session, * entry may be different. In this case, the mac keys specified in the usage * entry should be used. * - * NOTE: if signature pointer is null and/or input signature_length set to - * zero, this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * NOTE: if signature pointer is null and/or input signature_length is zero, + * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output * signature_length to the size needed to receive the output signature. * * Parameters: - * [in] session: crypto session identifier. - * [in] message: pointer to memory containing message to be signed. - * [in] message_length: length of the message, in bytes. - * [out] signature: pointer to memory to received the computed signature. May - * be null (see note above). + * [in/out] message: Pointer to memory for the entire message. Modified by + * OEMCrypto via the ODK library. + * [in] message_length: length of the entire message buffer. + * [in/out] core_message_size: length of the core message at the beginning of + * the message. (in) size of buffer reserved for the core message, in + * bytes. (out) actual length of the core message, in bytes. + * [out] signature: pointer to memory to receive the computed signature. * [in/out] signature_length: (in) length of the signature buffer, in bytes. - * (out) actual length of the signature, in bytes. + * (out) actual length of the signature, in bytes. * * Returns: * OEMCrypto_SUCCESS success @@ -879,7 +1006,8 @@ OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session, * OEMCrypto_ERROR_SYSTEM_INVALIDATED * * Buffer Sizes: - * OEMCrypto shall support message sizes of at least 8 KiB. + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier. * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is * larger than the supported size. * @@ -891,13 +1019,80 @@ OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session, * session, and a read lock on the OEMCrypto system. * * Version: - * This method changed in API version 12. + * This method changed in API version 16. */ -OEMCryptoResult OEMCrypto_GenerateSignature(OEMCrypto_SESSION session, - const uint8_t* message, - size_t message_length, - uint8_t* signature, - size_t* signature_length); +OEMCryptoResult OEMCrypto_PrepAndSignRenewalRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, size_t* signature_length); + +/* + * OEMCrypto_PrepAndSignProvisioningRequest + * + * Description: + * OEMCrypto will use ODK_PrepareCoreRenewalRequest, as described in the + * document "Widevine Core Message Serialization", to prepare the core + * message. If it returns an error, the error should be returned by OEMCrypto + * to the CDM layer. If it returns OEMCrypto_SUCCESS, then OEMCrypto shall + * sign compute the signature of the entire message. The entire message is + * the buffer starting at message with length message_length. + * + * For a device that has a keybox, i.e. Provisioning 2.0, OEMCrypto will sign + * the response with the session's derived client mac key from the previous + * call to OEMCrypto_GenerateDerivedKeys. + * + * For a device that has an OEM Certificate, i.e. Provisioning 3.0, OEMCrypto + * will sign the response with the private key associated with the OEM + * Certificate. The key shall have been loaded by a previous call to + * OEMCrypto_LoadDRMPrivateKey. + * + * Refer to the Signing Messages Sent to a Server section above for more + * details. + * + * NOTE: if signature pointer is null and/or input signature_length is zero, + * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * signature_length to the size needed to receive the output signature. + * + * Parameters: + * [in/out] message: Pointer to memory for the entire message. Modified by + * OEMCrypto via the ODK library. + * [in] message_length: length of the entire message buffer. + * [in/out] core_message_size: length of the core message at the beginning of + * the message. (in) size of buffer reserved for the core message, in + * bytes. (out) actual length of the core message, in bytes. + * [out] signature: pointer to memory to receive the computed signature. + * [in/out] signature_length: (in) length of the signature buffer, in bytes. + * (out) actual length of the signature, in bytes. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_SHORT_BUFFER if signature buffer is not large enough to + * hold the signature. + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * Buffer Sizes: + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier. + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * Threading: + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * Version: + * This method changed in API version 16. + */ +OEMCryptoResult OEMCrypto_PrepAndSignProvisioningRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, size_t* signature_length); /* * OEMCrypto_LoadSRM @@ -942,7 +1137,9 @@ OEMCryptoResult OEMCrypto_LoadSRM(const uint8_t* buffer, size_t buffer_length); * OEMCrypto_LoadKeys * * Description: - * Installs a set of keys for performing decryption in the current session. + * Install a set of keys for performing decryption in the current session. + * This function will be deprecated and will only be used for legacy license + * from a license server that does not yet support the v16 interface. * * The relevant fields have been extracted from the License Response protocol * message, but the entire message and associated signature are provided so @@ -962,22 +1159,19 @@ OEMCryptoResult OEMCrypto_LoadSRM(const uint8_t* buffer, size_t buffer_length); * The new mac_keys replaces the current mac_keys for future calls to * OEMCrypto_RefreshKeys(). The first 256 bits of the mac_keys become the * mac_key[server] and the following 256 bits of the mac_keys become the - * mac_key[client]. If enc_mac_keys is null, then there will not be a call to - * OEMCrypto_RefreshKeys for this session and the current mac_keys should - * remain unchanged. + * mac_key[client]. * * The mac_key and encrypt_key were generated and stored by the previous call * to OEMCrypto_GenerateDerivedKeys() or * OEMCrypto_DeriveKeysFromSessionKey(). The nonce was generated and stored - * by the previous call to OEMCrypto_GenerateNonce(). + * in the session's nonce_values by the previous call to + * OEMCrypto_GenerateNonce(). * * This session's elapsed time clock is started at 0. The clock will be used * in OEMCrypto_DecryptCENC(). * * NOTE: The calling software must have previously established the mac_keys - * and encrypt_key with a call to OEMCrypto_GenerateDerivedKeys(), - * OEMCrypto_DeriveKeysFromSessionKey(), or a previous call to - * OEMCrypto_LoadKeys(). + * and encrypt_key with a call to OEMCrypto_DeriveKeysFromSessionKey(). * * Refer to the Verification of Messages from a Server section above for more * details. @@ -1001,16 +1195,20 @@ OEMCryptoResult OEMCrypto_LoadSRM(const uint8_t* buffer, size_t buffer_length); * current interval, next interval) times 4 content keys (audio, SD, HD, UHD) * plus up to 8 keys for watermarks. * + * After a call to OEMCrypto_LoadKeys, oemcrypto should clear the encrypt_key + * for the session. + * * Verification: * The following checks should be performed. If any check fails, an error is * returned, and none of the keys are loaded. - * * 1. The signature of the message shall be computed, and the API shall * verify the computed signature matches the signature passed in. If * not, return OEMCrypto_ERROR_SIGNATURE_FAILURE. The signature * verification shall use a constant-time algorithm (a signature * mismatch will always take the same time as a successful comparison). - * 2. The enc_mac_keys substring must either have zero length, or satisfy + * 2. If there already is a license loaded into this session, return + * OEMCrypto_ERROR_LICENSE_RELOAD. + * 3. The enc_mac_keys substring must either have zero length, or satisfy * the range check. I.e. (offset < message_length) && (offset + length * < message_length) && (offset < offset+length),and offset+length does * not cause an integer overflow. If it does not have zero length, then @@ -1018,48 +1216,40 @@ OEMCryptoResult OEMCrypto_LoadSRM(const uint8_t* buffer, size_t buffer_length); * range check. If not, return OEMCrypto_ERROR_INVALID_CONTEXT. If the * length is zero, then OEMCrypto may assume that the offset is also * zero. - * 3. The API shall verify that each substring in each KeyObject points to + * 4. The API shall verify that each substring in each KeyObject points to * a location in the message. I.e. (offset < message_length) && * (offset + length < message_length) && (offset < offset+length) and * offset+length does not cause an integer overflow, for each of key_id, * key_data_iv, key_data, key_control_iv, key_control. If not, return * OEMCrypto_ERROR_INVALID_CONTEXT. - * 4. Each key's control block, after decryption, shall have a valid + * 5. Each key's control block, after decryption, shall have a valid * verification field. If not, return OEMCrypto_ERROR_INVALID_CONTEXT. - * 5. If any key control block has the Nonce_Enabled bit set, that key's + * 6. If any key control block has the Nonce_Enabled bit set, that key's * Nonce field shall match a nonce in the cache. If not, return * OEMCrypto_ERROR_INVALID_NONCE. If there is a match, remove that * nonce from the cache. Note that all the key control blocks in a * particular call shall have the same nonce value. - * 6. If any key control block has the Require_AntiRollback_Hardware bit + * 7. If any key control block has the Require_AntiRollback_Hardware bit * set, and the device does not protect the usage table from rollback, * then do not load the keys and return OEMCrypto_ERROR_UNKNOWN_FAILURE. - * 7. If the key control block has a nonzero Replay_Control, then the + * 8. If the key control block has a nonzero Replay_Control, then the * verification described below is also performed. - * 8. If the key control block has the bit SRMVersionRequired is set, then + * 9. If the key control block has the bit SRMVersionRequired is set, then * the verification described below is also performed. If the SRM * requirement is not met, then the key control block's HDCP_Version * will be changed to 0xF - local display only. - * 9. If num_keys == 0, then return OEMCrypto_ERROR_INVALID_CONTEXT. - * 10. If any key control block has the Shared_License bit set, and this - * call to LoadKeys is not replacing keys loaded from a previous call to - * LoadKeys, then the keys are not loaded, and the error - * OEMCrypto_ERROR_MISSING_MASTER is returned. This feature is obsolete, - * and no longer used by production license servers. OEMCrypto unit - * tests for this feature have been removed. + * 10. If key_array_length == 0, then return + * OEMCrypto_ERROR_INVALID_CONTEXT. * 11. If this session is associated with a usage table entry, and that * entry is marked as "inactive" (either kInactiveUsed or * kInactiveUnused), then the keys are not loaded, and the error * OEMCrypto_ERROR_LICENSE_INACTIVE is returned. * 12. The data in enc_mac_keys_iv is not identical to the 16 bytes before * enc_mac_keys. If it is, return OEMCrypto_ERROR_INVALID_CONTEXT. - * * Usage Table and Provider Session Token (pst) - * * If a key control block has a nonzero value for Replay_Control, then all * keys in this license will have the same value for Replay_Control. In this * case, the following additional checks are performed. - * * - The substring pst must have nonzero length and must satisfy the range * check described above. If not, return * OEMCrypto_ERROR_INVALID_CONTEXT. @@ -1109,6 +1299,11 @@ OEMCryptoResult OEMCrypto_LoadSRM(const uint8_t* buffer, size_t buffer_length); * Devices that do not support the Usage Table will return * OEMCrypto_ERROR_INVALID_CONTEXT if the Replay_Control is nonzero. * + * Timer Update + * After verification, the session's clock and timer values are updated by + * calling the function ODK_InitializeV15Values as described in the document + * "Widevine Core Message Serialization". + * * SRM Restriction Data * * If any key control block has the flag SRMVersionRequired set, then the @@ -1138,10 +1333,10 @@ OEMCryptoResult OEMCrypto_LoadSRM(const uint8_t* buffer, size_t buffer_length); * [in] message_length: length of the message, in bytes. * [in] signature: pointer to memory containing the signature. * [in] signature_length: length of the signature, in bytes. - * [in] enc_mac_key_iv: IV for decrypting new mac_key. Size is 128 bits. + * [in] enc_mac_keys_iv: IV for decrypting new mac_key. Size is 128 bits. * [in] enc_mac_keys: encrypted mac_keys for generating new mac_keys. Size is * 512 bits. - * [in] num_keys: number of keys present. + * [in] key_array_length: number of keys present. * [in] key_array: set of keys to be installed. * [in] pst: the Provider Session Token. * [in] srm_restriction_data: optional data specifying the minimum SRM @@ -1162,9 +1357,11 @@ OEMCryptoResult OEMCrypto_LoadSRM(const uint8_t* buffer, size_t buffer_length); * OEMCrypto_ERROR_BUFFER_TOO_LARGE * OEMCrypto_ERROR_SESSION_LOST_STATE * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * OEMCrypto_ERROR_LICENSE_RELOAD * * Buffer Sizes: - * OEMCrypto shall support message sizes of at least 8 KiB. + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier. * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is * larger than the supported size. * @@ -1176,16 +1373,270 @@ OEMCryptoResult OEMCrypto_LoadSRM(const uint8_t* buffer, size_t buffer_length); * session, and a read lock on the OEMCrypto system. * * Version: - * This method changed in API version 14. + * This method changed in API version 16. */ OEMCryptoResult OEMCrypto_LoadKeys( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, const uint8_t* signature, size_t signature_length, OEMCrypto_Substring enc_mac_keys_iv, OEMCrypto_Substring enc_mac_keys, - size_t num_keys, const OEMCrypto_KeyObject* key_array, + size_t key_array_length, const OEMCrypto_KeyObject* key_array, OEMCrypto_Substring pst, OEMCrypto_Substring srm_restriction_data, OEMCrypto_LicenseType license_type); +/* + * OEMCrypto_LoadLicense + * + * Description: + * Install a set of keys for performing decryption in the current session. + * + * First, OEMCrypto shall verify the signature of the message using + * HMAC-SHA256 with the derived mac_key[server]. The signature verification + * shall use a constant-time algorithm (a signature mismatch will always take + * the same time as a successful comparison). The signature is over the + * entire message buffer starting at message with length message_length. If + * the signature verification fails, ignore all other arguments and return + * OEMCrypto_ERROR_SIGNATURE_FAILURE. Otherwise, add the keys to the session + * context. + * + * NOTE: The calling software must have previously established the mac_keys + * and encrypt_key with a call to OEMCrypto_DeriveKeysFromSessionKey(). + * + * Refer to the Verification of Messages from a Server section above for more + * details. + * + * The function ODK_ParseLicense is called to parse the message. If it + * returns an error, OEMCrypto shall return that error to the CDM layer. The + * function ODK_ParseLicense is described in the document "Widevine Core + * Message Serialization". + * + * Below, all fields are found in the struct ODK_ParsedLicense parsed_license + * returned by ODK_ParseLicense. + * + * The keys will be decrypted using the current encrypt_key (AES-128-CBC) and + * the IV given in the KeyObject. Each key control block will be decrypted + * using the first 128 bits of the corresponding content key (AES-128-CBC) + * and the IV given in the KeyObject. + * + * If its length is not zero, enc_mac_keys will be used to create new + * mac_keys. After all keys have been decrypted and validated, the new + * mac_keys are decrypted with the current encrypt_key and the offered IV. + * The new mac_keys replaces the current mac_keys for future signing renewal + * requests and loading renewal responses. The first 256 bits of the mac_keys + * become the mac_key[server] and the following 256 bits of the mac_keys + * become the mac_key[client]. If enc_mac_keys is null, then there will not + * be a call to OEMCrypto_LoadRenewal for this session and the current + * mac_keys may be deleted. + * + * If the field license_type is OEMCrypto_ContentLicense, then the fields + * key_id and key_data in an OEMCrypto_KeyObject are loaded in to the + * content_key_id and content_key_data fields of the key table entry. In this + * case, entitlement key ids and entitlement key data is left blank. + * + * If the field license_type is OEMCrypto_EntitlementLicense, then the + * fields key_id and key_data in an OEMCrypto_KeyObject are loaded in to the + * entitlement_key_id and entitlement_key_data fields of the key table entry. + * In this case, content key ids and content key data will be loaded later + * with a call to OEMCrypto_LoadEntitledContentKeys(). + * + * OEMCrypto may assume that the key_id_length is at most 16. However, + * OEMCrypto shall correctly handle key id lengths from 1 to 16 bytes. + * + * OEMCrypto shall handle multiple keys, as described in the section on + * Resource Rating Tiers in this document. + * + * After a call to OEMCrypto_LoadLicense, oemcrypto should clear the + * encrypt_key for the session. + * + * Verification: + * The following checks should be performed. If any check fails, an error is + * returned, and none of the keys are loaded. + * 13. The signature of the message shall be computed, and the API shall + * verify the computed signature matches the signature passed in. If + * not, return OEMCrypto_ERROR_SIGNATURE_FAILURE. The signature + * verification shall use a constant-time algorithm (a signature + * mismatch will always take the same time as a successful comparison). + * 14. If there already is a license loaded into this session, return + * OEMCrypto_ERROR_LICENSE_RELOAD. + * 15. The enc_mac_keys substring must either have zero length, or satisfy + * the range check. I.e. (offset < message_length) && (offset + length + * < message_length) && (offset < offset+length),and offset+length does + * not cause an integer overflow. If it does not have zero length, then + * enc_mac_keys_iv must not have zero length, and must also satisfy the + * range check. If not, return OEMCrypto_ERROR_INVALID_CONTEXT. If the + * length is zero, then OEMCrypto may assume that the offset is also + * zero. + * 16. The API shall verify that each substring in each KeyObject points to + * a location in the message. I.e. (offset < message_length) && + * (offset + length < message_length) && (offset < offset+length) and + * offset+length does not cause an integer overflow, for each of key_id, + * key_data_iv, key_data, key_control_iv, key_control. If not, return + * OEMCrypto_ERROR_INVALID_CONTEXT. + * 17. Each key's control block, after decryption, shall have a valid + * verification field. If not, return OEMCrypto_ERROR_INVALID_CONTEXT. + * 18. If any key control block has the Nonce_Enabled bit set, that key's + * Nonce field shall match a nonce in the cache. If not, return + * OEMCrypto_ERROR_INVALID_NONCE. If there is a match, remove that + * nonce from the cache. Note that all the key control blocks in a + * particular call shall have the same nonce value. + * 19. If any key control block has the Require_AntiRollback_Hardware bit + * set, and the device does not protect the usage table from rollback, + * then do not load the keys and return OEMCrypto_ERROR_UNKNOWN_FAILURE. + * 20. If the key control block has a nonzero Replay_Control, then the + * verification described below is also performed. + * 21. If the key control block has the bit SRMVersionRequired is set, then + * the verification described below is also performed. If the SRM + * requirement is not met, then the key control block's HDCP_Version + * will be changed to 0xF - local display only. + * 22. If key_array_length == 0, then return + * OEMCrypto_ERROR_INVALID_CONTEXT. + * 23. If this session is associated with a usage table entry, and that + * entry is marked as "inactive" (either kInactiveUsed or + * kInactiveUnused), then the keys are not loaded, and the error + * OEMCrypto_ERROR_LICENSE_INACTIVE is returned. + * 24. The data in enc_mac_keys_iv is not identical to the 16 bytes before + * enc_mac_keys. If it is, return OEMCrypto_ERROR_INVALID_CONTEXT. + * + * Usage Table and Provider Session Token (pst) + * The function ODK_ParseLicense takes several parameters that may need more + * explanation. + * The parameter usage_entry_present shall be set to true if a usage entry + * was created or loaded for this session. This parameter is used by + * ODK_ParseLicense for usage entry verification. + * The parameter initial_license_load shall be false if the usage entry was + * loaded. If there is no usage entry or if the usage entry was created with + * OEMCrypto_CreateNewUsageEntry, then initial_license_load shall be true. + * If a usage entry is present, then it shall be verified after the call to + * ODK_ParseLicense. + * If initial_license_load is true: + * 1. OEMCrypto shall copy the PST from the parsed license to the usage + * entry. + * 2. OEMCrypto shall verify that the server and client mac keys were + * updated by the license. The server and client mac keys shall be + * copied to the usage entry. + * If initial_license_load is false: + * 1. OEMCrypto shall verify the PST from the parsed license matches that + * in the usage entry. If not, then an error OEMCrypto_ERROR_WRONG_PST + * is returned. + * 2. OEMCrypto shall verify that the server and client mac keys were + * updated by the license. OEMCrypto shall verify that the server and + * client mac keys match those in the usage entry. If not the error + * OEMCrypto_ERROR_WRONG_KEYS is returned. + * If a key control block has a nonzero value for Replay_Control, then all + * keys in this license will have the same value for Replay_Control. In this + * case, the following additional checks are performed. + * - The substring pst must have nonzero length and must satisfy the range + * check described above. If not, return + * OEMCrypto_ERROR_INVALID_CONTEXT. + * - The session must be associated with a usage table entry, either + * created via OEMCrypto_CreateNewUsageEntry or loaded via + * OEMCrypto_LoadUsageEntry. + * - If Replay_Control is 1 = Nonce_Required, then OEMCrypto will perform a + * nonce check as described above. OEMCrypto will verify that the + * usage entry is newly created with OEMCrypto_CreateNewUsageEntry. If + * an existing entry was reloaded, an error + * OEMCrypto_ERROR_INVALID_CONTEXT is returned and no keys are loaded. + * OEMCrypto will then copy the pst and the mac keys to the usage entry, + * and set the status to Unused. This Replay_Control prevents the + * license from being loaded more than once, and will be used for online + * streaming. + * - If Replay_Control is 2 = "Require existing Session Usage table entry + * or Nonce", then OEMCrypto will behave slightly differently on the + * first call to LoadKeys for this license. + * * If the usage entry was created with OEMCrypto_CreateNewUsageEntry + * for this session, then OEMCrypto will verify the nonce for each + * key. OEMCrypto will copy the pst and mac keys to the usage + * entry. The license received time of the entry will be updated + * to the current time, and the status will be set to Unused. + * * If the usage entry was loaded with OEMCrypto_LoadUsageEntry for + * this session, then OEMCrypto will NOT verify the nonce for each + * key. Instead, it will verify that the pst passed in matches + * that in the entry. Also, the entry's mac keys will be verified + * against the current session's mac keys. This allows an offline + * license to be reloaded but maintain continuity of the playback + * times from one session to the next. + * * If the nonce is not valid and a usage entry was not loaded, the + * return error is OEMCrypto_ERROR_INVALID_NONCE. + * * If the loaded usage entry has a pst that does not match, + * OEMCrypto returns the error OEMCrypto_ERROR_WRONG_PST. + * * If the loaded usage entry has mac keys that do not match the + * license, OEMCrypto returns the error OEMCrypto_ERROR_WRONG_KEYS. + * Note: If LoadKeys updates the mac keys, then the new updated mac keys will + * be used with the Usage Entry -- i.e. the new keys are stored in the + * usage table when creating a new entry, or the new keys are verified + * against those in the usage table if there is an existing entry. If + * LoadKeys does not update the mac keys, the existing session mac keys are + * used. + * Sessions that are associated with an entry will need to be able to update + * and verify the status of the entry, and the time stamps in the entry. + * Devices that do not support the Usage Table will return + * OEMCrypto_ERROR_INVALID_CONTEXT if the Replay_Control is nonzero. + * SRM Restriction Data + * If any key control block has the flag SRMVersionRequired set, then the + * following verification is also performed. + * 4. The substring srm_restriction_data must have nonzero length and must + * satisfy the range check described above. If not, return + * OEMCrypto_ERROR_INVALID_CONTEXT. + * 5. The first 8 bytes of srm_restriction_data must match the string + * "HDCPDATA". If not, return OEMCrypto_ERROR_INVALID_CONTEXT. + * 6. The next 4 bytes of srm_restriction_data will be converted from + * network byte order. If the current SRM installed on the device has a + * version number less than this, then the SRM requirement is not met. + * If the device does not support SRM files, or OEMCrypto cannot + * determine the current SRM version number, then the SRM requirement is + * not met. + * Note: if the current SRM version requirement is not met, LoadKeys will + * still succeed and the keys will be loaded. However, those keys with the + * SRMVersionRequired bit set will have their HDCP_Version increased to 0xF - + * local display only. Any future call to SelectKey for these keys while + * there is an external display will return OEMCrypto_ERROR_INSUFFICIENT_HDCP + * at that time. + * + * Parameters: + * [in] session: crypto session identifier. + * [in] message: pointer to memory containing data. + * [in] message_length: length of the message, in bytes. + * [in] core_message_length: length of the core submessage, in bytes. + * [in] signature: pointer to memory containing the signature. + * [in] signature_length: length of the signature, in bytes. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_SIGNATURE_FAILURE + * OEMCrypto_ERROR_INVALID_NONCE + * OEMCrypto_ERROR_TOO_MANY_KEYS + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * OEMCrypto_ERROR_LICENSE_RELOAD + * + * Buffer Sizes: + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier. + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * Threading: + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * Version: + * This method changed in API version 16. + */ +OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); + /* * OEMCrypto_LoadEntitledContentKeys * @@ -1222,7 +1673,9 @@ OEMCryptoResult OEMCrypto_LoadKeys( * * Parameters: * [in] session: handle for the session to be used. - * [in] num_keys: number of keys present. + * [in] message: pointer to memory containing message to be verified. + * [in] message_length: length of the message, in bytes. + * [in] key_array_length: number of keys present. * [in] key_array: set of key updates. * * Returns: @@ -1247,59 +1700,138 @@ OEMCryptoResult OEMCrypto_LoadKeys( */ OEMCryptoResult OEMCrypto_LoadEntitledContentKeys( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, - size_t num_keys, const OEMCrypto_EntitledContentKeyObject* key_array); + size_t key_array_length, + const OEMCrypto_EntitledContentKeyObject* key_array); /* * OEMCrypto_RefreshKeys * * Description: - * Updates an existing set of keys for continuing decryption in the current - * session. + * Updates the license clock values to allow playback to continue. This + * function is being deprecated and is only used for version v15 licenses -- + * i.e. offline license saved before an update or licenses from a server that + * has not update to the v16 license server SDK. * - * The relevant fields have been extracted from the Renewal Response protocol - * message, but the entire message and associated signature are provided so - * the message can be verified (using HMAC-SHA256 with the current - * mac_key[server]). If any verification step fails, an error is returned. - * Otherwise, the key table in trusted memory is updated using the - * key_control block. When updating an entry in the table, only the duration, - * nonce, and nonce_enabled fields are used. All other key control bits are - * not modified. + * OEMCrypto shall compute the signature of the message using + * mac_key[server], and shall verify the computed signature matches the + * signature passed in. If not, return OEMCrypto_ERROR_SIGNATURE_FAILURE. The + * signature verification shall use a constant-time algorithm (a signature + * mismatch will always take the same time as a successful comparison). * - * NOTE: OEMCrypto_LoadKeys() must be called first to load the keys into the - * session. - * - * This session's elapsed time clock is reset to 0 when this function is - * called. The elapsed time clock is used in OEMCrypto_DecryptCENC() and the - * other Decryption API functions to determine if the key has expired. - * - * This function does not add keys to the key table. It is only used to - * update a key control block license duration. This function is used to - * update the duration of a key, only. It is not used to update key control - * bits. + * The key control from the first OEMCrypto_KeyRefreshObject in the key_array + * shall be extracted. If it is encrypted, as described below, it shall be + * decrypted. The duration from the key control shall be extracted and + * converted to host byte order. This duration shall be passed to the + * function ODK_RefreshV15Values as the parameter new_key_duration. * * If the KeyRefreshObject's key_control_iv has zero length, then the * key_control is not encrypted. If the key_control_iv is specified, then * key_control is encrypted with the first 128 bits of the corresponding * content key. * - * If the KeyRefreshObject's key_id has zero length, then this refresh object - * should be used to update the duration of all keys for the current session. - * In this case, key_control_iv will also have zero length and the control - * block will not be encrypted. + * If the KeyRefreshObject's key_id has zero length, then it is an error for + * the key_control_iv to have nonzero length. OEMCrypto shall return an error + * of OEMCrypto_ERROR_INVALID_CONTEXT. * * If the session's license_type is OEMCrypto_ContentLicense, and the * KeyRefreshObject's key_id is not null, then the entry in the keytable with - * the matching content_key_id is updated. + * the matching content_key_id is used. * * If the session's license_type is OEMCrypto_EntitlementLicense, and the * KeyRefreshObject's key_id is not null, then the entry in the keytable with - * the matching entitlment_key_id is updated. + * the matching entitlment_key_id is used. * - * If the key_id is not null, and no matching entry is found in the key - * table, then return OEMCrypto_ERROR_NO_CONTENT_KEY. + * The function ODK_RefreshV15Values shall be called to update the clock + * values. See the document "Widevine Core Message Serialization" for the + * documentation of the ODK library functions. * - * Aside from the key's duration, no other values in the key control block - * should be updated by this function. + * If ODK_RefreshV15Values returns + * + * - ODK_SET_TIMER: Success. The timer should be reset to the specified + * timer value. + * - ODK_DISABLE_TIMER: Success, but disable timer. Unlimited playback is + * allowed. + * - ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. + * - ODK_STALE_RENEWAL: This renewal is not the most recently signed. It is + * rejected. Return this error + * - Any other error - OEMCrypto shall pass any other error up to the + * caller of OEMCrypto_RefreshKeys. + * + * NOTE: OEMCrypto_LoadKeys() must be called first to load the keys into the + * session. + * + * Parameters: + * [in] session: handle for the session to be used. + * [in] message: pointer to memory containing message to be verified. + * [in] message_length: length of the message, in bytes. + * [in] signature: pointer to memory containing the signature. + * [in] signature_length: length of the signature, in bytes. + * [in] num_keys: number of keys present. + * [in] key_array: set of key updates. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_SIGNATURE_FAILURE + * OEMCrypto_ERROR_INVALID_NONCE + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * OEMCrypto_ERROR_BUFFER_TOO_LARGE + * OEMCrypto_ERROR_NO_CONTENT_KEY + * OEMCrypto_ERROR_SESSION_LOST_STATE + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * Buffer Sizes: + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier. + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * Threading: + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * Version: + * This method changed in API version 16. + */ +OEMCryptoResult OEMCrypto_RefreshKeys( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length, size_t num_keys, + const OEMCrypto_KeyRefreshObject* key_array); + +/* + * OEMCrypto_LoadRenewal + * + * Description: + * Updates the clock values and resets the renewal timer for the current + * session. + * + * OEMCrypto shall verify the signature of the entire message using the + * session's renewal mac key for the server. The entire message is the buffer + * starting at message with length message_length. If the signature does not + * match, OEMCrypto returns OEMCrypto_ERROR_SIGNATURE_FAILURE. + * + * OEMCrypto shall verify that nonce_values.api_major_version is 16. If not, + * return the error OEMCrypto_ERROR_INVALID_CONTEXT. Legacy licenses will use + * the function OEMCrypto_RefreshKeys instead of OEMCrypto_LoadRenewal. + * + * If the signature passes, OEMCrypto shall use the function + * ODK_ParseRenewal, as described in the document "Widevine Core Message + * Serialization" to parse and verify the message. If ODK_ParseRenewal + * returns an error OEMCrypto returns the error to the CDM layer. + * + * The function ODK_ParseRenewal updates the clock values for the session, + * and may return ODK_SET_TIMER, ODK_DISABLE_TIMER or ODK_TIMER_EXPIRED on + * success. These values shall be handled by OEMCrypto, as discussed in the + * document "License Duration and Renewal". + * + * NOTE: OEMCrypto_LoadLicense() must be called first to load the keys into + * the session. * * Verification: * The following checks should be performed. If any check fails, an error is @@ -1327,10 +1859,9 @@ OEMCryptoResult OEMCrypto_LoadEntitledContentKeys( * [in] session: handle for the session to be used. * [in] message: pointer to memory containing message to be verified. * [in] message_length: length of the message, in bytes. + * [in] core_message_length: length of the core submessage, in bytes. * [in] signature: pointer to memory containing the signature. * [in] signature_length: length of the signature, in bytes. - * [in] num_keys: number of keys present. - * [in] key_array: set of key updates. * * Returns: * OEMCrypto_SUCCESS success @@ -1345,9 +1876,11 @@ OEMCryptoResult OEMCrypto_LoadEntitledContentKeys( * OEMCrypto_ERROR_NO_CONTENT_KEY * OEMCrypto_ERROR_SESSION_LOST_STATE * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * ODK_STALE_RENEWAL * * Buffer Sizes: - * OEMCrypto shall support message sizes of at least 8 KiB. + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier. * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is * larger than the supported size. * @@ -1361,10 +1894,12 @@ OEMCryptoResult OEMCrypto_LoadEntitledContentKeys( * Version: * This method changed in API version 12. */ -OEMCryptoResult OEMCrypto_RefreshKeys( - OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, - const uint8_t* signature, size_t signature_length, size_t num_keys, - const OEMCrypto_KeyRefreshObject* key_array); +OEMCryptoResult OEMCrypto_LoadRenewal(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); /* * OEMCrypto_QueryKeyControl @@ -1395,6 +1930,7 @@ OEMCryptoResult OEMCrypto_RefreshKeys( * OEMCrypto_ERROR_NO_CONTENT_KEY. * * Parameters: + * [in] session: handle for the session to be used. * [in] content_key_id: The unique id of the key of interest. * [in] content_key_id_length: The length of key_id, in bytes. From 1 to 16, * inclusive. @@ -1432,7 +1968,8 @@ OEMCryptoResult OEMCrypto_QueryKeyControl(OEMCrypto_SESSION session, * Select a content key and install it in the hardware key ladder for * subsequent decryption operations (OEMCrypto_DecryptCENC()) for this * session. The specified key must have been previously "installed" via - * OEMCrypto_LoadKeys() or OEMCrypto_RefreshKeys(). + * OEMCrypto_LoadKeys(), OEMCrypto_LoadLicense, or + * OEMCrypto_LoadEntitledContentKeys(). * * A key control block is associated with the key and the session, and is * used to configure the session context. The Key Control data is documented @@ -1442,7 +1979,7 @@ OEMCryptoResult OEMCrypto_QueryKeyControl(OEMCrypto_SESSION session, * includes the key value, and the key control block. * * Step 2: Latch the content key into the hardware key ladder. Set permission - * flags and timers based on the key's control block. + * flags based on the key's control block. * * Step 3: use the latched content key to decrypt (AES-128-CTR or * AES-128-CBC) buffers passed in via OEMCrypto_DecryptCENC(). If the key is @@ -1457,20 +1994,18 @@ OEMCryptoResult OEMCrypto_QueryKeyControl(OEMCrypto_SESSION session, * 1. If the key id is not found in the keytable for this session, then the * key state is not changed and OEMCrypto shall return * OEMCrypto_ERROR_NO_CONTENT_KEY. - * 2. If the current key's control block has a nonzero Duration field, then - * the API shall verify that the duration is greater than the session's - * elapsed time clock before the key is used. OEMCrypto may return - * OEMCrypto_ERROR_KEY_EXPIRED from OEMCrypto_SelectKey, or SelectKey - * may return success from select key and the decrypt or generic crypto - * call will return OEMCrypto_ERROR_KEY_EXPIRED. - * 3. If the key control block has the bit Disable_Analog_Output set, then + * 2. If the key control block has the bit Disable_Analog_Output set, then * the device should disable analog video output. If the device has * analog video output that cannot be disabled, then the key is not - * selected, and OEMCrypto_ERROR_ANALOG_OUTPUT is returned. - * 4. If the key control block has HDCP required, and the device cannot + * selected, and OEMCrypto_ERROR_ANALOG_OUTPUT is returned. This step is + * optional -- SelectKey may return OEMCrypto_SUCCESS and delay the + * error until a call to OEMCrypto_DecryptCENC. + * 3. If the key control block has HDCP required, and the device cannot * enforce HDCP, then the key is not selected, and - * OEMCrypto_ERROR_INSUFFICIENT_HDCP is returned. - * 5. If the key control block has a nonzero value for HDCP_Version, and + * OEMCrypto_ERROR_INSUFFICIENT_HDCP is returned. This step is optional + * -- SelectKey may return OEMCrypto_SUCCESS and delay the error until a + * call to OEMCrypto_DecryptCENC. + * 4. If the key control block has a nonzero value for HDCP_Version, and * the device cannot enforce at least that version of HDCP, then the key * is not selected, and OEMCrypto_ERROR_INSUFFICIENT_HDCP is returned. * @@ -1485,7 +2020,7 @@ OEMCryptoResult OEMCrypto_QueryKeyControl(OEMCrypto_SESSION session, * * Returns: * OEMCrypto_SUCCESS success - * OEMCrypto_ERROR_KEY_EXPIRED - if the key's timer has expired + * OEMCrypto_ERROR_KEY_EXPIRED - if the session's timer has expired * OEMCrypto_ERROR_INVALID_SESSION crypto session ID invalid or not open * OEMCrypto_ERROR_NO_DEVICE_KEY failed to decrypt device key * OEMCrypto_ERROR_NO_CONTENT_KEY failed to decrypt content key @@ -1508,7 +2043,7 @@ OEMCryptoResult OEMCrypto_QueryKeyControl(OEMCrypto_SESSION session, * session, and a read lock on the OEMCrypto system. * * Version: - * This method changed in API version 14. + * This method changed in API version 16. */ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, const uint8_t* content_key_id, @@ -1519,152 +2054,343 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, * OEMCrypto_DecryptCENC * * Description: - * Decrypts or copies the payload in the buffer referenced by the *data_addr - * parameter into the buffer referenced by the out_buffer parameter, using - * the session context indicated by the session parameter. Decryption mode is - * AES-128-CTR or AES-128-CBC depending on the value of cipher_mode passed in - * to OEMCrypto_SelectKey. If is_encrypted is true, the content key - * associated with the session is latched in the active hardware key ladder - * and is used for the decryption operation. If is_encrypted is false, the - * data is simply copied. + * Decrypts or copies a series of input payloads into output buffers using + * the session context indicated by the session parameter. The input payload + * is delivered in the form of samples. The samples are subdivided into + * subsamples. "Samples" and "subsamples" are defined as in the ISO Common + * Encryption standard (ISO/IEC 23001-7:2016). The samples parameter contains + * a list of samples, each of which has its own input and output buffers. + * Each sample contains a buffers field that contains the input and output + * buffers in its input_data and output fields, respectively. * - * After decryption, the data_length bytes are copied to the location - * described by out_buffer. This could be one of + * Each sample contains an array of subsample descriptions in its subsamples + * field. Each subsample is defined as a number of clear bytes followed by a + * number of encrypted bytes. Subsamples are consecutive inside the sample; + * the clear bytes of the second subsample begin immediately after the + * encrypted bytes of the first subsample. This follows the definition in the + * ISO-CENC standard. + * + * Decryption mode is AES-128-CTR or AES-128-CBC depending on the value of + * cipher_mode previously passed in to OEMCrypto_SelectKey. For the encrypted + * portion of subsamples, the content key associated with the session is + * latched in the active hardware key ladder and is used for the decryption + * operation. For the clear portion of subsamples, the data is simply copied. + * + * After decryption, all the input_data bytes are copied to the location + * described by the output field. The output field is an + * OEMCrypto_DestBufferDesc, which could be one of: + * + * 1. The structure OEMCrypto_DestBufferDesc contains a pointer to a clear + * text buffer. The OEMCrypto library shall verify that key control + * allows data to be returned in clear text. If it is not authorized, + * this method should return an error. + * 2. The structure OEMCrypto_DestBufferDesc contains a handle to a secure + * buffer. + * 3. The structure OEMCrypto_DestBufferDesc indicates that the data should + * be sent directly to the decoder and renderer. + * Depending on your platform's needs, you may not need to support all three + * of these options. + * + * SINGLE-SAMPLE DECRYPTION AND SINGLE-SUBSAMPLE DECRYPTION: + * + * If the OEMCrypto implementation is not able to handle the amount of + * samples and subsamples passed into it, it should return + * OEMCrypto_ERROR_BUFFER_TOO_LARGE, in which case the CDM can respond by + * breaking the samples up into smaller pieces and trying to decrypt each of + * them individually. It is possible that the CDM will break the samples + * array up into pieces that are still too large, in which case OEMCrypto may + * return OEMCrypto_ERROR_BUFFER_TOO_LARGE again. + * + * If the OEMCrypto implementation cannot handle multiple samples at once, it + * may return OEMCrypto_ERROR_BUFFER_TOO_LARGE any time it receives more than + * one sample in a single call to OEMCrypto_DecryptCENC. + * + * Similarly, if the OEMCrypto implementation cannot handle multiple + * subsamples at once, it may return OEMCrypto_ERROR_BUFFER_TOO_LARGE any + * time it receives more than one subsample in a single call to + * OEMCrypto_DecryptCENC. + * + * The exact way that the CDM code breaks up the samples array is not + * guaranteed by this specification. The CDM may break down the array of + * samples into many arrays each containing one sample. The CDM may break + * down samples into subsamples and pass individual subsamples into + * OEMCrypto, just like in OEMCrypto v15. The CDM may break down individual + * subsamples into smaller subsamples, just like in OEMCrypto v15. + * + * If OEMCrypto requests that the CDM break samples into subsamples, the + * "samples" passed into OEMCrypto_DecryptCENC will no longer be full + * samples. When a full sample is passed into OEMCrypto_DecryptCENC, the + * first subsample in the subsample array will have the + * OEMCrypto_FirstSubsample flag set in its subsample_flags field and the + * last subsample array will have the OEMCrypto_LastSubsample flag set in its + * subsample_flags field. If this is not the case, OEMCrypto will need to + * accumulate more subsamples from successive calls to OEMCrypto_DecryptCENC + * to receive the full sample. + * + * The first subsample in the sample will always have + * OEMCrypto_FirstSubsample set and the last subsample will always have the + * OEMCrypto_LastSubsample flag set, even if those subsamples are passed in + * separate calls to OEMCrypto_DecryptCENC. This is the same as in OEMCrypto + * v15. The decrypted data will not be used until after the subsample with + * the flag OEMCrypto_LastSubsample has been sent to OEMCrypto. This can be + * relied on by OEMCrypto for optimization by not doing decrypt until the + * last subsample has been received. However, a device that can do decrypt of + * more than one subsample at a time will always have better performance if + * it can receive those subsamples in one OEMCrypto_Decrypt call rather than + * as individual subsamples. + * + * Although the exact way that the CDM code breaks up the samples array when + * it receives OEMCrypto_ERROR_BUFFER_TOO_LARGE is not guaranteed by this + * specification, here is a sample way it might work: + * + * 1. It tries to pass the array of samples to OEMCrypto_DecryptCENC. + * 2. If OEMCrypto returns OEMCrypto_ERROR_BUFFER_TOO_LARGE, it tries to + * pass each sample individually into OEMCrypto_DecryptCENC. + * 3. If OEMCrypto returns OEMCrypto_ERROR_BUFFER_TOO_LARGE, it tries to + * pass the clear and encrypted parts of each subsample individually + * into OEMCrypto_DecryptCENC. At this point, (and in the subsequent + * steps) it is replicating the behavior of OEMCrypto v15 and lower. + * 4. If OEMCrypto returns OEMCrypto_ERROR_BUFFER_TOO_LARGE, it breaks each + * piece of a subsample into smaller pieces, down to the minimum + * subsample size required by the device's resource rating tier. It + * passes these pieces into OEMCrypto_DecryptCENC. + * 5. If OEMCrypto returns OEMCrypto_ERROR_BUFFER_TOO_LARGE, the device has + * failed to meet its resource rating tier requirements. It returns an + * error. + * Because this process requires a lot of back-and-forth between the CDM and + * OEMCrypto, partners are strongly recommended to support decrypting full + * samples or even multiple samples in their OEMCrypto implementation. + * + * ISO-CENC SCHEMES: + * + * The ISO Common Encryption standard (ISO/IEC 23001-7:2016) defines four + * "schemes" that may be used to encrypt content: 'cenc', 'cens', 'cbc1', and + * 'cbcs'. Starting with v16, OEMCrypto only supports 'cenc' and 'cbcs'. The + * schemes 'cens' and 'cbc1' are not supported. + * + * The decryption mode, either OEMCrypto_CipherMode_CTR or + * OEMCrypto_CipherMode_CBC, was already specified in the call to + * OEMCrypto_SelectKey. The encryption pattern is specified by the fields in + * the parameter pattern. A description of partial encryption patterns for + * 'cbcs' can be found in the ISO-CENC standard, section 10.4. + * + * 'cenc' SCHEME: + * + * The 'cenc' scheme is OEMCrypto_CipherMode_CTR without an encryption + * pattern. All the bytes in the encrypted portion of each subsample are + * encrypted. In the pattern parameter, both the encrypt and skip fields will + * be zero. + * + * The length of a crypto block in AES-128 is 16 bytes. In the 'cenc' scheme, + * if an encrypted subsample has a length that is not a multiple of 16 bytes, + * then all the bytes of the encrypted subsample must be decrypted, but the + * next encrypted subsample will begin by completing the incomplete crypto + * block from the previous encrypted subsample. The following diagram + * provides an example: + * + * (See drawing in "Widevine Modular DRM Security Integration Guide") + * + * To help with this, the block_offset field of each subsample will contain + * the number of bytes the initial crypto block of that subsample should be + * offset by. In the example above, the block_offset for the first subsample + * would be 0 and the block_offset for the second subsample would be 12. + * 'cenc' is the only mode that allows for a nonzero block_offset. This field + * satisfies 0 <= block_offset < 16. + * + * 'cbcs' SCHEME: + * + * The 'cbcs' scheme is OEMCrypto_CipherMode_CBC with an encryption pattern. + * Only some of the bytes in the encrypted portion of each subsample are + * encrypted. In the pattern parameter, the encrypt and skip fields will + * usually be non-zero. This mode allows devices to decrypt FMP4 HLS content, + * SAMPLE-AES HLS content, as well as content using the DASH 'cbcs' scheme. + * + * The skip field of OEMCrypto_CENCEncryptPatternDesc may also be zero. If + * the skip field is zero, then patterns are not in use and all crypto blocks + * in the encrypted part of the subsample are encrypted. It is not valid for + * the encrypt field to be zero. + * + * The length of a crypto block in AES-128 is 16 bytes. In the 'cbcs' scheme, + * if the encrypted part of a subsample has a length that is not a multiple + * of 16 bytes, then the final bytes that do not make up a full crypto block + * are clear and should never be decrypted. The following diagram provides an + * example: + * + * (See drawing in "Widevine Modular DRM Security Integration Guide") + * + * Whether any given protected block is actually encrypted also depends on + * the pattern. But the bytes at the end that do not make up a full crypto + * block will never be encrypted, regardless of what the pattern is. Even if + * the pattern says to decrypt every protected block, these bytes are clear + * and should not be decrypted. + * + * Of course, if the encrypted subsample has a length that is a multiple of + * 16 bytes, all the bytes in it are protected, and they may need to be + * decrypted following the pattern. The following diagram provides an example: + * + * (See drawing in "Widevine Modular DRM Security Integration Guide") + * + * INITIALIZATION VECTOR BETWEEN SUBSAMPLES: + * + * The IV is specified for the initial subsample in a sample in the iv field + * of the OEMCrypto_SampleDescription. OEMCrypto is responsible for correctly + * updating the IV for subsequent subsamples according to the ISO Common + * Encryption standard (ISO/IEC 23001-7:2016). Section 9.5.2.3 covers 'cenc' + * and section 9.5.2.5 covers 'cbcs'. A summary of the ISO-CENC behavior + * follows: + * + * For 'cenc', the IV at the end of each subsample carries forward to the + * next subsample and becomes the IV at the beginning of the next subsample. + * If the subsample ends on a crypto block boundary, then the IV should be + * incremented as normal at the end of the crypto block. If the subsample + * ends in the middle of a crypto block, the same IV should continue to be + * used until the crypto block is completed in the next subsample. Only + * increment the IV after the partial crypto block is completed. + * + * For 'cbcs', the IV is reset at the beginning of each subsample. Each + * subsample should start with the IV that was passed into + * OEMCrypto_DecryptCENC. + * + * To phrase it another way: In 'cenc', the encrypted portions of the + * subsamples can be concatenated to form one continuous ciphertext. In + * 'cbcs', each encrypted portion of a subsample is a separate ciphertext. + * Each separate ciphertext begins with the IV specified in the iv field of + * the OEMCrypto_SampleDescription. + * + * INITIALIZATION VECTOR WITHIN SUBSAMPLES: + * + * Once it has the IV for each subsample, OEMCrypto is responsible for + * correctly updating the IV for each crypto block of each encrypted + * subsample portion, as outlined in the ISO Common Encryption standard + * (ISO/IEC 23001-7:2016). Section 9.5.1 includes general information about + * IVs in subsample decryption. A summary of the ISO-CENC behavior follows: + * + * For 'cenc', the subsample's IV is the counter value to be used for the + * initial encrypted block of the subsample. The IV length is the AES block + * size. For subsequent encrypted AES blocks, OEMCrypto must calculate the IV + * by incrementing the lower 64 bits (byte 8-15) of the IV value used for the + * previous block. The counter rolls over to zero when it reaches its maximum + * value (0xFFFFFFFFFFFFFFFF). The upper 64 bits (byte 0-7) of the IV do not + * change. + * + * For 'cbcs', the subsample's IV is the initialization vector for the + * initial encrypted block of the subsample. Within each subsample, each + * crypto block is used as the IV for the next crypto block, as prescribed by + * AES-CBC. * - * 1. The structure out_buffer contains a pointer to a clear text buffer. - * The OEMCrypto library shall verify that key control allows data to be - * returned in clear text. If it is not authorized, this method should - * return an error. - * 2. The structure out_buffer contains a handle to a secure buffer. - * 3. The structure out_buffer indicates that the data should be sent - * directly to the decoder and renderer. * NOTES: * - * For CTR mode, IV points to the counter value to be used for the initial - * encrypted block of the input buffer. The IV length is the AES block size. - * For subsequent encrypted AES blocks the IV is calculated by incrementing - * the lower 64 bits (byte 8-15) of the IV value used for the previous block. - * The counter rolls over to zero when it reaches its maximum value - * (0xFFFFFFFFFFFFFFFF). The upper 64 bits (byte 0-7) of the IV do not change. - * - * For CBC mode, IV points to the initial vector for cipher block chaining. - * Within each subsample, OEMCrypto is responsible for updating the IV as - * prescribed by CBC mode. The calling layer above is responsible for - * updating the IV from one subsample to the next if needed. - * - * This method may be called several times before the decrypted data is used. - * For this reason, the parameter subsample_flags may be used to optimize - * decryption. The first buffer in a chunk of data will have the - * OEMCrypto_FirstSubsample bit set in subsample_flags. The last buffer in a - * chunk of data will have the OEMCrypto_LastSubsample bit set in - * subsample_flags. The decrypted data will not be used until after - * OEMCrypto_LastSubsample has been set. If an implementation decrypts data - * immediately, it may ignore subsample_flags. - * * If the destination buffer is secure, an offset may be specified. - * DecryptCENC begins storing data out_buffer->secure.offset bytes after the - * beginning of the secure buffer. + * OEMCrypto_DecryptCENC begins storing data buffers.output.secure.offset + * bytes after the beginning of the secure buffer. * - * If the session has an entry in the Usage Table, then OEMCrypto will update + * If the session has an entry in the Usage Table, then OEMCrypto must update * the time_of_last_decrypt. If the status of the entry is "unused", then * change the status to "active" and set the time_of_first_decrypt. * - * The decryption mode, either OEMCrypto_CipherMode_CTR or - * OEMCrypto_CipherMode_CBC, was specified in the call to - * OEMCrypto_SelectKey. The encryption pattern is specified by the fields in - * the parameter pattern. A description of partial encryption patterns can be - * found in the document Draft International Standard ISO/IEC DIS 23001-7. - * Search for the codes "cenc", "cbc1", "cens" or "cbcs". + * OEMCrypto cannot assume that the buffers of consecutive samples are + * consecutive in memory. * - * The most common mode is "cenc", which is OEMCrypto_CipherMode_CTR without - * a pattern. The entire subsample is either encrypted or clear, depending on - * the flag is_encrypted. In the structure pattern, both encrypt and skip - * will be 0. This is the only mode that allows for a nonzero block_offset. + * A subsample may consist entirely of encrypted bytes or clear bytes. In + * this case, the clear or the encrypted part of the subsample will be zero, + * indicating that no bytes of that kind appear in the subsample. * - * A less common mode is "cens", which is OEMCrypto_CipherMode_CTR with an - * encryption pattern. For this mode, OEMCrypto may assume that an encrypted - * subsample will have a length that is a multiple of 16, the AES block - * length. + * The ISO-CENC spec implicitly limits both the skip and encrypt values to be + * 4 bits, so they are at most 15. * - * The mode "cbc1" is OEMCrypto_CipherMode_CBC without a pattern. In the - * structure pattern, both encrypt and skip will be 0. If an encrypted - * subsample has a length that is not a multiple of 16, the final partial - * block will be in the clear. + * (See drawing in "Widevine Modular DRM Security Integration Guide") * - * The mode "cbcs" is OEMCrypto_CipherMode_CBC with an encryption pattern. - * This mode allows devices to decrypt HLS content. If an encrypted subsample - * has a length that is not a multiple of 16, the final partial block will be - * in the clear. In practice, the most common pattern is (1, 9), or 1 - * encrypted block followed by 9 clear blocks. The ISO-CENC spec implicitly - * limits both the skip and encrypt values to be 4 bits, so a value of at - * most 15. + * If OEMCrypto assembles all of the encrypted subsample portions into a + * single buffer and then decrypts it in one pass, it can assume that the + * block offset is 0. * - * A sample may be broken up into a mix of clear and encrypted subsamples. In - * order to support the VP9 standard, the breakup of a subsample into clear - * and encrypted subsamples is not always in pairs. - * - * If OEMCrypto assembles all of the subsamples into a single buffer and then - * decrypts, it can assume that the block offset is 0. + * (See drawing in "Widevine Modular DRM Security Integration Guide") * * Verification: - * The following checks should be performed if is_encrypted is true. If any - * check fails, an error is returned, and no decryption is performed. - * 1. If the current key's control block has a nonzero Duration field, then - * the API shall verify that the duration is greater than the session's - * elapsed time clock. If not, return OEMCrypto_ERROR_KEY_EXPIRED. - * 2. If the current key's control block has the Data_Path_Type bit set, + * The total size of all the subsamples cannot exceed the total size of the + * input buffer. OEMCrypto integrations should validate this and return + * OEMCrypto_ERROR_UNKNOWN_FAILURE if the subsamples are larger than the + * input buffer. No decryption should be performed in this case. + * If the subsamples all contain only clear bytes, then no further + * verification is performed. This call shall copy clear data even when there + * are no keys loaded, or there is no selected key. + * If this is the first use of a key for this session, then OEMCrypto shall + * call ODK_AttemptFirstPlayback to update the session's clock values and + * verify playback is allowed. If this is not the first use of a key for this + * session, then OEMCrypto shall call ODK_UpdateLastPlaybackTime. See the + * document "License Duration and Renewal" for handling the return value of + * these ODK functions. + * The following checks should be performed if any subsamples contain any + * encrypted bytes. If any check fails, an error is returned, and no + * decryption is performed. + * 1. If the current key's control block has the Data_Path_Type bit set, * then the API shall verify that the output buffer is secure or direct. * If not, return OEMCrypto_ERROR_DECRYPT_FAILED. - * 3. If the current key control block has the bit Disable_Analog_Output + * 2. If the current key control block has the bit Disable_Analog_Output * set, then the device should disable analog video output. If the * device has analog video output that cannot be disabled, then - * OEMCrypto_ERROR_ANALOG_OUTPUT is returned. - * 4. If the current key's control block has the HDCP bit set, then the API + * OEMCrypto_ERROR_ANALOG_OUTPUT is returned. (See note on delayed + * error conditions below) + * 3. If the current key's control block has the HDCP bit set, then the API * shall verify that the buffer will be displayed locally, or output * externally using HDCP only. If not, return - * OEMCrypto_ERROR_INSUFFICIENT_HDCP. - * 5. If the current key's control block has a nonzero value for + * OEMCrypto_ERROR_INSUFFICIENT_HDCP. (See note on delayed error + * conditions below) + * 4. If the current key's control block has a nonzero value for * HDCP_Version, then the current version of HDCP for the device and the * display combined will be compared against the version specified in * the control block. If the current version is not at least as high as - * that in the control block, then return - * OEMCrypto_ERROR_INSUFFICIENT_HDCP. - * 6. If the current session has an entry in the Usage Table, and the + * that in the control block, and the device is not able to restrict + * displays with HDCP levels lower than what's in the control block, + * return OEMCrypto_ERROR_INSUFFICIENT_HDCP. If the device is able to + * restrict those displays, return + * OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION. (See note on delayed + * error conditions below) + * 5. If the current session has an entry in the Usage Table, and the * status of that entry is either kInactiveUsed or kInactiveUnused, then * return the error OEMCrypto_ERROR_LICENSE_INACTIVE. - * 7. If a Decrypt Hash has been initialized via OEMCrypto_SetDecryptHash, + * 6. If a Decrypt Hash has been initialized via OEMCrypto_SetDecryptHash, * and the current key's control block does not have the * Allow_Hash_Verification bit set, then do not compute a hash and * return OEMCrypto_ERROR_UNKNOWN_FAILURE. - * If the flag is_encrypted is false, then no verification is performed. This - * call shall copy clear data even when there are no keys loaded, or there is - * no selected key. + * + * Delayed Error Conditions + * + * On some devices, the HDCP subsystem is not directly connected to the + * OEMCrypto TA. This means that returning the error + * OEMCrypto_ERROR_INSUFFICIENT_HDCP at the time of the decrypt call is a + * performance hit. However, some devices have the ability to tag output + * buffers with security requirements, such as the required HDCP level. + * For those devices, when a call to OEMCrypto_DecryptCENC is made using a + * key that requires HDCP output, and if the HDCP level on the output does + * not meet the required level. + * - OEMCrypto may tag the output buffer as requiring HDCP at the required + * level and return OEMCrypto_SUCCESS. + * - Output shall not be sent to the display. + * - On the second or third call to OEMCrypto_DecryptCENC with the same + * key, OEMCrypto shall return OEMCrypto_ERROR_INSUFFICIENT_HDCP. + * For those devices, when a call to OEMCrypto_DecryptCENC is made using a + * key that requires HDCP output, and if the HDCP level on some of the + * displays does not meet the required level. + * - OEMCrypto may tag the output buffer as requiring HDCP at the required + * level and return OEMCrypto_SUCCESS. + * - Output shall only be sent to the display with sufficient output + * control, e.g. the local display. + * - On the second or third call to OEMCrypto_DecryptCENC with the same + * key, OEMCrypto shall return OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION. + * In either case, a call to OEMCrypto_GetHDCPCapability shall return the + * current HDCP level. * * Parameters: - * [in] session: crypto session identifier. - * [in] data_addr: An unaligned pointer to this segment of the stream. - * [in] data_length: The length of this segment of the stream, in bytes. - * [in] is_encrypted: True if the buffer described by data_addr, data_length - * is encrypted. If is_encrypted is false, only the data_addr and - * data_length parameters are used. The iv and offset arguments are - * ignored. - * [in] iv: The initial value block to be used for content decryption. - * This is discussed further below. - * [in] block_offset: If non-zero, the decryption block boundary is different - * from the start of the data. block_offset should be subtracted from - * data_addr to compute the starting address of the first decrypted - * block. The bytes between the decryption block start address and - * data_addr are discarded after decryption. It does not adjust the - * beginning of the source or destination data. This parameter - * satisfies 0 <= block_offset < 16. - * [in] out_buffer: A caller-owned descriptor that specifies the handling of - * the decrypted byte stream. See OEMCrypto_DestbufferDesc for details. + * [in] session: Crypto session identifier. The crypto session in which + * decrypt is to be performed. + * [in] samples: A caller-owned array of OEMCrypto_SampleDescription + * structures. Each entry in this array contains one sample of the + * content. + * [in] samples_length: The length of the array pointed to by the samples + * parameter. * [in] pattern: A caller-owned structure indicating the encrypt/skip pattern - * as specified in the CENC standard. - * [in] subsample_flags: bitwise flags indicating if this is the first, - * middle, or last subsample in a chunk of data. 1 = first subsample, 2 - * = last subsample, 3 = both first and last subsample, 0 = neither - * first nor last subsample. + * as specified in the ISO-CENC standard. * * Returns: * OEMCrypto_SUCCESS @@ -1683,14 +2409,14 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, * OEMCrypto_ERROR_SYSTEM_INVALIDATED * * Buffer Sizes: - * OEMCrypto shall support subsample sizes (i.e. data_length) of at least 100 - * KiB. + * OEMCrypto shall support subsample sizes and total input buffer sizes as + * specified by its resource rating tier. * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is * larger than the supported size. If OEMCrypto returns - * OEMCrypto_ERROR_BUFFER_TOO_LARGE, the calling function must break the - * buffer into smaller chunks. For high performance devices, OEMCrypto should - * handle larger buffers. We encourage OEMCrypto implementers not to - * artificially restrict the maximum buffer size. + * OEMCrypto_ERROR_BUFFER_TOO_LARGE, the CDM will break the buffer into + * smaller chunks. For high performance devices, OEMCrypto should handle + * larger buffers. We encourage OEMCrypto implementers not to artificially + * restrict the maximum buffer size. * If OEMCrypto detects that the output data is too large, and breaking the * buffer into smaller subsamples will not work, then it returns * OEMCrypto_ERROR_OUTPUT_TOO_LARGE. This error will bubble up to the @@ -1705,22 +2431,21 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session, * session, and a read lock on the OEMCrypto system. * * Version: - * This method changed in API version 15. This method changed its name in API + * This method changed in API version 16. This method changed its name in API * version 11. */ OEMCryptoResult OEMCrypto_DecryptCENC( - OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, - bool is_encrypted, const uint8_t* iv, - size_t block_offset, // used for CTR "cenc" mode only. - OEMCrypto_DestBufferDesc* out_buffer, - const OEMCrypto_CENCEncryptPatternDesc* pattern, uint8_t subsample_flags); + OEMCrypto_SESSION session, + const OEMCrypto_SampleDescription* samples, // an array of samples. + size_t samples_length, // the number of samples. + const OEMCrypto_CENCEncryptPatternDesc* pattern); /* * OEMCrypto_CopyBuffer * * Description: - * Copies the payload in the buffer referenced by the *data_addr parameter - * into the buffer referenced by the out_buffer parameter. The data is simply + * Copies the payload in the buffer referenced by the *data parameter into + * the buffer referenced by the out_buffer parameter. The data is simply * copied. The definition of OEMCrypto_DestBufferDesc and subsample_flags are * the same as in OEMCrypto_DecryptCENC, above. * @@ -1748,13 +2473,13 @@ OEMCryptoResult OEMCrypto_DecryptCENC( * * Verification: * The following checks should be performed. - * 1. If either data_addr or out_buffer is null, return + * 1. If either data or out_buffer is null, return * OEMCrypto_ERROR_INVALID_CONTEXT. * * Parameters: * [in] session: crypto session identifier. * [in] data_addr: An unaligned pointer to the buffer to be copied. - * [in] data_length: The length of the buffer, in bytes. + * [in] data_addr_length: The length of the buffer, in bytes. * [in] out_buffer: A caller-owned descriptor that specifies the handling of * the byte stream. See OEMCrypto_DestbufferDesc for details. * [in] subsample_flags: bitwise flags indicating if this is the first, @@ -1773,7 +2498,8 @@ OEMCryptoResult OEMCrypto_DecryptCENC( * OEMCrypto_ERROR_SYSTEM_INVALIDATED * * Buffer Sizes: - * OEMCrypto shall support subsample sizes (i.e. data_length) up to 100 KiB. + * OEMCrypto shall support subsample sizes and total input buffer sizes as + * specified by its resource rating tier. * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is * larger than the supported size. If OEMCrypto returns * OEMCrypto_ERROR_BUFFER_TOO_LARGE, the calling function must break the @@ -1796,11 +2522,11 @@ OEMCryptoResult OEMCrypto_DecryptCENC( * Version: * This method is changed in API version 15. */ -OEMCryptoResult OEMCrypto_CopyBuffer(OEMCrypto_SESSION session, - const uint8_t* data_addr, - size_t data_length, - OEMCrypto_DestBufferDesc* out_buffer, - uint8_t subsample_flags); +OEMCryptoResult OEMCrypto_CopyBuffer( + OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* data_addr, + size_t data_addr_length, + const OEMCrypto_DestBufferDesc* out_buffer_descriptor, + uint8_t subsample_flags); /* * OEMCrypto_Generic_Encrypt @@ -1812,16 +2538,19 @@ OEMCryptoResult OEMCrypto_CopyBuffer(OEMCrypto_SESSION session, * the time_of_last_decrypt. If the status of the entry is "unused", then * change the status to "active" and set the time_of_first_decrypt. * - * OEMCrypto should be able to handle buffers at least 100 KiB long. + * OEMCrypto shall be able to handle buffers at least 100 KiB long. * * Verification: * The following checks should be performed. If any check fails, an error is * returned, and the data is not encrypted. * 1. The control bit for the current key shall have the Allow_Encrypt set. * If not, return OEMCrypto_ERROR_UNKNOWN_FAILURE. - * 2. If the current key's control block has a nonzero Duration field, then - * the API shall verify that the duration is greater than the session's - * elapsed time clock. If not, return OEMCrypto_ERROR_KEY_EXPIRED. + * 2. If this is the first use of a key for this session, then OEMCrypto + * shall call ODK_AttemptFirstPlayback to update the session's clock + * values and verify playback is allowed. If this is not the first use + * of a key for this session, then OEMCrypto shall call + * ODK_UpdateLastPlaybackTime. See the document "License Duration and + * Renewal" for handling the return value of these ODK functions. * 3. If the current session has an entry in the Usage Table, and the * status of that entry is either kInactiveUsed or kInactiveUnused, then * return the error OEMCrypto_ERROR_LICENSE_INACTIVE. @@ -1829,8 +2558,8 @@ OEMCryptoResult OEMCrypto_CopyBuffer(OEMCrypto_SESSION session, * Parameters: * [in] session: crypto session identifier. * [in] in_buffer: pointer to memory containing data to be encrypted. - * [in] buffer_length: length of the buffer, in bytes. The algorithm may - * restrict buffer_length to be a multiple of block size. + * [in] in_buffer_length: length of the buffer, in bytes. The algorithm may + * restrict in_buffer_length to be a multiple of block size. * [in] iv: IV for encrypting data. Size is 128 bits. * [in] algorithm: Specifies which encryption algorithm to use. Currently, * only CBC 128 mode is allowed for encryption. @@ -1847,6 +2576,7 @@ OEMCryptoResult OEMCrypto_CopyBuffer(OEMCrypto_SESSION session, * OEMCrypto_ERROR_BUFFER_TOO_LARGE * OEMCrypto_ERROR_SESSION_LOST_STATE * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * OEMCrypto_ERROR_NOT_IMPLEMENTED * * Buffer Sizes: * OEMCrypto shall support buffers sizes of at least 100 KiB for generic @@ -1862,11 +2592,12 @@ OEMCryptoResult OEMCrypto_CopyBuffer(OEMCrypto_SESSION session, * session, and a read lock on the OEMCrypto system. * * Version: - * This method changed in API version 12. + * This method changed in API version 16. */ OEMCryptoResult OEMCrypto_Generic_Encrypt( - OEMCrypto_SESSION session, const uint8_t* in_buffer, size_t buffer_length, - const uint8_t* iv, OEMCrypto_Algorithm algorithm, uint8_t* out_buffer); + OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* in_buffer, + size_t in_buffer_length, const uint8_t* iv, OEMCrypto_Algorithm algorithm, + OEMCrypto_SharedMemory* out_buffer); /* * OEMCrypto_Generic_Decrypt @@ -1887,9 +2618,12 @@ OEMCryptoResult OEMCrypto_Generic_Encrypt( * If not, return OEMCrypto_ERROR_DECRYPT_FAILED. * 2. If the current key's control block has the Data_Path_Type bit set, * then return OEMCrypto_ERROR_DECRYPT_FAILED. - * 3. If the current key's control block has a nonzero Duration field, then - * the API shall verify that the duration is greater than the session's - * elapsed time clock. If not, return OEMCrypto_ERROR_KEY_EXPIRED. + * 3. If this is the first use of a key for this session, then OEMCrypto + * shall call ODK_AttemptFirstPlayback to update the session's clock + * values and verify playback is allowed. If this is not the first use + * of a key for this session, then OEMCrypto shall call + * ODK_UpdateLastPlaybackTime. See the document "License Duration and + * Renewal" for handling the return value of these ODK functions. * 4. If the current session has an entry in the Usage Table, and the * status of that entry is either kInactiveUsed or kInactiveUnused, then * return the error OEMCrypto_ERROR_LICENSE_INACTIVE. @@ -1897,8 +2631,8 @@ OEMCryptoResult OEMCrypto_Generic_Encrypt( * Parameters: * [in] session: crypto session identifier. * [in] in_buffer: pointer to memory containing data to be encrypted. - * [in] buffer_length: length of the buffer, in bytes. The algorithm may - * restrict buffer_length to be a multiple of block size. + * [in] in_buffer_length: length of the buffer, in bytes. The algorithm may + * restrict in_buffer_length to be a multiple of block size. * [in] iv: IV for encrypting data. Size is 128 bits. * [in] algorithm: Specifies which encryption algorithm to use. Currently, * only CBC 128 mode is allowed for decryption. @@ -1916,6 +2650,7 @@ OEMCryptoResult OEMCrypto_Generic_Encrypt( * OEMCrypto_ERROR_BUFFER_TOO_LARGE * OEMCrypto_ERROR_SESSION_LOST_STATE * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * OEMCrypto_ERROR_NOT_IMPLEMENTED * * Buffer Sizes: * OEMCrypto shall support buffers sizes of at least 100 KiB for generic @@ -1931,11 +2666,12 @@ OEMCryptoResult OEMCrypto_Generic_Encrypt( * session, and a read lock on the OEMCrypto system. * * Version: - * This method changed in API version 12. + * This method changed in API version 16. */ OEMCryptoResult OEMCrypto_Generic_Decrypt( - OEMCrypto_SESSION session, const uint8_t* in_buffer, size_t buffer_length, - const uint8_t* iv, OEMCrypto_Algorithm algorithm, uint8_t* out_buffer); + OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* in_buffer, + size_t in_buffer_length, const uint8_t* iv, OEMCrypto_Algorithm algorithm, + OEMCrypto_SharedMemory* out_buffer); /* * OEMCrypto_Generic_Sign @@ -1951,16 +2687,19 @@ OEMCryptoResult OEMCrypto_Generic_Decrypt( * The following checks should be performed. If any check fails, an error is * returned, and the data is not signed. * 1. The control bit for the current key shall have the Allow_Sign set. - * 2. If the current key's control block has a nonzero Duration field, then - * the API shall verify that the duration is greater than the session's - * elapsed time clock. If not, return OEMCrypto_ERROR_KEY_EXPIRED. + * 2. If this is the first use of a key for this session, then OEMCrypto + * shall call ODK_AttemptFirstPlayback to update the session's clock + * values and verify playback is allowed. If this is not the first use + * of a key for this session, then OEMCrypto shall call + * ODK_UpdateLastPlaybackTime. See the document "License Duration and + * Renewal" for handling the return value of these ODK functions. * 3. If the current session has an entry in the Usage Table, and the * status of that entry is either kInactiveUsed or kInactiveUnused, then * return the error OEMCrypto_ERROR_LICENSE_INACTIVE. * * Parameters: * [in] session: crypto session identifier. - * [in] in_buffer: pointer to memory containing data to be encrypted. + * [in] buffer: pointer to memory containing data to be encrypted. * [in] buffer_length: length of the buffer, in bytes. * [in] algorithm: Specifies which algorithm to use. * [out] signature: pointer to buffer in which signature should be stored. @@ -1980,6 +2719,7 @@ OEMCryptoResult OEMCrypto_Generic_Decrypt( * OEMCrypto_ERROR_BUFFER_TOO_LARGE * OEMCrypto_ERROR_SESSION_LOST_STATE * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * OEMCrypto_ERROR_NOT_IMPLEMENTED * * Buffer Sizes: * OEMCrypto shall support buffers sizes of at least 100 KiB for generic @@ -1995,13 +2735,13 @@ OEMCryptoResult OEMCrypto_Generic_Decrypt( * session, and a read lock on the OEMCrypto system. * * Version: - * This method changed in API version 14. + * This method changed in API version 16. */ OEMCryptoResult OEMCrypto_Generic_Sign(OEMCrypto_SESSION session, - const uint8_t* in_buffer, + const OEMCrypto_SharedMemory* buffer, size_t buffer_length, OEMCrypto_Algorithm algorithm, - uint8_t* signature, + OEMCrypto_SharedMemory* signature, size_t* signature_length); /* @@ -2025,16 +2765,19 @@ OEMCryptoResult OEMCrypto_Generic_Sign(OEMCrypto_SESSION session, * 3. The signature verification shall use a constant-time algorithm (a * signature mismatch will always take the same time as a successful * comparison). - * 4. If the current key's control block has a nonzero Duration field, then - * the API shall verify that the duration is greater than the session's - * elapsed time clock. If not, return OEMCrypto_ERROR_KEY_EXPIRED. + * 4. If this is the first use of a key for this session, then OEMCrypto + * shall call ODK_AttemptFirstPlayback to update the session's clock + * values and verify playback is allowed. If this is not the first use + * of a key for this session, then OEMCrypto shall call + * ODK_UpdateLastPlaybackTime. See the document "License Duration and + * Renewal" for handling the return value of these ODK functions. * 5. If the current session has an entry in the Usage Table, and the * status of that entry is either kInactiveUsed or kInactiveUnused, then * return the error OEMCrypto_ERROR_LICENSE_INACTIVE. * * Parameters: * [in] session: crypto session identifier. - * [in] in_buffer: pointer to memory containing data to be encrypted. + * [in] buffer: pointer to memory containing data to be encrypted. * [in] buffer_length: length of the buffer, in bytes. * [in] algorithm: Specifies which algorithm to use. * [in] signature: pointer to buffer in which signature resides. @@ -2051,6 +2794,7 @@ OEMCryptoResult OEMCrypto_Generic_Sign(OEMCrypto_SESSION session, * OEMCrypto_ERROR_BUFFER_TOO_LARGE * OEMCrypto_ERROR_SESSION_LOST_STATE * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * OEMCrypto_ERROR_NOT_IMPLEMENTED * * Buffer Sizes: * OEMCrypto shall support buffers sizes of at least 100 KiB for generic @@ -2066,14 +2810,12 @@ OEMCryptoResult OEMCrypto_Generic_Sign(OEMCrypto_SESSION session, * session, and a read lock on the OEMCrypto system. * * Version: - * This method changed in API version 14. + * This method changed in API version 16. */ -OEMCryptoResult OEMCrypto_Generic_Verify(OEMCrypto_SESSION session, - const uint8_t* in_buffer, - size_t buffer_length, - OEMCrypto_Algorithm algorithm, - const uint8_t* signature, - size_t signature_length); +OEMCryptoResult OEMCrypto_Generic_Verify( + OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* buffer, + size_t buffer_length, OEMCrypto_Algorithm algorithm, + const OEMCrypto_SharedMemory* signature, size_t signature_length); /* * OEMCrypto_WrapKeyboxOrOEMCert @@ -2101,18 +2843,20 @@ OEMCryptoResult OEMCrypto_Generic_Verify(OEMCrypto_SESSION session, * file system. * * Parameters: - * [in] rot - pointer to root of trust data to encrypt -- this is either a - * keybox or an OEM Certificate private key. May be NULL on the first - * call to test size of wrapped keybox. The keybox may either be clear - * or previously encrypted. - * [in] rotLength - length the keybox data in bytes - * [out] wrappedRot – Pointer to wrapped keybox - * [out] wrappedRotLength – Pointer to the length of the wrapped rot in bytes - * [in] transportKey – Optional. AES transport key. If provided, the rot - * parameter was previously encrypted with this key. The keybox will be - * decrypted with the transport key using AES-CBC and a null IV. - * [in] transportKeyLength – Optional. Number of bytes in the transportKey, - * if used. + * [in] keybox_or_cert - pointer to root of trust data to encrypt -- this is + * either a keybox or an OEM Certificate private key. May be NULL on + * the first call to test size of wrapped keybox. The keybox may either + * be clear or previously encrypted. + * [in] keybox_or_cert_length - length the keybox or cert data in bytes + * [out] wrapped_keybox_or_cert – Pointer to wrapped keybox or cert + * [out] wrapped_keybox_or_cert_length – Pointer to the length of the wrapped + * keybox or certificate key in bytes + * [in] transport_key – Optional. AES transport key. If provided, the + * keybox_or_cert parameter was previously encrypted with this key. The + * keybox will be decrypted with the transport key using AES-CBC and a + * null IV. + * [in] transport_key_length – Optional. Number of bytes in the + * transport_key, if used. * * Returns: * OEMCrypto_SUCCESS success @@ -2131,12 +2875,10 @@ OEMCryptoResult OEMCrypto_Generic_Verify(OEMCrypto_SESSION session, * Version: * This method is supported in all API versions. */ -OEMCryptoResult OEMCrypto_WrapKeyboxOrOEMCert(const uint8_t* rot, - size_t rotLength, - uint8_t* wrappedRot, - size_t* wrappedRotLength, - const uint8_t* transportKey, - size_t transportKeyLength); +OEMCryptoResult OEMCrypto_WrapKeyboxOrOEMCert( + const uint8_t* keybox_or_cert, size_t keybox_or_cert_length, + uint8_t* wrapped_keybox_or_cert, size_t* wrapped_keybox_or_cert_length, + const uint8_t* transport_key, size_t transport_key_length); /* * OEMCrypto_InstallKeyboxOrOEMCert @@ -2153,8 +2895,8 @@ OEMCryptoResult OEMCrypto_WrapKeyboxOrOEMCert(const uint8_t* rot, * file system. * * Parameters: - * [in] rot - pointer to encrypted data as input - * [in] rotLength - length of the data in bytes + * [in] keybox_or_cert - pointer to encrypted data as input + * [in] keybox_or_cert_length - length of the data in bytes * * Returns: * OEMCrypto_SUCCESS success @@ -2172,8 +2914,8 @@ OEMCryptoResult OEMCrypto_WrapKeyboxOrOEMCert(const uint8_t* rot, * Version: * This method is supported in all API versions. */ -OEMCryptoResult OEMCrypto_InstallKeyboxOrOEMCert(const uint8_t* rot, - size_t rotLength); +OEMCryptoResult OEMCrypto_InstallKeyboxOrOEMCert(const uint8_t* keybox_or_cert, + size_t keybox_or_cert_length); /* * OEMCrypto_GetProvisioningMethod @@ -2259,21 +3001,18 @@ OEMCryptoResult OEMCrypto_IsKeyboxOrOEMCertValid(void); * Certificate device, it should set the device ID to a device-unique string, * such as the device serial number. The ID should be device-unique and it * should be stable -- i.e. it should not change across a device reboot or a - * system upgrade. - * - * This function is optional but recommended for Provisioning 3.0 in API v15. - * It may be required for a future version of this API. + * system upgrade. This shall match the device id found in the core + * provisioning request message. * * Parameters: - * [out] deviceId - pointer to the buffer that receives the Device ID - * [in/out] idLength – on input, size of the caller's device ID buffer. On - * output, the number of bytes written into the buffer. + * [out] device_id - pointer to the buffer that receives the Device ID + * [in/out] device_id_length – on input, size of the caller's device ID + * buffer. On output, the number of bytes written into the buffer. * * Returns: * OEMCrypto_SUCCESS success * OEMCrypto_ERROR_SHORT_BUFFER if the buffer is too small to return device ID * OEMCrypto_ERROR_NO_DEVICEID failed to return Device Id - * OEMCrypto_ERROR_NOT_IMPLEMENTED * OEMCrypto_ERROR_SYSTEM_INVALIDATED * * Threading: @@ -2285,7 +3024,8 @@ OEMCryptoResult OEMCrypto_IsKeyboxOrOEMCertValid(void); * Version: * This method is supported in all API versions. */ -OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, size_t* idLength); +OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* device_id, + size_t* device_id_length); /* * OEMCrypto_GetKeyData @@ -2316,7 +3056,8 @@ OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, size_t* idLength); * Version: * This method is supported in all API versions. */ -OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData, size_t* keyDataLength); +OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* key_data, + size_t* key_data_length); /* * OEMCrypto_LoadTestKeybox @@ -2334,7 +3075,7 @@ OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData, size_t* keyDataLength); * * Parameters: * [in] buffer: pointer to memory containing test keybox, in binary form. - * [in] length: length of the buffer, in bytes. + * [in] buffer_length: length of the buffer, in bytes. * * Returns: * OEMCrypto_SUCCESS success @@ -2353,22 +3094,49 @@ OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData, size_t* keyDataLength); * Version: * This method changed in API version 14. */ -OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t* buffer, size_t length); +OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t* buffer, + size_t buffer_length); + +/* + * OEMCrypto_LoadOEMPrivateKey + * + * Description: + * After a call to this function, all session functions using an RSA key + * should use the OEM certificate's private RSA key. See the section above + * discussing Provisioning 3.0. + * + * Parameters: + * - [in] session: this function affects the specified session only. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_NOT_IMPLEMENTED - this function is for Provisioning 3.0 + * only. + * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * Threading: + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * Version: + * This method is new API version 16. + */ +OEMCryptoResult OEMCrypto_LoadOEMPrivateKey(OEMCrypto_SESSION session); /* * OEMCrypto_GetOEMPublicCertificate * * Description: * This function should place the OEM public certificate in the buffer - * public_cert. After a call to this function, all methods using an RSA key - * should use the OEM certificate's private RSA key. See the section above - * discussing Provisioning 3.0. + * public_cert. See the section above discussing Provisioning 3.0. * * If the buffer is not large enough, OEMCrypto should update * public_cert_length and return OEMCrypto_ERROR_SHORT_BUFFER. * * Parameters: - * - [in] session: this function affects the specified session only. * - [out] public_cert: the buffer where the public certificate is stored. * - [in/out] public_cert_length: on input, this is the available size of * the buffer. On output, this is the number of bytes needed for the @@ -2382,17 +3150,15 @@ OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t* buffer, size_t length); * OEMCrypto_ERROR_SYSTEM_INVALIDATED * * Threading: - * This is a "Session Function" and may be called simultaneously with session - * functions for other sessions but not simultaneously with other functions - * for this session. It will not be called simultaneously with initialization - * or usage table functions. It is as if the CDM holds a write lock for this - * session, and a read lock on the OEMCrypto system. + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. * * Version: - * This method is new API version 12. + * This method is new API version 16. */ -OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(OEMCrypto_SESSION session, - uint8_t* public_cert, +OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(uint8_t* public_cert, size_t* public_cert_length); /* @@ -2404,8 +3170,8 @@ OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(OEMCrypto_SESSION session, * OEMCrypto_ERROR_RNG_NOT_SUPPORTED. * * Parameters: - * [out] randomData - pointer to the buffer that receives random data - * [in] dataLength - length of the random data buffer in bytes + * [out] random_data- pointer to the buffer that receives random data + * [in] random_data_length- length of the random data buffer in bytes * * Returns: * OEMCrypto_SUCCESS success @@ -2429,7 +3195,8 @@ OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(OEMCrypto_SESSION session, * Version: * This method is supported in all API versions. */ -OEMCryptoResult OEMCrypto_GetRandom(uint8_t* randomData, size_t dataLength); +OEMCryptoResult OEMCrypto_GetRandom(uint8_t* random_data, + size_t random_data_length); /* * OEMCrypto_APIVersion @@ -2445,7 +3212,7 @@ OEMCryptoResult OEMCrypto_GetRandom(uint8_t* randomData, size_t dataLength); * There is no plan to introduce forward-compatibility. Applications will * reject a library with a newer version of the API. * - * The version specified in this document is 15. Any OEM that returns this + * The version specified in this document is 16. Any OEM that returns this * version number guarantees it passes all unit tests associated with this * version. * @@ -2466,6 +3233,35 @@ OEMCryptoResult OEMCrypto_GetRandom(uint8_t* randomData, size_t dataLength); */ uint32_t OEMCrypto_APIVersion(void); +/* + * OEMCrypto_MinorAPIVersion + * + * Description: + * This function returns the current API minor version number. The version + * number allows the calling application to avoid version mis-match errors, + * because this API is part of a shared library. + * + * The minor version specified in this document is 2. Any OEM that returns + * this version number guarantees it passes all unit tests associated with + * this version. + * + * Parameters: + * none + * + * Returns: + * The supported API, as specified in the header file OEMCryptoCENC.h. + * + * Threading: + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * Version: + * This method changed in each API version. + */ +uint32_t OEMCrypto_MinorAPIVersion(void); + /* * OEMCrypto_BuildInformation * @@ -2590,10 +3386,11 @@ const char* OEMCrypto_SecurityLevel(void); * * When a key has version HDCP_V2_3 required in the key control block, the * transmitter must have HDCP version 2.3 and have negotiated a connection - * with a version 2.3 receiver or repeater. The transmitter must configure - * the content stream to be Type 1. Since the transmitter cannot distinguish - * between 2.2 and 2.3 downstream receivers when connected to a repeater, it - * may transmit to both 2.2 and 2.3 receivers, but not 2.1 receivers. + * with a version 2.2 or 2.3 receiver or repeater. The transmitter must + * configure the content stream to be Type 1. Since the transmitter cannot + * distinguish between 2.2 and 2.3 downstream receivers when connected to a + * repeater, it may transmit to both 2.2 and 2.3 receivers, but not 2.1 + * receivers. * * For example, if the transmitter is 2.3, and is connected to a receiver * that supports 2.3 then the current level is HDCP_V2_3. If the transmitter @@ -2658,6 +3455,33 @@ OEMCryptoResult OEMCrypto_GetHDCPCapability(OEMCrypto_HDCP_Capability* current, */ bool OEMCrypto_SupportsUsageTable(void); +/* + * OEMCrypto_MaximumUsageTableHeaderSize + * + * Description: + * Estimates the maximum usage table size. If the device does not have a + * fixed size, this returns an estimate. A maximum size of 0 means the header + * is constrained only by dynamic memory allocation. + * + * Widevine requires the size to be at least 300 entries. + * + * Parameters: + * none + * + * Returns: + * Returns an estimate for the maximum size of the usage table header. + * + * Threading: + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * Version: + * This method changed in API version 16. + */ +size_t OEMCrypto_MaximumUsageTableHeaderSize(void); + /* * OEMCrypto_IsAntiRollbackHwPresent * @@ -2727,7 +3551,7 @@ OEMCryptoResult OEMCrypto_GetNumberOfOpenSessions(size_t* count); * support more sessions -- we recommend a minimum of 50 sessions. * * Parameters: - * [out] count - this is the current number of opened sessions. + * [out] max - this is the max number of supported sessions. * * Returns: * OEMCrypto_SUCCESS @@ -2772,9 +3596,12 @@ OEMCryptoResult OEMCrypto_GetMaxNumberOfSessions(size_t* max); * - 0x2 = OEMCrypto_Supports_RSA_3072bit - the device can load a DRM * certificate with a 3072 bit RSA key. * - 0x10 = OEMCrypto_Supports_RSA_CAST - the device can load a CAST - * certificate. These certificate are used with + * certificate. These certificates are used with * OEMCrypto_GenerateRSASignature with padding type set to 0x2, PKCS1 * with block type 1 padding. + * - 0x100 = OEMCrypto_Supports_ECC_secp256r1 - Elliptic Curve secp256r1 + * - 0x200 = OEMCrypto_Supports_ECC_secp384r1 - Elliptic Curve secp384r1 + * - 0x200 = OEMCrypto_Supports_ECC_secp521r1 - Elliptic Curve secp521r1 * * Threading: * This is a "Property Function" and may be called simultaneously with any @@ -2783,7 +3610,7 @@ OEMCryptoResult OEMCrypto_GetMaxNumberOfSessions(size_t* max); * system. * * Version: - * This method changed in API version 13. + * This method changed in API version 16. */ uint32_t OEMCrypto_SupportedCertificates(void); @@ -2902,16 +3729,24 @@ uint32_t OEMCrypto_GetAnalogOutputFlags(void); * bandwidth and codec resolution are determined by the platform. * * Some parameters need more explanation. The Sample size is typically the - * size of one encoded frame. Converting this to resolution depends on the - * Codec, which is not specified by OEMCrypto. Some content has the sample - * broken into several subsamples. The "number of subsamples" restriction - * requires that any content can be broken into at least that many - * subsamples. However, this number may be larger if DecryptCENC returns - * OEMCrypto_ERROR_BUFFER_TOO_LARGE. In that case, the layer above OEMCrypto - * will break the sample into subsamples of size "Decrypt Buffer Size" as - * specified in the table below. The "Decrypt Buffer Size" means the size of - * one subsample that may be passed into DecryptCENC or CopyBuffer without - * returning error OEMCrypto_ERROR_BUFFER_TOO_LARGE. + * size of one encoded frame, but might be several frames for AV1. Converting + * this to resolution depends on the Codec, which is not specified by + * OEMCrypto. Some content has the sample broken into several subsamples. The + * "number of subsamples" restriction requires that any content can be broken + * into at least that many subsamples. However, this number may be larger if + * DecryptCENC returns OEMCrypto_ERROR_BUFFER_TOO_LARGE. In that case, the + * layer above OEMCrypto will break the sample into subsamples of size + * "Decrypt Buffer Size" as specified in the table below. The "Decrypt Buffer + * Size" means the size of one subsample that may be passed into DecryptCENC + * or CopyBuffer without returning error OEMCrypto_ERROR_BUFFER_TOO_LARGE. + * + * The minimum subsample buffer size is the smallest buffer that the CDM + * layer above OEMCrypto will use when breaking a sample into subsamples. As + * mentioned above, the CDM layer will only break a sample into smaller + * subsamples if OEMCrypto returns OEMCrypto_ERROR_BUFFER_TOO_LARGE. Because + * this might be a performance problem, OEMCrypto implementers are encouraged + * to process larger subsamples and to process multiple subsamples in a + * single call to DecryptCENC. * * The number of keys per session is an indication of how many different * track types there can be for a piece of content. Typically, content will @@ -2927,29 +3762,58 @@ uint32_t OEMCrypto_GetAnalogOutputFlags(void); * Each of these pictures would correspond to a separate playback session * with active decryption. * + * The total number of keys for all sessions indicates that the device may + * share key memory over multiple sessions. For example, on a Tier 3 device, + * the device must support four sessions with 20 keys each (80 total), or 20 + * sessions with 4 keys each (80 total), but it does not need to support 20 + * sessions with 20 keys each. + * + * The message size that is needed for a license with a large number of keys + * is larger than in previous versions. The message size limit applies to all + * functions that sign or verify messages. It also applies to the size of + * context buffers in the derive key functions. + * * Decrypted frames per second -- strictly speaking, OEMCrypto only controls * the decryption part of playback and cannot control the decoding and * display part. However, devices that support the higher resource tiers * should also support a higher frame rate. Platforms may enforce these * values. For example Android will enforce a frame rate via a GTS test. * - * +-----------------------------------+-----------+------------+-----------+ - * |Resource Rating Tier |1 - Low |2 - Medium |3 - High | - * +-----------------------------------+-----------+------------+-----------+ - * |Sample size |1 MB |2 MB |4 MB | - * +-----------------------------------+-----------+------------+-----------+ - * |Number of Subsamples |8 |16 |32 | - * +-----------------------------------+-----------+------------+-----------+ - * |Decrypt buffer size |100 KB |500 KB |1 MB | - * +-----------------------------------+-----------+------------+-----------+ - * |Generic crypto buffer size |10 KB |100 KB |500 KB | - * +-----------------------------------+-----------+------------+-----------+ - * |Number of concurrent sessions |10 |20 |20 | - * +-----------------------------------+-----------+------------+-----------+ - * |Number of keys per session |4 |20 |20 | - * +-----------------------------------+-----------+------------+-----------+ - * |Decrypted Frames per Second |30 fps SD |30 fps HD |60 fps HD | - * +-----------------------------------+-----------+------------+-----------+ + * Note on units: We will use KiB to mean 1024 bytes and MiB to mean 1024 KiB, + * as described at https://en.wikipedia.org/wiki/Kibibyte. + * + * +--------------------------------+---------+----------+---------+---------+ + * |Resource Rating Tier |1 - Low |2 - Medium|3 - High |4 - Very | + * | | | | | High | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum Sample size |1 MiB |2 MiB |4 MiB |16 MiB | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum Number of Subsamples |10 |16 |32 |64 | + * | (H264 or HEVC) | | | | | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum Number of Subsamples |9 |9 |9 |9 | + * |(VP9) | | | | | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum Number of Subsamples |72 |144 |288 |576 | + * |(AV1) | | | | | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum subsample buffer size |100 KiB |500 KiB |1 MiB |4 MiB | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum Generic crypto buffer |10 KiB |100 KiB |500 KiB |1 MiB | + * |size | | | | | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum number of open sessions |10 |20 |30 |40 | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum number of keys per |4 |20 |20 |30 | + * |session | | | | | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum total number of keys |16 |40 |80 |90 | + * | (all sessions) | | | | | + * +--------------------------------+---------+----------+---------+---------+ + * |Minimum Message Size |8 KiB |8 KiB |16 KiB |32 KiB | + * +--------------------------------+---------+----------+---------+---------+ + * |Decrypted Frames per Second |30 fps SD|30 fps HD |60 fps HD|60 fps 8k| + * +--------------------------------+---------+----------+---------+---------+ * * Parameters: * none. @@ -2969,22 +3833,38 @@ uint32_t OEMCrypto_GetAnalogOutputFlags(void); uint32_t OEMCrypto_ResourceRatingTier(void); /* - * OEMCrypto_RewrapDeviceRSAKey30 + * OEMCrypto_LoadProvisioning * * Description: - * This function is similar to RewrapDeviceRSAKey, except it uses the private - * key from an OEM certificate to decrypt the message key instead of keys - * derived from a keybox. Verifies an RSA provisioning response is valid and - * corresponds to the previous provisioning request by checking the nonce. - * The RSA private key is decrypted and stored in secure memory. The RSA key - * is then re-encrypted and signed for storage on the filesystem. We - * recommend that the OEM use an encryption key and signing key generated - * using an algorithm at least as strong as that in GenerateDerivedKeys. + * Load and parse a provisioning response, and then rewrap the private key + * for storage on the filesystem. We recommend that the OEM use an encryption + * key and signing key generated using an algorithm at least as strong as + * that in GenerateDerivedKeys. + * + * First, OEMCrypto shall verify the signature of the message using + * HMAC-SHA256 with the derived mac_key[server]. The signature verification + * shall use a constant-time algorithm (a signature mismatch will always take + * the same time as a successful comparison). The signature is over the + * entire message buffer starting at message with length message_length. If + * the signature verification fails, ignore all other arguments and return + * OEMCrypto_ERROR_SIGNATURE_FAILURE. + * + * NOTE: The calling software must have previously established the mac_keys + * and encrypt_key with a call to OEMCrypto_DeriveKeysFromSessionKey or + * OEMCrypto_GenerateDerivedKeys. + * + * The function ODK_ParseProvisioning is called to parse the message. If it + * returns an error, OEMCrypto shall return that error to the CDM layer. The + * function ODK_ParseProvisioning is described in the document "Widevine Core + * Message Serialization". + * + * Below, all fields are found in the struct ODK_ParsedLicense parsed_license + * returned by ODK_ParsedProvisioning. * * After decrypting enc_rsa_key, If the first four bytes of the buffer are * the string "SIGN", then the actual RSA key begins on the 9th byte of the * buffer. The second four bytes of the buffer is the 32 bit field - * "allowed_schemes", of type RSA_Padding_Scheme, which is used in + * "allowed_schemes"of type RSA_Padding_Scheme, which is used in * OEMCrypto_GenerateRSASignature. The value of allowed_schemes must also be * wrapped with RSA key. We recommend storing the magic string "SIGN" with * the key to distinguish keys that have a value for allowed_schemes from @@ -3002,156 +3882,18 @@ uint32_t OEMCrypto_ResourceRatingTier(void); * Verification and Algorithm: * The following checks should be performed. If any check fails, an error is * returned, and the key is not loaded. - * - * 1. Verify that in_wrapped_rsa_key_length is large enough to hold the - * rewrapped key, returning OEMCrypto_ERROR_SHORT_BUFFER otherwise. - * 2. Verify that the nonce matches one generated by a previous call to - * OEMCrypto_GenerateNonce(). The matching nonce shall be removed from - * the nonce table. If there is no matching nonce, return - * OEMCRYPTO_ERROR_INVALID_NONCE. Notice that the nonce may not point - * to a word aligned memory location. - * 3. Decrypt encrypted_message_key with the OEM certificate's private RSA - * key using RSA-OAEP into the buffer message_key. This message key is - * a 128 bit AES key used only in step 4. This message_key should be - * kept in secure memory and protected from the user. - * 4. Decrypt enc_rsa_key into the buffer rsa_key using the message_key, - * which was found in step 3. Use enc_rsa_key_iv as the initial vector - * for AES_128-CBC mode, with PKCS#5 padding. The rsa_key should be kept - * in secure memory and protected from the user. - * 5. If the first four bytes of the buffer rsa_key are the string "SIGN", - * then the actual RSA key begins on the 9th byte of the buffer. The - * second four bytes of the buffer is the 32 bit field - * "allowed_schemes", of type RSA_Padding_Scheme, which is used in - * OEMCrypto_GenerateRSASignature. The value of allowed_schemes must - * also be wrapped with RSA key. We recommend storing the magic string - * "SIGN" with the key to distinguish keys that have a value for - * allowed_schemes from those that should use the default - * allowed_schemes. Devices that do not support the alternative signing - * algorithms may refuse to load these keys and return an error of - * OEMCrypto_ERROR_NOT_IMPLEMENTED. The main use case for these - * alternative signing algorithms is to support devices that use X.509 - * certificates for authentication when acting as a ChromeCast receiver. - * This is not needed for devices that wish to send data to a ChromeCast. - * 6. If the first four bytes of the buffer rsa_key are not the string - * "SIGN", then the default value of allowed_schemes = 1 - * (kSign_RSASSA_PSS) will be used. - * 7. After possibly skipping past the first 8 bytes signifying the allowed - * signing algorithm, the rest of the buffer rsa_key contains an RSA - * device key in PKCS#8 binary DER encoded format. The OEMCrypto library - * shall verify that this RSA key is valid. - * 8. Re-encrypt the device RSA key with an internal key (such as the OEM - * key or Widevine Keybox key) and the generated IV using AES-128-CBC - * with PKCS#5 padding. - * 9. Copy the rewrapped key to the buffer specified by wrapped_rsa_key and - * the size of the wrapped key to wrapped_rsa_key_length. - * - * Parameters: - * [in] session: crypto session identifier. - * [in] nonce: A pointer to the nonce provided in the provisioning response. - * (unaligned uint32_t) - * [in] encrypted_message_key : message_key encrypted by private key from - * OEM cert. - * [in] encrypted_message_key_length : length of encrypted_message_key in - * bytes. - * [in] enc_rsa_key: Encrypted device private RSA key received from the - * provisioning server. Format is PKCS#8, binary DER encoded, and - * encrypted with message_key, using AES-128-CBC with PKCS#5 padding. - * [in] enc_rsa_key_length: length of the encrypted RSA key, in bytes. - * [in] enc_rsa_key_iv: IV for decrypting RSA key. Size is 128 bits. - * [out] wrapped_rsa_key: pointer to buffer in which encrypted RSA key should - * be stored. May be null on the first call in order to find required - * buffer size. - * [in/out] wrapped_rsa_key_length: (in) length of the encrypted RSA key, in - * bytes. - * (out) actual length of the encrypted RSA key - * - * Returns: - * OEMCrypto_SUCCESS success - * OEMCrypto_ERROR_NO_DEVICE_KEY - * OEMCrypto_ERROR_INVALID_SESSION - * OEMCrypto_ERROR_INVALID_RSA_KEY - * OEMCrypto_ERROR_SIGNATURE_FAILURE - * OEMCrypto_ERROR_INVALID_NONCE - * OEMCrypto_ERROR_SHORT_BUFFER - * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES - * OEMCrypto_ERROR_UNKNOWN_FAILURE - * OEMCrypto_ERROR_BUFFER_TOO_LARGE - * OEMCrypto_ERROR_SESSION_LOST_STATE - * OEMCrypto_ERROR_SYSTEM_INVALIDATED - * - * Buffer Sizes: - * OEMCrypto shall support message sizes of at least 8 KiB. - * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is - * larger than the supported size. - * - * Threading: - * This is a "Session Function" and may be called simultaneously with session - * functions for other sessions but not simultaneously with other functions - * for this session. It will not be called simultaneously with initialization - * or usage table functions. It is as if the CDM holds a write lock for this - * session, and a read lock on the OEMCrypto system. - * - * Version: - * This method changed in API version 12. - */ -OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( - OEMCrypto_SESSION session, const uint32_t* unaligned_nonce, - const uint8_t* encrypted_message_key, size_t encrypted_message_key_length, - const uint8_t* enc_rsa_key, size_t enc_rsa_key_length, - const uint8_t* enc_rsa_key_iv, uint8_t* wrapped_rsa_key, - size_t* wrapped_rsa_key_length); - -/* - * OEMCrypto_RewrapDeviceRSAKey - * - * Description: - * This function is similar to RewrapDeviceRSAKey30, except it uses session - * keys derived from the keybox instead of the OEM certificate. Verifies an - * RSA provisioning response is valid and corresponds to the previous - * provisioning request by checking the nonce. The RSA private key is - * decrypted and stored in secure memory. The RSA key is then re-encrypted - * and signed for storage on the filesystem. We recommend that the OEM use an - * encryption key and signing key generated using an algorithm at least as - * strong as that in GenerateDerivedKeys. - * - * After decrypting enc_rsa_key, If the first four bytes of the buffer are - * the string "SIGN", then the actual RSA key begins on the 9th byte of the - * buffer. The second four bytes of the buffer is the 32 bit field - * "allowed_schemes", of type RSA_Padding_Scheme, which is used in - * OEMCrypto_GenerateRSASignature. The value of allowed_schemes must also be - * wrapped with RSA key. We recommend storing the magic string "SIGN" with - * the key to distinguish keys that have a value for allowed_schemes from - * those that should use the default allowed_schemes. Devices that do not - * support the alternative signing algorithms may refuse to load these keys - * and return an error of OEMCrypto_ERROR_NOT_IMPLEMENTED. The main use case - * for these alternative signing algorithms is to support devices that use - * X509 certificates for authentication when acting as a ChromeCast receiver. - * This is not needed for devices that wish to send data to a ChromeCast. - * - * If the first four bytes of the buffer enc_rsa_key are not the string - * "SIGN", then the default value of allowed_schemes = 1 (kSign_RSASSA_PSS) - * will be used. - * - * Verification and Algorithm: - * The following checks should be performed. If any check fails, an error is - * returned, and the key is not loaded. - * * 1. Check that all the pointer values passed into it are within the * buffer specified by message and message_length. * 2. Verify that in_wrapped_rsa_key_length is large enough to hold the * rewrapped key, returning OEMCrypto_ERROR_SHORT_BUFFER otherwise. - * 3. Verify that the nonce matches one generated by a previous call to - * OEMCrypto_GenerateNonce(). The matching nonce shall be removed from - * the nonce table. If there is no matching nonce, return - * OEMCRYPTO_ERROR_INVALID_NONCE. - * 4. Verify the message signature, using the derived signing key + * 3. Verify the message signature, using the derived signing key * (mac_key[server]) from a previous call to - * OEMCrypto_GenerateDerivedKeys. - * 5. Decrypt enc_rsa_key in the buffer rsa_key using the derived - * encryption key (enc_key) from a previous call to - * OEMCrypto_GenerateDerivedKeys. Use enc_rsa_key_iv as the initial - * vector for AES_128-CBC mode, with PKCS#5 padding. The rsa_key should - * be kept in secure memory and protected from the user. + * OEMCrypto_GenerateDerivedKeys or OEMCrypto_DeriveKeysFromSessionKey. + * 4. The function ODK_ParseProvisioning is called to parse the message. + * 5. Decrypt enc_rsa_key in the buffer rsa_key using the session's derived + * encryption key (enc_key). Use enc_rsa_key_iv as the initial vector + * for AES_128-CBC mode, with PKCS#5 padding. The rsa_key should be kept + * in secure memory and protected from the user. * 6. If the first four bytes of the buffer rsa_key are the string "SIGN", * then the actual RSA key begins on the 9th byte of the buffer. The * second four bytes of the buffer is the 32 bit field @@ -3181,18 +3923,11 @@ OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( * * Parameters: * [in] session: crypto session identifier. - * [in] message: pointer to memory containing message to be verified. + * [in] message: pointer to memory containing data. * [in] message_length: length of the message, in bytes. - * [in] signature: pointer to memory containing the HMAC-SHA256 signature for - * message, received from the provisioning server. + * [in] core_message_length: length of the core submessage, in bytes. + * [in] signature: pointer to memory containing the signature. * [in] signature_length: length of the signature, in bytes. - * [in] nonce: A pointer to the nonce provided in the provisioning response. - * [in] enc_rsa_key: Encrypted device private RSA key received from the - * provisioning server. Format is PKCS#8, binary DER encoded, and - * encrypted with the derived encryption key, using AES-128-CBC with - * PKCS#5 padding. - * [in] enc_rsa_key_length: length of the encrypted RSA key, in bytes. - * [in] enc_rsa_key_iv: IV for decrypting RSA key. Size is 128 bits. * [out] wrapped_rsa_key: pointer to buffer in which encrypted RSA key should * be stored. May be null on the first call in order to find required * buffer size. @@ -3215,7 +3950,8 @@ OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( * OEMCrypto_ERROR_SYSTEM_INVALIDATED * * Buffer Sizes: - * OEMCrypto shall support message sizes of at least 8 KiB. + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier. * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is * larger than the supported size. * @@ -3227,28 +3963,29 @@ OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( * session, and a read lock on the OEMCrypto system. * * Version: - * This method changed in API version 12. + * This method changed in API version 16. */ -OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey( +OEMCryptoResult OEMCrypto_LoadProvisioning( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, - const uint8_t* signature, size_t signature_length, - const uint32_t* unaligned_nonce, const uint8_t* enc_rsa_key, - size_t enc_rsa_key_length, const uint8_t* enc_rsa_key_iv, - uint8_t* wrapped_rsa_key, size_t* wrapped_rsa_key_length); + size_t core_message_length, const uint8_t* signature, + size_t signature_length, uint8_t* wrapped_private_key, + size_t* wrapped_private_key_length); /* - * OEMCrypto_LoadDeviceRSAKey + * OEMCrypto_LoadDRMPrivateKey * * Description: - * Loads a wrapped RSA private key to secure memory for use by this session - * in future calls to OEMCrypto_GenerateRSASignature. The wrapped RSA key - * will be the one verified and wrapped by OEMCrypto_RewrapDeviceRSAKey. The - * RSA private key should be stored in secure memory. + * Loads a wrapped RSA or ECC private key to secure memory for use by this + * session in future calls to OEMCrypto_PrepAndSignLicenseRequest or + * OEMCrypto_DeriveKeysFromSessionKey. The wrapped private key will be the + * one verified and wrapped by OEMCrypto_LoadProvisioning. The private key + * should be stored in secure memory. * * If the bit field "allowed_schemes" was wrapped with this RSA key, its - * value will be loaded and stored with the RSA key. If there was not bit - * field wrapped with the RSA key, the key will use a default value of 1 = - * RSASSA-PSS with SHA1. + * value will be loaded and stored with the RSA key, and the key may be used + * with calls to OEMCrypto_GenerateRSASignature. If there was not a bit field + * wrapped with the RSA key, the key will be used for + * OEMCrypto_PrepAndSignLicenseRequest or OEMCrypto_DeriveKeysFromSessionKey * * Verification: * The following checks should be performed. If any check fails, an error is @@ -3261,6 +3998,8 @@ OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey( * * Parameters: * [in] session: crypto session identifier. + * [in] key_type: indicates either an RSA or ECC key for devices that support + * both. * [in] wrapped_rsa_key: wrapped device RSA key stored on the device. Format * is PKCS#8, binary DER encoded, and encrypted with a key internal to * the OEMCrypto instance, using AES-128-CBC with PKCS#5 padding. This @@ -3285,11 +4024,12 @@ OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey( * session, and a read lock on the OEMCrypto system. * * Version: - * This method changed in API version 9. + * This method changed in API version 16. */ -OEMCryptoResult OEMCrypto_LoadDeviceRSAKey(OEMCrypto_SESSION session, - const uint8_t* wrapped_rsa_key, - size_t wrapped_rsa_key_length); +OEMCryptoResult OEMCrypto_LoadDRMPrivateKey(OEMCrypto_SESSION session, + OEMCrypto_PrivateKeyType key_type, + const uint8_t* wrapped_rsa_key, + size_t wrapped_rsa_key_length); /* * OEMCrypto_LoadTestRSAKey @@ -3333,48 +4073,33 @@ OEMCryptoResult OEMCrypto_LoadTestRSAKey(void); * OEMCrypto_GenerateRSASignature * * Description: - * The OEMCrypto_GenerateRSASignature method is used to sign messages using - * the device private RSA key, specifically, it is used to sign the initial - * license request. + * The OEMCrypto_GenerateRSASignature method is only used for devices that + * are CAST receivers. This function is called after + * OEMCrypto_LoadDRMPrivateKey for the same session. * - * Refer to the Signing Messages Sent to a Server section above for more - * details. - * - * If this function is called after OEMCrypto_LoadDeviceRSAKey for the same - * session, then this function should use the device RSA key that was loaded. - * If this function is called after a call to - * OEMCrypto_GetOEMPublicCertificate for the same session, then this function - * should use the RSA private key associated with the OEM certificate. The - * only padding scheme that is valid for the OEM certificate is 0x1 - - * RSASSA-PSS with SHA1. Any other padding scheme must generate an error. - * - * For devices that wish to be CAST receivers, there is a new RSA padding - * scheme. The padding_scheme parameter indicates which hashing and padding - * is to be applied to the message so as to generate the encoded message (the - * modulus-sized block to which the integer conversion and RSA decryption is - * applied). The following values are defined: + * The parameter padding_scheme has two possible legacy values: * * 0x1 - RSASSA-PSS with SHA1. * * 0x2 - PKCS1 with block type 1 padding (only). * - * In the first case, a hash algorithm (SHA1) is first applied to the - * message, whose length is not otherwise restricted. In the second case, the - * "message" is already a digest, so no further hashing is applied, and the - * message_length can be no longer than 83 bytes. If the message_length is - * greater than 83 bytes OEMCrypto_ERROR_SIGNATURE_FAILURE shall be returned. + * The only supported padding scheme is 0x2 since version 16 of this API. In + * this second case, the "message" is already a digest, so no further hashing + * is applied, and the message_length can be no longer than 83 bytes. If the + * message_length is greater than 83 bytes OEMCrypto_ERROR_SIGNATURE_FAILURE + * shall be returned. * * The second padding scheme is for devices that use X509 certificates for * authentication. The main example is devices that work as a Cast receiver, * like a ChromeCast, not for devices that wish to send to the Cast device, * such as almost all Android devices. OEMs that do not support X509 - * certificate authentication need not implement the second scheme and can - * return OEMCrypto_ERROR_NOT_IMPLEMENTED. + * certificate authentication need not implement this function and can return + * OEMCrypto_ERROR_NOT_IMPLEMENTED. * * Verification: - * The bitwise AND of the parameter padding_scheme and the RSA key's - * allowed_schemes is computed. If this value is 0, then the signature is not - * computed and the error OEMCrypto_ERROR_INVALID_RSA_KEY is returned. + * Both the padding_scheme and the RSA key's allowed_schemes must be 0x2. If + * not, then the signature is not computed and the error + * OEMCrypto_ERROR_INVALID_RSA_KEY is returned. * * Parameters: * [in] session: crypto session identifier. @@ -3403,7 +4128,8 @@ OEMCryptoResult OEMCrypto_LoadTestRSAKey(void); * OEMCrypto_ERROR_SYSTEM_INVALIDATED * * Buffer Sizes: - * OEMCrypto shall support message sizes of at least 8 KiB. + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier. * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is * larger than the supported size. * @@ -3415,7 +4141,7 @@ OEMCryptoResult OEMCrypto_LoadTestRSAKey(void); * session, and a read lock on the OEMCrypto system. * * Version: - * This method changed in API version 12. + * This method changed in API version 16. */ OEMCryptoResult OEMCrypto_GenerateRSASignature( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, @@ -3433,6 +4159,9 @@ OEMCryptoResult OEMCrypto_GenerateRSASignature( * will encrypt and sign the new, empty, header and return it in the provided * buffer. * + * The new entry should be created with a status of kUnused and all times + * times should be set to 0. + * * Devices that do not implement a Session Usage Table may return * OEMCrypto_ERROR_NOT_IMPLEMENTED. * @@ -3474,7 +4203,7 @@ OEMCryptoResult OEMCrypto_CreateUsageTableHeader(uint8_t* header_buffer, * * Parameters: * [in] buffer: pointer to memory containing encrypted usage table header. - * [in] buffert_length: length of the buffer, in bytes. + * [in] buffer_length: length of the buffer, in bytes. * * Returns: * OEMCrypto_SUCCESS success @@ -3496,7 +4225,7 @@ OEMCryptoResult OEMCrypto_CreateUsageTableHeader(uint8_t* header_buffer, * system. * * Version: - * This method changed in API version 13. + * This method changed in API version 16. */ OEMCryptoResult OEMCrypto_LoadUsageTableHeader(const uint8_t* buffer, size_t buffer_length); @@ -3519,6 +4248,9 @@ OEMCryptoResult OEMCrypto_LoadUsageTableHeader(const uint8_t* buffer, * a rogue application from deleting an entry and then loading an old version * of it. * + * If the session already has a usage entry associated with it, the error + * OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES is returned. + * * Parameters: * [in] session: handle for the session to be used. * [out] usage_entry_number: index of new usage entry. @@ -3534,6 +4266,8 @@ OEMCryptoResult OEMCrypto_LoadUsageTableHeader(const uint8_t* buffer, * OEMCrypto_ERROR_UNKNOWN_FAILURE * OEMCrypto_ERROR_SESSION_LOST_STATE * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES - if there already is a usage entry + * loaded into this session * * Threading: * This is a "Usage Table Function" and will not be called simultaneously @@ -3554,13 +4288,24 @@ OEMCryptoResult OEMCrypto_CreateNewUsageEntry(OEMCrypto_SESSION session, * signature at the beginning of the buffer is verified and the buffer will * be decrypted. Then the verification field in the entry will be verified. * The index in the entry must match the index passed in. The generation - * number in the entry will be compared against that in the header. If it is - * off by 1, a warning is returned, but the entry is still loaded. This - * warning may be logged by the CDM layer. If the generation number is off by - * more than 1, an error is returned and the entry is not loaded. + * number in the entry will be compared against the entry's corresponding + * generation number in the header. If it is off by 1, a warning is returned, + * but the entry is still loaded. This warning may be logged by the CDM + * layer. If the generation number is off by more than 1, an error is + * returned and the entry is not loaded. + * + * OEMCrypto shall call ODK_ReloadClockValues, as described in "License + * Duration and Renweal" to set the session's clock values. * * If the entry is already loaded into another open session, then this fails - * and returns OEMCrypto_ERROR_INVALID_SESSION. + * and returns OEMCrypto_ERROR_INVALID_SESSION. If the session already has a + * usage entry associated with it, the error + * OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES is returned. + * + * Before version API 16, the usage entry stored the time that the license + * was loaded. This value is now interpreted as the time that the licence + * request was signed. This can be achieved by simply renaming the field and + * using the same value when reloading an older entry. * * Parameters: * [in] session: handle for the session to be used. @@ -3584,6 +4329,8 @@ OEMCryptoResult OEMCrypto_CreateNewUsageEntry(OEMCrypto_SESSION session, * OEMCrypto_ERROR_BAD_MAGIC - verification string does not match. * OEMCrypto_ERROR_SESSION_LOST_STATE * OEMCrypto_ERROR_SYSTEM_INVALIDATED + * OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES - if there already is a usage entry + * loaded into this session * * Threading: * This is a "Usage Table Function" and will not be called simultaneously @@ -3603,18 +4350,25 @@ OEMCryptoResult OEMCrypto_LoadUsageEntry(OEMCrypto_SESSION session, * * Description: * Updates the session's usage entry and fills buffers with the encrypted and - * signed entry and usage table header. OEMCrypto will update all time and - * status values in the entry, and then increment the entry's generation - * number. The corresponding generation number in the usage table header is - * also incremented so that it matches the one in the entry. The master - * generation number in the usage table header is incremented and is copied - * to secure persistent storage. OEMCrypto will encrypt and sign the entry - * into the entry_buffer, and it will encrypt and sign the usage table header - * into the header_buffer. Some actions, such as the first decrypt and - * deactivating an entry, will also increment the entry's generation number - * as well as changing the entry's status and time fields. As in OEMCrypto - * v12, the first decryption will change the status from Inactive to Active, - * and it will set the time stamp "first decrypt". + * signed entry and usage table header. + * + * OEMCrypto shall call ODK_UpdateLastPlaybackTime to update the session's + * clock values, as discussed in the document "License Duration and Renewal". + * The values in the session's clock values structure are copied to the usage + * entry. + * + * OEMCrypto shall update all time and status values in the entry, and then + * increment the entry's generation number. The corresponding generation + * number in the usage table header is also incremented so that it matches + * the one in the entry. The master generation number in the usage table + * header is incremented and the master generation number is copied to secure + * persistent storage. OEMCrypto will encrypt and sign the entry into the + * entry_buffer, and it will encrypt and sign the usage table header into the + * header_buffer. Some actions, such as the first decrypt and deactivating an + * entry, will also increment the entry's generation number as well as + * changing the entry's status and time fields. The first decryption will + * change the status from Inactive to Active, and it will set the time stamp + * "first decrypt". * * If the usage entry has the flag ForbidReport set, then the flag is * cleared. It is the responsibility of the CDM layer to call this function @@ -3622,10 +4376,10 @@ OEMCryptoResult OEMCrypto_LoadUsageEntry(OEMCrypto_SESSION session, * the CDM is terminated. Failure to do so will result in generation number * skew, which will invalidate all of the usage table. * - * If either buffer_length is not large enough, they are set to the needed - * size, and OEMCrypto_ERROR_SHORT_BUFFER. In this case, the entry is not - * updated, ForbidReport is not cleared, generation numbers are not - * incremented, and no other work is done. + * If either entry_buffer_length or header_buffer_length is not large enough, + * they are set to the needed size, and return OEMCrypto_ERROR_SHORT_BUFFER. + * In this case, the entry is not updated, ForbidReport is not cleared, + * generation numbers are not incremented, and no other work is done. * * Parameters: * [in] session: handle for the session to be used. @@ -3635,7 +4389,7 @@ OEMCryptoResult OEMCrypto_LoadUsageEntry(OEMCrypto_SESSION session, * (out) actual length of the header_buffer * [out] entry_buffer: pointer to memory where encrypted usage table entry is * written. - * [in/out] buffer_length: (in) length of the entry_buffer, in bytes. + * [in/out] entry_buffer_length: (in) length of the entry_buffer, in bytes. * (out) actual length of the entry_buffer * * Returns: @@ -3653,20 +4407,19 @@ OEMCryptoResult OEMCrypto_LoadUsageEntry(OEMCrypto_SESSION session, * system. * * Version: - * This method changed in API version 13. + * This method changed in API version 16. */ -OEMCryptoResult OEMCrypto_UpdateUsageEntry(OEMCrypto_SESSION session, - uint8_t* header_buffer, - size_t* header_buffer_length, - uint8_t* entry_buffer, - size_t* entry_buffer_length); +OEMCryptoResult OEMCrypto_UpdateUsageEntry( + OEMCrypto_SESSION session, OEMCrypto_SharedMemory* header_buffer, + size_t* header_buffer_length, OEMCrypto_SharedMemory* entry_buffer, + size_t* entry_buffer_length); /* * OEMCrypto_DeactivateUsageEntry * * Description: * This deactivates the usage entry associated with the current session. This - * means that the state of the usage entry is changed to InactiveUsed if it + * means that the status of the usage entry is changed to InactiveUsed if it * was Active, or InactiveUnused if it was Unused. This also increments the * entry's generation number, and the header's master generation number. The * corresponding generation number in the usage table header is also @@ -3675,6 +4428,9 @@ OEMCryptoResult OEMCrypto_UpdateUsageEntry(OEMCrypto_SESSION session, * generating a report of a deactivated license without first saving the * entry. * + * OEMCrypto shall call ODK_DeactivateUsageEntry to update the session's + * clock values, as discussed in the document "License Duration and Renewal". + * * It is allowed to call this function multiple times. If the state is * already InactiveUsed or InactiveUnused, then this function does not change * the entry or its state. @@ -3705,7 +4461,7 @@ OEMCryptoResult OEMCrypto_UpdateUsageEntry(OEMCrypto_SESSION session, * system. * * Version: - * This method changed in API version 13. + * This method changed in API version 16. */ OEMCryptoResult OEMCrypto_DeactivateUsageEntry(OEMCrypto_SESSION session, const uint8_t* pst, @@ -3720,10 +4476,9 @@ OEMCryptoResult OEMCrypto_DeactivateUsageEntry(OEMCrypto_SESSION session, * If the buffer_length is not sufficient to hold a report structure, set * buffer_length and return OEMCrypto_ERROR_SHORT_BUFFER. * - * If the an entry was not loaded or created with - * OEMCrypto_CreateNewUsageEntry or OEMCRypto_LoadUsageEntry, or if the pst - * does not match that in the entry, return the error - * OEMCrypto_ERROR_INVALID_CONTEXT. + * If an entry was not loaded or created with OEMCrypto_CreateNewUsageEntry + * or OEMCRypto_LoadUsageEntry, or if the pst does not match that in the + * entry, return the error OEMCrypto_ERROR_INVALID_CONTEXT. * * If the usage entry's flag ForbidReport is set, indicating the entry has * not been saved since the entry was deactivated, then the error @@ -3733,9 +4488,10 @@ OEMCryptoResult OEMCrypto_DeactivateUsageEntry(OEMCrypto_SESSION session, * and OEMCrypto returns the error OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE. * * The pst_report is filled out by subtracting the times in the Usage Entry - * from the current time on the secure clock. This is done in case the secure - * clock is not using UTC time, but is instead using something like seconds - * since clock installed. + * from the current time on the secure clock. This design was chosen to avoid + * the device's secure clock with any external clock. + * + * (See drawing in "Widevine Modular DRM Security Integration Guide") * * Valid values for status are: * @@ -3751,7 +4507,8 @@ OEMCryptoResult OEMCrypto_DeactivateUsageEntry(OEMCrypto_SESSION session, * - 0 = Insecure Clock - clock just uses system time. * - 1 = Secure Timer - clock runs from a secure timer which is initialized * from system time when OEMCrypto becomes active and cannot be modified - * by user software or the user while OEMCrypto is active. + * by user software or the user while OEMCrypto is active. A secure + * timer cannot run backwards, even while OEMCrypto is not active. * - 2 = Secure Clock - Real-time clock set from a secure source that * cannot be modified by user software regardless of whether OEMCrypto * is active or inactive. The clock time can only be modified by @@ -3760,12 +4517,18 @@ OEMCryptoResult OEMCrypto_DeactivateUsageEntry(OEMCrypto_SESSION session, * that cannot be modified by user software and there are security * features that prevent the user from modifying the clock in hardware, * such as a tamper proof battery. + * (See drawing in "Widevine Modular DRM Security Integration Guide") + * * After pst_report has been filled in, the HMAC SHA1 signature is computed * for the buffer from bytes 20 to the end of the pst field. The signature is * computed using the mac_key[client] which is stored in the usage table. The * HMAC SHA1 signature is used to prevent a rogue application from using * OMECrypto_GenerateSignature to forge a Usage Report. * + * Before version 16 of this API, seconds_since_license_received was reported + * instead of seconds_since_license_signed. For any practical bookkeeping + * purposes, these events are essentially at the same time. + * * Devices that do not implement a Session Usage Table may return * OEMCrypto_ERROR_NOT_IMPLEMENTED. * @@ -3808,8 +4571,7 @@ OEMCryptoResult OEMCrypto_DeactivateUsageEntry(OEMCrypto_SESSION session, */ OEMCryptoResult OEMCrypto_ReportUsage(OEMCrypto_SESSION session, const uint8_t* pst, size_t pst_length, - uint8_t* buffer, - size_t* buffer_length); + uint8_t* buffer, size_t* buffer_length); /* * OEMCrypto_MoveEntry @@ -3865,15 +4627,16 @@ OEMCryptoResult OEMCrypto_MoveEntry(OEMCrypto_SESSION session, * CDM layer after it has defragmented the usage table and can delete unused * entries. It is an error if any open session is associated with an entry * that will be erased - the error OEMCrypto_ERROR_ENTRY_IN_USE shall be - * returned in this case. If new_table_size is larger than the current size, - * then the header is not changed and the error is returned. If the header - * has not been previously loaded, then an error is returned. OEMCrypto will - * increment the master generation number in the header and store the new - * value in secure persistent storage. Then, OEMCrypto will encrypt and sign - * the header into the provided buffer. The generation numbers of all - * remaining entries will remain unchanged. The next time + * returned in this case, and the header shall not be modified. If + * new_entry_count is larger than the current size, then the header is not + * changed and the error OEMCrypto_ERROR_UNKNOWN_FAILURE is returned. If the + * header has not been previously loaded, then an error is returned. + * OEMCrypto will increment the master generation number in the header and + * store the new value in secure persistent storage. Then, OEMCrypto will + * encrypt and sign the header into the provided buffer. The generation + * numbers of all remaining entries will remain unchanged. The next time * OEMCrypto_CreateNewUsageEntry is called, the new entry will have an index - * of new_table_size. + * of new_entry_count. * * Devices that do not implement a Session Usage Table may return * OEMCrypto_ERROR_NOT_IMPLEMENTED. @@ -3882,6 +4645,9 @@ OEMCryptoResult OEMCrypto_MoveEntry(OEMCrypto_SESSION session, * set to the needed value, the generation number is not incremented, and * OEMCrypto_ERROR_SHORT_BUFFER is returned. * + * If the header has not been loaded or created, return the error + * OEMCrypto_ERROR_UNKNOWN_FAILURE. + * * Parameters: * [in] new_entry_count: number of entries in the to be in the header. * [out] header_buffer: pointer to memory where encrypted usage table header @@ -3909,76 +4675,6 @@ OEMCryptoResult OEMCrypto_ShrinkUsageTableHeader(uint32_t new_entry_count, uint8_t* header_buffer, size_t* header_buffer_length); -/* - * OEMCrypto_CopyOldUsageEntry - * - * Description: - * This function copies an entry from the old v12 table to the new table. The - * new entry will already have been loaded by CreateNewUsageEntry. If the - * device did not support pre-v13 usage tables, this may return - * OEMCrypto_ERROR_NOT_IMPLEMENTED. - * - * This is only needed for devices that are upgrading from a version of - * OEMCrypto before v13 to a recent version. Devices that have an existing - * usage table with customer's offline licenses will use this method to move - * entries from the old table to the new one. - * - * Parameters: - * [in] session: handle for the session to be used. - * [in] pst: pointer to memory containing Provider Session Token. - * [in] pst_length: length of the pst, in bytes. - * - * Returns: - * OEMCrypto_SUCCESS success - * OEMCrypto_ERROR_NOT_IMPLEMENTED - * OEMCrypto_ERROR_UNKNOWN_FAILURE - * OEMCrypto_ERROR_SESSION_LOST_STATE - * OEMCrypto_ERROR_SYSTEM_INVALIDATED - * - * Threading: - * This is a "Usage Table Function" and will not be called simultaneously - * with any other function, as if the CDM holds a write lock on the OEMCrypto - * system. - * - * Version: - * This method is new in API version 13. - */ -OEMCryptoResult OEMCrypto_CopyOldUsageEntry(OEMCrypto_SESSION session, - const uint8_t* pst, - size_t pst_length); - -/* - * OEMCrypto_DeleteOldUsageTable - * - * Description: - * This function will delete the old usage table, if possible, freeing any - * nonvolatile secure memory. This may return OEMCrypto_ERROR_NOT_IMPLEMENTED - * if the device did not support pre-v13 usage tables. - * - * This is only needed for devices that are upgrading from a version of - * OEMCrypto before v13 to a recent version. Devices that have an existing - * usage table with customer's offline licenses will use this method to move - * entries from the old table to the new one. - * - * Parameters: - * none - * - * Returns: - * OEMCrypto_SUCCESS success - * OEMCrypto_ERROR_NOT_IMPLEMENTED - * OEMCrypto_ERROR_UNKNOWN_FAILURE - * OEMCrypto_ERROR_SYSTEM_INVALIDATED - * - * Threading: - * This is an "Initialization and Termination Function" and will not be - * called simultaneously with any other function, as if the CDM holds a write - * lock on the OEMCrypto system. - * - * Version: - * This method is new in API version 13. - */ -OEMCryptoResult OEMCrypto_DeleteOldUsageTable(void); - /* * OEMCrypto_RemoveSRM * @@ -4006,41 +4702,6 @@ OEMCryptoResult OEMCrypto_DeleteOldUsageTable(void); */ OEMCryptoResult OEMCrypto_RemoveSRM(void); -/* - * OEMCrypto_CreateOldUsageEntry - * - * Description: - * This forces the creation of an entry in the old usage table in order to - * test OEMCrypto_CopyOldUsageTable. OEMCrypto will create a new entry, set - * the status and compute the times at license receive, first decrypt and - * last decrypt. The mac keys will be copied to the entry. The mac keys are - * not encrypted, but will only correspond to a test license. - * - * Devices that do not support usage tables, or devices that will not be - * field upgraded from a version of OEMCrypto before v13 to a recent version - * may return OEMCrypto_ERROR_NOT_IMPLEMENTED. - * - * Returns: - * OEMCrypto_ERROR_NOT_IMPLEMENTED - * OEMCrypto_SUCCESS - * - * Threading: - * This is an "Initialization and Termination Function" and will not be - * called simultaneously with any other function, as if the CDM holds a write - * lock on the OEMCrypto system. It is only used when running unit tests. - * - * Version: - * This method is new in API version 13. - */ -OEMCryptoResult OEMCrypto_CreateOldUsageEntry(uint64_t time_since_license_received, - uint64_t time_since_first_decrypt, - uint64_t time_since_last_decrypt, - OEMCrypto_Usage_Entry_Status status, - uint8_t *server_mac_key, - uint8_t *client_mac_key, - const uint8_t* pst, - size_t pst_length); - /* * OEMCrypto_SupportsDecryptHash * @@ -4086,14 +4747,15 @@ uint32_t OEMCrypto_SupportsDecryptHash(void); * Description: * Set the hash value for the next frame to be decrypted. This function is * called before the first subsample is passed to OEMCrypto_DecryptCENC, when - * the subsample_flag has the bit OEMCrytpo_FirstSubsample set. The hash is + * the subsample_flag has the bit OEMCrypto_FirstSubsample set. The hash is * over all of the frame or sample: encrypted and clear subsamples * concatenated together, up to, and including the subsample with the * subsample_flag having the bit OEMCrypto_LastSubsample set. If hashing the * output is not supported, then this will return * OEMCrypto_ERROR_NOT_IMPLEMENTED. If the hash is ill formed or there are * other error conditions, this returns OEMCrypto_ERROR_UNKNOWN_FAILURE. The - * length of the hash will be at most 128 bytes. + * length of the hash will be at most 128 bytes, and will be 4 bytes (32 + * bits) for the default CRC32 hash. * * This may be called before the first call to SelectKey. In that case, this * function cannot verify that the key control block allows hash @@ -4190,6 +4852,146 @@ OEMCryptoResult OEMCrypto_SetDecryptHash(OEMCrypto_SESSION session, OEMCryptoResult OEMCrypto_GetHashErrorCode(OEMCrypto_SESSION session, uint32_t* failed_frame_number); +/* + * OEMCrypto_AllocateSecureBuffer + * + * Description: + * Allocates a secure buffer and fills out the destination buffer information + * in output. The integer secure_fd may also be set to indicate the source of + * the buffer. OEMCrypto may use the secure_fd to help track the buffer if it + * wishes. The unit tests will pass a pointer to the same destination buffer + * description and the same secure_fd to OEMCrypto_FreeSecureBuffer when the + * buffer is to be freed. + * + * This is especially helpful if the hash functions above are supported. This + * will only be used by the OEMCrypto unit tests, so we recommend returning + * OEMCrypto_ERROR_NOT_IMPLEMENTED for production devices if performance is + * an issue. If OEMCrypto_ERROR_NOT_IMPLEMENTED is returned, then secure + * buffer unit tests will be skipped. + * + * Parameters: + * [in] session: session id for operation. + * [in] buffer_size: the requested buffer size. + * [out] output: the buffer descriptor for the created buffer. This will be + * passed into the OEMCrypto_DecryptCENC function. + * [out] secure_fd: a pointer to platform dependant file or buffer + * descriptor. This will be passed to OEMCrypto_FreeSecureBuffer. + * + * Returns: + * OEMCrypto_SUCCESS - if the buffer was created + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_OUTPUT_TOO_LARGE + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * Threading: + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * Version: + * This method is new in API version 16. + */ +OEMCryptoResult OEMCrypto_AllocateSecureBuffer( + OEMCrypto_SESSION session, size_t buffer_size, + OEMCrypto_DestBufferDesc* output_descriptor, int* secure_fd); + +/* + * OEMCrypto_FreeSecureBuffer + * + * Description: + * Frees a secure buffer that had previously been created with + * OEMCrypto_AllocateSecureBuffer. Any return value except OEMCrypto_SUCCESS + * will cause the unit test using secure buffers to fail. + * + * Parameters: + * [in] session: session id for operation. + * [out] output: the buffer descriptor modified by + * OEMCrypto_AllocateSecureBuffer + * [in] secure_fd: The integer returned by OEMCrypto_AllocateSecureBuffer + * + * Returns: + * OEMCrypto_SUCCESS - if the buffer was freed + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * Threading: + * This is a "Session Function" and may be called simultaneously with session + * functions for other sessions but not simultaneously with other functions + * for this session. It will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * Version: + * This method is new in API version 16. + */ +OEMCryptoResult OEMCrypto_FreeSecureBuffer( + OEMCrypto_SESSION session, OEMCrypto_DestBufferDesc* output_descriptor, + int secure_fd); + +/****************************************************************************/ +/****************************************************************************/ +/* The following functions are deprecated. They are not required for the + * current version of OEMCrypto. They are being declared here to help with + * backwards compatibility. + */ +OEMCryptoResult OEMCrypto_GenerateSignature(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + uint8_t* signature, + size_t* signature_length); +OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( + OEMCrypto_SESSION session, const uint32_t* unaligned_nonce, + const uint8_t* encrypted_message_key, size_t encrypted_message_key_length, + const uint8_t* enc_rsa_key, size_t enc_rsa_key_length, + const uint8_t* enc_rsa_key_iv, uint8_t* wrapped_rsa_key, + size_t* wrapped_rsa_key_length); +OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length, + const uint32_t* unaligned_nonce, const uint8_t* enc_rsa_key, + size_t enc_rsa_key_length, const uint8_t* enc_rsa_key_iv, + uint8_t* wrapped_rsa_key, size_t* wrapped_rsa_key_length); +OEMCryptoResult OEMCrypto_UpdateUsageTable(void); +OEMCryptoResult OEMCrypto_DeleteUsageEntry(OEMCrypto_SESSION, const uint8_t*, + size_t, const uint8_t*, size_t, + const uint8_t*, size_t); +OEMCryptoResult OEMCrypto_ForceDeleteUsageEntry(const uint8_t*, size_t); +OEMCryptoResult OEMCrypto_CopyOldUsageEntry(OEMCrypto_SESSION session, + const uint8_t* pst, + size_t pst_length); +OEMCryptoResult OEMCrypto_DeleteOldUsageTable(void); +OEMCryptoResult OEMCrypto_CreateOldUsageEntry( + uint64_t time_since_license_received, uint64_t time_since_first_decrypt, + uint64_t time_since_last_decrypt, OEMCrypto_Usage_Entry_Status status, + uint8_t* server_mac_key, uint8_t* client_mac_key, const uint8_t* pst, + size_t pst_length); +OEMCryptoResult OEMCrypto_GenerateDerivedKeys_V15( + OEMCrypto_SESSION session, const uint8_t* mac_key_context, + uint32_t mac_key_context_length, const uint8_t* enc_key_context, + uint32_t enc_key_context_length); +typedef struct { + size_t encrypt; // number of 16 byte blocks to decrypt. + size_t skip; // number of 16 byte blocks to leave in clear. + size_t offset; // offset into the pattern in blocks for this call. +} OEMCrypto_CENCEncryptPatternDesc_V15; +OEMCryptoResult OEMCrypto_DecryptCENC_V15( + OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, + bool is_encrypted, const uint8_t* iv, + size_t block_offset, // used for CTR "cenc" mode only. + OEMCrypto_DestBufferDesc* out_buffer_descriptor, + const OEMCrypto_CENCEncryptPatternDesc_V15* pattern, + uint8_t subsample_flags); +OEMCryptoResult OEMCrypto_GetOEMPublicCertificate_V15( + OEMCrypto_SESSION session, uint8_t* public_cert, + size_t* public_cert_length); +OEMCryptoResult OEMCrypto_LoadDeviceRSAKey(OEMCrypto_SESSION session, + const uint8_t* wrapped_rsa_key, + size_t wrapped_rsa_key_length); +/****************************************************************************/ +/****************************************************************************/ + #ifdef __cplusplus } #endif diff --git a/libwvdrmengine/oemcrypto/include/OEMCryptoCENCCommon.h b/libwvdrmengine/oemcrypto/include/OEMCryptoCENCCommon.h new file mode 120000 index 00000000..dd929640 --- /dev/null +++ b/libwvdrmengine/oemcrypto/include/OEMCryptoCENCCommon.h @@ -0,0 +1 @@ +../../oemcrypto/odk/include/OEMCryptoCENCCommon.h \ No newline at end of file diff --git a/libwvdrmengine/oemcrypto/include/level3.h b/libwvdrmengine/oemcrypto/include/level3.h index 49031fbc..f71584b8 100644 --- a/libwvdrmengine/oemcrypto/include/level3.h +++ b/libwvdrmengine/oemcrypto/include/level3.h @@ -187,15 +187,12 @@ OEMCryptoResult Level3_QueryKeyControl(OEMCrypto_SESSION session, size_t key_id_length, uint8_t* key_control_block, size_t* key_control_block_length); -OEMCryptoResult Level3_DecryptCENC(OEMCrypto_SESSION session, - const uint8_t *data_addr, - size_t data_length, - bool is_encrypted, - const uint8_t *iv, - size_t block_offset, - OEMCrypto_DestBufferDesc* out_buffer, - const OEMCrypto_CENCEncryptPatternDesc* pattern, - uint8_t subsample_flags); +OEMCryptoResult Level3_DecryptCENC( + OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, + bool is_encrypted, const uint8_t* iv, size_t block_offset, + OEMCrypto_DestBufferDesc* out_buffer_descriptor, + const OEMCrypto_CENCEncryptPatternDesc_V15* pattern, + uint8_t subsample_flags); OEMCryptoResult Level3_InstallKeyboxOrOEMCert(const uint8_t* rot, size_t rotLength); OEMCryptoResult Level3_IsKeyboxOrOEMCertValid(void); @@ -371,11 +368,10 @@ OEMCryptoResult Level3_RefreshKeys( OEMCryptoResult Level3_LoadEntitledContentKeys( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, size_t num_keys, const OEMCrypto_EntitledContentKeyObject* key_array); -OEMCryptoResult Level3_CopyBuffer(OEMCrypto_SESSION session, - const uint8_t *data_addr, - size_t data_length, - OEMCrypto_DestBufferDesc* out_buffer, - uint8_t subsample_flags); +OEMCryptoResult Level3_CopyBuffer( + OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, + const OEMCrypto_DestBufferDesc* out_buffer_descriptor, + uint8_t subsample_flags); // The following are specific to Google's Level 3 implementation and are not // required. diff --git a/libwvdrmengine/oemcrypto/include/oemcrypto_types.h b/libwvdrmengine/oemcrypto/include/oemcrypto_types.h index 2bff9d7f..bf2158f6 100644 --- a/libwvdrmengine/oemcrypto/include/oemcrypto_types.h +++ b/libwvdrmengine/oemcrypto/include/oemcrypto_types.h @@ -23,45 +23,59 @@ typedef struct WidevineKeybox { // 128 bytes total. uint8_t crc_[4]; } WidevineKeybox; +/* + * SRM_Restriction_Data + * + * Structure passed into LoadKeys to specify required SRM version. + */ +typedef struct { + uint8_t verification[8]; // must be "HDCPDATA" + uint32_t minimum_srm_version; // version number. +} SRM_Restriction_Data; + +// clang-format off // Key Control Block Bit Masks: -const uint32_t kControlObserveDataPath = (1<<31); -const uint32_t kControlObserveHDCP = (1<<30); -const uint32_t kControlObserveCGMS = (1<<29); -const uint32_t kControlRequireAntiRollbackHardware = (1<<28); -const uint32_t kControlAllowHashVerification = (1<<24); -const uint32_t kSharedLicense = (1<<23); -const uint32_t kControlSRMVersionRequired = (1<<22); -const uint32_t kControlDisableAnalogOutput = (1<<21); -const uint32_t kControlSecurityPatchLevelShift = 15; -const uint32_t kControlSecurityPatchLevelMask = - (0x3F< #include @@ -20,17 +20,17 @@ extern "C" { #endif -// clang-format off +/* clang-format off */ typedef enum OEMCryptoResult { OEMCrypto_SUCCESS = 0, OEMCrypto_ERROR_INIT_FAILED = 1, OEMCrypto_ERROR_TERMINATE_FAILED = 2, OEMCrypto_ERROR_OPEN_FAILURE = 3, OEMCrypto_ERROR_CLOSE_FAILURE = 4, - OEMCrypto_ERROR_ENTER_SECURE_PLAYBACK_FAILED = 5, // deprecated - OEMCrypto_ERROR_EXIT_SECURE_PLAYBACK_FAILED = 6, // deprecated + OEMCrypto_ERROR_ENTER_SECURE_PLAYBACK_FAILED = 5, /* deprecated */ + OEMCrypto_ERROR_EXIT_SECURE_PLAYBACK_FAILED = 6, /* deprecated */ OEMCrypto_ERROR_SHORT_BUFFER = 7, - OEMCrypto_ERROR_NO_DEVICE_KEY = 8, // no keybox device key. + OEMCrypto_ERROR_NO_DEVICE_KEY = 8, /* no keybox device key. */ OEMCrypto_ERROR_NO_ASSET_KEY = 9, OEMCrypto_ERROR_KEYBOX_INVALID = 10, OEMCrypto_ERROR_NO_KEYDATA = 11, @@ -62,9 +62,9 @@ typedef enum OEMCryptoResult { OEMCrypto_ERROR_INSUFFICIENT_RESOURCES = 37, OEMCrypto_ERROR_INSUFFICIENT_HDCP = 38, OEMCrypto_ERROR_BUFFER_TOO_LARGE = 39, - OEMCrypto_WARNING_GENERATION_SKEW = 40, // Warning, not an error. + OEMCrypto_WARNING_GENERATION_SKEW = 40, /* Warning, not error. */ OEMCrypto_ERROR_GENERATION_SKEW = 41, - OEMCrypto_LOCAL_DISPLAY_ONLY = 42, // Info, not an error. + OEMCrypto_LOCAL_DISPLAY_ONLY = 42, /* Info, not an error. */ OEMCrypto_ERROR_ANALOG_OUTPUT = 43, OEMCrypto_ERROR_WRONG_PST = 44, OEMCrypto_ERROR_WRONG_KEYS = 45, @@ -72,8 +72,9 @@ typedef enum OEMCryptoResult { OEMCrypto_ERROR_LICENSE_INACTIVE = 47, OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE = 48, OEMCrypto_ERROR_ENTRY_IN_USE = 49, - OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE = 50, // Reserved. Do not use. - OEMCrypto_KEY_NOT_LOADED = 51, // obsolete. use error 26. + OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE = 50, /* Obsolete. Don't use. */ + /* Use OEMCrypto_ERROR_NO_CONTENT_KEY instead of KEY_NOT_LOADED. */ + OEMCrypto_KEY_NOT_LOADED = 51, /* Obsolete. */ OEMCrypto_KEY_NOT_ENTITLED = 52, OEMCrypto_ERROR_BAD_HASH = 53, OEMCrypto_ERROR_OUTPUT_TOO_LARGE = 54, @@ -89,8 +90,9 @@ typedef enum OEMCryptoResult { ODK_DISABLE_TIMER = ODK_ERROR_BASE + 2, ODK_TIMER_EXPIRED = ODK_ERROR_BASE + 3, ODK_UNSUPPORTED_API = ODK_ERROR_BASE + 4, + ODK_STALE_RENEWAL = ODK_ERROR_BASE + 5, } OEMCryptoResult; -// clang-format on +/* clang-format on */ /* * OEMCrypto_Usage_Entry_Status. @@ -99,11 +101,26 @@ typedef enum OEMCryptoResult { typedef enum OEMCrypto_Usage_Entry_Status { kUnused = 0, kActive = 1, - kInactive = 2, // Deprecated. Used kInactiveUsed or kInactiveUnused. + kInactive = 2, /* Deprecated. Use kInactiveUsed or kInactiveUnused. */ kInactiveUsed = 3, kInactiveUnused = 4, } OEMCrypto_Usage_Entry_Status; +/* + * OEMCrypto_LicenseType is used in the license message to indicate if the key + * objects are for content keys, or for entitlement keys. + */ +typedef enum OEMCrypto_LicenseType { + OEMCrypto_ContentLicense = 0, + OEMCrypto_EntitlementLicense = 1 +} OEMCrypto_LicenseType; + +/* Private key type used in the provisioning response. */ +typedef enum OEMCrypto_PrivateKeyType { + OEMCrypto_RSA_Private_Key = 0, + OEMCrypto_ECC_Private_Key = 1, +} OEMCrypto_PrivateKeyType; + /* * OEMCrypto_Substring * @@ -150,4 +167,4 @@ typedef struct { } #endif -#endif // OEMCRYPTO_CENC_COMMON_H_ +#endif /* WIDEVINE_ODK_INCLUDE_OEMCRYPTOCENCCOMMON_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/include/core_message_deserialize.h b/libwvdrmengine/oemcrypto/odk/include/core_message_deserialize.h new file mode 100644 index 00000000..c2967dd2 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/include/core_message_deserialize.h @@ -0,0 +1,59 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ + +/********************************************************************* + * core_message_deserialize.h + * + * OEMCrypto v16 Core Message Serialization library counterpart (a.k.a. KDO) + * + * This file declares functions to deserialize request messages prepared by + * Widevine clients (OEMCrypto/ODK). + * + * Please refer to core_message_types.h for details. + * + *********************************************************************/ + +#ifndef WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_DESERIALIZE_H_ +#define WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_DESERIALIZE_H_ + +#include "core_message_types.h" + +namespace oemcrypto_core_message { +namespace deserialize { + +/** + * Counterpart (deserializer) of ODK_PrepareCoreLicenseRequest (serializer) + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_license_request + */ +bool CoreLicenseRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_LicenseRequest* core_license_request); + +/** + * Counterpart (deserializer) of ODK_PrepareCoreRenewalRequest (serializer) + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_renewal_request + */ +bool CoreRenewalRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_RenewalRequest* core_renewal_request); + +/** + * Counterpart (deserializer) of ODK_PrepareCoreProvisioningRequest (serializer) + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_provisioning_request + */ +bool CoreProvisioningRequestFromMessage( + const std::string& oemcrypto_core_message, + ODK_ProvisioningRequest* core_provisioning_request); + +} /* namespace deserialize */ +} /* namespace oemcrypto_core_message */ + +#endif /* WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_DESERIALIZE_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/include/core_message_serialize.h b/libwvdrmengine/oemcrypto/odk/include/core_message_serialize.h new file mode 100644 index 00000000..ffa43f5b --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/include/core_message_serialize.h @@ -0,0 +1,68 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ + +/********************************************************************* + * core_message_serialize.h + * + * OEMCrypto v16 Core Message Serialization library counterpart (a.k.a. KDO) + * + * This file declares functions to serialize response messages that will be + * parsed by Widevine clients (OEMCrypto/ODK). + * + * Please refer to core_message_types.h for details. + * + *********************************************************************/ + +#ifndef WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_H_ +#define WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_H_ + +#include "core_message_types.h" +#include "odk_structs.h" + +namespace oemcrypto_core_message { +namespace serialize { + +/** + * Counterpart (serializer) of ODK_ParseLicense (deserializer) + * struct-input variant + * + * Parameters: + * [in] parsed_lic + * [in] core_request + * [in] core_request_sha256 + * [out] oemcrypto_core_message + */ +bool CreateCoreLicenseResponse(const ODK_ParsedLicense& parsed_lic, + const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, + std::string* oemcrypto_core_message); + +/** + * Counterpart (serializer) of ODK_ParseRenewal (deserializer) + * + * Parameters: + * [in] core_request + * [in] renewal_duration_seconds + * [out] oemcrypto_core_message + */ +bool CreateCoreRenewalResponse(const ODK_RenewalRequest& core_request, + uint64_t renewal_duration_seconds, + std::string* oemcrypto_core_message); + +/** + * Counterpart (serializer) of ODK_ParseProvisioning (deserializer) + * struct-input variant + * + * Parameters: + * [in] parsed_prov + * [in] core_request + * [out] oemcrypto_core_message + */ +bool CreateCoreProvisioningResponse(const ODK_ParsedProvisioning& parsed_prov, + const ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message); +} /* namespace serialize */ +} /* namespace oemcrypto_core_message */ + +#endif /* WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/include/core_message_serialize_proto.h b/libwvdrmengine/oemcrypto/odk/include/core_message_serialize_proto.h new file mode 100644 index 00000000..0f494de8 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/include/core_message_serialize_proto.h @@ -0,0 +1,62 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ + +/********************************************************************* + * core_message_serialize_proto.h + * + * These functions are an extension of those found in + * core_message_serialize.h. The difference is that these use the + * license and provisioning messages in protobuf format to create the core + * message. + *********************************************************************/ + +#ifndef WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_PROTO_H_ +#define WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_PROTO_H_ + +#include +#include + +#include "core_message_types.h" +#include "license_protocol.pb.h" + +namespace oemcrypto_core_message { +namespace serialize { + +/* @ public create response (serializer) functions accepting proto input */ + +/** + * Counterpart (serializer) of ODK_ParseLicense (deserializer) + * + * Parameters: + * [in] serialized_license + serialized video_widevine::License + * [in] core_request oemcrypto core message from request. + * [in] core_request_sha256 - hash of serialized core request. + * [in] nonce_required - if the device should require a nonce match. + * [out] oemcrypto_core_message - the serialized oemcrypto core response. + */ +bool CreateCoreLicenseResponseFromProto(const std::string& serialized_license, + const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, + const bool nonce_required, + std::string* oemcrypto_core_message); + +/** + * Counterpart (serializer) of ODK_ParseProvisioning (deserializer) + * + * Parameters: + * [in] serialized_provisioning_response + * serialized video_widevine::ProvisioningResponse + * [in] core_request + * [out] oemcrypto_core_message + */ +bool CreateCoreProvisioningResponseFromProto( + const std::string& serialized_provisioning_response, + const ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message); + +} /* namespace serialize */ +} /* namespace oemcrypto_core_message */ + +#endif /* WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_PROTO_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/include/core_message_types.h b/libwvdrmengine/oemcrypto/odk/include/core_message_types.h new file mode 100644 index 00000000..488e5d21 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/include/core_message_types.h @@ -0,0 +1,99 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ + +/* clang-format off */ +/********************************************************************* + * core_message_types.h + * + * OEMCrypto v16 Core Message Serialization library counterpart (a.k.a. KDO) + * + * For Widevine Modular DRM, there are six message types between a server and + * a client device: license request and response, provisioning request and + * response, and renewal request and response. + * + * In OEMCrypto v15 and earlier, messages from the server were parsed by the + * CDM layer above OEMCrypto; the CDM in turn gave OEMCrypto a collection of + * pointers to protected data within the message. However, the pointers + * themselves were not signed by the server. + * + * Starting from OEMCrypto v16, all fields used by OEMCrypto in each of these + * messages have been identified in the document "Widevine Core Message + * Serialization". These fields are called the core of the message. Core + * message fields are (de)serialized using the ODK, a C library provided by + * Widevine. OEMCrypto will parse and verify the core of the message with + * help from the ODK. + * + * The KDO library is the counterpart of ODK used in the CDM & Widevine + * servers. For each message type generated by the ODK, KDO provides a + * corresponding parser. For each message type to be parsed by the ODK, + * KDO provides a corresponding writer. + * + * Table: ODK vs KDO (s: serialize; d: deserialize) + * +----------------------------------------+---------------------------------------+ + * | ODK | KDO | + * +---+------------------------------------+---+-----------------------------------+ + * | s | ODK_PrepareCoreLicenseRequest | d | CoreLicenseRequestFromMessage | + * | +------------------------------------+ +-----------------------------------+ + * | | ODK_PrepareCoreRenewalRequest | | CoreRenewalRequestFromMessage | + * | +------------------------------------+ +-----------------------------------+ + * | | ODK_PrepareCoreProvisioningRequest | | CoreProvisioningRequestFromMessage| + * +---+------------------------------------+---+-----------------------------------+ + * | d | ODK_ParseLicense | s | CreateCoreLicenseResponse | + * | +------------------------------------+ +-----------------------------------+ + * | | ODK_ParseRenewal | | CreateCoreRenewalResponse | + * | +------------------------------------+ +-----------------------------------+ + * | | ODK_ParseProvisioning | | CreateCoreProvisioningResponse | + * +---+------------------------------------+---+-----------------------------------+ + * + *********************************************************************/ +/* clang-format on */ + +#ifndef WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_TYPES_H_ +#define WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_TYPES_H_ + +#include +#include + +namespace oemcrypto_core_message { + +/* @ input/output structs */ + +/** + * Output structure for CoreLicenseRequestFromMessage + * Input structure for CreateCoreLicenseResponse + */ +struct ODK_LicenseRequest { + uint16_t api_minor_version; + uint16_t api_major_version; + uint32_t nonce; + uint32_t session_id; +}; + +/** + * Output structure for CoreRenewalRequestFromMessage + * Input structure for CreateCoreRenewalResponse + */ +struct ODK_RenewalRequest { + uint16_t api_minor_version; + uint16_t api_major_version; + uint32_t nonce; + uint32_t session_id; + uint64_t playback_time_seconds; +}; + +/** + * Output structure for CoreProvisioningRequestFromMessage + * Input structure for CreateCoreProvisioningResponse + */ +struct ODK_ProvisioningRequest { + uint16_t api_minor_version; + uint16_t api_major_version; + uint32_t nonce; + uint32_t session_id; + std::string device_id; +}; + +} /* namespace oemcrypto_core_message */ + +#endif /* WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_TYPES_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/include/odk.h b/libwvdrmengine/oemcrypto/odk/include/odk.h index 20193aaf..6bbf36fb 100644 --- a/libwvdrmengine/oemcrypto/odk/include/odk.h +++ b/libwvdrmengine/oemcrypto/odk/include/odk.h @@ -1,8 +1,6 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ /********************************************************************* * odk.h @@ -44,10 +42,11 @@ * *********************************************************************/ -#ifndef ODK_H_ -#define ODK_H_ +#ifndef WIDEVINE_ODK_INCLUDE_ODK_H_ +#define WIDEVINE_ODK_INCLUDE_ODK_H_ #include + #include "OEMCryptoCENCCommon.h" #include "odk_structs.h" @@ -66,7 +65,7 @@ extern "C" { * [out] timer_limits: the session's timer limits. * [out] clock_values: the session's clock values. * [out] nonce_values: the session's ODK nonce values. - * [in] api_version: the API version of OEMCrypto. + * [in] api_major_version: the API version of OEMCrypto. * [in] session_id: the session id of the newly created session. * * Returns: @@ -79,7 +78,7 @@ extern "C" { OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, ODK_NonceValues* nonce_values, - uint32_t api_version, + uint32_t api_major_version, uint32_t session_id); /* @@ -128,7 +127,9 @@ OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values, * * Description: * This function sets the values in the clock_values structure. It shall be - * called from OEMCrypto_LoadUsageEntry. + * called from OEMCrypto_LoadUsageEntry. When a usage entry from a v15 or + * earlier license is loaded, the value time_of_license_loaded shall be used + * in place of time_of_license_signed. * * Parameters: * [in/out] clock_values: the session's clock data. @@ -232,7 +233,7 @@ OEMCryptoResult ODK_UpdateLastPlaybackTime(uint64_t system_time_seconds, * * Description: * This function modifies the session's clock values to indicate that the - * license has been deactiviated. It shall be called from + * license has been deactivated. It shall be called from * OEMCrypto_DeactivateUsageEntry * * Parameters: @@ -257,8 +258,8 @@ OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values); * * This shall be called by OEMCrypto from OEMCrypto_PrepAndSignLicenseRequest. * - * NOTE: if message pointer is null and/or input core_message_size is zero, - * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * NOTE: if the message pointer is null and/or input core_message_size is + * zero, this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output * core_message_size to the size needed. * * Parameters: @@ -272,7 +273,7 @@ OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values); * * Returns: * OEMCrypto_SUCCESS - * OEMCrypto_ERROR_SHORT_BUFFER if core_message_size is too small + * OEMCrypto_ERROR_SHORT_BUFFER: core_message_size is too small * OEMCrypto_ERROR_INVALID_CONTEXT * * Version: @@ -293,8 +294,14 @@ OEMCryptoResult ODK_PrepareCoreLicenseRequest( * * This shall be called by OEMCrypto from OEMCrypto_PrepAndSignRenewalRequest. * - * NOTE: if message pointer is null and/or input core_message_size is zero, - * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * If status in clock_values indicates that a license has not been loaded, + * then this is a license release. The ODK library will change the value of + * nonce_values.api_major_version to 15. This will make + * OEMCrypto_PrepAndSignRenewalRequest sign just the message body, as it does + * for all legacy licenses. + * + * NOTE: if the message pointer is null and/or input core_message_size is + * zero, this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output * core_message_size to the size needed. * * Parameters: @@ -304,23 +311,25 @@ OEMCryptoResult ODK_PrepareCoreLicenseRequest( * [in/out] core_message_size: length of the core message at the beginning of * the message. (in) size of buffer reserved for the core message, in * bytes. (out) actual length of the core message, in bytes. - * [in] nonce_values: pointer to the session's nonce data. - * [in] clock_values: the session's clock values. + * [in/out] nonce_values: pointer to the session's nonce data. + * [in/out] clock_values: the session's clock values. * [in] system_time_seconds: the current time on OEMCrypto's clock, in * seconds. * * Returns: * OEMCrypto_SUCCESS - * OEMCrypto_ERROR_SHORT_BUFFER if core_message_size is too small + * OEMCrypto_ERROR_SHORT_BUFFER: core_message_size is too small * OEMCrypto_ERROR_INVALID_CONTEXT * * Version: * This method is new in version 16 of the API. */ -OEMCryptoResult ODK_PrepareCoreRenewalRequest( - uint8_t* message, size_t message_length, size_t* core_message_size, - const ODK_NonceValues* nonce_values, const ODK_ClockValues* clock_values, - uint64_t system_time_seconds); +OEMCryptoResult ODK_PrepareCoreRenewalRequest(uint8_t* message, + size_t message_length, + size_t* core_message_size, + ODK_NonceValues* nonce_values, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds); /* * ODK_PrepareCoreProvisioningRequest @@ -337,8 +346,8 @@ OEMCryptoResult ODK_PrepareCoreRenewalRequest( * OEMCrypto_GetDeviceID. The device ID shall be unique to the device, and * stable across reboots and factory resets for an L1 device. * - * NOTE: if message pointer is null and/or input core_message_size is zero, - * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * NOTE: if the message pointer is null and/or input core_message_size is + * zero, this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output * core_message_size to the size needed. * * Parameters: @@ -357,7 +366,7 @@ OEMCryptoResult ODK_PrepareCoreRenewalRequest( * * Returns: * OEMCrypto_SUCCESS - * OEMCrypto_ERROR_SHORT_BUFFER if core_message_size is too small + * OEMCrypto_ERROR_SHORT_BUFFER: core_message_size is too small * OEMCrypto_ERROR_INVALID_CONTEXT * * Version: @@ -374,7 +383,7 @@ OEMCryptoResult ODK_PrepareCoreProvisioningRequest( * Description: * This function sets all limits in the timer_limits struct to the * key_duration and initializes the other values. The field - * nonce_values.api_level will be set to 15. It shall be called from + * nonce_values.api_major_version will be set to 15. It shall be called from * OEMCrypto_LoadKeys when loading a legacy license. * * Parameters: @@ -400,6 +409,48 @@ OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, uint32_t key_duration, uint64_t system_time_seconds); +/* + * ODK_RefreshV15Values + * + * Description: + * This function updates the clock_values as needed if a v15 renewal is + * accepted. The field nonce_values.api_major_version is verified to be 15. + * + * This is called from OEMCrypto_RefreshKeys for a valid license renewal. + * OEMCrypto shall pass in the current system time, and the key duration from + * the first object in the OEMCrypto_KeyRefreshObject. + * + * Parameters: + * [in] timer_limits: The session's timer limits. + * [in/out] clock_values: The session's clock values. + * [in] nonce_values: The session's ODK nonce values. + * [in] system_time_seconds: The current time on the system clock, as + * described in the document "License Duration and Renewal". + * [in] new_key_duration: The duration from the first + * OEMCrypto_KeyRefreshObject in key_array. + * [out] timer_value: set to the new timer value. Only used if the return + * value is ODK_SET_TIMER. This must be non-null if OEMCrypto uses a + * hardware timer. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * ODK_SET_TIMER: Success. The timer should be reset to the specified value + * and playback is allowed. + * ODK_DISABLE_TIMER: Success, but disable timer. Unlimited playback is + * allowed. + * ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. + * + * Version: + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + const ODK_NonceValues* nonce_values, + uint64_t system_time_seconds, + uint32_t new_key_duration, + uint64_t* timer_value); + /* * ODK_ParseLicense * @@ -411,7 +462,7 @@ OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, * return ODK_ERROR_CORE_MESSAGE that OEMCrypto should return to the CDM * layer above. * - * If the api in the message is not 16, then ODK_UNSUPPORTED_API is returned. + * If the API in the message is not 16, then ODK_UNSUPPORTED_API is returned. * * If initial_license_load is true, and nonce_required in the license is * true, then the ODK library shall verify that nonce_values->nonce and @@ -422,14 +473,15 @@ OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, * ODK_ParseLicense will set the values in nonce_values from those in the * message. * - * The function ODK_ParseLicense will verify each substring points to a + * The function ODK_ParseLicense will verify that each substring points to a * location in the message body. The message body is the buffer starting at * message + core_message_length with size message_length - * core_message_length. * * If initial_license_load is true, then ODK_ParseLicense shall verify that - * hash matches request_hash in the parsed license. If verification fails, - * then it shall return ODK_ERROR_CORE_MESSAGE. + * the parameter request_hash matches request_hash in the parsed license. If + * verification fails, then it shall return ODK_ERROR_CORE_MESSAGE. This was + * computed by OEMCrypto when the license was requested. * * If usage_entry_present is true, then ODK_ParseLicense shall verify that * the pst in the license has a nonzero length. @@ -443,6 +495,8 @@ OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, * false when called for OEMCrypto_ReloadLicense. * [in] usage_entry_present: true if the session has a new usage entry * associated with it created via OEMCrypto_CreateNewUsageEntry. + * [in] request_hash: the hash of the license request core message. This was + * computed by OEMCrypto when the license request was signed. * [in/out] timer_limits: The session's timer limits. These will be updated. * [in/out] clock_values: The session's clock values. These will be updated. * [in/out] nonce_values: The session's nonce values. These will be updated. @@ -450,7 +504,7 @@ OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, * * Returns: * OEMCrypto_SUCCESS - * ODK_ERROR_CORE_MESSAGE if the message did not parse correctly, or there + * ODK_ERROR_CORE_MESSAGE: if the message did not parse correctly, or there * were other incorrect values. An error should be returned to the CDM * layer. * ODK_UNSUPPORTED_API @@ -470,9 +524,9 @@ OEMCryptoResult ODK_ParseLicense( * ODK_ParseRenewal * * Description: - * The function ODK_ParseRenewal will parse the message and verify. If the - * message does not parse correctly, an error of ODK_ERROR_CORE_MESSAGE is - * returned. + * The function ODK_ParseRenewal will parse the message and verify its + * contents. If the message does not parse correctly, an error of + * ODK_ERROR_CORE_MESSAGE is returned. * * ODK_ParseRenewal shall verify that all fields in nonce_values match those * in the license. Otherwise it shall return OEMCrypto_ERROR_INVALID_NONCE. @@ -487,8 +541,8 @@ OEMCryptoResult ODK_ParseLicense( * ODK_DISABLE_TIMER, then playback time is not limited. * * If OEMCrypto uses a hardware timer, and this function returns - * ODK_SET_TIMER, then the timer should be set to the value pointed to by - * timer_value. + * ODK_SET_TIMER, then OEMCrypto shall set the timer to the value pointed to + * by timer_value. * * Parameters: * [in] message: pointer to the message buffer. @@ -505,15 +559,16 @@ OEMCryptoResult ODK_ParseLicense( * hardware timer. * * Returns: - * ODK_ERROR_CORE_MESSAGE if the message did not parse correctly, or there - * were other incorrect values. An error should be returned to the CDM - * layer. + * ODK_ERROR_CORE_MESSAGE: the message did not parse correctly, or there were + * other incorrect values. An error should be returned to the CDM layer. * ODK_SET_TIMER: Success. The timer should be reset to the specified timer * value. * ODK_DISABLE_TIMER: Success, but disable timer. Unlimited playback is * allowed. * ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. * ODK_UNSUPPORTED_API + * ODK_STALE_RENEWAL: This renewal is not the most recently signed. It is + * rejected. * OEMCrypto_ERROR_INVALID_NONCE * * Version: @@ -537,16 +592,16 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, * If the message does not parse correctly, ODK_ParseProvisioning will return * an error that OEMCrypto should return to the CDM layer above. * - * If the api in the message is larger than 16, then ODK_UNSUPPORTED_API is + * If the API in the message is larger than 16, then ODK_UNSUPPORTED_API is * returned. * * ODK_ParseProvisioning shall verify that nonce_values->nonce and * nonce_values->session_id are the same as those in the message. Otherwise * it shall return OEMCrypto_ERROR_INVALID_NONCE. * - * The function ODK_ParseProvisioning will verify each substring points to a - * location in the message body. The message body is the buffer starting at - * message + core_message_length with size message_length - + * The function ODK_ParseProvisioning will verify that each substring points + * to a location in the message body. The message body is the buffer starting + * at message + core_message_length with size message_length - * core_message_length. * * Parameters: @@ -562,9 +617,8 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, * * Returns: * OEMCrypto_SUCCESS - * ODK_ERROR_CORE_MESSAGE if the message did not parse correctly, or there - * were other incorrect values. An error should be returned to the CDM - * layer. + * ODK_ERROR_CORE_MESSAGE: the message did not parse correctly, or there were + * other incorrect values. An error should be returned to the CDM layer. * ODK_UNSUPPORTED_API * OEMCrypto_ERROR_INVALID_NONCE * @@ -580,4 +634,4 @@ OEMCryptoResult ODK_ParseProvisioning( } #endif -#endif // ODK_H_ +#endif /* WIDEVINE_ODK_INCLUDE_ODK_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/include/odk_assert.h b/libwvdrmengine/oemcrypto/odk/include/odk_assert.h deleted file mode 100644 index e1a21fd0..00000000 --- a/libwvdrmengine/oemcrypto/odk/include/odk_assert.h +++ /dev/null @@ -1,27 +0,0 @@ - -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -#ifndef ODK_ASSERT_H_ -#define ODK_ASSERT_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#if (__STDC_VERSION__ >= 201112L) -# include -# define odk_static_assert static_assert -#else -# define odk_static_assert(msg, e) \ - enum { odk_static_assert = 1 / (!!((msg) && (e))) }; -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* ODK_ASSERT_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/include/odk_serialize.h b/libwvdrmengine/oemcrypto/odk/include/odk_serialize.h deleted file mode 100644 index c9488d71..00000000 --- a/libwvdrmengine/oemcrypto/odk/include/odk_serialize.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -/* - * This code is auto-generated, do not edit - */ -#ifndef ODKITEE_SERIALIZER_H_ -#define ODKITEE_SERIALIZER_H_ - -#include "odk_structs_priv.h" -#include "serialization_base.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* odk pack */ -void Pack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense const* obj); -void Pack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage const* obj); -void Pack_ODK_ProvisioningMessage(Message* msg, - ODK_ProvisioningMessage const* obj); - -/* odk unpack */ -void Unpack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse* obj); -void Unpack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage* obj); -void Unpack_ODK_ProvisioningResponse(Message* msg, - ODK_ProvisioningResponse* obj); - -/* kdo pack */ -void Pack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse const* obj); -void Pack_ODK_ProvisioningResponse(Message* msg, - ODK_ProvisioningResponse const* obj); - -/* kdo unpack */ -void Unpack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense* obj); -void Unpack_ODK_ProvisioningMessage(Message* msg, ODK_ProvisioningMessage* obj); - -#ifdef __cplusplus -} // extern "C" -#endif -#endif /* ODKITEE_SERIALIZER_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/include/odk_structs.h b/libwvdrmengine/oemcrypto/odk/include/odk_structs.h index 52b01bd6..b24f7d9d 100644 --- a/libwvdrmengine/oemcrypto/odk/include/odk_structs.h +++ b/libwvdrmengine/oemcrypto/odk/include/odk_structs.h @@ -1,96 +1,215 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ -#ifndef ODK_STRUCTS_H_ -#define ODK_STRUCTS_H_ +#ifndef WIDEVINE_ODK_INCLUDE_ODK_STRUCTS_H_ +#define WIDEVINE_ODK_INCLUDE_ODK_STRUCTS_H_ #include -#include "OEMCryptoCENCCommon.h" -#define ODK_MAX_NUM_KEYS 32 +#include "OEMCryptoCENCCommon.h" +#include "odk_target.h" + +/* The version of this library. */ +#define ODK_MAJOR_VERSION 16 +#define ODK_MINOR_VERSION 2 + +/* Some useful constants. */ #define ODK_DEVICE_ID_LEN_MAX 64 #define ODK_SHA256_HASH_SIZE 32 /* - * ODK_TimerLimits is filled out by the function ODK_ParseLicense. + * ODK_TimerLimits Structure * - * The fields in this structure are defined in the core license response - * message. This structure should be kept as part of the session and used - * when calling the ODK timer functions described in the document "License - * Duration and Renewal" distributed as part of the OEMCrypto v16 design. + * Description: + * Timer limits are specified in a license and are used to determine when + * playback is allowed. See the document "License Duration and Renewal" for a + * discussion on the time restrictions that may be placed on a license. The + * fields in this structure are directly related to the fields in the core + * license message. The fields are set when OEMCrypto calls the function + * ODK_ParseLicense or ODK_InitializeV15Values. + * + * Fields: + * soft_enforce_rental_duration: A boolean controlling the soft or hard + * enforcement of rental duration. + * soft_enforce_playback_duration: A boolean controlling the soft or hard + * enforcement of playback duration. + * earliest_playback_start_seconds: The earliest time that the first playback + * is allowed. Measured in seconds since the license request was signed. For + * most use cases, this is zero. + * rental_duration_seconds: Window of time for the allowed first playback. + * Measured in seconds since the earliest playback start. If + * soft_enforce_rental_duration is true, this applies only to the first + * playback. If soft_enforce_rental_duration is false, then this restricts + * any playback. A value of zero means no limit. + * total_playback_duration_seconds: Window of time for allowed playback. + * Measured in seconds since the first playback start. If + * soft_enforce_playback_duration is true, this applies only to the start of + * playback for any session. If soft_enforce_playback_duration is false, then + * this restricts any playback. A value of zero means no limit. + * initial_renewal_duration_seconds: Window of time for allowed playback. + * Measured in seconds since the first playback start. This value is only + * used to start the renewal timer. After a renewal message is loaded, the + * timer will be reset. A value of zero means no limit. + * + * Version: + * This struct changed in API version 16.2. */ typedef struct { - uint32_t /*boolean*/ soft_expiry; - uint64_t earliest_playback_start_seconds; // seconds since license signed. - uint64_t latest_playback_start_seconds; // seconds since license signed. - uint64_t initial_playback_duration_seconds; // seconds since playback start. - uint64_t renewal_playback_duration_seconds; // seconds since renewal signed. - uint64_t license_duration_seconds; // seconds since license signed. + bool soft_enforce_rental_duration; + bool soft_enforce_playback_duration; + uint64_t earliest_playback_start_seconds; + uint64_t rental_duration_seconds; + uint64_t total_playback_duration_seconds; + uint64_t initial_renewal_duration_seconds; } ODK_TimerLimits; /* - * ODK_ParsedLicense holds fields from the core license response. - */ -typedef struct { - OEMCrypto_Substring enc_mac_keys_iv; - OEMCrypto_Substring enc_mac_keys; - OEMCrypto_Substring pst; - OEMCrypto_Substring srm_restriction_data; - uint32_t /* OEMCrypto_LicenseType */ license_type; - uint32_t nonce_required; - ODK_TimerLimits timer_limits; - uint8_t request_hash[ODK_SHA256_HASH_SIZE]; - uint32_t key_array_length; /* num_keys */ - OEMCrypto_KeyObject key_array[ODK_MAX_NUM_KEYS]; -} ODK_ParsedLicense; - -/* - * ODK_ParsedProvisioning holds fields from the core provisioning response. - */ -typedef struct { - uint32_t key_type; - OEMCrypto_Substring enc_private_key; - OEMCrypto_Substring enc_private_key_iv; - OEMCrypto_Substring encrypted_message_key; /* Used for Prov 3.0 */ -} ODK_ParsedProvisioning; - -/* - * ODK_ClockValues keeps information about a session's current clock values - * and timers. + * ODK_ClockValues Structure * - * Most of the fields in this structure are saved in the usage entry for each - * session. This structure should be initialized when a usage entry is - * created or loaded, and should be used to save a usage entry. It is - * updated using ODK functions listed in the document "License Duration and - * Renewal". The time values are based on OEMCrypto’s system clock. + * Description: + * Clock values are modified when decryption occurs or when a renewal is + * processed. They are used to track the current status of the license -- + * i.e. has playback started? When does the timer expire? See the section + * "Complete ODK API" of the document "Widevine Core Message Serialization" + * for a complete list of all fields in this structure. Most of these values + * shall be saved with the usage entry. + * + * All times are in seconds. Most of the fields in this structure are saved + * in the usage entry. This structure should be initialized when a usage + * entry is created or loaded, and should be used to save a usage entry. It + * is updated using the ODK functions listed below. The time values are based + * on OEMCrypto's system clock, as described in the document "License + * Duration and Renewal". + * + * Fields: + * time_of_license_signed: Time that the license request was signed, based on + * OEMCrypto's system clock. This value shall be stored and reloaded with + * usage entry as time_of_license_received. + * time_of_first_decrypt: Time of the first decrypt or call select key, based + * on OEMCrypto's system clock. This is 0 if the license has not been used to + * decrypt any data. This value shall be stored and reloaded with usage entry. + * time_of_last_decrypt: Time of the most recent decrypt call, based on + * OEMCrypto's system clock. This value shall be stored and reloaded with + * usage entry. + * time_of_renewal_request: Time of the most recent renewal request, based on + * OEMCrypto's system clock. This is used to verify that a renewal is not + * stale. + * time_when_timer_expires: Time that the current timer expires, based on + * OEMCrypto's system clock. If the timer is active, this is used by the ODK + * library to determine if it has expired. + * timer_status: Used internally by the ODK library to indicate the current + * timer status. + * status: The license or usage entry status. This value shall be stored and + * reloaded with usage entry. + * + * Version: + * This struct changed in API version 16.2. */ typedef struct { uint64_t time_of_license_signed; uint64_t time_of_first_decrypt; uint64_t time_of_last_decrypt; + uint64_t time_of_renewal_request; uint64_t time_when_timer_expires; uint32_t timer_status; enum OEMCrypto_Usage_Entry_Status status; } ODK_ClockValues; /* - * ODK_NonceValues are used to match a license or provisioning request to a - * license or provisioning response. For this reason, the api_version might be - * lower than that supported by OEMCrypto. The api_version matches the version - * of the license. Similarly the nonce and session_id match the session that - * generated the license request. For an offline license, these might not match - * the session that is loading the license. We use the nonce to prevent a - * license from being replayed. By also including a session_id in the license - * request and license response, we prevent an attack using the birthday paradox - * to generate nonce collisions on a single device. + * ODK_NonceValues Structure + * + * Description: + * Nonce values are used to match a license or provisioning request to a + * license or provisioning response. They are also used to match a renewal + * request and response to a license. For this reason, the api_version might + * be lower than that supported by OEMCrypto. The api_version matches the + * version of the license. Similarly the nonce and session_id match the + * session that generated the license request. For an offline license, these + * might not match the session that is loading the license. We use the nonce + * to prevent a license from being replayed. By also including a session_id + * in the license request and license response, we prevent an attack using + * the birthday paradox to generate nonce collisions on a single device. + * + * Fields: + * api_major_version: the API version of the license. This is initialized to + * the API version of the ODK library, but may be lower. + * api_minor_version: the minor version of the ODK library. This is used by + * the server to verify that device is not using an obsolete version of the + * ODK library. + * nonce: a randomly generated number used to prevent replay attacks. + * session_id: the session id of the session which signed the license or + * provisioning request. It is used to prevent replay attacks from one + * session to another. + * + * Version: + * This struct changed in API version 16.2. */ typedef struct { - uint32_t api_version; + uint16_t api_minor_version; + uint16_t api_major_version; uint32_t nonce; uint32_t session_id; } ODK_NonceValues; -#endif // ODK_STRUCTS_H_ +/* + * ODK_ParsedLicense Structure + * + * Description: + * The parsed license structure contains information from the license + * message. The function ODK_ParseLicense will fill in the fields of this + * message. All substrings are contained within the message body. + * + * Fields: + * enc_mac_keys_iv: IV for decrypting new mac_key. Size is 128 bits. + * enc_mac_keys: encrypted mac_keys for generating new mac_keys. Size is 512 + * bits. + * pst: the Provider Session Token. + * srm_restriction_data: optional data specifying the minimum SRM version. + * license_type: specifies if the license contains content keys or + * entitlement keys. + * nonce_required: indicates if the license requires a nonce. + * timer_limits: time limits of the for the license. + * key_array_length: number of keys present. + * key_array: set of keys to be installed. + * + * Version: + * This struct changed in API version 16.2. + */ +typedef struct { + OEMCrypto_Substring enc_mac_keys_iv; + OEMCrypto_Substring enc_mac_keys; + OEMCrypto_Substring pst; + OEMCrypto_Substring srm_restriction_data; + OEMCrypto_LicenseType license_type; + bool nonce_required; + ODK_TimerLimits timer_limits; + uint32_t key_array_length; + OEMCrypto_KeyObject key_array[ODK_MAX_NUM_KEYS]; +} ODK_ParsedLicense; + +/* + * ODK_ParsedProvisioning Structure + * + * Description: + * The parsed provisioning structure contains information from the license + * message. The function ODK_ParseProvisioning will fill in the fields of + * this message. All substrings are contained within the message body. + * + * Fields: + * key_type: indicates if this key is an RSA or ECC private key. + * enc_private_key: encrypted private key for the DRM certificate. + * enc_private_key_iv: IV for decrypting new private key. Size is 128 bits. + * encrypted_message_key: used for provisioning 3.0 to derive keys. + * + * Version: + * This struct changed in API version 16.2. + */ +typedef struct { + OEMCrypto_PrivateKeyType key_type; + OEMCrypto_Substring enc_private_key; + OEMCrypto_Substring enc_private_key_iv; + OEMCrypto_Substring encrypted_message_key; /* Used for Prov 3.0 */ +} ODK_ParsedProvisioning; + +#endif /* WIDEVINE_ODK_INCLUDE_ODK_STRUCTS_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/include/odk_structs_priv.h b/libwvdrmengine/oemcrypto/odk/include/odk_structs_priv.h deleted file mode 100644 index 8b3ee35a..00000000 --- a/libwvdrmengine/oemcrypto/odk/include/odk_structs_priv.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -#ifndef ODK_STRUCTS_PRIV_H_ -#define ODK_STRUCTS_PRIV_H_ - -#include -#include "OEMCryptoCENCCommon.h" -#include "odk_structs.h" - -typedef enum { - ODK_License_Request_Type = 1, - ODK_License_Response_Type = 2, - ODK_Renewal_Request_Type = 3, - ODK_Renewal_Response_Type = 4, - ODK_Provisioning_Request_Type = 5, - ODK_Provisioning_Response_Type = 6, -} ODK_MessageType; - -typedef struct { - uint32_t message_type; - uint32_t message_length; - ODK_NonceValues nonce_values; -} ODK_CoreMessage; - -typedef struct { - ODK_CoreMessage core_message; -} ODK_PreparedLicense; - -typedef struct { - ODK_CoreMessage core_message; - uint64_t playback_time; -} ODK_RenewalMessage; - -typedef struct { - ODK_CoreMessage core_message; - uint32_t device_id_length; - uint8_t device_id[ODK_DEVICE_ID_LEN_MAX]; -} ODK_ProvisioningMessage; - -typedef struct { - ODK_CoreMessage core_message; - ODK_ParsedLicense* parsed_license; -} ODK_LicenseResponse; - -typedef struct { - ODK_ProvisioningMessage core_provisioning; - ODK_ParsedProvisioning* parsed_provisioning; -} ODK_ProvisioningResponse; - -#endif // ODK_STRUCTS_PRIV_H_ diff --git a/libwvdrmengine/oemcrypto/odk/include/odk_target.h b/libwvdrmengine/oemcrypto/odk/include/odk_target.h new file mode 100644 index 00000000..92252103 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/include/odk_target.h @@ -0,0 +1,13 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file is distributed */ +/* under the Widevine Master License Agreement. */ + +/* Partners are expected to edit this file to support target specific code */ +/* and limits. */ + +#ifndef WIDEVINE_ODK_INCLUDE_ODK_TARGET_H_ +#define WIDEVINE_ODK_INCLUDE_ODK_TARGET_H_ + +/* Maximum number of keys can be modified to suit target's resource tier. */ +#define ODK_MAX_NUM_KEYS 32 + +#endif /* WIDEVINE_ODK_INCLUDE_ODK_TARGET_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/kdo/include/oec_util.h b/libwvdrmengine/oemcrypto/odk/kdo/include/oec_util.h deleted file mode 100644 index 20bee97b..00000000 --- a/libwvdrmengine/oemcrypto/odk/kdo/include/oec_util.h +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -// clang-format off -/********************************************************************* - * oec_util.h - * - * OEMCrypto v16 Core Message Serialization library counterpart (a.k.a. KDO) - * - * For Widevine Modular DRM, there are six message types between a server and - * a client device: license request and response, provisioning request and - * response, and renewal request and response. - * - * In OEMCrypto v15 and earlier, messages from the server were parsed by the - * CDM layer above OEMCrypto; the CDM in turn gave OEMCrypto a collection of - * pointers to protected data within the message. However, the pointers - * themselves were not signed by the server. - * - * Starting from OEMCrypto v16, all fields used by OEMCrypto in each of these - * messages have been identified in the document "Widevine Core Message - * Serialization". These fields are called the core of the message. Core - * message fields are (de)serialized using the ODK, a C library provided by - * Widevine. OEMCrypto will parse and verify the core of the message with - * help from the ODK. - * - * The KDO library is the counterpart of ODK used in the CDM & Widevine - * servers. For each message type generated by the ODK, KDO provides a - * corresponding parser. For each message type to be parsed by the ODK, - * KDO provides a corresponding writer. - * - * Table: ODK vs KDO (s: serialize; d: deserialize) - * +----------------------------------------+------------------------------------+ - * | ODK | KDO | - * +---+------------------------------------+---+--------------------------------+ - * | s | ODK_PrepareCoreLicenseRequest | d | ParseLicenseRequest | - * | +------------------------------------+ +--------------------------------+ - * | | ODK_PrepareCoreRenewalRequest | | ParseRenewalRequest | - * | +------------------------------------+ +--------------------------------+ - * | | ODK_PrepareCoreProvisioningRequest | | ParseProvisioningRequest | - * +---+------------------------------------+---+--------------------------------+ - * | d | ODK_ParseLicense | s | CreateCoreLicenseResponse | - * | +------------------------------------+ +--------------------------------+ - * | | ODK_ParseRenewal | | CreateCoreRenewalResponse | - * | +------------------------------------+ +--------------------------------+ - * | | ODK_ParseProvisioning | | CreateCoreProvisioningResponse | - * +---+------------------------------------+---+--------------------------------+ - * - *********************************************************************/ -// clang-format on - -#ifndef OEC_UTIL_H_ -#define OEC_UTIL_H_ - -#include -#include - -#include "odk_structs.h" - -using namespace std; - -namespace oec_util { - -// @ input/output structs - -/** - * Output structure for ParseLicenseRequest - * Input structure for CreateCoreLicenseResponse - */ -struct ODK_LicenseRequest { - uint32_t api_version; - uint32_t nonce; - uint32_t session_id; -}; - -/** - * Output structure for ParseRenewalRequest - * Input structure for CreateCoreRenewalResponse - */ -struct ODK_RenewalRequest { - uint32_t api_version; - uint32_t nonce; - uint32_t session_id; - uint64_t playback_time; -}; - -/** - * Output structure for ParseProvisioningRequest - * Input structure for CreateCoreProvisioningResponse - */ -struct ODK_ProvisioningRequest { - uint32_t api_version; - uint32_t nonce; - uint32_t session_id; - string device_id; -}; - -// @ public parse request (deserializer) functions - -/** - * Counterpart (deserializer) of ODK_PrepareCoreLicenseRequest (serializer) - * - * Parameters: - * [in] oemcrypto_core_message - * [out] core_license_request - */ -bool ParseLicenseRequest(const string& oemcrypto_core_message, - ODK_LicenseRequest* core_license_request); - -/** - * Counterpart (deserializer) of ODK_PrepareCoreRenewalRequest (serializer) - * - * Parameters: - * [in] oemcrypto_core_message - * [out] core_renewal_request - */ -bool ParseRenewalRequest(const string& oemcrypto_core_message, - ODK_RenewalRequest* core_renewal_request); - -/** - * Counterpart (deserializer) of ODK_PrepareCoreProvisioningRequest (serializer) - * - * Parameters: - * [in] oemcrypto_core_message - * [out] core_provisioning_request - */ -bool ParseProvisioningRequest( - const string& oemcrypto_core_message, - ODK_ProvisioningRequest* core_provisioning_request); - -// @ public create response (serializer) functions - -/** - * Counterpart (serializer) of ODK_ParseLicense (deserializer) - * struct-input variant - * - * Parameters: - * [in] parsed_lic - * [in] core_request - * [out] oemcrypto_core_message - */ -bool CreateCoreLicenseResponse(const ODK_ParsedLicense& parsed_lic, - const ODK_LicenseRequest& core_request, - string* oemcrypto_core_message); - -/** - * Counterpart (serializer) of ODK_ParseRenewal (deserializer) - * - * Parameters: - * [in] core_request - * [out] oemcrypto_core_message - */ -bool CreateCoreRenewalResponse(const ODK_RenewalRequest& core_request, - string* oemcrypto_core_message); - -/** - * Counterpart (serializer) of ODK_ParseProvisioning (deserializer) - * struct-input variant - * - * Parameters: - * [in] parsed_prov - * [in] core_request - * [out] oemcrypto_core_message - */ -bool CreateCoreProvisioningResponse(const ODK_ParsedProvisioning& parsed_prov, - const ODK_ProvisioningRequest& core_request, - string* oemcrypto_core_message); -} // namespace oec_util - -#endif // OEC_UTIL_H_ diff --git a/libwvdrmengine/oemcrypto/odk/kdo/include/oec_util_proto.h b/libwvdrmengine/oemcrypto/odk/kdo/include/oec_util_proto.h deleted file mode 100644 index f9d8017a..00000000 --- a/libwvdrmengine/oemcrypto/odk/kdo/include/oec_util_proto.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -/********************************************************************* - * oec_util_proto.h - * - * These functions are an extension of those found in oec_util.h. The - * difference is that these use the license and provisioning messages - * in protobuf format to create the core message. - *********************************************************************/ - -#ifndef OEC_UTIL_PROTO_H_ -#define OEC_UTIL_PROTO_H_ - -#include -#include - -#include "license_protocol.pb.h" -#include "oec_util.h" - -using namespace std; -using video_widevine::License; -using video_widevine::License_KeyContainer; - -namespace oec_util { - -// @ public create response (serializer) functions - -/** - * Counterpart (serializer) of ODK_ParseLicense (deserializer) - * - * Parameters: - * [in] license - * [in] core_request - * [out] oemcrypto_core_message - */ -bool CreateCoreLicenseResponse(const video_widevine::License& license, - const ODK_LicenseRequest& core_request, - string* oemcrypto_core_message); - -/** - * Counterpart (serializer) of ODK_ParseProvisioning (deserializer) - * - * Parameters: - * [in] provisioning_response - * [in] core_request - * [out] oemcrypto_core_message - */ -bool CreateCoreProvisioningResponse( - const video_widevine::ProvisioningResponse& provisioning_response, - const ODK_ProvisioningRequest& core_request, - string* oemcrypto_core_message); - -} // namespace oec_util - -#endif // OEC_UTIL_PROTO_H_ diff --git a/libwvdrmengine/oemcrypto/odk/kdo/src/oec_util.cpp b/libwvdrmengine/oemcrypto/odk/kdo/src/oec_util.cpp deleted file mode 100644 index affd9792..00000000 --- a/libwvdrmengine/oemcrypto/odk/kdo/src/oec_util.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -#include "oec_util.h" - -#include -#include -#include -#include -#include -#include - -#include "odk_overflow.h" -#include "odk_serialize.h" -#include "odk_structs.h" -#include "odk_structs_priv.h" -#include "oemcrypto_types.h" -#include "serialization_base.h" - -using namespace oec_util; - -namespace oec_util { - -namespace { - -/* @ private functions */ - -const int CURRENT_OEC_VERSION = 16; - -/** - * Template for parsing requests - * - * Template arguments: - * S: kdo output struct - * T: struct serialized by odk - * U: auto-generated deserializing function for |T| - */ -template -bool ParseRequest(uint32_t message_type, const string& oemcrypto_core_message, - S* core_request, T* prepared, const U unpacker) { - if (!core_request) { - return false; - } - - const uint8_t* buf = - reinterpret_cast(oemcrypto_core_message.c_str()); - size_t buf_length = oemcrypto_core_message.size(); - - Message* msg = NULL; - AllocateMessage(&msg, message_block); - InitMessage(msg, const_cast(buf), buf_length); - SetSize(msg, buf_length); - - unpacker(msg, prepared); - if (!ValidMessage(msg)) { - return false; - } - - const auto& core_message = prepared->core_message; - core_request->api_version = core_message.nonce_values.api_version; - core_request->nonce = core_message.nonce_values.nonce; - core_request->session_id = core_message.nonce_values.session_id; - return core_message.message_type == message_type && - core_message.message_length == GetOffset(msg) && - core_request->api_version == CURRENT_OEC_VERSION; -} - -/** - * Template for parsing requests - * - * Template arguments: - * T: struct to be deserialized by odk - * S: kdo input struct - * P: auto-generated serializing function for |T| - */ -template -bool CreateResponse(uint32_t message_type, const S& core_request, - string* oemcrypto_core_message, T& response, - const P& packer) { - if (!oemcrypto_core_message) { - return false; - } - - auto* header = reinterpret_cast(&response); - header->message_type = message_type; - header->nonce_values.api_version = core_request.api_version; - header->nonce_values.nonce = core_request.nonce; - header->nonce_values.session_id = core_request.session_id; - - uint8_t buf[2048] = {0}; - Message* msg = NULL; - AllocateMessage(&msg, message_block); - InitMessage(msg, buf, sizeof(buf)); - packer(msg, &response); - if (!ValidMessage(msg)) { - return false; - } - - uint32_t message_length = GetSize(msg); - InitMessage(msg, buf + sizeof(header->message_type), - sizeof(header->message_length)); - Pack_uint32_t(msg, &message_length); - oemcrypto_core_message->assign(reinterpret_cast(buf), - message_length); - return true; -} - -bool CopyDeviceId(ODK_ProvisioningResponse& dest, - const ODK_ProvisioningRequest& src) { - auto& core_provisioning = dest.core_provisioning; - const string& device_id = src.device_id; - core_provisioning.device_id_length = device_id.size(); - if (core_provisioning.device_id_length > - sizeof(core_provisioning.device_id)) { - return false; - } - memset(core_provisioning.device_id, 0, sizeof(core_provisioning.device_id)); - memcpy(core_provisioning.device_id, device_id.data(), - core_provisioning.device_id_length); - return true; -} - -} // namespace - -// @ public parse request (deserializer) functions - -bool ParseLicenseRequest(const string& oemcrypto_core_message, - ODK_LicenseRequest* core_license_request) { - const auto unpacker = Unpack_ODK_PreparedLicense; - ODK_PreparedLicense prepared_license = {}; - return ParseRequest(ODK_License_Request_Type, oemcrypto_core_message, - core_license_request, &prepared_license, unpacker); -} - -bool ParseRenewalRequest(const string& oemcrypto_core_message, - ODK_RenewalRequest* core_renewal_request) { - const auto unpacker = Unpack_ODK_RenewalMessage; - ODK_RenewalMessage prepared_renewal = {}; - if (!ParseRequest(ODK_Renewal_Request_Type, oemcrypto_core_message, - core_renewal_request, &prepared_renewal, unpacker)) { - return false; - } - core_renewal_request->playback_time = prepared_renewal.playback_time; - return true; -} - -bool ParseProvisioningRequest( - const string& oemcrypto_core_message, - ODK_ProvisioningRequest* core_provisioning_request) { - const auto unpacker = Unpack_ODK_ProvisioningMessage; - ODK_ProvisioningMessage prepared_provision = {}; - if (!ParseRequest(ODK_Provisioning_Request_Type, oemcrypto_core_message, - core_provisioning_request, &prepared_provision, unpacker)) { - return false; - } - const uint8_t* device_id = prepared_provision.device_id; - const uint32_t device_id_length = prepared_provision.device_id_length; - if (device_id_length > ODK_DEVICE_ID_LEN_MAX) { - return false; - } - uint8_t zero[ODK_DEVICE_ID_LEN_MAX] = {}; - if (memcmp(zero, device_id + device_id_length, - ODK_DEVICE_ID_LEN_MAX - device_id_length)) { - return false; - } - core_provisioning_request->device_id.assign( - reinterpret_cast(device_id), device_id_length); - return true; -} - -// @ public create response functions - -bool CreateCoreLicenseResponse(const ODK_ParsedLicense& parsed_lic, - const ODK_LicenseRequest& core_request, - string* oemcrypto_core_message) { - ODK_LicenseResponse license_response{ - {}, const_cast(&parsed_lic)}; - return CreateResponse(ODK_License_Response_Type, core_request, - oemcrypto_core_message, license_response, - Pack_ODK_LicenseResponse); -} - -bool CreateCoreRenewalResponse(const ODK_RenewalRequest& core_request, - string* oemcrypto_core_message) { - ODK_RenewalMessage renewal{{}, core_request.playback_time}; - renewal.playback_time = core_request.playback_time; - return CreateResponse(ODK_Renewal_Response_Type, core_request, - oemcrypto_core_message, renewal, - Pack_ODK_RenewalMessage); -} - -bool CreateCoreProvisioningResponse(const ODK_ParsedProvisioning& parsed_prov, - const ODK_ProvisioningRequest& core_request, - string* oemcrypto_core_message) { - ODK_ProvisioningResponse prov_response{ - {}, const_cast(&parsed_prov)}; - if (!CopyDeviceId(prov_response, core_request)) { - return false; - } - - return CreateResponse(ODK_Provisioning_Response_Type, core_request, - oemcrypto_core_message, prov_response, - Pack_ODK_ProvisioningResponse); -} - -} // namespace oec_util diff --git a/libwvdrmengine/oemcrypto/odk/kdo/src/oec_util_proto.cpp b/libwvdrmengine/oemcrypto/odk/kdo/src/oec_util_proto.cpp deleted file mode 100644 index 02474d2b..00000000 --- a/libwvdrmengine/oemcrypto/odk/kdo/src/oec_util_proto.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -#include "oec_util_proto.h" - -#include -#include -#include -#include -#include -#include - -#include "odk_overflow.h" -#include "odk_serialize.h" -#include "odk_structs.h" -#include "odk_structs_priv.h" -#include "oemcrypto_types.h" -#include "serialization_base.h" - -using namespace oec_util; - -namespace oec_util { - -namespace { - -/* @ private functions */ - -/** - * Extract OEMCrypto_Substring (offset, length) from serialized protobuf - * - * Parameters: - * message: serialized license protobuf - * field: substring value - */ -OEMCrypto_Substring GetOecSubstring(const std::string& message, - const std::string& field) { - OEMCrypto_Substring substring = {}; - size_t pos = message.find(field); - if (pos != std::string::npos) { - substring = OEMCrypto_Substring{pos, field.length()}; - } - return substring; -} - -OEMCrypto_KeyObject KeyContainerToOecKey(const string& proto, - const License::KeyContainer& k) { - OEMCrypto_KeyObject obj = {}; - obj.key_id = GetOecSubstring(proto, k.id()); - obj.key_data_iv = GetOecSubstring(proto, k.iv()); - // Strip off PKCS#5 padding - since we know the key is 16 or 32 bytes, - // the padding will always be 16 bytes. - const string& key_data = k.key(); - const size_t PKCS5_PADDING_SIZE = 16; - obj.key_data = GetOecSubstring( - proto, key_data.substr(0, std::max(PKCS5_PADDING_SIZE, key_data.size()) - - PKCS5_PADDING_SIZE)); - if (k.has_key_control()) { - const auto& key_control = k.key_control(); - obj.key_control_iv = GetOecSubstring(proto, key_control.iv()); - obj.key_control = GetOecSubstring(proto, key_control.key_control_block()); - } - return obj; -} - -} // namespace - -// @ public create response functions - -bool CreateCoreLicenseResponse(const video_widevine::License& lic, - const ODK_LicenseRequest& core_request, - string* oemcrypto_core_message) { - string proto; - if (!lic.SerializeToString(&proto)) { - return false; - } - ODK_ParsedLicense parsed_lic{}; - - for (int i = 0; i < lic.key_size(); ++i) { - const auto& k = lic.key(i); - switch (k.type()) { - case License_KeyContainer::SIGNING: { - parsed_lic.enc_mac_keys_iv = GetOecSubstring(proto, k.iv()); - // Strip off PKCS#5 padding - string mac_keys(k.key(), 2 * wvoec::MAC_KEY_SIZE); - parsed_lic.enc_mac_keys = GetOecSubstring(proto, mac_keys); - break; - } - case License_KeyContainer::CONTENT: { - if (parsed_lic.key_array_length >= ODK_MAX_NUM_KEYS) { - return false; - } - uint32_t& n = parsed_lic.key_array_length; - parsed_lic.key_array[n++] = KeyContainerToOecKey(proto, k); - break; - } - default: { - continue; - } - } - } - - const auto& license_id = lic.id(); - if (license_id.has_provider_session_token()) { - parsed_lic.pst = - GetOecSubstring(proto, license_id.provider_session_token()); - } - - if (lic.has_srm_requirement()) { - parsed_lic.srm_restriction_data = - GetOecSubstring(proto, lic.srm_requirement()); - } - - parsed_lic.license_type = license_id.type(); - // todo: nonce_required - const auto& policy = lic.policy(); - ODK_TimerLimits& timer_limits = parsed_lic.timer_limits; - timer_limits.soft_expiry = policy.soft_enforce_playback_duration(); - timer_limits.earliest_playback_start_seconds = 0; - timer_limits.latest_playback_start_seconds = - policy.license_duration_seconds(); - timer_limits.initial_playback_duration_seconds = - policy.playback_duration_seconds(); - timer_limits.renewal_playback_duration_seconds = - policy.playback_duration_seconds(); - timer_limits.license_duration_seconds = policy.license_duration_seconds(); - - return CreateCoreLicenseResponse(parsed_lic, core_request, - oemcrypto_core_message); -} - -bool CreateCoreProvisioningResponse( - const video_widevine::ProvisioningResponse& prov, - const ODK_ProvisioningRequest& core_request, - string* oemcrypto_core_message) { - ODK_ParsedProvisioning parsed_prov{}; - string proto; - if (!prov.SerializeToString(&proto)) { - return false; - } - - parsed_prov.key_type = 0; // todo: ECC or RSA - if (prov.has_device_rsa_key()) { - parsed_prov.enc_private_key = GetOecSubstring(proto, prov.device_rsa_key()); - } - if (prov.has_device_rsa_key_iv()) { - parsed_prov.enc_private_key_iv = - GetOecSubstring(proto, prov.device_rsa_key_iv()); - } - if (prov.has_wrapping_key()) { - parsed_prov.encrypted_message_key = - GetOecSubstring(proto, prov.wrapping_key()); - } - - return CreateCoreProvisioningResponse(parsed_prov, core_request, - oemcrypto_core_message); -} - -} // namespace oec_util diff --git a/libwvdrmengine/oemcrypto/odk/src/core_message_deserialize.cpp b/libwvdrmengine/oemcrypto/odk/src/core_message_deserialize.cpp new file mode 100644 index 00000000..f2e909b5 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/src/core_message_deserialize.cpp @@ -0,0 +1,126 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "core_message_deserialize.h" + +#include +#include +#include +#include +#include + +#include "odk_serialize.h" +#include "odk_structs.h" +#include "odk_structs_priv.h" +#include "serialization_base.h" + +namespace oemcrypto_core_message { +namespace deserialize { +namespace { + +constexpr int EARLIEST_OEMCRYPTO_VERSION_WITH_ODK = 16; +constexpr int LATEST_OEMCRYPTO_VERSION = 16; + +/** + * Template for parsing requests + * + * Template arguments: + * S: kdo output struct + * T: struct serialized by odk + * U: auto-generated deserializing function for |T| + */ +template +bool ParseRequest(uint32_t message_type, + const std::string& oemcrypto_core_message, S* core_request, + T* prepared, const U unpacker) { + if (core_request == nullptr || prepared == nullptr) { + return false; + } + + const uint8_t* buf = + reinterpret_cast(oemcrypto_core_message.c_str()); + const size_t buf_length = oemcrypto_core_message.size(); + + Message* msg = nullptr; + AllocateMessage(&msg, message_block); + InitMessage(msg, const_cast(buf), buf_length); + SetSize(msg, buf_length); + + unpacker(msg, prepared); + if (!ValidMessage(msg)) { + return false; + } + + const auto& core_message = prepared->core_message; + core_request->api_major_version = core_message.nonce_values.api_major_version; + core_request->api_minor_version = core_message.nonce_values.api_minor_version; + core_request->nonce = core_message.nonce_values.nonce; + core_request->session_id = core_message.nonce_values.session_id; + // Verify that the minor version matches the released version for the given + // major version. + if ((core_request->api_major_version < EARLIEST_OEMCRYPTO_VERSION_WITH_ODK) || + (core_request->api_major_version > LATEST_OEMCRYPTO_VERSION)) { + // Non existing and future versions are not supported. + return false; + } else if (core_request->api_major_version == 16) { + // For version 16, we demand a minor version of at least 2. + if (core_request->api_major_version < 2) return false; + } else { + // Other versions do not (yet) have a restriction on minor number. + } + return core_message.message_type == message_type && + core_message.message_length == GetOffset(msg) && + core_request->api_major_version >= + EARLIEST_OEMCRYPTO_VERSION_WITH_ODK && + core_request->api_major_version <= LATEST_OEMCRYPTO_VERSION; +} + +} // namespace + +bool CoreLicenseRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_LicenseRequest* core_license_request) { + const auto unpacker = Unpack_ODK_PreparedLicenseRequest; + ODK_PreparedLicenseRequest prepared_license = {}; + return ParseRequest(ODK_License_Request_Type, oemcrypto_core_message, + core_license_request, &prepared_license, unpacker); +} + +bool CoreRenewalRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_RenewalRequest* core_renewal_request) { + const auto unpacker = Unpack_ODK_PreparedRenewalRequest; + ODK_PreparedRenewalRequest prepared_renewal = {}; + if (!ParseRequest(ODK_Renewal_Request_Type, oemcrypto_core_message, + core_renewal_request, &prepared_renewal, unpacker)) { + return false; + } + core_renewal_request->playback_time_seconds = prepared_renewal.playback_time; + return true; +} + +bool CoreProvisioningRequestFromMessage( + const std::string& oemcrypto_core_message, + ODK_ProvisioningRequest* core_provisioning_request) { + const auto unpacker = Unpack_ODK_PreparedProvisioningRequest; + ODK_PreparedProvisioningRequest prepared_provision = {}; + if (!ParseRequest(ODK_Provisioning_Request_Type, oemcrypto_core_message, + core_provisioning_request, &prepared_provision, unpacker)) { + return false; + } + const uint8_t* device_id = prepared_provision.device_id; + const uint32_t device_id_length = prepared_provision.device_id_length; + if (device_id_length > ODK_DEVICE_ID_LEN_MAX) { + return false; + } + uint8_t zero[ODK_DEVICE_ID_LEN_MAX] = {}; + if (memcmp(zero, device_id + device_id_length, + ODK_DEVICE_ID_LEN_MAX - device_id_length)) { + return false; + } + core_provisioning_request->device_id.assign( + reinterpret_cast(device_id), device_id_length); + return true; +} + +} // namespace deserialize +} // namespace oemcrypto_core_message diff --git a/libwvdrmengine/oemcrypto/odk/src/core_message_serialize.cpp b/libwvdrmengine/oemcrypto/odk/src/core_message_serialize.cpp new file mode 100644 index 00000000..1ce827e4 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/src/core_message_serialize.cpp @@ -0,0 +1,119 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "core_message_serialize.h" + +#include +#include +#include +#include +#include + +#include "odk_serialize.h" +#include "odk_structs.h" +#include "odk_structs_priv.h" +#include "serialization_base.h" + +namespace oemcrypto_core_message { +namespace serialize { +namespace { + +/** + * Template for parsing requests + * + * Template arguments: + * T: struct to be deserialized by odk + * S: kdo input struct + * P: auto-generated serializing function for |T| + */ +template +bool CreateResponse(uint32_t message_type, const S& core_request, + std::string* oemcrypto_core_message, T& response, + const P& packer) { + if (!oemcrypto_core_message) { + return false; + } + + auto* header = reinterpret_cast(&response); + header->message_type = message_type; + header->nonce_values.api_major_version = core_request.api_major_version; + header->nonce_values.api_minor_version = core_request.api_minor_version; + header->nonce_values.nonce = core_request.nonce; + header->nonce_values.session_id = core_request.session_id; + + static constexpr size_t BUF_CAPACITY = 2048; + std::vector buf(BUF_CAPACITY, 0); + Message* msg = nullptr; + AllocateMessage(&msg, message_block); + InitMessage(msg, buf.data(), buf.capacity()); + packer(msg, &response); + if (!ValidMessage(msg)) { + return false; + } + + uint32_t message_length = GetSize(msg); + InitMessage(msg, buf.data() + sizeof(header->message_type), + sizeof(header->message_length)); + Pack_uint32_t(msg, &message_length); + oemcrypto_core_message->assign(reinterpret_cast(buf.data()), + message_length); + return true; +} + +bool CopyDeviceId(const ODK_ProvisioningRequest& src, + ODK_ProvisioningResponse* dest) { + auto& request = dest->request; + const std::string& device_id = src.device_id; + if (request.device_id_length > sizeof(request.device_id)) { + return false; + } + request.device_id_length = device_id.size(); + memset(request.device_id, 0, sizeof(request.device_id)); + memcpy(request.device_id, device_id.data(), request.device_id_length); + return true; +} + +} // namespace + +bool CreateCoreLicenseResponse(const ODK_ParsedLicense& parsed_lic, + const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, + std::string* oemcrypto_core_message) { + ODK_LicenseResponse license_response{ + {}, const_cast(&parsed_lic), {0}}; + if (core_request_sha256.size() != sizeof(license_response.request_hash)) + return false; + memcpy(license_response.request_hash, core_request_sha256.data(), + sizeof(license_response.request_hash)); + return CreateResponse(ODK_License_Response_Type, core_request, + oemcrypto_core_message, license_response, + Pack_ODK_LicenseResponse); +} + +bool CreateCoreRenewalResponse(const ODK_RenewalRequest& core_request, + uint64_t renewal_duration_seconds, + std::string* oemcrypto_core_message) { + ODK_RenewalResponse renewal_response{{}, core_request.playback_time_seconds}; + renewal_response.request.playback_time = core_request.playback_time_seconds; + renewal_response.renewal_duration_seconds = renewal_duration_seconds; + return CreateResponse(ODK_Renewal_Response_Type, core_request, + oemcrypto_core_message, renewal_response, + Pack_ODK_RenewalResponse); +} + +bool CreateCoreProvisioningResponse(const ODK_ParsedProvisioning& parsed_prov, + const ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message) { + ODK_ProvisioningResponse prov_response{ + {}, const_cast(&parsed_prov)}; + if (!CopyDeviceId(core_request, &prov_response)) { + return false; + } + return CreateResponse(ODK_Provisioning_Response_Type, core_request, + oemcrypto_core_message, prov_response, + Pack_ODK_ProvisioningResponse); +} + +} // namespace serialize +} // namespace oemcrypto_core_message diff --git a/libwvdrmengine/oemcrypto/odk/src/core_message_serialize_proto.cpp b/libwvdrmengine/oemcrypto/odk/src/core_message_serialize_proto.cpp new file mode 100644 index 00000000..25f1887f --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/src/core_message_serialize_proto.cpp @@ -0,0 +1,189 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "core_message_serialize_proto.h" + +#include +#include +#include +#include +#include + +#include "core_message_serialize.h" +#include "license_protocol.pb.h" +#include "odk_serialize.h" +#include "odk_structs.h" +#include "odk_structs_priv.h" +#include "serialization_base.h" + +namespace oemcrypto_core_message { +namespace serialize { +namespace { + +/* @ private functions */ + +/** + * Extract OEMCrypto_Substring (offset, length) from serialized protobuf + * + * Parameters: + * message: serialized license protobuf + * field: substring value + */ +OEMCrypto_Substring GetOecSubstring(const std::string& message, + const std::string& field) { + OEMCrypto_Substring substring = {}; + size_t pos = message.find(field); + if (pos != std::string::npos) { + substring = OEMCrypto_Substring{pos, field.length()}; + } + return substring; +} + +OEMCrypto_KeyObject KeyContainerToOecKey( + const std::string& proto, const video_widevine::License::KeyContainer& k) { + OEMCrypto_KeyObject obj = {}; + obj.key_id = GetOecSubstring(proto, k.id()); + obj.key_data_iv = GetOecSubstring(proto, k.iv()); + // Strip off PKCS#5 padding - since we know the key is 16 or 32 bytes, + // the padding will always be 16 bytes. + const std::string& key_data = k.key(); + const size_t PKCS5_PADDING_SIZE = 16; + obj.key_data = GetOecSubstring( + proto, key_data.substr(0, std::max(PKCS5_PADDING_SIZE, key_data.size()) - + PKCS5_PADDING_SIZE)); + if (k.has_key_control()) { + const auto& key_control = k.key_control(); + obj.key_control_iv = GetOecSubstring(proto, key_control.iv()); + obj.key_control = GetOecSubstring(proto, key_control.key_control_block()); + } + return obj; +} + +} // namespace + +// @ public create response functions + +bool CreateCoreLicenseResponseFromProto(const std::string& serialized_license, + const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, + const bool nonce_required, + std::string* oemcrypto_core_message) { + video_widevine::License lic; + if (!lic.ParseFromString(serialized_license)) { + return false; + } + + ODK_ParsedLicense parsed_lic{}; + bool any_content = false; + bool any_entitlement = false; + + for (int i = 0; i < lic.key_size(); ++i) { + const auto& k = lic.key(i); + switch (k.type()) { + case video_widevine::License_KeyContainer::SIGNING: { + if (!k.has_key()) { + continue; + } + parsed_lic.enc_mac_keys_iv = + GetOecSubstring(serialized_license, k.iv()); + std::string mac_keys(k.key(), k.key().size()); + parsed_lic.enc_mac_keys = GetOecSubstring(serialized_license, mac_keys); + break; + } + case video_widevine::License_KeyContainer::CONTENT: { + any_content = true; + if (parsed_lic.key_array_length >= ODK_MAX_NUM_KEYS) { + return false; + } + uint32_t& n = parsed_lic.key_array_length; + parsed_lic.key_array[n++] = KeyContainerToOecKey(serialized_license, k); + break; + } + case video_widevine::License_KeyContainer::ENTITLEMENT: { + any_entitlement = true; + if (parsed_lic.key_array_length >= ODK_MAX_NUM_KEYS) { + return false; + } + uint32_t& n = parsed_lic.key_array_length; + parsed_lic.key_array[n++] = KeyContainerToOecKey(serialized_license, k); + break; + } + default: { + continue; + } + } + } + if (any_content && any_entitlement) { + // TODO(b/147513335): this should be logged -- both type of keys. + return false; + } + if (!any_content && !any_entitlement) { + // TODO(b/147513335): this should be logged -- no keys? + return false; + } + parsed_lic.license_type = + any_content ? OEMCrypto_ContentLicense : OEMCrypto_EntitlementLicense; + const auto& lid = lic.id(); + if (lid.has_provider_session_token()) { + parsed_lic.pst = + GetOecSubstring(serialized_license, lid.provider_session_token()); + } + + if (lic.has_srm_requirement()) { + parsed_lic.srm_restriction_data = + GetOecSubstring(serialized_license, lic.srm_requirement()); + } + + parsed_lic.nonce_required = nonce_required; + const auto& policy = lic.policy(); + ODK_TimerLimits& timer_limits = parsed_lic.timer_limits; + // TODO(b/148241181): add field to protobuf. + // timer_limits.soft_enforce_rental_duration = + // policy.soft_enforce_rental_duration(); + timer_limits.soft_enforce_rental_duration = true; + timer_limits.soft_enforce_playback_duration = + policy.soft_enforce_playback_duration(); + timer_limits.earliest_playback_start_seconds = 0; + timer_limits.rental_duration_seconds = policy.rental_duration_seconds(); + timer_limits.total_playback_duration_seconds = + policy.playback_duration_seconds(); + timer_limits.initial_renewal_duration_seconds = + policy.renewal_delay_seconds() + + policy.renewal_recovery_duration_seconds(); + + return CreateCoreLicenseResponse(parsed_lic, core_request, + core_request_sha256, oemcrypto_core_message); +} + +bool CreateCoreProvisioningResponseFromProto( + const std::string& serialized_provisioning_resp, + const ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message) { + ODK_ParsedProvisioning parsed_prov{}; + video_widevine::ProvisioningResponse prov; + if (!prov.ParseFromString(serialized_provisioning_resp)) { + return false; + } + + parsed_prov.key_type = + OEMCrypto_RSA_Private_Key; // TODO(b/148404408): ECC or RSA + if (prov.has_device_rsa_key()) { + parsed_prov.enc_private_key = + GetOecSubstring(serialized_provisioning_resp, prov.device_rsa_key()); + } + if (prov.has_device_rsa_key_iv()) { + parsed_prov.enc_private_key_iv = + GetOecSubstring(serialized_provisioning_resp, prov.device_rsa_key_iv()); + } + if (prov.has_wrapping_key()) { + parsed_prov.encrypted_message_key = + GetOecSubstring(serialized_provisioning_resp, prov.wrapping_key()); + } + + return CreateCoreProvisioningResponse(parsed_prov, core_request, + oemcrypto_core_message); +} + +} // namespace serialize +} // namespace oemcrypto_core_message diff --git a/libwvdrmengine/oemcrypto/odk/src/odk.c b/libwvdrmengine/oemcrypto/odk/src/odk.c index 92cb8ca4..1ce0d3bc 100644 --- a/libwvdrmengine/oemcrypto/odk/src/odk.c +++ b/libwvdrmengine/oemcrypto/odk/src/odk.c @@ -1,18 +1,18 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ + +#include "odk.h" #include #include #include -#include "odk.h" #include "odk_overflow.h" #include "odk_serialize.h" #include "odk_structs.h" #include "odk_structs_priv.h" +#include "odk_util.h" #include "serialization_base.h" #define ODK_LICENSE_REQUEST_SIZE 20 @@ -26,8 +26,8 @@ static OEMCryptoResult ODK_PrepareRequest(uint8_t* buffer, size_t buffer_length, uint32_t message_type, const ODK_NonceValues* nonce_values, ODK_CoreMessage* core_message) { - if (!nonce_values || !core_message_length || !core_message || - *core_message_length > buffer_length) { + if (nonce_values == NULL || core_message_length == NULL || + core_message == NULL || *core_message_length > buffer_length) { return ODK_ERROR_CORE_MESSAGE; } @@ -35,23 +35,28 @@ static OEMCryptoResult ODK_PrepareRequest(uint8_t* buffer, size_t buffer_length, AllocateMessage(&msg, message_block); InitMessage(msg, buffer, *core_message_length); *core_message = (ODK_CoreMessage){ - message_type, 0, *nonce_values, + message_type, + 0, + *nonce_values, }; switch (message_type) { case ODK_License_Request_Type: { core_message->message_length = ODK_LICENSE_REQUEST_SIZE; - Pack_ODK_PreparedLicense(msg, (ODK_PreparedLicense*)core_message); + Pack_ODK_PreparedLicenseRequest( + msg, (ODK_PreparedLicenseRequest*)core_message); break; } case ODK_Renewal_Request_Type: { core_message->message_length = ODK_RENEWAL_REQUEST_SIZE; - Pack_ODK_RenewalMessage(msg, (ODK_RenewalMessage*)core_message); + Pack_ODK_PreparedRenewalRequest( + msg, (ODK_PreparedRenewalRequest*)core_message); break; } case ODK_Provisioning_Request_Type: { core_message->message_length = ODK_PROVISIONING_REQUEST_SIZE; - Pack_ODK_ProvisioningMessage(msg, (ODK_ProvisioningMessage*)core_message); + Pack_ODK_PreparedProvisioningRequest( + msg, (ODK_PreparedProvisioningRequest*)core_message); break; } default: { @@ -69,13 +74,21 @@ static OEMCryptoResult ODK_PrepareRequest(uint8_t* buffer, size_t buffer_length, static OEMCryptoResult ODK_ParseResponse(const uint8_t* buf, size_t message_length, + size_t core_message_length, uint32_t message_type, const ODK_NonceValues* nonce_values, ODK_CoreMessage* const core_message) { + if (core_message_length > message_length) { + return ODK_ERROR_CORE_MESSAGE; + } Message* msg = NULL; AllocateMessage(&msg, message_block); + /* We initialize the message buffer with a size of the entire message + * length. */ InitMessage(msg, (uint8_t*)buf, message_length); - SetSize(msg, message_length); + /* The core message should be at the beginning of the buffer, and with a + * shorter length. The core message is the part we are parsing. */ + SetSize(msg, core_message_length); switch (message_type) { case ODK_License_Response_Type: { @@ -83,7 +96,7 @@ static OEMCryptoResult ODK_ParseResponse(const uint8_t* buf, break; } case ODK_Renewal_Response_Type: { - Unpack_ODK_RenewalMessage(msg, (ODK_RenewalMessage*)core_message); + Unpack_ODK_RenewalResponse(msg, (ODK_RenewalResponse*)core_message); break; } case ODK_Provisioning_Response_Type: { @@ -104,7 +117,10 @@ static OEMCryptoResult ODK_ParseResponse(const uint8_t* buf, if (nonce_values) { /* always verify nonce_values for Renewal and Provisioning responses */ - if (nonce_values->api_version != core_message->nonce_values.api_version || + if (nonce_values->api_major_version != + core_message->nonce_values.api_major_version || + nonce_values->api_minor_version != + core_message->nonce_values.api_minor_version || nonce_values->nonce != core_message->nonce_values.nonce || nonce_values->session_id != core_message->nonce_values.session_id) { return OEMCrypto_ERROR_INVALID_NONCE; @@ -121,34 +137,53 @@ static OEMCryptoResult ODK_ParseResponse(const uint8_t* buf, OEMCryptoResult ODK_PrepareCoreLicenseRequest( uint8_t* message, size_t message_length, size_t* core_message_length, const ODK_NonceValues* nonce_values) { - ODK_PreparedLicense license_request = {0}; + ODK_PreparedLicenseRequest license_request = { + {0}, + }; return ODK_PrepareRequest(message, message_length, core_message_length, ODK_License_Request_Type, nonce_values, &license_request.core_message); } -OEMCryptoResult ODK_PrepareCoreRenewalRequest( - uint8_t* message, size_t message_length, size_t* core_message_length, - const ODK_NonceValues* nonce_values, - const ODK_ClockValues* clock_values, uint64_t system_time_seconds) { - ODK_RenewalMessage renewal_request = { - {0}, - }; - if (odk_sub_overflow_u64(system_time_seconds, - clock_values->time_of_first_decrypt, - &renewal_request.playback_time)) { +OEMCryptoResult ODK_PrepareCoreRenewalRequest(uint8_t* message, + size_t message_length, + size_t* core_message_size, + ODK_NonceValues* nonce_values, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds) { + if (nonce_values == NULL || clock_values == NULL) return ODK_ERROR_CORE_MESSAGE; + ODK_PreparedRenewalRequest renewal_request = {{0}, 0}; + /* First, we compute the time this request was made relative to the playback + * clock. */ + if (clock_values->time_of_first_decrypt == 0) { + /* It is OK to preemptively request a renewal before playback starts. + * We'll treat this as asking for a renewal at playback time 0. */ + renewal_request.playback_time = 0; + } else { + /* Otherwise, playback_time is relative to the first decrypt. */ + if (odk_sub_overflow_u64(system_time_seconds, + clock_values->time_of_first_decrypt, + &renewal_request.playback_time)) { + return ODK_ERROR_CORE_MESSAGE; + } } - return ODK_PrepareRequest(message, message_length, core_message_length, + /* Save time for this request so that we can verify the response. This makes + * all earlier requests invalid. If preparing this request fails, then all + * requests will be bad. */ + clock_values->time_of_renewal_request = renewal_request.playback_time; + return ODK_PrepareRequest(message, message_length, core_message_size, ODK_Renewal_Request_Type, nonce_values, &renewal_request.core_message); } OEMCryptoResult ODK_PrepareCoreProvisioningRequest( uint8_t* message, size_t message_length, size_t* core_message_length, - const ODK_NonceValues* nonce_values, - const uint8_t* device_id, size_t device_id_length) { - ODK_ProvisioningMessage provisioning_request = { + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length) { + ODK_PreparedProvisioningRequest provisioning_request = { + {0}, + 0, {0}, }; if (device_id_length > sizeof(provisioning_request.device_id)) { @@ -165,55 +200,67 @@ OEMCryptoResult ODK_PrepareCoreProvisioningRequest( /* @@ parse request functions */ -OEMCryptoResult ODK_ParseLicense(const uint8_t* message, size_t message_length, - size_t core_message_length, - bool initial_license_load, - bool usage_entry_present, - const uint8_t* request_hash, - ODK_TimerLimits* timer_limits, - ODK_ClockValues* clock_values, - ODK_NonceValues* nonce_values, - ODK_ParsedLicense* parsed_license) { - - if (!nonce_values || !parsed_license) { +OEMCryptoResult ODK_ParseLicense( + const uint8_t* message, size_t message_length, size_t core_message_length, + bool initial_license_load, bool usage_entry_present, + const uint8_t* request_hash, ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, ODK_NonceValues* nonce_values, + ODK_ParsedLicense* parsed_license) { + if (message == NULL || request_hash == NULL || timer_limits == NULL || + clock_values == NULL || nonce_values == NULL || parsed_license == NULL) { return ODK_ERROR_CORE_MESSAGE; } - ODK_LicenseResponse license_response = {{0}, parsed_license}; - OEMCryptoResult err = ODK_ParseResponse( - message, message_length, ODK_License_Response_Type, NULL, - &license_response.core_message); + ODK_LicenseResponse license_response = {{{0}}, parsed_license, {0}}; + const OEMCryptoResult err = ODK_ParseResponse( + message, message_length, core_message_length, ODK_License_Response_Type, + NULL, &license_response.request.core_message); if (err != OEMCrypto_SUCCESS) { return err; } - if (license_response.core_message.nonce_values.api_version != 16) { + /* This function should not be used for legacy licenses. */ + if (license_response.request.core_message.nonce_values.api_major_version != + ODK_MAJOR_VERSION) { return ODK_UNSUPPORTED_API; } + /* If the license has a provider session token (pst), then OEMCrypto should + * have a usage entry loaded. The opposite is also an error. */ + if ((usage_entry_present && parsed_license->pst.length == 0) || + (!usage_entry_present && parsed_license->pst.length > 0)) { + return ODK_ERROR_CORE_MESSAGE; + } + if (parsed_license->nonce_required) { if (initial_license_load) { - if (nonce_values->nonce != license_response.core_message.nonce_values.nonce || - nonce_values->session_id != license_response.core_message.nonce_values.session_id) { + if (nonce_values->nonce != + license_response.request.core_message.nonce_values.nonce || + nonce_values->session_id != + license_response.request.core_message.nonce_values.session_id) { return OEMCrypto_ERROR_INVALID_NONCE; } } else { /* !initial_license_load */ - nonce_values->nonce = license_response.core_message.nonce_values.nonce; - nonce_values->session_id = license_response.core_message.nonce_values.session_id; + nonce_values->nonce = + license_response.request.core_message.nonce_values.nonce; + nonce_values->session_id = + license_response.request.core_message.nonce_values.session_id; } } - + /* For v16, in order to be backwards compatible with a v15 license server, + * OEMCrypto stores a hash of the core license request and only signs the + * message body. Here, when we process the license response, we verify that + * the server has the same hash of the core request. */ if (initial_license_load && - memcmp(request_hash, parsed_license->request_hash, ODK_SHA256_HASH_SIZE)) { + crypto_memcmp(request_hash, license_response.request_hash, + ODK_SHA256_HASH_SIZE)) { return ODK_ERROR_CORE_MESSAGE; } - - if (usage_entry_present && parsed_license->pst.length == 0) { - return ODK_ERROR_CORE_MESSAGE; - } - - return err; + *timer_limits = parsed_license->timer_limits; + /* And update the clock values state. */ + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; + return OEMCrypto_SUCCESS; } OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, @@ -223,16 +270,18 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, uint64_t* timer_value) { - if (!nonce_values || !timer_limits || !clock_values || !timer_value) { + if (message == NULL || nonce_values == NULL || timer_limits == NULL || + clock_values == NULL) { return ODK_ERROR_CORE_MESSAGE; } - ODK_RenewalMessage renewal_response = { - {0}, + ODK_RenewalResponse renewal_response = { + {{0}, 0}, + 0, }; OEMCryptoResult err = ODK_ParseResponse( - message, message_length, ODK_Renewal_Response_Type, nonce_values, - &renewal_response.core_message); + message, message_length, core_message_length, ODK_Renewal_Response_Type, + nonce_values, &renewal_response.request.core_message); if (err) { return err; @@ -242,73 +291,51 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, * Doc: License Duration and Renewal (Changes for OEMCrypto v16) * Section: Renewal Message */ - - uint64_t playback_timer = 0; - if (odk_sub_overflow_u64(clock_values->time_when_timer_expires, system_time, - &playback_timer)) { - return ODK_TIMER_EXPIRED; + /* If a renewal request is lost in transit, we should throw it out and create + * a new one. We use the timestamp to make sure we have the latest request. + */ + if (clock_values->time_of_renewal_request < + renewal_response.request.playback_time) { + return ODK_STALE_RENEWAL; } - - uint64_t time_since_playback_began = 0; - uint64_t time_since_reset = 0; - uint64_t time_since_message_signed = 0; - /* ... or use clock_values->time_of_license_signed ? */ - if (odk_sub_overflow_u64(system_time, clock_values->time_of_first_decrypt, - &time_since_playback_began) || - odk_sub_overflow_u64(timer_limits->renewal_playback_duration_seconds, - playback_timer, &time_since_reset) || - odk_sub_overflow_u64(time_since_playback_began, - renewal_response.playback_time, - &time_since_message_signed) || - time_since_message_signed >= time_since_reset || - odk_add_overflow_u64(system_time, - timer_limits->renewal_playback_duration_seconds, - &clock_values->time_when_timer_expires)) { - return ODK_ERROR_CORE_MESSAGE; - } - - /* todo: when to return ODK_DISABLE_TIMER */ - *timer_value = timer_limits->renewal_playback_duration_seconds; - return ODK_SET_TIMER; + return ODK_ComputeRenewalDuration(timer_limits, clock_values, system_time, + renewal_response.renewal_duration_seconds, + timer_value); } OEMCryptoResult ODK_ParseProvisioning( - const uint8_t* message, size_t message_length, - size_t core_message_length, - const ODK_NonceValues* nonce_values, - const uint8_t* device_id, + const uint8_t* message, size_t message_length, size_t core_message_length, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, size_t device_id_length, ODK_ParsedProvisioning* parsed_response) { - if (!nonce_values || !device_id || !parsed_response) { + if (message == NULL || nonce_values == NULL || device_id == NULL || + parsed_response == NULL) { return ODK_ERROR_CORE_MESSAGE; } - ODK_ProvisioningResponse provisioning_response = {{ - {0}, - }, + ODK_ProvisioningResponse provisioning_response = {{{0}, 0, {0}}, parsed_response}; if (device_id_length > ODK_DEVICE_ID_LEN_MAX) { return ODK_ERROR_CORE_MESSAGE; } - OEMCryptoResult err = ODK_ParseResponse( - message, message_length, ODK_Provisioning_Response_Type, - nonce_values, &provisioning_response.core_provisioning.core_message); + OEMCryptoResult err = + ODK_ParseResponse(message, message_length, core_message_length, + ODK_Provisioning_Response_Type, nonce_values, + &provisioning_response.request.core_message); if (err) { return err; } - if (memcmp(device_id, provisioning_response.core_provisioning.device_id, + if (memcmp(device_id, provisioning_response.request.device_id, device_id_length)) { return ODK_ERROR_CORE_MESSAGE; } uint8_t zero[ODK_DEVICE_ID_LEN_MAX] = {0}; /* check bytes beyond device_id_length are 0 */ - if (memcmp( - zero, - provisioning_response.core_provisioning.device_id + device_id_length, - ODK_DEVICE_ID_LEN_MAX - device_id_length)) { + if (memcmp(zero, provisioning_response.request.device_id + device_id_length, + ODK_DEVICE_ID_LEN_MAX - device_id_length)) { return ODK_ERROR_CORE_MESSAGE; } diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_assert.h b/libwvdrmengine/oemcrypto/odk/src/odk_assert.h new file mode 100644 index 00000000..2057c40a --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/src/odk_assert.h @@ -0,0 +1,24 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ + +#ifndef WIDEVINE_ODK_SRC_ODK_ASSERT_H_ +#define WIDEVINE_ODK_SRC_ODK_ASSERT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if (__STDC_VERSION__ >= 201112L) +# include +# define odk_static_assert static_assert +#else +# define odk_static_assert(msg, e) \ + enum { odk_static_assert = 1 / (!!((msg) && (e))) }; +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* WIDEVINE_ODK_SRC_ODK_ASSERT_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_endian.h b/libwvdrmengine/oemcrypto/odk/src/odk_endian.h new file mode 100644 index 00000000..bfbb9bf0 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/src/odk_endian.h @@ -0,0 +1,29 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ + +#ifndef WIDEVINE_ODK_SRC_ODK_ENDIAN_H_ +#define WIDEVINE_ODK_SRC_ODK_ENDIAN_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__linux__) || defined(__ANDROID__) +# include +# define oemcrypto_htobe32 htobe32 +# define oemcrypto_be32toh be32toh +# define oemcrypto_htobe64 htobe64 +# define oemcrypto_be64toh be64toh +#else /* defined(__linux__) || defined(__ANDROID__) */ +uint32_t oemcrypto_htobe32(uint32_t u32); +uint32_t oemcrypto_be32toh(uint32_t u32); +uint64_t oemcrypto_htobe64(uint64_t u64); +uint64_t oemcrypto_be64toh(uint64_t u64); +#endif /* defined(__linux__) || defined(__ANDROID__) */ + +#ifdef __cplusplus +} +#endif + +#endif /* WIDEVINE_ODK_SRC_ODK_ENDIAN_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_overflow.c b/libwvdrmengine/oemcrypto/odk/src/odk_overflow.c index 3a05f222..76c685f5 100644 --- a/libwvdrmengine/oemcrypto/odk/src/odk_overflow.c +++ b/libwvdrmengine/oemcrypto/odk/src/odk_overflow.c @@ -1,8 +1,6 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ #include #include diff --git a/libwvdrmengine/oemcrypto/odk/include/odk_overflow.h b/libwvdrmengine/oemcrypto/odk/src/odk_overflow.h similarity index 62% rename from libwvdrmengine/oemcrypto/odk/include/odk_overflow.h rename to libwvdrmengine/oemcrypto/odk/src/odk_overflow.h index 32aebe76..3fcb32cc 100644 --- a/libwvdrmengine/oemcrypto/odk/include/odk_overflow.h +++ b/libwvdrmengine/oemcrypto/odk/src/odk_overflow.h @@ -1,11 +1,12 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ -#ifndef ODK_OVERFLOW_H_ -#define ODK_OVERFLOW_H_ +#ifndef WIDEVINE_ODK_SRC_ODK_OVERFLOW_H_ +#define WIDEVINE_ODK_SRC_ODK_OVERFLOW_H_ + +#include +#include #ifdef __cplusplus extern "C" { @@ -30,4 +31,4 @@ int odk_add_overflow_ux(size_t a, size_t b, size_t* c); } #endif -#endif /* ODK_OVERFLOW_H_ */ +#endif /* WIDEVINE_ODK_SRC_ODK_OVERFLOW_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_serialize.c b/libwvdrmengine/oemcrypto/odk/src/odk_serialize.c index dab42be6..e0500505 100644 --- a/libwvdrmengine/oemcrypto/odk/src/odk_serialize.c +++ b/libwvdrmengine/oemcrypto/odk/src/odk_serialize.c @@ -1,8 +1,6 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ /* * This code is auto-generated, do not edit @@ -16,7 +14,8 @@ /* @@ private serialize */ static void Pack_ODK_NonceValues(Message* msg, ODK_NonceValues const* obj) { - Pack_uint32_t(msg, &obj->api_version); + Pack_uint16_t(msg, &obj->api_minor_version); + Pack_uint16_t(msg, &obj->api_major_version); Pack_uint32_t(msg, &obj->nonce); Pack_uint32_t(msg, &obj->session_id); } @@ -29,21 +28,20 @@ static void Pack_ODK_CoreMessage(Message* msg, ODK_CoreMessage const* obj) { static void Pack_OEMCrypto_KeyObject(Message* msg, OEMCrypto_KeyObject const* obj) { - Pack_OEMCrypto_Substring(msg, (const OEMCrypto_Substring*)&obj->key_id); - Pack_OEMCrypto_Substring(msg, (const OEMCrypto_Substring*)&obj->key_data_iv); - Pack_OEMCrypto_Substring(msg, (const OEMCrypto_Substring*)&obj->key_data); - Pack_OEMCrypto_Substring(msg, - (const OEMCrypto_Substring*)&obj->key_control_iv); - Pack_OEMCrypto_Substring(msg, (const OEMCrypto_Substring*)&obj->key_control); + Pack_OEMCrypto_Substring(msg, &obj->key_id); + Pack_OEMCrypto_Substring(msg, &obj->key_data_iv); + Pack_OEMCrypto_Substring(msg, &obj->key_data); + Pack_OEMCrypto_Substring(msg, &obj->key_control_iv); + Pack_OEMCrypto_Substring(msg, &obj->key_control); } static void Pack_ODK_TimerLimits(Message* msg, ODK_TimerLimits const* obj) { - Pack_uint32_t(msg, (const uint32_t*)&obj->soft_expiry); - Pack_uint64_t(msg, (const uint64_t*)&obj->earliest_playback_start_seconds); - Pack_uint64_t(msg, (const uint64_t*)&obj->latest_playback_start_seconds); - Pack_uint64_t(msg, (const uint64_t*)&obj->initial_playback_duration_seconds); - Pack_uint64_t(msg, (const uint64_t*)&obj->renewal_playback_duration_seconds); - Pack_uint64_t(msg, (const uint64_t*)&obj->license_duration_seconds); + Pack_bool(msg, &obj->soft_enforce_rental_duration); + Pack_bool(msg, &obj->soft_enforce_playback_duration); + Pack_uint64_t(msg, &obj->earliest_playback_start_seconds); + Pack_uint64_t(msg, &obj->rental_duration_seconds); + Pack_uint64_t(msg, &obj->total_playback_duration_seconds); + Pack_uint64_t(msg, &obj->initial_renewal_duration_seconds); } static void Pack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense const* obj) { @@ -52,62 +50,64 @@ static void Pack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense const* obj) { SetStatus(msg, MESSAGE_STATUS_OVERFLOW_ERROR); return; } - Pack_OEMCrypto_Substring(msg, - (const OEMCrypto_Substring*)&obj->enc_mac_keys_iv); - Pack_OEMCrypto_Substring(msg, (const OEMCrypto_Substring*)&obj->enc_mac_keys); - Pack_OEMCrypto_Substring(msg, (const OEMCrypto_Substring*)&obj->pst); - Pack_OEMCrypto_Substring( - msg, (const OEMCrypto_Substring*)&obj->srm_restriction_data); - Pack_uint32_t(msg, (const uint32_t*)&obj->license_type); - Pack_uint32_t(msg, (const uint32_t*)&obj->nonce_required); - Pack_ODK_TimerLimits(msg, (const ODK_TimerLimits*)&obj->timer_limits); - PackArray(msg, (const uint8_t*)&obj->request_hash[0], sizeof(obj->request_hash)); - Pack_uint32_t(msg, (const uint32_t*)&obj->key_array_length); - for (size_t i = 0; i < (size_t)obj->key_array_length; i++) { + Pack_OEMCrypto_Substring(msg, &obj->enc_mac_keys_iv); + Pack_OEMCrypto_Substring(msg, &obj->enc_mac_keys); + Pack_OEMCrypto_Substring(msg, &obj->pst); + Pack_OEMCrypto_Substring(msg, &obj->srm_restriction_data); + Pack_enum(msg, obj->license_type); + Pack_bool(msg, &obj->nonce_required); + Pack_ODK_TimerLimits(msg, &obj->timer_limits); + Pack_uint32_t(msg, &obj->key_array_length); + size_t i; + for (i = 0; i < (size_t)obj->key_array_length; i++) { Pack_OEMCrypto_KeyObject(msg, &obj->key_array[i]); } } static void Pack_ODK_ParsedProvisioning(Message* msg, ODK_ParsedProvisioning const* obj) { - Pack_uint32_t(msg, (const uint32_t*)&obj->key_type); - Pack_OEMCrypto_Substring(msg, - (const OEMCrypto_Substring*)&obj->enc_private_key); - Pack_OEMCrypto_Substring( - msg, (const OEMCrypto_Substring*)&obj->enc_private_key_iv); - Pack_OEMCrypto_Substring( - msg, (const OEMCrypto_Substring*)&obj->encrypted_message_key); + Pack_enum(msg, obj->key_type); + Pack_OEMCrypto_Substring(msg, &obj->enc_private_key); + Pack_OEMCrypto_Substring(msg, &obj->enc_private_key_iv); + Pack_OEMCrypto_Substring(msg, &obj->encrypted_message_key); } /* @@ odk serialize */ -void Pack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense const* obj) { - Pack_ODK_CoreMessage(msg, (const ODK_CoreMessage*)&obj->core_message); +void Pack_ODK_PreparedLicenseRequest(Message* msg, + ODK_PreparedLicenseRequest const* obj) { + Pack_ODK_CoreMessage(msg, &obj->core_message); } -void Pack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage const* obj) { - Pack_ODK_CoreMessage(msg, (const ODK_CoreMessage*)&obj->core_message); - Pack_uint64_t(msg, (const uint64_t*)&obj->playback_time); +void Pack_ODK_PreparedRenewalRequest(Message* msg, + ODK_PreparedRenewalRequest const* obj) { + Pack_ODK_CoreMessage(msg, &obj->core_message); + Pack_uint64_t(msg, &obj->playback_time); } -void Pack_ODK_ProvisioningMessage(Message* msg, - ODK_ProvisioningMessage const* obj) { - Pack_ODK_CoreMessage(msg, (const ODK_CoreMessage*)&obj->core_message); - Pack_uint32_t(msg, (const uint32_t*)&obj->device_id_length); - PackArray(msg, (const uint8_t*)&obj->device_id[0], sizeof(obj->device_id)); +void Pack_ODK_PreparedProvisioningRequest( + Message* msg, ODK_PreparedProvisioningRequest const* obj) { + Pack_ODK_CoreMessage(msg, &obj->core_message); + Pack_uint32_t(msg, &obj->device_id_length); + PackArray(msg, &obj->device_id[0], sizeof(obj->device_id)); } /* @@ kdo serialize */ void Pack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse const* obj) { - Pack_ODK_CoreMessage(msg, (const ODK_CoreMessage*)&obj->core_message); + Pack_ODK_PreparedLicenseRequest(msg, &obj->request); Pack_ODK_ParsedLicense(msg, (const ODK_ParsedLicense*)obj->parsed_license); + PackArray(msg, &obj->request_hash[0], sizeof(obj->request_hash)); +} + +void Pack_ODK_RenewalResponse(Message* msg, ODK_RenewalResponse const* obj) { + Pack_ODK_PreparedRenewalRequest(msg, &obj->request); + Pack_uint64_t(msg, &obj->renewal_duration_seconds); } void Pack_ODK_ProvisioningResponse(Message* msg, ODK_ProvisioningResponse const* obj) { - Pack_ODK_ProvisioningMessage( - msg, (const ODK_ProvisioningMessage*)&obj->core_provisioning); + Pack_ODK_PreparedProvisioningRequest(msg, &obj->request); Pack_ODK_ParsedProvisioning( msg, (const ODK_ParsedProvisioning*)obj->parsed_provisioning); } @@ -117,7 +117,8 @@ void Pack_ODK_ProvisioningResponse(Message* msg, /* @@ private deserialize */ static void Unpack_ODK_NonceValues(Message* msg, ODK_NonceValues* obj) { - Unpack_uint32_t(msg, &obj->api_version); + Unpack_uint16_t(msg, &obj->api_minor_version); + Unpack_uint16_t(msg, &obj->api_major_version); Unpack_uint32_t(msg, &obj->nonce); Unpack_uint32_t(msg, &obj->session_id); } @@ -129,81 +130,84 @@ static void Unpack_ODK_CoreMessage(Message* msg, ODK_CoreMessage* obj) { } static void Unpack_OEMCrypto_KeyObject(Message* msg, OEMCrypto_KeyObject* obj) { - Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->key_id); - Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->key_data_iv); - Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->key_data); - Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->key_control_iv); - Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->key_control); + Unpack_OEMCrypto_Substring(msg, &obj->key_id); + Unpack_OEMCrypto_Substring(msg, &obj->key_data_iv); + Unpack_OEMCrypto_Substring(msg, &obj->key_data); + Unpack_OEMCrypto_Substring(msg, &obj->key_control_iv); + Unpack_OEMCrypto_Substring(msg, &obj->key_control); } static void Unpack_ODK_TimerLimits(Message* msg, ODK_TimerLimits* obj) { - Unpack_uint32_t(msg, (uint32_t*)&obj->soft_expiry); - Unpack_uint64_t(msg, (uint64_t*)&obj->earliest_playback_start_seconds); - Unpack_uint64_t(msg, (uint64_t*)&obj->latest_playback_start_seconds); - Unpack_uint64_t(msg, (uint64_t*)&obj->initial_playback_duration_seconds); - Unpack_uint64_t(msg, (uint64_t*)&obj->renewal_playback_duration_seconds); - Unpack_uint64_t(msg, (uint64_t*)&obj->license_duration_seconds); + Unpack_bool(msg, &obj->soft_enforce_rental_duration); + Unpack_bool(msg, &obj->soft_enforce_playback_duration); + Unpack_uint64_t(msg, &obj->earliest_playback_start_seconds); + Unpack_uint64_t(msg, &obj->rental_duration_seconds); + Unpack_uint64_t(msg, &obj->total_playback_duration_seconds); + Unpack_uint64_t(msg, &obj->initial_renewal_duration_seconds); } static void Unpack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense* obj) { - Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->enc_mac_keys_iv); - Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->enc_mac_keys); - Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->pst); - Unpack_OEMCrypto_Substring(msg, - (OEMCrypto_Substring*)&obj->srm_restriction_data); - Unpack_uint32_t(msg, (uint32_t*)&obj->license_type); - Unpack_uint32_t(msg, (uint32_t*)&obj->nonce_required); - Unpack_ODK_TimerLimits(msg, (ODK_TimerLimits*)&obj->timer_limits); - UnpackArray(msg, (uint8_t*)&obj->request_hash[0], sizeof(obj->request_hash)); - Unpack_uint32_t(msg, (uint32_t*)&obj->key_array_length); + Unpack_OEMCrypto_Substring(msg, &obj->enc_mac_keys_iv); + Unpack_OEMCrypto_Substring(msg, &obj->enc_mac_keys); + Unpack_OEMCrypto_Substring(msg, &obj->pst); + Unpack_OEMCrypto_Substring(msg, &obj->srm_restriction_data); + obj->license_type = Unpack_enum(msg); + Unpack_bool(msg, &obj->nonce_required); + Unpack_ODK_TimerLimits(msg, &obj->timer_limits); + Unpack_uint32_t(msg, &obj->key_array_length); if (obj->key_array_length > ODK_MAX_NUM_KEYS) { SetStatus(msg, MESSAGE_STATUS_OVERFLOW_ERROR); return; } - for (uint32_t i = 0; i < obj->key_array_length; i++) { + uint32_t i; + for (i = 0; i < obj->key_array_length; i++) { Unpack_OEMCrypto_KeyObject(msg, &obj->key_array[i]); } } static void Unpack_ODK_ParsedProvisioning(Message* msg, ODK_ParsedProvisioning* obj) { - Unpack_uint32_t(msg, (uint32_t*)&obj->key_type); - Unpack_OEMCrypto_Substring(msg, (OEMCrypto_Substring*)&obj->enc_private_key); - Unpack_OEMCrypto_Substring(msg, - (OEMCrypto_Substring*)&obj->enc_private_key_iv); - Unpack_OEMCrypto_Substring(msg, - (OEMCrypto_Substring*)&obj->encrypted_message_key); + obj->key_type = Unpack_enum(msg); + Unpack_OEMCrypto_Substring(msg, &obj->enc_private_key); + Unpack_OEMCrypto_Substring(msg, &obj->enc_private_key_iv); + Unpack_OEMCrypto_Substring(msg, &obj->encrypted_message_key); } /* @ kdo deserialize */ -void Unpack_ODK_PreparedLicense(Message* msg, ODK_PreparedLicense* obj) { - Unpack_ODK_CoreMessage(msg, (ODK_CoreMessage*)&obj->core_message); +void Unpack_ODK_PreparedLicenseRequest(Message* msg, + ODK_PreparedLicenseRequest* obj) { + Unpack_ODK_CoreMessage(msg, &obj->core_message); } -void Unpack_ODK_ProvisioningMessage(Message* msg, - ODK_ProvisioningMessage* obj) { - Unpack_ODK_CoreMessage(msg, (ODK_CoreMessage*)&obj->core_message); - Unpack_uint32_t(msg, (uint32_t*)&obj->device_id_length); - UnpackArray(msg, (uint8_t*)&obj->device_id[0], sizeof(obj->device_id)); +void Unpack_ODK_PreparedRenewalRequest(Message* msg, + ODK_PreparedRenewalRequest* obj) { + Unpack_ODK_CoreMessage(msg, &obj->core_message); + Unpack_uint64_t(msg, &obj->playback_time); +} + +void Unpack_ODK_PreparedProvisioningRequest( + Message* msg, ODK_PreparedProvisioningRequest* obj) { + Unpack_ODK_CoreMessage(msg, &obj->core_message); + Unpack_uint32_t(msg, &obj->device_id_length); + UnpackArray(msg, &obj->device_id[0], sizeof(obj->device_id)); } /* @@ odk deserialize */ void Unpack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse* obj) { - Unpack_ODK_CoreMessage(msg, (ODK_CoreMessage*)&obj->core_message); - Unpack_ODK_ParsedLicense(msg, (ODK_ParsedLicense*)obj->parsed_license); + Unpack_ODK_PreparedLicenseRequest(msg, &obj->request); + Unpack_ODK_ParsedLicense(msg, obj->parsed_license); + UnpackArray(msg, &obj->request_hash[0], sizeof(obj->request_hash)); } -void Unpack_ODK_RenewalMessage(Message* msg, ODK_RenewalMessage* obj) { - Unpack_ODK_CoreMessage(msg, (ODK_CoreMessage*)&obj->core_message); - Unpack_uint64_t(msg, (uint64_t*)&obj->playback_time); +void Unpack_ODK_RenewalResponse(Message* msg, ODK_RenewalResponse* obj) { + Unpack_ODK_PreparedRenewalRequest(msg, &obj->request); + Unpack_uint64_t(msg, &obj->renewal_duration_seconds); } void Unpack_ODK_ProvisioningResponse(Message* msg, ODK_ProvisioningResponse* obj) { - Unpack_ODK_ProvisioningMessage( - msg, (ODK_ProvisioningMessage*)&obj->core_provisioning); - Unpack_ODK_ParsedProvisioning( - msg, (ODK_ParsedProvisioning*)obj->parsed_provisioning); + Unpack_ODK_PreparedProvisioningRequest(msg, &obj->request); + Unpack_ODK_ParsedProvisioning(msg, obj->parsed_provisioning); } diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_serialize.h b/libwvdrmengine/oemcrypto/odk/src/odk_serialize.h new file mode 100644 index 00000000..f35f1782 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/src/odk_serialize.h @@ -0,0 +1,49 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ + +/* + * This code is auto-generated, do not edit + */ +#ifndef WIDEVINE_ODK_SRC_ODK_SERIALIZE_H_ +#define WIDEVINE_ODK_SRC_ODK_SERIALIZE_H_ + +#include "odk_structs_priv.h" +#include "serialization_base.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* odk pack */ +void Pack_ODK_PreparedLicenseRequest(Message* msg, + const ODK_PreparedLicenseRequest* obj); +void Pack_ODK_PreparedRenewalRequest(Message* msg, + const ODK_PreparedRenewalRequest* obj); +void Pack_ODK_PreparedProvisioningRequest( + Message* msg, const ODK_PreparedProvisioningRequest* obj); + +/* odk unpack */ +void Unpack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse* obj); +void Unpack_ODK_RenewalResponse(Message* msg, ODK_RenewalResponse* obj); +void Unpack_ODK_ProvisioningResponse(Message* msg, + ODK_ProvisioningResponse* obj); + +/* kdo pack */ +void Pack_ODK_LicenseResponse(Message* msg, const ODK_LicenseResponse* obj); +void Pack_ODK_RenewalResponse(Message* msg, const ODK_RenewalResponse* obj); +void Pack_ODK_ProvisioningResponse(Message* msg, + const ODK_ProvisioningResponse* obj); + +/* kdo unpack */ +void Unpack_ODK_PreparedLicenseRequest(Message* msg, + ODK_PreparedLicenseRequest* obj); +void Unpack_ODK_PreparedRenewalRequest(Message* msg, + ODK_PreparedRenewalRequest* obj); +void Unpack_ODK_PreparedProvisioningRequest( + Message* msg, ODK_PreparedProvisioningRequest* obj); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* WIDEVINE_ODK_SRC_ODK_SERIALIZE_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_structs_priv.h b/libwvdrmengine/oemcrypto/odk/src/odk_structs_priv.h new file mode 100644 index 00000000..00e31bf5 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/src/odk_structs_priv.h @@ -0,0 +1,91 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ + +#ifndef WIDEVINE_ODK_SRC_ODK_STRUCTS_PRIV_H_ +#define WIDEVINE_ODK_SRC_ODK_STRUCTS_PRIV_H_ + +#include + +#include "OEMCryptoCENCCommon.h" +#include "odk_structs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + ODK_License_Request_Type = 1, + ODK_License_Response_Type = 2, + ODK_Renewal_Request_Type = 3, + ODK_Renewal_Response_Type = 4, + ODK_Provisioning_Request_Type = 5, + ODK_Provisioning_Response_Type = 6, +} ODK_MessageType; + +typedef struct { + uint32_t message_type; + uint32_t message_length; + ODK_NonceValues nonce_values; +} ODK_CoreMessage; + +typedef struct { + ODK_CoreMessage core_message; +} ODK_PreparedLicenseRequest; + +typedef struct { + ODK_CoreMessage core_message; + uint64_t playback_time; +} ODK_PreparedRenewalRequest; + +typedef struct { + ODK_CoreMessage core_message; + uint32_t device_id_length; + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX]; +} ODK_PreparedProvisioningRequest; + +typedef struct { + ODK_PreparedLicenseRequest request; + ODK_ParsedLicense* parsed_license; + uint8_t request_hash[ODK_SHA256_HASH_SIZE]; +} ODK_LicenseResponse; + +typedef struct { + ODK_PreparedRenewalRequest request; + uint64_t renewal_duration_seconds; +} ODK_RenewalResponse; + +typedef struct { + ODK_PreparedProvisioningRequest request; + ODK_ParsedProvisioning* parsed_provisioning; +} ODK_ProvisioningResponse; + +/* These are the possible timer status values. */ +#define ODK_CLOCK_TIMER_STATUS_UNDEFINED 0 /* Should not happen. */ +/* When the structure has been initialized, but no license is loaded. */ +#define ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED 1 +/* After the license is loaded, before a successful decrypt. */ +#define ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED 2 +/* After the license is loaded, if a renewal has also been loaded. */ +#define ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED 3 +/* The first decrypt has occurred and the timer is active. */ +#define ODK_CLOCK_TIMER_STATUS_ACTIVE 4 +/* The first decrypt has occurred and the timer is unlimited. */ +#define ODK_CLOCK_TIMER_STATUS_UNLIMITED 5 +/* The timer has transitioned from active to expired. */ +#define ODK_CLOCK_TIMER_STATUS_EXPIRED 6 +/* The license has been marked as inactive. */ +#define ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE 7 + +/* A helper function for computing timer limits when a renewal is loaded. */ +OEMCryptoResult ODK_ComputeRenewalDuration(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds, + uint64_t new_renewal_duration, + uint64_t* timer_value); + +#ifdef __cplusplus +} +#endif + +#endif /* WIDEVINE_ODK_SRC_ODK_STRUCTS_PRIV_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_timer.c b/libwvdrmengine/oemcrypto/odk/src/odk_timer.c index e0ae2bfa..8bef3d9a 100644 --- a/libwvdrmengine/oemcrypto/odk/src/odk_timer.c +++ b/libwvdrmengine/oemcrypto/odk/src/odk_timer.c @@ -1,48 +1,284 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ #include #include #include "odk.h" +#include "odk_overflow.h" +#include "odk_structs_priv.h" +/* Private function. Checks to see if the license is active. Returns + * ODK_TIMER_EXPIRED if the license is valid but inactive. Returns + * OEMCrypto_SUCCESS if the license is active. Returns + * OEMCrypto_ERROR_UNKNOWN_FAILURE on other errors. This also updates the + * timer_status if appropriate. */ +static OEMCryptoResult ODK_LicenseActive(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values) { + /* Check some basic errors. */ + if (clock_values == NULL || timer_limits == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* Check if the license has not been loaded yet. */ + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_UNDEFINED || + clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (clock_values->status > kActive) { + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE; + return ODK_TIMER_EXPIRED; + } + return OEMCrypto_SUCCESS; +} + +/* Private function. Sets the timer_value to be the min(timer_value, new_value), + * with the convention that 0 means infinite. The convention that 0 means + * infinite is used for all Widevine license and duration values. */ +static void ComputeMinimum(uint64_t* timer_value, uint64_t new_value) { + if (timer_value == NULL) return; + if (new_value > 0) { + if (*timer_value == 0 || *timer_value > new_value) { + *timer_value = new_value; + } + } +} + +/* Private function. Check to see if the rental window restricts playback. If + * the rental enforcement is hard, or if this is the first playback, then we + * verify that system_time_seconds is within the rental window. If the + * enforcement is soft and we have already started playback, then there is no + * restriction. + * Return ODK_TIMER_EXPIRED if out of the window. + * Return ODK_TIMER_ACTIVE if within the window, and there is a hard limit. + * Return ODK_DISABLE_TIMER if no there should be no limit. + * Return other error on error. + * Also, if this function does compute a limit, the timer_value is reduced to + * obey that limit. If the limit is less restrictive than the current + * timer_value, then timer_value is not changed. */ +static OEMCryptoResult ODK_CheckRentalWindow( + const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, + uint64_t system_time_seconds, uint64_t* timer_value) { + if (clock_values == NULL || timer_limits == NULL || timer_value == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* If playback has already started, and rental duration enforcement is soft, + * then there is no restriction. */ + if (clock_values->time_of_first_decrypt > 0 && + timer_limits->soft_enforce_rental_duration) { + return ODK_DISABLE_TIMER; + } + + /* rental_clock = time since license signed. */ + uint64_t rental_clock = 0; + if (odk_sub_overflow_u64(system_time_seconds, + clock_values->time_of_license_signed, + &rental_clock)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* Check if it is before license is valid. This is an unusual case. First + * playback may still work if it occurs after the rental window opens. */ + if (rental_clock < timer_limits->earliest_playback_start_seconds) { + return ODK_TIMER_EXPIRED; + } + /* If the rental duration is 0, there is no limit. */ + if (timer_limits->rental_duration_seconds == 0) { + return ODK_DISABLE_TIMER; + } + /* End of rental window, based on rental clock (not system time). */ + uint64_t end_of_rental_window = 0; + if (odk_add_overflow_u64(timer_limits->earliest_playback_start_seconds, + timer_limits->rental_duration_seconds, + &end_of_rental_window)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (end_of_rental_window <= rental_clock) { + return ODK_TIMER_EXPIRED; + } + /* At this point system_time is within the rental window. */ + if (timer_limits->soft_enforce_rental_duration) { + /* For soft enforcement, we allow playback, and do not adjust the timer. */ + return ODK_DISABLE_TIMER; + } + uint64_t time_left = 0; + if (odk_sub_overflow_u64(end_of_rental_window, rental_clock, &time_left)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + ComputeMinimum(timer_value, time_left); + return ODK_SET_TIMER; +} + +/* Private function. Check to see if the playback window restricts + * playback. This should only be called if playback has started, so that + * clock_values->time_of_first_decrypt is nonzero. + * Return ODK_TIMER_EXPIRED if out of the window. + * Return ODK_SET_TIMER if within the window, and there is a hard limit. + * Return ODK_DISABLE_TIMER if no limit. + * Return other error on error. + * Also, if this function does compute a limit, the timer_value is reduced to + * obey that limit. If the limit is less restrictive than the current + * timer_value, then timer_value is not changed. */ +static OEMCryptoResult ODK_CheckPlaybackWindow( + const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, + uint64_t system_time_seconds, uint64_t* timer_value) { + if (clock_values == NULL || timer_limits == NULL || timer_value == NULL) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + /* if the playback duration is 0, there is no limit. */ + if (timer_limits->total_playback_duration_seconds == 0) { + return ODK_DISABLE_TIMER; + } + uint64_t end_of_playback_window = 0; + if (odk_add_overflow_u64(timer_limits->total_playback_duration_seconds, + clock_values->time_of_first_decrypt, + &end_of_playback_window)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (end_of_playback_window <= system_time_seconds) { + return ODK_TIMER_EXPIRED; + } + /* At this point, system_time is within the total playback window. */ + if (timer_limits->soft_enforce_playback_duration) { + /* For soft enforcement, we allow playback, and do not adjust the timer. */ + return ODK_DISABLE_TIMER; + } + uint64_t time_left = 0; + if (odk_sub_overflow_u64(end_of_playback_window, system_time_seconds, + &time_left)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + ComputeMinimum(timer_value, time_left); + return ODK_SET_TIMER; +} + +/* Update the timer status. If playback has already started, we use the given + * status. However, if playback has not yet started, then we expect a call to + * ODK_AttemptFirstPlayback in the future, and we need to signal to it that we + * have already computed the timer limit. */ +static void ODK_UpdateTimerStatusForRenewal(ODK_ClockValues* clock_values, + uint32_t new_status) { + if (clock_values == NULL) return; /* should not happen. */ + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED) { + /* Signal that the timer is already set. */ + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED; + } else { + clock_values->timer_status = new_status; + } +} + +/* Private function, but accessed from odk.c so cannot be static. This checks to + * see if a renewal message should restart the playback timer and sets the value + * appropriately. */ +OEMCryptoResult ODK_ComputeRenewalDuration(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds, + uint64_t new_renewal_duration, + uint64_t* timer_value) { + if (timer_limits == NULL || clock_values == NULL) + return OEMCrypto_ERROR_INVALID_CONTEXT; /* should not happen. */ + /* If this is before the license was signed, something is odd. Return an + * error. */ + if (system_time_seconds < clock_values->time_of_license_signed) + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + + const OEMCryptoResult license_status = + ODK_LicenseActive(timer_limits, clock_values); + /* If the license is not active, then we cannot renew the license. */ + if (license_status != OEMCrypto_SUCCESS) return license_status; + + /* We start with the new renewal duration as the new timer limit. */ + uint64_t new_timer_value = new_renewal_duration; + + /* Then we factor in the rental window restrictions. This might decrease + * new_timer_value. */ + const OEMCryptoResult rental_status = ODK_CheckRentalWindow( + timer_limits, clock_values, system_time_seconds, &new_timer_value); + /* If the rental status forbids playback, then we're done. */ + if ((rental_status != ODK_DISABLE_TIMER) && (rental_status != ODK_SET_TIMER)) + return rental_status; + + /* If playback has already started and it has hard enforcement, then check + * total playback window. */ + if (clock_values->time_of_first_decrypt > 0 && + !timer_limits->soft_enforce_playback_duration) { + /* This might decrease new_timer_value. */ + const OEMCryptoResult playback_status = ODK_CheckPlaybackWindow( + timer_limits, clock_values, system_time_seconds, &new_timer_value); + /* If the timer limits forbid playback in the playback window, then we're + * done. */ + if ((playback_status != ODK_DISABLE_TIMER) && + (playback_status != ODK_SET_TIMER)) + return playback_status; + } + + /* If new_timer_value is infinite (represented by 0), then there are no + * limits, so we can return now. */ + if (new_timer_value == 0) { + clock_values->time_when_timer_expires = 0; + ODK_UpdateTimerStatusForRenewal(clock_values, + ODK_CLOCK_TIMER_STATUS_UNLIMITED); + return ODK_DISABLE_TIMER; + } + /* If the caller gave us a pointer to store the new timer value. Fill it. */ + if (timer_value != NULL) { + *timer_value = new_timer_value; + } + if (odk_add_overflow_u64(system_time_seconds, new_timer_value, + &clock_values->time_when_timer_expires)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + ODK_UpdateTimerStatusForRenewal(clock_values, ODK_CLOCK_TIMER_STATUS_ACTIVE); + return ODK_SET_TIMER; +} + +/************************************************************************/ +/************************************************************************/ +/* Public functions, declared in odk.h. */ + +/* This is called when OEMCrypto opens a new session. */ OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, ODK_NonceValues* nonce_values, - uint32_t api_version, + uint32_t api_major_version, uint32_t session_id) { if (clock_values == NULL || clock_values == NULL || nonce_values == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; - timer_limits->soft_expiry = false; + /* Check that the API version passed in from OEMCrypto matches the version of + * this ODK library. */ + if (api_major_version != ODK_MAJOR_VERSION) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + timer_limits->soft_enforce_rental_duration = false; + timer_limits->soft_enforce_playback_duration = false; timer_limits->earliest_playback_start_seconds = 0; - timer_limits->latest_playback_start_seconds = 0; - timer_limits->initial_playback_duration_seconds = 0; - timer_limits->renewal_playback_duration_seconds = 0; - timer_limits->license_duration_seconds = 0; + timer_limits->rental_duration_seconds = 0; + timer_limits->total_playback_duration_seconds = 0; + timer_limits->initial_renewal_duration_seconds = 0; - clock_values->time_of_license_signed = 0; - clock_values->time_of_first_decrypt = 0; - clock_values->time_of_last_decrypt = 0; - clock_values->time_when_timer_expires = 0; - clock_values->timer_status = 0; - clock_values->status = kUnused; + ODK_InitializeClockValues(clock_values, 0); - nonce_values->api_version = api_version; + nonce_values->api_major_version = ODK_MAJOR_VERSION; + nonce_values->api_minor_version = ODK_MINOR_VERSION; nonce_values->nonce = 0; nonce_values->session_id = session_id; return OEMCrypto_SUCCESS; } +/* This is called when OEMCrypto generates a new nonce in + * OEMCrypto_GenerateNonce. */ OEMCryptoResult ODK_SetNonceValues(ODK_NonceValues* nonce_values, uint32_t nonce) { + if (nonce_values == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; + /* Setting the nonce should only happen once per session. */ + if (nonce_values->nonce != 0) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } nonce_values->nonce = nonce; return OEMCrypto_SUCCESS; } +/* This is called when OEMCrypto signs a license. */ OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values, uint64_t system_time_seconds) { if (clock_values == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; @@ -50,130 +286,101 @@ OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values, clock_values->time_of_first_decrypt = 0; clock_values->time_of_last_decrypt = 0; clock_values->time_when_timer_expires = 0; - /* TODO(b/142415188): document this. */ - clock_values->timer_status = 0; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED; clock_values->status = kUnused; return OEMCrypto_SUCCESS; } +/* This is called when OEMCrypto reloads a usage entry. */ OEMCryptoResult ODK_ReloadClockValues(ODK_ClockValues* clock_values, - uint64_t time_of_license_signed, - uint64_t time_of_first_decrypt, - uint64_t time_of_last_decrypt, - enum OEMCrypto_Usage_Entry_Status status, - uint64_t system_time_seconds) { + uint64_t time_of_license_signed, + uint64_t time_of_first_decrypt, + uint64_t time_of_last_decrypt, + enum OEMCrypto_Usage_Entry_Status status, + uint64_t system_time_seconds) { if (clock_values == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; clock_values->time_of_license_signed = time_of_license_signed; clock_values->time_of_first_decrypt = time_of_first_decrypt; clock_values->time_of_last_decrypt = time_of_last_decrypt; clock_values->time_when_timer_expires = 0; - clock_values->timer_status = 0; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED; clock_values->status = status; return OEMCrypto_SUCCESS; } /* This is called on the first playback for a session. */ -uint32_t ODK_AttemptFirstPlayback(uint64_t system_time_seconds, - const ODK_TimerLimits* timer_limits, - ODK_ClockValues* clock_values, - uint64_t* timer_value) { +OEMCryptoResult ODK_AttemptFirstPlayback(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value) { if (clock_values == NULL || timer_limits == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE; /* All times are relative to when the license was signed. */ - const uint64_t rental_time = - system_time_seconds - clock_values->time_of_license_signed; + uint64_t rental_time = 0; + if (odk_sub_overflow_u64(system_time_seconds, + clock_values->time_of_license_signed, + &rental_time)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } if (rental_time < timer_limits->earliest_playback_start_seconds) { clock_values->timer_status = ODK_TIMER_EXPIRED; return ODK_TIMER_EXPIRED; } - /* If the clock status is already marked as inactive, then playback is - * not allowed. */ - /* TODO(b/142415188): add helper function. */ - if (clock_values->status > kActive) { - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; - } - /* If this license is still inactive (never used) then we just look at the - * rental window. This is the first playback for the license, not just this - * session. */ - if (clock_values->status == kUnused) { - /* If the rental clock has expired, the license has expired. */ - if (rental_time > timer_limits->latest_playback_start_seconds) { - clock_values->timer_status = ODK_TIMER_EXPIRED; + /* If the license is inactive or not loaded, then playback is not allowed. */ + OEMCryptoResult status = ODK_LicenseActive(timer_limits, clock_values); + if (status != OEMCrypto_SUCCESS) return status; + + /* We start with the initial renewal duration as the timer limit. */ + uint64_t new_timer_value = timer_limits->initial_renewal_duration_seconds; + /* However, if a renewal was loaded before this first playback, use the + * previously computed limit. */ + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED) { + if (clock_values->time_when_timer_expires <= system_time_seconds) { return ODK_TIMER_EXPIRED; } - /* The timer should be limited by the playback duration. */ - uint64_t time_left = timer_limits->initial_playback_duration_seconds; - /* If there is a license duration, it also limits the timer. Remeber, a - * limit of 0 means no limit, or infinite. */ - if (timer_limits->license_duration_seconds > 0) { - if (timer_limits->license_duration_seconds < rental_time) { - /* If the license duration has expired. This is unusual, because this - * can only happen if the license duration is less than the rental - * window. */ - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; - } - if (timer_limits->license_duration_seconds - rental_time < time_left || - time_left == 0) { - time_left = timer_limits->license_duration_seconds - rental_time; - } + if (odk_sub_overflow_u64(clock_values->time_when_timer_expires, + system_time_seconds, &new_timer_value)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; } - /* This is a new license, and we can start playback. */ - clock_values->status = kActive; + } + + /* Then we factor in the rental window restrictions. This might decrease + * new_timer_value. */ + status = ODK_CheckRentalWindow(timer_limits, clock_values, + system_time_seconds, &new_timer_value); + if ((status != ODK_DISABLE_TIMER) && (status != ODK_SET_TIMER)) return status; + + /* If playback has not already started, then this is the first playback. */ + if (clock_values->time_of_first_decrypt == 0) { clock_values->time_of_first_decrypt = system_time_seconds; - clock_values->time_of_last_decrypt = system_time_seconds; - if (time_left == 0 || timer_limits->soft_expiry) { /* Unlimited. */ - clock_values->time_when_timer_expires = 0; - clock_values->timer_status = ODK_DISABLE_TIMER; - return ODK_DISABLE_TIMER; - } - /* Set timer to limit playback. */ - if (timer_value) *timer_value = time_left; - clock_values->time_when_timer_expires = system_time_seconds + time_left; - clock_values->timer_status = ODK_SET_TIMER; - return ODK_SET_TIMER; + clock_values->status = kActive; } - /* Otherwise, this is the second loading of a persistent license. In this - * case, we ignore the rental window. */ - const uint64_t time_since_first_decrypt = - system_time_seconds - clock_values->time_of_first_decrypt; - uint64_t time_left = 0; - /* If there is an initial playback duration, the we use that as a limit. - * This ignores any license renewals. If renewals are allowed, then the last - * one can be reloaded to reset the timer. */ - if (timer_limits->initial_playback_duration_seconds > 0) { - if (timer_limits->initial_playback_duration_seconds <= - time_since_first_decrypt) { - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; - } - time_left = timer_limits->initial_playback_duration_seconds - - time_since_first_decrypt; - } - /* If there is a license duration, it also limits the timer. */ - if (timer_limits->license_duration_seconds > 0) { - if (timer_limits->license_duration_seconds < rental_time) { - /* The license duration has expired. */ - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; - } - if (timer_limits->license_duration_seconds - rental_time < time_left || - time_left == 0) { - time_left = timer_limits->license_duration_seconds - rental_time; - } - } - /* We can restart playback for this license. Update last playback time. */ + + /* Similar to the rental window, we check the playback window + * restrictions. This might decrease new_timer_value. */ + status = ODK_CheckPlaybackWindow(timer_limits, clock_values, + system_time_seconds, &new_timer_value); + if ((status != ODK_DISABLE_TIMER) && (status != ODK_SET_TIMER)) return status; + + /* We know we are allowed to decrypt. The rest computes the timer duration. */ clock_values->time_of_last_decrypt = system_time_seconds; - if (time_left == 0 || timer_limits->soft_expiry) { /* Unlimited. */ + + /* If new_timer_value is infinite (represented by 0), then there are no + * limits, so we can return now. */ + if (new_timer_value == 0) { clock_values->time_when_timer_expires = 0; - clock_values->timer_status = ODK_DISABLE_TIMER; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_UNLIMITED; return ODK_DISABLE_TIMER; } - /* Set timer. */ - if (timer_value) *timer_value = time_left; - clock_values->time_when_timer_expires = system_time_seconds + time_left; - clock_values->timer_status = ODK_SET_TIMER; + /* If the caller gave us a pointer to store the new timer value. Fill it. */ + if (timer_value) { + *timer_value = new_timer_value; + } + if (odk_add_overflow_u64(system_time_seconds, new_timer_value, + &clock_values->time_when_timer_expires)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_ACTIVE; return ODK_SET_TIMER; } @@ -182,30 +389,42 @@ uint32_t ODK_AttemptFirstPlayback(uint64_t system_time_seconds, OEMCryptoResult ODK_UpdateLastPlaybackTime(uint64_t system_time_seconds, const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values) { - if (clock_values == NULL || timer_limits == NULL) - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - if (clock_values->timer_status == ODK_TIMER_EXPIRED) { - return ODK_TIMER_EXPIRED; - } - if (clock_values->time_when_timer_expires > 0 && - system_time_seconds > clock_values->time_when_timer_expires) { - clock_values->timer_status = ODK_TIMER_EXPIRED; - return ODK_TIMER_EXPIRED; + OEMCryptoResult status = ODK_LicenseActive(timer_limits, clock_values); + if (status != OEMCrypto_SUCCESS) return status; + switch (clock_values->timer_status) { + case ODK_CLOCK_TIMER_STATUS_UNLIMITED: + break; + case ODK_CLOCK_TIMER_STATUS_ACTIVE: + /* Note: we allow playback at the time when the timer expires, but not + * after. This is not important for business cases, but it makes it + * easier to write tests. */ + if (clock_values->time_when_timer_expires > 0 && + system_time_seconds > clock_values->time_when_timer_expires) { + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_EXPIRED; + return ODK_TIMER_EXPIRED; + } + break; + default: /* Expired, error state, or never started. */ + return ODK_TIMER_EXPIRED; } clock_values->time_of_last_decrypt = system_time_seconds; return OEMCrypto_SUCCESS; } +/* This is called from OEMCrypto_DeactivateUsageEntry. */ OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values) { if (clock_values == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE; if (clock_values->status == kUnused) { clock_values->status = kInactiveUnused; - } else { + } else if (clock_values->status == kActive) { clock_values->status = kInactiveUsed; } + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE; return OEMCrypto_SUCCESS; } +/* This is called when OEMCrypto loads a legacy v15 license, from + * OEMCrypto_LoadKeys. */ OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, ODK_NonceValues* nonce_values, @@ -213,17 +432,41 @@ OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, uint64_t system_time_seconds) { if (clock_values == NULL || clock_values == NULL || nonce_values == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; - timer_limits->soft_expiry = false; + timer_limits->soft_enforce_playback_duration = false; + timer_limits->soft_enforce_rental_duration = false; timer_limits->earliest_playback_start_seconds = 0; - timer_limits->latest_playback_start_seconds = 0; - timer_limits->initial_playback_duration_seconds = key_duration; - timer_limits->renewal_playback_duration_seconds = key_duration; - timer_limits->license_duration_seconds = 0; - nonce_values->api_version = 15; + timer_limits->rental_duration_seconds = 0; + timer_limits->total_playback_duration_seconds = 0; + timer_limits->initial_renewal_duration_seconds = key_duration; + + nonce_values->api_major_version = 15; + nonce_values->api_minor_version = 0; if (key_duration > 0) { clock_values->time_when_timer_expires = system_time_seconds + key_duration; } else { clock_values->time_when_timer_expires = 0; } + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; return OEMCrypto_SUCCESS; } + +/* This is called when OEMCrypto loads a legacy license renewal in + * OEMCrypto_RefreshKeys. */ +OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + const ODK_NonceValues* nonce_values, + uint64_t system_time_seconds, + uint32_t new_key_duration, + uint64_t* timer_value) { + if (timer_limits == NULL || clock_values == NULL || nonce_values == NULL) + return OEMCrypto_ERROR_INVALID_CONTEXT; + if (nonce_values->api_major_version != 15) + return OEMCrypto_ERROR_INVALID_NONCE; + if (clock_values->status > kActive) { + clock_values->timer_status = ODK_TIMER_EXPIRED; + return ODK_TIMER_EXPIRED; + } + return ODK_ComputeRenewalDuration(timer_limits, clock_values, + system_time_seconds, new_key_duration, + timer_value); +} diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_util.c b/libwvdrmengine/oemcrypto/odk/src/odk_util.c new file mode 100644 index 00000000..7a85b1a4 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/src/odk_util.c @@ -0,0 +1,25 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ + +#include "odk_util.h" + +int crypto_memcmp(const void* in_a, const void* in_b, size_t len) { + if (len == 0) { + return 0; + } + + /* Only valid pointers are allowed. */ + if (in_a == NULL || in_b == NULL) { + return -1; + } + + const uint8_t* a = in_a; + const uint8_t* b = in_b; + uint8_t x = 0; + + for (size_t i = 0; i < len; i++) { + x |= a[i] ^ b[i]; + } + return x; +} diff --git a/libwvdrmengine/oemcrypto/odk/src/odk_util.h b/libwvdrmengine/oemcrypto/odk/src/odk_util.h new file mode 100644 index 00000000..42d492b1 --- /dev/null +++ b/libwvdrmengine/oemcrypto/odk/src/odk_util.h @@ -0,0 +1,24 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ + +#ifndef WIDEVINE_ODK_SRC_ODK_UTIL_H_ +#define WIDEVINE_ODK_SRC_ODK_UTIL_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* crypto_memcmp returns zero iff the |len| bytes at |a| and |b| are equal. It + * takes an amount of time dependent on |len|, but independent of the contents + * of |a| and |b|. Unlike memcmp, it cannot be used to order elements as the + * return value when a != b is undefined, other than being non-zero. */ +int crypto_memcmp(const void* a, const void* b, size_t len); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* WIDEVINE_ODK_SRC_ODK_UTIL_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/src/serialization_base.c b/libwvdrmengine/oemcrypto/odk/src/serialization_base.c index 60cc473e..505d6486 100644 --- a/libwvdrmengine/oemcrypto/odk/src/serialization_base.c +++ b/libwvdrmengine/oemcrypto/odk/src/serialization_base.c @@ -1,8 +1,6 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ #include "serialization_base.h" @@ -53,6 +51,26 @@ static void PackBytes(Message* message, const uint8_t* ptr, size_t count) { } } +void Pack_enum(Message* message, int value) { + uint32_t v32 = value; + Pack_uint32_t(message, &v32); +} + +void Pack_bool(Message* message, const bool* value) { + if (!ValidMessage(message)) return; + uint8_t data[4] = {0}; + data[3] = *value ? 1 : 0; + PackBytes(message, data, sizeof(data)); +} + +void Pack_uint16_t(Message* message, const uint16_t* value) { + if (!ValidMessage(message)) return; + uint8_t data[2] = {0}; + data[0] = *value >> 8; + data[1] = *value >> 0; + PackBytes(message, data, sizeof(data)); +} + void Pack_uint32_t(Message* message, const uint32_t* value) { if (!ValidMessage(message)) return; uint8_t data[4] = {0}; @@ -92,6 +110,27 @@ static void UnpackBytes(Message* message, uint8_t* ptr, size_t count) { } } +int Unpack_enum(Message* message) { + uint32_t v32; + Unpack_uint32_t(message, &v32); + return v32; +} + +void Unpack_bool(Message* message, bool* value) { + if (!ValidMessage(message)) return; + uint8_t data[4] = {0}; + UnpackBytes(message, data, sizeof(data)); + *value = (0 != data[3]); +} + +void Unpack_uint16_t(Message* message, uint16_t* value) { + if (!ValidMessage(message)) return; + uint8_t data[2] = {0}; + UnpackBytes(message, data, sizeof(data)); + *value = data[0]; + *value = *value << 8 | data[1]; +} + void Unpack_uint32_t(Message* message, uint32_t* value) { if (!ValidMessage(message)) return; uint8_t data[4] = {0}; @@ -117,8 +156,16 @@ void Unpack_OEMCrypto_Substring(Message* msg, OEMCrypto_Substring* obj) { Unpack_uint32_t(msg, &offset); Unpack_uint32_t(msg, &length); if (!ValidMessage(msg)) return; - size_t end = 0; - if (offset > msg->capacity || odk_add_overflow_ux(offset, length, &end) || + /* Each substring should be contained within the message body, which is in the + * total message, just after the core message. The offset of a substring is + * relative to the message body. So we need to verify: + * 0 < offset and offset + length < message->capacity - message->size + * or offset + length + message->size < message->capacity + */ + size_t substring_end = 0; /* = offset + length; */ + size_t end = 0; /* = substring_end + message->size; */ + if (odk_add_overflow_ux(offset, length, &substring_end) || + odk_add_overflow_ux(substring_end, msg->size, &end) || end > msg->capacity) { msg->status = MESSAGE_STATUS_OVERFLOW_ERROR; return; @@ -188,7 +235,9 @@ size_t GetSize(Message* message) { void SetSize(Message* message, size_t size) { if (message == NULL) return; - message->size = size; + if (size > message->capacity) message->status = MESSAGE_STATUS_OVERFLOW_ERROR; + else + message->size = size; } MessageStatus GetStatus(Message* message) { return message->status; } diff --git a/libwvdrmengine/oemcrypto/odk/include/serialization_base.h b/libwvdrmengine/oemcrypto/odk/src/serialization_base.h similarity index 76% rename from libwvdrmengine/oemcrypto/odk/include/serialization_base.h rename to libwvdrmengine/oemcrypto/odk/src/serialization_base.h index cc1a3d12..109bd496 100644 --- a/libwvdrmengine/oemcrypto/odk/include/serialization_base.h +++ b/libwvdrmengine/oemcrypto/odk/src/serialization_base.h @@ -1,11 +1,9 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ -#ifndef ODKITEE_SERIALIZATION_BASE_H_ -#define ODKITEE_SERIALIZATION_BASE_H_ +#ifndef WIDEVINE_ODK_SRC_SERIALIZATION_BASE_H_ +#define WIDEVINE_ODK_SRC_SERIALIZATION_BASE_H_ #ifdef __cplusplus extern "C" { @@ -36,14 +34,21 @@ typedef struct _Message Message; bool ValidMessage(Message* message); +void Pack_enum(Message* message, int value); +void Pack_bool(Message* message, const bool* value); +void Pack_uint16_t(Message* message, const uint16_t* value); void Pack_uint32_t(Message* message, const uint32_t* value); void Pack_uint64_t(Message* message, const uint64_t* value); void PackArray(Message* message, const uint8_t* base, size_t size); void Pack_OEMCrypto_Substring(Message* msg, const OEMCrypto_Substring* obj); +int Unpack_enum(Message* message); +void Unpack_bool(Message* message, bool* value); +void Unpack_uint16_t(Message* message, uint16_t* value); void Unpack_uint32_t(Message* message, uint32_t* value); void Unpack_uint64_t(Message* message, uint64_t* value); -void UnpackArray(Message* message, uint8_t* base, size_t size); /* copy out */ +void UnpackArray(Message* message, uint8_t* address, + size_t size); /* copy out */ void Unpack_OEMCrypto_Substring(Message* msg, OEMCrypto_Substring* obj); typedef enum { @@ -85,7 +90,7 @@ size_t GetOffset(Message* message); size_t SizeOfMessageStruct(); #ifdef __cplusplus -} // extern "C" +} /* extern "C" */ #endif -#endif // ODKITEE_SERIALIZATION_BASE_H_ +#endif /* WIDEVINE_ODK_SRC_SERIALIZATION_BASE_H_ */ diff --git a/libwvdrmengine/oemcrypto/odk/test/odk_fuzz.cpp b/libwvdrmengine/oemcrypto/odk/test/odk_fuzz.cpp index 1ad80b71..75dd7093 100644 --- a/libwvdrmengine/oemcrypto/odk/test/odk_fuzz.cpp +++ b/libwvdrmengine/oemcrypto/odk/test/odk_fuzz.cpp @@ -1,5 +1,4 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary * source code may only be used and distributed under the Widevine Master * License Agreement. */ @@ -13,12 +12,16 @@ #include #include "OEMCryptoCENCCommon.h" +#include "core_message_deserialize.h" +#include "core_message_serialize.h" +#include "core_message_types.h" #include "odk.h" #include "odk_serialize.h" -#include "oec_util.h" +#include "odk_structs.h" +#include "odk_structs_priv.h" -using namespace std; -using namespace oec_util; +// TODO(b/147297226): remove this: using namespace std; +// TODO(b/147297226): remove this: using namespace oec_util; typedef std::function roundtrip_fun; @@ -43,7 +46,7 @@ static OEMCryptoResult odk_fun_RenewalRequest( static OEMCryptoResult odk_fun_ProvisioningRequest( uint8_t* out, size_t* size, uint32_t api_version, uint32_t nonce, uint32_t session_id, const ODK_ProvisioningRequest& core_provisioning) { - const string& device_id = core_provisioning.device_id; + const std::string& device_id = core_provisioning.device_id; return ODK_PrepareCoreProvisioningRequest( out, SIZE_MAX, size, api_version, nonce, session_id, reinterpret_cast(device_id.data()), device_id.size()); @@ -52,7 +55,7 @@ static OEMCryptoResult odk_fun_ProvisioningRequest( template static roundtrip_fun kdo_odk(const F& kdo_fun, const G& odk_fun) { auto roundtrip = [&](const uint8_t* in, uint8_t* out, size_t size) -> size_t { - string input(reinterpret_cast(in), size); + std::string input(reinterpret_cast(in), size); T t = {}; if (!kdo_fun(input, &t)) { return 0; @@ -93,14 +96,15 @@ static OEMCryptoResult odk_fun_LicenseResponse( const uint8_t* message, size_t message_length, uint32_t api_version, uint32_t nonce, uint32_t session_id, const ODK_ParseLicense_Args* a, ODK_ParsedLicense& parsed_lic) { - return ODK_ParseLicense(message, message_length, api_version, nonce, - session_id, bool(a->initial_license_load), - bool(a->usage_entry_present), &parsed_lic); + return ODK_ParseLicense( + message, message_length, api_version, nonce, session_id, + static_cast(a->initial_license_load), + static_cast(a->usage_entry_present), &parsed_lic); } static bool kdo_fun_LicenseResponse(const ODK_ParseLicense_Args* args, const ODK_ParsedLicense& parsed_lic, - string* oemcrypto_core_message) { + std::string* oemcrypto_core_message) { const auto& common = args->common; ODK_LicenseRequest core_request{common.api_version, common.nonce, common.session_id}; @@ -111,7 +115,7 @@ static bool kdo_fun_LicenseResponse(const ODK_ParseLicense_Args* args, static OEMCryptoResult odk_fun_RenewalResponse( const uint8_t* buf, size_t len, uint32_t api_version, uint32_t nonce, uint32_t session_id, ODK_ParseRenewal_Args* a, - ODK_RenewalMessage& renewal_msg) { + ODK_PreparedRenewalRequest& renewal_msg) { uint64_t timer_value = 0; OEMCryptoResult err = ODK_ParseRenewal(buf, len, api_version, nonce, session_id, a->system_time, @@ -121,15 +125,16 @@ static OEMCryptoResult odk_fun_RenewalResponse( AllocateMessage(&msg, message_block); InitMessage(msg, const_cast(buf), len); SetSize(msg, len); - Unpack_ODK_RenewalMessage(msg, &renewal_msg); + Unpack_ODK_PreparedRenewalRequest(msg, &renewal_msg); assert(ValidMessage(msg)); } return err; } -static bool kdo_fun_RenewalResponse(const ODK_ParseRenewal_Args* args, - const ODK_RenewalMessage& renewal_msg, - string* oemcrypto_core_message) { +static bool kdo_fun_RenewalResponse( + const ODK_ParseRenewal_Args* args, + const ODK_PreparedRenewalRequest& renewal_msg, + std::string* oemcrypto_core_message) { const auto& common = args->common; ODK_RenewalRequest core_request{common.api_version, common.nonce, common.session_id, renewal_msg.playback_time}; @@ -146,13 +151,14 @@ static OEMCryptoResult odk_fun_ProvisioningResponse( static bool kdo_fun_ProvisioningResponse( const ODK_ParseProvisioning_Args* args, - const ODK_ParsedProvisioning& parsed_prov, string* oemcrypto_core_message) { + const ODK_ParsedProvisioning& parsed_prov, + std::string* oemcrypto_core_message) { const auto& common = args->common; assert(args->device_id_length <= sizeof(args->device_id)); ODK_ProvisioningRequest core_request{ common.api_version, common.nonce, common.session_id, - string(reinterpret_cast(args->device_id), - args->device_id_length)}; + std::string(reinterpret_cast(args->device_id), + args->device_id_length)}; return CreateCoreProvisioningResponse(parsed_prov, core_request, oemcrypto_core_message); } @@ -177,7 +183,7 @@ static roundtrip_fun odk_kdo(const F& odk_fun, const G& kdo_fun) { return 0; } - string oemcrypto_core_message; + std::string oemcrypto_core_message; if (!kdo_fun(args, t, &oemcrypto_core_message)) { return 0; } @@ -212,7 +218,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { odk_kdo( odk_fun_LicenseResponse, kdo_fun_LicenseResponse)); verify_roundtrip(data, size, - odk_kdo( + odk_kdo( odk_fun_RenewalResponse, kdo_fun_RenewalResponse)); verify_roundtrip( data, size, diff --git a/libwvdrmengine/oemcrypto/odk/test/odk_test.cpp b/libwvdrmengine/oemcrypto/odk/test/odk_test.cpp index e21bc838..016b8bfa 100644 --- a/libwvdrmengine/oemcrypto/odk/test/odk_test.cpp +++ b/libwvdrmengine/oemcrypto/odk/test/odk_test.cpp @@ -1,34 +1,69 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "odk.h" + +#include // TODO(b/147944591): use this one? Or odk_endian.h? -#include -#include #include #include #include #include #include #include -#include #include #include #include -#include +#include "OEMCryptoCENCCommon.h" +#include "core_message_deserialize.h" +#include "core_message_serialize.h" +#include "core_message_types.h" +#include "gtest/gtest.h" +#include "odk_structs.h" +#include "odk_structs_priv.h" -#include +namespace { -#include "odk.h" -#include "odk_test.h" -#include "oec_util.h" +using oemcrypto_core_message::ODK_LicenseRequest; +using oemcrypto_core_message::ODK_ProvisioningRequest; +using oemcrypto_core_message::ODK_RenewalRequest; -using namespace oec_util; +using oemcrypto_core_message::deserialize::CoreLicenseRequestFromMessage; +using oemcrypto_core_message::deserialize::CoreProvisioningRequestFromMessage; +using oemcrypto_core_message::deserialize::CoreRenewalRequestFromMessage; + +using oemcrypto_core_message::serialize::CreateCoreLicenseResponse; +using oemcrypto_core_message::serialize::CreateCoreProvisioningResponse; +using oemcrypto_core_message::serialize::CreateCoreRenewalResponse; + +enum ODK_FieldType { + ODK_UINT16, + ODK_UINT32, + ODK_UINT64, + ODK_SUBSTRING, + ODK_DEVICEID, + ODK_HASH, + ODK_NUMTYPES, +}; + +enum ODK_FieldMode { + ODK_READ, + ODK_WRITE, + ODK_DUMP, +}; + +struct ODK_Field { + ODK_FieldType type; + void* value; + std::string name; +}; size_t ODK_FieldLength(ODK_FieldType type) { switch (type) { + case ODK_UINT16: + return sizeof(uint16_t); case ODK_UINT32: return sizeof(uint32_t); case ODK_UINT64: @@ -57,6 +92,11 @@ OEMCryptoResult ODK_WriteSingleField(uint8_t* const buf, return ODK_ERROR_CORE_MESSAGE; } switch (field->type) { + case ODK_UINT16: { + uint16_t u16 = htobe16(*static_cast(field->value)); + memcpy(buf, &u16, sizeof(u16)); + break; + } case ODK_UINT32: { uint32_t u32 = htobe32(*static_cast(field->value)); memcpy(buf, &u32, sizeof(u32)); @@ -95,6 +135,12 @@ OEMCryptoResult ODK_ReadSingleField(const uint8_t* const buf, return ODK_ERROR_CORE_MESSAGE; } switch (field->type) { + case ODK_UINT16: { + memcpy(field->value, buf, sizeof(uint16_t)); + uint16_t* u16p = static_cast(field->value); + *u16p = be16toh(*u16p); + break; + } case ODK_UINT32: { memcpy(field->value, buf, sizeof(uint32_t)); uint32_t* u32p = static_cast(field->value); @@ -130,6 +176,62 @@ OEMCryptoResult ODK_ReadSingleField(const uint8_t* const buf, return OEMCrypto_SUCCESS; } +OEMCryptoResult ODK_DumpSingleField(const uint8_t* const buf, + const ODK_Field* const field) { + if (!field || !field->value) { + return ODK_ERROR_CORE_MESSAGE; + } + switch (field->type) { + case ODK_UINT16: { + uint16_t val; + memcpy(&val, buf, sizeof(uint16_t)); + val = be16toh(val); + std::cerr << field->name << ": " << val << " = 0x" << std::hex << val + << "\n"; + break; + } + case ODK_UINT32: { + uint32_t val; + memcpy(&val, buf, sizeof(uint32_t)); + val = be32toh(val); + std::cerr << field->name << ": " << val << " = 0x" << std::hex << val + << "\n"; + break; + } + case ODK_UINT64: { + uint64_t val; + memcpy(&val, buf, sizeof(uint64_t)); + val = be64toh(val); + std::cerr << field->name << ": " << val << " = 0x" << std::hex << val + << "\n"; + break; + } + case ODK_SUBSTRING: { + uint32_t off = 0; + uint32_t len = 0; + memcpy(&off, buf, sizeof(off)); + memcpy(&len, buf + sizeof(off), sizeof(len)); + std::cerr << field->name << ": (off=" << off << ", len=" << len << ")\n"; + break; + } + case ODK_DEVICEID: + case ODK_HASH: { + const size_t field_len = ODK_FieldLength(field->type); + std::cerr << field->name << ": "; + for (size_t i = 0; i < field_len; i++) { + std::cerr << std::hex << std::setfill('0') << std::setw(2) << buf[i] + << "\n"; + } + std::cerr << "\n"; + break; + } + default: + return ODK_ERROR_CORE_MESSAGE; + } + std::cerr << std::dec; // Return to normal. + return OEMCrypto_SUCCESS; +} + /* * Parameters: * [in] size_in: buffer size @@ -137,7 +239,7 @@ OEMCryptoResult ODK_ReadSingleField(const uint8_t* const buf, */ OEMCryptoResult ODK_IterFields(ODK_FieldMode mode, uint8_t* const buf, const size_t size_in, size_t* size_out, - std::vector& fields) { + const std::vector& fields) { if (!buf || !size_out) { return ODK_ERROR_CORE_MESSAGE; } @@ -152,12 +254,18 @@ OEMCryptoResult ODK_IterFields(ODK_FieldMode mode, uint8_t* const buf, return ODK_ERROR_CORE_MESSAGE; } uint8_t* const buf_off = buf + off; - if (mode == ODK_WRITE) { - ODK_WriteSingleField(buf_off, &fields[i]); - } else if (mode == ODK_READ) { - ODK_ReadSingleField(buf_off, &fields[i]); - } else { - return ODK_ERROR_CORE_MESSAGE; + switch (mode) { + case ODK_WRITE: + ODK_WriteSingleField(buf_off, &fields[i]); + break; + case ODK_READ: + ODK_ReadSingleField(buf_off, &fields[i]); + break; + case ODK_DUMP: + ODK_DumpSingleField(buf_off, &fields[i]); + break; + default: + return ODK_ERROR_CORE_MESSAGE; } off = off2; } @@ -168,31 +276,28 @@ OEMCryptoResult ODK_IterFields(ODK_FieldMode mode, uint8_t* const buf, return OEMCrypto_SUCCESS; } -OEMCryptoResult ODK_ReadFields(const uint8_t* const buf, const size_t size_in, - size_t* size_out, - std::vector& fields) { - return ODK_IterFields(ODK_READ, const_cast(buf), size_in, size_out, - fields); -} - -OEMCryptoResult ODK_WriteFields(uint8_t* const buf, const size_t size_in, - size_t* size_out, - std::vector& fields) { - return ODK_IterFields(ODK_WRITE, buf, size_in, size_out, fields); -} - -void expect_eq_buf(const void* s1, const void* s2, size_t n) { +void expect_eq_buf(const void* s1, const void* s2, size_t n, + const std::vector& fields) { if (memcmp(s1, s2, n)) { const void* buffers[] = {s1, s2}; for (int i = 0; i < 2; i++) { char _tmp[] = "/tmp/fileXXXXXX"; - mkstemp(_tmp); + const int temp_fd = mkstemp(_tmp); + if (temp_fd >= 0) { + close(temp_fd); + } else { + std::cerr << "Failed to open temp file." << std::endl; + break; + } std::string tmp(_tmp); std::fstream out(tmp, std::ios::out | std::ios::binary); - out.write((char*)buffers[i], n); + out.write(static_cast(buffers[i]), n); out.close(); - std: std::cerr << "buffer " << i << " dumped to " << tmp << std::endl; + size_t bytes_written; + uint8_t* buf = + const_cast(reinterpret_cast(buffers[i])); + ODK_IterFields(ODK_DUMP, buf, n, &bytes_written, fields); } FAIL(); } @@ -200,17 +305,22 @@ void expect_eq_buf(const void* s1, const void* s2, size_t n) { template void ValidateRequest(uint32_t message_type, - std::vector& extra_fields, + const std::vector& extra_fields, const F& odk_prepare_func, const G& kdo_parse_func) { uint32_t message_size = 0; - uint32_t api_version = 16; + uint16_t api_major_version = ODK_MAJOR_VERSION; + uint16_t api_minor_version = ODK_MINOR_VERSION; uint32_t nonce = 0xdeadbeef; uint32_t session_id = 0xcafebabe; - ODK_NonceValues nonce_values{api_version, nonce, session_id}; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; std::vector total_fields = { - {ODK_UINT32, &message_type}, {ODK_UINT32, &message_size}, - {ODK_UINT32, &api_version}, {ODK_UINT32, &nonce}, - {ODK_UINT32, &session_id}, + {ODK_UINT32, &message_type, "message_type"}, + {ODK_UINT32, &message_size, "message_size"}, + {ODK_UINT16, &api_minor_version, "api_minor_version"}, + {ODK_UINT16, &api_major_version, "api_major_version"}, + {ODK_UINT32, &nonce, "nonce"}, + {ODK_UINT32, &session_id, "session_id"}, }; total_fields.insert(total_fields.end(), extra_fields.begin(), @@ -231,20 +341,21 @@ void ValidateRequest(uint32_t message_type, &bytes_written, total_fields)); EXPECT_EQ(bytes_written, message_size); - expect_eq_buf(buf, buf2, message_size); + EXPECT_NO_FATAL_FAILURE(expect_eq_buf(buf, buf2, message_size, total_fields)); // odk kdo roundtrip T t = {}; std::string oemcrypto_core_message(reinterpret_cast(buf), message_size); EXPECT_TRUE(kdo_parse_func(oemcrypto_core_message, &t)); - nonce_values.api_version = t.api_version; + nonce_values.api_minor_version = t.api_minor_version; + nonce_values.api_major_version = t.api_major_version; nonce_values.nonce = t.nonce; nonce_values.session_id = t.session_id; EXPECT_EQ(OEMCrypto_SUCCESS, odk_prepare_func(buf2, &bytes_written, &nonce_values)); EXPECT_EQ(bytes_written, message_size); - expect_eq_buf(buf, buf2, message_size); + EXPECT_NO_FATAL_FAILURE(expect_eq_buf(buf, buf2, message_size, total_fields)); delete[] buf; delete[] buf2; @@ -258,16 +369,20 @@ void ValidateRequest(uint32_t message_type, */ template void ValidateResponse(uint32_t message_type, - std::vector& extra_fields, + const std::vector& extra_fields, const F& odk_parse_func, const G& kdo_prepare_func) { uint32_t message_size = 0; - uint32_t api_version = 16; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = ODK_MAJOR_VERSION; uint32_t nonce = 0xdeadbeef; uint32_t session_id = 0xcafebabe; std::vector total_fields = { - {ODK_UINT32, &message_type}, {ODK_UINT32, &message_size}, - {ODK_UINT32, &api_version}, {ODK_UINT32, &nonce}, - {ODK_UINT32, &session_id}, + {ODK_UINT32, &message_type, "message_type"}, + {ODK_UINT32, &message_size, "message_size"}, + {ODK_UINT16, &api_minor_version, "api_minor_version"}, + {ODK_UINT16, &api_major_version, "api_major_version"}, + {ODK_UINT32, &nonce, "nonce"}, + {ODK_UINT32, &session_id, "session_id"}, }; uint32_t header_size = 0; @@ -286,7 +401,8 @@ void ValidateResponse(uint32_t message_type, size_t bytes_read = 0, bytes_written = 0; T t = {}; - t.api_version = api_version; + t.api_minor_version = api_minor_version; + t.api_major_version = api_major_version; t.nonce = nonce; t.session_id = session_id; @@ -302,7 +418,8 @@ void ValidateResponse(uint32_t message_type, bytes_written - bytes_read == header_size); // parse buf with odk - ODK_NonceValues nonce_values{api_version, nonce, session_id}; + ODK_NonceValues nonce_values{ODK_MINOR_VERSION, api_major_version, nonce, + session_id}; EXPECT_EQ(OEMCrypto_SUCCESS, odk_parse_func(buf, bytes_written, &nonce_values)); @@ -311,7 +428,8 @@ void ValidateResponse(uint32_t message_type, EXPECT_TRUE(kdo_prepare_func(t, &oemcrypto_core_message)); EXPECT_EQ(bytes_written, message_size); - expect_eq_buf(buf, oemcrypto_core_message.data(), message_size); + EXPECT_NO_FATAL_FAILURE(expect_eq_buf(buf, oemcrypto_core_message.data(), + message_size, total_fields)); delete[] buf; delete[] zero; } @@ -321,9 +439,10 @@ TEST(OdkTest, SerializeFields) { uint64_t y[] = {3ll << 32, 4ll << 32, 5ll << 32}; OEMCrypto_Substring s = {.offset = 6, .length = 7}; std::vector fields = { - {ODK_UINT32, &x[0]}, {ODK_UINT32, &x[1]}, {ODK_UINT32, &x[2]}, - {ODK_UINT64, &y[0]}, {ODK_UINT64, &y[1]}, {ODK_UINT64, &y[2]}, - {ODK_SUBSTRING, &s}, + {ODK_UINT32, &x[0], "x[0]"}, {ODK_UINT32, &x[1], "x[1]"}, + {ODK_UINT32, &x[2], "x[2]"}, {ODK_UINT64, &y[0], "y[0]"}, + {ODK_UINT64, &y[1], "y[1]"}, {ODK_UINT64, &y[2], "y[2]"}, + {ODK_SUBSTRING, &s, "s"}, }; uint8_t buf[1024] = {0}; uint8_t buf2[1024] = {0}; @@ -332,7 +451,7 @@ TEST(OdkTest, SerializeFields) { ODK_IterFields(ODK_READ, buf, bytes_read, &bytes_written, fields); ODK_IterFields(ODK_WRITE, buf2, SIZE_MAX, &bytes_read, fields); - expect_eq_buf(buf, buf2, bytes_read); + EXPECT_NO_FATAL_FAILURE(expect_eq_buf(buf, buf2, bytes_read, fields)); } TEST(OdkTest, SerializeFieldsStress) { @@ -343,8 +462,8 @@ TEST(OdkTest, SerializeFieldsStress) { for (int i = 0; i < n; i++) { fields[i].type = static_cast(std::rand() % static_cast(ODK_NUMTYPES)); - size_t field_size = ODK_AllocSize(fields[i].type); fields[i].value = malloc(ODK_AllocSize(fields[i].type)); + fields[i].name = "stress"; total_size += ODK_FieldLength(fields[i].type); } @@ -360,7 +479,7 @@ TEST(OdkTest, SerializeFieldsStress) { ODK_IterFields(ODK_WRITE, buf2, total_size, &bytes_written, fields); EXPECT_EQ(bytes_written, total_size); - expect_eq_buf(buf, buf2, total_size); + EXPECT_NO_FATAL_FAILURE(expect_eq_buf(buf, buf2, total_size, fields)); // cleanup for (int i = 0; i < n; i++) { @@ -376,27 +495,31 @@ TEST(OdkTest, LicenseRequest) { ODK_NonceValues* nonce_values) { return ODK_PrepareCoreLicenseRequest(buf, SIZE_MAX, size, nonce_values); }; - auto kdo_parse_func = ParseLicenseRequest; + auto kdo_parse_func = CoreLicenseRequestFromMessage; ValidateRequest(ODK_License_Request_Type, empty, odk_prepare_func, kdo_parse_func); } TEST(OdkTest, RenewalRequest) { - uint64_t system_time_seconds = 0xBADDCAFE000FF1CE; - std::vector extra_fields = { - {ODK_UINT64, &system_time_seconds}, + constexpr uint64_t system_time_seconds = 0xBADDCAFE000FF1CE; + uint64_t playback_time = 0xCAFE00000000; + const uint64_t playback_start = system_time_seconds - playback_time; + const std::vector extra_fields = { + {ODK_UINT64, &playback_time, "playback_time"}, }; ODK_ClockValues clock_values = {0}; + clock_values.time_of_first_decrypt = playback_start; auto odk_prepare_func = [&](uint8_t* const buf, size_t* size, - const ODK_NonceValues* nonce_values) { + ODK_NonceValues* nonce_values) { return ODK_PrepareCoreRenewalRequest(buf, SIZE_MAX, size, nonce_values, &clock_values, system_time_seconds); }; auto kdo_parse_func = [&](const std::string& oemcrypto_core_message, ODK_RenewalRequest* core_renewal_request) { - bool ok = ParseRenewalRequest(oemcrypto_core_message, core_renewal_request); + bool ok = CoreRenewalRequestFromMessage(oemcrypto_core_message, + core_renewal_request); if (ok) { - system_time_seconds = core_renewal_request->playback_time; + playback_time = core_renewal_request->playback_time_seconds; } return ok; }; @@ -405,12 +528,12 @@ TEST(OdkTest, RenewalRequest) { } TEST(OdkTest, ProvisionRequest) { - uint32_t device_id_length = DEVICE_ID_MAX / 2; - uint8_t device_id[DEVICE_ID_MAX] = {0}; + uint32_t device_id_length = ODK_DEVICE_ID_LEN_MAX / 2; + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX] = {0}; memset(device_id, 0xff, device_id_length); std::vector extra_fields = { - {ODK_UINT32, &device_id_length}, - {ODK_DEVICEID, device_id}, + {ODK_UINT32, &device_id_length, "device_id_length"}, + {ODK_DEVICEID, device_id, "device_id"}, }; auto odk_prepare_func = [&](uint8_t* const buf, size_t* size, const ODK_NonceValues* nonce_values) { @@ -420,8 +543,8 @@ TEST(OdkTest, ProvisionRequest) { auto kdo_parse_func = [&](const std::string& oemcrypto_core_message, ODK_ProvisioningRequest* core_provisioning_request) { - bool ok = ParseProvisioningRequest(oemcrypto_core_message, - core_provisioning_request); + bool ok = CoreProvisioningRequestFromMessage(oemcrypto_core_message, + core_provisioning_request); if (ok) { const std::string& device_id_str = core_provisioning_request->device_id; @@ -441,20 +564,17 @@ TEST(OdkTest, LicenseResponse) { .enc_mac_keys = {.offset = 2, .length = 3}, .pst = {.offset = 4, .length = 5}, .srm_restriction_data = {.offset = 6, .length = 7}, - .license_type = 8, - .nonce_required = 0xDEADC0DE, + .license_type = OEMCrypto_EntitlementLicense, + .nonce_required = true, .timer_limits = { - .soft_expiry = 9, + .soft_enforce_rental_duration = true, + .soft_enforce_playback_duration = false, .earliest_playback_start_seconds = 10, - .latest_playback_start_seconds = 11, - .initial_playback_duration_seconds = 12, - .renewal_playback_duration_seconds = 13, - .license_duration_seconds = 14, + .rental_duration_seconds = 11, + .total_playback_duration_seconds = 12, + .initial_renewal_duration_seconds = 13, }, - .request_hash = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, - 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, .key_array_length = 3, .key_array = { @@ -482,52 +602,69 @@ TEST(OdkTest, LicenseResponse) { }, }; - uint32_t message_type = ODK_License_Response_Type; + const uint8_t request_hash[ODK_SHA256_HASH_SIZE] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + uint8_t request_hash_read[ODK_SHA256_HASH_SIZE]; + memcpy(request_hash_read, request_hash, sizeof(request_hash)); std::vector extra_fields = { - {ODK_SUBSTRING, &parsed_license.enc_mac_keys_iv}, - {ODK_SUBSTRING, &parsed_license.enc_mac_keys}, - {ODK_SUBSTRING, &parsed_license.pst}, - {ODK_SUBSTRING, &parsed_license.srm_restriction_data}, - {ODK_UINT32, &parsed_license.license_type}, - {ODK_UINT32, &parsed_license.nonce_required}, - {ODK_UINT32, &parsed_license.timer_limits.soft_expiry}, + {ODK_SUBSTRING, &parsed_license.enc_mac_keys_iv, ".enc_mac_keys_iv"}, + {ODK_SUBSTRING, &parsed_license.enc_mac_keys, ".enc_mac_keys"}, + {ODK_SUBSTRING, &parsed_license.pst, ".pst"}, + {ODK_SUBSTRING, &parsed_license.srm_restriction_data, + ".srm_restriction_data"}, + {ODK_UINT32, &parsed_license.license_type, ".license_type"}, + {ODK_UINT32, &parsed_license.nonce_required, ".nonce_required"}, + {ODK_UINT32, &parsed_license.timer_limits.soft_enforce_rental_duration, + ".soft_enforce_rental_duration"}, + {ODK_UINT32, &parsed_license.timer_limits.soft_enforce_playback_duration, + ".soft_enforce_playback_duration"}, + {ODK_UINT64, &parsed_license.timer_limits.earliest_playback_start_seconds, + ".earliest_playback_start_seconds"}, + {ODK_UINT64, &parsed_license.timer_limits.rental_duration_seconds, + ".rental_duration_seconds"}, + {ODK_UINT64, &parsed_license.timer_limits.total_playback_duration_seconds, + ".total_playback_duration_seconds"}, {ODK_UINT64, - &parsed_license.timer_limits.earliest_playback_start_seconds}, - {ODK_UINT64, &parsed_license.timer_limits.latest_playback_start_seconds}, - {ODK_UINT64, - &parsed_license.timer_limits.initial_playback_duration_seconds}, - {ODK_UINT64, - &parsed_license.timer_limits.renewal_playback_duration_seconds}, - {ODK_UINT64, &parsed_license.timer_limits.license_duration_seconds}, - {ODK_HASH, &parsed_license.request_hash}, - {ODK_UINT32, &parsed_license.key_array_length}, - {ODK_SUBSTRING, &parsed_license.key_array[0].key_id}, - {ODK_SUBSTRING, &parsed_license.key_array[0].key_data_iv}, - {ODK_SUBSTRING, &parsed_license.key_array[0].key_data}, - {ODK_SUBSTRING, &parsed_license.key_array[0].key_control_iv}, - {ODK_SUBSTRING, &parsed_license.key_array[0].key_control}, - {ODK_SUBSTRING, &parsed_license.key_array[1].key_id}, - {ODK_SUBSTRING, &parsed_license.key_array[1].key_data_iv}, - {ODK_SUBSTRING, &parsed_license.key_array[1].key_data}, - {ODK_SUBSTRING, &parsed_license.key_array[1].key_control_iv}, - {ODK_SUBSTRING, &parsed_license.key_array[1].key_control}, - {ODK_SUBSTRING, &parsed_license.key_array[2].key_id}, - {ODK_SUBSTRING, &parsed_license.key_array[2].key_data_iv}, - {ODK_SUBSTRING, &parsed_license.key_array[2].key_data}, - {ODK_SUBSTRING, &parsed_license.key_array[2].key_control_iv}, - {ODK_SUBSTRING, &parsed_license.key_array[2].key_control}, + &parsed_license.timer_limits.initial_renewal_duration_seconds, + ".initial_renewal_duration_seconds"}, + {ODK_UINT32, &parsed_license.key_array_length, ".key_array_length"}, + {ODK_SUBSTRING, &parsed_license.key_array[0].key_id, ".key_id"}, + {ODK_SUBSTRING, &parsed_license.key_array[0].key_data_iv, ".key_data_iv"}, + {ODK_SUBSTRING, &parsed_license.key_array[0].key_data, ".key_data"}, + {ODK_SUBSTRING, &parsed_license.key_array[0].key_control_iv, + ".key_control_iv"}, + {ODK_SUBSTRING, &parsed_license.key_array[0].key_control, ".key_control"}, + {ODK_SUBSTRING, &parsed_license.key_array[1].key_id, ".key_id"}, + {ODK_SUBSTRING, &parsed_license.key_array[1].key_data_iv, ".key_data_iv"}, + {ODK_SUBSTRING, &parsed_license.key_array[1].key_data, ".key_data"}, + {ODK_SUBSTRING, &parsed_license.key_array[1].key_control_iv, + ".key_control_iv"}, + {ODK_SUBSTRING, &parsed_license.key_array[1].key_control, ".key_control"}, + {ODK_SUBSTRING, &parsed_license.key_array[2].key_id, ".key_id"}, + {ODK_SUBSTRING, &parsed_license.key_array[2].key_data_iv, ".key_data_iv"}, + {ODK_SUBSTRING, &parsed_license.key_array[2].key_data, ".key_data"}, + {ODK_SUBSTRING, &parsed_license.key_array[2].key_control_iv, + ".key_control_iv"}, + {ODK_SUBSTRING, &parsed_license.key_array[2].key_control, ".key_control"}, + {ODK_HASH, request_hash_read, ".request_hash"}, }; - - uint8_t request_hash[ODK_SHA256_HASH_SIZE] = {}; - memcpy(request_hash, parsed_license.request_hash, ODK_SHA256_HASH_SIZE); + const std::string request_hash_string( + reinterpret_cast(request_hash), sizeof(request_hash)); auto odk_parse_func = [&](const uint8_t* buf, size_t size, ODK_NonceValues* nonce_values) { - return ODK_ParseLicense(buf, size + 128, size, 1, 0, request_hash, nullptr, - nullptr, nonce_values, &parsed_license); + ODK_TimerLimits timer_limits; + ODK_ClockValues clock_values; + constexpr bool initial_license_load = true; + constexpr bool usage_entry_present = true; + return ODK_ParseLicense(buf, size + 128, size, initial_license_load, + usage_entry_present, request_hash, &timer_limits, + &clock_values, nonce_values, &parsed_license); }; auto kdo_prepare_func = [&](const ODK_LicenseRequest& core_request, std::string* oemcrypto_core_message) { return CreateCoreLicenseResponse(parsed_license, core_request, + request_hash_string, oemcrypto_core_message); }; ValidateResponse(ODK_License_Response_Type, extra_fields, @@ -539,25 +676,29 @@ TEST(OdkTest, RenewalResponse) { uint64_t playback_clock = 11; uint64_t playback_timer = 12; uint64_t message_playback_clock = 10; + constexpr uint64_t renewal_duration = 130; + uint64_t var_renewal_duration = renewal_duration; std::vector extra_fields = { - {ODK_UINT64, &message_playback_clock}, + {ODK_UINT64, &message_playback_clock, "message_playback_clock"}, + {ODK_UINT64, &var_renewal_duration, "renewal_duration"}, }; ODK_TimerLimits timer_limits = { - .soft_expiry = 0, + .soft_enforce_rental_duration = false, + .soft_enforce_playback_duration = false, .earliest_playback_start_seconds = 0, - .latest_playback_start_seconds = 100, - .initial_playback_duration_seconds = 10, - .renewal_playback_duration_seconds = 20, - .license_duration_seconds = 100, + .rental_duration_seconds = 1000, + .total_playback_duration_seconds = 2000, + .initial_renewal_duration_seconds = 30, }; ODK_ClockValues clock_values = { - .time_of_license_signed = 0, + .time_of_license_signed = system_time - playback_clock - 42, .time_of_first_decrypt = system_time - playback_clock, .time_of_last_decrypt = 0, + .time_of_renewal_request = message_playback_clock, .time_when_timer_expires = system_time + playback_timer, - .timer_status = 0, + .timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED, .status = kUnused, }; @@ -568,7 +709,7 @@ TEST(OdkTest, RenewalResponse) { &timer_limits, &clock_values, &playback_timer); EXPECT_EQ(ODK_SET_TIMER, err); - EXPECT_EQ(timer_limits.renewal_playback_duration_seconds, playback_timer); + EXPECT_EQ(renewal_duration, playback_timer); EXPECT_EQ(clock_values.time_when_timer_expires, system_time + playback_timer); @@ -579,16 +720,17 @@ TEST(OdkTest, RenewalResponse) { }; auto kdo_prepare_func = [&](ODK_RenewalRequest& core_request, std::string* oemcrypto_core_message) { - core_request.playback_time = message_playback_clock; - return CreateCoreRenewalResponse(core_request, oemcrypto_core_message); + core_request.playback_time_seconds = message_playback_clock; + return CreateCoreRenewalResponse(core_request, renewal_duration, + oemcrypto_core_message); }; ValidateResponse(ODK_Renewal_Response_Type, extra_fields, odk_parse_func, kdo_prepare_func); } TEST(OdkTest, ProvisionResponse) { - uint32_t device_id_length = DEVICE_ID_MAX / 2; - uint8_t device_id[DEVICE_ID_MAX] = {0}; + uint32_t device_id_length = ODK_DEVICE_ID_LEN_MAX / 2; + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX] = {0}; memset(device_id, 0xff, device_id_length); ODK_ParsedProvisioning parsed_response = { @@ -598,18 +740,20 @@ TEST(OdkTest, ProvisionResponse) { }; std::vector extra_fields = { - {ODK_UINT32, &device_id_length}, - {ODK_DEVICEID, device_id}, - {ODK_UINT32, &parsed_response.key_type}, - {ODK_SUBSTRING, &parsed_response.enc_private_key}, - {ODK_SUBSTRING, &parsed_response.enc_private_key_iv}, - {ODK_SUBSTRING, &parsed_response.encrypted_message_key}, + {ODK_UINT32, &device_id_length, "device_id_length"}, + {ODK_DEVICEID, device_id, "device_id"}, + {ODK_UINT32, &parsed_response.key_type, "key_type"}, + {ODK_SUBSTRING, &parsed_response.enc_private_key, "enc_private_key"}, + {ODK_SUBSTRING, &parsed_response.enc_private_key_iv, + "enc_private_key_iv"}, + {ODK_SUBSTRING, &parsed_response.encrypted_message_key, + "encrypted_message_key"}, }; auto odk_parse_func = [&](const uint8_t* buf, size_t size, ODK_NonceValues* nonce_values) { // restore device id because it is not part of parsed_response - device_id_length = DEVICE_ID_MAX / 2; + device_id_length = ODK_DEVICE_ID_LEN_MAX / 2; memset(device_id, 0xff, device_id_length); OEMCryptoResult err = ODK_ParseProvisioning(buf, size + 16, size, nonce_values, device_id, @@ -632,10 +776,12 @@ TEST(OdkSizeTest, LicenseRequest) { uint8_t* message = nullptr; size_t message_length = 0; size_t core_message_length = 0; - uint32_t api_version = 0; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = 0; uint32_t nonce = 0; uint32_t session_id = 0; - ODK_NonceValues nonce_values{api_version, nonce, session_id}; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, ODK_PrepareCoreLicenseRequest(message, message_length, &core_message_length, &nonce_values)); @@ -648,13 +794,15 @@ TEST(OdkSizeTest, RenewalRequest) { uint8_t* message = nullptr; size_t message_length = 0; size_t core_message_length = 0; - uint32_t api_version = 0; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = 0; uint32_t nonce = 0; uint32_t session_id = 0; ODK_ClockValues clock_values = {}; clock_values.time_of_first_decrypt = 10; uint64_t system_time_seconds = 15; - ODK_NonceValues nonce_values{api_version, nonce, session_id}; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, ODK_PrepareCoreRenewalRequest(message, message_length, &core_message_length, &nonce_values, @@ -668,12 +816,13 @@ TEST(OdkSizeTest, ProvisioningRequest) { uint8_t* message = nullptr; size_t message_length = 0; size_t core_message_length = 0; - uint32_t api_version = 0; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = 0; uint32_t nonce = 0; uint32_t session_id = 0; - uint8_t* device_id = nullptr; uint32_t device_id_length = 0; - ODK_NonceValues nonce_values{api_version, nonce, session_id}; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, ODK_PrepareCoreProvisioningRequest( message, message_length, &core_message_length, &nonce_values, @@ -682,3 +831,5 @@ TEST(OdkSizeTest, ProvisioningRequest) { size_t minimum_message_size = 5 * 4; EXPECT_GE(core_message_length, minimum_message_size); } + +} // namespace diff --git a/libwvdrmengine/oemcrypto/odk/test/odk_test.h b/libwvdrmengine/oemcrypto/odk/test/odk_test.h deleted file mode 100644 index a8114a10..00000000 --- a/libwvdrmengine/oemcrypto/odk/test/odk_test.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary - * source code may only be used and distributed under the Widevine Master - * License Agreement. - */ - -#ifndef ODK_TEST_H_ -#define ODK_TEST_H_ - -#include "OEMCryptoCENCCommon.h" - -typedef enum { - ODK_License_Request_Type = 1, - ODK_License_Response_Type = 2, - ODK_Renewal_Request_Type = 3, - ODK_Renewal_Response_Type = 4, - ODK_Provisioning_Request_Type = 5, - ODK_Provisioning_Response_Type = 6, -} ODK_MessageType; - -typedef enum { - ODK_UINT32, - ODK_UINT64, - ODK_SUBSTRING, - ODK_DEVICEID, - ODK_HASH, - ODK_NUMTYPES, -} ODK_FieldType; - -typedef enum { - ODK_READ, - ODK_WRITE, -} ODK_FieldMode; - -typedef struct { - ODK_FieldType type; - void* value; -} ODK_Field; - -#define DEVICE_ID_MAX (64) - -#ifdef __cplusplus -extern "C" { -#endif - -size_t ODK_FieldLength(ODK_FieldType type); -OEMCryptoResult ODK_WriteSingleField(uint8_t* const buf, - const ODK_Field* const field); -OEMCryptoResult ODK_ReadSingleField(const uint8_t* const buf, - const ODK_Field* const field); - -OEMCryptoResult ODK_ReadFields(const uint8_t* const buf, const size_t size_in, - size_t* size_out, const size_t n, - const ODK_Field* const fields); - -OEMCryptoResult ODK_WriteFields(uint8_t* const buf, const size_t size_in, - size_t* size_out, const size_t n, - const ODK_Field* const fields); - -#ifdef __cplusplus -} -#endif - -#endif // ODK_TEST_H_ diff --git a/libwvdrmengine/oemcrypto/odk/test/odk_timer_test.cpp b/libwvdrmengine/oemcrypto/odk/test/odk_timer_test.cpp index de398103..fae23bc0 100644 --- a/libwvdrmengine/oemcrypto/odk/test/odk_timer_test.cpp +++ b/libwvdrmengine/oemcrypto/odk/test/odk_timer_test.cpp @@ -1,26 +1,27 @@ -/* - * Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary * source code may only be used and distributed under the Widevine Master * License Agreement. */ -#include - +#include "OEMCryptoCENCCommon.h" +#include "gtest/gtest.h" #include "odk.h" - -using ::testing::tuple; -using ::testing::Values; -using ::testing::WithParamInterface; +#include "odk_structs_priv.h" namespace { -constexpr uint64_t kTolerance = 1; // Allow 1 second of roundoff. -} // namespace -namespace odk_test { -struct ServerExpiry { - bool soft_rental; - bool soft_playback; -}; +// The rental clock starts when the request is signed. If any test fails +// because a time is off by exactly 1000, that is a strong indication that we +// confused rental and system clocks. The system clock should only be used +// internally on the device, because it might not be synchronized from one +// device to another. Rental clock is used in the license message from the +// server. +constexpr uint64_t kRentalClockStart = 1000u; + +// The renewal grace period is used by the server and the CDM layer to allow +// time between when the renewal is requested and when playback is cutoff if the +// renewal is not loaded. +constexpr uint64_t kGracePeriod = 5u; TEST(OdkTimerBasicTest, NullTest) { // Assert that nullptr does not cause a core dump. @@ -37,10 +38,11 @@ TEST(OdkTimerBasicTest, Init) { uint64_t time = 42; ODK_InitializeClockValues(&clock_values, time); EXPECT_EQ(clock_values.time_of_license_signed, time); - EXPECT_EQ(clock_values.time_of_first_decrypt, 0); - EXPECT_EQ(clock_values.time_of_last_decrypt, 0); - EXPECT_EQ(clock_values.time_when_timer_expires, 0); - EXPECT_EQ(clock_values.timer_status, 0); + EXPECT_EQ(clock_values.time_of_first_decrypt, 0u); + EXPECT_EQ(clock_values.time_of_last_decrypt, 0u); + EXPECT_EQ(clock_values.time_when_timer_expires, 0u); + EXPECT_EQ(clock_values.timer_status, + ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED); EXPECT_EQ(clock_values.status, kUnused); } @@ -59,878 +61,1173 @@ TEST(OdkTimerBasicTest, Reload) { EXPECT_EQ(clock_values.time_of_first_decrypt, first_decrypt); EXPECT_EQ(clock_values.time_of_last_decrypt, last_decrypt); EXPECT_EQ(clock_values.time_when_timer_expires, 0u); - EXPECT_EQ(clock_values.timer_status, 0u); + EXPECT_EQ(clock_values.timer_status, + ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED); EXPECT_EQ(clock_values.status, status); } -// ************************************************************************ -// Test that we can only start playback within the rental window. This -// simulates requesting a license at rental_clock_start_, and a rental window of -// 50-150s relative to license request, or 100-200s absolute. -class OdkTimerRentalWindow : public ::testing::Test { +// All of the following test cases are derived from this base class. It +// simulates loading a license, attempting playback, and reloading a license. +class ODKTimerTest : public ::testing::Test { public: - OdkTimerRentalWindow() { - rental_clock_start_ = 10000u; - rental_window_start_ = 50u; - rental_window_duration_ = 100u; + ODKTimerTest() { + // These are reasonable initial values for most tests. This is an unlimited + // license. Most of the tests below will add some restrictions by changing + // these values, and then verify that the ODK library behaves correctly. + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.soft_enforce_playback_duration = true; + timer_limits_.earliest_playback_start_seconds = 0u; + // A duration of 0 means infinite. + timer_limits_.rental_duration_seconds = 0u; + timer_limits_.total_playback_duration_seconds = 0u; + timer_limits_.initial_renewal_duration_seconds = 0u; + + // This is when we will attempt the first valid playback. + start_of_playback_ = + GetSystemTime(timer_limits_.earliest_playback_start_seconds + 10); } protected: void SetUp() override { ::testing::Test::SetUp(); - // Start rental clocks at rental_clock_start_. - ODK_InitializeClockValues(&clock_values_, rental_clock_start_); - // Simple license values: - timer_limits_.soft_expiry = false; - timer_limits_.earliest_playback_start_seconds = rental_window_start_; - timer_limits_.latest_playback_start_seconds = - rental_window_start_ + rental_window_duration_; - timer_limits_.initial_playback_duration_seconds = 0; - timer_limits_.renewal_playback_duration_seconds = 0; - timer_limits_.license_duration_seconds = 0; + // Start rental clock at kRentalClockStart. This happens when the license + // request is signed. + ODK_InitializeClockValues(&clock_values_, kRentalClockStart); + EXPECT_EQ(clock_values_.time_of_license_signed, kRentalClockStart); } - // Simulate reloading a license in a new session. An offline license should - // have several of the clock_value's fields saved into the usage table. When - // it is reloaded those values should be reloaded. From these fields, the - // ODK function can tell if this is the first playback for the license, or - // just the first playback for this session. The key fields that should be - // saved are the status, and the times for license signed, and first and - // last playback times. - void ReloadClock(uint64_t system_time) { + // Simulate loading or reloading a license in a new session. An offline + // license should have several of the clock_value's fields saved into the + // usage table. When it is reloaded those values should be reloaded. From + // these fields, the ODK function can tell if this is the first playback for + // the license, or just the first playback for this session. The key fields + // that should be saved are the status, and the times for license signed, and + // first and last playback times. + void ReloadLicense(uint64_t system_time) { ODK_ClockValues old_clock_values = clock_values_; // First clear out the old clock values. - ODK_InitializeClockValues(&clock_values_, 0u); + memset(&clock_values_, 0, sizeof(clock_values_)); + // When the session is opened, the clock values are initialized. + ODK_InitializeClockValues(&clock_values_, 0); + // When the usage entry is reloaded, the clock values are reloaded. ODK_ReloadClockValues(&clock_values_, old_clock_values.time_of_license_signed, old_clock_values.time_of_first_decrypt, old_clock_values.time_of_last_decrypt, old_clock_values.status, system_time); + EXPECT_EQ(clock_values_.timer_status, + ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED); + // These shall not change: + EXPECT_EQ(clock_values_.time_of_license_signed, kRentalClockStart); + EXPECT_EQ(clock_values_.time_of_first_decrypt, + old_clock_values.time_of_first_decrypt); + EXPECT_EQ(clock_values_.time_of_last_decrypt, + old_clock_values.time_of_last_decrypt); + // ODK_ParseLicense sets the new timer state. + clock_values_.timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; } - uint64_t system_time(uint64_t rental_clock) { - return rental_clock_start_ + rental_clock; + // Simulate loading or reloading a license, then verify that we are allowed + // playback from |start| to |stop|. If |cutoff| is not 0, then expect the + // timer to expire at that time. If |cutoff| is 0, expect the timer is not + // set. If you refer to the diagrams in "License Duration and Renewal", this + // tests a cyan bar with a green check mark. + // When nonzero |start|, |stop|, and |cutoff| are all system times. + void LoadAndAllowPlayback(uint64_t start, uint64_t stop, uint64_t cutoff) { + ReloadLicense(start); + AllowPlayback(start, stop, cutoff); + } + + // Verify that we are allowed playback from |start| to |stop|. If |cutoff| is + // not 0, then expect the timer to expire at that time. If |cutoff| is 0, + // expect the timer is not set. If you refer to the diagrams in "License + // Duration and Renewal", this tests a cyan bar with a green check mark. + // When nonzero |start|, |stop|, and |cutoff| are all system times. + void AllowPlayback(uint64_t start, uint64_t stop, uint64_t cutoff) { + ASSERT_LT(start, stop); + if (cutoff > 0) ASSERT_LE(stop, cutoff); + uint64_t timer_value; + const OEMCryptoResult result = ODK_AttemptFirstPlayback( + start, &timer_limits_, &clock_values_, &timer_value); + // After first playback, the license is active. + EXPECT_EQ(clock_values_.status, kActive); + EXPECT_EQ(clock_values_.time_when_timer_expires, cutoff); + if (cutoff > 0) { // If we expect the timer to be set. + EXPECT_EQ(result, ODK_SET_TIMER); + EXPECT_EQ(timer_value, cutoff - start); + } else { + EXPECT_EQ(result, ODK_DISABLE_TIMER); + } + CheckClockValues(start); + const uint64_t mid = (start + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(mid); + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(stop); + } + + // Simulate loading or reloading a license, then attempt to play from |start| + // to |cutoff|. Verify that we are allowed playback from |start| to |cutoff|, + // but playback is not allowed after |cutoff|. If you refer to the diagrams in + // "License Duration and Renewal", this tests a cyan bar with a black X. When + // nonzero |start|, and |cutoff| are all system times. + void LoadAndTerminatePlayback(uint64_t start, uint64_t cutoff) { + ReloadLicense(start); + TerminatePlayback(start, cutoff, cutoff + 10); + } + + // Attempt to play from |start| to |stop|. Verify that we are allowed playback + // from |start| to |cutoff|, but playback not allowed after |cutoff|. If you + // refer to the diagrams in "License Duration and Renewal", this tests a cyan + // bar with a black X. This assumes that |cutoff| is before |stop|. + // When nonzero |start|, |stop|, and |cutoff| are all system times. + void TerminatePlayback(uint64_t start, uint64_t cutoff, uint64_t stop) { + ASSERT_LT(start, cutoff); + ASSERT_LT(cutoff, stop); + AllowPlayback(start, cutoff, cutoff); + EXPECT_EQ( + ODK_UpdateLastPlaybackTime(cutoff + 1, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + const uint64_t mid = (cutoff + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + // times do not change if playback was not allowed. + CheckClockValues(cutoff); + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + } + + // Verify that we are not allowed playback at the |start| time. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan line + // followed by a black X. The parameter |start| is system time. + void ForbidPlayback(uint64_t start) { + ReloadLicense(start); + ODK_ClockValues old_clock_values = clock_values_; + uint64_t timer_value; + EXPECT_EQ(ODK_AttemptFirstPlayback(start, &timer_limits_, &clock_values_, + &timer_value), + ODK_TIMER_EXPIRED); + // These should not have changed. In particular, if the license was unused + // before, it should reamin unused. + EXPECT_EQ(clock_values_.time_of_license_signed, + old_clock_values.time_of_license_signed); + EXPECT_EQ(clock_values_.time_of_first_decrypt, + old_clock_values.time_of_first_decrypt); + EXPECT_EQ(clock_values_.time_of_last_decrypt, + old_clock_values.time_of_last_decrypt); + EXPECT_EQ(clock_values_.status, old_clock_values.status); + } + + // Verify that the clock values are correct. + void CheckClockValues(uint64_t time_of_last_decrypt) { + EXPECT_EQ(clock_values_.time_of_license_signed, kRentalClockStart); + EXPECT_EQ(clock_values_.time_of_first_decrypt, start_of_playback_); + EXPECT_EQ(clock_values_.time_of_last_decrypt, time_of_last_decrypt); + EXPECT_EQ(clock_values_.status, kActive); + } + + // Convert from rental time to system time. By "system time", we mean + // OEMCrypto's montonic clock. The spec does not specify a starting time for + // this clock. + uint64_t GetSystemTime(uint64_t rental_clock) { + return kRentalClockStart + rental_clock; + } + + // The end of the playback window. (using system clock) + // This is not useful if the playback duration is 0. + uint64_t EndOfPlaybackWindow() { + return start_of_playback_ + timer_limits_.total_playback_duration_seconds; + } + + // The end of the rental window. (using system clock) + // This is not useful if the rental duration is 0. + uint64_t EndOfRentalWindow() { + return GetSystemTime(timer_limits_.earliest_playback_start_seconds) + + timer_limits_.rental_duration_seconds; } ODK_TimerLimits timer_limits_; ODK_ClockValues clock_values_; - - // The rental clock starts when the request is signed. - uint64_t rental_clock_start_; - // start of rental window in seconds since rental clock start. - uint64_t rental_window_start_; - // The "width" of window. - uint64_t rental_window_duration_; + // The start of playback. This is set to the planned start at the beginning of + // the test. (using system clock) + uint64_t start_of_playback_; }; -TEST_F(OdkTimerRentalWindow, EarlyTest) { - uint64_t timer_value = 0; - // An attempt to start playback before the timer starts is an error. - // We use the TIMER_EXPIRED error to mean both early or late. - const uint64_t bad_start_time = - system_time(timer_limits_.earliest_playback_start_seconds - 10); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, - &clock_values_, &timer_value)); - const uint64_t good_start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(good_start_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(good_start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(good_start_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0, clock_values_.time_when_timer_expires); +TEST_F(ODKTimerTest, SimplePlayback) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, 0); } -TEST_F(OdkTimerRentalWindow, NullTimer) { - // If OEMCrypto passes in a nullpointer, then the ODK should allow this. +// This tests that we are not allowed to start playback before the rental window +// opens. +TEST_F(ODKTimerTest, EarlyTest) { + timer_limits_.earliest_playback_start_seconds = 100u; + // This is earlier than we are allowed. + const uint64_t bad_start_time = + GetSystemTime(timer_limits_.earliest_playback_start_seconds - 10); + // An attempt to start playback before the timer starts is an error. + // We use the TIMER_EXPIRED error to mean both early or late. + ForbidPlayback(bad_start_time); + // And times were not updated: + EXPECT_EQ(clock_values_.status, kUnused); + // This is when we will attempt the first valid playback. + start_of_playback_ = + GetSystemTime(timer_limits_.earliest_playback_start_seconds + 10); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, 0); +} + +// This runs the same test as above, but explicitly gives the ODK library a null +// pointer for the timer value. A null pointer is allowed for OEMCrypto +// implementations that do not manage their own timer. +TEST_F(ODKTimerTest, NullTimer) { + timer_limits_.earliest_playback_start_seconds = 100u; + // This is the earlier, invalid start time. + const uint64_t bad_start_time = + GetSystemTime(timer_limits_.earliest_playback_start_seconds - 10); + timer_limits_.rental_duration_seconds = 300; + timer_limits_.soft_enforce_rental_duration = false; + ReloadLicense(GetSystemTime(bad_start_time)); + + // If OEMCrypto passes in a null pointer, then the ODK should allow this. uint64_t* timer_value_pointer = nullptr; - const uint64_t bad_start_time = - system_time(timer_limits_.earliest_playback_start_seconds - 10); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, - &clock_values_, timer_value_pointer)); - const uint64_t good_start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(good_start_time, &timer_limits_, - &clock_values_, timer_value_pointer)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(good_start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(good_start_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0, clock_values_.time_when_timer_expires); + EXPECT_EQ(ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, + &clock_values_, timer_value_pointer), + ODK_TIMER_EXPIRED); + start_of_playback_ = + GetSystemTime(timer_limits_.earliest_playback_start_seconds + 10); + EXPECT_EQ(ODK_AttemptFirstPlayback(start_of_playback_, &timer_limits_, + &clock_values_, timer_value_pointer), + ODK_SET_TIMER); + CheckClockValues(start_of_playback_); } -// Verify that an attempt to start playback outside the rental window fails. -TEST_F(OdkTimerRentalWindow, LateTest) { - uint64_t timer_value = 0; - // An attempt to start playback before the timer starts is an error. - // We use the TIMER_EXPIRED error to mean both early or late. - const uint64_t start_time = system_time(201); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kUnused, clock_values_.status); -} +/*****************************************************************************/ +// Note on Use Case tests. The test classes below correspond to the use cases +// in the doucment "License Duration and Renewal.". Each diagram in that +// document has a test class below to verify the use case is supported. +// +// In the document, we use realistic rental times in hours or days. In these +// tests, we will use round numbers so that it is easier to read. For example, +// instead of a seven day rental duration, we will use a 700 rental duration. +/*****************************************************************************/ -// ************************************************************************ -// The Hard Rental use case is a strict time limit on playback. -class OdkTimerHardTest : public OdkTimerRentalWindow { - protected: - void SetUp() override { - OdkTimerRentalWindow::SetUp(); - timer_limits_.soft_expiry = false; // Hard expiry. - // License duration is 200. The license starts when it was signed at 50, - // so the license is valid from 50-250. The rental window is 100-200 -- as - // inherited from the ODKRentalWindow class. - timer_limits_.license_duration_seconds = 200u; - } -}; - -TEST_F(OdkTimerHardTest, EarlyTest) { - uint64_t timer_value = 0; - // An attempt to start playback before the timer starts is an error. - // We use the TIMER_EXPIRED error to mean both early or late. - const uint64_t bad_start_time = - system_time(timer_limits_.earliest_playback_start_seconds - 10); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(kUnused, clock_values_.status); - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = false, we should set a timer. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); -} - -TEST_F(OdkTimerHardTest, NormalTest) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = false, we should set a timer. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); - // Try to play before the cutoff time is valid. - const uint64_t valid_play_time = cutoff_time - 1; - // This play time is outside the rental window. We are allowed to continue - // playback after the rental window expires as long as the first decrypt is - // within the rental window. - EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - // Try to play after the cutoff time is not valid. - const uint64_t late_play_time = cutoff_time + 1; - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); -} - -// This verifies that the rental window only affects the first load for the -// license, not the first load for the session. -TEST_F(OdkTimerHardTest, Reload) { - uint64_t timer_value = 0; - // An attempt to start playback before the timer starts is an error. - // We use the TIMER_EXPIRED error to mean both early or late. - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(75, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kUnused, clock_values_.status); - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = false, we should set a timer. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); - - // We can restart playback before the cutoff time. - const uint64_t valid_restart_time = cutoff_time - 10; - ReloadClock(valid_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - - // This restart is outside the rental window. We are allowed to - // restart playback after the rental window expires as long as the first - // decrypt for the license is within the rental window. - EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_restart_time); - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(valid_restart_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - valid_restart_time, timer_value, kTolerance); - - // Try to play before the cutoff time is valid. - const uint64_t valid_play_time = cutoff_time - 1; - // This play time is outside the rental window. That means we are allowed - // to continue playback after the rental window expires as long as the first - // decrypt is within the rental window. - EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - // Try to play after the cutoff time is not valid. - const uint64_t late_play_time = cutoff_time + 1; - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); -} - -TEST_F(OdkTimerHardTest, ReloadLate) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = false, we should set a timer. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); - - // We can not restart playback after the cutoff time. - const uint64_t late_restart_time = cutoff_time + 10; - ReloadClock(late_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - - // Calling UpdateLastPlaybackTimer should not be done, but if it were done, - // it should also fail. - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, - &clock_values_)); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); -} - -// ************************************************************************ -// The Soft Rental use case is a strict time limit on playback. -class OdkTimerSoftTest : public OdkTimerRentalWindow { - protected: - void SetUp() override { - OdkTimerRentalWindow::SetUp(); - timer_limits_.soft_expiry = true; // Soft expiry. - // License duration is 200. The license starts when it was signed at 50, - // so the license is valid from 50-250. The rental window is 100-200 -- as - // inherited from the ODKRentalWindow class. - timer_limits_.license_duration_seconds = 200u; - } -}; - -TEST_F(OdkTimerSoftTest, EarlyTest) { - uint64_t timer_value = 0; - // An attempt to start playback before the timer starts is an error. - // We use the TIMER_EXPIRED error to mean both early or late. - const uint64_t bad_start_time = - system_time(timer_limits_.earliest_playback_start_seconds - 10); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, - &clock_values_, &timer_value)); - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = true, we should not set a timer. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0u, clock_values_.time_when_timer_expires); -} - -TEST_F(OdkTimerSoftTest, NormalTest) { - uint64_t timer_value = 0; - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = true, we should not set a timer. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0u, clock_values_.time_when_timer_expires); - // Try to play before the cutoff time is valid. - const uint64_t valid_play_time = cutoff_time - 1; - // This play time is outside the rental window. We are allowed to continue - // playback after the rental window expires as long as the first decrypt is - // within the rental window. - EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - // Try to play after the cutoff time is still valid for soft expiry. - const uint64_t late_play_time = cutoff_time + 1; - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); -} - -// This verifies that the rental window only affects the first load for the -// license, not the first load for the session. -TEST_F(OdkTimerSoftTest, Reload) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = true, we should not set a timer. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0, clock_values_.time_when_timer_expires); - - // We can restart playback before the cutoff time. - const uint64_t valid_restart_time = cutoff_time - 10; - ReloadClock(valid_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - - // This restart is outside the rental window. We are allowed to - // restart playback after the rental window expires as long as the first - // decrypt for the license is within the rental window. - EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_restart_time); - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(valid_restart_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0, clock_values_.time_when_timer_expires); - - // Try to play before the cutoff time is valid. - const uint64_t valid_play_time = cutoff_time - 1; - // This play time is outside the rental window. That means we are allowed - // to continue playback after the rental window expires as long as the first - // decrypt is within the rental window. - EXPECT_LE(timer_limits_.latest_playback_start_seconds, valid_play_time); - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0u, clock_values_.time_when_timer_expires); - // Try to play after the cutoff time is still valid. - const uint64_t late_play_time = cutoff_time + 1; - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0u, clock_values_.time_when_timer_expires); -} - -TEST_F(OdkTimerSoftTest, ReloadLate) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t start_time = - system_time(timer_limits_.earliest_playback_start_seconds); - const uint64_t cutoff_time = - system_time(timer_limits_.license_duration_seconds); - // For a soft_expiry = true, we should not set a timer. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - EXPECT_EQ(0u, clock_values_.time_when_timer_expires); - - // We can not restart playback after the cutoff time. - const uint64_t late_restart_time = cutoff_time + 10; - ReloadClock(late_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - - // Calling UpdateLastPlaybackTimer should not be done, but if it were done, - // it should also fail. - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, - &clock_values_)); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); -} -// ************************************************************************ - -// ************************************************************************ -// From the server's point of view, there are two flags that decide soft or -// hard limits: the rental duration, and the playback duration. From -// OEMCrypto's point of view, there is only playback duration. A soft or hard -// rental duration is translated into different rental and license durations. -// The four test classes below all have a 700 rental window and a 200 playback -// duration. We'll use the server description, and then set the OEMCrypto -// restraints in the test SetUp() function. Note, it's easier to use the word -// "day" but really the rental window is 700 seconds, not 7 days. These tests -// have some coverage overlap with the ones above, but it is better to have -// these all grouped together to make sure we cover all of the server team's -// needs. -// ************************************************************************ -class Odk7DayTest : public OdkTimerRentalWindow, - public WithParamInterface { +/*****************************************************************************/ +// Streaming is the simplest use case. The user has three hours to watch the +// movie from the time of the rental. (See above for note on Use Case tests) +class ODKUseCase_Streaming : public ODKTimerTest { public: - Odk7DayTest() { - rental_window_duration_ = 700; - rental_window_start_ = 100u; + ODKUseCase_Streaming() { + // Rental duration = 3 hours hard. (use 300 for readability) + // Playback duration = 0 (unlimited) + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.rental_duration_seconds = 300; + timer_limits_.total_playback_duration_seconds = 0; + } +}; + +// Playback within rental duration. +TEST_F(ODKUseCase_Streaming, Case1) { + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow(), + EndOfRentalWindow()); +} + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_Streaming, Case2) { + // Allow playback within the rental window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback with stops/restarts within rental duration, last one exceeds rental +// duration. +TEST_F(ODKUseCase_Streaming, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 110, start_of_playback_ + 120, + EndOfRentalWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 200, EndOfRentalWindow()); +} + +// Playback within rental duration, restart exceeds rental duration. +TEST_F(ODKUseCase_Streaming, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + ForbidPlayback(EndOfRentalWindow() + 10); +} + +// Initial playback exceeds rental duration. +TEST_F(ODKUseCase_Streaming, Case5) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Streaming Quick Start. The user must start watching within 30 seconds, and +// then has three hours to finish. (See above for note on Use Case tests) +class ODKUseCase_StreamingQuickStart : public ODKTimerTest { + public: + ODKUseCase_StreamingQuickStart() { + // Rental duration = 30 seconds, soft. + // Playback duration = 3 hours (use 300 for readability) + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.rental_duration_seconds = 30; + timer_limits_.total_playback_duration_seconds = 300; + timer_limits_.soft_enforce_playback_duration = false; + + // A valid start of playback time. + start_of_playback_ = + GetSystemTime(timer_limits_.rental_duration_seconds - 10); + } +}; + +// Playback starts within rental duration, continues within playback duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case1) { + // As seen in the drawing, the playback window exceeds the rental window. + EXPECT_LE(EndOfRentalWindow(), EndOfPlaybackWindow()); + // Allow playback within the playback window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfPlaybackWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 110, start_of_playback_ + 120, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 200, EndOfPlaybackWindow()); +} + +// Initial playback exceeds rental duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case4) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is cutoff by the +// smaller of the two time limits. The first four cases start on day +// three. (See above for note on Use Case tests) +class ODKUseCase_SevenHardTwoHard_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenHardTwoHard_Start3() { + // Rental duration = 700, hard + // Playback duration = 200, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = false; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfPlaybackWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 125, EndOfPlaybackWindow()); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenHardTwoHard_Start6 + : public ODKUseCase_SevenHardTwoHard_Start3 { + public: + ODKUseCase_SevenHardTwoHard_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case5) { + // As seen in the drawing, the playback window is exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback with stops/restarts within playback duration, last one is terminated +// at the end of the rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, + EndOfRentalWindow()); + // Allow playback that starts within rental window, but terminate at end of + // rental window. + LoadAndTerminatePlayback(start_of_playback_ + 90, EndOfRentalWindow()); +} + +// Playback within playback duration, restart exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + // Restart does not work after end of playback window. + ForbidPlayback(EndOfRentalWindow() + 10); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case8) { + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is cutoff by the +// rental duration time limits. The first four cases start on day three. (See +// above for note on Use Case tests) +class ODKUseCase_SevenHardTwoSoft_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenHardTwoSoft_Start3() { + // Rental duration = 700, hard + // Playback duration = 200, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = true; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfRentalWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case2) { + // Allow playback within the playback window, and a little after. + // Timer expires at end of rental window. + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 50, + EndOfRentalWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 125, EndOfPlaybackWindow() + 50, + EndOfRentalWindow()); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenHardTwoSoft_Start6 + : public ODKUseCase_SevenHardTwoSoft_Start3 { + public: + ODKUseCase_SevenHardTwoSoft_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case5) { + // As seen in the drawing, the playback window is exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, + EndOfRentalWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 90, EndOfRentalWindow()); +} + +// Playback within playback duration, restart exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + // Restart does not work after end of playback window. + ForbidPlayback(EndOfRentalWindow() + 10); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case8) { + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is cutoff by the +// playback duration. The first four cases start on day three. (See above for +// note on Use Case tests) +class ODKUseCase_SevenSoftTwoHard_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenSoftTwoHard_Start3() { + // Rental duration = 700, hard + // Playback duration = 300, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = false; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfPlaybackWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 125, EndOfPlaybackWindow()); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenSoftTwoHard_Start6 + : public ODKUseCase_SevenSoftTwoHard_Start3 { + public: + ODKUseCase_SevenSoftTwoHard_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case5) { + // As seen in the drawing, the playback window exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback to continue beyond the rental window, but not beyond the + // playback window. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 90, EndOfPlaybackWindow()); +} + +// Restart exceeds rental duration, playback exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(EndOfRentalWindow() + 10, EndOfPlaybackWindow()); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case8) { + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is not cutoff, +// but restarts are not allowed after playback duration. The first four cases +// start on day three. (See above for note on Use Case tests) +class ODKUseCase_SevenSoftTwoSoft_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenSoftTwoSoft_Start3() { + // Rental duration = 700, hard + // Playback duration = 200, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = true; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, 0); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case2) { + // Allow playback within the playback window, and beyond. No timer limit. + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 50, 0); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, 0); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, 0); + LoadAndAllowPlayback(start_of_playback_ + 125, EndOfPlaybackWindow() + 50, 0); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, 0); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenSoftTwoSoft_Start6 + : public ODKUseCase_SevenSoftTwoSoft_Start3 { + public: + ODKUseCase_SevenSoftTwoSoft_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case5) { + // As seen in the drawing, the playback window exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow() + 25); + // Allow playback past the rental window, but within the playback window. + LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow() + 25, 0); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// rental and playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, 0); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, 0); + LoadAndAllowPlayback(start_of_playback_ + 100, EndOfPlaybackWindow() + 50, 0); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, 0); + // Allow playback to start after end of rental window, and continue after + // playback window. + LoadAndAllowPlayback(EndOfRentalWindow() + 10, EndOfPlaybackWindow() + 10, 0); + // But forbid restart after playback window. + ForbidPlayback(EndOfPlaybackWindow() + 20); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case8) { + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 100, 0); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +class RenewalTest : public ODKTimerTest { + protected: + // Verify that a renewal can be processed and playback may continue from + // |start| to |stop|. If |cutoff| is not 0, then expect the timer to expire + // at that time. If |cutoff| is 0, expect the timer is not set. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan bar + // with a green check mark. + void RenewAndContinue(uint64_t start, uint64_t stop, uint64_t cutoff, + uint64_t renewal_duration_seconds) { + uint64_t timer_value; + RenewAndContinue(start, stop, cutoff, renewal_duration_seconds, + &timer_value); + } + + // Verify that a renewal can be processed and playback may continue from + // |start| to |stop|. If |cutoff| is not 0, then expect the timer to expire + // at that time. If |cutoff| is 0, expect the timer is not set. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan bar + // with a green check mark. + // This does the work of the function above, except it allows the caller to + // explicitly set the timer_value_pointer. The timer_value_pointer can be a + // nullptr. + void RenewAndContinue(uint64_t start, uint64_t stop, uint64_t cutoff, + uint64_t renewal_duration_seconds, + uint64_t* timer_value_pointer) { + ASSERT_LT(start, stop); + if (cutoff > 0) ASSERT_LE(stop, cutoff); + // We'll fake instantaneous renewal requests. Flight time not important. + clock_values_.time_of_renewal_request = start; + OEMCryptoResult result = ODK_ComputeRenewalDuration( + &timer_limits_, &clock_values_, start, renewal_duration_seconds, + timer_value_pointer); + // After first playback, the license is active. + EXPECT_EQ(clock_values_.status, kActive); + EXPECT_EQ(clock_values_.time_when_timer_expires, cutoff); + if (cutoff > 0) { // If we expect the timer to be set. + EXPECT_EQ(result, ODK_SET_TIMER); + if (timer_value_pointer != nullptr) + EXPECT_EQ(*timer_value_pointer, cutoff - start); + } else { + EXPECT_EQ(result, ODK_DISABLE_TIMER); + } + EXPECT_EQ(ODK_UpdateLastPlaybackTime(start, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(start); + const uint64_t mid = (start + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(mid); + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(stop); + } + + // Verify that a renewal can be processed and attempt to play from |start| to + // after |cutoff|. Verify that we are allowed playback from |start| to + // |cutoff|, but playback not allowed after |cutoff|. If you refer to the + // diagrams in "License Duration and Renewal", this tests a cyan bar with a + // black X. + void RenewAndTerminate(uint64_t start, uint64_t cutoff, + uint64_t renewal_duration_seconds) { + RenewAndTerminate(start, cutoff, cutoff + 100, renewal_duration_seconds); + } + + // Verify that a renewal can be processed and attempt to play from |start| to + // |stop|. Verify that we are allowed playback from |start| to |cutoff|, but + // playback not allowed after |cutoff|. If you refer to the diagrams in + // "License Duration and Renewal", this tests a cyan bar with a black X. This + // assumes that |cutoff| is between |start| and |stop|. + void RenewAndTerminate(uint64_t start, uint64_t cutoff, uint64_t stop, + uint64_t renewal_duration_seconds) { + ASSERT_LT(start, cutoff); + ASSERT_LT(cutoff, stop); + RenewAndContinue(start, cutoff, cutoff, renewal_duration_seconds); + EXPECT_EQ( + ODK_UpdateLastPlaybackTime(cutoff + 1, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + const uint64_t mid = (cutoff + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues( + cutoff); // times do not change if playback was not allowed. + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + } + + // Verify that a renewal can be processed and playback may start from + // |start| to |stop|. If |cutoff| is not 0, then expect the timer to expire + // at that time. If |cutoff| is 0, expect the timer is not set. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan bar + // with a green check mark. This is different from the previous functions, + // because the renewal is loaded before the first playback. + void RenewAndStart(uint64_t start, uint64_t stop, uint64_t cutoff, + uint64_t renewal_duration_seconds) { + ASSERT_LT(start, stop); + if (cutoff > 0) ASSERT_LE(stop, cutoff); + // We'll fake instantaneous renewal requests. Flight time not important. + clock_values_.time_of_renewal_request = start; + uint64_t timer_value; + OEMCryptoResult result = + ODK_ComputeRenewalDuration(&timer_limits_, &clock_values_, start, + renewal_duration_seconds, &timer_value); + EXPECT_EQ(clock_values_.time_when_timer_expires, cutoff); + if (cutoff > 0) { // If we expect the timer to be set. + EXPECT_EQ(result, ODK_SET_TIMER); + EXPECT_EQ(timer_value, cutoff - start); + } else { + EXPECT_EQ(result, ODK_DISABLE_TIMER); + } + AllowPlayback(start, stop, cutoff); + } +}; + +// License with Renewal, limited by playback duration. (See above for note on +// Use Case tests) These tests are parameterized. If the parameter is 0, we +// limit the playback duration. If the parameter is 1, we limit the rental +// duration. The behavior is basically the same. +class ODKUseCase_LicenseWithRenewal + : public RenewalTest, + public ::testing::WithParamInterface { + public: + ODKUseCase_LicenseWithRenewal() { + // Either Playback or rental duration = 2 days hard + if (GetParam() == 0) { + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.rental_duration_seconds = 2000; + } else { + timer_limits_.soft_enforce_playback_duration = false; + timer_limits_.total_playback_duration_seconds = 2000; + } + timer_limits_.initial_renewal_duration_seconds = 50; + } + + void SetUp() override { + RenewalTest::SetUp(); + renewal_interval_ = + timer_limits_.initial_renewal_duration_seconds - kGracePeriod; + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, next_renewal, + next_renewal + kGracePeriod); + } + + uint64_t playback_end_restriction() { + if (GetParam() == 0) { + return EndOfRentalWindow(); + } else { + return EndOfPlaybackWindow(); + } } protected: - void SetUp() override { - OdkTimerRentalWindow::SetUp(); - server_expiry_ = GetParam(); - const uint64_t playback_duration = 200; - // Beginning of rental window. it is unusual to start it in the future, - // but it is a supported use case, so we'll test it here. - timer_limits_.earliest_playback_start_seconds = rental_window_start_; - // The rental window is 700 long. - timer_limits_.latest_playback_start_seconds = - timer_limits_.earliest_playback_start_seconds + rental_window_duration_; - // Playback duration is 200 starting from first playback. - timer_limits_.initial_playback_duration_seconds = playback_duration; - if (server_expiry_.soft_rental) { - // The license duration limits any restart. For soft rental window, the - // server will set this to the rental end + playback duration. - // License duration is in seconds since license signed. - timer_limits_.license_duration_seconds = - timer_limits_.latest_playback_start_seconds + playback_duration; - } else { - // The license duration limits any restart. For hard rental window, the - // server will set this to the rental end. - // License duration is in seconds since license signed. - timer_limits_.license_duration_seconds = - timer_limits_.latest_playback_start_seconds; - } - timer_limits_.soft_expiry = server_expiry_.soft_playback; - } - ServerExpiry server_expiry_; + // How long to wait before we load the next renewal. i.e. cutoff - + // kGracePeriod. This is because the renewal_duration includes the grace + // period. + uint64_t renewal_interval_; }; -TEST_P(Odk7DayTest, StartDay3) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t three_days = 300u; - const uint64_t start_time = system_time(rental_window_start_ + three_days); - const uint64_t cutoff_time = - start_time + timer_limits_.initial_playback_duration_seconds; - if (server_expiry_.soft_playback) { - // If the playback expiry is soft, then the timer is disabled. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, - &clock_values_, &timer_value)); - } else { - // If the playback expiry is hard, then the timer should be set. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); - } - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - // Try to play before the cutoff time is valid. - const uint64_t valid_play_time = cutoff_time - 50; - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - if (!server_expiry_.soft_playback) { - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); +// Playback within rental duration and renewal duration. +TEST_P(ODKUseCase_LicenseWithRenewal, Case1) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); } } -TEST_P(Odk7DayTest, StartDay3Reload) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t three_days = 300u; - const uint64_t start_time = system_time(rental_window_start_ + three_days); - const uint64_t cutoff_time = - start_time + timer_limits_.initial_playback_duration_seconds; - EXPECT_NE(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - // ----------------------------------------------------------------------- - // Try to reload and play before the cutoff time is valid. - uint64_t valid_restart_time = cutoff_time - 10; - ReloadClock(valid_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - if (server_expiry_.soft_playback) { - // If the playback expiry is soft, then the timer is disabled. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - } else { - // If the playback expiry is hard, then the timer should be set. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - valid_restart_time, timer_value, kTolerance); +// Playback within rental duration, last renewal_interval_ exceeds renewal +// duration. +TEST_P(ODKUseCase_LicenseWithRenewal, Case2) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); } + // We attempt to continue playing beyond the renewal renewal_interval_. + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); +} - // Try to play before the cutoff time is valid. - const uint64_t valid_play_time = cutoff_time - 1; - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - if (!server_expiry_.soft_playback) { - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); +// Playback interrupted by late renewal. +TEST_P(ODKUseCase_LicenseWithRenewal, Case3) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); } + // We attempt to continue playing beyond the renewal renewal_interval_. + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + next_renewal + kGracePeriod, // stop: when timer expires. + timer_limits_.initial_renewal_duration_seconds); - // Try to play after the cutoff time is valid for soft expiry, but not hard. - const uint64_t late_play_time = cutoff_time + 1; - if (server_expiry_.soft_playback) { - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - // Last decrypt should change because we were allowed to decrypt. - EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); - } else { - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); + const uint64_t late_renewal = next_renewal + kGracePeriod + 10; + next_renewal = late_renewal + renewal_interval_; + RenewAndContinue(late_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); +} + +// Playback & restart within playback duration and renewal duration. Note: this +// simulates reloading a persistent license, and then reloading the last +// renewal. +TEST_P(ODKUseCase_LicenseWithRenewal, Case4) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 2; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); } - // First decrypt should NOT change for either soft or hard. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - - // ----------------------------------------------------------------------- - // Try to reload after the cutoff time is not valid for soft or hard - // playback expiry. - const uint64_t late_restart_time = cutoff_time + 1; - ReloadClock(late_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - - // Calling UpdateLastPlaybackTimer should not be done, but if it were done, - // it should also fail. - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, - &clock_values_)); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should not change. - if (server_expiry_.soft_playback) { - EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); - } else { - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); + // Reload license after timer expired. + uint64_t reload_time = next_renewal + kGracePeriod + 100; + next_renewal = reload_time + renewal_interval_; + ReloadLicense(reload_time); + RenewAndStart(reload_time, next_renewal, next_renewal + kGracePeriod, + timer_limits_.initial_renewal_duration_seconds); + for (int i = 0; i < 2; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); } } -TEST_P(Odk7DayTest, StartDay6Reload) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t six_days = 600u; - const uint64_t start_time = system_time(rental_window_start_ + six_days); - const uint64_t cutoff_time = - server_expiry_.soft_rental - ? - // If the rental expiry is soft, we can continue playing and - // reloading for the full playback duration. - start_time + timer_limits_.initial_playback_duration_seconds - // If the rental expiry is hard, we can reload only within the - // rental window. - : system_time(timer_limits_.latest_playback_start_seconds); - // Let's double check that math: - if (server_expiry_.soft_rental) { - // If that was not clear, the cutoff = 100 start of window + 600 start time - // + 200 playback duration... - EXPECT_EQ(system_time(900u), cutoff_time); - } else { - // ...and for hard rental, the cutoff = 100 start of window + 700 rental - // duration. - EXPECT_EQ(system_time(800u), cutoff_time); - } - - if (server_expiry_.soft_playback) { - // If the playback expiry is soft, then the timer is disabled. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, - &clock_values_, &timer_value)); - } else { - // If the playback expiry is hard, then the timer should be set. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - start_time, timer_value, kTolerance); - } - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - EXPECT_EQ(start_time, clock_values_.time_of_last_decrypt); - // Try to play before the cutoff time is valid. - uint64_t valid_play_time = cutoff_time - 50; - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - if (!server_expiry_.soft_playback) { - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - } - - // ----------------------------------------------------------------------- - // Try to reload and play before the cutoff time is valid. - uint64_t valid_restart_time = cutoff_time - 10; - ReloadClock(valid_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - if (server_expiry_.soft_playback) { - // If the playback expiry is soft, then the timer is disabled. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - } else { - // If the playback expiry is hard, then the timer should be set. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(valid_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - EXPECT_NEAR(cutoff_time - valid_restart_time, timer_value, kTolerance); - } - - // Try to play before the cutoff time is valid. - valid_play_time = cutoff_time - 1; - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(valid_play_time, &timer_limits_, - &clock_values_)); - EXPECT_EQ(kActive, clock_values_.status); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should change. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - if (!server_expiry_.soft_playback) { - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - } - - // Try to play after the cutoff time is valid for soft expiry, but not hard. - const uint64_t late_play_time = cutoff_time + 1; - if (server_expiry_.soft_playback) { - EXPECT_EQ(OEMCrypto_SUCCESS, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - // Last decrypt should change because we were allowed to decrypt. - EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); - } else { - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_play_time, &timer_limits_, - &clock_values_)); - // Last decrypt should NOT change because we were not allowed to decrypt. - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, kTolerance); - } - // First decrypt should NOT change for either soft or hard. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - - // ----------------------------------------------------------------------- - // Try to reload after the cutoff time is not valid for soft or hard - // playback expiry. - const uint64_t late_restart_time = cutoff_time + 2; - ReloadClock(late_restart_time); - EXPECT_EQ(kActive, clock_values_.status); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(late_restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - - // Calling UpdateLastPlaybackTimer should not be done, but if it were done, - // it should also fail. - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_UpdateLastPlaybackTime(late_restart_time, &timer_limits_, - &clock_values_)); - // First decrypt should NOT change. - EXPECT_EQ(start_time, clock_values_.time_of_first_decrypt); - // Last decrypt should not change. - if (server_expiry_.soft_playback) { - EXPECT_EQ(late_play_time, clock_values_.time_of_last_decrypt); - } else { - EXPECT_EQ(valid_play_time, clock_values_.time_of_last_decrypt); - } +// Playback within renewal duration, but exceeds playback duration. +TEST_P(ODKUseCase_LicenseWithRenewal, Case5) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + do { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); + } while (next_renewal + renewal_interval_ + kGracePeriod < + playback_end_restriction()); + // Attemt playing beyond the playback window. + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + playback_end_restriction(), // stop: when timer expires. + timer_limits_.initial_renewal_duration_seconds); } -// This test explicitly shows the difference between hard and soft rental -// expiry. This is not an OEMCrypto concept, but it is used on the serer, and -// this test verifies the logic is handled correctly when we translate it into -// OEMCrypto concepts. -TEST_P(Odk7DayTest, StartDay6ReloadDay7) { - uint64_t timer_value = 0; - // Starting playback within the window should work. - const uint64_t six_days = 600u; - const uint64_t seven_days = 700u; - const uint64_t start_time = system_time(rental_window_start_ + six_days); - EXPECT_NE(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); +// Renewal duration increases over time. +TEST_P(ODKUseCase_LicenseWithRenewal, Case6) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + do { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); + // Renewal_Interval_ increases with each renewal. + renewal_interval_ += 100; + } while (next_renewal + renewal_interval_ + kGracePeriod < + playback_end_restriction()); + // Attemt playing beyond the playback window: + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + playback_end_restriction(), // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); +} - // Reload time is 1 second after end of rental window. - const uint64_t restart_time = - system_time(timer_limits_.latest_playback_start_seconds + 1); - ReloadClock(restart_time); - if (server_expiry_.soft_rental) { - if (server_expiry_.soft_playback) { - // If the playback expiry is soft, then the timer is disabled. - EXPECT_EQ(ODK_DISABLE_TIMER, - ODK_AttemptFirstPlayback(restart_time, &timer_limits_, - &clock_values_, &timer_value)); - } else { - const uint64_t cutoff_time = - start_time + timer_limits_.initial_playback_duration_seconds; - // If the playback expiry is hard, then the timer should be set. - EXPECT_EQ(ODK_SET_TIMER, - ODK_AttemptFirstPlayback(restart_time, &timer_limits_, - &clock_values_, &timer_value)); - EXPECT_NEAR(cutoff_time, clock_values_.time_when_timer_expires, - kTolerance); - EXPECT_NEAR(cutoff_time - restart_time, timer_value, kTolerance); - } - } else { - // For hard rental expiry, reloading after the rental window fails. - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(restart_time, &timer_limits_, - &clock_values_, &timer_value)); +// Increasing renewal duration, playback exceeds last renewal. +// This is a mix between case 2 and case 6. +TEST_P(ODKUseCase_LicenseWithRenewal, Case7) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); + // Renewal_Interval_ increases with each renewal. + renewal_interval_ += 100; } + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + next_renewal + kGracePeriod, // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); } -TEST_P(Odk7DayTest, StartDay8) { - uint64_t timer_value = 0; - // Starting playback after the rental window should not work. - const uint64_t eight_days = 800u; - const uint64_t start_time = system_time(rental_window_start_ + eight_days); - EXPECT_EQ(ODK_TIMER_EXPIRED, - ODK_AttemptFirstPlayback(start_time, &timer_limits_, &clock_values_, - &timer_value)); - EXPECT_EQ(kUnused, clock_values_.status); - EXPECT_EQ(0u, clock_values_.time_of_first_decrypt); - EXPECT_EQ(0u, clock_values_.time_of_last_decrypt); - // Calling UpdateLastPlaybackTimer should not be done, but if it were done, - // it should also fail. - EXPECT_EQ(ODK_TIMER_EXPIRED, ODK_UpdateLastPlaybackTime( - start_time, &timer_limits_, &clock_values_)); - EXPECT_EQ(kUnused, clock_values_.status); - EXPECT_EQ(0u, clock_values_.time_of_first_decrypt); - EXPECT_EQ(0u, clock_values_.time_of_last_decrypt); +// This is just like Case1, except we use a null pointer for the timer values in +// RenewAndContinue. It is not shown in the use case document. +TEST_P(ODKUseCase_LicenseWithRenewal, NullPointerTest) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + const uint64_t start = next_renewal; + const uint64_t stop = start + renewal_interval_; + const uint64_t cutoff = stop + kGracePeriod; + const uint64_t renewal_duration_seconds = + timer_limits_.initial_renewal_duration_seconds; + uint64_t* timer_value_pointer = nullptr; + RenewAndContinue(start, stop, cutoff, renewal_duration_seconds, + timer_value_pointer); } -INSTANTIATE_TEST_CASE_P(OdkSoftHard, Odk7DayTest, - Values(ServerExpiry({true, true}), - ServerExpiry({true, false}), - ServerExpiry({false, true}), - ServerExpiry({false, false}))); +INSTANTIATE_TEST_CASE_P(RestrictRenewal, ODKUseCase_LicenseWithRenewal, + ::testing::Values(0, 1)); -// ************************************************************************ +// Limited Duration License. (See above for notes on Use Case tests). The user +// has 15 minutes to begin watching the movie. If a renewal is not received, +// playback is terminated after 30 seconds. If a renewal is received, playback +// may continue for two hours from playback start. +class ODKUseCase_LimitedDurationLicense : public RenewalTest { + public: + ODKUseCase_LimitedDurationLicense() { + renewal_delay_ = 30u; + time_of_renewal_ = start_of_playback_ + renewal_delay_; -// ************************************************************************ -// TODO(b/140765031): Cover all tests in Use Cases document. -// Limited Duration License -// 7 day with renewal. -// Streaming with renewal -// Persistent with renewal + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.rental_duration_seconds = 150; // 15 minutes. + timer_limits_.soft_enforce_playback_duration = false; + timer_limits_.total_playback_duration_seconds = 2000; // Two hours. + timer_limits_.initial_renewal_duration_seconds = + renewal_delay_ + kGracePeriod; + } + uint64_t renewal_delay_; + uint64_t time_of_renewal_; +}; -} // namespace odk_test +// Playback started within rental window and continues. +TEST_F(ODKUseCase_LimitedDurationLicense, Case1) { + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, time_of_renewal_, + time_of_renewal_ + kGracePeriod); + const uint64_t renewal_duration = 2000; // two hour renewal. + const uint64_t play_for_one_hour = GetSystemTime(1000); + RenewAndContinue(time_of_renewal_, // start: when renewal is loaded. + play_for_one_hour, // stop: expect play allowed. + EndOfPlaybackWindow(), // cutoff: when timer expires. + renewal_duration); +} + +// Playback started after rental duration. +TEST_F(ODKUseCase_LimitedDurationLicense, Case2) { + start_of_playback_ = EndOfRentalWindow() + 1; + EXPECT_NEAR(start_of_playback_, GetSystemTime(150), 5); + ForbidPlayback(start_of_playback_); +} + +// Playback started within rental window but renewal not received. +TEST_F(ODKUseCase_LimitedDurationLicense, Case3) { + // Allow playback within the initial renewal window. + LoadAndTerminatePlayback(start_of_playback_, time_of_renewal_ + kGracePeriod); +} + +// Playback started within rental window and continues. +TEST_F(ODKUseCase_LimitedDurationLicense, Case4) { + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, time_of_renewal_, + time_of_renewal_ + kGracePeriod); + const uint64_t renewal_duration = 2000; // two hour renewal. + RenewAndTerminate(time_of_renewal_, // start: when renewal is loaded. + EndOfPlaybackWindow(), // stop: when timer expires. + renewal_duration); +} + +// Playback may be restarted. +TEST_F(ODKUseCase_LimitedDurationLicense, Case5) { + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, time_of_renewal_, + time_of_renewal_ + kGracePeriod); + const uint64_t renewal_duration = 2000; // two hour renewal. + const uint64_t play_for_one_hour = GetSystemTime(1000); + RenewAndContinue(time_of_renewal_, // start: when renewal is loaded. + play_for_one_hour, // stop: expect play allowed. + EndOfPlaybackWindow(), // cutoff: when timer expires. + renewal_duration); + + uint64_t reload_time = play_for_one_hour + 100; + ReloadLicense(reload_time); + RenewAndStart(reload_time, EndOfPlaybackWindow(), EndOfPlaybackWindow(), + renewal_duration); + // But not one second more. + EXPECT_EQ(ODK_UpdateLastPlaybackTime(EndOfPlaybackWindow() + 1, + &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(EndOfPlaybackWindow()); +} + +// Verify that the backwards compatible function, ODK_InitializeV15Values, can +// set up the clock values and timer limits. +TEST_F(RenewalTest, V15Test) { + const uint32_t key_duration = 25; + ODK_NonceValues nonce_values; + const uint64_t license_loaded = GetSystemTime(10); + EXPECT_EQ( + ODK_InitializeV15Values(&timer_limits_, &clock_values_, &nonce_values, + key_duration, license_loaded), + OEMCrypto_SUCCESS); + const uint64_t later_on = GetSystemTime(200); + TerminatePlayback(license_loaded, license_loaded + key_duration, later_on); + const uint32_t new_key_duration = 100; + RenewAndTerminate(later_on, later_on + new_key_duration, new_key_duration); +} +} // namespace diff --git a/libwvdrmengine/oemcrypto/ref/Android.mk b/libwvdrmengine/oemcrypto/ref/Android.mk index f65cffaa..013b70a4 100644 --- a/libwvdrmengine/oemcrypto/ref/Android.mk +++ b/libwvdrmengine/oemcrypto/ref/Android.mk @@ -11,8 +11,6 @@ LOCAL_SRC_FILES:= \ src/oemcrypto_keybox_ref.cpp \ src/oemcrypto_keybox_testkey.cpp \ src/oemcrypto_ref.cpp \ - src/oemcrypto_nonce_table.cpp \ - src/oemcrypto_old_usage_table_ref.cpp \ src/oemcrypto_rsa_key_shared.cpp \ src/oemcrypto_session.cpp \ src/oemcrypto_session_key_table.cpp \ diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_device_properties_prov30.cpp b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_device_properties_prov30.cpp index fe78fe61..6fd2269f 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_device_properties_prov30.cpp +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_device_properties_prov30.cpp @@ -53,7 +53,7 @@ class Prov30CryptoEngine : public CryptoEngine { if (kOEMPublicCertSize == 0) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } - if (public_cert_length == NULL) { + if (public_cert_length == nullptr) { return OEMCrypto_ERROR_UNKNOWN_FAILURE; } if (*public_cert_length < kOEMPublicCertSize) { @@ -61,7 +61,7 @@ class Prov30CryptoEngine : public CryptoEngine { return OEMCrypto_ERROR_SHORT_BUFFER; } *public_cert_length = kOEMPublicCertSize; - if (public_cert == NULL) { + if (public_cert == nullptr) { return OEMCrypto_ERROR_SHORT_BUFFER; } memcpy(public_cert, kOEMPublicCert, kOEMPublicCertSize); diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_ref.cpp b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_ref.cpp index 82b88de8..4b401ab6 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_ref.cpp +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_ref.cpp @@ -7,7 +7,6 @@ #include "oemcrypto_engine_ref.h" #include -#include #include #include #include @@ -17,6 +16,7 @@ #include #include +#include "clock.h" #include "keys.h" #include "log.h" #include "oemcrypto_key_ref.h" @@ -42,11 +42,15 @@ CryptoEngine::~CryptoEngine() { } bool CryptoEngine::Initialize() { + std::string file_path = GetUsageTimeFileFullPath(); + LoadOfflineTimeInfo(file_path); usage_table_.reset(MakeUsageTable()); return true; } void CryptoEngine::Terminate() { + std::string file_path = GetUsageTimeFileFullPath(); + SaveOfflineTimeInfo(file_path); std::unique_lock lock(session_table_lock_); ActiveSessions::iterator it; for (it = sessions_.begin(); it != sessions_.end(); ++it) { @@ -88,55 +92,67 @@ SessionContext* CryptoEngine::FindSession(SessionId sid) { if (it != sessions_.end()) { return it->second; } - return NULL; + return nullptr; } -time_t CryptoEngine::OnlineTime() { +int64_t CryptoEngine::OnlineTime() { // Use the monotonic clock for times that don't have to be stable across // device boots. - std::chrono::steady_clock clock; - return clock.now().time_since_epoch() / std::chrono::seconds(1); + int64_t now = wvcdm::Clock().GetCurrentTime(); + static int64_t then = now; + if (now < then) now = then; + then = now; + return now; } -time_t CryptoEngine::RollbackCorrectedOfflineTime() { - struct TimeInfo { - // The max time recorded through this function call. - time_t previous_time; - // If the wall time is rollbacked to before the previous_time, this member - // is updated to reflect the offset. - time_t rollback_offset; - // Pad the struct so that TimeInfo is a multiple of 16. - uint8_t padding[16 - (2 * sizeof(time_t)) % 16]; - }; +int64_t CryptoEngine::RollbackCorrectedOfflineTime() { + // Add any time offsets in the past to the current time. + int64_t current_time = OnlineTime() + offline_time_info_.rollback_offset; + // Write time info to disk if kTimeInfoUpdateWindowInSeconds has elapsed since + // last write. + if (current_time - offline_time_info_.previous_time > + kTimeInfoUpdateWindowInSeconds) { + std::string file_path = GetUsageTimeFileFullPath(); + SaveOfflineTimeInfo(file_path); + } + return current_time; +} - std::vector encrypted_buffer(sizeof(TimeInfo)); - std::vector clear_buffer(sizeof(TimeInfo)); - TimeInfo time_info; - memset(&time_info, 0, sizeof(time_info)); - // Use the device key for encrypt/decrypt. - const std::vector& key = DeviceRootKey(); - - std::unique_ptr file; - std::string path; - // Note: this path is OK for a real implementation, but using security level 1 - // would be better. +std::string CryptoEngine::GetUsageTimeFileFullPath() const { + std::string file_path; + // Note: file path is OK for a real implementation, but using security + // level 1 would be better. // TODO(fredgc, jfore): Address how this property is presented to the ref. - // For now, the path is empty. + // For now, the file path is empty. /*if (!wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL3, - &path)) { - LOGE("RollbackCorrectedOfflineTime: Unable to get base path"); + &file_path)) { + LOGE("RollbackCorrectedOfflineTime: Unable to get base path"); }*/ - std::string filename = path + "StoredUsageTime.dat"; + return file_path + kStoredUsageTimeFileName; +} +bool CryptoEngine::LoadOfflineTimeInfo(const std::string& file_path) { + memset(&offline_time_info_, 0, sizeof(TimeInfo)); wvcdm::FileSystem* file_system = file_system_.get(); - if (file_system->Exists(filename)) { - // Load time info from previous call to this function. - file = file_system->Open(filename, wvcdm::FileSystem::kReadOnly); + if (file_system->Exists(file_path)) { + std::vector encrypted_buffer(sizeof(TimeInfo)); + std::vector clear_buffer(sizeof(TimeInfo)); + + KeyboxError error_code = ValidateKeybox(); + if (error_code != NO_ERROR) { + LOGE("Keybox is invalid: %d", error_code); + return false; + } + // Use the device key for encrypt/decrypt. + const std::vector& key = DeviceRootKey(); + std::unique_ptr file = + file_system->Open(file_path, wvcdm::FileSystem::kReadOnly); if (!file) { LOGE("RollbackCorrectedOfflineTime: File open failed: %s", - filename.c_str()); - return time(NULL); + file_path.c_str()); + return false; } + // Load time info from previous call. file->Read(reinterpret_cast(&encrypted_buffer[0]), sizeof(TimeInfo)); // Decrypt the encrypted TimeInfo buffer. AES_KEY aes_key; @@ -144,48 +160,71 @@ time_t CryptoEngine::RollbackCorrectedOfflineTime() { std::vector iv(wvoec::KEY_IV_SIZE, 0); AES_cbc_encrypt(&encrypted_buffer[0], &clear_buffer[0], sizeof(TimeInfo), &aes_key, iv.data(), AES_DECRYPT); - memcpy(&time_info, &clear_buffer[0], sizeof(TimeInfo)); - } + memcpy(&offline_time_info_, &clear_buffer[0], sizeof(TimeInfo)); - time_t current_time; - // Add any time offsets in the past to the current time. - current_time = time(NULL) + time_info.rollback_offset; - if (time_info.previous_time > current_time) { - // Time has been rolled back. - // Update the rollback offset. - time_info.rollback_offset += time_info.previous_time - current_time; - // Keep current time at previous recorded time. - current_time = time_info.previous_time; + // Detect offline time rollback after loading from disk. + // Add any time offsets in the past to the current time. + int64_t current_time = OnlineTime() + offline_time_info_.rollback_offset; + if (offline_time_info_.previous_time > current_time) { + // Current time is earlier than the previously saved time. Time has been + // rolled back. Update the rollback offset. + offline_time_info_.rollback_offset += + offline_time_info_.previous_time - current_time; + // Keep current time at previous recorded time. + current_time = offline_time_info_.previous_time; + } + // The new previous_time will either stay the same or move forward. + offline_time_info_.previous_time = current_time; } + return true; +} + +bool CryptoEngine::SaveOfflineTimeInfo(const std::string& file_path) { + // Add any time offsets in the past to the current time. If there was an + // earlier offline rollback, the rollback offset will be updated in + // LoadOfflineTimeInfo(). It guarantees that the current time to be saved + // will never go back. + int64_t current_time = OnlineTime() + offline_time_info_.rollback_offset; // The new previous_time will either stay the same or move forward. - time_info.previous_time = current_time; + if (current_time > offline_time_info_.previous_time) + offline_time_info_.previous_time = current_time; + + KeyboxError error_code = ValidateKeybox(); + if (error_code != NO_ERROR) { + LOGE("Keybox is invalid: %d", error_code); + return false; + } + // Use the device key for encrypt/decrypt. + const std::vector& key = DeviceRootKey(); + std::vector encrypted_buffer(sizeof(TimeInfo)); + std::vector clear_buffer(sizeof(TimeInfo)); // Copy updated data and encrypt the buffer. - memcpy(&clear_buffer[0], &time_info, sizeof(TimeInfo)); + memcpy(&clear_buffer[0], &offline_time_info_, sizeof(TimeInfo)); AES_KEY aes_key; AES_set_encrypt_key(&key[0], 128, &aes_key); std::vector iv(wvoec::KEY_IV_SIZE, 0); AES_cbc_encrypt(&clear_buffer[0], &encrypted_buffer[0], sizeof(TimeInfo), &aes_key, iv.data(), AES_ENCRYPT); + std::unique_ptr file; + wvcdm::FileSystem* file_system = file_system_.get(); // Write the encrypted buffer to disk. file = file_system->Open( - filename, wvcdm::FileSystem::kCreate | wvcdm::FileSystem::kTruncate); + file_path, wvcdm::FileSystem::kCreate | wvcdm::FileSystem::kTruncate); if (!file) { LOGE("RollbackCorrectedOfflineTime: File open failed: %s", - filename.c_str()); - return time(NULL); + file_path.c_str()); + return false; } file->Write(reinterpret_cast(&encrypted_buffer[0]), sizeof(TimeInfo)); - - // Return time with offset. - return current_time; + return true; } bool CryptoEngine::NonceCollision(uint32_t nonce) { - for (const auto & session_pair : sessions_) { + for (const auto& session_pair : sessions_) { const SessionContext* session = session_pair.second; - if (session->NonceCollision(nonce)) return true; + if (nonce == session->nonce()) return true; } return false; } @@ -199,45 +238,45 @@ OEMCrypto_HDCP_Capability CryptoEngine::config_maximum_hdcp_capability() { } OEMCryptoResult CryptoEngine::SetDestination( - OEMCrypto_DestBufferDesc* out_description, size_t data_length, + const OEMCrypto_DestBufferDesc& out_description, size_t data_length, uint8_t subsample_flags) { size_t max_length = 0; - switch (out_description->type) { + switch (out_description.type) { case OEMCrypto_BufferType_Clear: - destination_ = out_description->buffer.clear.address; - max_length = out_description->buffer.clear.max_length; + destination_ = out_description.buffer.clear.address; + max_length = out_description.buffer.clear.address_length; break; case OEMCrypto_BufferType_Secure: destination_ = - reinterpret_cast(out_description->buffer.secure.handle) + - out_description->buffer.secure.offset; - max_length = out_description->buffer.secure.max_length - - out_description->buffer.secure.offset; + reinterpret_cast(out_description.buffer.secure.handle) + + out_description.buffer.secure.offset; + max_length = out_description.buffer.secure.handle_length - + out_description.buffer.secure.offset; break; case OEMCrypto_BufferType_Direct: // Direct buffer type is only used on some specialized devices where // oemcrypto has a direct connection to the screen buffer. It is not, // for example, supported on Android. - destination_ = NULL; + destination_ = nullptr; break; default: return OEMCrypto_ERROR_INVALID_CONTEXT; } - size_t max_allowed = max_output_size(); + const size_t max_allowed = max_sample_size(); if (max_allowed > 0 && (max_allowed < max_length || max_allowed < data_length)) { LOGE("Output too large (or buffer too small)."); return OEMCrypto_ERROR_OUTPUT_TOO_LARGE; } - if (out_description->type != OEMCrypto_BufferType_Direct && + if (out_description.type != OEMCrypto_BufferType_Direct && max_length < data_length) { LOGE("[SetDestination(): OEMCrypto_ERROR_SHORT_BUFFER]"); return OEMCrypto_ERROR_SHORT_BUFFER; } adjust_destination(out_description, data_length, subsample_flags); - if ((out_description->type != OEMCrypto_BufferType_Direct) && - (destination_ == NULL)) { + if ((out_description.type != OEMCrypto_BufferType_Direct) && + (destination_ == nullptr)) { return OEMCrypto_ERROR_INVALID_CONTEXT; } return OEMCrypto_SUCCESS; diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_ref.h b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_ref.h index cc132aa3..dd39e16f 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_ref.h +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_ref.h @@ -29,8 +29,24 @@ namespace wvoec_ref { typedef std::map ActiveSessions; +static const std::string kStoredUsageTimeFileName = "StoredUsageTime.dat"; + +typedef struct { + // The max time recorded + int64_t previous_time; + // If the wall time is rollbacked to before the previous_time, this member + // is updated to reflect the offset. + int64_t rollback_offset; + // Pad the struct so that TimeInfo is a multiple of 16. + uint8_t padding[16 - (2 * sizeof(time_t)) % 16]; +} TimeInfo; + class CryptoEngine { public: + static const uint32_t kApiVersion = 16; + static const uint32_t kMinorApiVersion = 0; + static const int64_t kTimeInfoUpdateWindowInSeconds = 300; + // This is like a factory method, except we choose which version to use at // compile time. It is defined in several source files. The build system // should choose which one to use by only linking in the correct one. @@ -87,12 +103,11 @@ class CryptoEngine { return kMaxSupportedOEMCryptoSessions; } - time_t OnlineTime(); + // The OEMCrypto system time. Prevents time rollback. + // TODO(b/145836634): Combine RollbackCorrectedOfflineTime with OnlineTime(). + int64_t SystemTime() { return RollbackCorrectedOfflineTime(); } - time_t RollbackCorrectedOfflineTime(); - - // Verify that this nonce does not collide with another nonce in any session's - // nonce table. + // Verify that this nonce does not collide with another nonce in any session. virtual bool NonceCollision(uint32_t nonce); // Returns the HDCP version currently in use. @@ -133,12 +148,15 @@ class CryptoEngine { return OEMCrypto_Keybox; } - virtual OEMCryptoResult get_oem_certificate(SessionContext* session, - uint8_t* public_cert, + virtual OEMCryptoResult get_oem_certificate(uint8_t* public_cert, size_t* public_cert_length) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } + virtual OEMCryptoResult load_oem_private_key(SessionContext* session) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + // Used for OEMCrypto_IsAntiRollbackHwPresent. virtual bool config_is_anti_rollback_hw_present() { return false; } @@ -150,14 +168,14 @@ class CryptoEngine { // been applied to the device that fixes a security bug. virtual uint8_t config_security_patch_level() { return 0; } - // If 0 no restriction, otherwise it's the max buffer for DecryptCENC. - // This is the same as the max subsample size, not the sample or frame size. - virtual size_t max_buffer_size() { return 1024 * 100; } // 100 KiB. + // If 0 no restriction, otherwise it's the max subsample size for + // DecryptCENC. This is not the same as the max sample or buffer size. + virtual size_t max_subsample_size() { return 1024 * 100; } // 100 KiB - // If 0 no restriction, otherwise it's the max output buffer for DecryptCENC - // and CopyBuffer. This is the same as the max frame or sample size, not the - // subsample size. - virtual size_t max_output_size() { return 0; } + // If 0 no restriction, otherwise it's the max sample size for DecryptCENC. + // This is the same as the max input and output buffer size for DecryptCENC + // and CopyBuffer. It is not the same as the max subsample size. + virtual size_t max_sample_size() { return 1024 * 1024; } // 1 MiB virtual bool srm_update_supported() { return false; } @@ -176,8 +194,8 @@ class CryptoEngine { virtual bool srm_blacklisted_device_attached() { return false; } - // Rate limit for nonce generation. Default to 20 nonce/second. - virtual int nonce_flood_count() { return 20; } + // Rate limit for nonce generation. Default to 200 nonce/second. + virtual int nonce_flood_count() { return 200; } // Limit for size of usage table. If this is zero, then the // size is unlimited -- or limited only by memory size. @@ -186,24 +204,36 @@ class CryptoEngine { virtual uint32_t resource_rating() { return 1; } // Set destination pointer based on the output destination description. - OEMCryptoResult SetDestination(OEMCrypto_DestBufferDesc* out_description, - size_t data_length, uint8_t subsample_flags); + OEMCryptoResult SetDestination( + const OEMCrypto_DestBufferDesc& out_description, size_t data_length, + uint8_t subsample_flags); // The current destination. uint8_t* destination() { return destination_; } // Subclasses can adjust the destination -- for use in testing. - virtual void adjust_destination(OEMCrypto_DestBufferDesc* out_description, - size_t data_length, uint8_t subsample_flags) { - } + virtual void adjust_destination( + const OEMCrypto_DestBufferDesc& out_description, size_t data_length, + uint8_t subsample_flags) {} // Push destination buffer to output -- used by subclasses for testing. virtual OEMCryptoResult PushDestination( - OEMCrypto_DestBufferDesc* out_description, uint8_t subsample_flags) { + const OEMCrypto_DestBufferDesc& out_description, + uint8_t subsample_flags) { return OEMCrypto_SUCCESS; } protected: + // System clock, measuring time in seconds. + int64_t OnlineTime(); + + // System clock with antirollback protection, measuring time in seconds. + int64_t RollbackCorrectedOfflineTime(); + + bool LoadOfflineTimeInfo(const std::string& file_path); + bool SaveOfflineTimeInfo(const std::string& file_path); + std::string GetUsageTimeFileFullPath() const; + explicit CryptoEngine(std::unique_ptr&& file_system); virtual SessionContext* MakeSession(SessionId sid); virtual UsageTable* MakeUsageTable(); @@ -213,6 +243,7 @@ class CryptoEngine { std::mutex session_table_lock_; std::unique_ptr file_system_; std::unique_ptr usage_table_; + TimeInfo offline_time_info_; CORE_DISALLOW_COPY_AND_ASSIGN(CryptoEngine); }; diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_key_ref.cpp b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_key_ref.cpp index cc08c27f..feb23740 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_key_ref.cpp +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_key_ref.cpp @@ -22,7 +22,8 @@ bool KeyControlBlock::Validate() { memcmp(verification_, "kc12", 4) && // add in version 12 api memcmp(verification_, "kc13", 4) && // add in version 13 api memcmp(verification_, "kc14", 4) && // add in version 14 api - memcmp(verification_, "kc15", 4)) { // add in version 15 api + memcmp(verification_, "kc15", 4) && // add in version 15 api + memcmp(verification_, "kc16", 4)) { // add in version 16 api LOGE("KCB: BAD verification string: %4.4s", verification_); valid_ = false; } else { diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_keybox_ref.cpp b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_keybox_ref.cpp index d7ec6355..0e4a6307 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_keybox_ref.cpp +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_keybox_ref.cpp @@ -18,7 +18,10 @@ namespace wvoec_ref { -WvKeybox::WvKeybox() : loaded_(false) {} +WvKeybox::WvKeybox() : loaded_(false) { + static std::string fake_device_id = "device_with_no_keybox"; + device_id_.assign(fake_device_id.begin(), fake_device_id.end()); +} KeyboxError WvKeybox::Validate() { if (!loaded_) { diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_nonce_table.cpp b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_nonce_table.cpp deleted file mode 100644 index 65d92692..00000000 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_nonce_table.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. -// -// Reference implementation of OEMCrypto APIs -// -#include "oemcrypto_nonce_table.h" - -namespace wvoec_ref { - -void NonceTable::AddNonce(uint32_t nonce) { - int new_slot = -1; - int oldest_slot = -1; - - // Flush any nonces that have been checked but not flushed. - // After flush, nonces will be either valid or invalid. - Flush(); - - for (int i = 0; i < kTableSize; ++i) { - // Increase age of all valid nonces. - if (kNTStateValid == state_[i]) { - ++age_[i]; - if (-1 == oldest_slot) { - oldest_slot = i; - } else { - if (age_[i] > age_[oldest_slot]) { - oldest_slot = i; - } - } - } else { - if (-1 == new_slot) { - age_[i] = 0; - nonces_[i] = nonce; - state_[i] = kNTStateValid; - new_slot = i; - } - } - } - if (-1 == new_slot) { - // reuse oldest - // assert (oldest_slot != -1) - int i = oldest_slot; - age_[i] = 0; - nonces_[i] = nonce; - state_[i] = kNTStateValid; - } -} - -bool NonceTable::CheckNonce(uint32_t nonce) { - for (int i = 0; i < kTableSize; ++i) { - if (kNTStateInvalid != state_[i]) { - if (nonce == nonces_[i]) { - state_[i] = kNTStateFlushPending; - return true; - } - } - } - return false; -} - -bool NonceTable::NonceCollision(uint32_t nonce) const { - for (int i = 0; i < kTableSize; ++i) { - if (nonce == nonces_[i]) return true; - } - return false; -} - -void NonceTable::Flush() { - for (int i = 0; i < kTableSize; ++i) { - if (kNTStateFlushPending == state_[i]) { - state_[i] = kNTStateInvalid; - } - } -} - -} // namespace wvoec_ref diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_nonce_table.h b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_nonce_table.h deleted file mode 100644 index 409e7fcc..00000000 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_nonce_table.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. -// -// Reference implementation of OEMCrypto APIs -// -#ifndef REF_OEMCRYPTO_NONCE_TABLE_H_ -#define REF_OEMCRYPTO_NONCE_TABLE_H_ - -#include - -namespace wvoec_ref { - -class NonceTable { - public: - static const int kTableSize = 4; - NonceTable() { - for (int i = 0; i < kTableSize; ++i) { - state_[i] = kNTStateInvalid; - } - } - ~NonceTable() {} - void AddNonce(uint32_t nonce); - bool CheckNonce(uint32_t nonce); - // Verify that the nonce is not the same as any in this table. - bool NonceCollision(uint32_t nonce) const; - void Flush(); - - private: - enum NonceTableState { - kNTStateInvalid, - kNTStateValid, - kNTStateFlushPending - }; - NonceTableState state_[kTableSize]; - uint32_t age_[kTableSize]; - uint32_t nonces_[kTableSize]; -}; - -} // namespace wvoec_ref - -#endif // REF_OEMCRYPTO_NONCE_TABLE_H_ diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_old_usage_table_ref.cpp b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_old_usage_table_ref.cpp deleted file mode 100644 index 5e9e9a75..00000000 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_old_usage_table_ref.cpp +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. -// -// Reference implementation of OEMCrypto APIs -// -// This is from the v12 version of oemcrypto usage tables. It is used for -// devices that upgrade from v12 to v13 in the field, and need to convert from -// the old type of usage table to the new. -#include "oemcrypto_old_usage_table_ref.h" - -#include -#include - -#include -#include - -#include -#include -#include -#include - -#include "file_store.h" -#include "log.h" -#include "oemcrypto_engine_ref.h" -// TODO(fredgc): Setting the device files base bath is currently broken as -// wvcdm::Properties is no longer used by the reference code. -//#include "properties.h" -#include "pst_report.h" -#include "string_conversions.h" - -namespace wvoec_ref { - -OldUsageTableEntry::OldUsageTableEntry(OldUsageTable *old_usage_table, - const std::vector &pst_hash) - : pst_hash_(pst_hash), - old_usage_table_(old_usage_table), - time_of_license_received_( - old_usage_table_->ce_->RollbackCorrectedOfflineTime()), - time_of_first_decrypt_(0), - time_of_last_decrypt_(0), - status_(kUnused) {} - -OldUsageTableEntry::~OldUsageTableEntry() {} - -OldUsageTableEntry::OldUsageTableEntry(OldUsageTable *old_usage_table, - const OldStoredUsageEntry *buffer) - : old_usage_table_(old_usage_table) { - pst_hash_.assign(buffer->pst_hash, buffer->pst_hash + SHA256_DIGEST_LENGTH); - time_of_license_received_ = buffer->time_of_license_received; - time_of_first_decrypt_ = buffer->time_of_first_decrypt; - time_of_last_decrypt_ = buffer->time_of_last_decrypt; - status_ = buffer->status; - mac_key_server_.assign(buffer->mac_key_server, - buffer->mac_key_server + wvoec::MAC_KEY_SIZE); - mac_key_client_.assign(buffer->mac_key_client, - buffer->mac_key_client + wvoec::MAC_KEY_SIZE); -} - -OldUsageTable::OldUsageTable(CryptoEngine *ce) { - ce_ = ce; - generation_ = 0; - table_.clear(); - - // Load saved table. - wvcdm::FileSystem *file_system = ce->file_system(); - std::unique_ptr file; - std::string path; - // Note: this path is OK for a real implementation, but using security level 1 - // would be better. - // TODO(fredgc, jfore): Address how this property is presented to the ref. - // For now, the path is empty. - /*if (!Properties::GetDeviceFilesBasePath(kSecurityLevelL3, &path)) { - LOGE("OldUsageTable: Unable to get base path"); - return; - }*/ - std::string filename = path + "UsageTable.dat"; - if (!file_system->Exists(filename)) { - return; - } - - size_t file_size = file_system->FileSize(filename); - std::vector encrypted_buffer(file_size); - std::vector buffer(file_size); - OldStoredUsageTable *stored_table = - reinterpret_cast(&buffer[0]); - OldStoredUsageTable *encrypted_table = - reinterpret_cast(&encrypted_buffer[0]); - - file = file_system->Open(filename, wvcdm::FileSystem::kReadOnly); - if (!file) { - LOGE("OldUsageTable: File open failed: %s", path.c_str()); - return; - } - file->Read(reinterpret_cast(&encrypted_buffer[0]), file_size); - - // Verify the signature of the usage table file. - - // This should be encrypted and signed with a device specific key. - // For the reference implementation, I'm just going to use the keybox key. - const std::vector &key = ce_->DeviceRootKey(); - if (key.empty()) { - LOGE("OldUsageTable: DeviceRootKey is unexpectedly empty."); - table_.clear(); - return; - } - - uint8_t computed_signature[SHA256_DIGEST_LENGTH]; - unsigned int sig_length = sizeof(computed_signature); - if (!HMAC(EVP_sha256(), &key[0], key.size(), - &encrypted_buffer[SHA256_DIGEST_LENGTH], - file_size - SHA256_DIGEST_LENGTH, computed_signature, - &sig_length)) { - LOGE("OldUsageTable: Could not recreate signature."); - table_.clear(); - return; - } - if (memcmp(encrypted_table->signature, computed_signature, sig_length)) { - LOGE("OldUsageTable: Invalid signature given: %s", - wvcdm::HexEncode(&encrypted_buffer[0], sig_length).c_str()); - LOGE("OldUsageTable: Invalid signature computed: %s", - wvcdm::HexEncode(computed_signature, sig_length).c_str()); - table_.clear(); - return; - } - - // Next, decrypt the table. - uint8_t iv_buffer[wvoec::KEY_IV_SIZE]; - memcpy(iv_buffer, encrypted_table->iv, wvoec::KEY_IV_SIZE); - AES_KEY aes_key; - AES_set_decrypt_key(&key[0], 128, &aes_key); - AES_cbc_encrypt(&encrypted_buffer[SHA256_DIGEST_LENGTH + wvoec::KEY_IV_SIZE], - &buffer[SHA256_DIGEST_LENGTH + wvoec::KEY_IV_SIZE], - file_size - SHA256_DIGEST_LENGTH - wvoec::KEY_IV_SIZE, &aes_key, - iv_buffer, AES_DECRYPT); - - // Next, read the generation number from a different location. - // On a real implementation, you should NOT put the generation number in - // a file in user space. It should be stored in secure memory. For the - // reference implementation, we'll just pretend this is secure. - std::string filename2 = path + "GenerationNumber.dat"; - file = file_system->Open(filename2, wvcdm::FileSystem::kReadOnly); - if (!file) { - LOGE("OldUsageTable: File open failed: %s (clearing table)", path.c_str()); - generation_ = 0; - table_.clear(); - return; - } - file->Read(reinterpret_cast(&generation_), sizeof(int64_t)); - if ((stored_table->generation > generation_ + 1) || - (stored_table->generation < generation_ - 1)) { - LOGE("OldUsageTable: Rollback detected. Clearing Usage Table. %lx -> %lx", - generation_, stored_table->generation); - table_.clear(); - generation_ = 0; - return; - } - - // At this point, the stored table looks valid. We can load in all the - // entries. - for (uint64_t i = 0; i < stored_table->count; i++) { - OldUsageTableEntry *entry = - new OldUsageTableEntry(this, &stored_table->entries[i].entry); - table_[entry->pst_hash()] = entry; - } -} - -OldUsageTableEntry *OldUsageTable::FindEntry(const std::vector &pst) { - std::unique_lock lock(lock_); - return FindEntryLocked(pst); -} - -OldUsageTableEntry *OldUsageTable::FindEntryLocked( - const std::vector &pst) { - std::vector pst_hash; - if (!ComputeHash(pst, pst_hash)) { - LOGE("OldUsageTable: Could not compute hash of pst."); - return NULL; - } - EntryMap::iterator it = table_.find(pst_hash); - if (it == table_.end()) { - return NULL; - } - return it->second; -} - -OldUsageTableEntry *OldUsageTable::CreateEntry( - const std::vector &pst) { - std::vector pst_hash; - if (!ComputeHash(pst, pst_hash)) { - LOGE("OldUsageTable: Could not compute hash of pst."); - return NULL; - } - OldUsageTableEntry *entry = new OldUsageTableEntry(this, pst_hash); - std::unique_lock lock(lock_); - table_[pst_hash] = entry; - return entry; -} - -void OldUsageTable::Clear() { - std::unique_lock lock(lock_); - for (EntryMap::iterator i = table_.begin(); i != table_.end(); ++i) { - if (i->second) delete i->second; - } - table_.clear(); -} - -void OldUsageTable::DeleteFile(CryptoEngine *ce) { - wvcdm::FileSystem *file_system = ce->file_system(); - std::string path; - // Note: this path is OK for a real implementation, but using security level 1 - // would be better. - // TODO(jfore): Address how this property is presented to the ref. For now, - // the path is empty. - /*if (!Properties::GetDeviceFilesBasePath(kSecurityLevelL3, &path)) { - LOGE("OldUsageTable: Unable to get base path"); - return; - }*/ - std::string filename = path + "UsageTable.dat"; - if (file_system->Exists(filename)) { - if (!file_system->Remove(filename)) { - LOGE("DeleteOldUsageTable: error removing file."); - } - } -} - -bool OldUsageTable::ComputeHash(const std::vector &pst, - std::vector &pst_hash) { - // The PST is not fixed size, and we have no promises that it is reasonbly - // sized, so we compute a hash of it, and store that instead. - pst_hash.resize(SHA256_DIGEST_LENGTH); - SHA256_CTX context; - if (!SHA256_Init(&context)) return false; - if (!SHA256_Update(&context, &pst[0], pst.size())) return false; - if (!SHA256_Final(&pst_hash[0], &context)) return false; - return true; -} - -} // namespace wvoec_ref diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_old_usage_table_ref.h b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_old_usage_table_ref.h deleted file mode 100644 index 452d5047..00000000 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_old_usage_table_ref.h +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. -// -// Reference implementation of OEMCrypto APIs -// -// This is from the v12 version of oemcrypto usage tables. It is used for -// devices that upgrade from v12 to v13 in the field, and need to convert from -// the old type of usage table to the new. -#ifndef OEMCRYPTO_OLD_USAGE_TABLE_REF_H_ -#define OEMCRYPTO_OLD_USAGE_TABLE_REF_H_ - -#include -#include -#include -#include -#include - -#include "OEMCryptoCENC.h" -#include "oemcrypto_types.h" -#include "openssl/sha.h" - -namespace wvoec_ref { - -class CryptoEngine; -class OldUsageTable; -class UsagetTableEntry; - -struct OldStoredUsageEntry { - // To save disk space, we only store a hash of the pst. - uint8_t pst_hash[SHA256_DIGEST_LENGTH]; - int64_t time_of_license_received; - int64_t time_of_first_decrypt; - int64_t time_of_last_decrypt; - enum OEMCrypto_Usage_Entry_Status status; - uint8_t mac_key_server[wvoec::MAC_KEY_SIZE]; - uint8_t mac_key_client[wvoec::MAC_KEY_SIZE]; -}; - -typedef union { - struct OldStoredUsageEntry entry; - uint8_t padding[128]; // multiple of block size and bigger than entry size. -} AlignedOldStoredUsageEntry; - -struct OldStoredUsageTable { - uint8_t signature[SHA256_DIGEST_LENGTH]; - uint8_t iv[wvoec::KEY_IV_SIZE]; - int64_t generation; - uint64_t count; - AlignedOldStoredUsageEntry entries[]; -}; - -class OldUsageTableEntry { - public: - OldUsageTableEntry(OldUsageTable *old_usage_table, - const std::vector &pst_hash); - OldUsageTableEntry(OldUsageTable *old_usage_table, - const OldStoredUsageEntry *buffer); - ~OldUsageTableEntry(); - const std::vector &pst_hash() const { return pst_hash_; } - - private: - std::vector pst_hash_; - const OldUsageTable *old_usage_table_; - int64_t time_of_license_received_; - int64_t time_of_first_decrypt_; - int64_t time_of_last_decrypt_; - enum OEMCrypto_Usage_Entry_Status status_; - std::vector mac_key_server_; - std::vector mac_key_client_; - - friend class UsageTableEntry; - friend class UsageTable; -}; - -class OldUsageTable { - public: - OldUsageTable(CryptoEngine *ce); - ~OldUsageTable() { Clear(); } - OldUsageTableEntry *FindEntry(const std::vector &pst); - OldUsageTableEntry *CreateEntry(const std::vector &pst); - void Clear(); - static void DeleteFile(CryptoEngine *ce); - - private: - OldUsageTableEntry *FindEntryLocked(const std::vector &pst); - bool ComputeHash(const std::vector &pst, - std::vector &pst_hash); - - typedef std::map, OldUsageTableEntry *> EntryMap; - EntryMap table_; - std::mutex lock_; - int64_t generation_; - CryptoEngine *ce_; - - friend class OldUsageTableEntry; -}; - -} // namespace wvoec_ref - -#endif // OEMCRYPTO_OLD_USAGE_TABLE_REF_H_ diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_ref.cpp b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_ref.cpp index 355fbf74..cfede60b 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_ref.cpp +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_ref.cpp @@ -6,9 +6,11 @@ // #include "OEMCryptoCENC.h" +#include #include #include #include +#include #include #include #include @@ -20,8 +22,10 @@ #include #include #include + #include "file_store.h" #include "log.h" +#include "odk.h" #include "oemcrypto_engine_ref.h" #include "oemcrypto_session.h" #include "oemcrypto_usage_table_ref.h" @@ -37,9 +41,9 @@ namespace { const uint8_t kBakedInCertificateMagicBytes[] = {0xDE, 0xAD, 0xBE, 0xEF}; // Return uint32 referenced through a potentially unaligned pointer. -// If the pointer is NULL, return 0. +// If the pointer is nullptr, return 0. uint32_t unaligned_dereference_uint32(const void* unaligned_ptr) { - if (unaligned_ptr == NULL) return 0; + if (unaligned_ptr == nullptr) return 0; uint32_t value; const uint8_t* src = reinterpret_cast(unaligned_ptr); uint8_t* dest = reinterpret_cast(&value); @@ -124,8 +128,8 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_CloseSession( OEMCRYPTO_API OEMCryptoResult OEMCrypto_GenerateDerivedKeys( OEMCrypto_SESSION session, const uint8_t* mac_key_context, - uint32_t mac_key_context_length, const uint8_t* enc_key_context, - uint32_t enc_key_context_length) { + size_t mac_key_context_length, const uint8_t* enc_key_context, + size_t enc_key_context_length) { if (crypto_engine == nullptr) { LOGE("OEMCrypto_GenerateDerivedKeys: OEMCrypto not initialized."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; @@ -139,7 +143,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_GenerateDerivedKeys( } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_GenerateDerivedKeys(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } @@ -157,7 +161,6 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_GenerateDerivedKeys( return OEMCrypto_SUCCESS; } - OEMCRYPTO_API OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session, uint32_t* nonce) { if (crypto_engine == nullptr) { @@ -165,7 +168,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session, return OEMCrypto_ERROR_UNKNOWN_FAILURE; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_GenerateNonce(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } @@ -199,46 +202,67 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session, return OEMCrypto_ERROR_UNKNOWN_FAILURE; } } - session_ctx->AddNonce(nonce_value); + if (!session_ctx->set_nonce(nonce_value)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } *nonce = nonce_value; return OEMCrypto_SUCCESS; } -OEMCRYPTO_API OEMCryptoResult OEMCrypto_GenerateSignature( - OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, - uint8_t* signature, size_t* signature_length) { +OEMCRYPTO_API OEMCryptoResult OEMCrypto_PrepAndSignLicenseRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_length, uint8_t* signature, size_t* signature_length) { if (crypto_engine == nullptr) { - LOGE("OEMCrypto_GenerateSignature: OEMCrypto Not Initialized."); + LOGE("OEMCrypto Not Initialized."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - - if (*signature_length < SHA256_DIGEST_LENGTH) { - *signature_length = SHA256_DIGEST_LENGTH; - return OEMCrypto_ERROR_SHORT_BUFFER; - } - - if (message == NULL || message_length == 0 || signature == NULL || - signature_length == 0) { - LOGE("[OEMCrypto_GenerateSignature(): OEMCrypto_ERROR_INVALID_CONTEXT]"); - return OEMCrypto_ERROR_INVALID_CONTEXT; - } - SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { - LOGE("[OEMCrypto_GenerateSignature(): ERROR_INVALID_SESSION]"); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("OEMCrypto_ERROR_INVALID_SESSION"); return OEMCrypto_ERROR_INVALID_SESSION; } + return session_ctx->PrepAndSignLicenseRequest(message, message_length, + core_message_length, signature, + signature_length); +} - if (session_ctx->GenerateSignature(message, message_length, signature, - signature_length)) { - return OEMCrypto_SUCCESS; +OEMCRYPTO_API OEMCryptoResult OEMCrypto_PrepAndSignRenewalRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_length, uint8_t* signature, size_t* signature_length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - return OEMCrypto_ERROR_UNKNOWN_FAILURE; + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("OEMCrypto_ERROR_INVALID_SESSION"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + return session_ctx->PrepAndSignRenewalRequest(message, message_length, + core_message_length, signature, + signature_length); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_PrepAndSignProvisioningRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_length, uint8_t* signature, size_t* signature_length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("OEMCrypto_ERROR_INVALID_SESSION"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + return session_ctx->PrepAndSignProvisioningRequest( + message, message_length, core_message_length, signature, + signature_length); } bool RangeCheck(const uint8_t* message, uint32_t message_length, const uint8_t* field, uint32_t field_length, bool allow_null) { - if (field == NULL) return allow_null; + if (field == nullptr) return allow_null; if (field < message) return false; if (field + field_length > message + message_length) return false; return true; @@ -252,6 +276,34 @@ bool RangeCheck(uint32_t message_length, const OEMCrypto_Substring& substring, return true; } +OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length) { + if (crypto_engine == nullptr) { + LOGE("not initialized"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (message == nullptr || message_length == 0 || signature == nullptr || + signature_length == 0) { + LOGE("OEMCrypto_ERROR_INVALID_CONTEXT"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("ERROR_KEYBOX_INVALID"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("ERROR_INVALID_SESSION sid=%d", session); + return OEMCrypto_ERROR_INVALID_SESSION; + } + return session_ctx->LoadLicense(message, message_length, core_message_length, + signature, signature_length); +} + OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadKeys( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, const uint8_t* signature, size_t signature_length, @@ -264,16 +316,16 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadKeys( return OEMCrypto_ERROR_UNKNOWN_FAILURE; } if (!crypto_engine->ValidRootOfTrust()) { - LOGE("[OEMCrypto_LoadKeys(): ERROR_KEYBOX_INVALID]"); + LOGE("ERROR_KEYBOX_INVALID"); return OEMCrypto_ERROR_KEYBOX_INVALID; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { - LOGE("[OEMCrypto_LoadKeys(): ERROR_INVALID_SESSION]"); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("ERROR_INVALID_SESSION sid=%d", session); return OEMCrypto_ERROR_INVALID_SESSION; } - if (message == NULL || message_length == 0 || signature == NULL || - signature_length == 0 || key_array == NULL || num_keys == 0) { + if (message == nullptr || message_length == 0 || signature == nullptr || + signature_length == 0 || key_array == nullptr || num_keys == 0) { LOGE("[OEMCrypto_LoadKeys(): OEMCrypto_ERROR_INVALID_CONTEXT]"); return OEMCrypto_ERROR_INVALID_CONTEXT; } @@ -305,8 +357,9 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadKeys( "range check iv]"); return OEMCrypto_ERROR_INVALID_CONTEXT; } else { - if (memcmp(message + enc_mac_keys.offset - wvoec::KEY_IV_SIZE, - message + enc_mac_keys_iv.offset, wvoec::KEY_IV_SIZE) == 0) { + if (CRYPTO_memcmp(message + enc_mac_keys.offset - wvoec::KEY_IV_SIZE, + message + enc_mac_keys_iv.offset, + wvoec::KEY_IV_SIZE) == 0) { LOGE("[OEMCrypto_LoadKeys(): OEMCrypto_ERROR_INVALID_CONTEXT - " "suspicious iv]"); return OEMCrypto_ERROR_INVALID_CONTEXT; @@ -321,12 +374,13 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadKeys( OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadEntitledContentKeys( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, - size_t num_keys, const OEMCrypto_EntitledContentKeyObject* key_array) { - if (num_keys == 0) { + size_t key_array_length, + const OEMCrypto_EntitledContentKeyObject* key_array) { + if (key_array_length == 0) { LOGE("[OEMCrypto_LoadEntitledContentKeys(): key_array is empty."); return OEMCrypto_SUCCESS; } - if (!key_array) { + if (key_array == nullptr) { LOGE("[OEMCrypto_LoadEntitledContentKeys(): missing key_array."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } @@ -335,11 +389,11 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadEntitledContentKeys( return OEMCrypto_ERROR_UNKNOWN_FAILURE; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_LoadEntitledContentKeys(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } - for (unsigned int i = 0; i < num_keys; i++) { + for (size_t i = 0; i < key_array_length; i++) { if (!RangeCheck(message_length, key_array[i].entitlement_key_id, false) || !RangeCheck(message_length, key_array[i].content_key_id, false) || !RangeCheck(message_length, key_array[i].content_key_data_iv, false) || @@ -347,14 +401,44 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadEntitledContentKeys( LOGE( "[OEMCrypto_LoadEntitledContentKeys(): " "OEMCrypto_ERROR_INVALID_CONTEXT -range " - "check %d]", + "check %zu]", i); return OEMCrypto_ERROR_INVALID_CONTEXT; } } + return session_ctx->LoadEntitledContentKeys(message, message_length, + key_array_length, key_array); +} - return session_ctx->LoadEntitledContentKeys(message, message_length, num_keys, - key_array); +OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadRenewal(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("ERROR_KEYBOX_INVALID"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("ERROR_INVALID_SESSION"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + + if (message == nullptr || message_length == 0 || signature == nullptr || + signature_length == 0) { + LOGE("ERROR_INVALID_CONTEXT"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + return session_ctx->LoadRenewal(message, message_length, core_message_length, + signature, signature_length); } OEMCRYPTO_API OEMCryptoResult OEMCrypto_RefreshKeys( @@ -362,41 +446,44 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RefreshKeys( const uint8_t* signature, size_t signature_length, size_t num_keys, const OEMCrypto_KeyRefreshObject* key_array) { if (crypto_engine == nullptr) { - LOGE("OEMCrypto_RefreshKeys: OEMCrypto Not Initialized."); + LOGE("OEMCrypto Not Initialized."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } if (!crypto_engine->ValidRootOfTrust()) { - LOGE("[OEMCrypto_RefreshKeys(): ERROR_KEYBOX_INVALID]"); + LOGE("ERROR_KEYBOX_INVALID"); return OEMCrypto_ERROR_KEYBOX_INVALID; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { - LOGE("[OEMCrypto_RefreshKeys(): ERROR_INVALID_SESSION]"); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("ERROR_INVALID_SESSION"); return OEMCrypto_ERROR_INVALID_SESSION; } - if (message == NULL || message_length == 0 || signature == NULL || + if (message == nullptr || message_length == 0 || signature == nullptr || signature_length == 0 || num_keys == 0) { - LOGE("[OEMCrypto_RefreshKeys(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + LOGE("ERROR_INVALID_CONTEXT"); return OEMCrypto_ERROR_INVALID_CONTEXT; } - // Range check - for (unsigned int i = 0; i < num_keys; i++) { - if (!RangeCheck(message_length, key_array[i].key_id, true) || - !RangeCheck(message_length, key_array[i].key_control, false) || - !RangeCheck(message_length, key_array[i].key_control_iv, true)) { - LOGE("[OEMCrypto_RefreshKeys(): Range Check %d]", i); - return OEMCrypto_ERROR_INVALID_CONTEXT; - } + // We only use the first key object to update the entire license. Since we + // know num_keys > 0 after the last if statement, we can assume index is not + // out of bounds. + constexpr size_t kIndex = 0; + + // Range check. + if (!RangeCheck(message_length, key_array[kIndex].key_id, true) || + !RangeCheck(message_length, key_array[kIndex].key_control, false) || + !RangeCheck(message_length, key_array[kIndex].key_control_iv, true)) { + LOGE("Range Check %zu", kIndex); + return OEMCrypto_ERROR_INVALID_CONTEXT; } // Validate message signature if (!session_ctx->ValidateMessage(message, message_length, signature, signature_length)) { - LOGE("[OEMCrypto_RefreshKeys(): signature was invalid]"); + LOGE("Signature was invalid"); return OEMCrypto_ERROR_SIGNATURE_FAILURE; } @@ -405,44 +492,34 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RefreshKeys( std::vector key_id; std::vector key_control; std::vector key_control_iv; - for (unsigned int i = 0; i < num_keys; i++) { - if (key_array[i].key_id.length != 0) { - key_id.assign( - message + key_array[i].key_id.offset, - message + key_array[i].key_id.offset + key_array[i].key_id.length); - key_control.assign( - message + key_array[i].key_control.offset, - message + key_array[i].key_control.offset + wvoec::KEY_CONTROL_SIZE); - if (key_array[i].key_control_iv.length == 0) { - key_control_iv.clear(); - } else { - key_control_iv.assign( - message + key_array[i].key_control_iv.offset, - message + key_array[i].key_control_iv.offset + wvoec::KEY_IV_SIZE); - } - } else { - // key_id could be null if special control key type - // key_control is not encrypted in this case - key_id.clear(); + if (key_array[kIndex].key_id.length != 0) { + key_id.assign(message + key_array[kIndex].key_id.offset, + message + key_array[kIndex].key_id.offset + + key_array[kIndex].key_id.length); + key_control.assign(message + key_array[kIndex].key_control.offset, + message + key_array[kIndex].key_control.offset + + wvoec::KEY_CONTROL_SIZE); + if (key_array[kIndex].key_control_iv.length == 0) { key_control_iv.clear(); - key_control.assign( - message + key_array[i].key_control.offset, - message + key_array[i].key_control.offset + wvoec::KEY_CONTROL_SIZE); - } - - status = session_ctx->RefreshKey(key_id, key_control, key_control_iv); - if (status != OEMCrypto_SUCCESS) { - LOGE("[OEMCrypto_RefreshKeys(): error %u in key %i]", status, i); - break; + } else { + key_control_iv.assign(message + key_array[kIndex].key_control_iv.offset, + message + key_array[kIndex].key_control_iv.offset + + wvoec::KEY_IV_SIZE); } + } else { + // key_id could be null if special control key type + // key_control is not encrypted in this case + key_id.clear(); + key_control_iv.clear(); + key_control.assign(message + key_array[kIndex].key_control.offset, + message + key_array[kIndex].key_control.offset + + wvoec::KEY_CONTROL_SIZE); } - session_ctx->FlushNonces(); + status = session_ctx->RefreshKey(key_id, key_control, key_control_iv); if (status != OEMCrypto_SUCCESS) { return status; } - - session_ctx->StartTimer(); return OEMCrypto_SUCCESS; } @@ -454,13 +531,13 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_QueryKeyControl( return OEMCrypto_ERROR_UNKNOWN_FAILURE; } uint32_t* block = reinterpret_cast(key_control_block); - if ((key_control_block_length == NULL) || + if ((key_control_block_length == nullptr) || (*key_control_block_length < wvoec::KEY_CONTROL_SIZE)) { LOGE("[OEMCrypto_QueryKeyControl(): OEMCrypto_ERROR_SHORT_BUFFER]"); return OEMCrypto_ERROR_SHORT_BUFFER; } *key_control_block_length = wvoec::KEY_CONTROL_SIZE; - if (key_id == NULL) { + if (key_id == nullptr) { LOGE( "[OEMCrypto_QueryKeyControl(): key_id null. " "OEMCrypto_ERROR_UNKNOWN_FAILURE]"); @@ -468,7 +545,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_QueryKeyControl( } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_QueryKeyControl(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } @@ -492,7 +569,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_SelectKey( #endif SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_SelectKey(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } @@ -503,32 +580,27 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_SelectKey( } OEMCRYPTO_API OEMCryptoResult OEMCrypto_DecryptCENC( - OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, - bool is_encrypted, const uint8_t* iv, size_t block_offset, - OEMCrypto_DestBufferDesc* out_buffer, - const OEMCrypto_CENCEncryptPatternDesc* pattern, uint8_t subsample_flags) { + OEMCrypto_SESSION session, const OEMCrypto_SampleDescription* samples, + size_t samples_length, const OEMCrypto_CENCEncryptPatternDesc* pattern) { if (crypto_engine == nullptr) { LOGE("OEMCrypto_DecryptCENC: OEMCrypto Not Initialized."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - if (data_addr == NULL || data_length == 0 || iv == NULL || - out_buffer == NULL) { - LOGE("[OEMCrypto_DecryptCENC(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + if (samples == nullptr || samples_length == 0) { + LOGE("[OEMCrypto_DecryptCENC(): No samples]"); return OEMCrypto_ERROR_INVALID_CONTEXT; } - if (crypto_engine->max_buffer_size() > 0 && - data_length > crypto_engine->max_buffer_size()) { - // For testing reasons only, pretend that this integration only supports - // the minimum possible buffer size. - LOGE("[OEMCrypto_DecryptCENC(): OEMCrypto_ERROR_BUFFER_TOO_LARGE]"); - return OEMCrypto_ERROR_BUFFER_TOO_LARGE; + if (pattern == nullptr) { + LOGE("[OEMCrypto_DecryptCENC(): No pattern]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; } - OEMCryptoResult status = - crypto_engine->SetDestination(out_buffer, data_length, subsample_flags); - if (status != OEMCrypto_SUCCESS) { - LOGE("[OEMCrypto_DecryptCENC(): destination status: %d]", status); - return status; + + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("[OEMCrypto_DecryptCENC(): ERROR_INVALID_SESSION]"); + return OEMCrypto_ERROR_INVALID_SESSION; } + #ifndef NDEBUG if (!crypto_engine->ValidRootOfTrust()) { LOGE("[OEMCrypto_DecryptCENC(): ERROR_KEYBOX_INVALID]"); @@ -536,44 +608,78 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_DecryptCENC( } #endif - SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { - LOGE("[OEMCrypto_DecryptCENC(): ERROR_INVALID_SESSION]"); - return OEMCrypto_ERROR_INVALID_SESSION; + // Iterate through all the samples and validate them before doing any decrypt + for (size_t sample_index = 0; sample_index < samples_length; ++sample_index) { + const OEMCrypto_SampleDescription& sample = samples[sample_index]; + + if (sample.buffers.input_data == nullptr || + sample.buffers.input_data_length == 0) { + LOGE("[OEMCrypto_DecryptCENC(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (crypto_engine->max_sample_size() > 0 && + sample.buffers.input_data_length > crypto_engine->max_sample_size()) { + // For testing reasons only, pretend that this integration only supports + // the given buffer size. + LOGE("[OEMCrypto_DecryptCENC(): Sample too large]"); + return OEMCrypto_ERROR_BUFFER_TOO_LARGE; + } + + // Iterate through all the subsamples and sum their lengths + size_t subsample_length_tally = 0; + for (size_t subsample_index = 0; subsample_index < sample.subsamples_length; + ++subsample_index) { + const OEMCrypto_SubSampleDescription& subsample = + sample.subsamples[subsample_index]; + const size_t length = + subsample.num_bytes_clear + subsample.num_bytes_encrypted; + if (crypto_engine->max_subsample_size() > 0 && + length > crypto_engine->max_subsample_size()) { + // For testing reasons only, pretend that this integration only supports + // the given buffer size. + LOGE("[OEMCrypto_DecryptCENC(): Subsample too large]"); + return OEMCrypto_ERROR_BUFFER_TOO_LARGE; + } + subsample_length_tally += length; + } + if (subsample_length_tally != sample.buffers.input_data_length) { + LOGE( + "[OEMCrypto_DecryptCENC(): Sample and subsample lengths do not " + "match.]"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } } - OEMCryptoResult result = session_ctx->DecryptCENC( - iv, block_offset, pattern, data_addr, data_length, is_encrypted, - crypto_engine->destination(), out_buffer->type, subsample_flags); - if (result != OEMCrypto_SUCCESS) return result; - return crypto_engine->PushDestination(out_buffer, subsample_flags); + return session_ctx->DecryptSamples(samples, samples_length, pattern); } OEMCRYPTO_API OEMCryptoResult OEMCrypto_CopyBuffer( OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, - OEMCrypto_DestBufferDesc* out_buffer, uint8_t subsample_flags) { + const OEMCrypto_DestBufferDesc* out_buffer_descriptor, + uint8_t subsample_flags) { if (crypto_engine == nullptr) { LOGE("OEMCrypto_CopyBuffer: OEMCrypto Not Initialized."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - if (data_addr == NULL || out_buffer == NULL) { + if (data_addr == nullptr || out_buffer_descriptor == nullptr) { LOGE("[OEMCrypto_CopyBuffer(): OEMCrypto_ERROR_INVALID_CONTEXT]"); return OEMCrypto_ERROR_INVALID_CONTEXT; } - if (crypto_engine->max_buffer_size() > 0 && - data_length > crypto_engine->max_buffer_size()) { + if (crypto_engine->max_subsample_size() > 0 && + data_length > crypto_engine->max_subsample_size()) { // For testing reasons only, pretend that this integration only supports // the minimum possible buffer size. LOGE("[OEMCrypto_CopyBuffer(): OEMCrypto_ERROR_BUFFER_TOO_LARGE]"); return OEMCrypto_ERROR_BUFFER_TOO_LARGE; } - OEMCryptoResult status = - crypto_engine->SetDestination(out_buffer, data_length, subsample_flags); + OEMCryptoResult status = crypto_engine->SetDestination( + *out_buffer_descriptor, data_length, subsample_flags); if (status != OEMCrypto_SUCCESS) return status; - if (crypto_engine->destination() != NULL) { + if (crypto_engine->destination() != nullptr) { memmove(crypto_engine->destination(), data_addr, data_length); } - return crypto_engine->PushDestination(out_buffer, subsample_flags); + return crypto_engine->PushDestination(*out_buffer_descriptor, + subsample_flags); } OEMCRYPTO_API OEMCryptoResult OEMCrypto_WrapKeyboxOrOEMCert( @@ -661,25 +767,37 @@ OEMCRYPTO_API OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod() { return crypto_engine->config_provisioning_method(); } -OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetOEMPublicCertificate( - OEMCrypto_SESSION session, uint8_t* public_cert, - size_t* public_cert_length) { +OEMCRYPTO_API OEMCryptoResult +OEMCrypto_LoadOEMPrivateKey(OEMCrypto_SESSION session) { if (crypto_engine == nullptr) { - LOGE("OEMCrypto_GetOEMPublicCertificate: OEMCrypto Not Initialized."); + LOGE("OEMCrypto Not Initialized."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } if (crypto_engine->config_provisioning_method() != OEMCrypto_OEMCertificate) { - LOGE("OEMCrypto_GetOEMPublicCertificate: Provisioning method = %d.", + LOGE("Unexpected provisioning method = %d.", crypto_engine->config_provisioning_method()); return OEMCrypto_ERROR_NOT_IMPLEMENTED; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { - LOGE("[OEMCrypto_GetOEMPublicCertificate(): ERROR_INVALID_SESSION]"); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("OEMCrypto_ERROR_INVALID_SESSION"); return OEMCrypto_ERROR_INVALID_SESSION; } - return crypto_engine->get_oem_certificate(session_ctx, public_cert, - public_cert_length); + return crypto_engine->load_oem_private_key(session_ctx); +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetOEMPublicCertificate( + uint8_t* public_cert, size_t* public_cert_length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto Not Initialized."); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (crypto_engine->config_provisioning_method() != OEMCrypto_OEMCertificate) { + LOGE("Unexpected provisioning method = %d.", + crypto_engine->config_provisioning_method()); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + return crypto_engine->get_oem_certificate(public_cert, public_cert_length); } OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, @@ -688,11 +806,6 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, LOGE("OEMCrypto_GetDeviceID: OEMCrypto Not Initialized."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - if (crypto_engine->config_provisioning_method() != OEMCrypto_Keybox) { - return OEMCrypto_ERROR_NOT_IMPLEMENTED; - } - // Devices that do not support a keybox should use some other method to - // store the device id. const std::vector& dev_id_string = crypto_engine->DeviceRootId(); if (dev_id_string.empty()) { LOGE("[OEMCrypto_GetDeviceId(): Keybox Invalid]"); @@ -721,7 +834,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData, return OEMCrypto_ERROR_NOT_IMPLEMENTED; } size_t length = crypto_engine->DeviceRootTokenLength(); - if (keyDataLength == NULL) { + if (keyDataLength == nullptr) { LOGE("[OEMCrypto_GetKeyData(): null pointer. ERROR_UNKNOWN_FAILURE]"); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } @@ -730,7 +843,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData, LOGE("[OEMCrypto_GetKeyData(): ERROR_SHORT_BUFFER]"); return OEMCrypto_ERROR_SHORT_BUFFER; } - if (keyData == NULL) { + if (keyData == nullptr) { LOGE("[OEMCrypto_GetKeyData(): null pointer. ERROR_UNKNOWN_FAILURE]"); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } @@ -755,21 +868,22 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetRandom(uint8_t* randomData, return OEMCrypto_ERROR_UNKNOWN_FAILURE; } -OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( +// This function is no longer exported -- it is only used by LoadProvisioning. +static OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( OEMCrypto_SESSION session, const uint32_t* unaligned_nonce, const uint8_t* encrypted_message_key, size_t encrypted_message_key_length, const uint8_t* enc_rsa_key, size_t enc_rsa_key_length, const uint8_t* enc_rsa_key_iv, uint8_t* wrapped_rsa_key, size_t* wrapped_rsa_key_length) { uint32_t nonce = unaligned_dereference_uint32(unaligned_nonce); - if (unaligned_nonce == NULL) { + if (unaligned_nonce == nullptr) { return OEMCrypto_ERROR_INVALID_CONTEXT; } if (crypto_engine == nullptr) { LOGE("OEMCrypto_RewrapDeviceRSAKey30: OEMCrypto Not Initialized."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - if (wrapped_rsa_key_length == NULL) { + if (wrapped_rsa_key_length == nullptr) { LOGE("[OEMCrypto_RewrapDeviceRSAKey30(): OEMCrypto_ERROR_INVALID_CONTEXT]"); return OEMCrypto_ERROR_INVALID_CONTEXT; } @@ -777,9 +891,9 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( // key are the same size -- just encrypted with different keys. // We add 32 bytes for a context, 32 for iv, and 32 bytes for a signature. // Important: This layout must match OEMCrypto_LoadDeviceRSAKey below. - size_t buffer_size = enc_rsa_key_length + sizeof(WrappedRSAKey); + const size_t buffer_size = enc_rsa_key_length + sizeof(WrappedRSAKey); - if (wrapped_rsa_key == NULL || *wrapped_rsa_key_length < buffer_size) { + if (wrapped_rsa_key == nullptr || *wrapped_rsa_key_length < buffer_size) { *wrapped_rsa_key_length = buffer_size; return OEMCrypto_ERROR_SHORT_BUFFER; } @@ -789,13 +903,13 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( return OEMCrypto_ERROR_KEYBOX_INVALID; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_RewrapDeviceRSAKey30(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } - if (encrypted_message_key == NULL || encrypted_message_key_length == 0 || - enc_rsa_key == NULL || enc_rsa_key_iv == NULL || - unaligned_nonce == NULL) { + if (encrypted_message_key == nullptr || encrypted_message_key_length == 0 || + enc_rsa_key == nullptr || enc_rsa_key_iv == nullptr || + unaligned_nonce == nullptr) { LOGE("[OEMCrypto_RewrapDeviceRSAKey30(): OEMCrypto_ERROR_INVALID_CONTEXT]"); return OEMCrypto_ERROR_INVALID_CONTEXT; } @@ -804,7 +918,6 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( if (!session_ctx->CheckNonce(nonce)) { return OEMCrypto_ERROR_INVALID_NONCE; } - session_ctx->FlushNonces(); if (!session_ctx->InstallRSAEncryptedKey(encrypted_message_key, encrypted_message_key_length)) { @@ -822,11 +935,8 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( } size_t padding = pkcs8_rsa_key[enc_rsa_key_length - 1]; if (padding > 16) { - LOGE( - "[OEMCrypto_RewrapDeviceRSAKey30(): " - "Encrypted RSA has bad padding: %d]", - padding); - return OEMCrypto_ERROR_INVALID_RSA_KEY; + // Do not return an error at this point, to avoid a padding oracle attack. + padding = 0; } size_t rsa_key_length = enc_rsa_key_length - padding; if (!session_ctx->LoadRSAKey(&pkcs8_rsa_key[0], rsa_key_length)) { @@ -872,16 +982,17 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30( return OEMCrypto_SUCCESS; } -OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey( +// This function is no longer exported -- it is only used by LoadProvisioning. +static OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, const uint8_t* signature, size_t signature_length, const uint32_t* unaligned_nonce, const uint8_t* enc_rsa_key, size_t enc_rsa_key_length, const uint8_t* enc_rsa_key_iv, uint8_t* wrapped_rsa_key, size_t* wrapped_rsa_key_length) { uint32_t nonce = unaligned_dereference_uint32(unaligned_nonce); - if (unaligned_nonce == NULL) { - return OEMCrypto_ERROR_INVALID_CONTEXT; - } + if (unaligned_nonce == nullptr) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } if (crypto_engine == nullptr) { LOGE("OEMCrypto_RewrapDeviceRSAKey: OEMCrypto Not Initialized."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; @@ -889,7 +1000,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey( if (crypto_engine->config_provisioning_method() != OEMCrypto_Keybox) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } - if (wrapped_rsa_key_length == NULL) { + if (wrapped_rsa_key_length == nullptr) { LOGE("[OEMCrypto_RewrapDeviceRSAKey(): OEMCrypto_ERROR_INVALID_CONTEXT]"); return OEMCrypto_ERROR_INVALID_CONTEXT; } @@ -897,9 +1008,9 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey( // key are the same size -- just encrypted with different keys. // We add 32 bytes for a context, 32 for iv, and 32 bytes for a signature. // Important: This layout must match OEMCrypto_LoadDeviceRSAKey below. - size_t buffer_size = enc_rsa_key_length + sizeof(WrappedRSAKey); + const size_t buffer_size = enc_rsa_key_length + sizeof(WrappedRSAKey); - if (wrapped_rsa_key == NULL || *wrapped_rsa_key_length < buffer_size) { + if (wrapped_rsa_key == nullptr || *wrapped_rsa_key_length < buffer_size) { *wrapped_rsa_key_length = buffer_size; return OEMCrypto_ERROR_SHORT_BUFFER; } @@ -909,51 +1020,44 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey( return OEMCrypto_ERROR_KEYBOX_INVALID; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_RewrapDeviceRSAKey(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } - if (message == NULL || message_length == 0 || signature == NULL || - signature_length == 0 || unaligned_nonce == NULL || enc_rsa_key == NULL) { + if (message == nullptr || message_length == 0 || signature == nullptr || + signature_length == 0 || unaligned_nonce == nullptr || + enc_rsa_key == nullptr) { LOGE("[OEMCrypto_RewrapDeviceRSAKey(): OEMCrypto_ERROR_INVALID_CONTEXT]"); return OEMCrypto_ERROR_INVALID_CONTEXT; } - // Range check - if (!RangeCheck(message, message_length, - reinterpret_cast(unaligned_nonce), - sizeof(uint32_t), true) || - !RangeCheck(message, message_length, enc_rsa_key, enc_rsa_key_length, - true) || - !RangeCheck(message, message_length, enc_rsa_key_iv, wvoec::KEY_IV_SIZE, true)) { - LOGE("[OEMCrypto_RewrapDeviceRSAKey(): - range check.]"); - return OEMCrypto_ERROR_INVALID_CONTEXT; - } - - // Validate nonce - if (!session_ctx->CheckNonce(nonce)) { - return OEMCrypto_ERROR_INVALID_NONCE; - } - session_ctx->FlushNonces(); - - // Decrypt RSA key. - std::vector pkcs8_rsa_key(enc_rsa_key_length); - if (!session_ctx->DecryptRSAKey(enc_rsa_key, enc_rsa_key_length, - enc_rsa_key_iv, &pkcs8_rsa_key[0])) { - return OEMCrypto_ERROR_INVALID_RSA_KEY; - } - size_t padding = pkcs8_rsa_key[enc_rsa_key_length - 1]; - if (padding > 16) { - LOGE("[RewrapDeviceRSAKey(): Encrypted RSA has bad padding: %d]", padding); - return OEMCrypto_ERROR_INVALID_RSA_KEY; - } - size_t rsa_key_length = enc_rsa_key_length - padding; - // verify signature, verify RSA key, and load it. + // verify signature. if (!session_ctx->ValidateMessage(message, message_length, signature, signature_length)) { LOGE("[RewrapDeviceRSAKey(): Could not verify signature]"); return OEMCrypto_ERROR_SIGNATURE_FAILURE; } + + // Range check performed by ODK library. + + // Validate nonce + if (!session_ctx->CheckNonce(nonce)) { + return OEMCrypto_ERROR_INVALID_NONCE; + } + + // Decrypt RSA key and verify it. + std::vector pkcs8_rsa_key(enc_rsa_key_length); + if (!session_ctx->DecryptRSAKey(enc_rsa_key, enc_rsa_key_length, + enc_rsa_key_iv, &pkcs8_rsa_key[0])) { + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } + + size_t padding = pkcs8_rsa_key[enc_rsa_key_length - 1]; + if (padding > 16) { + // Do not return an error at this point, to avoid a padding oracle attack. + padding = 0; + } + size_t rsa_key_length = enc_rsa_key_length - padding; if (!session_ctx->LoadRSAKey(&pkcs8_rsa_key[0], rsa_key_length)) { return OEMCrypto_ERROR_INVALID_RSA_KEY; } @@ -992,10 +1096,84 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey( return OEMCrypto_SUCCESS; } +OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadProvisioning( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t core_message_length, const uint8_t* signature, + size_t signature_length, uint8_t* wrapped_private_key, + size_t* wrapped_private_key_length) { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto Not Initialized"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (wrapped_private_key_length == nullptr || message == nullptr || + message_length == 0 || signature == nullptr || signature_length == 0) { + LOGE("OEMCrypto_ERROR_INVALID_CONTEXT"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (!crypto_engine->ValidRootOfTrust()) { + LOGE("OEMCrypto_ERROR_KEYBOX_INVALID"); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + SessionContext* session_ctx = crypto_engine->FindSession(session); + if (session_ctx == nullptr || !session_ctx->isValid()) { + LOGE("OEMCrypto_ERROR_INVALID_SESSION"); + return OEMCrypto_ERROR_INVALID_SESSION; + } + std::vector device_id = crypto_engine->DeviceRootId(); + ODK_ParsedProvisioning parsed_response; + const uint32_t nonce = session_ctx->nonce(); + const OEMCryptoResult result = + ODK_ParseProvisioning(message, message_length, core_message_length, + &(session_ctx->nonce_values()), device_id.data(), + device_id.size(), &parsed_response); + if (result != OEMCrypto_SUCCESS) { + LOGE("ODK Error %d", result); + return result; + } + + // For the reference implementation, the wrapped key and the encrypted + // key are the same size -- just encrypted with different keys. + // We add 32 bytes for a context, 32 for iv, and 32 bytes for a signature. + // Important: This layout must match OEMCrypto_LoadDeviceRSAKey below. + const size_t buffer_size = + parsed_response.enc_private_key.length + sizeof(WrappedRSAKey); + + if (wrapped_private_key == nullptr || + *wrapped_private_key_length < buffer_size) { + *wrapped_private_key_length = buffer_size; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + *wrapped_private_key_length = + buffer_size; // Tell caller how much space we used. + const uint8_t* message_body = message + core_message_length; + if (crypto_engine->config_provisioning_method() == OEMCrypto_Keybox) { + return OEMCrypto_RewrapDeviceRSAKey( + session, message, message_length, signature, signature_length, &nonce, + message_body + parsed_response.enc_private_key.offset, + parsed_response.enc_private_key.length, + message_body + parsed_response.enc_private_key_iv.offset, + wrapped_private_key, wrapped_private_key_length); + } else if (crypto_engine->config_provisioning_method() == + OEMCrypto_OEMCertificate) { + return OEMCrypto_RewrapDeviceRSAKey30( + session, &nonce, + message_body + parsed_response.encrypted_message_key.offset, + parsed_response.encrypted_message_key.length, + message_body + parsed_response.enc_private_key.offset, + parsed_response.enc_private_key.length, + message_body + parsed_response.enc_private_key_iv.offset, + wrapped_private_key, wrapped_private_key_length); + } else { + LOGE("Invalid provisioning method: %d.", + crypto_engine->config_provisioning_method()); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } +} + OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadDeviceRSAKey( OEMCrypto_SESSION session, const uint8_t* wrapped_rsa_key, size_t wrapped_rsa_key_length) { - if (wrapped_rsa_key == NULL) { + if (wrapped_rsa_key == nullptr) { LOGE("[OEMCrypto_LoadDeviceRSAKey(): OEMCrypto_ERROR_INVALID_CONTEXT]"); return OEMCrypto_ERROR_INVALID_CONTEXT; } @@ -1023,7 +1201,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadDeviceRSAKey( } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_LoadDeviceRSAKey(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } @@ -1034,6 +1212,13 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadDeviceRSAKey( context)) { return OEMCrypto_ERROR_UNKNOWN_FAILURE; } + // verify signature. + if (!session_ctx->ValidateMessage( + wrapped->context, wrapped_rsa_key_length - sizeof(wrapped->signature), + wrapped->signature, sizeof(wrapped->signature))) { + LOGE("[LoadDeviceRSAKey(): Could not verify signature]"); + return OEMCrypto_ERROR_SIGNATURE_FAILURE; + } // Decrypt RSA key. std::vector pkcs8_rsa_key(wrapped_rsa_key_length - sizeof(wrapped->signature)); @@ -1044,17 +1229,10 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadDeviceRSAKey( } size_t padding = pkcs8_rsa_key[enc_rsa_key_length - 1]; if (padding > 16) { - LOGE("[LoadDeviceRSAKey(): Encrypted RSA has bad padding: %d]", padding); - return OEMCrypto_ERROR_INVALID_RSA_KEY; + // Do not return an error at this point, to avoid a padding oracle attack. + padding = 0; } size_t rsa_key_length = enc_rsa_key_length - padding; - // verify signature. - if (!session_ctx->ValidateMessage( - wrapped->context, wrapped_rsa_key_length - sizeof(wrapped->signature), - wrapped->signature, sizeof(wrapped->signature))) { - LOGE("[LoadDeviceRSAKey(): Could not verify signature]"); - return OEMCrypto_ERROR_SIGNATURE_FAILURE; - } if (!session_ctx->LoadRSAKey(&pkcs8_rsa_key[0], rsa_key_length)) { return OEMCrypto_ERROR_INVALID_RSA_KEY; } @@ -1085,7 +1263,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_GenerateRSASignature( } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_GenerateRSASignature(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } @@ -1096,7 +1274,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_GenerateRSASignature( return OEMCrypto_ERROR_SHORT_BUFFER; } - if (message == NULL || message_length == 0 || signature == NULL || + if (message == nullptr || message_length == 0 || signature == nullptr || signature_length == 0) { LOGE("[OEMCrypto_GenerateRSASignature(): OEMCrypto_ERROR_INVALID_CONTEXT]"); return OEMCrypto_ERROR_INVALID_CONTEXT; @@ -1122,7 +1300,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey( } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_GenerateDerivedKeys(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } @@ -1146,7 +1324,13 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey( return OEMCrypto_SUCCESS; } -OEMCRYPTO_API uint32_t OEMCrypto_APIVersion() { return 15; } +OEMCRYPTO_API uint32_t OEMCrypto_APIVersion() { + return CryptoEngine::kApiVersion; +} + +OEMCRYPTO_API uint32_t OEMCrypto_MinorAPIVersion() { + return CryptoEngine::kMinorApiVersion; +} OEMCRYPTO_API uint8_t OEMCrypto_Security_Patch_Level() { uint8_t security_patch_level = crypto_engine->config_security_patch_level(); @@ -1164,8 +1348,8 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetHDCPCapability( LOGE("OEMCrypto_GetHDCPCapability: OEMCrypto Not Initialized."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - if (current == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE; - if (maximum == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + if (current == nullptr) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + if (maximum == nullptr) return OEMCrypto_ERROR_UNKNOWN_FAILURE; *current = crypto_engine->config_current_hdcp_capability(); *maximum = crypto_engine->config_maximum_hdcp_capability(); return OEMCrypto_SUCCESS; @@ -1200,12 +1384,20 @@ OEMCRYPTO_API bool OEMCrypto_SupportsUsageTable() { return supports_usage; } +OEMCRYPTO_API size_t OEMCrypto_MaximumUsageTableHeaderSize() { + if (crypto_engine == nullptr) { + LOGE("OEMCrypto_MaximumUsageTableHeaderSize: OEMCrypto Not Initialized."); + return 0; + } + return crypto_engine->max_usage_table_size(); +} + OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetNumberOfOpenSessions(size_t* count) { if (crypto_engine == nullptr) { LOGE("OEMCrypto_GetNumberOfOpenSessions: OEMCrypto Not Initialized."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - if (count == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + if (count == nullptr) return OEMCrypto_ERROR_UNKNOWN_FAILURE; *count = crypto_engine->GetNumberOfOpenSessions(); return OEMCrypto_SUCCESS; } @@ -1216,7 +1408,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetMaxNumberOfSessions( LOGE("OEMCrypto_GetMaxNumberOfSessions: OEMCrypto Not Initialized."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - if (maximum == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + if (maximum == nullptr) return OEMCrypto_ERROR_UNKNOWN_FAILURE; *maximum = crypto_engine->GetMaxNumberOfSessions(); return OEMCrypto_SUCCESS; } @@ -1252,12 +1444,12 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_Generic_Encrypt( return OEMCrypto_ERROR_KEYBOX_INVALID; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_Generic_Encrypt(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } - if (in_buffer == NULL || buffer_length == 0 || iv == NULL || - out_buffer == NULL) { + if (in_buffer == nullptr || buffer_length == 0 || iv == nullptr || + out_buffer == nullptr) { LOGE("[OEMCrypto_Generic_Encrypt(): OEMCrypto_ERROR_INVALID_CONTEXT]"); return OEMCrypto_ERROR_INVALID_CONTEXT; } @@ -1278,12 +1470,12 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_Generic_Decrypt( return OEMCrypto_ERROR_KEYBOX_INVALID; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_Generic_Decrypt(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } - if (in_buffer == NULL || buffer_length == 0 || iv == NULL || - out_buffer == NULL) { + if (in_buffer == nullptr || buffer_length == 0 || iv == nullptr || + out_buffer == nullptr) { LOGE("[OEMCrypto_Generic_Decrypt(): OEMCrypto_ERROR_INVALID_CONTEXT]"); return OEMCrypto_ERROR_INVALID_CONTEXT; } @@ -1305,7 +1497,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_Generic_Sign( return OEMCrypto_ERROR_KEYBOX_INVALID; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_Generic_Sign(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } @@ -1313,7 +1505,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_Generic_Sign( *signature_length = SHA256_DIGEST_LENGTH; return OEMCrypto_ERROR_SHORT_BUFFER; } - if (in_buffer == NULL || buffer_length == 0 || signature == NULL) { + if (in_buffer == nullptr || buffer_length == 0 || signature == nullptr) { LOGE("[OEMCrypto_Generic_Sign(): OEMCrypto_ERROR_INVALID_CONTEXT]"); return OEMCrypto_ERROR_INVALID_CONTEXT; } @@ -1335,14 +1527,14 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_Generic_Verify( return OEMCrypto_ERROR_KEYBOX_INVALID; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_Generic_Verify(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } if (signature_length != SHA256_DIGEST_LENGTH) { return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - if (in_buffer == NULL || buffer_length == 0 || signature == NULL) { + if (in_buffer == nullptr || buffer_length == 0 || signature == nullptr) { LOGE("[OEMCrypto_Generic_Verify(): OEMCrypto_ERROR_INVALID_CONTEXT]"); return OEMCrypto_ERROR_INVALID_CONTEXT; } @@ -1350,11 +1542,6 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_Generic_Verify( signature, signature_length); } -// TODO(fredgc): remove this. -OEMCRYPTO_API OEMCryptoResult OEMCrypto_UpdateUsageTable() { - return OEMCrypto_ERROR_NOT_IMPLEMENTED; -} - OEMCRYPTO_API OEMCryptoResult OEMCrypto_DeactivateUsageEntry( OEMCrypto_SESSION session, const uint8_t* pst, size_t pst_length) { if (crypto_engine == nullptr) { @@ -1365,7 +1552,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_DeactivateUsageEntry( return OEMCrypto_ERROR_NOT_IMPLEMENTED; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_DeactivateUsageEntry(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } @@ -1389,7 +1576,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_ReportUsage(OEMCrypto_SESSION session, return OEMCrypto_ERROR_INVALID_CONTEXT; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_ReportUsage(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } @@ -1398,30 +1585,6 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_ReportUsage(OEMCrypto_SESSION session, return sts; } -OEMCRYPTO_API OEMCryptoResult OEMCrypto_DeleteUsageEntry( - OEMCrypto_SESSION, const uint8_t*, size_t, const uint8_t*, size_t, - const uint8_t*, size_t) { - // TODO(fredgc): delete this. - return OEMCrypto_ERROR_NOT_IMPLEMENTED; -} - -OEMCRYPTO_API OEMCryptoResult OEMCrypto_ForceDeleteUsageEntry(const uint8_t*, - size_t) { - // TODO(fredgc): delete this. - return OEMCrypto_ERROR_NOT_IMPLEMENTED; -} - -OEMCRYPTO_API OEMCryptoResult OEMCrypto_DeleteOldUsageTable() { - if (crypto_engine == nullptr) { - LOGE("OEMCrypto_DeleteOldUsageTable: OEMCrypto Not Initialized."); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - if (!crypto_engine->config_supports_usage_table()) { - return OEMCrypto_ERROR_NOT_IMPLEMENTED; - } - return crypto_engine->usage_table().DeleteOldUsageTable(); -} - OEMCRYPTO_API bool OEMCrypto_IsSRMUpdateSupported() { if (crypto_engine == nullptr) { LOGE("OEMCrypto_IsSRMUpdateSupported: OEMCrypto Not Initialized."); @@ -1502,7 +1665,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_CreateNewUsageEntry( return OEMCrypto_ERROR_NOT_IMPLEMENTED; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_CreateNewUsageEntry(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } @@ -1524,7 +1687,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_LoadUsageEntry( return OEMCrypto_ERROR_NOT_IMPLEMENTED; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_LoadUsageEntry(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } @@ -1551,7 +1714,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_UpdateUsageEntry( return OEMCrypto_ERROR_INVALID_CONTEXT; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_UpdateUsageEntry(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } @@ -1583,49 +1746,13 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_MoveEntry(OEMCrypto_SESSION session, return OEMCrypto_ERROR_NOT_IMPLEMENTED; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_MoveEntry(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } return session_ctx->MoveEntry(new_index); } -OEMCRYPTO_API OEMCryptoResult OEMCrypto_CopyOldUsageEntry( - OEMCrypto_SESSION session, const uint8_t* pst, size_t pst_length) { - if (crypto_engine == nullptr) { - LOGE("OEMCrypto_CopyOldUsageEntry: OEMCrypto Not Initialized."); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - if (!crypto_engine->config_supports_usage_table()) { - return OEMCrypto_ERROR_NOT_IMPLEMENTED; - } - SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { - LOGE("[OEMCrypto_CopyOldUsageEntry(): ERROR_INVALID_SESSION]"); - return OEMCrypto_ERROR_INVALID_SESSION; - } - std::vector pstv(pst, pst + pst_length); - return session_ctx->CopyOldUsageEntry(pstv); -} - -OEMCRYPTO_API OEMCryptoResult OEMCrypto_CreateOldUsageEntry( - uint64_t time_since_license_received, uint64_t time_since_first_decrypt, - uint64_t time_since_last_decrypt, OEMCrypto_Usage_Entry_Status status, - uint8_t* server_mac_key, uint8_t* client_mac_key, const uint8_t* pst, - size_t pst_length) { - if (crypto_engine == nullptr) { - LOGE("OEMCrypto_CreateOldUsageEntry: OEMCrypto Not Initialized."); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - if (!crypto_engine->config_supports_usage_table()) { - return OEMCrypto_ERROR_NOT_IMPLEMENTED; - } - return crypto_engine->usage_table().CreateOldUsageEntry( - time_since_license_received, time_since_first_decrypt, - time_since_last_decrypt, status, server_mac_key, client_mac_key, pst, - pst_length); -} - OEMCRYPTO_API uint32_t OEMCrypto_SupportsDecryptHash() { return OEMCrypto_CRC_Clear_Buffer; } @@ -1638,7 +1765,7 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_SetDecryptHash( return OEMCrypto_ERROR_UNKNOWN_FAILURE; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_SetDecryptHash(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } @@ -1652,11 +1779,23 @@ OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetHashErrorCode( return OEMCrypto_ERROR_UNKNOWN_FAILURE; } SessionContext* session_ctx = crypto_engine->FindSession(session); - if (!session_ctx || !session_ctx->isValid()) { + if (session_ctx == nullptr || !session_ctx->isValid()) { LOGE("[OEMCrypto_GetHashErrorCode(): ERROR_INVALID_SESSION]"); return OEMCrypto_ERROR_INVALID_SESSION; } return session_ctx->GetHashErrorCode(failed_frame_number); } +OEMCRYPTO_API OEMCryptoResult OEMCrypto_AllocateSecureBuffer( + OEMCrypto_SESSION session, size_t buffer_size, + OEMCrypto_DestBufferDesc* output_descriptor, int* secure_fd) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCRYPTO_API OEMCryptoResult OEMCrypto_FreeSecureBuffer( + OEMCrypto_SESSION session, OEMCrypto_DestBufferDesc* output_descriptor, + int secure_fd) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + } // namespace wvoec_ref diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_rsa_key_shared.cpp b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_rsa_key_shared.cpp index b34a316f..6c73ad81 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_rsa_key_shared.cpp +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_rsa_key_shared.cpp @@ -34,50 +34,50 @@ void RSA_shared_ptr::reset() { RSA_free(rsa_key_); } key_owned_ = false; - rsa_key_ = NULL; + rsa_key_ = nullptr; } bool RSA_shared_ptr::LoadPkcs8RsaKey(const uint8_t* buffer, size_t length) { - assert(buffer != NULL); + assert(buffer != nullptr); reset(); uint8_t* pkcs8_rsa_key = const_cast(buffer); BIO* bio = BIO_new_mem_buf(pkcs8_rsa_key, length); - if (bio == NULL) { + if (bio == nullptr) { LOGE("[LoadPkcs8RsaKey(): Could not allocate bio buffer]"); return false; } bool success = true; - PKCS8_PRIV_KEY_INFO* pkcs8_pki = d2i_PKCS8_PRIV_KEY_INFO_bio(bio, NULL); - if (pkcs8_pki == NULL) { + PKCS8_PRIV_KEY_INFO* pkcs8_pki = d2i_PKCS8_PRIV_KEY_INFO_bio(bio, nullptr); + if (pkcs8_pki == nullptr) { BIO_reset(bio); - pkcs8_pki = d2i_PKCS8_PRIV_KEY_INFO_bio(bio, NULL); - if (pkcs8_pki == NULL) { - LOGE("[LoadPkcs8RsaKey(): d2i_PKCS8_PRIV_KEY_INFO_bio returned NULL]"); + pkcs8_pki = d2i_PKCS8_PRIV_KEY_INFO_bio(bio, nullptr); + if (pkcs8_pki == nullptr) { + LOGE("[LoadPkcs8RsaKey(): d2i_PKCS8_PRIV_KEY_INFO_bio returned nullptr]"); dump_boringssl_error(); success = false; } } - EVP_PKEY* evp = NULL; + EVP_PKEY* evp = nullptr; if (success) { evp = EVP_PKCS82PKEY(pkcs8_pki); - if (evp == NULL) { - LOGE("[LoadPkcs8RsaKey(): EVP_PKCS82PKEY returned NULL]"); + if (evp == nullptr) { + LOGE("[LoadPkcs8RsaKey(): EVP_PKCS82PKEY returned nullptr]"); dump_boringssl_error(); success = false; } } if (success) { rsa_key_ = EVP_PKEY_get1_RSA(evp); - if (rsa_key_ == NULL) { + if (rsa_key_ == nullptr) { LOGE("[LoadPkcs8RsaKey(): PrivateKeyInfo did not contain an RSA key]"); success = false; } key_owned_ = true; } - if (evp != NULL) { + if (evp != nullptr) { EVP_PKEY_free(evp); } - if (pkcs8_pki != NULL) { + if (pkcs8_pki != nullptr) { PKCS8_PRIV_KEY_INFO_free(pkcs8_pki); } BIO_free(bio); diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_rsa_key_shared.h b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_rsa_key_shared.h index c80745d3..98ea154e 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_rsa_key_shared.h +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_rsa_key_shared.h @@ -18,7 +18,7 @@ namespace wvoec_ref { // counting. class RSA_shared_ptr { public: - RSA_shared_ptr() : rsa_key_(NULL), key_owned_(false) {} + RSA_shared_ptr() : rsa_key_(nullptr), key_owned_(false) {} ~RSA_shared_ptr() { reset(); }; // Explicitly allow copy as share. explicit RSA_shared_ptr(const RSA_shared_ptr& other) : diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session.cpp b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session.cpp index eb70320d..4e74cf6e 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session.cpp +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session.cpp @@ -18,19 +18,21 @@ #include #include #include +#include #include #include #include #include +#include "disallow_copy_and_assign.h" #include "keys.h" #include "log.h" +#include "odk.h" #include "oemcrypto_engine_ref.h" #include "oemcrypto_key_ref.h" #include "oemcrypto_rsa_key_shared.h" #include "oemcrypto_types.h" #include "platform.h" -#include "disallow_copy_and_assign.h" #include "string_conversions.h" #include "wvcrc32.h" @@ -46,6 +48,43 @@ void ctr128_inc64(uint8_t* counter) { if (++counter[--n] != 0) return; } while (n > 8); } + +void advance_dest_buffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) { + switch (dest_buffer->type) { + case OEMCrypto_BufferType_Clear: + dest_buffer->buffer.clear.address += bytes; + dest_buffer->buffer.clear.address_length -= bytes; + break; + + case OEMCrypto_BufferType_Secure: + dest_buffer->buffer.secure.offset += bytes; + break; + + case OEMCrypto_BufferType_Direct: + // Nothing to do for this buffer type. + break; + } +} + +// Advance an IV according to ISO-CENC's CTR modes. The lower half of the IV is +// split off and treated as an unsigned 64-bit integer, then incremented by the +// number of complete crypto blocks decrypted. The resulting value is then +// copied back into the IV over the previous lower half. +void advance_iv_ctr(uint8_t (*subsample_iv)[wvoec::KEY_IV_SIZE], size_t bytes) { + uint64_t counter; + // Per its type, sizeof(*subsample_iv) == wvoec::KEY_IV_SIZE + static_assert(sizeof(counter) * 2 == wvoec::KEY_IV_SIZE, + "A uint64_t failed to be half the size of an AES-128 IV."); + constexpr size_t half_iv_size = wvoec::KEY_IV_SIZE / 2; + memcpy(&counter, &(*subsample_iv)[half_iv_size], half_iv_size); + + const size_t increment = + bytes / wvoec::AES_128_BLOCK_SIZE; // The truncation here is intentional + counter = wvcdm::htonll64(wvcdm::ntohll64(counter) + increment); + + memcpy(&(*subsample_iv)[half_iv_size], &counter, half_iv_size); +} + } // namespace namespace wvoec_ref { @@ -59,6 +98,7 @@ class ContentKeysContext : public SessionContextKeys { size_t size() override { return session_keys_.size(); } bool Insert(const KeyId& key_id, const Key& key_data) override; Key* Find(const KeyId& key_id) override; + Key* FirstKey() override; void Remove(const KeyId& key_id) override; void UpdateDuration(const KeyControlBlock& control) override; @@ -82,6 +122,8 @@ Key* ContentKeysContext::Find(const KeyId& key_id) { return session_keys_.Find(key_id); } +Key* ContentKeysContext::FirstKey() { return session_keys_.FirstKey(); } + void ContentKeysContext::Remove(const KeyId& key_id) { session_keys_.Remove(key_id); } @@ -112,6 +154,7 @@ class EntitlementKeysContext : public SessionContextKeys { size_t size() override { return session_keys_.size(); } bool Insert(const KeyId& key_id, const Key& key_data) override; Key* Find(const KeyId& key_id) override; + Key* FirstKey() override; void Remove(const KeyId& key_id) override; void UpdateDuration(const KeyControlBlock& control) override; bool SetContentKey(const KeyId& entitlement_id, @@ -134,6 +177,8 @@ Key* EntitlementKeysContext::Find(const KeyId& key_id) { return session_keys_.Find(key_id); } +Key* EntitlementKeysContext::FirstKey() { return session_keys_.FirstKey(); } + void EntitlementKeysContext::Remove(const KeyId& key_id) { session_keys_.Remove(key_id); } @@ -156,14 +201,40 @@ EntitlementKey* EntitlementKeysContext::GetEntitlementKey( /***************************************/ +SessionContext::SessionContext(CryptoEngine* ce, SessionId sid, + const RSA_shared_ptr& rsa_key) + : valid_(true), + ce_(ce), + id_(sid), + current_content_key_(nullptr), + session_keys_(nullptr), + rsa_key_(rsa_key), + allowed_schemes_(kSign_RSASSA_PSS), + decrypt_started_(false), + timer_limits_(), + clock_values_(), + usage_entry_(nullptr), + srm_requirements_status_(NoSRMVersion), + usage_entry_status_(kNoUsageEntry), + compute_hash_(false), + current_hash_(0), + bad_frame_number_(0), + hash_error_(OEMCrypto_SUCCESS), + state_nonce_created_(false), + state_request_signed_(false), + state_response_loaded_(false) { + ODK_InitializeSessionValues(&timer_limits_, &clock_values_, &nonce_values_, + CryptoEngine::kApiVersion, sid); +} + SessionContext::~SessionContext() { if (usage_entry_) { delete usage_entry_; - usage_entry_ = NULL; + usage_entry_ = nullptr; } if (session_keys_) { delete session_keys_; - session_keys_ = NULL; + session_keys_ = nullptr; } } @@ -171,7 +242,7 @@ SessionContext::~SessionContext() { bool SessionContext::DeriveKey(const std::vector& key, const std::vector& context, int counter, std::vector* out) { - if (key.empty() || counter > 4 || context.empty() || out == NULL) { + if (key.empty() || counter > 4 || context.empty() || out == nullptr) { LOGE("[DeriveKey(): OEMCrypto_ERROR_INVALID_CONTEXT]"); return false; } @@ -286,109 +357,274 @@ bool SessionContext::RSADeriveKeys( return DeriveKeys(session_key_, mac_key_context, enc_key_context); } +OEMCryptoResult SessionContext::PrepAndSignLicenseRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + uint8_t* signature, size_t* signature_length) { + if (signature_length == nullptr || core_message_length == nullptr) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + const size_t required_signature_size = CertSignatureSize(); + OEMCryptoResult result = ODK_PrepareCoreLicenseRequest( + message, message_length, core_message_length, &nonce_values_); + if (*signature_length < required_signature_size || + result == OEMCrypto_ERROR_SHORT_BUFFER) { + *signature_length = required_signature_size; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + if (result != OEMCrypto_SUCCESS) { + LOGE("ODK error: %d", result); + return result; + } + if (message == nullptr || message_length < *core_message_length || + signature == nullptr) { + LOGE("OEMCrypto_ERROR_INVALID_CONTEXT"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (state_request_signed_) { + LOGE("Attempt to sign two license requests"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + // For backwards compatibility, we only sign the message body, and we compute + // a SHA256 of the core message. + SHA256(message, *core_message_length, license_request_hash_); + const uint8_t* message_body = message + *core_message_length; + const size_t message_body_length = message_length - *core_message_length; + result = GenerateCertSignature(message_body, message_body_length, signature, + signature_length); + if (result == OEMCrypto_SUCCESS) state_request_signed_ = true; + ODK_InitializeClockValues(&clock_values_, ce_->SystemTime()); + return result; +} + +OEMCryptoResult SessionContext::PrepAndSignRenewalRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + uint8_t* signature, size_t* signature_length) { + if (signature_length == nullptr || core_message_length == nullptr) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + // If we have signed a request, but have not loaded it, something is wrong. + // On the other hand, we can sign a license release using the mac keys from + // the usage table. So it is OK if we have never signed a license request. + if (state_request_signed_ && !state_response_loaded_) { + LOGE("Attempt to sign renewal before load"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const size_t required_signature_size = SHA256_DIGEST_LENGTH; + const uint64_t now = ce_->SystemTime(); + const OEMCryptoResult result = ODK_PrepareCoreRenewalRequest( + message, message_length, core_message_length, &nonce_values_, + &clock_values_, now); + if (*signature_length < required_signature_size || + result == OEMCrypto_ERROR_SHORT_BUFFER) { + *signature_length = required_signature_size; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + if (result != OEMCrypto_SUCCESS) { + LOGE("ODK error: %d", result); + return result; + } + if (message == nullptr || message_length < *core_message_length || + signature == nullptr) { + LOGE("OEMCrypto_ERROR_INVALID_CONTEXT"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + // If we are talking to an old license server, then we only sign the message + // body. + if (nonce_values_.api_major_version < 16) { + const uint8_t* message_body = message + *core_message_length; + const size_t message_body_length = message_length - *core_message_length; + return GenerateSignature(message_body, message_body_length, signature, + signature_length); + } else { + return GenerateSignature(message, message_length, signature, + signature_length); + } +} + +OEMCryptoResult SessionContext::PrepAndSignProvisioningRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + uint8_t* signature, size_t* signature_length) { + if (signature_length == nullptr || core_message_length == nullptr) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (state_request_signed_) { + LOGE("Attempt to sign prov request after license request"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const size_t required_signature_size = ROTSignatureSize(); + if (required_signature_size == 0) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + const std::vector& device_id = ce_->DeviceRootId(); + OEMCryptoResult result = ODK_PrepareCoreProvisioningRequest( + message, message_length, core_message_length, &nonce_values_, + device_id.data(), device_id.size()); + if (*signature_length < required_signature_size || + result == OEMCrypto_ERROR_SHORT_BUFFER) { + *signature_length = required_signature_size; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + if (result != OEMCrypto_SUCCESS) { + LOGE("ODK error: %d", result); + return result; + } + if (message == nullptr || message_length == 0 || signature == nullptr) { + LOGE("OEMCrypto_ERROR_INVALID_CONTEXT"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (ce_->config_provisioning_method() == OEMCrypto_Keybox) { + result = + GenerateSignature(message, message_length, signature, signature_length); + } else if (ce_->config_provisioning_method() == OEMCrypto_OEMCertificate) { + result = GenerateCertSignature(message, message_length, signature, + signature_length); + } else { + LOGE("Bad prov method = %d", ce_->config_provisioning_method()); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (result == OEMCrypto_SUCCESS) state_request_signed_ = true; + return result; +} + // Utility function to generate a message signature -bool SessionContext::GenerateSignature(const uint8_t* message, - size_t message_length, - uint8_t* signature, - size_t* signature_length) { - if (message == NULL || message_length == 0 || signature == NULL || - signature_length == 0) { - LOGE("[OEMCrypto_GenerateSignature(): OEMCrypto_ERROR_INVALID_CONTEXT]"); - return false; +OEMCryptoResult SessionContext::GenerateSignature(const uint8_t* message, + size_t message_length, + uint8_t* signature, + size_t* signature_length) { + if (message == nullptr || message_length == 0 || signature == nullptr || + signature_length == nullptr) { + LOGE("OEMCrypto_ERROR_INVALID_CONTEXT"); + return OEMCrypto_ERROR_INVALID_CONTEXT; } - if (mac_key_client_.size() != wvoec::MAC_KEY_SIZE) { - return false; + return OEMCrypto_ERROR_INVALID_CONTEXT; } - - if (*signature_length < SHA256_DIGEST_LENGTH) { + if (*signature_length != SHA256_DIGEST_LENGTH) { *signature_length = SHA256_DIGEST_LENGTH; - return false; + return OEMCrypto_ERROR_SHORT_BUFFER; } - unsigned int md_len = *signature_length; if (HMAC(EVP_sha256(), &mac_key_client_[0], wvoec::MAC_KEY_SIZE, message, message_length, signature, &md_len)) { *signature_length = md_len; - return true; + return OEMCrypto_SUCCESS; } - return false; + return OEMCrypto_ERROR_UNKNOWN_FAILURE; } +// This is ussd when the device is a cast receiver. size_t SessionContext::RSASignatureSize() { if (!rsa_key()) { - LOGE("[GenerateRSASignature(): no RSA key set]"); + LOGE("no RSA key set"); return 0; } return static_cast(RSA_size(rsa_key())); } -OEMCryptoResult SessionContext::GenerateRSASignature( +size_t SessionContext::CertSignatureSize() { + // TODO(b/67735947): Add ECC cert support. + if (!rsa_key()) { + LOGE("No private key set"); + return 0; + } + return static_cast(RSA_size(rsa_key())); +} + +size_t SessionContext::ROTSignatureSize() { + if (ce_->config_provisioning_method() == OEMCrypto_Keybox) + return SHA256_DIGEST_LENGTH; + if (ce_->config_provisioning_method() == OEMCrypto_OEMCertificate) + return CertSignatureSize(); + LOGE("Bad prov method = %d", ce_->config_provisioning_method()); + return 0; +} + +OEMCryptoResult SessionContext::GenerateCertSignature( const uint8_t* message, size_t message_length, uint8_t* signature, - size_t* signature_length, RSA_Padding_Scheme padding_scheme) { - if (message == NULL || message_length == 0 || signature == NULL || + size_t* signature_length) { + // TODO(b/67735947): Add ECC cert support. + if (message == nullptr || message_length == 0 || signature == nullptr || signature_length == 0) { - LOGE("[GenerateRSASignature(): OEMCrypto_ERROR_INVALID_CONTEXT]"); + LOGE("OEMCrypto_ERROR_INVALID_CONTEXT"); return OEMCrypto_ERROR_INVALID_CONTEXT; } if (!rsa_key()) { - LOGE("[GenerateRSASignature(): no RSA key set]"); + LOGE("no RSA key set"); + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } + if (*signature_length < static_cast(RSA_size(rsa_key()))) { + *signature_length = CertSignatureSize(); + return OEMCrypto_ERROR_SHORT_BUFFER; + } + if (allowed_schemes_ != kSign_RSASSA_PSS) { + LOGE("message signing not allowed"); + return OEMCrypto_ERROR_INVALID_RSA_KEY; + } + + // Hash the message using SHA1. + uint8_t hash[SHA_DIGEST_LENGTH]; + if (!SHA1(message, message_length, hash)) { + LOGE("Error creating signature hash"); + dump_boringssl_error(); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + // Add PSS padding. + std::vector padded_digest(*signature_length); + int status = RSA_padding_add_PKCS1_PSS_mgf1( + rsa_key(), &padded_digest[0], hash, EVP_sha1(), nullptr, kPssSaltLength); + if (status == -1) { + LOGE("Error padding hash"); + dump_boringssl_error(); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + // Encrypt PSS padded digest. + status = RSA_private_encrypt(*signature_length, &padded_digest[0], signature, + rsa_key(), RSA_NO_PADDING); + if (status == -1) { + LOGE("Error in private encrypt"); + dump_boringssl_error(); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::GenerateRSASignature( + const uint8_t* message, size_t message_length, uint8_t* signature, + size_t* signature_length, RSA_Padding_Scheme padding_scheme) { + if (message == nullptr || message_length == 0 || signature == nullptr || + signature_length == 0) { + LOGE("OEMCrypto_ERROR_INVALID_CONTEXT"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (!rsa_key()) { + LOGE("no RSA key set"); return OEMCrypto_ERROR_INVALID_RSA_KEY; } if (*signature_length < static_cast(RSA_size(rsa_key()))) { *signature_length = RSA_size(rsa_key()); return OEMCrypto_ERROR_SHORT_BUFFER; } - if ((padding_scheme & allowed_schemes_) != padding_scheme) { - LOGE("[GenerateRSASignature(): padding_scheme not allowed]"); + if (((padding_scheme & allowed_schemes_) != padding_scheme) || + (padding_scheme != kSign_PKCS1_Block1)) { + LOGE("padding_scheme not allowed"); return OEMCrypto_ERROR_INVALID_RSA_KEY; } - // This is the standard padding scheme used for license requests. - if (padding_scheme == kSign_RSASSA_PSS) { - // Hash the message using SHA1. - uint8_t hash[SHA_DIGEST_LENGTH]; - if (!SHA1(message, message_length, hash)) { - LOGE("[GeneratRSASignature(): error creating signature hash.]"); - dump_boringssl_error(); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - - // Add PSS padding. - std::vector padded_digest(*signature_length); - int status = RSA_padding_add_PKCS1_PSS_mgf1( - rsa_key(), &padded_digest[0], hash, EVP_sha1(), NULL, kPssSaltLength); - if (status == -1) { - LOGE("[GeneratRSASignature(): error padding hash.]"); - dump_boringssl_error(); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - - // Encrypt PSS padded digest. - status = RSA_private_encrypt(*signature_length, &padded_digest[0], - signature, rsa_key(), RSA_NO_PADDING); - if (status == -1) { - LOGE("[GeneratRSASignature(): error in private encrypt.]"); - dump_boringssl_error(); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - // This is the alternate padding scheme used by cast receivers only. - } else if (padding_scheme == kSign_PKCS1_Block1) { - if (message_length > 83) { - LOGE("[GeneratRSASignature(): RSA digest too large.]"); - return OEMCrypto_ERROR_SIGNATURE_FAILURE; - } - // Pad the message with PKCS1 padding, and then encrypt. - size_t status = RSA_private_encrypt(message_length, message, signature, - rsa_key(), RSA_PKCS1_PADDING); - if (status != *signature_length) { - LOGE("[GeneratRSASignature(): error in RSA private encrypt. status=%d]", - status); - dump_boringssl_error(); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - } else { // Bad RSA_Padding_Scheme - return OEMCrypto_ERROR_INVALID_RSA_KEY; + // This is the maximum digest size possible for PKCS1 block type 1, + // as used for a CAST receiver. + const size_t max_digest_size = 83u; + if (message_length > max_digest_size) { + LOGE("RSA digest too large"); + return OEMCrypto_ERROR_SIGNATURE_FAILURE; } + // Pad the message with PKCS1 padding, and then encrypt. + int status = RSA_private_encrypt(message_length, message, signature, + rsa_key(), RSA_PKCS1_PADDING); + if (status < 0) { + LOGE("error in RSA private encrypt. status=%d", status); + dump_boringssl_error(); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + *signature_length = static_cast(RSA_size(rsa_key())); return OEMCrypto_SUCCESS; } @@ -405,10 +641,10 @@ bool SessionContext::ValidateMessage(const uint8_t* given_message, unsigned int md_len = SHA256_DIGEST_LENGTH; if (!HMAC(EVP_sha256(), mac_key_server_.data(), mac_key_server_.size(), given_message, message_length, computed_signature, &md_len)) { - LOGE("ValidateMessage: Could not compute signature."); + LOGE("ValidateMessage: Could not compute signature"); return false; } - if (memcmp(given_signature, computed_signature, signature_length)) { + if (CRYPTO_memcmp(given_signature, computed_signature, signature_length)) { LOGE("Invalid signature given: %s", wvcdm::HexEncode(given_signature, signature_length).c_str()); LOGE("Invalid signature computed: %s", @@ -421,16 +657,16 @@ bool SessionContext::ValidateMessage(const uint8_t* given_message, OEMCryptoResult SessionContext::CheckStatusOnline(uint32_t nonce, uint32_t control) { if (!(control & wvoec::kControlNonceEnabled)) { - LOGE("LoadKeys: Server provided Nonce_Required but Nonce_Enabled = 0."); + LOGE("LoadKeys: Server provided Nonce_Required but Nonce_Enabled = 0"); // Server error. Continue, and assume nonce required. } if (!CheckNonce(nonce)) return OEMCrypto_ERROR_INVALID_NONCE; switch (usage_entry_status_) { case kNoUsageEntry: - LOGE("LoadKeys: Session did not create usage entry."); + LOGE("LoadKeys: Session did not create usage entry"); return OEMCrypto_ERROR_INVALID_CONTEXT; case kUsageEntryLoaded: - LOGE("LoadKeys: Session reloaded existing entry."); + LOGE("LoadKeys: Session reloaded existing entry"); return OEMCrypto_ERROR_INVALID_CONTEXT; case kUsageEntryNew: return OEMCrypto_SUCCESS; @@ -442,12 +678,12 @@ OEMCryptoResult SessionContext::CheckStatusOnline(uint32_t nonce, OEMCryptoResult SessionContext::CheckStatusOffline(uint32_t nonce, uint32_t control) { if (control & wvoec::kControlNonceEnabled) { - LOGE("KCB: Server provided NonceOrEntry but Nonce_Enabled = 1."); + LOGE("KCB: Server provided NonceOrEntry but Nonce_Enabled = 1"); // Server error. Continue, and assume nonce required. } switch (usage_entry_status_) { case kNoUsageEntry: - LOGE("LoadKeys: Session did not create or load usage entry."); + LOGE("LoadKeys: Session did not create or load usage entry"); return OEMCrypto_ERROR_INVALID_CONTEXT; case kUsageEntryLoaded: // Repeat load. Calling function will verify pst and keys. @@ -482,11 +718,38 @@ OEMCryptoResult SessionContext::CheckNonceOrEntry( return OEMCrypto_SUCCESS; } -void SessionContext::StartTimer() { timer_start_ = ce_->OnlineTime(); } - -uint32_t SessionContext::CurrentTimer() { - time_t now = ce_->OnlineTime(); - return now - timer_start_; +OEMCryptoResult SessionContext::LoadLicense(const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length) { + // Check state before we check signature. State is change in + // LoadKeysNoSignature. + if (state_response_loaded_) { + return OEMCrypto_ERROR_LICENSE_RELOAD; + } + ODK_ParsedLicense parsed_response; + const bool initial_license_load = (usage_entry_status_ != kUsageEntryLoaded); + const OEMCryptoResult result = ODK_ParseLicense( + message, message_length, core_message_length, initial_license_load, + usage_entry_present(), license_request_hash_, &timer_limits_, + &clock_values_, &nonce_values_, &parsed_response); + if (result != OEMCrypto_SUCCESS) { + LOGE("ODK Error %d", result); + return result; + } + // Validate message signature + if (!ValidateMessage(message, message_length, signature, signature_length)) { + return OEMCrypto_ERROR_SIGNATURE_FAILURE; + } + const uint8_t* message_body = message + core_message_length; + const size_t message_body_length = message_length - core_message_length; + return LoadKeysNoSignature( + message_body, message_body_length, parsed_response.enc_mac_keys_iv, + parsed_response.enc_mac_keys, parsed_response.key_array_length, + parsed_response.key_array, parsed_response.pst, + parsed_response.srm_restriction_data, + static_cast(parsed_response.license_type)); } OEMCryptoResult SessionContext::LoadKeys( @@ -496,12 +759,40 @@ OEMCryptoResult SessionContext::LoadKeys( const OEMCrypto_KeyObject* key_array, OEMCrypto_Substring pst, OEMCrypto_Substring srm_restriction_data, OEMCrypto_LicenseType license_type) { + // Check state before we check signature. State is change in + // LoadKeysNoSignature. + if (state_response_loaded_) { + return OEMCrypto_ERROR_LICENSE_RELOAD; + } // Validate message signature if (!ValidateMessage(message, message_length, signature, signature_length)) { return OEMCrypto_ERROR_SIGNATURE_FAILURE; } + OEMCryptoResult result = LoadKeysNoSignature( + message, message_length, enc_mac_keys_iv, enc_mac_keys, num_keys, + key_array, pst, srm_restriction_data, license_type); + if (result != OEMCrypto_SUCCESS) return result; + Key* key = session_keys_->FirstKey(); + uint32_t duration = key ? key->control().duration() : 0; + result = ODK_InitializeV15Values(&timer_limits_, &clock_values_, + &nonce_values_, duration, ce_->SystemTime()); + // TODO(b/140765227): clear session on errors + return result; +} - if (!session_keys_) { +OEMCryptoResult SessionContext::LoadKeysNoSignature( + const uint8_t* message, size_t message_length, + OEMCrypto_Substring enc_mac_keys_iv, OEMCrypto_Substring enc_mac_keys, + size_t num_keys, const OEMCrypto_KeyObject* key_array, + OEMCrypto_Substring pst, OEMCrypto_Substring srm_restriction_data, + OEMCrypto_LicenseType license_type) { + if (state_response_loaded_) { + return OEMCrypto_ERROR_LICENSE_RELOAD; + } + state_response_loaded_ = true; + if (num_keys < 1) return OEMCrypto_ERROR_INVALID_CONTEXT; + + if (session_keys_ == nullptr) { switch (license_type) { case OEMCrypto_ContentLicense: session_keys_ = new ContentKeysContext(); @@ -519,9 +810,6 @@ OEMCryptoResult SessionContext::LoadKeys( return OEMCrypto_ERROR_INVALID_CONTEXT; } } - - StartTimer(); - if (srm_restriction_data.length != 0) { const std::string kSRMVerificationString = "HDCPDATA"; if (memcmp(message + srm_restriction_data.offset, @@ -534,7 +822,7 @@ OEMCryptoResult SessionContext::LoadKeys( message + srm_restriction_data.offset + 8)); uint16_t current_version = 0; if (OEMCrypto_SUCCESS != ce_->current_srm_version(¤t_version)) { - LOGW("[LoadKeys: SRM Version not available."); + LOGW("[LoadKeys: SRM Version not available"); srm_requirements_status_ = InvalidSRMVersion; } else if (current_version < minimum_version) { LOGW("[LoadKeys: SRM Version is too small %d, required: %d", @@ -587,10 +875,9 @@ OEMCryptoResult SessionContext::LoadKeys( break; } } - FlushNonces(); if (status != OEMCrypto_SUCCESS) return status; - // enc_mac_key can be NULL if license renewal is not supported + // enc_mac_key can be nullptr if license renewal is not supported if (enc_mac_keys.length != 0) { // V2.1 license protocol: update mac keys after processing license response const std::vector enc_mac_keys_str = std::vector( @@ -610,7 +897,7 @@ OEMCryptoResult SessionContext::LoadKeys( switch (usage_entry_status_) { case kNoUsageEntry: if (pst.length > 0) { - LOGE("LoadKeys: PST specified but no usage entry loaded."); + LOGE("LoadKeys: PST specified but no usage entry loaded"); return OEMCrypto_ERROR_INVALID_CONTEXT; } break; // no extra check. @@ -641,7 +928,7 @@ OEMCryptoResult SessionContext::LoadKeys( } OEMCryptoResult SessionContext::LoadEntitledContentKeys( - const uint8_t* message, size_t message_length, size_t num_keys, + const uint8_t* message, size_t message_length, size_t key_array_length, const OEMCrypto_EntitledContentKeyObject* key_array) { if (!key_array) { return OEMCrypto_ERROR_UNKNOWN_FAILURE; @@ -649,7 +936,7 @@ OEMCryptoResult SessionContext::LoadEntitledContentKeys( if (!session_keys_ || session_keys_->type() != OEMCrypto_EntitlementLicense) { return OEMCrypto_ERROR_INVALID_CONTEXT; } - for (size_t i = 0; i < num_keys; ++i) { + for (size_t i = 0; i < key_array_length; ++i) { const OEMCrypto_EntitledContentKeyObject* key_data = &key_array[i]; std::vector entitlement_key_id; entitlement_key_id.assign(message + key_data->entitlement_key_id.offset, @@ -719,18 +1006,18 @@ OEMCryptoResult SessionContext::InstallKey( KeyControlBlock key_control_block(key_control_str); if (!key_control_block.valid()) { - LOGE("Error parsing key control."); + LOGE("Error parsing key control"); return OEMCrypto_ERROR_INVALID_CONTEXT; } if ((key_control_block.control_bits() & wvoec::kControlRequireAntiRollbackHardware) && !ce_->config_is_anti_rollback_hw_present()) { - LOGE("Anti-rollback hardware is required but hardware not present."); + LOGE("Anti-rollback hardware is required but hardware not present"); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - uint8_t minimum_patch_level = - (key_control_block.control_bits() & wvoec::kControlSecurityPatchLevelMask) >> - wvoec::kControlSecurityPatchLevelShift; + uint8_t minimum_patch_level = (key_control_block.control_bits() & + wvoec::kControlSecurityPatchLevelMask) >> + wvoec::kControlSecurityPatchLevelShift; if (minimum_patch_level > OEMCrypto_Security_Patch_Level()) { LOGE("[InstallKey(): security patch level: %d. Minimum:%d]", OEMCrypto_Security_Patch_Level(), minimum_patch_level); @@ -738,7 +1025,7 @@ OEMCryptoResult SessionContext::InstallKey( } OEMCryptoResult result = CheckNonceOrEntry(key_control_block); if (result != OEMCrypto_SUCCESS) { - LOGE("LoadKeys: Failed Nonce/PST check."); + LOGE("LoadKeys: Failed Nonce/PST check"); return result; } if (key_control_block.control_bits() & wvoec::kControlSRMVersionRequired) { @@ -753,7 +1040,7 @@ OEMCryptoResult SessionContext::InstallKey( } Key key(content_key, key_control_block); - if (!session_keys_) { + if (session_keys_ == nullptr) { return OEMCrypto_ERROR_INVALID_CONTEXT; } session_keys_->Insert(key_id, key); @@ -782,64 +1069,75 @@ bool SessionContext::InstallRSAEncryptedKey( return true; } +OEMCryptoResult SessionContext::LoadRenewal(const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length) { + if (session_keys_ == nullptr) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (!ValidateMessage(message, message_length, signature, signature_length)) { + LOGE("signature was invalid"); + return OEMCrypto_ERROR_SIGNATURE_FAILURE; + } + + // The reference implementation does not use a hardware timer. + uint64_t* timer_value = nullptr; + const OEMCryptoResult result = ODK_ParseRenewal( + message, message_length, core_message_length, &nonce_values_, + ce_->SystemTime(), &timer_limits_, &clock_values_, timer_value); + if (result == ODK_SET_TIMER || result == ODK_DISABLE_TIMER) + return OEMCrypto_SUCCESS; + if (result == ODK_TIMER_EXPIRED) return OEMCrypto_ERROR_KEY_EXPIRED; + // All other errors are returned to the caller. + return result; +} + OEMCryptoResult SessionContext::RefreshKey( const KeyId& key_id, const std::vector& key_control, const std::vector& key_control_iv) { - if (!session_keys_) { + if (session_keys_ == nullptr) { return OEMCrypto_ERROR_INVALID_CONTEXT; } - if (key_id.empty()) { - // Key control is not encrypted if key id is NULL - KeyControlBlock key_control_block(key_control); - if (!key_control_block.valid()) { - LOGE("Parse key control error."); - return OEMCrypto_ERROR_INVALID_CONTEXT; - } - if ((key_control_block.control_bits() & wvoec::kControlNonceEnabled) && - (!CheckNonce(key_control_block.nonce()))) { - LOGE("KCB: BAD Nonce"); - return OEMCrypto_ERROR_INVALID_NONCE; - } - // Apply duration to all keys in this session - session_keys_->UpdateDuration(key_control_block); - return OEMCrypto_SUCCESS; - } - - Key* content_key = session_keys_->Find(key_id); - - if (NULL == content_key) { - LOGE("Key ID not found."); - return OEMCrypto_ERROR_NO_CONTENT_KEY; - } - if (key_control.empty()) { return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - - const std::vector content_key_value = content_key->value(); - - // Decrypt encrypted key control block - std::vector control; - if (key_control_iv.empty()) { - control = key_control; + std::vector decrypted_key_control; + if (key_id.empty()) { + // Key control is not encrypted if key id is NULL + decrypted_key_control = key_control; } else { - if (!DecryptMessage(content_key_value, key_control_iv, key_control, - &control, 128 /* key size */)) { - return OEMCrypto_ERROR_UNKNOWN_FAILURE; + Key* content_key = session_keys_->Find(key_id); + if (nullptr == content_key) { + LOGE("Key ID not found."); + return OEMCrypto_ERROR_NO_CONTENT_KEY; + } + const std::vector content_key_value = content_key->value(); + // Decrypt encrypted key control block + if (key_control_iv.empty()) { + decrypted_key_control = key_control; + } else { + if (!DecryptMessage(content_key_value, key_control_iv, key_control, + &decrypted_key_control, 128 /* key size */)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } } } - - KeyControlBlock key_control_block(control); + KeyControlBlock key_control_block(decrypted_key_control); if (!key_control_block.valid()) { - LOGE("Error parsing key control."); + LOGE("Parse key control error."); return OEMCrypto_ERROR_INVALID_CONTEXT; } - if ((key_control_block.control_bits() & wvoec::kControlNonceEnabled) && - (!CheckNonce(key_control_block.nonce()))) { - return OEMCrypto_ERROR_INVALID_NONCE; - } - content_key->UpdateDuration(key_control_block); - return OEMCrypto_SUCCESS; + uint32_t new_key_duration = key_control_block.duration(); + uint64_t* timer_value = nullptr; + const OEMCryptoResult result = + ODK_RefreshV15Values(&timer_limits_, &clock_values_, &nonce_values_, + ce_->SystemTime(), new_key_duration, timer_value); + if (result == ODK_SET_TIMER || result == ODK_DISABLE_TIMER) + return OEMCrypto_SUCCESS; + if (result == ODK_TIMER_EXPIRED) return OEMCrypto_ERROR_KEY_EXPIRED; + return result; } bool SessionContext::DecryptRSAKey(const uint8_t* enc_rsa_key, @@ -894,7 +1192,7 @@ OEMCryptoResult SessionContext::CheckKeyUse(const std::string& log_string, OEMCryptoBufferType buffer_type) { const KeyControlBlock& control = current_content_key()->control(); if (use_type && (!(control.control_bits() & use_type))) { - LOGE("[%s(): control bit says not allowed.", log_string.c_str()); + LOGE("[%s(): control bit says not allowed", log_string.c_str()); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } if (control.control_bits() & wvoec::kControlDataPathSecure) { @@ -904,17 +1202,19 @@ OEMCryptoResult SessionContext::CheckKeyUse(const std::string& log_string, return OEMCrypto_ERROR_DECRYPT_FAILED; } } - if (control.control_bits() & wvoec::kControlReplayMask) { - if (!CheckUsageEntry()) { - LOGE("[%s(): usage entry not valid]", log_string.c_str()); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - } - if (control.duration() > 0) { - if (control.duration() < CurrentTimer()) { - LOGE("[%s(): key expired.", log_string.c_str()); - return OEMCrypto_ERROR_KEY_EXPIRED; - } + if (!decrypt_started_) { + // The reference implementation does not have a hardware timer. + uint64_t* timer_expiration = nullptr; + const OEMCryptoResult result = ODK_AttemptFirstPlayback( + ce_->SystemTime(), &timer_limits_, &clock_values_, timer_expiration); + if (result == ODK_TIMER_EXPIRED) return OEMCrypto_ERROR_KEY_EXPIRED; + if (usage_entry_ != nullptr) usage_entry_->ForbidReport(); + } else { + // Continued playback. + const OEMCryptoResult result = ODK_UpdateLastPlaybackTime( + ce_->SystemTime(), &timer_limits_, &clock_values_); + if (result == ODK_TIMER_EXPIRED) return OEMCrypto_ERROR_KEY_EXPIRED; + if (usage_entry_ != nullptr) usage_entry_->set_recent_decrypt(true); } if (!ce_->config_local_display_only()) { // Only look at HDCP restrictions if the display can be non-local. @@ -939,21 +1239,22 @@ OEMCryptoResult SessionContext::CheckKeyUse(const std::string& log_string, if ((control.control_bits() & wvoec::kControlDisableAnalogOutput) && (ce_->analog_display_active() || (buffer_type == OEMCrypto_BufferType_Clear))) { - LOGE("[%s(): control bit says disable analog.", log_string.c_str()); + LOGE("[%s(): control bit says disable analog", log_string.c_str()); return OEMCrypto_ERROR_ANALOG_OUTPUT; } // Check if CGMS is required. if (control.control_bits() & wvoec::kControlCGMSMask) { // We can't control CGMS for a clear buffer. if (buffer_type == OEMCrypto_BufferType_Clear) { - LOGE("[%s(): CGMS required, but buffer is clear.", log_string.c_str()); + LOGE("[%s(): CGMS required, but buffer is clear", log_string.c_str()); return OEMCrypto_ERROR_ANALOG_OUTPUT; } if ( ce_->analog_display_active() && !ce_->cgms_a_active()) { - LOGE("[%s(): control bit says CGMS required.", log_string.c_str()); + LOGE("[%s(): control bit says CGMS required", log_string.c_str()); return OEMCrypto_ERROR_ANALOG_OUTPUT; } } + decrypt_started_ = true; // First playback for session. return OEMCrypto_SUCCESS; } @@ -963,7 +1264,7 @@ OEMCryptoResult SessionContext::Generic_Encrypt(const uint8_t* in_buffer, OEMCrypto_Algorithm algorithm, uint8_t* out_buffer) { // Check there is a content key - if (current_content_key() == NULL) { + if (current_content_key() == nullptr) { LOGE("[Generic_Encrypt(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); return OEMCrypto_ERROR_NO_CONTENT_KEY; } @@ -977,11 +1278,11 @@ OEMCryptoResult SessionContext::Generic_Encrypt(const uint8_t* in_buffer, OEMCrypto_BufferType_Clear); if (result != OEMCrypto_SUCCESS) return result; if (algorithm != OEMCrypto_AES_CBC_128_NO_PADDING) { - LOGE("[Generic_Encrypt(): algorithm bad."); + LOGE("[Generic_Encrypt(): algorithm bad"); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } if (buffer_length % AES_BLOCK_SIZE != 0) { - LOGE("[Generic_Encrypt(): buffers size bad."); + LOGE("[Generic_Encrypt(): buffers size bad"); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } const uint8_t* key_u8 = &key[0]; @@ -1003,14 +1304,14 @@ OEMCryptoResult SessionContext::Generic_Decrypt(const uint8_t* in_buffer, OEMCrypto_Algorithm algorithm, uint8_t* out_buffer) { // Check there is a content key - if (current_content_key() == NULL) { + if (current_content_key() == nullptr) { LOGE("[Generic_Decrypt(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); return OEMCrypto_ERROR_NO_CONTENT_KEY; } const std::vector& key = current_content_key()->value(); // Set the AES key. if (static_cast(key.size()) != AES_BLOCK_SIZE) { - LOGE("[Generic_Decrypt(): CONTENT_KEY has wrong size."); + LOGE("[Generic_Decrypt(): CONTENT_KEY has wrong size"); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } OEMCryptoResult result = CheckKeyUse("Generic_Decrypt", wvoec::kControlAllowDecrypt, @@ -1018,11 +1319,11 @@ OEMCryptoResult SessionContext::Generic_Decrypt(const uint8_t* in_buffer, if (result != OEMCrypto_SUCCESS) return result; if (algorithm != OEMCrypto_AES_CBC_128_NO_PADDING) { - LOGE("[Generic_Decrypt(): bad algorithm."); + LOGE("[Generic_Decrypt(): bad algorithm"); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } if (buffer_length % AES_BLOCK_SIZE != 0) { - LOGE("[Generic_Decrypt(): bad buffer size."); + LOGE("[Generic_Decrypt(): bad buffer size"); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } const uint8_t* key_u8 = &key[0]; @@ -1044,13 +1345,13 @@ OEMCryptoResult SessionContext::Generic_Sign(const uint8_t* in_buffer, uint8_t* signature, size_t* signature_length) { // Check there is a content key - if (current_content_key() == NULL) { + if (current_content_key() == nullptr) { LOGE("[Generic_Sign(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); return OEMCrypto_ERROR_NO_CONTENT_KEY; } if (*signature_length < SHA256_DIGEST_LENGTH) { *signature_length = SHA256_DIGEST_LENGTH; - LOGE("[Generic_Sign(): bad signature length."); + LOGE("[Generic_Sign(): bad signature length"); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } const std::vector& key = current_content_key()->value(); @@ -1062,7 +1363,7 @@ OEMCryptoResult SessionContext::Generic_Sign(const uint8_t* in_buffer, OEMCrypto_BufferType_Clear); if (result != OEMCrypto_SUCCESS) return result; if (algorithm != OEMCrypto_HMAC_SHA256) { - LOGE("[Generic_Sign(): bad algorithm."); + LOGE("[Generic_Sign(): bad algorithm"); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } unsigned int md_len = *signature_length; @@ -1071,7 +1372,7 @@ OEMCryptoResult SessionContext::Generic_Sign(const uint8_t* in_buffer, *signature_length = md_len; return OEMCrypto_SUCCESS; } - LOGE("[Generic_Sign(): hmac failed."); + LOGE("[Generic_Sign(): hmac failed"); dump_boringssl_error(); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } @@ -1082,7 +1383,7 @@ OEMCryptoResult SessionContext::Generic_Verify(const uint8_t* in_buffer, const uint8_t* signature, size_t signature_length) { // Check there is a content key - if (current_content_key() == NULL) { + if (current_content_key() == nullptr) { LOGE("[Decrypt_Verify(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } @@ -1098,20 +1399,21 @@ OEMCryptoResult SessionContext::Generic_Verify(const uint8_t* in_buffer, OEMCrypto_BufferType_Clear); if (result != OEMCrypto_SUCCESS) return result; if (algorithm != OEMCrypto_HMAC_SHA256) { - LOGE("[Generic_Verify(): bad algorithm."); + LOGE("[Generic_Verify(): bad algorithm"); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } unsigned int md_len = signature_length; uint8_t computed_signature[SHA256_DIGEST_LENGTH]; if (HMAC(EVP_sha256(), &key[0], key.size(), in_buffer, buffer_length, computed_signature, &md_len)) { - if (0 == memcmp(signature, computed_signature, SHA256_DIGEST_LENGTH)) { + if (0 == + CRYPTO_memcmp(signature, computed_signature, SHA256_DIGEST_LENGTH)) { return OEMCrypto_SUCCESS; } else { return OEMCrypto_ERROR_SIGNATURE_FAILURE; } } - LOGE("[Generic_Verify(): HMAC failed."); + LOGE("[Generic_Verify(): HMAC failed"); dump_boringssl_error(); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } @@ -1132,11 +1434,11 @@ bool SessionContext::UpdateMacKeys(const std::vector& enc_mac_keys, } bool SessionContext::QueryKeyControlBlock(const KeyId& key_id, uint32_t* data) { - if (!session_keys_) { + if (session_keys_ == nullptr) { return false; } const Key* content_key = session_keys_->Find(key_id); - if (NULL == content_key) { + if (content_key == nullptr) { LOGE("[QueryKeyControlBlock(): No key matches key id]"); return false; } @@ -1149,44 +1451,26 @@ bool SessionContext::QueryKeyControlBlock(const KeyId& key_id, uint32_t* data) { OEMCryptoResult SessionContext::SelectContentKey( const KeyId& key_id, OEMCryptoCipherMode cipher_mode) { - if (!session_keys_) { - LOGE("Select Key: no session keys."); + if (session_keys_ == nullptr) { + LOGE("Select Key: no session keys"); return OEMCrypto_ERROR_INVALID_CONTEXT; } Key* content_key = session_keys_->Find(key_id); - if (NULL == content_key) { + if (content_key == nullptr) { LOGE("No key matches key id"); return OEMCrypto_ERROR_NO_CONTENT_KEY; } content_key->set_ctr_mode(cipher_mode == OEMCrypto_CipherMode_CTR); current_content_key_ = content_key; - const KeyControlBlock& control = current_content_key()->control(); - - if (control.duration() > 0) { - if (control.duration() < CurrentTimer()) { - LOGE("[SelectContentKey(): KEY_EXPIRED %d versus %d]", control.duration(), - CurrentTimer()); - return OEMCrypto_ERROR_KEY_EXPIRED; - } - } return OEMCrypto_SUCCESS; } -void SessionContext::AddNonce(uint32_t nonce) { nonce_table_.AddNonce(nonce); } - -bool SessionContext::CheckNonce(uint32_t nonce) { - return nonce_table_.CheckNonce(nonce); -} - -void SessionContext::FlushNonces() { nonce_table_.Flush(); } - -bool SessionContext::CheckUsageEntry() { - if (!usage_entry_) return false; - return usage_entry_->CheckForUse(); -} - OEMCryptoResult SessionContext::CreateNewUsageEntry( uint32_t* usage_entry_number) { + if (usage_entry_) { + // Can only load one entry per session. + return OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES; + } OEMCryptoResult result = ce_->usage_table().CreateNewUsageEntry( this, &usage_entry_, usage_entry_number); if (usage_entry_) { @@ -1197,18 +1481,25 @@ OEMCryptoResult SessionContext::CreateNewUsageEntry( OEMCryptoResult SessionContext::LoadUsageEntry( uint32_t index, const std::vector& buffer) { - OEMCryptoResult result = - ce_->usage_table().LoadUsageEntry(this, &usage_entry_, index, buffer); if (usage_entry_) { - usage_entry_status_ = kUsageEntryLoaded; - // Copy the mac keys to the current session. - mac_key_server_ = std::vector( - usage_entry_->mac_key_server(), - usage_entry_->mac_key_server() + wvoec::MAC_KEY_SIZE); - mac_key_client_ = std::vector( - usage_entry_->mac_key_client(), - usage_entry_->mac_key_client() + wvoec::MAC_KEY_SIZE); + // Can only load one entry per session. + return OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES; } + const OEMCryptoResult result = ce_->usage_table().LoadUsageEntry( + this, &usage_entry_, index, buffer, &clock_values_); + if ((result != OEMCrypto_SUCCESS) && + (result != OEMCrypto_WARNING_GENERATION_SKEW)) + return result; + if (!usage_entry_) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + + usage_entry_status_ = kUsageEntryLoaded; + // Copy the mac keys to the current session. + mac_key_server_ = std::vector( + usage_entry_->mac_key_server(), + usage_entry_->mac_key_server() + wvoec::MAC_KEY_SIZE); + mac_key_client_ = std::vector( + usage_entry_->mac_key_client(), + usage_entry_->mac_key_client() + wvoec::MAC_KEY_SIZE); return result; } @@ -1217,19 +1508,19 @@ OEMCryptoResult SessionContext::UpdateUsageEntry(uint8_t* header_buffer, uint8_t* entry_buffer, size_t* entry_buffer_length) { if (!usage_entry_) { - LOGE("UpdateUsageEntry: Session has no entry."); + LOGE("UpdateUsageEntry: Session has no entry"); return OEMCrypto_ERROR_INVALID_CONTEXT; } - return ce_->usage_table().UpdateUsageEntry(this, usage_entry_, header_buffer, - header_buffer_length, entry_buffer, - entry_buffer_length); + return ce_->usage_table().UpdateUsageEntry( + this, usage_entry_, header_buffer, header_buffer_length, entry_buffer, + entry_buffer_length, &clock_values_); } OEMCryptoResult SessionContext::DeactivateUsageEntry( const std::vector& pst) { if (!usage_entry_) return OEMCrypto_ERROR_INVALID_CONTEXT; - usage_entry_->Deactivate(pst); - return OEMCrypto_SUCCESS; + usage_entry_->ForbidReport(); + return ODK_DeactivateUsageEntry(&clock_values_); } OEMCryptoResult SessionContext::ReportUsage(const std::vector& pst, @@ -1244,12 +1535,6 @@ OEMCryptoResult SessionContext::MoveEntry(uint32_t new_index) { return ce_->usage_table().MoveEntry(usage_entry_, new_index); } -OEMCryptoResult SessionContext::CopyOldUsageEntry( - const std::vector& pst) { - if (!usage_entry_) return OEMCrypto_ERROR_INVALID_CONTEXT; - return usage_entry_->CopyOldUsageEntry(pst); -} - // Internal utility function to decrypt the message bool SessionContext::DecryptMessage(const std::vector& key, const std::vector& iv, @@ -1270,16 +1555,89 @@ bool SessionContext::DecryptMessage(const std::vector& key, return true; } -OEMCryptoResult SessionContext::DecryptCENC( - const uint8_t* iv, size_t block_offset, - const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data, - size_t cipher_data_length, bool is_encrypted, uint8_t* clear_data, - OEMCryptoBufferType buffer_type, uint8_t subsample_flags) { - OEMCryptoResult result = - ChooseDecrypt(iv, block_offset, pattern, cipher_data, cipher_data_length, - is_encrypted, clear_data, buffer_type); +OEMCryptoResult SessionContext::DecryptSamples( + const OEMCrypto_SampleDescription* samples, size_t samples_length, + const OEMCrypto_CENCEncryptPatternDesc* pattern) { + // Iterate through all the samples and decrypt each one + for (size_t sample_index = 0; sample_index < samples_length; ++sample_index) { + const OEMCrypto_SampleDescription& sample = samples[sample_index]; + + // Iterate through all the subsamples and decrypt each one. A production + // implementation may be able to do something more efficient, like + // decrypting all the encrypted portions in one pass. + const uint8_t* subsample_source = sample.buffers.input_data; + OEMCrypto_DestBufferDesc subsample_dest = sample.buffers.output_descriptor; + uint8_t subsample_iv[wvoec::KEY_IV_SIZE]; + static_assert(sizeof(sample.iv) == wvoec::KEY_IV_SIZE, + "The IV in OEMCrypto_SampleDescription is the wrong length."); + // Per its type, sizeof(subsample_iv) == wvoec::KEY_IV_SIZE + memcpy(subsample_iv, sample.iv, wvoec::KEY_IV_SIZE); + for (size_t subsample_index = 0; subsample_index < sample.subsamples_length; + ++subsample_index) { + const OEMCrypto_SubSampleDescription& subsample = + sample.subsamples[subsample_index]; + const size_t subsample_length = + subsample.num_bytes_clear + subsample.num_bytes_encrypted; + + OEMCryptoResult result = ce_->SetDestination( + subsample_dest, subsample_length, subsample.subsample_flags); + if (result != OEMCrypto_SUCCESS) { + LOGE("SetDestination status: %d", result); + return result; + } + + result = DecryptSubsample(subsample, subsample_source, ce_->destination(), + subsample_dest.type, subsample_iv, pattern); + if (result != OEMCrypto_SUCCESS) { + LOGE("DecryptSubsample status: %d", result); + return result; + } + + result = ce_->PushDestination(subsample_dest, subsample.subsample_flags); + if (result != OEMCrypto_SUCCESS) { + LOGE("PushDestination status: %d", result); + return result; + } + + // Advance the source buffer, the dest buffer, and (if necessary) the IV + subsample_source += subsample_length; + advance_dest_buffer(&subsample_dest, subsample_length); + if (subsample.num_bytes_encrypted > 0 && + current_content_key()->ctr_mode()) { + advance_iv_ctr(&subsample_iv, + subsample.block_offset + subsample.num_bytes_encrypted); + } + } // Subsample loop + } // Sample loop + + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult SessionContext::DecryptSubsample( + const OEMCrypto_SubSampleDescription& subsample, const uint8_t* cipher_data, + uint8_t* clear_data, OEMCryptoBufferType buffer_type, + const uint8_t (&iv)[wvoec::KEY_IV_SIZE], + const OEMCrypto_CENCEncryptPatternDesc* pattern) { + // Handle the clear portion of the subsample. + if (subsample.num_bytes_clear > 0) { + if (buffer_type != OEMCrypto_BufferType_Direct) { + memmove(clear_data, cipher_data, subsample.num_bytes_clear); + } + // For the reference implementation, we quietly drop the clear direct video. + } + + // Handle the encrypted portion of the subsample. + OEMCryptoResult result = OEMCrypto_SUCCESS; + if (subsample.num_bytes_encrypted > 0) { + const uint8_t* source = cipher_data + subsample.num_bytes_clear; + uint8_t* dest = clear_data + subsample.num_bytes_clear; + result = ChooseDecrypt(iv, subsample.block_offset, pattern, source, + subsample.num_bytes_encrypted, dest, buffer_type); + } + + // Compute hash for FDPT. if (compute_hash_) { - if (current_content_key() == NULL || + if (current_content_key() == nullptr || (current_content_key()->control().control_bits() & wvoec::kControlAllowHashVerification) == 0) { LOGE("[DecryptCENC(): OEMCrypto_ERROR_UNKNOWN_FAILURE]"); @@ -1288,12 +1646,13 @@ OEMCryptoResult SessionContext::DecryptCENC( current_hash_ = 0; current_frame_number_ = 0; } else { - if (OEMCrypto_FirstSubsample & subsample_flags) { + if (OEMCrypto_FirstSubsample & subsample.subsample_flags) { current_hash_ = wvcrc32Init(); } - current_hash_ = - wvcrc32Cont(clear_data, cipher_data_length, current_hash_); - if (OEMCrypto_LastSubsample & subsample_flags) { + current_hash_ = wvcrc32Cont( + clear_data, subsample.num_bytes_clear + subsample.num_bytes_encrypted, + current_hash_); + if (OEMCrypto_LastSubsample & subsample.subsample_flags) { if (current_hash_ != given_hash_) { LOGE("CRC for frame %d is %08x, should be %08x\n", current_frame_number_, current_hash_, given_hash_); @@ -1307,27 +1666,19 @@ OEMCryptoResult SessionContext::DecryptCENC( } } } + + // Return the result of the previous ChooseDecrypt() call after computing the + // hash. return result; } OEMCryptoResult SessionContext::ChooseDecrypt( const uint8_t* iv, size_t block_offset, const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data, - size_t cipher_data_length, bool is_encrypted, uint8_t* clear_data, + size_t cipher_data_length, uint8_t* clear_data, OEMCryptoBufferType buffer_type) { - // If the data is clear, we do not need a current key selected. - if (!is_encrypted) { - if (buffer_type != OEMCrypto_BufferType_Direct) { - memmove(reinterpret_cast(clear_data), cipher_data, - cipher_data_length); - return OEMCrypto_SUCCESS; - } - // For reference implementation, we quietly drop the clear direct video. - return OEMCrypto_SUCCESS; - } - // Check there is a content key - if (current_content_key() == NULL) { + if (current_content_key() == nullptr) { LOGE("[DecryptCTR(): OEMCrypto_ERROR_NO_CONTENT_KEY]"); return OEMCrypto_ERROR_DECRYPT_FAILED; } @@ -1350,19 +1701,21 @@ OEMCryptoResult SessionContext::ChooseDecrypt( } if (!current_content_key()->ctr_mode()) { - if (block_offset > 0) return OEMCrypto_ERROR_INVALID_CONTEXT; - return DecryptCBC(key_u8, iv, pattern, cipher_data, cipher_data_length, + if (block_offset > 0 || pattern->encrypt == 0) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + return PatternDecryptCBC(key_u8, iv, pattern, cipher_data, + cipher_data_length, clear_data); + } else { + if (pattern->skip != 0 || pattern->encrypt != 0) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + return DecryptCTR(key_u8, iv, block_offset, cipher_data, cipher_data_length, clear_data); } - if (pattern->skip > 0) { - return PatternDecryptCTR(key_u8, iv, block_offset, pattern, cipher_data, - cipher_data_length, clear_data); - } - return DecryptCTR(key_u8, iv, block_offset, cipher_data, cipher_data_length, - clear_data); } -OEMCryptoResult SessionContext::DecryptCBC( +OEMCryptoResult SessionContext::PatternDecryptCBC( const uint8_t* key, const uint8_t* initial_iv, const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data, size_t cipher_data_length, uint8_t* clear_data) { @@ -1372,19 +1725,22 @@ OEMCryptoResult SessionContext::DecryptCBC( uint8_t next_iv[AES_BLOCK_SIZE]; memcpy(iv, &initial_iv[0], AES_BLOCK_SIZE); + const size_t pattern_length = pattern->encrypt + pattern->skip; + if (pattern_length <= 0) return OEMCrypto_ERROR_INVALID_CONTEXT; + size_t l = 0; - size_t pattern_offset = pattern->offset; + size_t pattern_offset = 0; while (l < cipher_data_length) { - size_t size = + const size_t size = std::min(cipher_data_length - l, static_cast(AES_BLOCK_SIZE)); - size_t pattern_length = pattern->encrypt + pattern->skip; - bool skip_block = - (pattern_offset >= pattern->encrypt) && (pattern_length > 0); - if (pattern_length > 0) { - pattern_offset = (pattern_offset + 1) % pattern_length; - } + const bool skip_block = (pattern_offset >= pattern->encrypt); + pattern_offset = (pattern_offset + 1) % pattern_length; if (skip_block || (size < AES_BLOCK_SIZE)) { - memmove(&clear_data[l], &cipher_data[l], size); + // If we are decrypting in-place, then this byte is already correct and + // can be skipped. + if (clear_data != cipher_data) { + memcpy(&clear_data[l], &cipher_data[l], size); + } } else { uint8_t aes_output[AES_BLOCK_SIZE]; // Save the iv for the next block, in case cipher_data is in the same @@ -1401,44 +1757,6 @@ OEMCryptoResult SessionContext::DecryptCBC( return OEMCrypto_SUCCESS; } -OEMCryptoResult SessionContext::PatternDecryptCTR( - const uint8_t* key, const uint8_t* initial_iv, size_t block_offset, - const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data, - size_t cipher_data_length, uint8_t* clear_data) { - AES_KEY aes_key; - AES_set_encrypt_key(&key[0], AES_BLOCK_SIZE * 8, &aes_key); - uint8_t iv[AES_BLOCK_SIZE]; - memcpy(iv, &initial_iv[0], AES_BLOCK_SIZE); - - size_t l = 0; - size_t pattern_offset = pattern->offset; - while (l < cipher_data_length) { - size_t size = - std::min(cipher_data_length - l, AES_BLOCK_SIZE - block_offset); - size_t pattern_length = pattern->encrypt + pattern->skip; - bool skip_block = - (pattern_offset >= pattern->encrypt) && (pattern_length > 0); - if (pattern_length > 0) { - pattern_offset = (pattern_offset + 1) % pattern_length; - } - if (skip_block) { - memmove(&clear_data[l], &cipher_data[l], size); - } else { - uint8_t aes_output[AES_BLOCK_SIZE]; - AES_encrypt(iv, aes_output, &aes_key); - for (size_t n = 0; n < size; n++) { - clear_data[l + n] = aes_output[n + block_offset] ^ cipher_data[l + n]; - } - ctr128_inc64(iv); - } - l += size; - block_offset = 0; - } - return OEMCrypto_SUCCESS; -} - -// This is a special case of PatternDecryptCTR with no skip pattern. It uses -// more optimized versions of openssl's implementation of AES CTR mode. OEMCryptoResult SessionContext::DecryptCTR(const uint8_t* key_u8, const uint8_t* iv, size_t block_offset, @@ -1484,7 +1802,7 @@ OEMCryptoResult SessionContext::DecryptCTR(const uint8_t* key_u8, while (remaining) { EVP_CIPHER_CTX* evp_cipher_ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX_set_padding(evp_cipher_ctx, 0); - if (!EVP_DecryptInit_ex(evp_cipher_ctx, EVP_aes_128_ctr(), NULL, key_u8, + if (!EVP_DecryptInit_ex(evp_cipher_ctx, EVP_aes_128_ctr(), nullptr, key_u8, aes_iv_u8)) { LOGE("[DecryptCTR(): EVP_INIT ERROR]"); EVP_CIPHER_CTX_free(evp_cipher_ctx); @@ -1551,10 +1869,18 @@ OEMCryptoResult SessionContext::SetDecryptHash(uint32_t frame_number, OEMCryptoResult SessionContext::GetHashErrorCode( uint32_t* failed_frame_number) { - if (failed_frame_number == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + if (failed_frame_number == nullptr) return OEMCrypto_ERROR_UNKNOWN_FAILURE; if (hash_error_ != OEMCrypto_SUCCESS) *failed_frame_number = bad_frame_number_; return hash_error_; } +bool SessionContext::set_nonce(uint32_t nonce) { + if (state_nonce_created_) return false; + if (nonce == 0) return false; + state_nonce_created_ = true; + ODK_SetNonceValues(&nonce_values_, nonce); + return true; +} + } // namespace wvoec_ref diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session.h b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session.h index 8687da1f..f092d639 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session.h +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session.h @@ -15,9 +15,9 @@ #include #include "OEMCryptoCENC.h" +#include "odk_structs.h" #include "oemcrypto_auth_ref.h" #include "oemcrypto_key_ref.h" -#include "oemcrypto_nonce_table.h" #include "oemcrypto_rsa_key_shared.h" #include "oemcrypto_session_key_table.h" #include "oemcrypto_types.h" @@ -37,6 +37,7 @@ class SessionContextKeys { virtual size_t size() = 0; virtual bool Insert(const KeyId& key_id, const Key& key_data) = 0; virtual Key* Find(const KeyId& key_id) = 0; + virtual Key* FirstKey() = 0; virtual void Remove(const KeyId& key_id) = 0; virtual void UpdateDuration(const KeyControlBlock& control) = 0; @@ -58,25 +59,11 @@ class SessionContextKeys { }; class SessionContext { - private: - SessionContext() {} public: - SessionContext(CryptoEngine* ce, SessionId sid, const RSA_shared_ptr& rsa_key) - : valid_(true), - ce_(ce), - id_(sid), - current_content_key_(NULL), - session_keys_(NULL), - rsa_key_(rsa_key), - allowed_schemes_(kSign_RSASSA_PSS), - usage_entry_(NULL), - srm_requirements_status_(NoSRMVersion), - usage_entry_status_(kNoUsageEntry), - compute_hash_(false), - current_hash_(0), - bad_frame_number_(0), - hash_error_(OEMCrypto_SUCCESS) {} + SessionContext(CryptoEngine* ce, SessionId sid, + const RSA_shared_ptr& rsa_key); + SessionContext() = delete; virtual ~SessionContext(); bool isValid() { return valid_; } @@ -87,8 +74,21 @@ class SessionContext { virtual bool RSADeriveKeys(const std::vector& enc_session_key, const std::vector& mac_context, const std::vector& enc_context); - virtual bool GenerateSignature(const uint8_t* message, size_t message_length, - uint8_t* signature, size_t* signature_length); + virtual OEMCryptoResult PrepAndSignLicenseRequest(uint8_t* message, + size_t message_length, + size_t* core_message_length, + uint8_t* signature, + size_t* signature_length); + virtual OEMCryptoResult PrepAndSignRenewalRequest(uint8_t* message, + size_t message_length, + size_t* core_message_length, + uint8_t* signature, + size_t* signature_length); + virtual OEMCryptoResult PrepAndSignProvisioningRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + uint8_t* signature, size_t* signature_length); + // The size of an RSA signature. This is used when signing as a CAST + // receiver. size_t RSASignatureSize(); virtual OEMCryptoResult GenerateRSASignature( const uint8_t* message, size_t message_length, uint8_t* signature, @@ -96,13 +96,9 @@ class SessionContext { virtual bool ValidateMessage(const uint8_t* message, size_t message_length, const uint8_t* signature, size_t signature_length); - OEMCryptoResult DecryptCENC(const uint8_t* iv, size_t block_offset, - const OEMCrypto_CENCEncryptPatternDesc* pattern, - const uint8_t* cipher_data, - size_t cipher_data_length, bool is_encrypted, - uint8_t* clear_data, - OEMCryptoBufferType buffer_type, - uint8_t subsample_flags); + OEMCryptoResult DecryptSamples( + const OEMCrypto_SampleDescription* samples, size_t samples_length, + const OEMCrypto_CENCEncryptPatternDesc* pattern); OEMCryptoResult Generic_Encrypt(const uint8_t* in_buffer, size_t buffer_length, const uint8_t* iv, @@ -119,8 +115,11 @@ class SessionContext { OEMCrypto_Algorithm algorithm, const uint8_t* signature, size_t signature_length); - void StartTimer(); - uint32_t CurrentTimer(); // (seconds). + virtual OEMCryptoResult LoadLicense(const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); virtual OEMCryptoResult LoadKeys( const uint8_t* message, size_t message_length, const uint8_t* signature, size_t signature_length, OEMCrypto_Substring enc_mac_keys_iv, @@ -128,8 +127,14 @@ class SessionContext { const OEMCrypto_KeyObject* key_array, OEMCrypto_Substring pst, OEMCrypto_Substring srm_restriction_data, OEMCrypto_LicenseType license_type); + virtual OEMCryptoResult LoadKeysNoSignature( + const uint8_t* message, size_t message_length, + OEMCrypto_Substring enc_mac_keys_iv, OEMCrypto_Substring enc_mac_keys, + size_t num_keys, const OEMCrypto_KeyObject* key_array, + OEMCrypto_Substring pst, OEMCrypto_Substring srm_restriction_data, + OEMCrypto_LicenseType license_type); virtual OEMCryptoResult LoadEntitledContentKeys( - const uint8_t* message, size_t message_length, size_t num_keys, + const uint8_t* message, size_t message_length, size_t key_array_length, const OEMCrypto_EntitledContentKeyObject* key_array); virtual OEMCryptoResult InstallKey( const KeyId& key_id, const std::vector& key_data, @@ -143,6 +148,11 @@ class SessionContext { bool EncryptRSAKey(const uint8_t* pkcs8_rsa_key, size_t enc_rsa_key_length, const uint8_t* enc_rsa_key_iv, uint8_t* enc_rsa_key); bool LoadRSAKey(const uint8_t* pkcs8_rsa_key, size_t rsa_key_length); + virtual OEMCryptoResult LoadRenewal(const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); virtual OEMCryptoResult RefreshKey( const KeyId& key_id, const std::vector& key_control, const std::vector& key_control_iv); @@ -171,13 +181,14 @@ class SessionContext { const std::vector& encryption_key() { return encryption_key_; } uint32_t allowed_schemes() const { return allowed_schemes_; } - void AddNonce(uint32_t nonce); - bool CheckNonce(uint32_t nonce); - // Verify that the nonce does not match any in this session's nonce table. - bool NonceCollision(uint32_t nonce) const { - return nonce_table_.NonceCollision(nonce); - } - void FlushNonces(); + // Return true if nonce was set. + bool set_nonce(uint32_t nonce); + uint32_t nonce() const { return nonce_values_.nonce; } + ODK_NonceValues& nonce_values() { return nonce_values_; } + + bool CheckNonce(uint32_t nonce) const { + return nonce != 0 && nonce == nonce_values_.nonce; + }; virtual OEMCryptoResult CreateNewUsageEntry(uint32_t* usage_entry_number); virtual OEMCryptoResult LoadUsageEntry(uint32_t index, @@ -190,9 +201,21 @@ class SessionContext { virtual OEMCryptoResult ReportUsage(const std::vector& pst, uint8_t* buffer, size_t* buffer_length); OEMCryptoResult MoveEntry(uint32_t new_index); - OEMCryptoResult CopyOldUsageEntry(const std::vector& pst); + bool usage_entry_present() const { return usage_entry_ != nullptr; } protected: + // Signature size of the currently loaded private key. + size_t CertSignatureSize(); + // Signature size when using a keybox or OEM Cert's private key. + size_t ROTSignatureSize(); + virtual OEMCryptoResult GenerateCertSignature(const uint8_t* message, + size_t message_length, + uint8_t* signature, + size_t* signature_length); + virtual OEMCryptoResult GenerateSignature(const uint8_t* message, + size_t message_length, + uint8_t* signature, + size_t* signature_length); bool DeriveKey(const std::vector& key, const std::vector& context, int counter, std::vector* out); @@ -211,18 +234,19 @@ class SessionContext { OEMCryptoResult CheckStatusOnline(uint32_t nonce, uint32_t control); // Check that the usage entry status is valid for offline use. OEMCryptoResult CheckStatusOffline(uint32_t nonce, uint32_t control); + + OEMCryptoResult DecryptSubsample( + const OEMCrypto_SubSampleDescription& subsample, + const uint8_t* cipher_data, uint8_t* clear_data, + OEMCryptoBufferType buffer_type, const uint8_t (&iv)[wvoec::KEY_IV_SIZE], + const OEMCrypto_CENCEncryptPatternDesc* pattern); OEMCryptoResult ChooseDecrypt(const uint8_t* iv, size_t block_offset, const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data, - size_t cipher_data_length, bool is_encrypted, - uint8_t* clear_data, + size_t cipher_data_length, uint8_t* clear_data, OEMCryptoBufferType buffer_type); - OEMCryptoResult DecryptCBC(const uint8_t* key, const uint8_t* iv, - const OEMCrypto_CENCEncryptPatternDesc* pattern, - const uint8_t* cipher_data, - size_t cipher_data_length, uint8_t* clear_data); - OEMCryptoResult PatternDecryptCTR( - const uint8_t* key, const uint8_t* iv, size_t block_offset, + OEMCryptoResult PatternDecryptCBC( + const uint8_t* key, const uint8_t* iv, const OEMCrypto_CENCEncryptPatternDesc* pattern, const uint8_t* cipher_data, size_t cipher_data_length, uint8_t* clear_data); @@ -244,10 +268,13 @@ class SessionContext { std::vector session_key_; const Key* current_content_key_; SessionContextKeys* session_keys_; - NonceTable nonce_table_; + ODK_NonceValues nonce_values_; + uint8_t license_request_hash_[ODK_SHA256_HASH_SIZE]; RSA_shared_ptr rsa_key_; uint32_t allowed_schemes_; // for RSA signatures. - time_t timer_start_; + bool decrypt_started_; // If the license has been used in this session. + ODK_TimerLimits timer_limits_; + ODK_ClockValues clock_values_; UsageTableEntry* usage_entry_; SRMVersionStatus srm_requirements_status_; enum UsageEntryStatus { @@ -265,6 +292,12 @@ class SessionContext { uint32_t bad_frame_number_; // Frame number with bad hash. OEMCryptoResult hash_error_; // Error code for first bad frame. + // The bare minimum state machine is to only call each of these function + // categories at most once. + bool state_nonce_created_; + bool state_request_signed_; + bool state_response_loaded_; + CORE_DISALLOW_COPY_AND_ASSIGN(SessionContext); }; diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session_key_table.cpp b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session_key_table.cpp index 42b8a586..e7131975 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session_key_table.cpp +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session_key_table.cpp @@ -13,7 +13,7 @@ namespace wvoec_ref { SessionKeyTable::~SessionKeyTable() { for (KeyMap::iterator i = keys_.begin(); i != keys_.end(); ++i) { - if (NULL != i->second) { + if (nullptr != i->second) { delete i->second; } } @@ -27,7 +27,7 @@ bool SessionKeyTable::Insert(const KeyId key_id, const Key& key_data) { Key* SessionKeyTable::Find(const KeyId key_id) { if (keys_.find(key_id) == keys_.end()) { - return NULL; + return nullptr; } return keys_[key_id]; } @@ -59,11 +59,11 @@ Key* EntitlementKeyTable::Find(const KeyId key_id) { ContentIdToEntitlementIdMap::iterator it = contentid_to_entitlementid_.find(key_id); if (it == contentid_to_entitlementid_.end()) { - return NULL; + return nullptr; } if (keys_.find(it->second) == keys_.end()) { - return NULL; + return nullptr; } return keys_[it->second]; } diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session_key_table.h b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session_key_table.h index cac2e8eb..c84096be 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session_key_table.h +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_session_key_table.h @@ -34,6 +34,7 @@ class SessionKeyTable { bool Insert(const KeyId key_id, const Key& key_data); Key* Find(const KeyId key_id); + Key* FirstKey() { return keys_.begin()->second; } void Remove(const KeyId key_id); void UpdateDuration(const KeyControlBlock& control); size_t size() const { return keys_.size(); } @@ -52,6 +53,7 @@ class EntitlementKeyTable { ~EntitlementKeyTable() {} bool Insert(const KeyId key_id, const Key& key_data); Key* Find(const KeyId key_id); + Key* FirstKey() { return keys_.begin()->second; } void Remove(const KeyId key_id); void UpdateDuration(const KeyControlBlock& control); size_t size() const { return contentid_to_entitlementid_.size(); } diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_usage_table_ref.cpp b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_usage_table_ref.cpp index c24275dd..abe6167a 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_usage_table_ref.cpp +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_usage_table_ref.cpp @@ -14,13 +14,14 @@ #include #include +#include #include #include #include "file_store.h" #include "log.h" +#include "odk.h" #include "oemcrypto_engine_ref.h" -#include "oemcrypto_old_usage_table_ref.h" // TODO(fredgc): Setting the device files base bath is currently broken as // wvcdm::Properties is no longer used by the reference code. //#include "properties.h" @@ -71,8 +72,7 @@ OEMCryptoResult UsageTableEntry::SetPST(const uint8_t* pst, size_t pst_length) { data_.pst_length = pst_length; if (!pst || !pst_length) return OEMCrypto_ERROR_INVALID_CONTEXT; memcpy(data_.pst, pst, pst_length); - data_.time_of_license_received = - usage_table_->ce_->RollbackCorrectedOfflineTime(); + data_.time_of_license_received = usage_table_->ce_->SystemTime(); return OEMCrypto_SUCCESS; } @@ -80,15 +80,17 @@ bool UsageTableEntry::VerifyPST(const uint8_t* pst, size_t pst_length) { if (pst_length > kMaxPSTLength) return false; if (data_.pst_length != pst_length) return false; if (!pst || !pst_length) return false; - return 0 == memcmp(pst, data_.pst, pst_length); + return 0 == CRYPTO_memcmp(pst, data_.pst, pst_length); } bool UsageTableEntry::VerifyMacKeys(const std::vector& server, const std::vector& client) { return (server.size() == wvoec::MAC_KEY_SIZE) && (client.size() == wvoec::MAC_KEY_SIZE) && - (0 == memcmp(&server[0], data_.mac_key_server, wvoec::MAC_KEY_SIZE)) && - (0 == memcmp(&client[0], data_.mac_key_client, wvoec::MAC_KEY_SIZE)); + (0 == CRYPTO_memcmp(&server[0], data_.mac_key_server, + wvoec::MAC_KEY_SIZE)) && + (0 == + CRYPTO_memcmp(&client[0], data_.mac_key_client, wvoec::MAC_KEY_SIZE)); } bool UsageTableEntry::SetMacKeys(const std::vector& server, @@ -101,25 +103,7 @@ bool UsageTableEntry::SetMacKeys(const std::vector& server, return true; } -bool UsageTableEntry::CheckForUse() { - if (Inactive()) return false; - recent_decrypt_ = true; - if (data_.status == kUnused) { - data_.status = kActive; - data_.time_of_first_decrypt = - usage_table_->ce_->RollbackCorrectedOfflineTime(); - data_.generation_number++; - usage_table_->IncrementGeneration(); - } - return true; -} - -void UsageTableEntry::Deactivate(const std::vector& pst) { - if (data_.status == kUnused) { - data_.status = kInactiveUnused; - } else if (data_.status == kActive) { - data_.status = kInactiveUsed; - } +void UsageTableEntry::ForbidReport() { forbid_report_ = true; data_.generation_number++; usage_table_->IncrementGeneration(); @@ -136,7 +120,7 @@ OEMCryptoResult UsageTableEntry::ReportUsage(const std::vector& pst, data_.pst_length); return OEMCrypto_ERROR_WRONG_PST; } - if (memcmp(&pst[0], data_.pst, data_.pst_length)) { + if (CRYPTO_memcmp(&pst[0], data_.pst, data_.pst_length)) { LOGE("ReportUsage: wrong pst %s, should be %s.", wvcdm::b2a_hex(pst).c_str(), wvcdm::HexEncode(data_.pst, data_.pst_length).c_str()); return OEMCrypto_ERROR_WRONG_PST; @@ -151,7 +135,7 @@ OEMCryptoResult UsageTableEntry::ReportUsage(const std::vector& pst, return OEMCrypto_ERROR_INVALID_CONTEXT; } wvcdm::Unpacked_PST_Report pst_report(buffer); - int64_t now = usage_table_->ce_->RollbackCorrectedOfflineTime(); + int64_t now = usage_table_->ce_->SystemTime(); pst_report.set_seconds_since_license_received(now - data_.time_of_license_received); pst_report.set_seconds_since_first_decrypt(now - data_.time_of_first_decrypt); @@ -170,12 +154,29 @@ OEMCryptoResult UsageTableEntry::ReportUsage(const std::vector& pst, return OEMCrypto_SUCCESS; } -void UsageTableEntry::UpdateAndIncrement() { +void UsageTableEntry::UpdateAndIncrement(ODK_ClockValues* clock_values) { if (recent_decrypt_) { - data_.time_of_last_decrypt = - usage_table_->ce_->RollbackCorrectedOfflineTime(); + data_.time_of_last_decrypt = usage_table_->ce_->SystemTime(); recent_decrypt_ = false; } + data_.time_of_license_received = clock_values->time_of_license_signed; + data_.time_of_first_decrypt = clock_values->time_of_first_decrypt; + // Use the most recent time_of_last_decrypt. + if (static_cast(data_.time_of_last_decrypt) < + clock_values->time_of_last_decrypt) { + // For the reference implementation, we update the clock_values on every + // decrypt. + data_.time_of_last_decrypt = clock_values->time_of_last_decrypt; + } else { + // For this reference implementation of OEMCrypto, we regularly update + // clock_values->time_of_last_decrypt and we could just update + // data_.time_of_last_decrypt here. However, I'm including the line below to + // make it clear that you could do it the other way around. When this + // function is called, the two values should be synced so that the usage + // entry can be saved with the correct value. + clock_values->time_of_last_decrypt = data_.time_of_last_decrypt; + } + data_.status = clock_values->status; data_.generation_number++; usage_table_->IncrementGeneration(); forbid_report_ = false; @@ -194,7 +195,7 @@ OEMCryptoResult UsageTableEntry::SaveData(CryptoEngine* ce, reinterpret_cast(&clear_buffer[0]); SignedEntryBlock* encrypted = reinterpret_cast(signed_buffer); - clear->data = this->data_; // Copy the current data. + clear->data = data_; // Copy the current data. memcpy(clear->verification, kEntryVerification, kMagicLength); // This should be encrypted and signed with a device specific key. @@ -228,7 +229,8 @@ OEMCryptoResult UsageTableEntry::SaveData(CryptoEngine* ce, } OEMCryptoResult UsageTableEntry::LoadData(CryptoEngine* ce, uint32_t index, - const std::vector& buffer) { + const std::vector& buffer, + ODK_ClockValues* clock_values) { if (buffer.size() < SignedEntrySize()) return OEMCrypto_ERROR_SHORT_BUFFER; if (buffer.size() > SignedEntrySize()) LOGW("LoadUsageTableEntry: buffer is large. %d > %d", buffer.size(), @@ -255,7 +257,8 @@ OEMCryptoResult UsageTableEntry::LoadData(CryptoEngine* ce, uint32_t index, LOGE("LoadUsageEntry: Could not sign entry."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - if (memcmp(clear->signature, encrypted->signature, SHA256_DIGEST_LENGTH)) { + if (CRYPTO_memcmp(clear->signature, encrypted->signature, + SHA256_DIGEST_LENGTH)) { LOGE("LoadUsageEntry: Signature did not match."); LOGE("LoadUsageEntry: Invalid signature given: %s", wvcdm::HexEncode(encrypted->signature, sig_length).c_str()); @@ -295,39 +298,10 @@ OEMCryptoResult UsageTableEntry::LoadData(CryptoEngine* ce, uint32_t index, LOGE("LoadUsageEntry: entry has bad status %d", clear->data.status); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - this->data_ = clear->data; - return OEMCrypto_SUCCESS; -} - -OEMCryptoResult UsageTableEntry::CopyOldUsageEntry( - const std::vector& pst) { - OldUsageTableEntry* old_entry = usage_table_->FindOldUsageEntry(pst); - if (!old_entry) return OEMCrypto_ERROR_WRONG_PST; - data_.time_of_license_received = old_entry->time_of_license_received_; - data_.time_of_first_decrypt = old_entry->time_of_first_decrypt_; - data_.time_of_last_decrypt = old_entry->time_of_last_decrypt_; - data_.status = old_entry->status_; - if (old_entry->mac_key_server_.size() != wvoec::MAC_KEY_SIZE) { - LOGE("CopyOldEntry: Old entry has bad server mac key."); - } else { - memcpy(data_.mac_key_server, &(old_entry->mac_key_server_[0]), - wvoec::MAC_KEY_SIZE); - } - if (old_entry->mac_key_client_.size() != wvoec::MAC_KEY_SIZE) { - LOGE("CopyOldEntry: Old entry has bad client mac key."); - } else { - memcpy(data_.mac_key_client, &(old_entry->mac_key_client_[0]), - wvoec::MAC_KEY_SIZE); - } - if (pst.size() > kMaxPSTLength) { - LOGE("CopyOldEntry: PST Length was too large. Truncating."); - data_.pst_length = kMaxPSTLength; - } else { - data_.pst_length = pst.size(); - } - memcpy(data_.pst, pst.data(), data_.pst_length); - data_.pst[data_.pst_length] = '\0'; - return OEMCrypto_SUCCESS; + data_ = clear->data; + return ODK_ReloadClockValues( + clock_values, data_.time_of_license_received, data_.time_of_first_decrypt, + data_.time_of_last_decrypt, data_.status, ce->SystemTime()); } size_t UsageTableEntry::SignedEntrySize() { @@ -337,12 +311,7 @@ size_t UsageTableEntry::SignedEntrySize() { return blocks * wvoec::KEY_IV_SIZE; } -UsageTable::~UsageTable() { - if (old_table_) { - delete old_table_; - old_table_ = NULL; - } -} +UsageTable::~UsageTable() {} size_t UsageTable::SignedHeaderSize(size_t count) { size_t base = sizeof(SignedHeaderBlock) + count * sizeof(int64_t); @@ -351,12 +320,10 @@ size_t UsageTable::SignedHeaderSize(size_t count) { return blocks * wvoec::KEY_IV_SIZE; } -OEMCryptoResult UsageTable::UpdateUsageEntry(SessionContext* session, - UsageTableEntry* entry, - uint8_t* header_buffer, - size_t* header_buffer_length, - uint8_t* entry_buffer, - size_t* entry_buffer_length) { +OEMCryptoResult UsageTable::UpdateUsageEntry( + SessionContext* session, UsageTableEntry* entry, uint8_t* header_buffer, + size_t* header_buffer_length, uint8_t* entry_buffer, + size_t* entry_buffer_length, ODK_ClockValues* clock_values) { size_t signed_header_size = SignedHeaderSize(generation_numbers_.size()); if (*entry_buffer_length < UsageTableEntry::SignedEntrySize() || *header_buffer_length < signed_header_size) { @@ -368,7 +335,7 @@ OEMCryptoResult UsageTable::UpdateUsageEntry(SessionContext* session, *header_buffer_length = signed_header_size; if ((!header_buffer) || (!entry_buffer)) return OEMCrypto_ERROR_UNKNOWN_FAILURE; - entry->UpdateAndIncrement(); + entry->UpdateAndIncrement(clock_values); generation_numbers_[entry->index()] = entry->generation_number(); OEMCryptoResult result = entry->SaveData(ce_, session, entry_buffer, *entry_buffer_length); @@ -408,7 +375,8 @@ OEMCryptoResult UsageTable::CreateNewUsageEntry(SessionContext* session, OEMCryptoResult UsageTable::LoadUsageEntry(SessionContext* session, UsageTableEntry** entry, uint32_t index, - const std::vector& buffer) { + const std::vector& buffer, + ODK_ClockValues* clock_values) { if (!header_loaded_) { LOGE("CreateNewUsageEntry: Header not loaded."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; @@ -427,7 +395,8 @@ OEMCryptoResult UsageTable::LoadUsageEntry(SessionContext* session, } UsageTableEntry* new_entry = MakeEntry(index); - OEMCryptoResult status = new_entry->LoadData(ce_, index, buffer); + OEMCryptoResult status = + new_entry->LoadData(ce_, index, buffer, clock_values); if (status != OEMCrypto_SUCCESS) { delete new_entry; return status; @@ -565,7 +534,8 @@ OEMCryptoResult UsageTable::LoadUsageTableHeader( LOGE("LoadUsageTableHeader: Could not sign entry."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - if (memcmp(clear->signature, encrypted->signature, SHA256_DIGEST_LENGTH)) { + if (CRYPTO_memcmp(clear->signature, encrypted->signature, + SHA256_DIGEST_LENGTH)) { LOGE("LoadUsageTableHeader: Signature did not match."); LOGE("LoadUsageTableHeader: Invalid signature given: %s", wvcdm::HexEncode(encrypted->signature, sig_length).c_str()); @@ -722,7 +692,7 @@ OEMCryptoResult UsageTable::CreateUsageTableHeader( if (!LoadGenerationNumber(true)) return OEMCrypto_ERROR_UNKNOWN_FAILURE; // Make sure there are no entries that are currently tied to an open session. for (size_t i = 0; i < sessions_.size(); ++i) { - if (sessions_[i] != NULL) { + if (sessions_[i] != nullptr) { LOGE("CreateUsageTableHeader: index %d used by session.", i); return OEMCrypto_ERROR_INVALID_SESSION; } @@ -733,41 +703,4 @@ OEMCryptoResult UsageTable::CreateUsageTableHeader( return SaveUsageTableHeader(header_buffer, *header_buffer_length); } -OldUsageTableEntry* UsageTable::FindOldUsageEntry( - const std::vector& pst) { - if (!old_table_) old_table_ = new OldUsageTable(ce_); - return old_table_->FindEntry(pst); -} - -OEMCryptoResult UsageTable::DeleteOldUsageTable() { - if (old_table_) { - old_table_->Clear(); - delete old_table_; - old_table_ = NULL; - } - OldUsageTable::DeleteFile(ce_); - return OEMCrypto_SUCCESS; -} - -OEMCryptoResult UsageTable::CreateOldUsageEntry( - uint64_t time_since_license_received, uint64_t time_since_first_decrypt, - uint64_t time_since_last_decrypt, OEMCrypto_Usage_Entry_Status status, - uint8_t* server_mac_key, uint8_t* client_mac_key, const uint8_t* pst, - size_t pst_length) { - if (!old_table_) old_table_ = new OldUsageTable(ce_); - std::vector pstv(pst, pst + pst_length); - OldUsageTableEntry* old_entry = old_table_->CreateEntry(pstv); - - int64_t now = ce_->RollbackCorrectedOfflineTime(); - old_entry->time_of_license_received_ = now - time_since_license_received; - old_entry->time_of_first_decrypt_ = now - time_since_first_decrypt; - old_entry->time_of_last_decrypt_ = now - time_since_last_decrypt; - old_entry->status_ = status; - old_entry->mac_key_server_.assign(server_mac_key, - server_mac_key + wvoec::MAC_KEY_SIZE); - old_entry->mac_key_client_.assign(client_mac_key, - client_mac_key + wvoec::MAC_KEY_SIZE); - return OEMCrypto_SUCCESS; -} - } // namespace wvoec_ref diff --git a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_usage_table_ref.h b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_usage_table_ref.h index 3278852a..661ba3ab 100644 --- a/libwvdrmengine/oemcrypto/ref/src/oemcrypto_usage_table_ref.h +++ b/libwvdrmengine/oemcrypto/ref/src/oemcrypto_usage_table_ref.h @@ -13,16 +13,15 @@ #include #include "OEMCryptoCENC.h" -#include "openssl/sha.h" +#include "odk_structs.h" #include "oemcrypto_types.h" +#include "openssl/sha.h" namespace wvoec_ref { class SessionContext; class CryptoEngine; class UsageTable; -class OldUsageTable; -class OldUsageTableEntry; const size_t kMaxPSTLength = 255; // This is the data we store offline. @@ -44,29 +43,34 @@ class UsageTableEntry { UsageTableEntry(UsageTable* table, uint32_t index, int64_t generation); virtual ~UsageTableEntry(); // Free memory, remove reference in header. bool Inactive() { return data_.status >= kInactive; } + // Mark this entry as modified and forbid a usage report until the data has + // been saved. This is done on important events like first decrypt and + // deactivation. + void ForbidReport(); OEMCryptoResult SetPST(const uint8_t* pst, size_t pst_length); bool VerifyPST(const uint8_t* pst, size_t pst_length); bool VerifyMacKeys(const std::vector& server, const std::vector& client); bool SetMacKeys(const std::vector& server, const std::vector& client); - // Returns false if the entry is inactive. Otherwise, returns true. - // If the status was unused, it is updated, and decrypt times are flaged - // for update. - bool CheckForUse(); - void Deactivate(const std::vector& pst); virtual OEMCryptoResult ReportUsage(const std::vector& pst, uint8_t* buffer, size_t* buffer_length); - virtual void UpdateAndIncrement(); + virtual void UpdateAndIncrement(ODK_ClockValues* clock_values); + // Save all data to the give buffer. This should be called after updating the + // data. OEMCryptoResult SaveData(CryptoEngine* ce, SessionContext* session, uint8_t* signed_buffer, size_t buffer_size); + // Load all data from the buffer, and then update clock_values. OEMCryptoResult LoadData(CryptoEngine* ce, uint32_t index, - const std::vector& buffer); - virtual OEMCryptoResult CopyOldUsageEntry(const std::vector& pst); + const std::vector& buffer, + ODK_ClockValues* clock_values); int64_t generation_number() { return data_.generation_number; } void set_generation_number(int64_t value) { data_.generation_number = value; } void set_index(int32_t index) { data_.index = index; } uint32_t index() { return data_.index; } + void set_recent_decrypt(bool recent_decrypt) { + recent_decrypt_ = recent_decrypt; + } static size_t SignedEntrySize(); const uint8_t* mac_key_server() { return data_.mac_key_server; } const uint8_t* mac_key_client() { return data_.mac_key_client; } @@ -80,8 +84,7 @@ class UsageTableEntry { class UsageTable { public: - explicit UsageTable(CryptoEngine* ce) - : ce_(ce), header_loaded_(false), old_table_(NULL) {}; + explicit UsageTable(CryptoEngine* ce) : ce_(ce), header_loaded_(false){}; virtual ~UsageTable(); OEMCryptoResult CreateNewUsageEntry(SessionContext* session, @@ -89,13 +92,12 @@ class UsageTable { uint32_t* usage_entry_number); OEMCryptoResult LoadUsageEntry(SessionContext* session, UsageTableEntry** entry, uint32_t index, - const std::vector& buffer); - OEMCryptoResult UpdateUsageEntry(SessionContext* session, - UsageTableEntry* entry, - uint8_t* header_buffer, - size_t* header_buffer_length, - uint8_t* entry_buffer, - size_t* entry_buffer_length); + const std::vector& buffer, + ODK_ClockValues* clock_values); + OEMCryptoResult UpdateUsageEntry( + SessionContext* session, UsageTableEntry* entry, uint8_t* header_buffer, + size_t* header_buffer_length, uint8_t* entry_buffer, + size_t* entry_buffer_length, ODK_ClockValues* clock_values); OEMCryptoResult MoveEntry(UsageTableEntry* entry, uint32_t new_index); OEMCryptoResult CreateUsageTableHeader(uint8_t* header_buffer, size_t* header_buffer_length); @@ -106,15 +108,6 @@ class UsageTable { void ReleaseEntry(uint32_t index) { sessions_[index] = 0; } void IncrementGeneration(); static size_t SignedHeaderSize(size_t count); - OldUsageTableEntry* FindOldUsageEntry(const std::vector& pst); - OEMCryptoResult DeleteOldUsageTable(); - OEMCryptoResult CreateOldUsageEntry(uint64_t time_since_license_received, - uint64_t time_since_first_decrypt, - uint64_t time_since_last_decrypt, - OEMCrypto_Usage_Entry_Status status, - uint8_t* server_mac_key, - uint8_t* client_mac_key, - const uint8_t* pst, size_t pst_length); protected: virtual UsageTableEntry* MakeEntry(uint32_t index); @@ -128,7 +121,6 @@ class UsageTable { int64_t master_generation_number_; std::vector generation_numbers_; std::vector sessions_; - OldUsageTable* old_table_; friend class UsageTableEntry; }; diff --git a/libwvdrmengine/oemcrypto/renewal/Makefile b/libwvdrmengine/oemcrypto/renewal/Makefile new file mode 100644 index 00000000..f2bdd380 --- /dev/null +++ b/libwvdrmengine/oemcrypto/renewal/Makefile @@ -0,0 +1,3 @@ +derive_key: derive_key.cpp + g++ -g -o derive_key derive_key.cpp -lssl -lcrypto + diff --git a/libwvdrmengine/oemcrypto/renewal/derive_key.cpp b/libwvdrmengine/oemcrypto/renewal/derive_key.cpp new file mode 100644 index 00000000..8db2afaa --- /dev/null +++ b/libwvdrmengine/oemcrypto/renewal/derive_key.cpp @@ -0,0 +1,176 @@ +// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. +// +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// clang-format off + +// This is a test keybox. It will not be accepted by production systems. +static std::vector test_keybox = { + // sample keybox used for test vectors + // deviceID = WidevineTestOnlyKeybox000 + 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, + 0x54, 0x65, 0x73, 0x74, 0x4f, 0x6e, 0x6c, 0x79, + 0x4b, 0x65, 0x79, 0x62, 0x6f, 0x78, 0x30, 0x30, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + // key + 0xe4, 0xff, 0x57, 0x4c, 0x32, 0x2e, 0xf5, 0x34, + 0x26, 0x21, 0x2c, 0xb3, 0xed, 0x37, 0xf3, 0x5e, + + // data (system ID 7912 = 1EE8). + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x1e, 0xe8, + 0xca, 0x1e, 0x71, 0x7c, 0xfb, 0xe8, 0xa3, 0x94, + 0x52, 0x0a, 0x6b, 0x71, 0x37, 0xd2, 0x69, 0xfa, + 0x5a, 0xc6, 0xb5, 0x4c, 0x6b, 0x46, 0x63, 0x9b, + 0xbe, 0x80, 0x3d, 0xbb, 0x4f, 0xf7, 0x4c, 0x5f, + 0x6f, 0x55, 0x0e, 0x3d, 0x3d, 0x9a, 0xcf, 0x81, + 0x12, 0x5d, 0x52, 0xe0, 0x47, 0x8c, 0xda, 0x0b, + 0xf4, 0x31, 0x41, 0x13, 0xd0, 0xd5, 0x2d, 0xa0, + 0x5b, 0x20, 0x9a, 0xed, 0x51, 0x5d, 0x13, 0xd6, + + // magic + 0x6b, 0x62, 0x6f, 0x78, + + // Crc + 0x39, 0xf2, 0x94, 0xa7, +}; + +const size_t DEVICE_KEY_OFFSET = 0x20; +const size_t CA_TOKEN_OFFSET = 0x30; +const size_t KEYBOX_VERSION_OFFSET = CA_TOKEN_OFFSET; +const size_t SYSTEM_ID_OFFSET = CA_TOKEN_OFFSET + sizeof(uint32_t); +const size_t CA_TOKEN_SIZE = 0x48; +const size_t CA_TOKEN_END = CA_TOKEN_OFFSET + CA_TOKEN_SIZE; + +// sample renewal key for testing +static const std::vector renewal_key = { + 0xfa, 0xfd, 0xc1, 0x1b, 0x55, 0x6f, 0xac, 0xd3, + 0x14, 0x62, 0x50, 0xa0, 0xf7, 0xa5, 0x1a, 0x0e +}; + +static const std::string label = "Keyboxv3"; +static const std::vector new_system_id = {0x00, 0x00, 0x23, 0x45}; +static const std::vector new_keybox_version = {0x00, 0x00, 0x00, 0x03}; + +// clang-format on + +static std::string GetSSLError() { + char error_buffer[128]; + ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer)); + return error_buffer; +} + +// Derive the new device key from renewal key and context using +// NIST 800-108 key derivation with 128-bit AES-128-CMAC as the pseudorandom +// function in counter mode: +// +// New Device Key := PRF(renewal_key, 1 || label || 0x00 || context || L) +// PRF := AES-128-CMAC +// label := “Keyboxv3” +// L=(unsigned long)0x80: 0x00|0x00|0x00|0x80 +static bool DeriveKey(const std::vector& key, + const std::vector& context, + std::vector* out) { + if (key.empty() || context.empty() || out == NULL) { + std::cerr << "DeriveKey(): Invalid inputs" << std::endl; + return false; + } + + std::vector cmac_input; + cmac_input.push_back(1); + std::copy(label.begin(), label.end(), std::back_inserter(cmac_input)); + cmac_input.push_back(0x00); + std::copy(context.begin(), context.end(), std::back_inserter(cmac_input)); + cmac_input.push_back(0x00); + cmac_input.push_back(0x00); + cmac_input.push_back(0x00); + cmac_input.push_back(0x80); + + const EVP_CIPHER* cipher = EVP_aes_128_cbc(); + CMAC_CTX* cmac_ctx = CMAC_CTX_new(); + + if (!cmac_ctx) { + std::cerr << "DeriveKey(): Failed to create context: " << GetSSLError() + << std::endl; + return false; + } + + if (!CMAC_Init(cmac_ctx, &key[0], key.size(), cipher, 0)) { + std::cerr << "DeriveKey(): Failed to initialize: " << GetSSLError() + << std::endl; + CMAC_CTX_free(cmac_ctx); + return false; + } + + if (!CMAC_Update(cmac_ctx, &cmac_input[0], cmac_input.size())) { + std::cerr << "DeriveKey(): Failed to update: " << GetSSLError() + << std::endl; + CMAC_CTX_free(cmac_ctx); + return false; + } + + size_t reslen; + uint8_t res[128]; + if (!CMAC_Final(cmac_ctx, res, &reslen)) { + std::cerr << "DeriveKey(): Failed to finalize: " << GetSSLError() + << std::endl; + CMAC_CTX_free(cmac_ctx); + return false; + } + out->assign(res, res + reslen); + CMAC_CTX_free(cmac_ctx); + + return true; +} + +int main(int argc, char** argv) { + if (argc != 2) { + std::cerr << "usage: " << argv[0] << " " << std::endl; + exit(0); + } + + std::string filename = argv[1]; + std::ofstream new_key_file; + new_key_file.open(filename, std::ios::binary); + if (!new_key_file) { + std::cerr << "unable to open " << filename << " for writing" << std::endl; + exit(-1); + } + + // patch the keybox with version 3 and system ID + std::copy(new_keybox_version.begin(), new_keybox_version.end(), + test_keybox.begin() + KEYBOX_VERSION_OFFSET); + std::copy(new_system_id.begin(), new_system_id.end(), + test_keybox.begin() + SYSTEM_ID_OFFSET); + + // context is Device Key || CA token + std::vector context(test_keybox.begin() + DEVICE_KEY_OFFSET, + test_keybox.begin() + CA_TOKEN_END); + + // derive the new device key + std::vector new_device_key; + if (!DeriveKey(renewal_key, context, &new_device_key)) { + std::cerr << "Failed to derive new renewal key" << std::endl; + exit(-1); + } else { + std::ostream_iterator output_iterator(new_key_file); + std::copy(new_device_key.begin(), new_device_key.end(), output_iterator); + new_key_file.close(); + std::cout << "New key written to " << filename << std::endl; + ; + } +} diff --git a/libwvdrmengine/oemcrypto/test/common.mk b/libwvdrmengine/oemcrypto/test/common.mk index 5c67ef01..8a0dfd84 100644 --- a/libwvdrmengine/oemcrypto/test/common.mk +++ b/libwvdrmengine/oemcrypto/test/common.mk @@ -5,20 +5,29 @@ ifeq ($(filter mips mips64, $(TARGET_ARCH)),) LOCAL_LDFLAGS+=-Wl,--hash-style=both endif +# The unit tests can access v15 functions through the dynamic adapter: +LOCAL_CFLAGS += -DTEST_OEMCRYPTO_V15 + LOCAL_SRC_FILES:= \ oec_device_features.cpp \ + oec_decrypt_fallback_chain.cpp \ + oec_key_deriver.cpp \ oec_session_util.cpp \ oemcrypto_session_tests_helper.cpp \ oemcrypto_test.cpp \ oemcrypto_test_android.cpp \ oemcrypto_test_main.cpp \ wvcrc.cpp \ + ../../cdm/util/test/test_sleep.cpp \ LOCAL_C_INCLUDES += \ $(LOCAL_PATH)/../include \ + $(LOCAL_PATH)/../odk/include \ + $(LOCAL_PATH)/../odk/kdo/include \ $(LOCAL_PATH)/../ref/src \ vendor/widevine/libwvdrmengine/cdm/core/include \ vendor/widevine/libwvdrmengine/cdm/util/include \ + vendor/widevine/libwvdrmengine/cdm/util/test \ LOCAL_STATIC_LIBRARIES := \ libcdm \ @@ -27,14 +36,17 @@ LOCAL_STATIC_LIBRARIES := \ libgtest \ libgtest_main \ libwvlevel3 \ + libcdm_protos \ libcdm_utils \ + libwv_kdo \ + libwv_odk \ LOCAL_SHARED_LIBRARIES := \ libbase \ libdl \ liblog \ libmedia_omx \ + libprotobuf-cpp-lite \ libstagefright_foundation \ libutils \ libz \ - diff --git a/libwvdrmengine/oemcrypto/test/oec_decrypt_fallback_chain.cpp b/libwvdrmengine/oemcrypto/test/oec_decrypt_fallback_chain.cpp new file mode 100644 index 00000000..e8d82477 --- /dev/null +++ b/libwvdrmengine/oemcrypto/test/oec_decrypt_fallback_chain.cpp @@ -0,0 +1,178 @@ +// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "oec_decrypt_fallback_chain.h" + +#include +#include + +#include "oemcrypto_types.h" +#include "string_conversions.h" + +namespace { + +void advance_dest_buffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) { + switch (dest_buffer->type) { + case OEMCrypto_BufferType_Clear: + dest_buffer->buffer.clear.address += bytes; + dest_buffer->buffer.clear.address_length -= bytes; + break; + + case OEMCrypto_BufferType_Secure: + dest_buffer->buffer.secure.offset += bytes; + break; + + case OEMCrypto_BufferType_Direct: + // Nothing to do for this buffer type. + break; + } +} + +void advance_iv_ctr(uint8_t (*subsample_iv)[wvoec::KEY_IV_SIZE], size_t bytes) { + uint64_t counter; + constexpr size_t half_iv_size = wvoec::KEY_IV_SIZE / 2; + memcpy(&counter, &(*subsample_iv)[half_iv_size], half_iv_size); + + const size_t increment = + bytes / wvoec::AES_128_BLOCK_SIZE; // The truncation here is intentional + counter = wvcdm::htonll64(wvcdm::ntohll64(counter) + increment); + + memcpy(&(*subsample_iv)[half_iv_size], &counter, half_iv_size); +} + +} // namespace + +namespace wvoec { + +// Decrypts the given array of samples. Handles fallback behavior correctly if +// the OEMCrypto implementation does not accept multiple samples. +OEMCryptoResult DecryptFallbackChain::Decrypt( + OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription* samples, + size_t samples_length, OEMCryptoCipherMode cipher_mode, + const OEMCrypto_CENCEncryptPatternDesc* pattern) { + OEMCryptoResult sts = + OEMCrypto_DecryptCENC(session_id, samples, samples_length, pattern); + + // No need for a fallback. Abort early. + if (sts != OEMCrypto_ERROR_BUFFER_TOO_LARGE) return sts; + + // Fall back to decrypting individual samples. + for (size_t i = 0; i < samples_length; ++i) { + sts = DecryptSample(session_id, samples[i], cipher_mode, pattern); + if (sts != OEMCrypto_SUCCESS) return sts; + } + + return sts; +} + +// Decrypts the given sample. Handles fallback behavior correctly if the +// OEMCrypto implementation does not accept full samples. +OEMCryptoResult DecryptFallbackChain::DecryptSample( + OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription& sample, + OEMCryptoCipherMode cipher_mode, + const OEMCrypto_CENCEncryptPatternDesc* pattern) { + OEMCryptoResult sts = OEMCrypto_DecryptCENC(session_id, &sample, 1, pattern); + + // No need for a fallback. Abort early. + if (sts != OEMCrypto_ERROR_BUFFER_TOO_LARGE) return sts; + + // Fall back to decrypting individual subsamples. + OEMCrypto_SampleDescription fake_sample = sample; + for (size_t i = 0; i < sample.subsamples_length; ++i) { + const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[i]; + + const size_t length = + subsample.num_bytes_clear + subsample.num_bytes_encrypted; + fake_sample.buffers.input_data_length = length; + fake_sample.subsamples = &subsample; + fake_sample.subsamples_length = 1; + + sts = DecryptSubsample(session_id, fake_sample, pattern); + if (sts != OEMCrypto_SUCCESS) return sts; + + fake_sample.buffers.input_data += length; + advance_dest_buffer(&fake_sample.buffers.output_descriptor, length); + if (cipher_mode == OEMCrypto_CipherMode_CTR) { + advance_iv_ctr(&fake_sample.iv, + subsample.block_offset + subsample.num_bytes_encrypted); + } + } + + return sts; +} + +// Decrypts the given subsample. Handles fallback behavior correctly if the +// OEMCrypto implementation does not accept full subsamples. +OEMCryptoResult DecryptFallbackChain::DecryptSubsample( + OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription& sample, + const OEMCrypto_CENCEncryptPatternDesc* pattern) { + OEMCryptoResult sts = OEMCrypto_DecryptCENC(session_id, &sample, 1, pattern); + + // No need for a fallback. Abort early. + if (sts != OEMCrypto_ERROR_BUFFER_TOO_LARGE) return sts; + + // Fall back to decrypting individual subsample halves. + const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0]; + OEMCrypto_SampleDescription fake_sample = sample; + OEMCrypto_SubSampleDescription fake_subsample; + fake_sample.subsamples = &fake_subsample; + fake_sample.subsamples_length = 1; + + if (subsample.num_bytes_clear > 0) { + fake_sample.buffers.input_data_length = subsample.num_bytes_clear; + fake_subsample.num_bytes_clear = subsample.num_bytes_clear; + fake_subsample.num_bytes_encrypted = 0; + fake_subsample.block_offset = 0; + + fake_subsample.subsample_flags = 0; + if (subsample.subsample_flags & OEMCrypto_FirstSubsample) + fake_subsample.subsample_flags |= OEMCrypto_FirstSubsample; + if (subsample.subsample_flags & OEMCrypto_LastSubsample && + subsample.num_bytes_encrypted == 0) + fake_subsample.subsample_flags |= OEMCrypto_LastSubsample; + + sts = DecryptSubsampleHalf(session_id, fake_sample, pattern); + if (sts != OEMCrypto_SUCCESS) return sts; + + // Advance the buffers for the other half, in case they're needed. + fake_sample.buffers.input_data += subsample.num_bytes_clear; + advance_dest_buffer(&fake_sample.buffers.output_descriptor, + subsample.num_bytes_clear); + } + + if (subsample.num_bytes_encrypted > 0) { + fake_sample.buffers.input_data_length = subsample.num_bytes_encrypted; + fake_subsample.num_bytes_clear = 0; + fake_subsample.num_bytes_encrypted = subsample.num_bytes_encrypted; + fake_subsample.block_offset = subsample.block_offset; + + fake_subsample.subsample_flags = 0; + if (subsample.subsample_flags & OEMCrypto_FirstSubsample && + subsample.num_bytes_clear == 0) + fake_subsample.subsample_flags |= OEMCrypto_FirstSubsample; + if (subsample.subsample_flags & OEMCrypto_LastSubsample) + fake_subsample.subsample_flags |= OEMCrypto_LastSubsample; + + sts = DecryptSubsampleHalf(session_id, fake_sample, pattern); + if (sts != OEMCrypto_SUCCESS) return sts; + } + + return sts; +} + +// Decrypts the given subsample half. There is no fallback behavior after this; +// an OEMCrypto_ERROR_BUFFER_TOO_LARGE produced here will be returned to the +// caller. +OEMCryptoResult DecryptFallbackChain::DecryptSubsampleHalf( + OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription& sample, + const OEMCrypto_CENCEncryptPatternDesc* pattern) { + return OEMCrypto_DecryptCENC(session_id, &sample, 1, pattern); + // In a real CDM, you would want some fallback here to handle the case where + // the buffer is too big for the OEMCrypto implementation. But in the case of + // the tests, we won't be passing a buffer that's too big unless we are trying + // to test that failure condition, so there's no need to handle that case + // here. +} + +} // namespace wvoec diff --git a/libwvdrmengine/oemcrypto/test/oec_decrypt_fallback_chain.h b/libwvdrmengine/oemcrypto/test/oec_decrypt_fallback_chain.h new file mode 100644 index 00000000..0e1512b1 --- /dev/null +++ b/libwvdrmengine/oemcrypto/test/oec_decrypt_fallback_chain.h @@ -0,0 +1,59 @@ +// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef CDM_OEC_DECRYPT_FALLBACK_CHAIN_H_ +#define CDM_OEC_DECRYPT_FALLBACK_CHAIN_H_ + +#include "OEMCryptoCENC.h" +#include "disallow_copy_and_assign.h" + +namespace wvoec { + +// This class groups static methods relating to providing proper fallback +// behavior when calling DecryptCENC in OEMCrypto v16. Outside code can leverage +// this behavior by passing the samples to be decrypted to Decrypt(), which will +// set off the chain of fallback functions as needed. +// +// The behavior of this class is pathological. For each block of data, it will +// greedily try every possible way of passing data to OEMCrypto until one works. +// In the order tried, the ways to send data are: +// 1) Multiple Samples at once +// 2) Individual Samples one at a time +// 3) Individual Subsamples one at a time +// 4) Individual Half-Subsamples one at a time +// On a device that only accepts half-subsamples, the way OEMCrypto v15 did, +// this results in many needless roundtrips to OEMCrypto. This would be +// inefficient behavior for a real CDM, but for the sake of testing, we want to +// use the maximal way the OEMCrypto implementation will accept the data. And, +// for implementations that do not accept multiple samples or subsamples per +// call, we want to test that they correctly reject larger calls. +class DecryptFallbackChain { + public: + static OEMCryptoResult Decrypt( + OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription* samples, + size_t samples_length, OEMCryptoCipherMode cipher_mode, + const OEMCrypto_CENCEncryptPatternDesc* pattern); + + private: + static OEMCryptoResult DecryptSample( + OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription& sample, + OEMCryptoCipherMode cipher_mode, + const OEMCrypto_CENCEncryptPatternDesc* pattern); + + static OEMCryptoResult DecryptSubsample( + OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription& sample, + const OEMCrypto_CENCEncryptPatternDesc* pattern); + + static OEMCryptoResult DecryptSubsampleHalf( + OEMCrypto_SESSION session_id, const OEMCrypto_SampleDescription& sample, + const OEMCrypto_CENCEncryptPatternDesc* pattern); + + // There is no reason to have an instance of this class. + DecryptFallbackChain() = delete; + CORE_DISALLOW_COPY_AND_ASSIGN(DecryptFallbackChain); +}; + +} // namespace wvoec + +#endif // CDM_OEC_DECRYPT_FALLBACK_CHAIN_H_ diff --git a/libwvdrmengine/oemcrypto/test/oec_device_features.cpp b/libwvdrmengine/oemcrypto/test/oec_device_features.cpp index 98eae18e..bf9d61a8 100644 --- a/libwvdrmengine/oemcrypto/test/oec_device_features.cpp +++ b/libwvdrmengine/oemcrypto/test/oec_device_features.cpp @@ -60,11 +60,9 @@ bool CanChangeTime() { #endif } -void DeviceFeatures::Initialize(bool is_cast_receiver, - bool force_load_test_keybox) { - cast_receiver = is_cast_receiver; +void DeviceFeatures::Initialize() { + if (initialized_) return; uses_keybox = false; - uses_certificate = false; loads_certificate = false; generic_crypto = false; usage_table = false; @@ -76,7 +74,6 @@ void DeviceFeatures::Initialize(bool is_cast_receiver, printf("OEMCrypto_Initialize failed. All tests will fail.\n"); return; } - uint32_t nonce = 0; uint8_t buffer[1]; size_t size = 0; provisioning_method = OEMCrypto_GetProvisioningMethod(); @@ -92,24 +89,16 @@ void DeviceFeatures::Initialize(bool is_cast_receiver, } // If the device uses a keybox, check to see if loading a certificate is // installed. - if (provisioning_method == OEMCrypto_Keybox) { - loads_certificate = - (OEMCrypto_ERROR_NOT_IMPLEMENTED != - OEMCrypto_RewrapDeviceRSAKey(session, buffer, 0, buffer, 0, &nonce, - buffer, 0, buffer, buffer, &size)); - } else if (provisioning_method == OEMCrypto_OEMCertificate) { - // If the device says it uses Provisioning 3.0, then it should be able to - // load a DRM certificate. These devices must support RewrapDeviceRSAKey30. + if (provisioning_method == OEMCrypto_Keybox || + provisioning_method == OEMCrypto_OEMCertificate) { + // Devices with a keybox or OEM Certificate are required to support loading + // a DRM certificate. loads_certificate = true; } else { // Other devices are either broken, or they have a baked in certificate. loads_certificate = false; } printf("loads_certificate = %s.\n", loads_certificate ? "true" : "false"); - uses_certificate = (OEMCrypto_ERROR_NOT_IMPLEMENTED != - OEMCrypto_GenerateRSASignature(session, buffer, 0, buffer, - &size, kSign_RSASSA_PSS)); - printf("uses_certificate = %s.\n", uses_certificate ? "true" : "false"); generic_crypto = (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_Generic_Encrypt(session, buffer, 0, buffer, @@ -122,11 +111,7 @@ void DeviceFeatures::Initialize(bool is_cast_receiver, // usage tables. if (api_version > 12) usage_table = OEMCrypto_SupportsUsageTable(); printf("usage_table = %s.\n", usage_table ? "true" : "false"); - if (force_load_test_keybox) { - derive_key_method = FORCE_TEST_KEYBOX; - } else { - PickDerivedKey(); - } + PickDerivedKey(); if (api_version >= 13) { uint32_t supported_cert = OEMCrypto_SupportedCertificates(); if (supported_cert & OEMCrypto_Supports_RSA_CAST) { @@ -155,7 +140,6 @@ void DeviceFeatures::Initialize(bool is_cast_receiver, printf("NO_METHOD: Cannot derive known session keys.\n"); // Note: cast_receiver left unchanged because set by user on command line. uses_keybox = false; - uses_certificate = false; loads_certificate = false; generic_crypto = false; usage_table = false; @@ -166,9 +150,6 @@ void DeviceFeatures::Initialize(bool is_cast_receiver, case LOAD_TEST_RSA_KEY: printf("LOAD_TEST_RSA_KEY: Call LoadTestRSAKey before deriving keys.\n"); break; - case FORCE_TEST_KEYBOX: - printf("FORCE_TEST_KEYBOX: User requested calling InstallKeybox.\n"); - break; case TEST_PROVISION_30: printf("TEST_PROVISION_30: Device provisioed with OEM Cert.\n"); break; @@ -178,15 +159,15 @@ void DeviceFeatures::Initialize(bool is_cast_receiver, printf("SecurityLevel is %s (%s)\n", supports_level_1 ? "Level 1" : "Not Level 1", security_level.c_str()); + CheckSecureBuffers(); OEMCrypto_Terminate(); + initialized_ = true; } std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) { std::string filter = initial_filter; + // clang-format off if (!uses_keybox) FilterOut(&filter, "*KeyboxTest*"); - if (derive_key_method - != FORCE_TEST_KEYBOX) FilterOut(&filter, "*ForceKeybox*"); - if (!uses_certificate) FilterOut(&filter, "OEMCrypto*Cert*"); if (!loads_certificate) FilterOut(&filter, "OEMCryptoLoadsCert*"); if (!generic_crypto) FilterOut(&filter, "*GenericCrypto*"); if (!cast_receiver) FilterOut(&filter, "*CastReceiver*"); @@ -202,10 +183,12 @@ std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) { if (api_version < 13) FilterOut(&filter, "*API13*"); if (api_version < 14) FilterOut(&filter, "*API14*"); if (api_version < 15) FilterOut(&filter, "*API15*"); + if (api_version < 16) FilterOut(&filter, "*API16*"); + // clang-format on // Some tests may require root access. If user is not root, filter these tests // out. if (!CanChangeTime()) { - FilterOut(&filter, "UsageTableTest.TimeRollbackPrevention"); + FilterOut(&filter, "*TimeRollbackPrevention*"); } // Performance tests take a long time. Filter them out if they are not // specifically requested. @@ -239,7 +222,8 @@ void DeviceFeatures::PickDerivedKey() { } if (uses_keybox) { // If device uses a keybox, try to load the test keybox. - if (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_LoadTestKeybox(NULL, 0)) { + if (OEMCrypto_ERROR_NOT_IMPLEMENTED != + OEMCrypto_LoadTestKeybox(nullptr, 0)) { derive_key_method = LOAD_TEST_KEYBOX; } } else if (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_LoadTestRSAKey()) { @@ -247,6 +231,39 @@ void DeviceFeatures::PickDerivedKey() { } } +void DeviceFeatures::CheckSecureBuffers() { + output_types_.push_back({false, OEMCrypto_BufferType_Clear}); + output_types_.push_back({true, OEMCrypto_BufferType_Clear}); + test_secure_buffers = false; + OEMCrypto_SESSION session; + OEMCryptoResult result = OEMCrypto_OpenSession(&session); + if (result != OEMCrypto_SUCCESS) { + printf("--- ERROR: Could not open session: %d ----\n", result); + return; + } + OEMCrypto_DestBufferDesc output_descriptor; + output_descriptor.type = OEMCrypto_BufferType_Secure; + int secure_fd; + result = OEMCrypto_AllocateSecureBuffer(session, 42, &output_descriptor, + &secure_fd); + if (result == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + printf("Secure buffers will not be tested\n"); + return; + } + if (result != OEMCrypto_SUCCESS) { + printf("--- ERROR: Could not create secure buffer: %d ----\n", result); + return; + } + result = OEMCrypto_FreeSecureBuffer(session, &output_descriptor, secure_fd); + if (result != OEMCrypto_SUCCESS) { + printf("--- ERROR: Could not free secure buffer: %d ----\n", result); + return; + } + printf("Secure buffers will be tested\n"); + output_types_.push_back({false, OEMCrypto_BufferType_Secure}); + test_secure_buffers = true; +} + void DeviceFeatures::FilterOut(std::string* current_filter, const std::string& new_filter) { if (current_filter->find('-') == std::string::npos) { @@ -256,6 +273,14 @@ void DeviceFeatures::FilterOut(std::string* current_filter, } } +// Return the list of output types for the decrypt tests. +const std::vector& DeviceFeatures::GetOutputTypes() { + if (!initialized_) { + Initialize(); + } + return output_types_; +} + const char* ProvisioningMethodName(OEMCrypto_ProvisioningMethod method) { switch (method) { case OEMCrypto_ProvisioningError: diff --git a/libwvdrmengine/oemcrypto/test/oec_device_features.h b/libwvdrmengine/oemcrypto/test/oec_device_features.h index c0d94e09..9cef66f2 100644 --- a/libwvdrmengine/oemcrypto/test/oec_device_features.h +++ b/libwvdrmengine/oemcrypto/test/oec_device_features.h @@ -2,12 +2,26 @@ #define CDM_OEC_DEVICE_FEATURES_H_ #include +#include #include "OEMCryptoCENC.h" #include "oemcrypto_types.h" namespace wvoec { +// These tests are designed to work for this version: +constexpr unsigned int kCurrentAPI = 16; +// The API version when Core Messages were introduced. +constexpr unsigned int kCoreMessagesAPI = 16; + +// An output type for testing. The type field is secure, clear, or direct. If +// the type is clear, then decrypt_inplace could be true. Otherwise, +// decrypt_inplace is false. +struct OutputType { + bool decrypt_inplace; + OEMCryptoBufferType type; +}; + // Keeps track of which features are supported by the version of OEMCrypto being // tested. See the integration guide for a list of optional features. class DeviceFeatures { @@ -19,13 +33,11 @@ class DeviceFeatures { NO_METHOD, // Cannot derive known session keys. LOAD_TEST_KEYBOX, // Call LoadTestKeybox before deriving keys. LOAD_TEST_RSA_KEY, // Call LoadTestRSAKey before deriving keys. - FORCE_TEST_KEYBOX, // User requested calling InstallKeybox. TEST_PROVISION_30, // Device has OEM Certificate installed. }; enum DeriveMethod derive_key_method; bool uses_keybox; // Device uses a keybox to derive session keys. - bool uses_certificate; // Device uses a certificate to derive session keys. bool loads_certificate; // Device can load a certificate from the server. bool generic_crypto; // Device supports generic crypto. bool cast_receiver; // Device supports alternate rsa signature padding. @@ -34,23 +46,36 @@ class DeviceFeatures { bool supports_level_1; // Device supports Level 1 security. uint32_t resource_rating; // Device's resource rating tier. bool supports_crc; // Supported decrypt hash type CRC. + bool test_secure_buffers; // If we can create a secure buffer for testing. uint32_t api_version; OEMCrypto_ProvisioningMethod provisioning_method; // This should be called from the test program's main procedure. - void Initialize(bool is_cast_receiver, bool force_load_test_keybox); + void Initialize(); + void set_cast_receiver(bool is_cast_receiver) { + cast_receiver = is_cast_receiver; + } // Generate a GTest filter of tests that should not be run. This should be // called after Initialize. Tests are filtered out based on which features // are not supported. For example, a device that uses Provisioning 3.0 will // have all keybox tests filtered out. std::string RestrictFilter(const std::string& initial_filter); + // Get a list of output types that should be tested. + const std::vector& GetOutputTypes(); + private: // Decide which method should be used to derive session keys, based on // supported featuers. void PickDerivedKey(); + // Decide if secure buffers can be created, and initialize output_types_. + void CheckSecureBuffers(); // Add a GTest filter restriction to the current filter. void FilterOut(std::string* current_filter, const std::string& new_filter); + + // A list of possible output types. + std::vector output_types_; + bool initialized_ = false; }; // There is one global set of features for the version of OEMCrypto being diff --git a/libwvdrmengine/oemcrypto/test/oec_key_deriver.cpp b/libwvdrmengine/oemcrypto/test/oec_key_deriver.cpp new file mode 100644 index 00000000..34fb0fe5 --- /dev/null +++ b/libwvdrmengine/oemcrypto/test/oec_key_deriver.cpp @@ -0,0 +1,168 @@ +// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. +// +// OEMCrypto unit tests +// + +#include "oec_session_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "OEMCryptoCENC.h" +#include "disallow_copy_and_assign.h" +#include "log.h" +#include "oec_device_features.h" +#include "oec_test_data.h" +#include "oemcrypto_types.h" +#include "platform.h" +#include "string_conversions.h" + +using namespace std; + +namespace wvoec { + +void Encryptor::set_enc_key(const std::vector& enc_key) { + enc_key_ = enc_key; +} + +void Encryptor::CBCEncrypt(const uint8_t* data, uint8_t* encrypted_data, + size_t data_length, + const uint8_t (&iv)[KEY_IV_SIZE]) const { + ASSERT_EQ(enc_key_.size(), KEY_SIZE); + ASSERT_NE(data, nullptr); + ASSERT_NE(encrypted_data, nullptr); + AES_KEY aes_key; + static const int key_size = KEY_SIZE * 8; // in bits. + AES_set_encrypt_key(enc_key_.data(), key_size, &aes_key); + uint8_t iv_buffer[KEY_IV_SIZE]; + memcpy(iv_buffer, iv, KEY_IV_SIZE); + AES_cbc_encrypt(data, encrypted_data, data_length, &aes_key, iv_buffer, + AES_ENCRYPT); +} + +void Encryptor::PadAndEncryptProvisioningMessage( + RSAPrivateKeyMessage* data, RSAPrivateKeyMessage* encrypted) const { + EXPECT_EQ(1, GetRandBytes(data->rsa_key_iv, KEY_IV_SIZE)); + ASSERT_EQ(enc_key_.size(), KEY_SIZE); + *encrypted = *data; + size_t padding = AES_BLOCK_SIZE - (data->rsa_key_length % AES_BLOCK_SIZE); + memset(data->rsa_key + data->rsa_key_length, static_cast(padding), + padding); + encrypted->rsa_key_length = data->rsa_key_length + padding; + AES_KEY aes_key; + static const int key_size = KEY_SIZE * 8; // in bits. + AES_set_encrypt_key(enc_key_.data(), key_size, &aes_key); + uint8_t iv_buffer[KEY_IV_SIZE]; + memcpy(iv_buffer, &data->rsa_key_iv[0], KEY_IV_SIZE); + AES_cbc_encrypt(&data->rsa_key[0], &encrypted->rsa_key[0], + encrypted->rsa_key_length, &aes_key, iv_buffer, AES_ENCRYPT); +} + +// This generates the data for deriving one key. If there are failures in +// this function, then there is something wrong with the test program and its +// dependency on BoringSSL. +void KeyDeriver::DeriveKey(const uint8_t* key, const vector& context, + int counter, vector* out) { + ASSERT_NE(key, nullptr); + ASSERT_FALSE(context.empty()); + ASSERT_GE(4, counter); + ASSERT_LE(1, counter); + ASSERT_NE(out, nullptr); + + const EVP_CIPHER* cipher = EVP_aes_128_cbc(); + CMAC_CTX* cmac_ctx = CMAC_CTX_new(); + ASSERT_NE(nullptr, cmac_ctx); + + ASSERT_TRUE(CMAC_Init(cmac_ctx, key, KEY_SIZE, cipher, 0)); + + std::vector message; + message.push_back(static_cast(counter)); + message.insert(message.end(), context.begin(), context.end()); + + ASSERT_TRUE(CMAC_Update(cmac_ctx, message.data(), message.size())); + + size_t reslen; + uint8_t res[128]; + ASSERT_TRUE(CMAC_Final(cmac_ctx, res, &reslen)); + + out->assign(res, res + reslen); + CMAC_CTX_free(cmac_ctx); +} + +// This generates the data for deriving a set of keys. If there are failures in +// this function, then there is something wrong with the test program and its +// dependency on BoringSSL. +void KeyDeriver::DeriveKeys(const uint8_t* master_key, + const vector& mac_key_context, + const vector& enc_key_context) { + // Generate derived key for mac key + std::vector mac_key_part2; + DeriveKey(master_key, mac_key_context, 1, &mac_key_server_); + DeriveKey(master_key, mac_key_context, 2, &mac_key_part2); + mac_key_server_.insert(mac_key_server_.end(), mac_key_part2.begin(), + mac_key_part2.end()); + + DeriveKey(master_key, mac_key_context, 3, &mac_key_client_); + DeriveKey(master_key, mac_key_context, 4, &mac_key_part2); + mac_key_client_.insert(mac_key_client_.end(), mac_key_part2.begin(), + mac_key_part2.end()); + + // Generate derived key for encryption key + std::vector enc_key; + DeriveKey(master_key, enc_key_context, 1, &enc_key); + set_enc_key(enc_key); +} + +void KeyDeriver::set_mac_keys(const uint8_t* mac_keys) { + ASSERT_EQ(mac_key_server_.size(), MAC_KEY_SIZE); + ASSERT_EQ(mac_key_client_.size(), MAC_KEY_SIZE); + memcpy(mac_key_server_.data(), mac_keys, MAC_KEY_SIZE); + memcpy(mac_key_client_.data(), mac_keys + MAC_KEY_SIZE, MAC_KEY_SIZE); +} + +void KeyDeriver::ServerSignBuffer(const uint8_t* data, size_t data_length, + std::vector* signature) const { + ASSERT_EQ(mac_key_server_.size(), MAC_KEY_SIZE); + signature->assign(SHA256_DIGEST_LENGTH, 0); + unsigned int sig_len = SHA256_DIGEST_LENGTH; + ASSERT_TRUE(HMAC(EVP_sha256(), mac_key_server_.data(), mac_key_server_.size(), + data, data_length, signature->data(), &sig_len)); +} + +void KeyDeriver::ClientSignBuffer(const vector& buffer, + std::vector* signature) const { + ASSERT_EQ(mac_key_client_.size(), MAC_KEY_SIZE); + signature->assign(SHA256_DIGEST_LENGTH, 0); + unsigned int sig_len = SHA256_DIGEST_LENGTH; + ASSERT_TRUE(HMAC(EVP_sha256(), mac_key_client_.data(), mac_key_client_.size(), + buffer.data(), buffer.size(), signature->data(), &sig_len)); +} + +void KeyDeriver::ClientSignPstReport(const vector& pst_report_buffer, + std::vector* signature) const { + ASSERT_EQ(mac_key_client_.size(), MAC_KEY_SIZE); + signature->assign(SHA_DIGEST_LENGTH, 0); + unsigned int sig_len = SHA_DIGEST_LENGTH; + ASSERT_TRUE(HMAC(EVP_sha1(), mac_key_client_.data(), mac_key_client_.size(), + &pst_report_buffer[SHA_DIGEST_LENGTH], + pst_report_buffer.size() - SHA_DIGEST_LENGTH, + signature->data(), &sig_len)); +} + +} // namespace wvoec diff --git a/libwvdrmengine/oemcrypto/test/oec_key_deriver.h b/libwvdrmengine/oemcrypto/test/oec_key_deriver.h new file mode 100644 index 00000000..7e05038a --- /dev/null +++ b/libwvdrmengine/oemcrypto/test/oec_key_deriver.h @@ -0,0 +1,93 @@ +// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. +// +#ifndef CDM_OEC_KEY_DERIVER_H_ +#define CDM_OEC_KEY_DERIVER_H_ + +#include +#include +#include +#include +#include + +#include "oec_device_features.h" +#include "oemcrypto_types.h" +#include "pst_report.h" + +namespace wvoec { + +constexpr size_t kMaxTestRSAKeyLength = 2000; // Rough estimate. +constexpr size_t kMaxCoreProvRequest = 150; // Rough estimate. + +// This structure will be signed to simulate a provisioning response from the +// server. +struct RSAPrivateKeyMessage { + uint8_t rsa_key[kMaxTestRSAKeyLength]; + uint8_t rsa_key_iv[KEY_IV_SIZE]; + size_t rsa_key_length; + uint8_t enc_message_key[kMaxTestRSAKeyLength]; + size_t enc_message_key_length; + uint32_t nonce; +}; + +// Holds an encryption key and can encrypt a provisioning message. It also can +// encrypt short buffers using CBC, such as content keys in a license. +class Encryptor { + public: + Encryptor() : enc_key_(KEY_SIZE, 0) {} + Encryptor(const std::vector& enc_key) { set_enc_key(enc_key); }; + Encryptor& operator=(const Encryptor&) = default; + void set_enc_key(const std::vector& enc_key); + + // This encrypts an RSAPrivateKeyMessage with encryption_key so that it may be + // loaded with OEMCrypto_LoadProvisioningResponse. + // This modifies the clear data: it adds padding and generates a random iv. + void PadAndEncryptProvisioningMessage(RSAPrivateKeyMessage* data, + RSAPrivateKeyMessage* encrypted) const; + + void CBCEncrypt(const uint8_t* data, uint8_t* encrypted_data, + size_t data_length, const uint8_t (&iv)[KEY_IV_SIZE]) const; + + private: + std::vector enc_key_; +}; + +// Holds encryption and mac keys derived from a master key. +// Can be used to sign a buffer as either a server or client. +class KeyDeriver : public Encryptor { + public: + KeyDeriver() + : mac_key_server_(MAC_KEY_SIZE, 0), mac_key_client_(MAC_KEY_SIZE, 0) {} + KeyDeriver& operator=(const KeyDeriver&) = default; + + // Generate mac and enc keys give the master key. + void DeriveKeys(const uint8_t* master_key, + const std::vector& mac_key_context, + const std::vector& enc_key_context); + // Sign the buffer with server's mac key. + void ServerSignBuffer(const uint8_t* data, size_t data_length, + std::vector* signature) const; + // Sign the buffer with client's known mac key. Known test keys must be + // installed first. This uses HMAC with SHA256, so is suitable for a message. + void ClientSignBuffer(const std::vector& buffer, + std::vector* signature) const; + // Sign the pst buffer with client's known mac key. Known test keys must be + // installed first. This uses HMAC with SHA128, and skips the beginning of the + // buffer, so is only suitable for a pst report. + void ClientSignPstReport(const std::vector& pst_report_buffer, + std::vector* signature) const; + void set_mac_keys(const uint8_t* mac_keys); + + private: + // Internal utility function to derive key using CMAC-128 + void DeriveKey(const uint8_t* key, const std::vector& context, + int counter, std::vector* out); + + std::vector mac_key_server_; + std::vector mac_key_client_; +}; + +} // namespace wvoec + +#endif // CDM_OEC_KEY_DERIVER_H_ diff --git a/libwvdrmengine/oemcrypto/test/oec_session_util.cpp b/libwvdrmengine/oemcrypto/test/oec_session_util.cpp index b041a034..6c10c32f 100644 --- a/libwvdrmengine/oemcrypto/test/oec_session_util.cpp +++ b/libwvdrmengine/oemcrypto/test/oec_session_util.cpp @@ -19,12 +19,16 @@ #include #include +#include #include #include #include #include #include "OEMCryptoCENC.h" +#include "clock.h" +#include "core_message_deserialize.h" +#include "core_message_serialize.h" #include "disallow_copy_and_assign.h" #include "log.h" #include "oec_device_features.h" @@ -32,6 +36,7 @@ #include "oemcrypto_types.h" #include "platform.h" #include "string_conversions.h" +#include "test_sleep.h" using namespace std; @@ -44,25 +49,60 @@ void PrintTo(const vector& value, ostream* os) { } // namespace std namespace { +void DeleteX509Stack(STACK_OF(X509)* stack) { + sk_X509_pop_free(stack, X509_free); +} + +constexpr size_t kTestSubsampleSectionSize = 256; + +} // namespace + +namespace wvoec { + int GetRandBytes(unsigned char* buf, int num) { // returns 1 on success, -1 if not supported, or 0 if other failure. return RAND_bytes(buf, num); } -void DeleteX509Stack(STACK_OF(X509)* stack) { - sk_X509_pop_free(stack, X509_free); +// Does the boilerplate to fill out sample and subsample descriptions for +// decrypting a single contiguous block of encrypted data to clear memory, which +// is a common operation for tests. Generates a random IV which can be used to +// encrypt the input buffer. +void GenerateSimpleSampleDescription( + const std::vector& in, std::vector& out, + OEMCrypto_SampleDescription* sample, + OEMCrypto_SubSampleDescription* subsample) { + ASSERT_NE(sample, nullptr); + ASSERT_NE(subsample, nullptr); + + // Generate test data + EXPECT_EQ(GetRandBytes(&sample->iv[0], KEY_IV_SIZE), 1); + + // Describe the test data + sample->buffers.input_data = in.data(); + sample->buffers.input_data_length = in.size(); + subsample->num_bytes_clear = 0; + subsample->num_bytes_encrypted = sample->buffers.input_data_length; + subsample->subsample_flags = + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample; + subsample->block_offset = 0; + sample->subsamples = subsample; + sample->subsamples_length = 1; + + // Describe the output + OEMCrypto_DestBufferDesc& out_buffer_descriptor = + sample->buffers.output_descriptor; + out_buffer_descriptor.type = OEMCrypto_BufferType_Clear; + out_buffer_descriptor.buffer.clear.address = out.data(); + out_buffer_descriptor.buffer.clear.address_length = out.size(); } -} // namespace - -namespace wvoec { - // Increment counter for AES-CTR. The CENC spec specifies we increment only // the low 64 bits of the IV counter, and leave the high 64 bits alone. This // is different from the BoringSSL implementation, so we implement the CTR loop // ourselves. void ctr128_inc64(int64_t increaseBy, uint8_t* iv) { - ASSERT_NE(static_cast(NULL), iv); + ASSERT_NE(nullptr, iv); uint64_t* counterBuffer = reinterpret_cast(&iv[8]); (*counterBuffer) = wvcdm::htonll64(wvcdm::ntohll64(*counterBuffer) + increaseBy); @@ -84,7 +124,7 @@ void dump_boringssl_error() { template class boringssl_ptr { public: - explicit boringssl_ptr(T* p = NULL) : ptr_(p) {} + explicit boringssl_ptr(T* p = nullptr) : ptr_(p) {} ~boringssl_ptr() { if (ptr_) func(ptr_); } @@ -98,43 +138,814 @@ class boringssl_ptr { CORE_DISALLOW_COPY_AND_ASSIGN(boringssl_ptr); }; -OEMCrypto_Substring GetSubstring(const std::string& message, - const std::string& field, bool set_zero) { +Test_PST_Report::Test_PST_Report(const std::string& pst_in, + OEMCrypto_Usage_Entry_Status status_in) + : status(status_in), pst(pst_in) { + time_created = wvcdm::Clock().GetCurrentTime(); +} + +template +void RoundTrip::SignAndVerifyRequest() { + // In the real world, a message should be signed by the client and + // verified by the server. This simulates that. + size_t gen_signature_length = 0; + size_t core_message_length = 0; + constexpr size_t small_size = 42; // arbitrary. + size_t message_size = + std::max(required_message_size_, core_message_length + small_size); + vector data(message_size, 0); + for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF; + ASSERT_EQ( + PrepAndSignRequest(session()->session_id(), data.data(), data.size(), + &core_message_length, nullptr, &gen_signature_length), + OEMCrypto_ERROR_SHORT_BUFFER); + // Make the message buffer a little bigger than the core message, or the + // required size, whichever is larger. + message_size = + std::max(required_message_size_, core_message_length + small_size); + data.resize(message_size); + for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF; + + vector gen_signature(gen_signature_length); + ASSERT_EQ(PrepAndSignRequest(session()->session_id(), data.data(), + data.size(), &core_message_length, + gen_signature.data(), &gen_signature_length), + OEMCrypto_SUCCESS); + if (global_features.api_version >= kCoreMessagesAPI) { + ASSERT_GT(data.size(), core_message_length); + std::string core_message(reinterpret_cast(data.data()), + core_message_length); + FillAndVerifyCoreRequest(core_message); + } + VerifyRequestSignature(data, gen_signature, core_message_length); +} + +template +OEMCrypto_Substring RoundTrip::FindSubstring(const void* pointer, + size_t length) { OEMCrypto_Substring substring; - if (set_zero || field.empty() || message.empty()) { + if (length == 0 || pointer == nullptr) { substring.offset = 0; substring.length = 0; } else { - size_t pos = message.find(field); - if (pos == std::string::npos) { - LOGW("GetSubstring : Cannot find offset for %s", field.c_str()); - substring.offset = 0; - substring.length = 0; - } else { - substring.offset = pos; - substring.length = field.length(); - } + substring.offset = reinterpret_cast(pointer) - + reinterpret_cast(&response_data_); + substring.length = length; } return substring; } +void ProvisioningRoundTrip::PrepareSession( + const wvoec::WidevineKeybox& keybox) { + ASSERT_NO_FATAL_FAILURE(session_->open()); + session_->GenerateNonce(); + if (global_features.provisioning_method == OEMCrypto_Keybox) { + session_->GenerateDerivedKeysFromKeybox(keybox); + encryptor_ = session_->key_deriver(); + } else { + EXPECT_EQ(global_features.provisioning_method, OEMCrypto_OEMCertificate); + session_->LoadOEMCert(true); + session_->GenerateRSASessionKey(&message_key_, &encrypted_message_key_); + encryptor_.set_enc_key(message_key_); + } +} + +void ProvisioningRoundTrip::VerifyRequestSignature( + const vector& data, const vector& generated_signature, + size_t core_message_length) { + if (global_features.provisioning_method == OEMCrypto_OEMCertificate) { + session()->VerifyRSASignature(data, generated_signature.data(), + generated_signature.size(), kSign_RSASSA_PSS); + } else { + EXPECT_EQ(global_features.provisioning_method, OEMCrypto_Keybox); + ASSERT_EQ(HMAC_SHA256_SIGNATURE_SIZE, generated_signature.size()); + std::vector expected_signature; + session()->key_deriver().ClientSignBuffer(data, &expected_signature); + ASSERT_EQ(expected_signature, generated_signature); + } +} + +void ProvisioningRoundTrip::FillAndVerifyCoreRequest( + const std::string& core_message_string) { + EXPECT_TRUE( + oemcrypto_core_message::deserialize::CoreProvisioningRequestFromMessage( + core_message_string, &core_request_)); + EXPECT_EQ(global_features.api_version, core_request_.api_major_version); + EXPECT_EQ(session()->nonce(), core_request_.nonce); + EXPECT_EQ(session()->session_id(), core_request_.session_id); + size_t device_id_length = core_request_.device_id.size(); + std::vector device_id(device_id_length); + EXPECT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GetDeviceID(device_id.data(), &device_id_length)); + EXPECT_EQ(core_request_.device_id.size(), device_id_length); + std::string device_id_string(reinterpret_cast(device_id.data()), + device_id_length); + EXPECT_EQ(device_id_string, core_request_.device_id); +} + +void ProvisioningRoundTrip::CreateDefaultResponse() { + if (allowed_schemes_ != kSign_RSASSA_PSS) { + uint32_t algorithm_n = htonl(allowed_schemes_); + memcpy(response_data_.rsa_key, "SIGN", 4); + memcpy(response_data_.rsa_key + 4, &algorithm_n, 4); + memcpy(response_data_.rsa_key + 8, encoded_rsa_key_.data(), + encoded_rsa_key_.size()); + response_data_.rsa_key_length = 8 + encoded_rsa_key_.size(); + } else { + memcpy(response_data_.rsa_key, encoded_rsa_key_.data(), + encoded_rsa_key_.size()); + response_data_.rsa_key_length = encoded_rsa_key_.size(); + } + response_data_.nonce = session_->nonce(); + if (encrypted_message_key_.size() > 0) { + ASSERT_LE(encrypted_message_key_.size(), kMaxTestRSAKeyLength); + memcpy(response_data_.enc_message_key, encrypted_message_key_.data(), + encrypted_message_key_.size()); + response_data_.enc_message_key_length = encrypted_message_key_.size(); + } else { + response_data_.enc_message_key_length = 0; + } + core_response_.key_type = OEMCrypto_RSA_Private_Key; + core_response_.enc_private_key = + FindSubstring(response_data_.rsa_key, response_data_.rsa_key_length); + core_response_.enc_private_key_iv = FindSubstring( + response_data_.rsa_key_iv, sizeof(response_data_.rsa_key_iv)); + core_response_.encrypted_message_key = FindSubstring( + response_data_.enc_message_key, response_data_.enc_message_key_length); +} + +void ProvisioningRoundTrip::EncryptAndSignResponse() { + encryptor_.PadAndEncryptProvisioningMessage(&response_data_, + &encrypted_response_data_); + core_response_.enc_private_key.length = + encrypted_response_data_.rsa_key_length; + if (global_features.api_version >= kCoreMessagesAPI) { + ASSERT_TRUE( + oemcrypto_core_message::serialize::CreateCoreProvisioningResponse( + core_response_, core_request_, &serialized_core_message_)); + } + // Make the message buffer a just big enough, or the + // required size, whichever is larger. + const size_t message_size = + std::max(required_message_size_, serialized_core_message_.size() + + sizeof(encrypted_response_data_)); + // Stripe the encrypted message. + encrypted_response_.resize(message_size); + for (size_t i = 0; i < encrypted_response_.size(); i++) { + encrypted_response_[i] = i & 0xFF; + } + ASSERT_GE(encrypted_response_.size(), serialized_core_message_.size()); + memcpy(encrypted_response_.data(), serialized_core_message_.data(), + serialized_core_message_.size()); + ASSERT_GE(encrypted_response_.size(), + serialized_core_message_.size() + sizeof(encrypted_response_data_)); + memcpy(encrypted_response_.data() + serialized_core_message_.size(), + reinterpret_cast(&encrypted_response_data_), + sizeof(encrypted_response_data_)); + if (global_features.provisioning_method == OEMCrypto_OEMCertificate) { + session()->GenerateDerivedKeysFromSessionKey(); + } + session()->key_deriver().ServerSignBuffer(encrypted_response_.data(), + encrypted_response_.size(), + &response_signature_); +} + +OEMCryptoResult ProvisioningRoundTrip::LoadResponse(Session* session) { + EXPECT_NE(session, nullptr); + size_t wrapped_key_length = 0; + const OEMCryptoResult sts = LoadResponseNoRetry(session, &wrapped_key_length); + if (sts != OEMCrypto_ERROR_SHORT_BUFFER) return sts; + wrapped_rsa_key_.clear(); + wrapped_rsa_key_.assign(wrapped_key_length, 0); + return LoadResponseNoRetry(session, &wrapped_key_length); +} + +#ifdef TEST_OEMCRYPTO_V15 +// If this platform supports v15 functions, then will test with them: +# define OEMCrypto_RewrapDeviceRSAKey_V15 OEMCrypto_RewrapDeviceRSAKey +# define OEMCrypto_RewrapDeviceRSAKey30_V15 OEMCrypto_RewrapDeviceRSAKey30 + +#else +// If this platform does not support v15 functions, we just need to stub these +// out so that the tests compile. +OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30_V15( + OEMCrypto_SESSION session, const uint32_t* unaligned_nonce, + const uint8_t* encrypted_message_key, size_t encrypted_message_key_length, + const uint8_t* enc_rsa_key, size_t enc_rsa_key_length, + const uint8_t* enc_rsa_key_iv, uint8_t* wrapped_rsa_key, + size_t* wrapped_rsa_key_length) { + LOGE("Support for v15 functions not included. Define TEST_OEMCRYPTO_V15."); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey_V15( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + const uint8_t* signature, size_t signature_length, + const uint32_t* unaligned_nonce, const uint8_t* enc_rsa_key, + size_t enc_rsa_key_length, const uint8_t* enc_rsa_key_iv, + uint8_t* wrapped_rsa_key, size_t* wrapped_rsa_key_length) { + LOGE("Support for v15 functions not included. Define TEST_OEMCRYPTO_V15."); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} +#endif + +template +const T* ProvisioningRoundTrip::RemapPointer(const T* response_pointer) const { + const uint8_t* original_pointer = + reinterpret_cast(response_pointer); + size_t delta = + original_pointer - reinterpret_cast(&response_data_); + // Base offset should be 0 if this is a v15 message, which is the only time + // this function is called. + size_t base_offset = serialized_core_message_.size(); + const uint8_t* new_pointer = encrypted_response_.data() + delta + base_offset; + return reinterpret_cast(new_pointer); +} + +OEMCryptoResult ProvisioningRoundTrip::LoadResponseNoRetry( + Session* session, size_t* wrapped_key_length) { + EXPECT_NE(session, nullptr); + if (global_features.api_version >= kCoreMessagesAPI) { + return OEMCrypto_LoadProvisioning( + session->session_id(), encrypted_response_.data(), + encrypted_response_.size(), serialized_core_message_.size(), + response_signature_.data(), response_signature_.size(), + wrapped_rsa_key_.data(), wrapped_key_length); + } else if (global_features.provisioning_method == OEMCrypto_Keybox) { + return OEMCrypto_RewrapDeviceRSAKey_V15( + session->session_id(), encrypted_response_.data(), + encrypted_response_.size(), response_signature_.data(), + response_signature_.size(), RemapPointer(&response_data_.nonce), + RemapPointer(response_data_.rsa_key), + encrypted_response_data_.rsa_key_length, + RemapPointer(response_data_.rsa_key_iv), wrapped_rsa_key_.data(), + wrapped_key_length); + } else { + return OEMCrypto_RewrapDeviceRSAKey30_V15( + session->session_id(), &encrypted_response_data_.nonce, + RemapPointer(response_data_.enc_message_key), + response_data_.enc_message_key_length, + RemapPointer(response_data_.rsa_key), + encrypted_response_data_.rsa_key_length, + RemapPointer(response_data_.rsa_key_iv), wrapped_rsa_key_.data(), + wrapped_key_length); + } +} + +void ProvisioningRoundTrip::VerifyLoadFailed() { + if (wrapped_rsa_key_.size() == 0) return; + std::vector zero(wrapped_rsa_key_.size(), 0); + ASSERT_EQ(zero, wrapped_rsa_key_); +} + +void LicenseRoundTrip::VerifyRequestSignature( + const vector& data, const vector& generated_signature, + size_t core_message_length) { + const std::vector subdata(data.begin() + core_message_length, + data.end()); + session()->VerifyRSASignature(subdata, generated_signature.data(), + generated_signature.size(), kSign_RSASSA_PSS); + SHA256(data.data(), core_message_length, request_hash_); + // If the api version was not set by the test, then we record the api version + // from the request. Also, if the api was set to be higher than oemcrypto + // supports, then we lower it. This version will be used in the response. + if (api_version_ == 0) api_version_ = core_request_.api_major_version; + if (api_version_ > global_features.api_version) + api_version_ = global_features.api_version; +} + +void LicenseRoundTrip::FillAndVerifyCoreRequest( + const std::string& core_message_string) { + EXPECT_TRUE( + oemcrypto_core_message::deserialize::CoreLicenseRequestFromMessage( + core_message_string, &core_request_)); + EXPECT_EQ(global_features.api_version, core_request_.api_major_version); + // If we are testing the latest OEMCrypto version, make sure it is built with + // the latest ODK version, too: + if (global_features.api_version == ODK_MAJOR_VERSION) { + EXPECT_EQ(ODK_MINOR_VERSION, core_request_.api_minor_version); + } + if (expect_request_has_correct_nonce_) { + EXPECT_EQ(session()->nonce(), core_request_.nonce); + } + EXPECT_EQ(session()->session_id(), core_request_.session_id); +} + +void LicenseRoundTrip::CreateDefaultResponse() { + EXPECT_EQ(1, GetRandBytes(response_data_.mac_key_iv, + sizeof(response_data_.mac_key_iv))); + memset(response_data_.padding, 0, sizeof(response_data_.padding)); + EXPECT_EQ(1, GetRandBytes(response_data_.mac_keys, + sizeof(response_data_.mac_keys))); + // For backwards compatibility, we use the largest limit in timer_limits for + // each key's duration. + uint32_t key_duration = static_cast( + std::max({core_response_.timer_limits.rental_duration_seconds, + core_response_.timer_limits.total_playback_duration_seconds, + core_response_.timer_limits.initial_renewal_duration_seconds})); + // The key data for an entitlement license is an AES-256 key, otherwise the + // default is an AES_128 key. + uint32_t default_key_size = + (license_type_ == OEMCrypto_EntitlementLicense) ? KEY_SIZE * 2 : KEY_SIZE; + for (unsigned int i = 0; i < num_keys_; i++) { + memset(response_data_.keys[i].key_id, 0, kTestKeyIdMaxLength); + response_data_.keys[i].key_id_length = kDefaultKeyIdLength; + memset(response_data_.keys[i].key_id, i, + response_data_.keys[i].key_id_length); + EXPECT_EQ(1, GetRandBytes(response_data_.keys[i].key_data, + sizeof(response_data_.keys[i].key_data))); + response_data_.keys[i].key_data_length = default_key_size; + EXPECT_EQ(1, GetRandBytes(response_data_.keys[i].key_iv, + sizeof(response_data_.keys[i].key_iv))); + EXPECT_EQ(1, GetRandBytes(response_data_.keys[i].control_iv, + sizeof(response_data_.keys[i].control_iv))); + std::string kcVersion = "kc" + std::to_string(api_version_); + memcpy(response_data_.keys[i].control.verification, kcVersion.c_str(), 4); + response_data_.keys[i].control.duration = htonl(key_duration); + response_data_.keys[i].control.nonce = htonl(session_->nonce()); + response_data_.keys[i].control.control_bits = htonl(control_); + response_data_.keys[i].cipher_mode = OEMCrypto_CipherMode_CTR; + } + // Fill in the default core_response_ fields, except the substrings, which are + // filled in the next function. + core_response_.nonce_required = + ((wvoec::kControlNonceEnabled | wvoec::kControlNonceOrEntry | + wvoec::kControlNonceRequired) & + control_) + ? 1 + : 0; + core_response_.license_type = license_type_; + FillCoreResponseSubstrings(); +} + +void LicenseRoundTrip::CreateResponseWithGenericCryptoKeys() { + CreateDefaultResponse(); + response_data_.keys[0].control.control_bits |= + htonl(wvoec::kControlAllowEncrypt); + response_data_.keys[1].control.control_bits |= + htonl(wvoec::kControlAllowDecrypt); + response_data_.keys[2].control.control_bits |= + htonl(wvoec::kControlAllowSign); + response_data_.keys[3].control.control_bits |= + htonl(wvoec::kControlAllowVerify); + response_data_.keys[2].key_data_length = wvoec::MAC_KEY_SIZE; + response_data_.keys[3].key_data_length = wvoec::MAC_KEY_SIZE; + FillCoreResponseSubstrings(); +} + +void LicenseRoundTrip::FillCoreResponseSubstrings() { + if (update_mac_keys_) { + core_response_.enc_mac_keys_iv = FindSubstring( + response_data_.mac_key_iv, sizeof(response_data_.mac_key_iv)); + core_response_.enc_mac_keys = + FindSubstring(response_data_.mac_keys, sizeof(response_data_.mac_keys)); + } + if (pst_.size() > 0) { + ASSERT_LE(pst_.size(), sizeof(response_data_.pst)); + memcpy(response_data_.pst, pst_.c_str(), + min(sizeof(response_data_.pst), pst_.length())); + core_response_.pst = FindSubstring(response_data_.pst, pst_.size()); + } + if (minimum_srm_version_ > 0) { + const std::string verification = "HDCPDATA"; + ASSERT_EQ(verification.size(), + sizeof(response_data_.srm_restriction_data.verification)); + memcpy(response_data_.srm_restriction_data.verification, + verification.c_str(), verification.size()); + response_data_.srm_restriction_data.minimum_srm_version = + htonl(minimum_srm_version_); + core_response_.srm_restriction_data = + FindSubstring(&response_data_.srm_restriction_data, + sizeof(response_data_.srm_restriction_data)); + } + core_response_.key_array_length = num_keys_; + for (unsigned int i = 0; i < num_keys_; i++) { + core_response_.key_array[i].key_id = FindSubstring( + response_data_.keys[i].key_id, response_data_.keys[i].key_id_length); + core_response_.key_array[i].key_data_iv = FindSubstring( + response_data_.keys[i].key_iv, sizeof(response_data_.keys[i].key_iv)); + core_response_.key_array[i].key_data = + FindSubstring(response_data_.keys[i].key_data, + response_data_.keys[i].key_data_length); + core_response_.key_array[i].key_control_iv = + FindSubstring(response_data_.keys[i].control_iv, + sizeof(response_data_.keys[i].control_iv)); + core_response_.key_array[i].key_control = + FindSubstring(&response_data_.keys[i].control, + sizeof(response_data_.keys[i].control)); + } +} + +void LicenseRoundTrip::EncryptAndSignResponse() { + ASSERT_NO_FATAL_FAILURE(session_->GenerateDerivedKeysFromSessionKey()); + encrypted_response_data_ = response_data_; + uint8_t iv_buffer[KEY_IV_SIZE]; + memcpy(iv_buffer, &response_data_.mac_key_iv[0], KEY_IV_SIZE); + session_->key_deriver().CBCEncrypt( + &response_data_.mac_keys[0], &encrypted_response_data_.mac_keys[0], + 2 * MAC_KEY_SIZE, response_data_.mac_key_iv); + + for (unsigned int i = 0; i < num_keys_; i++) { + memcpy(iv_buffer, &response_data_.keys[i].control_iv[0], KEY_IV_SIZE); + AES_KEY aes_key; + AES_set_encrypt_key(&response_data_.keys[i].key_data[0], 128, &aes_key); + AES_cbc_encrypt( + reinterpret_cast(&response_data_.keys[i].control), + reinterpret_cast(&encrypted_response_data_.keys[i].control), + KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT); + session_->key_deriver().CBCEncrypt( + &response_data_.keys[i].key_data[0], + &encrypted_response_data_.keys[i].key_data[0], + response_data_.keys[i].key_data_length, response_data_.keys[i].key_iv); + } + if (api_version_ < kCoreMessagesAPI) { + serialized_core_message_.resize(0); + } else { + std::string request_hash_string( + reinterpret_cast(request_hash_), sizeof(request_hash_)); + ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreLicenseResponse( + core_response_, core_request_, request_hash_string, + &serialized_core_message_)); + } + + // Make the message buffer a just big enough, or the + // required size, whichever is larger. + const size_t message_size = + std::max(required_message_size_, serialized_core_message_.size() + + sizeof(encrypted_response_data_)); + // Stripe the encrypted message. + encrypted_response_.resize(message_size); + for (size_t i = 0; i < encrypted_response_.size(); i++) { + encrypted_response_[i] = i % 0x100; + } + ASSERT_GE(kMaxCoreMessage, serialized_core_message_.size()); + ASSERT_GE(encrypted_response_.size(), serialized_core_message_.size()); + memcpy(encrypted_response_.data(), serialized_core_message_.data(), + serialized_core_message_.size()); + ASSERT_GE(encrypted_response_.size(), + serialized_core_message_.size() + sizeof(encrypted_response_data_)); + memcpy(encrypted_response_.data() + serialized_core_message_.size(), + reinterpret_cast(&encrypted_response_data_), + sizeof(encrypted_response_data_)); + if (global_features.provisioning_method == OEMCrypto_OEMCertificate) { + session()->GenerateDerivedKeysFromSessionKey(); + } + session()->key_deriver().ServerSignBuffer(encrypted_response_.data(), + encrypted_response_.size(), + &response_signature_); +} + +OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session) { + EXPECT_NE(session, nullptr); + // Some tests adjust the offset to be beyond the length of the message. Here, + // we create a duplicate of the main message buffer so that these offsets do + // not point to garbage data. The goal is to make sure OEMCrypto is verifying + // that the offset points outside of the message -- we don't want OEMCrypto to + // look at what offset points to and return an error if the data is + // garbage. Since the memory after the message buffer is an exact copy of the + // message, we can increment the offset by the message size and get valid + // data. + std::vector double_message = encrypted_response_; + double_message.insert( + double_message.end(), + reinterpret_cast(&encrypted_response_data_), + reinterpret_cast(&encrypted_response_data_) + + sizeof(encrypted_response_data_)); + OEMCryptoResult result; + if (api_version_ < kCoreMessagesAPI) { + result = OEMCrypto_LoadKeys( + session->session_id(), double_message.data(), + encrypted_response_.size(), response_signature_.data(), + response_signature_.size(), core_response_.enc_mac_keys_iv, + core_response_.enc_mac_keys, core_response_.key_array_length, + core_response_.key_array, core_response_.pst, + core_response_.srm_restriction_data, + static_cast(core_response_.license_type)); + } else { + result = OEMCrypto_LoadLicense( + session->session_id(), double_message.data(), + encrypted_response_.size(), serialized_core_message_.size(), + response_signature_.data(), response_signature_.size()); + } + if (result == OEMCrypto_SUCCESS) { + // Give the session object a copy of the license truth data so that it can + // call SelectKey, use key control information, and so that it has key data + // to verify decrypt operations. + session->set_license(response_data_); + // Also, if the license has new mac keys, then install them now. + if (core_response_.enc_mac_keys.length > 0) { + session->set_mac_keys(response_data_.mac_keys); + } + + // Note: we verify content licenses here. For entitlement license, we verify + // the key control blocks after loading entitled content keys. + if (license_type_ == OEMCrypto_ContentLicense) VerifyTestKeys(); + } + return result; +} + +OEMCryptoResult LicenseRoundTrip::ReloadResponse(Session* session) { + session->GenerateDerivedKeysFromSessionKey(); + return LoadResponse(session); +} + +// This function verifies that the key control block reported by OEMCrypto agree +// with the truth key control block. Failures in this function probably +// indicate the OEMCrypto_LoadLicense/LoadKeys did not correctly process the key +// control block. +void LicenseRoundTrip::VerifyTestKeys() { + for (unsigned int i = 0; i < num_keys_; i++) { + KeyControlBlock block; + size_t size = sizeof(block); + OEMCryptoResult sts = OEMCrypto_QueryKeyControl( + session_->session_id(), response_data_.keys[i].key_id, + response_data_.keys[i].key_id_length, + reinterpret_cast(&block), &size); + if (sts != OEMCrypto_ERROR_NOT_IMPLEMENTED) { + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(sizeof(block), size); + // Note: we do not assume that duration is stored with each key after v16. + // control bits stored in network byte order. For printing + // we change to host byte order. + ASSERT_EQ(htonl_fnc(response_data_.keys[i].control.control_bits), + htonl_fnc(block.control_bits)) + << "For key " << i; + } + } +} + +void LicenseRoundTrip::SetKeyId(size_t index, const string& key_id) { + ASSERT_LT(index, num_keys_); + MessageKeyData& key = response_data_.keys[index]; + key.key_id_length = key_id.length(); + ASSERT_LE(key.key_id_length, kTestKeyIdMaxLength); + memcpy(key.key_id, key_id.data(), key.key_id_length); +} + +void EntitledMessage::FillKeyArray() { + for (size_t i = 0; i < license_messages_->num_keys(); ++i) { + MakeOneKey(i); + } +} + +void EntitledMessage::MakeOneKey(size_t entitlement_key_index) { + ASSERT_LT(entitlement_key_index, kMaxNumKeys); + ASSERT_LT(num_keys_, kMaxNumKeys); + EntitledContentKeyData* key_data = &entitled_key_data_[num_keys_]; + MessageKeyData* entitlement_key = + &license_messages_->response_data().keys[entitlement_key_index]; + OEMCrypto_EntitledContentKeyObject* offsets = &entitled_key_array_[num_keys_]; + num_keys_++; + + key_data->key_index = entitlement_key_index; + ASSERT_LE(entitlement_key->key_id_length, kTestKeyIdMaxLength); + memcpy(key_data->entitlement_key_id, entitlement_key->key_id, + entitlement_key->key_id_length); + key_data->entitlement_key_id_length = entitlement_key->key_id_length; + offsets->entitlement_key_id = FindSubstring(key_data->entitlement_key_id, + entitlement_key->key_id_length); + + key_data->content_key_id_length = kDefaultKeyIdLength; + // Fill the key ID as CnCnCnCn... so it's easy to see in debug logs. + memset(key_data->content_key_id, 0xC0 + num_keys_, + key_data->content_key_id_length); + offsets->content_key_id = + FindSubstring(key_data->content_key_id, key_data->content_key_id_length); + + EXPECT_EQ(1, GetRandBytes(key_data->content_key_data, + sizeof(key_data->content_key_data))); + // Note: we give the encrypted content key to OEMCrypto, not the clear + // content key. + offsets->content_key_data = + FindSubstring(key_data->encrypted_content_key_data, + sizeof(key_data->encrypted_content_key_data)); + + EXPECT_EQ(1, GetRandBytes(key_data->content_key_data_iv, + sizeof(key_data->content_key_data_iv))); + offsets->content_key_data_iv = FindSubstring( + key_data->content_key_data_iv, sizeof(key_data->content_key_data_iv)); +} + +void EntitledMessage::SetEntitlementKeyId(unsigned int index, + const std::string& key_id) { + ASSERT_LT(index, num_keys_); + ASSERT_LE(key_id.size(), kTestKeyIdMaxLength); + entitled_key_data_[index].entitlement_key_id_length = key_id.size(); + memcpy(entitled_key_data_[index].entitlement_key_id, + reinterpret_cast(key_id.c_str()), key_id.length()); + entitled_key_array_[index].entitlement_key_id = FindSubstring( + entitled_key_data_[index].entitlement_key_id, key_id.length()); +} + +OEMCrypto_Substring EntitledMessage::FindSubstring(const void* ptr, + size_t size) { + OEMCrypto_Substring substring{0, 0}; + if (ptr != nullptr) { + substring.offset = reinterpret_cast(ptr) - + reinterpret_cast(entitled_key_data_); + substring.length = size; + } + return substring; +} + +void EntitledMessage::LoadKeys(OEMCryptoResult expected_sts) { + for (size_t i = 0; i < num_keys_; ++i) { + EntitledContentKeyData* key_data = &entitled_key_data_[i]; + const size_t entitlement_key_index = key_data->key_index; + MessageKeyData* entitlement_key = + &license_messages_->response_data().keys[entitlement_key_index]; + + // Load the entitlement key from |key_array_|. + AES_KEY aes_key; + AES_set_encrypt_key(entitlement_key->key_data, 256, &aes_key); + + // Encrypt the content key with the entitlement key. + uint8_t iv[16]; + memcpy(&iv[0], key_data->content_key_data_iv, KEY_IV_SIZE); + AES_cbc_encrypt(key_data->content_key_data, + key_data->encrypted_content_key_data, KEY_SIZE, &aes_key, + iv, AES_ENCRYPT); + } + ASSERT_EQ(expected_sts, + OEMCrypto_LoadEntitledContentKeys( + license_messages_->session()->session_id(), + reinterpret_cast(entitled_key_data_), + sizeof(entitled_key_data_), num_keys_, entitled_key_array_)); + if (expected_sts != OEMCrypto_SUCCESS) { + return; + } + VerifyEntitlementTestKeys(); +} + +// This function verifies that the key control block reported by OEMCrypto agree +// with the truth key control block. Failures in this function probably +// indicate the OEMCrypto_LoadEntitledKeys did not correctly process the key +// control block. +void EntitledMessage::VerifyEntitlementTestKeys() { + for (unsigned int i = 0; i < num_keys_; i++) { + EntitledContentKeyData* key_data = &entitled_key_data_[i]; + const size_t entitlement_key_index = key_data->key_index; + MessageKeyData* entitlement_key = + &license_messages_->response_data().keys[entitlement_key_index]; + KeyControlBlock block; + size_t size = sizeof(block); + OEMCryptoResult sts = OEMCrypto_QueryKeyControl( + license_messages_->session()->session_id(), key_data->content_key_id, + key_data->content_key_id_length, reinterpret_cast(&block), + &size); + if (sts != OEMCrypto_ERROR_NOT_IMPLEMENTED) { + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(sizeof(block), size); + // control duration and bits stored in network byte order. For printing + // we change to host byte order. + ASSERT_EQ((htonl_fnc(entitlement_key->control.duration)), + (htonl_fnc(block.duration))) + << "For key " << i; + ASSERT_EQ(htonl_fnc(entitlement_key->control.control_bits), + htonl_fnc(block.control_bits)) + << "For key " << i; + } + } +} + +void RenewalRoundTrip::VerifyRequestSignature( + const vector& data, const vector& generated_signature, + size_t core_message_length) { + ASSERT_EQ(HMAC_SHA256_SIGNATURE_SIZE, generated_signature.size()); + std::vector expected_signature; + if (license_messages_->api_version() < kCoreMessagesAPI) { + // For v15 or earlier, we only sign the message body. Ignore the core + // message. + std::vector subdata(data.begin() + core_message_length, + data.end()); + session()->key_deriver().ClientSignBuffer(subdata, &expected_signature); + } else { + session()->key_deriver().ClientSignBuffer(data, &expected_signature); + } + ASSERT_EQ(expected_signature, generated_signature); +} + +void RenewalRoundTrip::FillAndVerifyCoreRequest( + const std::string& core_message_string) { + if (license_messages_->api_version() < kCoreMessagesAPI) { + // For v15, we expect that no core request was created. + EXPECT_FALSE( + oemcrypto_core_message::deserialize::CoreRenewalRequestFromMessage( + core_message_string, &core_request_)); + } else { + EXPECT_TRUE( + oemcrypto_core_message::deserialize::CoreRenewalRequestFromMessage( + core_message_string, &core_request_)); + EXPECT_EQ(license_messages_->core_request().api_major_version, + core_request_.api_major_version); + if (!is_release_) { + // For a license release, we do not expect the nonce to be correct. That + // is because a release might be sent without loading the license first. + EXPECT_EQ(license_messages_->core_request().nonce, core_request_.nonce); + EXPECT_EQ(license_messages_->core_request().session_id, + core_request_.session_id); + } + } +} + +void RenewalRoundTrip::CreateDefaultResponse() { + if (license_messages_->api_version() < kCoreMessagesAPI) { + uint32_t control = 0; + uint32_t nonce = 0; + // If this is a v15 device, and a v15 license, and the license used a nonce, + // then the response should require a new nonce, too. + if (global_features.api_version < kCoreMessagesAPI && + (license_messages_->control() & wvoec::kControlNonceEnabled)) { + control = wvoec::kControlNonceEnabled; + session_->GenerateNonce(); + nonce = session_->nonce(); + } + // A single key object with no key id should update all keys. + constexpr size_t index = 0; + response_data_.keys[index].key_id_length = 0; + response_data_.keys[index].key_id[0] = '\0'; + std::string kcVersion = + "kc" + std::to_string(core_request_.api_major_version); + if (global_features.api_version < kCoreMessagesAPI) { + // For v15 or earlier devices, we use the api of the device. + kcVersion = "kc" + std::to_string(global_features.api_version); + } + memcpy(response_data_.keys[index].control.verification, kcVersion.c_str(), + 4); + const uint32_t duration = static_cast( + license_messages_->core_response() + .timer_limits.initial_renewal_duration_seconds); + response_data_.keys[index].control.duration = htonl(duration); + response_data_.keys[index].control.nonce = htonl(nonce); + response_data_.keys[index].control.control_bits = htonl(control); + } +} + +void RenewalRoundTrip::EncryptAndSignResponse() { + // Renewal messages are not encrypted. + encrypted_response_data_ = response_data_; + // Either create a KeyRefreshObject for a call to RefreshKeys or a core + // response for a call to LoadRenewal. + if (license_messages_->api_version() < kCoreMessagesAPI) { + refresh_object_.key_id = FindSubstring(nullptr, 0); + refresh_object_.key_control_iv = FindSubstring(nullptr, 0); + refresh_object_.key_control = + FindSubstring(&response_data_.keys[0].control, + sizeof(response_data_.keys[0].control)); + serialized_core_message_.resize(0); + } else { + ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreRenewalResponse( + core_request_, renewal_duration_seconds_, &serialized_core_message_)); + } + // Make the message buffer a just big enough, or the + // required size, whichever is larger. + const size_t message_size = + std::max(required_message_size_, serialized_core_message_.size() + + sizeof(encrypted_response_data_)); + // Stripe the encrypted message. + encrypted_response_.resize(message_size); + for (size_t i = 0; i < encrypted_response_.size(); i++) { + encrypted_response_[i] = i % 0x100; + } + // Concatenate the core message and the response. + ASSERT_GE(kMaxCoreMessage, serialized_core_message_.size()); + ASSERT_GE(encrypted_response_.size(), serialized_core_message_.size()); + memcpy(encrypted_response_.data(), serialized_core_message_.data(), + serialized_core_message_.size()); + ASSERT_GE(encrypted_response_.size(), + serialized_core_message_.size() + sizeof(encrypted_response_data_)); + memcpy(encrypted_response_.data() + serialized_core_message_.size(), + reinterpret_cast(&encrypted_response_data_), + sizeof(encrypted_response_data_)); + session()->key_deriver().ServerSignBuffer(encrypted_response_.data(), + encrypted_response_.size(), + &response_signature_); +} + +OEMCryptoResult RenewalRoundTrip::LoadResponse(Session* session) { + if (license_messages_->api_version() < kCoreMessagesAPI) { + return OEMCrypto_RefreshKeys( + session->session_id(), encrypted_response_.data(), + encrypted_response_.size(), response_signature_.data(), + response_signature_.size(), 1, &refresh_object_); + } else { + return OEMCrypto_LoadRenewal( + session->session_id(), encrypted_response_.data(), + encrypted_response_.size(), serialized_core_message_.size(), + response_signature_.data(), response_signature_.size()); + } +} + Session::Session() : open_(false), forced_session_id_(false), session_id_(0), - mac_key_server_(MAC_KEY_SIZE), - mac_key_client_(MAC_KEY_SIZE), - enc_key_(KEY_SIZE), - public_rsa_(0), - message_size_(sizeof(MessageData)), - // Most tests only use 4 keys. Other tests will explicitly call - // set_num_keys. - num_keys_(4) { - // Stripe the padded message. - for (size_t i = 0; i < sizeof(padded_message_.padding); i++) { - padded_message_.padding[i] = i % 0x100; - } -} + nonce_(0), + public_rsa_(0) {} Session::~Session() { if (!forced_session_id_ && open_) close(); @@ -171,7 +982,7 @@ void Session::GenerateNonce(int* error_counter) { if (error_counter) { (*error_counter)++; } else { - sleep(1); // wait a second, then try again. + wvcdm::TestSleep::Sleep(1); // wait a second, then try again. // The following is after a 1 second pause, so it cannot be from a nonce // flood. ASSERT_EQ(OEMCrypto_SUCCESS, @@ -200,62 +1011,10 @@ void Session::FillDefaultContext(vector* mac_context, "180120002a0c31383836373837343035000000000080"); } -// This generates the truth data for deriving one key. If there are failures in -// this function, then there is something wrong with the test program and its -// dependency on BoringSSL. -void Session::DeriveKey(const uint8_t* key, const vector& context, - int counter, vector* out) { - ASSERT_FALSE(context.empty()); - ASSERT_GE(4, counter); - ASSERT_NE(static_cast(NULL), out); - - const EVP_CIPHER* cipher = EVP_aes_128_cbc(); - CMAC_CTX* cmac_ctx = CMAC_CTX_new(); - ASSERT_NE(static_cast(NULL), cmac_ctx); - - ASSERT_EQ(1, CMAC_Init(cmac_ctx, key, KEY_SIZE, cipher, 0)); - - std::vector message; - message.push_back(counter); - message.insert(message.end(), context.begin(), context.end()); - - ASSERT_EQ(1, CMAC_Update(cmac_ctx, message.data(), message.size())); - - size_t reslen; - uint8_t res[128]; - ASSERT_EQ(1, CMAC_Final(cmac_ctx, res, &reslen)); - - out->assign(res, res + reslen); - CMAC_CTX_free(cmac_ctx); -} - -// This generates the truth data for deriving a set of keys. If there are -// failures in this function, then there is something wrong with the test -// program and its dependency on BoringSSL. -void Session::DeriveKeys(const uint8_t* master_key, - const vector& mac_key_context, - const vector& enc_key_context) { - // Generate derived key for mac key - std::vector mac_key_part2; - DeriveKey(master_key, mac_key_context, 1, &mac_key_server_); - DeriveKey(master_key, mac_key_context, 2, &mac_key_part2); - mac_key_server_.insert(mac_key_server_.end(), mac_key_part2.begin(), - mac_key_part2.end()); - - DeriveKey(master_key, mac_key_context, 3, &mac_key_client_); - DeriveKey(master_key, mac_key_context, 4, &mac_key_part2); - mac_key_client_.insert(mac_key_client_.end(), mac_key_part2.begin(), - mac_key_part2.end()); - - // Generate derived key for encryption key - DeriveKey(master_key, enc_key_context, 1, &enc_key_); -} - // This should only be called if the device uses Provisioning 2.0. A failure in // this function is probably caused by a bad keybox. void Session::GenerateDerivedKeysFromKeybox( const wvoec::WidevineKeybox& keybox) { - GenerateNonce(); vector mac_context; vector enc_context; FillDefaultContext(&mac_context, &enc_context); @@ -263,16 +1022,14 @@ void Session::GenerateDerivedKeysFromKeybox( OEMCrypto_GenerateDerivedKeys( session_id(), mac_context.data(), mac_context.size(), enc_context.data(), enc_context.size())); - - DeriveKeys(keybox.device_key_, mac_context, enc_context); + key_deriver_.DeriveKeys(keybox.device_key_, mac_context, enc_context); } void Session::GenerateDerivedKeysFromSessionKey() { // Uses test certificate. - GenerateNonce(); vector session_key; vector enc_session_key; - if (public_rsa_ == NULL) PreparePublicKey(); + if (public_rsa_ == nullptr) PreparePublicKey(); // A failure here probably indicates that there is something wrong with the // test program and its dependency on BoringSSL. ASSERT_TRUE(GenerateRSASessionKey(&session_key, &enc_session_key)); @@ -286,523 +1043,15 @@ void Session::GenerateDerivedKeysFromSessionKey() { mac_context.data(), mac_context.size(), enc_context.data(), enc_context.size())); - DeriveKeys(session_key.data(), mac_context, enc_context); -} - -void Session::LoadTestKeys(const std::string& provider_session_token, - bool new_mac_keys) { - std::string message = - wvcdm::BytesToString(message_ptr(), sizeof(MessageData)); - OEMCrypto_Substring pst = GetSubstring(message, provider_session_token); - OEMCrypto_Substring enc_mac_keys_iv = GetSubstring( - message, wvcdm::BytesToString(encrypted_license().mac_key_iv, - sizeof(encrypted_license().mac_key_iv))); - OEMCrypto_Substring enc_mac_keys = GetSubstring( - message, wvcdm::BytesToString(encrypted_license().mac_keys, - sizeof(encrypted_license().mac_keys))); - if (new_mac_keys) { - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys( - session_id(), message_ptr(), message_size_, signature_.data(), - signature_.size(), enc_mac_keys_iv, enc_mac_keys, num_keys_, - key_array_, pst, GetSubstring(), OEMCrypto_ContentLicense)); - // Update new generated keys. - memcpy(mac_key_server_.data(), license_.mac_keys, MAC_KEY_SIZE); - memcpy(mac_key_client_.data(), license_.mac_keys + MAC_KEY_SIZE, - MAC_KEY_SIZE); - } else { - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys( - session_id(), message_ptr(), message_size_, signature_.data(), - signature_.size(), GetSubstring(), GetSubstring(), num_keys_, - key_array_, pst, GetSubstring(), OEMCrypto_ContentLicense)); - } - VerifyTestKeys(); -} - -void Session::LoadEntitlementTestKeys(const std::string& provider_session_token, - bool new_mac_keys, - OEMCryptoResult expected_sts) { - std::string message = - wvcdm::BytesToString(message_ptr(), sizeof(MessageData)); - OEMCrypto_Substring pst = GetSubstring(message, provider_session_token); - OEMCrypto_Substring enc_mac_keys_iv = GetSubstring( - message, wvcdm::BytesToString(encrypted_license().mac_key_iv, - sizeof(encrypted_license().mac_key_iv))); - OEMCrypto_Substring enc_mac_keys = GetSubstring( - message, wvcdm::BytesToString(encrypted_license().mac_keys, - sizeof(encrypted_license().mac_keys))); - if (new_mac_keys) { - ASSERT_EQ( - expected_sts, - OEMCrypto_LoadKeys(session_id(), message_ptr(), message_size_, - signature_.data(), signature_.size(), - enc_mac_keys_iv, enc_mac_keys, num_keys_, - key_array_, pst, GetSubstring(), - OEMCrypto_EntitlementLicense)); - // Update new generated keys. - memcpy(mac_key_server_.data(), license_.mac_keys, MAC_KEY_SIZE); - memcpy(mac_key_client_.data(), license_.mac_keys + MAC_KEY_SIZE, - MAC_KEY_SIZE); - } else { - ASSERT_EQ( - expected_sts, - OEMCrypto_LoadKeys(session_id(), message_ptr(), message_size_, - signature_.data(), signature_.size(), GetSubstring(), - GetSubstring(), num_keys_, key_array_, pst, - GetSubstring(), OEMCrypto_EntitlementLicense)); - } -} - -void Session::FillEntitledKeyArray() { - int offset = 0; - entitled_message_.clear(); - for (size_t i = 0; i < num_keys_; ++i) { - EntitledContentKeyData* key_data = &entitled_key_data_[i]; - - entitled_key_array_[i].entitlement_key_id.offset = offset; - entitled_key_array_[i].entitlement_key_id.length = - key_array_[i].key_id.length; - offset += key_array_[i].key_id.length; - entitled_message_ += - wvcdm::BytesToString(message_ptr() + key_array_[i].key_id.offset, - key_array_[i].key_id.length); - - EXPECT_EQ(1, GetRandBytes(key_data->content_key_id, - sizeof(key_data->content_key_id))); - entitled_key_array_[i].content_key_id.offset = offset; - entitled_key_array_[i].content_key_id.length = - sizeof(key_data->content_key_id); - offset += sizeof(key_data->content_key_id); - entitled_message_ += wvcdm::BytesToString(key_data->content_key_id, - sizeof(key_data->content_key_id)); - - EXPECT_EQ(1, GetRandBytes(key_data->content_key_data, - sizeof(key_data->content_key_data))); - entitled_key_array_[i].content_key_data.offset = offset; - entitled_key_array_[i].content_key_data.length = - sizeof(key_data->content_key_data); - offset += sizeof(key_data->content_key_data); - entitled_message_ += wvcdm::BytesToString( - key_data->content_key_data, sizeof(key_data->content_key_data)); - - EXPECT_EQ(1, GetRandBytes(key_data[i].content_key_data_iv, - sizeof(key_data[i].content_key_data_iv))); - entitled_key_array_[i].content_key_data_iv.offset = offset; - entitled_key_array_[i].content_key_data_iv.length = - sizeof(key_data->content_key_data_iv); - offset += sizeof(key_data->content_key_data_iv); - entitled_message_ += wvcdm::BytesToString( - key_data->content_key_data_iv, sizeof(key_data->content_key_data_iv)); - } -} - -void Session::LoadEntitledContentKeys(OEMCryptoResult expected_sts) { - encrypted_entitled_message_ = entitled_message_; - std::vector encrypted_entitled_key_array( - entitled_key_array_, entitled_key_array_ + num_keys_); - - for (size_t i = 0; i < num_keys_; ++i) { - // Load the entitlement key from |key_array_|. - AES_KEY aes_key; - AES_set_encrypt_key(message_ptr() + key_array_[i].key_data.offset, 256, - &aes_key); - - // Encrypt the content key with the entitlement key. - uint8_t iv[16]; - const uint8_t* content_key_data = reinterpret_cast( - entitled_message_.data() + - entitled_key_array_[i].content_key_data.offset); - const uint8_t* encrypted_content_key_data = - reinterpret_cast( - encrypted_entitled_message_.data() + - encrypted_entitled_key_array[i].content_key_data.offset); - memcpy(&iv[0], - message_ptr() + - encrypted_entitled_key_array[i].content_key_data_iv.offset, - 16); - AES_cbc_encrypt(content_key_data, - const_cast(encrypted_content_key_data), - encrypted_entitled_key_array[i].content_key_data.length, - &aes_key, iv, AES_ENCRYPT); - } - ASSERT_EQ( - expected_sts, - OEMCrypto_LoadEntitledContentKeys( - session_id(), - reinterpret_cast(encrypted_entitled_message_.data()), - encrypted_entitled_message_.size(), num_keys_, - encrypted_entitled_key_array.data())); - if (expected_sts != OEMCrypto_SUCCESS) { - return; - } - VerifyEntitlementTestKeys(); -} - -// This function verifies that the key control block reported by OEMCrypto agree -// with the truth key control block. Failures in this function probably -// indicate the OEMCrypto_LoadKeys did not correctly process the key control -// block. -void Session::VerifyTestKeys() { - for (unsigned int i = 0; i < num_keys_; i++) { - KeyControlBlock block; - size_t size = sizeof(block); - OEMCryptoResult sts = OEMCrypto_QueryKeyControl( - session_id(), license_.keys[i].key_id, license_.keys[i].key_id_length, - reinterpret_cast(&block), &size); - if (sts != OEMCrypto_ERROR_NOT_IMPLEMENTED) { - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_EQ(sizeof(block), size); - // control duration and bits stored in network byte order. For printing - // we change to host byte order. - ASSERT_EQ((htonl_fnc(license_.keys[i].control.duration)), - (htonl_fnc(block.duration))) - << "For key " << i; - ASSERT_EQ(htonl_fnc(license_.keys[i].control.control_bits), - htonl_fnc(block.control_bits)) - << "For key " << i; - } - } -} - -// This function verifies that the key control block reported by OEMCrypto agree -// with the truth key control block. Failures in this function probably -// indicate the OEMCrypto_LoadEntitledKeys did not correctly process the key -// control block. -void Session::VerifyEntitlementTestKeys() { - for (unsigned int i = 0; i < num_keys_; i++) { - KeyControlBlock block; - size_t size = sizeof(block); - const uint8_t* content_key_id = - reinterpret_cast(entitled_message_.data()); - OEMCryptoResult sts = OEMCrypto_QueryKeyControl( - session_id(), - content_key_id + entitled_key_array_[i].content_key_id.offset, - entitled_key_array_[i].content_key_id.length, - reinterpret_cast(&block), &size); - if (sts != OEMCrypto_ERROR_NOT_IMPLEMENTED) { - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_EQ(sizeof(block), size); - // control duration and bits stored in network byte order. For printing - // we change to host byte order. - ASSERT_EQ((htonl_fnc(license_.keys[i].control.duration)), - (htonl_fnc(block.duration))) - << "For key " << i; - ASSERT_EQ(htonl_fnc(license_.keys[i].control.control_bits), - htonl_fnc(block.control_bits)) - << "For key " << i; - } - } -} - -void Session::RefreshTestKeys(const size_t key_count, uint32_t control_bits, - uint32_t nonce, OEMCryptoResult expected_result) { - // Note: we store the message in encrypted_license_, but the refresh key - // message is not actually encrypted. It is, however, signed. - // FillRefreshMessage fills the message with a duration of kLongDuration. - FillRefreshMessage(key_count, control_bits, nonce); - ServerSignBuffer(reinterpret_cast(&padded_message_), - message_size_, &signature_); - std::vector key_array(key_count); - FillRefreshArray(key_array.data(), key_count); - OEMCryptoResult sts = OEMCrypto_RefreshKeys( - session_id(), message_ptr(), message_size_, signature_.data(), - signature_.size(), key_count, key_array.data()); - ASSERT_EQ(expected_result, sts); - - ASSERT_NO_FATAL_FAILURE(TestDecryptCTR()); - // This should still be valid key, even if the refresh failed, because this - // is before the original license duration. - sleep(kShortSleep); - ASSERT_NO_FATAL_FAILURE(TestDecryptCTR(false)); - // This should be after duration of the original license, but before the - // expiration of the refresh message. This should succeed if and only if the - // refresh succeeded. - sleep(kShortSleep + kLongSleep); - if (expected_result == OEMCrypto_SUCCESS) { - ASSERT_NO_FATAL_FAILURE(TestDecryptCTR(false, OEMCrypto_SUCCESS)); - } else { - ASSERT_NO_FATAL_FAILURE( - TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); - } -} - -void Session::SetKeyId(int index, const string& key_id) { - MessageKeyData& key = license_.keys[index]; - key.key_id_length = key_id.length(); - ASSERT_LE(key.key_id_length, kTestKeyIdMaxLength); - memcpy(key.key_id, key_id.data(), key.key_id_length); -} - -void Session::FillSimpleMessage(uint32_t duration, uint32_t control, - uint32_t nonce, const std::string& pst) { - EXPECT_EQ( - 1, GetRandBytes(license_.mac_key_iv, sizeof(license_.mac_key_iv))); - memset(license_.padding, 0, sizeof(license_.padding)); - EXPECT_EQ(1, GetRandBytes(license_.mac_keys, sizeof(license_.mac_keys))); - for (unsigned int i = 0; i < num_keys_; i++) { - memset(license_.keys[i].key_id, 0, kTestKeyIdMaxLength); - license_.keys[i].key_id_length = kDefaultKeyIdLength; - memset(license_.keys[i].key_id, i, license_.keys[i].key_id_length); - EXPECT_EQ(1, GetRandBytes(license_.keys[i].key_data, - sizeof(license_.keys[i].key_data))); - license_.keys[i].key_data_length = KEY_SIZE; - EXPECT_EQ(1, GetRandBytes(license_.keys[i].key_iv, - sizeof(license_.keys[i].key_iv))); - EXPECT_EQ(1, GetRandBytes(license_.keys[i].control_iv, - sizeof(license_.keys[i].control_iv))); - if (global_features.api_version >= 12) { - // For version 12 and above, we require OEMCrypto to handle kcNN for all - // licenses. - std::string kcVersion = - "kc" + std::to_string(global_features.api_version); - memcpy(license_.keys[i].control.verification, kcVersion.c_str(), 4); - } else if (control & wvoec::kControlSecurityPatchLevelMask) { - // For versions before 12, we require the special key control block only - // when there are newer features present. - memcpy(license_.keys[i].control.verification, "kc11", 4); - } else if (control & wvoec::kControlRequireAntiRollbackHardware) { - memcpy(license_.keys[i].control.verification, "kc10", 4); - } else if (control & (wvoec::kControlHDCPVersionMask | - wvoec::kControlReplayMask)) { - memcpy(license_.keys[i].control.verification, "kc09", 4); - } else { - memcpy(license_.keys[i].control.verification, "kctl", 4); - } - license_.keys[i].control.duration = htonl(duration); - license_.keys[i].control.nonce = htonl(nonce); - license_.keys[i].control.control_bits = htonl(control); - license_.keys[i].cipher_mode = OEMCrypto_CipherMode_CTR; - } - memcpy(license_.pst, pst.c_str(), min(sizeof(license_.pst), pst.length())); - pst_ = pst; -} - -void Session::FillSimpleEntitlementMessage( - uint32_t duration, uint32_t control, uint32_t nonce, - const std::string& pst) { - EXPECT_EQ( - 1, GetRandBytes(license_.mac_key_iv, sizeof(license_.mac_key_iv))); - EXPECT_EQ(1, GetRandBytes(license_.mac_keys, sizeof(license_.mac_keys))); - for (unsigned int i = 0; i < num_keys_; i++) { - memset(license_.keys[i].key_id, 0, kTestKeyIdMaxLength); - license_.keys[i].key_id_length = kDefaultKeyIdLength; - memset(license_.keys[i].key_id, i, license_.keys[i].key_id_length); - EXPECT_EQ(1, GetRandBytes(license_.keys[i].key_data, - sizeof(license_.keys[i].key_data))); - license_.keys[i].key_data_length = KEY_SIZE * 2; // AES-256 keys - EXPECT_EQ(1, GetRandBytes(license_.keys[i].key_iv, - sizeof(license_.keys[i].key_iv))); - EXPECT_EQ(1, GetRandBytes(license_.keys[i].control_iv, - sizeof(license_.keys[i].control_iv))); - if (global_features.api_version >= 12) { - // For version 12 and above, we require OEMCrypto to handle kcNN for all - // licenses. - std::string kcVersion = - "kc" + std::to_string(global_features.api_version); - memcpy(license_.keys[i].control.verification, kcVersion.c_str(), 4); - } else if (control & wvoec::kControlSecurityPatchLevelMask) { - // For versions before 12, we require the special key control block only - // when there are newer features present. - memcpy(license_.keys[i].control.verification, "kc11", 4); - } else if (control & wvoec::kControlRequireAntiRollbackHardware) { - memcpy(license_.keys[i].control.verification, "kc10", 4); - } else if (control & (wvoec::kControlHDCPVersionMask | - wvoec::kControlReplayMask)) { - memcpy(license_.keys[i].control.verification, "kc09", 4); - } else { - memcpy(license_.keys[i].control.verification, "kctl", 4); - } - license_.keys[i].control.duration = htonl(duration); - license_.keys[i].control.nonce = htonl(nonce); - license_.keys[i].control.control_bits = htonl(control); - license_.keys[i].cipher_mode = OEMCrypto_CipherMode_CTR; - } - memcpy(license_.pst, pst.c_str(), min(sizeof(license_.pst), pst.length())); - pst_ = pst; -} - -void Session::FillRefreshMessage(size_t key_count, uint32_t control_bits, - uint32_t nonce) { - for (unsigned int i = 0; i < key_count; i++) { - encrypted_license().keys[i].key_id_length = license_.keys[i].key_id_length; - memcpy(encrypted_license().keys[i].key_id, license_.keys[i].key_id, - encrypted_license().keys[i].key_id_length); - if (global_features.api_version >= 12) { - // For version 12 and above, we require OEMCrypto to handle kcNN for all - // licenses. - std::string kcVersion = - "kc" + std::to_string(global_features.api_version); - memcpy(encrypted_license().keys[i].control.verification, - kcVersion.c_str(), 4); - } else { - // For versions before 12, we require the special key control block only - // when there are newer features present. - memcpy(encrypted_license().keys[i].control.verification, "kctl", 4); - } - encrypted_license().keys[i].control.duration = htonl(kLongDuration); - encrypted_license().keys[i].control.nonce = htonl(nonce); - encrypted_license().keys[i].control.control_bits = htonl(control_bits); - } -} - -void Session::SetLoadKeysSubstringParams() { - load_keys_params_.resize(4); - std::string message = - wvcdm::BytesToString(message_ptr(), sizeof(MessageData)); - OEMCrypto_Substring* enc_mac_keys_iv = load_keys_params_.data(); - *enc_mac_keys_iv = GetSubstring( - message, wvcdm::BytesToString(encrypted_license().mac_key_iv, - sizeof(encrypted_license().mac_key_iv))); - OEMCrypto_Substring* enc_mac_keys = &load_keys_params_[1]; - *enc_mac_keys = GetSubstring( - message, wvcdm::BytesToString(encrypted_license().mac_keys, - sizeof(encrypted_license().mac_keys))); - OEMCrypto_Substring* pst = &load_keys_params_[2]; - size_t pst_length = - strlen(reinterpret_cast(encrypted_license().pst)); - *pst = GetSubstring( - message, wvcdm::BytesToString(encrypted_license().pst, pst_length)); - OEMCrypto_Substring* srm_req = &load_keys_params_[3]; - *srm_req = GetSubstring(); -} - -void Session::EncryptAndSign() { - encrypted_license() = license_; - - uint8_t iv_buffer[16]; - memcpy(iv_buffer, &license_.mac_key_iv[0], KEY_IV_SIZE); - AES_KEY aes_key; - AES_set_encrypt_key(enc_key_.data(), 128, &aes_key); - AES_cbc_encrypt(&license_.mac_keys[0], &encrypted_license().mac_keys[0], - 2 * MAC_KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT); - - for (unsigned int i = 0; i < num_keys_; i++) { - memcpy(iv_buffer, &license_.keys[i].control_iv[0], KEY_IV_SIZE); - AES_set_encrypt_key(&license_.keys[i].key_data[0], 128, &aes_key); - AES_cbc_encrypt( - reinterpret_cast(&license_.keys[i].control), - reinterpret_cast(&encrypted_license().keys[i].control), - KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT); - - memcpy(iv_buffer, &license_.keys[i].key_iv[0], KEY_IV_SIZE); - AES_set_encrypt_key(enc_key_.data(), 128, &aes_key); - AES_cbc_encrypt( - &license_.keys[i].key_data[0], &encrypted_license().keys[i].key_data[0], - license_.keys[i].key_data_length, &aes_key, iv_buffer, AES_ENCRYPT); - } - memcpy(encrypted_license().pst, license_.pst, sizeof(license_.pst)); - ServerSignBuffer(reinterpret_cast(&padded_message_), - message_size_, &signature_); - FillKeyArray(encrypted_license(), key_array_); - SetLoadKeysSubstringParams(); -} - -void Session::EncryptProvisioningMessage( - RSAPrivateKeyMessage* data, RSAPrivateKeyMessage* encrypted, - const vector& encryption_key) { - ASSERT_EQ(encryption_key.size(), KEY_SIZE); - *encrypted = *data; - size_t padding = KEY_SIZE - (data->rsa_key_length % KEY_SIZE); - memset(data->rsa_key + data->rsa_key_length, static_cast(padding), - padding); - encrypted->rsa_key_length = data->rsa_key_length + padding; - uint8_t iv_buffer[16]; - memcpy(iv_buffer, &data->rsa_key_iv[0], KEY_IV_SIZE); - AES_KEY aes_key; - AES_set_encrypt_key(&encryption_key[0], 128, &aes_key); - AES_cbc_encrypt(&data->rsa_key[0], &encrypted->rsa_key[0], - encrypted->rsa_key_length, &aes_key, iv_buffer, AES_ENCRYPT); -} - -void Session::ServerSignBuffer(const uint8_t* data, size_t data_length, - std::vector* signature) { - ASSERT_LE(data_length, kMaxMessageSize); - signature->assign(SHA256_DIGEST_LENGTH, 0); - unsigned int md_len = SHA256_DIGEST_LENGTH; - HMAC(EVP_sha256(), mac_key_server_.data(), mac_key_server_.size(), data, - data_length, &(signature->front()), &md_len); -} - -void Session::ClientSignMessage(const vector& data, - std::vector* signature) { - signature->assign(SHA256_DIGEST_LENGTH, 0); - unsigned int md_len = SHA256_DIGEST_LENGTH; - HMAC(EVP_sha256(), mac_key_client_.data(), mac_key_client_.size(), - &(data.front()), data.size(), &(signature->front()), &md_len); -} - -void Session::VerifyClientSignature(size_t data_length) { - // In the real world, a message should be signed by the client and - // verified by the server. This simulates that. - vector data(data_length); - for (size_t i = 0; i < data.size(); i++) data[i] = i % 0xFF; - OEMCryptoResult sts; - size_t gen_signature_length = 0; - sts = OEMCrypto_GenerateSignature(session_id(), data.data(), data.size(), - NULL, &gen_signature_length); - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - ASSERT_EQ(static_cast(32), gen_signature_length); - vector gen_signature(gen_signature_length); - sts = OEMCrypto_GenerateSignature(session_id(), data.data(), data.size(), - gen_signature.data(), - &gen_signature_length); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - std::vector expected_signature; - ClientSignMessage(data, &expected_signature); - ASSERT_EQ(expected_signature, gen_signature); -} - -void Session::FillKeyArray(const MessageData& data, - OEMCrypto_KeyObject* key_array) { - const uint8_t* data_ptr = reinterpret_cast(&data); - std::string message = wvcdm::BytesToString(data_ptr, sizeof(MessageData)); - for (unsigned int i = 0; i < num_keys_; i++) { - key_array[i].key_id = GetSubstring( - message, - wvcdm::BytesToString(data.keys[i].key_id, data.keys[i].key_id_length)); - key_array[i].key_data_iv = GetSubstring( - message, - wvcdm::BytesToString(data.keys[i].key_iv, sizeof(data.keys[i].key_iv))); - key_array[i].key_data = GetSubstring( - message, wvcdm::BytesToString(data.keys[i].key_data, - data.keys[i].key_data_length)); - key_array[i].key_control_iv = GetSubstring( - message, wvcdm::BytesToString(data.keys[i].control_iv, - sizeof(data.keys[i].control_iv))); - const uint8_t* key_control_ptr = - reinterpret_cast(&data.keys[i].control); - key_array[i].key_control = GetSubstring( - message, - wvcdm::BytesToString(key_control_ptr, sizeof(data.keys[i].control))); - } -} - -void Session::FillRefreshArray(OEMCrypto_KeyRefreshObject* key_array, - size_t key_count) { - std::string message = - wvcdm::BytesToString(message_ptr(), sizeof(MessageData)); - for (size_t i = 0; i < key_count; i++) { - key_array[i].key_id = GetSubstring( - message, - wvcdm::BytesToString(encrypted_license().keys[i].key_id, - sizeof(encrypted_license().keys[i].key_id)), - key_count <= 1); - key_array[i].key_control_iv = GetSubstring(); - key_array[i].key_control = GetSubstring( - message, - wvcdm::BytesToString(reinterpret_cast( - &encrypted_license().keys[i].control), - sizeof(encrypted_license().keys[i].control))); - } + key_deriver_.DeriveKeys(session_key.data(), mac_context, enc_context); } void Session::EncryptCTR(const vector& in_buffer, const uint8_t* key, const uint8_t* starting_iv, vector* out_buffer) { - ASSERT_NE(static_cast(NULL), key); - ASSERT_NE(static_cast(NULL), starting_iv); - ASSERT_NE(static_cast(NULL), out_buffer); + ASSERT_NE(nullptr, key); + ASSERT_NE(nullptr, starting_iv); + ASSERT_NE(nullptr, out_buffer); AES_KEY aes_key; AES_set_encrypt_key(key, AES_BLOCK_SIZE * 8, &aes_key); out_buffer->resize(in_buffer.size()); @@ -823,68 +1072,78 @@ void Session::EncryptCTR(const vector& in_buffer, const uint8_t* key, void Session::TestDecryptCTR(bool select_key_first, OEMCryptoResult expected_result, int key_index) { - OEMCryptoResult sts; + OEMCryptoResult select_result = OEMCrypto_SUCCESS; if (select_key_first) { // Select the key (from FillSimpleMessage) - sts = OEMCrypto_SelectKey(session_id(), license_.keys[key_index].key_id, - license_.keys[key_index].key_id_length, - OEMCrypto_CipherMode_CTR); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); + select_result = OEMCrypto_SelectKey( + session_id(), license_.keys[key_index].key_id, + license_.keys[key_index].key_id_length, OEMCrypto_CipherMode_CTR); } - vector unencryptedData(256); - for (size_t i = 0; i < unencryptedData.size(); i++) - unencryptedData[i] = i % 256; - EXPECT_EQ(1, GetRandBytes(unencryptedData.data(), unencryptedData.size())); - vector encryptionIv(KEY_IV_SIZE); - EXPECT_EQ(1, GetRandBytes(encryptionIv.data(), KEY_IV_SIZE)); - vector encryptedData(unencryptedData.size()); - EncryptCTR(unencryptedData, license_.keys[key_index].key_data, - encryptionIv.data(), &encryptedData); + // Create test sample description + vector unencrypted_data(kTestSubsampleSectionSize); + vector encrypted_data(unencrypted_data.size()); + vector output_buffer(unencrypted_data.size()); + OEMCrypto_SampleDescription sample_description; + OEMCrypto_SubSampleDescription subsample_description; + + ASSERT_NO_FATAL_FAILURE(GenerateSimpleSampleDescription( + encrypted_data, output_buffer, &sample_description, + &subsample_description)); + + // Generate test data + EXPECT_EQ(GetRandBytes(unencrypted_data.data(), unencrypted_data.size()), 1); + EncryptCTR(unencrypted_data, license_.keys[key_index].key_data, + &sample_description.iv[0], &encrypted_data); + + // Create the pattern description (always 0,0 for CTR) + OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0}; - // Describe the output - vector outputBuffer(256); - OEMCrypto_DestBufferDesc destBuffer; - destBuffer.type = OEMCrypto_BufferType_Clear; - destBuffer.buffer.clear.address = outputBuffer.data(); - destBuffer.buffer.clear.max_length = outputBuffer.size(); - OEMCrypto_CENCEncryptPatternDesc pattern; - pattern.encrypt = 0; - pattern.skip = 0; - pattern.offset = 0; // Decrypt the data - sts = OEMCrypto_DecryptCENC( - session_id(), encryptedData.data(), encryptedData.size(), true, - encryptionIv.data(), 0, &destBuffer, &pattern, - OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + const OEMCryptoResult decrypt_result = + OEMCrypto_DecryptCENC(session_id(), &sample_description, 1, &pattern); // We only have a few errors that we test are reported. + ASSERT_NO_FATAL_FAILURE( + TestDecryptResult(expected_result, select_result, decrypt_result)) + << "Either SelectKey or DecryptCENC should return " << expected_result + << ", but they returned " << select_result << " and " << decrypt_result + << ", respectively."; if (expected_result == OEMCrypto_SUCCESS) { // No error. - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_EQ(unencryptedData, outputBuffer); + ASSERT_EQ(unencrypted_data, output_buffer); } else { - ASSERT_NO_FATAL_FAILURE(TestDecryptResult(expected_result, sts)); - ASSERT_NE(unencryptedData, outputBuffer); + ASSERT_NE(unencrypted_data, output_buffer); } } void Session::TestDecryptResult(OEMCryptoResult expected_result, - OEMCryptoResult actual_result) { - + OEMCryptoResult actual_select_result, + OEMCryptoResult actual_decrypt_result) { + // In most cases, we expect the result to come from either the select key or + // from the decrypt call. if (expected_result == OEMCrypto_SUCCESS) { // No error. - ASSERT_EQ(OEMCrypto_SUCCESS, actual_result); - } else if (expected_result == OEMCrypto_ERROR_KEY_EXPIRED && - global_features.api_version >= 9) { - // Report stale keys, required in v9 and beyond. - ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, actual_result); - } else if (expected_result == OEMCrypto_ERROR_INSUFFICIENT_HDCP) { - // Report HDCP errors. - ASSERT_EQ(OEMCrypto_ERROR_INSUFFICIENT_HDCP, actual_result); - } else if (expected_result == OEMCrypto_ERROR_ANALOG_OUTPUT) { - // Report analog errors. - ASSERT_EQ(OEMCrypto_ERROR_ANALOG_OUTPUT, actual_result); + ASSERT_EQ(OEMCrypto_SUCCESS, actual_select_result); + ASSERT_EQ(OEMCrypto_SUCCESS, actual_decrypt_result); + } else if (expected_result == OEMCrypto_ERROR_KEY_EXPIRED || + expected_result == OEMCrypto_ERROR_INSUFFICIENT_HDCP || + expected_result == OEMCrypto_ERROR_ANALOG_OUTPUT) { + // Key expired or output problems may be reported from select key or + // decrypt, but must be reported. + ASSERT_TRUE(actual_select_result == expected_result || + actual_decrypt_result == expected_result); + } else if (expected_result == OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION && + global_features.api_version >= kCoreMessagesAPI) { + // OEMCrypto is allowed to report either this warning or + // OEMCrypto_ERROR_INSUFFICIENT_HDCP depending on if it can disable + // restricted displays. + ASSERT_TRUE( + actual_select_result == OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION || + actual_select_result == OEMCrypto_ERROR_INSUFFICIENT_HDCP || + actual_decrypt_result == OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION || + actual_decrypt_result == OEMCrypto_ERROR_INSUFFICIENT_HDCP); } else { // OEM's can fine tune other error codes for debugging. - ASSERT_NE(OEMCrypto_SUCCESS, actual_result); + ASSERT_TRUE(actual_select_result != OEMCrypto_SUCCESS || + actual_decrypt_result != OEMCrypto_SUCCESS); } } @@ -907,13 +1166,12 @@ void Session::LoadOEMCert(bool verify_cert) { vector public_cert; size_t public_cert_length = 0; ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, - OEMCrypto_GetOEMPublicCertificate(session_id(), NULL, - &public_cert_length)); + OEMCrypto_GetOEMPublicCertificate(nullptr, &public_cert_length)); ASSERT_LT(0u, public_cert_length); public_cert.resize(public_cert_length); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_GetOEMPublicCertificate(session_id(), public_cert.data(), - &public_cert_length)); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_GetOEMPublicCertificate( + public_cert.data(), &public_cert_length)); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadOEMPrivateKey(session_id())); // Load the certificate chain into a BoringSSL X509 Stack const boringssl_ptr x509_stack( @@ -939,7 +1197,7 @@ void Session::LoadOEMCert(bool verify_cert) { if (!public_rsa_) { cout << "d2i_RSAPrivateKey failed.\n"; dump_boringssl_error(); - ASSERT_TRUE(NULL != public_rsa_); + ASSERT_TRUE(nullptr != public_rsa_); } } if (verify_cert) { @@ -954,7 +1212,7 @@ void Session::LoadOEMCert(bool verify_cert) { X509_STORE_CTX_new()); ASSERT_TRUE(store_ctx.NotNull()); - X509_STORE_CTX_init(store_ctx.get(), store.get(), x509_cert, NULL); + X509_STORE_CTX_init(store_ctx.get(), store.get(), x509_cert, nullptr); // TODO(fredgc): Verify cert is signed by Google. @@ -971,85 +1229,8 @@ void Session::LoadOEMCert(bool verify_cert) { } } -void Session::MakeRSACertificate(struct RSAPrivateKeyMessage* encrypted, - size_t message_size, - std::vector* signature, - uint32_t allowed_schemes, - const vector& rsa_key, - const vector* encryption_key) { - if (encryption_key == NULL) encryption_key = &enc_key_; - struct RSAPrivateKeyMessage message; - if (allowed_schemes != kSign_RSASSA_PSS) { - uint32_t algorithm_n = htonl(allowed_schemes); - memcpy(message.rsa_key, "SIGN", 4); - memcpy(message.rsa_key + 4, &algorithm_n, 4); - memcpy(message.rsa_key + 8, rsa_key.data(), rsa_key.size()); - message.rsa_key_length = 8 + rsa_key.size(); - } else { - memcpy(message.rsa_key, rsa_key.data(), rsa_key.size()); - message.rsa_key_length = rsa_key.size(); - } - EXPECT_EQ(1, GetRandBytes(message.rsa_key_iv, KEY_IV_SIZE)); - message.nonce = nonce_; - - EncryptProvisioningMessage(&message, encrypted, *encryption_key); - ServerSignBuffer(reinterpret_cast(encrypted), message_size, - signature); -} - -void Session::RewrapRSAKey(const struct RSAPrivateKeyMessage& encrypted, - size_t message_size, - const std::vector& signature, - vector* wrapped_key, bool force) { - size_t wrapped_key_length = 0; - const uint8_t* message_ptr = reinterpret_cast(&encrypted); - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, - OEMCrypto_RewrapDeviceRSAKey( - session_id(), message_ptr, message_size, signature.data(), - signature.size(), &encrypted.nonce, encrypted.rsa_key, - encrypted.rsa_key_length, encrypted.rsa_key_iv, NULL, - &wrapped_key_length)); - wrapped_key->clear(); - wrapped_key->assign(wrapped_key_length, 0); - OEMCryptoResult sts = OEMCrypto_RewrapDeviceRSAKey( - session_id(), message_ptr, message_size, signature.data(), - signature.size(), &encrypted.nonce, encrypted.rsa_key, - encrypted.rsa_key_length, encrypted.rsa_key_iv, &(wrapped_key->front()), - &wrapped_key_length); - if (force) { - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - } - if (OEMCrypto_SUCCESS != sts) { - wrapped_key->clear(); - } -} - -void Session::RewrapRSAKey30(const struct RSAPrivateKeyMessage& encrypted, - const std::vector& encrypted_message_key, - vector* wrapped_key, bool force) { - size_t wrapped_key_length = 0; - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, - OEMCrypto_RewrapDeviceRSAKey30( - session_id(), &nonce_, encrypted_message_key.data(), - encrypted_message_key.size(), encrypted.rsa_key, - encrypted.rsa_key_length, encrypted.rsa_key_iv, NULL, - &wrapped_key_length)); - wrapped_key->clear(); - wrapped_key->assign(wrapped_key_length, 0); - OEMCryptoResult sts = OEMCrypto_RewrapDeviceRSAKey30( - session_id(), &nonce_, encrypted_message_key.data(), - encrypted_message_key.size(), encrypted.rsa_key, encrypted.rsa_key_length, - encrypted.rsa_key_iv, &(wrapped_key->front()), &wrapped_key_length); - if (force) { - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - } - if (OEMCrypto_SUCCESS != sts) { - wrapped_key->clear(); - } -} - void Session::PreparePublicKey(const uint8_t* rsa_key, size_t rsa_key_length) { - if (rsa_key == NULL) { + if (rsa_key == nullptr) { rsa_key = kTestRSAPKCS8PrivateKeyInfo2_2048; rsa_key_length = sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048); } @@ -1057,7 +1238,7 @@ void Session::PreparePublicKey(const uint8_t* rsa_key, size_t rsa_key_length) { boringssl_ptr bio(BIO_new_mem_buf(p, rsa_key_length)); ASSERT_TRUE(bio.NotNull()); boringssl_ptr pkcs8_pki( - d2i_PKCS8_PRIV_KEY_INFO_bio(bio.get(), NULL)); + d2i_PKCS8_PRIV_KEY_INFO_bio(bio.get(), nullptr)); ASSERT_TRUE(pkcs8_pki.NotNull()); boringssl_ptr evp(EVP_PKCS82PKEY(pkcs8_pki.get())); ASSERT_TRUE(evp.NotNull()); @@ -1087,10 +1268,10 @@ bool Session::VerifyPSSSignature(EVP_PKEY* pkey, const uint8_t* message, EVP_MD_CTX md_ctx_struct; EVP_MD_CTX* md_ctx = &md_ctx_struct; EVP_MD_CTX_init(md_ctx); - EVP_PKEY_CTX* pkey_ctx = NULL; + EVP_PKEY_CTX* pkey_ctx = nullptr; - if (EVP_DigestVerifyInit(md_ctx, &pkey_ctx, EVP_sha1(), NULL /* no ENGINE */, - pkey) != 1) { + if (EVP_DigestVerifyInit(md_ctx, &pkey_ctx, EVP_sha1(), + nullptr /* no ENGINE */, pkey) != 1) { LOGE("EVP_DigestVerifyInit failed in VerifyPSSSignature"); goto err; } @@ -1137,7 +1318,7 @@ void Session::VerifyRSASignature(const vector& message, const uint8_t* signature, size_t signature_length, RSA_Padding_Scheme padding_scheme) { - EXPECT_TRUE(NULL != public_rsa_) + EXPECT_TRUE(nullptr != public_rsa_) << "No public RSA key loaded in test code.\n"; EXPECT_EQ(static_cast(RSA_size(public_rsa_)), signature_length) @@ -1192,7 +1373,6 @@ void Session::InstallRSASessionTestKey(const vector& wrapped_rsa_key) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadDeviceRSAKey(session_id(), wrapped_rsa_key.data(), wrapped_rsa_key.size())); - GenerateDerivedKeysFromSessionKey(); } void Session::CreateNewUsageEntry(OEMCryptoResult* status) { @@ -1210,8 +1390,8 @@ void Session::UpdateUsageEntry(std::vector* header_buffer) { size_t entry_buffer_length = 0; ASSERT_EQ( OEMCrypto_ERROR_SHORT_BUFFER, - OEMCrypto_UpdateUsageEntry(session_id(), NULL, &header_buffer_length, - NULL, &entry_buffer_length)); + OEMCrypto_UpdateUsageEntry(session_id(), nullptr, &header_buffer_length, + nullptr, &entry_buffer_length)); ASSERT_LT(0u, header_buffer_length); header_buffer->resize(header_buffer_length); ASSERT_LT(0u, entry_buffer_length); @@ -1257,8 +1437,7 @@ void Session::GenerateReport(const std::string& pst, Session* other) { ASSERT_TRUE(open_); if (other) { // If other is specified, copy mac keys. - mac_key_server_ = other->mac_key_server_; - mac_key_client_ = other->mac_key_client_; + key_deriver_ = other->key_deriver_; } size_t length = 0; OEMCryptoResult sts = OEMCrypto_ReportUsage( @@ -1279,19 +1458,13 @@ void Session::GenerateReport(const std::string& pst, } EXPECT_EQ(wvcdm::Unpacked_PST_Report::report_size(pst.length()), length); vector computed_signature(SHA_DIGEST_LENGTH); - unsigned int sig_len = SHA_DIGEST_LENGTH; - HMAC(EVP_sha1(), mac_key_client_.data(), mac_key_client_.size(), - &pst_report_buffer_[SHA_DIGEST_LENGTH], length - SHA_DIGEST_LENGTH, - computed_signature.data(), &sig_len); + key_deriver_.ClientSignPstReport(pst_report_buffer_, &computed_signature); EXPECT_EQ(0, memcmp(computed_signature.data(), pst_report().signature(), SHA_DIGEST_LENGTH)); EXPECT_GE(kInactiveUnused, pst_report().status()); EXPECT_GE(kHardwareSecureClock, pst_report().clock_security_level()); EXPECT_EQ(pst.length(), pst_report().pst_length()); EXPECT_EQ(0, memcmp(pst.c_str(), pst_report().pst(), pst.length())); - // Also, we the session to be able to sign the release message with the - // correct mac keys from the usage table entry. - ASSERT_NO_FATAL_FAILURE(VerifyClientSignature()); } void Session::VerifyPST(const Test_PST_Report& expected) { @@ -1300,7 +1473,7 @@ void Session::VerifyPST(const Test_PST_Report& expected) { char* pst_ptr = reinterpret_cast(computed.pst()); std::string computed_pst(pst_ptr, pst_ptr + computed.pst_length()); ASSERT_EQ(expected.pst, computed_pst); - time_t now = time(NULL); + int64_t now = wvcdm::Clock().GetCurrentTime(); int64_t age = now - expected.time_created; // How old is this report. EXPECT_NEAR(expected.seconds_since_license_received + age, computed.seconds_since_license_received(), @@ -1315,94 +1488,25 @@ void Session::VerifyPST(const Test_PST_Report& expected) { kUsageTableTimeTolerance); } std::vector signature(SHA_DIGEST_LENGTH); - unsigned int md_len = SHA_DIGEST_LENGTH; - if (!HMAC(EVP_sha1(), mac_key_client_.data(), mac_key_client_.size(), - pst_report_buffer_.data() + SHA_DIGEST_LENGTH, - pst_report_buffer_.size() - SHA_DIGEST_LENGTH, - signature.data(), &md_len)) { - cout << "Error computing HMAC.\n"; - dump_boringssl_error(); - } + key_deriver_.ClientSignPstReport(pst_report_buffer_, &signature); EXPECT_EQ(0, memcmp(computed.signature(), signature.data(), SHA_DIGEST_LENGTH)); } -// This might adjust t to be "seconds since now". If t is small, we assume it -// is "seconds since now", but if the value of t is large, assume it is -// "absolute time" and convert to "seconds since now". -static int64_t MaybeAdjustTime(int64_t t, time_t now) { - int64_t k10Minutes = 60 * 10; // in seconds. - if (t > k10Minutes) return now - t; - return t; -} - void Session::VerifyReport(Test_PST_Report expected, int64_t time_license_received, int64_t time_first_decrypt, int64_t time_last_decrypt) { - time_t now = time(NULL); - expected.seconds_since_license_received = - MaybeAdjustTime(time_license_received, now); + const int64_t now = wvcdm::Clock().GetCurrentTime(); + expected.seconds_since_license_received = now - time_license_received; expected.seconds_since_first_decrypt = - MaybeAdjustTime(time_first_decrypt, now); - expected.seconds_since_last_decrypt = MaybeAdjustTime(time_last_decrypt, now); + (time_first_decrypt > 0 && time_first_decrypt < now) + ? now - time_first_decrypt + : 0; + expected.seconds_since_last_decrypt = + (time_last_decrypt > 0 && time_last_decrypt < now) + ? now - time_last_decrypt + : 0; ASSERT_NO_FATAL_FAILURE(VerifyPST(expected)); } - -void Session::GenerateVerifyReport(const std::string& pst, - OEMCrypto_Usage_Entry_Status status, - int64_t time_license_received, - int64_t time_first_decrypt, - int64_t time_last_decrypt) { - ASSERT_NO_FATAL_FAILURE(GenerateReport(pst)); - Test_PST_Report expected(pst, status); - ASSERT_NO_FATAL_FAILURE(VerifyReport(expected, time_license_received, - time_first_decrypt, time_last_decrypt)); - // The PST report was signed above. Below we verify that the entire message - // that is sent to the server will be signed by the right mac keys. - ASSERT_NO_FATAL_FAILURE(VerifyClientSignature()); -} - -void Session::CreateOldEntry(const Test_PST_Report& report) { - OEMCryptoResult result = OEMCrypto_CreateOldUsageEntry( - report.seconds_since_license_received, - report.seconds_since_first_decrypt, - report.seconds_since_last_decrypt, - report.status, mac_key_server_.data(), - mac_key_client_.data(), - reinterpret_cast(report.pst.c_str()), - report.pst.length()); - if (result == OEMCrypto_ERROR_NOT_IMPLEMENTED) return; - ASSERT_EQ(OEMCrypto_SUCCESS, result); -} - -void Session::CopyAndVerifyOldEntry(const Test_PST_Report& report, - std::vector* header_buffer) { - ASSERT_NO_FATAL_FAILURE(CreateNewUsageEntry()); - OEMCryptoResult result = OEMCrypto_CopyOldUsageEntry( - session_id(), reinterpret_cast(report.pst.c_str()), - report.pst.length()); - if (result == OEMCrypto_ERROR_NOT_IMPLEMENTED) { - cout << "WARNING: OEMCrypto CANNOT copy old usage table to new." << endl; - return; - } - ASSERT_NO_FATAL_FAILURE(UpdateUsageEntry(header_buffer)); - ASSERT_NO_FATAL_FAILURE(GenerateReport(report.pst)); - ASSERT_NO_FATAL_FAILURE(VerifyPST(report)); -} - -const uint8_t* Session::message_ptr() { - return reinterpret_cast(&encrypted_license()); -} - -void Session::set_message_size(size_t size) { - message_size_ = size; - ASSERT_GE(message_size_, sizeof(MessageData)); - ASSERT_LE(message_size_, kMaxMessageSize); -} - -const uint8_t* Session::encrypted_entitled_message_ptr() { - return reinterpret_cast(encrypted_entitled_message_.data()); -} - } // namespace wvoec diff --git a/libwvdrmengine/oemcrypto/test/oec_session_util.h b/libwvdrmengine/oemcrypto/test/oec_session_util.h index f1627601..e3fa6967 100644 --- a/libwvdrmengine/oemcrypto/test/oec_session_util.h +++ b/libwvdrmengine/oemcrypto/test/oec_session_util.h @@ -7,12 +7,17 @@ // // OEMCrypto unit tests // +#include #include #include #include #include +#include "core_message_deserialize.h" +#include "core_message_serialize.h" +#include "odk.h" #include "oec_device_features.h" +#include "oec_key_deriver.h" #include "oemcrypto_types.h" #include "pst_report.h" @@ -29,21 +34,21 @@ void PrintTo(const vector& value, ostream* os); namespace wvoec { // Make sure this is larger than kMaxKeysPerSession, in oemcrypto_test.cpp -const size_t kMaxNumKeys = 20; +constexpr size_t kMaxNumKeys = 30; namespace { #if defined(TEST_SPEED_MULTIPLIER) // Can slow test time limits when // debugging is slowing everything. -const int kSpeedMultiplier = TEST_SPEED_MULTIPLIER; +constexpr int kSpeedMultiplier = TEST_SPEED_MULTIPLIER; #else -const int kSpeedMultiplier = 1; +constexpr int kSpeedMultiplier = 1; #endif -const int kShortSleep = 1 * kSpeedMultiplier; -const int kLongSleep = 2 * kSpeedMultiplier; -const uint32_t kDuration = 2 * kSpeedMultiplier; -const uint32_t kLongDuration = 5 * kSpeedMultiplier; -const int32_t kTimeTolerance = 3 * kSpeedMultiplier; -const time_t kUsageTableTimeTolerance = 10 * kSpeedMultiplier; +constexpr int kShortSleep = 1 * kSpeedMultiplier; +constexpr int kLongSleep = 2 * kSpeedMultiplier; +constexpr uint32_t kDuration = 2 * kSpeedMultiplier; +constexpr uint32_t kLongDuration = 5 * kSpeedMultiplier; +constexpr int32_t kTimeTolerance = 3 * kSpeedMultiplier; +constexpr int64_t kUsageTableTimeTolerance = 10 * kSpeedMultiplier; } // namespace typedef struct { @@ -55,14 +60,12 @@ typedef struct { // Note: The API does not specify a maximum key id length. We specify a // maximum just for these tests, so that we have a fixed message size. -const size_t kTestKeyIdMaxLength = 16; +constexpr size_t kTestKeyIdMaxLength = 16; // Most content will use a key id that is 16 bytes long. -const int kDefaultKeyIdLength = 16; - -const size_t kMaxTestRSAKeyLength = 2000; // Rough estimate. -const size_t kMaxPSTLength = 255; // In specification. -const size_t kMaxMessageSize = 8 * 1024; // In specification. +constexpr int kDefaultKeyIdLength = 16; +constexpr size_t kMaxPSTLength = 255; // In specification. +constexpr size_t kMaxCoreMessage = 200 * kMaxNumKeys + 200; // Rough estimate. typedef struct { uint8_t key_id[kTestKeyIdMaxLength]; @@ -84,37 +87,40 @@ struct MessageData { uint8_t padding[KEY_IV_SIZE]; uint8_t mac_keys[2 * MAC_KEY_SIZE]; uint8_t pst[kMaxPSTLength]; -}; - -// This structure will be signed to simulate a provisioning response from the -// server. -struct RSAPrivateKeyMessage { - uint8_t rsa_key[kMaxTestRSAKeyLength]; - uint8_t rsa_key_iv[KEY_IV_SIZE]; - size_t rsa_key_length; - uint32_t nonce; + SRM_Restriction_Data srm_restriction_data; }; struct Test_PST_Report { Test_PST_Report(const std::string& pst_in, - OEMCrypto_Usage_Entry_Status status_in) - : status(status_in), pst(pst_in), time_created(time(NULL)) {} + OEMCrypto_Usage_Entry_Status status_in); OEMCrypto_Usage_Entry_Status status; int64_t seconds_since_license_received; int64_t seconds_since_first_decrypt; int64_t seconds_since_last_decrypt; std::string pst; - time_t time_created; + int64_t time_created; }; struct EntitledContentKeyData { - uint8_t entitlement_key_id[KEY_SIZE]; - uint8_t content_key_id[KEY_SIZE]; - uint8_t content_key_data_iv[KEY_SIZE]; + uint8_t entitlement_key_id[kTestKeyIdMaxLength]; + size_t entitlement_key_id_length; + uint8_t content_key_id[kTestKeyIdMaxLength]; + size_t content_key_id_length; + uint8_t content_key_data_iv[KEY_IV_SIZE]; uint8_t content_key_data[KEY_SIZE]; + uint8_t encrypted_content_key_data[KEY_SIZE]; + size_t key_index; // Index into the license's key array. Only for testing. }; +// returns 1 on success, -1 if not supported, or 0 if other failure. +int GetRandBytes(unsigned char* buf, int num); + +void GenerateSimpleSampleDescription(const std::vector& in, + std::vector& out, + OEMCrypto_SampleDescription* sample, + OEMCrypto_SubSampleDescription* subsample); + // Increment counter for AES-CTR. The CENC spec specifies we increment only // the low 64 bits of the IV counter, and leave the high 64 bits alone. This // is different from the OpenSSL implementation, so we implement the CTR loop @@ -127,12 +133,307 @@ uint32_t htonl_fnc(uint32_t x); // Prints error string from BoringSSL void dump_boringssl_error(); -// Given a message and field, returns an OEMCrypto_Substring with the field's -// offset into the message and its length. If |set_zero| is true, both the -// offset and length will be zero. -OEMCrypto_Substring GetSubstring(const std::string& message = "", - const std::string& field = "", - bool set_zero = false); +class Session; +// The prototype of the OEMCrypto function to prepare and sign a request. +typedef OEMCryptoResult (*PrepAndSignRequest_t)( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_length, uint8_t* signature, size_t* signature_length); + +// A RoundTrip helps generate and verify a request message, helps generate the +// corresponding response, and then helps verify loading the response. +template +class RoundTrip { + public: + RoundTrip() = delete; + RoundTrip(Session* session) + : session_(session), + core_request_(), + core_response_(), + response_data_(), + encrypted_response_data_(), + required_message_size_(0) {} + virtual ~RoundTrip() {} + + // Have OEMCrypto sign a request message and then verify the signature and the + // core message. + virtual void SignAndVerifyRequest(); + // Create a default |response_data| and |core_response|. + virtual void CreateDefaultResponse() = 0; + // Copy fields from |response_data| to |padded_response_data|, encrypting + // those that should be encrypted. Serialize the core message. Then sign the + // response. + virtual void EncryptAndSignResponse() = 0; + // Attempt to load the response and return the error. Short buffer errors are + // handled by LoadResponse, not the caller. All other errors should be + // handled by the caller. + virtual OEMCryptoResult LoadResponse() { return LoadResponse(session_); } + // As with LoadResponse, but load into a different session. + virtual OEMCryptoResult LoadResponse(Session* session) = 0; + + // Accessors are all read/write because tests modify default values. + Session* session() { return session_; } + void set_session(Session* session) { session_ = session; } + CoreRequest& core_request() { return core_request_; } + CoreResponse& core_response() { return core_response_; } + ResponseData& response_data() { return response_data_; } + ResponseData& encrypted_response_data() { return encrypted_response_data_; } + std::vector& encrypted_response_buffer() { + return encrypted_response_; + } + + // Set the size of the buffer used the encrypted license. + void set_message_size(size_t size) { required_message_size_ = size; } + std::vector& response_signature() { return response_signature_; } + const std::string& serialized_core_message() const { + return serialized_core_message_; + } + + protected: + // ---------------------------------------------------------------------- + // Specialized functionality for each message type. + + // Verify the signature of the request. + virtual void VerifyRequestSignature( + const vector& data, const vector& generated_signature, + size_t core_message_length) = 0; + // Verify the values of the core response. + virtual void FillAndVerifyCoreRequest( + const std::string& core_message_string) = 0; + // Find the given pointer in the response_data_. + virtual OEMCrypto_Substring FindSubstring(const void* pointer, size_t length); + + // ---------------------------------------------------------------------- + // Member variables. + Session* session_; + CoreRequest core_request_; + CoreResponse core_response_; + ResponseData response_data_, encrypted_response_data_; + // Message buffers will be at least this big. Tests for loading and signing + // messages will increase all buffers to this size. + size_t required_message_size_; + std::vector response_signature_; + std::string serialized_core_message_; + std::vector encrypted_response_; +}; + +class ProvisioningRoundTrip + : public RoundTrip< + /* CoreRequest */ oemcrypto_core_message::ODK_ProvisioningRequest, + OEMCrypto_PrepAndSignProvisioningRequest, + /* CoreResponse */ ODK_ParsedProvisioning, + /* ResponseData */ RSAPrivateKeyMessage> { + public: + ProvisioningRoundTrip(Session* session, + const std::vector& encoded_rsa_key) + : RoundTrip(session), + allowed_schemes_(kSign_RSASSA_PSS), + encryptor_(), + encoded_rsa_key_(encoded_rsa_key) {} + // Prepare the session for signing the request. + virtual void PrepareSession(const wvoec::WidevineKeybox& keybox); + void CreateDefaultResponse() override; + void EncryptAndSignResponse() override; + OEMCryptoResult LoadResponse() override { return LoadResponse(session_); } + OEMCryptoResult LoadResponse(Session* session) override; + void VerifyLoadFailed(); + const std::vector& encoded_rsa_key() { return encoded_rsa_key_; } + const std::vector& wrapped_rsa_key() { return wrapped_rsa_key_; } + void set_allowed_schemes(uint32_t allowed_schemes) { + allowed_schemes_ = allowed_schemes; + } + + protected: + void VerifyRequestSignature(const vector& data, + const vector& generated_signature, + size_t core_message_length) override; + // Verify the values of the core response. + virtual void FillAndVerifyCoreRequest( + const std::string& core_message_string) override; + // Load the response, without the retry. Called by LoadResponse. + OEMCryptoResult LoadResponseNoRetry(Session* session, + size_t* wrapped_key_length); + // This takes a pointer in the response_data_ and remaps it to the same + // pointer within the encrypted message. This is used for backwards + // compatibliity testing, so that a v15 oemcrypto will accept range checks. + template + const T* RemapPointer(const T* response_pointer) const; + + uint32_t allowed_schemes_; + Encryptor encryptor_; + // The message key used for Prov 3.0. + std::vector message_key_; + std::vector encrypted_message_key_; + std::vector encoded_rsa_key_; + std::vector wrapped_rsa_key_; +}; + +class LicenseRoundTrip + : public RoundTrip< + /* CoreRequest */ oemcrypto_core_message::ODK_LicenseRequest, + OEMCrypto_PrepAndSignLicenseRequest, + /* CoreResponse */ ODK_ParsedLicense, + /* ResponseData */ MessageData> { + public: + LicenseRoundTrip(Session* session) + : RoundTrip(session), + control_(wvoec::kControlNonceEnabled), + num_keys_(4), + pst_(""), + minimum_srm_version_(0), + update_mac_keys_(true), + api_version_(kCurrentAPI), + expect_request_has_correct_nonce_(true), + license_type_(OEMCrypto_ContentLicense) {} + void CreateDefaultResponse() override; + // Create a license with four keys. Each key is responsible for one of generic + // encrypt (key 0), decrypt (key 1), sign (key 2) and verify (key 3). Each key + // is allowed only one type of operation. + void CreateResponseWithGenericCryptoKeys(); + // Fill the |core_response| substrings. + virtual void FillCoreResponseSubstrings(); + void EncryptAndSignResponse() override; + OEMCryptoResult LoadResponse() override { return LoadResponse(session_); } + OEMCryptoResult LoadResponse(Session* session) override; + // Reload an offline license into a different session. This derives new mac + // keys and then calls LoadResponse. + OEMCryptoResult ReloadResponse(Session* session); + void VerifyTestKeys(); + // Set the default key control block for all keys. This is used in + // CreateDefaultResponse. The key control block determines the restrictions + // that OEMCrypto should place on a key's use. For example, it specifies the + // minimum HDCP requirement and whether the key can only be used with a secure + // video path. See the section "Key Control Block" in the document "Widevine + // Modular DRM Security Integration Guide for CENC". + void set_control(uint32_t control) { control_ = control; } + uint32_t control() const { return control_; } + // Set the number of keys to use in the license. + void set_num_keys(uint32_t num_keys) { num_keys_ = num_keys; } + uint32_t num_keys() const { return num_keys_; } + // Get/Set the pst for the license and usage table entry. + const std::string& pst() const { return pst_; } + void set_pst(const std::string& pst) { pst_ = pst; } + // Set the minimum SRM version for the license. + void set_minimum_srm_version(uint32_t minimum_srm_version) { + minimum_srm_version_ = minimum_srm_version; + } + // Change the hash of the core request. This should cause the response to be + // rejected. + void BreakRequestHash() { request_hash_[3] ^= 42; } + // Set the API version for the license itself. This will be used in + // CreateDefaultResponse. + void set_api_version(uint32_t api_version) { api_version_ = api_version; } + uint32_t api_version() const { return api_version_; } + void set_update_mac_keys(bool update_mac_keys) { + update_mac_keys_ = update_mac_keys; + } + void set_license_type(OEMCrypto_LicenseType license_type) { + license_type_ = license_type; + } + // Skip the nonce check when verifying the license request. + void skip_nonce_check() { expect_request_has_correct_nonce_ = false; } + // This sets the key id of the specified key to the specified string. + // This is used to test with different key id lengths. + void SetKeyId(size_t index, const string& key_id); + + protected: + void VerifyRequestSignature(const vector& data, + const vector& generated_signature, + size_t core_message_length) override; + // Verify the values of the core response. + virtual void FillAndVerifyCoreRequest( + const std::string& core_message_string) override; + + // The default key control bits used with CreateDefaultResponse. + uint32_t control_; + // The number of keys in the license response. + uint32_t num_keys_; + // If non-empty, the license's provider session token. + std::string pst_; + // If non-zero, the minimum SRM version. + uint32_t minimum_srm_version_; + // If true, the license contains new mac keys for signing renewals. + bool update_mac_keys_; + // API version for the license itself. If this is 0 when the license request + // is signed, it will be set to the same as OEMCrypto's API version. It may + // be set to a lower value in order to test backwards compatibility. + uint32_t api_version_; + // If true, then we expect the nonce in the core request to match that in + // session. This is usually true, but when we are testing how OEMCrypto + // handles a bad nonce, we don't want to. + bool expect_request_has_correct_nonce_; + // Whether this is a content license or an entitlement license. Used in + // CreateDefaultResponse. + OEMCrypto_LicenseType license_type_; + uint8_t request_hash_[ODK_SHA256_HASH_SIZE]; +}; + +class RenewalRoundTrip + : public RoundTrip< + /* CoreRequest */ oemcrypto_core_message::ODK_RenewalRequest, + OEMCrypto_PrepAndSignRenewalRequest, + // Renewal response info is same as request: + /* CoreResponse */ oemcrypto_core_message::ODK_RenewalRequest, + /* ResponseData */ MessageData> { + public: + RenewalRoundTrip(LicenseRoundTrip* license_messages) + : RoundTrip(license_messages->session()), + license_messages_(license_messages), + refresh_object_(), + renewal_duration_seconds_( + license_messages->core_response() + .timer_limits.initial_renewal_duration_seconds), + is_release_(false) {} + void CreateDefaultResponse() override; + void EncryptAndSignResponse() override; + OEMCryptoResult LoadResponse() override { return LoadResponse(session_); } + OEMCryptoResult LoadResponse(Session* session) override; + uint64_t renewal_duration_seconds() const { + return renewal_duration_seconds_; + } + void set_renewal_duration_seconds(uint64_t renewal_duration_seconds) { + renewal_duration_seconds_ = renewal_duration_seconds; + } + void set_is_release(bool is_release) { is_release_ = is_release; } + + protected: + void VerifyRequestSignature(const vector& data, + const vector& generated_signature, + size_t core_message_length) override; + // Verify the values of the core response. + virtual void FillAndVerifyCoreRequest( + const std::string& core_message_string) override; + LicenseRoundTrip* license_messages_; + OEMCrypto_KeyRefreshObject refresh_object_; + uint64_t renewal_duration_seconds_; + bool is_release_; // If this is a license release, and not a real renewal. +}; + +class EntitledMessage { + public: + EntitledMessage(LicenseRoundTrip* license_messages) + : license_messages_(license_messages), num_keys_() {} + void FillKeyArray(); + void MakeOneKey(size_t entitlement_key_index); + void LoadKeys(OEMCryptoResult expected_sts); + void set_num_keys(uint32_t num_keys) { num_keys_ = num_keys; } + uint32_t num_keys() const { return num_keys_; } + void SetEntitlementKeyId(unsigned int index, const std::string& key_id); + // Verify that key control blocks of the loaded keys. + void VerifyEntitlementTestKeys(); + + private: + // Find the offset of the give pointer, relative to |entitled_key_data_|. + OEMCrypto_Substring FindSubstring(const void* ptr, size_t size); + + LicenseRoundTrip* license_messages_; + uint32_t num_keys_; + // Clear Entitlement key data. This is the backing data for + // |entitled_key_array_|. + EntitledContentKeyData entitled_key_data_[kMaxNumKeys]; + // Entitled key object. Pointers are backed by |entitled_key_data_|. + OEMCrypto_EntitledContentKeyObject entitled_key_array_[kMaxNumKeys]; +}; class Session { public: @@ -141,9 +442,9 @@ class Session { // Returns the most recently generated nonce. // Valid after call to GenerateNonce. - uint32_t get_nonce() { return nonce_; } + uint32_t nonce() const { return nonce_; } // Valid after call to open(). - uint32_t session_id() { return (uint32_t)session_id_; } + uint32_t session_id() const { return (uint32_t)session_id_; } // Call OEMCrypto_OpenSession, with GTest ASSERTs. void open(); // Call OEMCrypto_CloseSession, with GTest ASSERTs. @@ -155,7 +456,7 @@ class Session { // Generates one nonce. If error_counter is null, this will sleep 1 second // and try again if a nonce flood has been detected. If error_counter is // not null, it will be incremented when a nonce flood is detected. - void GenerateNonce(int* error_counter = NULL); + void GenerateNonce(int* error_counter = nullptr); // Fill the vectors with test context which generate known mac and enc keys. void FillDefaultContext(vector* mac_context, vector* enc_context); @@ -165,87 +466,6 @@ class Session { // Generate known mac and enc keys using OEMCrypto_DeriveKeysFromSessionKey // and also fill out enc_key_, mac_key_server_, and mac_key_client_. void GenerateDerivedKeysFromSessionKey(); - // Loads and verifies the keys in the message pointed to by message_ptr() - // using OEMCrypto_LoadKeys. This message should have already been created - // by FillSimpleMessage, modified if needed, and then encrypted and signed by - // the server's mac key in EncryptAndSign. - void LoadTestKeys(const std::string& pst = "", bool new_mac_keys = true); - // Loads the entitlement keys in the message pointed to by message_ptr() - // using OEMCrypto_LoadKeys. This message should have already been created - // by FillSimpleEntitlementMessage, modified if needed, and then encrypted - // and signed by the server's mac key in EncryptAndSign. - void LoadEntitlementTestKeys(const std::string& pst = "", - bool new_mac_keys = true, - OEMCryptoResult expected_sts = OEMCrypto_SUCCESS); - // Fills an OEMCrypto_EntitledContentKeyObject using the information from - // the license_ and randomly generated content keys. This method should be - // called after LoadEntitlementTestKeys. - void FillEntitledKeyArray(); - // Encrypts and loads the entitled content keys via - // OEMCrypto_LoadEntitledContentKeys. - void LoadEntitledContentKeys( - OEMCryptoResult expected_sts = OEMCrypto_SUCCESS); - // This uses OEMCrypto_QueryKeyControl to check that the keys in OEMCrypto - // have the correct key control data. - void VerifyTestKeys(); - // This uses OEMCrypto_QueryKeyControl to check that the keys in OEMCrypto - // have the correct key control data. - void VerifyEntitlementTestKeys(); - // This creates a refresh key or license renewal message, signs it with the - // server's mac key, and calls OEMCrypto_RefreshKeys. - void RefreshTestKeys(const size_t key_count, uint32_t control_bits, - uint32_t nonce, OEMCryptoResult expected_result); - // This sets the key id in the current message data to the specified string. - // This is used to test with different key id lengths. - void SetKeyId(int index, const string& key_id); - // This fills the data structure license_ with key information. This data - // can be modified, and then should be encrypted and signed in EncryptAndSign - // before being loaded in LoadTestKeys. - void FillSimpleMessage(uint32_t duration, uint32_t control, uint32_t nonce, - const std::string& pst = ""); - // This fills the data structure license_ with entitlement key information. - // This data can be modified, and then should be encrypted and signed in - // EncryptAndSign before being loaded in LoadEntitlementTestKeys. - void FillSimpleEntitlementMessage( - uint32_t duration, uint32_t control, - uint32_t nonce, const std::string& pst = ""); - // Like FillSimpleMessage, this fills encrypted_license_ with data. The name - // is a little misleading: the license renewal message is not encrypted, it - // is just signed. The signature is computed in RefreshTestKeys, above. - void FillRefreshMessage(size_t key_count, uint32_t control_bits, - uint32_t nonce); - // Sets the OEMCrypto_Substring parameters of the LoadKeys method. - // Specifically, it sets the |enc_mac_keys_iv|, |enc_mac_keys|, |pst|, and - // |srm_restriction_data| in that order. For testing purposes, - // |srm_restriction_data| will always be NULL. - void SetLoadKeysSubstringParams(); - // This copies data from license_ to encrypted_license_, and then encrypts - // each field in the key array appropriately. It then signes the buffer with - // the server mac keys. It then fills out the key_array_ so that pointers in - // that array point to the locations in the encrypted message. - void EncryptAndSign(); - // This encrypts an RSAPrivateKeyMessage with encryption_key so that it may be - // loaded with OEMCrypto_RewrapDeviceRSAKey. - void EncryptProvisioningMessage(RSAPrivateKeyMessage* data, - RSAPrivateKeyMessage* encrypted, - const vector& encryption_key); - // Sign the buffer with server's mac key. - void ServerSignBuffer(const uint8_t* data, size_t data_length, - std::vector* signature); - // Sign the buffer with client's known mac key. Known test keys must be - // installed first. - void ClientSignMessage(const vector& data, - std::vector* signature); - // This checks the signature generated by OEMCrypto_GenerateSignature against - // that generaged by ClientSignMessage. - void VerifyClientSignature(size_t data_length = 400); - // Set the pointers in key_array[*] to point values inside data. This is - // needed to satisfy range checks in OEMCrypto_LoadKeys. - void FillKeyArray(const MessageData& data, OEMCrypto_KeyObject* key_array); - // As in FillKeyArray but for the license renewal message passed to - // OEMCrypto_RefreshKeys. - void FillRefreshArray(OEMCrypto_KeyRefreshObject* key_array, - size_t key_count); // Encrypt a block of data using CTR mode. void EncryptCTR(const vector& in_buffer, const uint8_t* key, const uint8_t* starting_iv, vector* out_buffer); @@ -253,24 +473,12 @@ class Session { void TestDecryptCTR(bool select_key_first = true, OEMCryptoResult expected_result = OEMCrypto_SUCCESS, int key_index = 0); - // This compares the actual result with the expected result. If OEMCrypto is - // an older version, we allow it to report an equivalent error code. - void TestDecryptResult(OEMCryptoResult expected_result, - OEMCryptoResult actual_result); // Verify that an attempt to select an expired key either succeeds, or gives // an actionable error code. void TestSelectExpired(unsigned int key_index); - // Calls OEMCrypto_GetOEMPublicCertificate and loads the OEM cert's public - // rsa key into public_rsa_. + // Calls OEMCrypto_GetOEMPublicCertificate and OEMCrypto_LoadOEMPrivateKey and + // loads the OEM cert's public rsa key into public_rsa_. void LoadOEMCert(bool verify_cert = false); - // Creates RSAPrivateKeyMessage for the specified rsa_key, encrypts it with - // the specified encryption key, and then signs it with the server's mac key. - // If encryption_key is null, use the session's enc_key_. - void MakeRSACertificate(struct RSAPrivateKeyMessage* encrypted, - size_t message_size, std::vector* signature, - uint32_t allowed_schemes, - const vector& rsa_key, - const vector* encryption_key = NULL); // Calls OEMCrypto_RewrapDeviceRSAKey with the given provisioning response // message. If force is true, we assert that the key loads successfully. void RewrapRSAKey(const struct RSAPrivateKeyMessage& encrypted, @@ -278,7 +486,7 @@ class Session { vector* wrapped_key, bool force); // Loads the specified RSA public key into public_rsa_. If rsa_key is null, // the default test key is loaded. - void PreparePublicKey(const uint8_t* rsa_key = NULL, + void PreparePublicKey(const uint8_t* rsa_key = nullptr, size_t rsa_key_length = 0); // Verifies the given signature is from the given message and RSA key, pkey. static bool VerifyPSSSignature(EVP_PKEY* pkey, const uint8_t* message, @@ -306,7 +514,7 @@ class Session { // Creates a new usage entry, and keeps track of the index. // If status is null, we expect success, otherwise status is set to the // return value. - void CreateNewUsageEntry(OEMCryptoResult *status = NULL); + void CreateNewUsageEntry(OEMCryptoResult* status = nullptr); // Copy encrypted usage entry from other session, and then load it. // This session must already be open. void LoadUsageEntry(uint32_t index, const vector& buffer); @@ -330,7 +538,7 @@ class Session { } // Generates a usage report for the specified pst. If there is success, // the report's signature is verified, and several fields are given sanity - // checks. If other is not null, then the mac keys are copied from other in + // checks. If |other| is not null, then the mac keys are copied from other in // order to verify signatures. void GenerateReport(const std::string& pst, OEMCryptoResult expected_result = OEMCrypto_SUCCESS, @@ -355,12 +563,6 @@ class Session { int64_t time_license_received = 0, int64_t time_first_decrypt = 0, int64_t time_last_decrypt = 0); - // Same as above, but generates the report with the given status. - void GenerateVerifyReport(const std::string& pst, - OEMCrypto_Usage_Entry_Status status, - int64_t time_license_received = 0, - int64_t time_first_decrypt = 0, - int64_t time_last_decrypt = 0); // Create an entry in the old usage table based on the given report. void CreateOldEntry(const Test_PST_Report &report); // Create a new entry and copy the old entry into it. Then very the report @@ -370,88 +572,33 @@ class Session { // The unencrypted license response or license renewal response. MessageData& license() { return license_; } - // The encrypted license response or license renewal response. - MessageData& encrypted_license() { return padded_message_; } - // A pointer to the buffer holding encrypted_license. - const uint8_t* message_ptr(); + void set_license(const MessageData& license) { license_ = license; } - // An array of key objects for use in LoadKeys. - OEMCrypto_KeyObject* key_array() { return key_array_; } - - // An array of key objects for LoadEntitledContentKeys. - OEMCrypto_EntitledContentKeyObject* entitled_key_array() { - return entitled_key_array_; + const KeyDeriver& key_deriver() const { return key_deriver_; } + void set_mac_keys(const uint8_t* mac_keys) { + key_deriver_.set_mac_keys(mac_keys); } - // The last signature generated with the server's mac key. - std::vector& signature() { return signature_; } - - // Set the number of keys to use in the license(), encrypted_license() - // and key_array(). - void set_num_keys(int num_keys) { num_keys_ = num_keys; } - // The current number of keys to use in the license(), encrypted_license() - // and key_array(). - unsigned int num_keys() const { return num_keys_; } - - // Set the size of the buffer used the encrypted license. - // Must be between sizeof(MessageData) and kMaxMessageSize. - void set_message_size(size_t size); - // The size of the encrypted message. - size_t message_size() { return message_size_; } - - // The OEMCrypto_Substrings associated with the encrypted license that are - // passed to LoadKeys. - vector load_keys_params() { return load_keys_params_; } - OEMCrypto_Substring enc_mac_keys_iv_substr() { return load_keys_params_[0]; } - OEMCrypto_Substring enc_mac_keys_substr() { return load_keys_params_[1]; } - OEMCrypto_Substring pst_substr() { return load_keys_params_[2]; } - OEMCrypto_Substring srm_restriction_data_substr() { - return load_keys_params_[3]; - } - - // Pointer to buffer holding |encrypted_entitled_message_| - const uint8_t* encrypted_entitled_message_ptr(); - private: - // Generate mac and enc keys give the master key. - void DeriveKeys(const uint8_t* master_key, - const vector& mac_key_context, - const vector& enc_key_context); - // Internal utility function to derive key using CMAC-128 - void DeriveKey(const uint8_t* key, const vector& context, - int counter, vector* out); + // This compares the actual result with the expected result. If OEMCrypto is + // an older version, we allow it to report an equivalent error code. + void TestDecryptResult(OEMCryptoResult expected_result, + OEMCryptoResult actual_select_result, + OEMCryptoResult actual_decryt_result); bool open_; bool forced_session_id_; OEMCrypto_SESSION session_id_; - vector mac_key_server_; - vector mac_key_client_; - vector enc_key_; + KeyDeriver key_deriver_; uint32_t nonce_; RSA* public_rsa_; vector pst_report_buffer_; MessageData license_; - struct PaddedMessageData : public MessageData { - uint8_t padding[kMaxMessageSize - sizeof(MessageData)]; - } padded_message_; - size_t message_size_; // How much of the padded message to use. - OEMCrypto_KeyObject key_array_[kMaxNumKeys]; - vector load_keys_params_; - std::vector signature_; - unsigned int num_keys_; + vector encrypted_usage_entry_; uint32_t usage_entry_number_; string pst_; - - // Clear Entitlement key data. This is the backing data for - // |entitled_key_array_|. - EntitledContentKeyData entitled_key_data_[kMaxNumKeys]; - // Message containing data from |key_array| and |entitled_key_data_|. - std::string entitled_message_; - // Entitled key object. Pointers are backed by |entitled_key_data_|. - OEMCrypto_EntitledContentKeyObject entitled_key_array_[kMaxNumKeys]; - std::string encrypted_entitled_message_; }; } // namespace wvoec diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.cpp index 60eb36a4..9e2bc7e7 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.cpp @@ -15,73 +15,21 @@ const uint8_t* find(const vector& message, vector::const_iterator pos = search( message.begin(), message.end(), substring.begin(), substring.end()); if (pos == message.end()) { - return NULL; + return nullptr; } return &(*pos); } -// This creates a wrapped RSA key for devices that have the test keybox -// installed. If force is true, we assert that the key loads successfully. -void SessionUtil::CreateWrappedRSAKeyFromKeybox(uint32_t allowed_schemes, - bool force) { +// This creates a wrapped RSA key. +void SessionUtil::CreateWrappedRSAKey() { Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromKeybox(keybox_)); - // Provisioning request would be signed by the client and verified by the - // server. - ASSERT_NO_FATAL_FAILURE(s.VerifyClientSignature()); - struct RSAPrivateKeyMessage encrypted; - std::vector signature; - ASSERT_NO_FATAL_FAILURE( - s.MakeRSACertificate(&encrypted, sizeof(encrypted), - &signature, allowed_schemes, - encoded_rsa_key_)); - ASSERT_NO_FATAL_FAILURE(s.RewrapRSAKey( - encrypted, sizeof(encrypted), signature, &wrapped_rsa_key_, force)); - // Verify that the clear key is not contained in the wrapped key. - // It should be encrypted. - ASSERT_EQ(NULL, find(wrapped_rsa_key_, encoded_rsa_key_)); -} - -// This creates a wrapped RSA key for devices using provisioning 3.0. If force -// is true, we assert that the key loads successfully. -void SessionUtil::CreateWrappedRSAKeyFromOEMCert( - uint32_t allowed_schemes, bool force) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.LoadOEMCert()); - s.GenerateNonce(); - struct RSAPrivateKeyMessage encrypted; - std::vector signature; - std::vector message_key; - std::vector encrypted_message_key; - s.GenerateRSASessionKey(&message_key, &encrypted_message_key); - ASSERT_NO_FATAL_FAILURE( - s.MakeRSACertificate(&encrypted, sizeof(encrypted), &signature, - allowed_schemes, encoded_rsa_key_, &message_key)); - ASSERT_NO_FATAL_FAILURE( - s.RewrapRSAKey30(encrypted, encrypted_message_key, - &wrapped_rsa_key_, force)); - // Verify that the clear key is not contained in the wrapped key. - // It should be encrypted. - ASSERT_EQ(NULL, find(wrapped_rsa_key_, encoded_rsa_key_)); -} - -// If force is true, we assert that the key loads successfully. -void SessionUtil::CreateWrappedRSAKey(uint32_t allowed_schemes, - bool force) { - switch (global_features.provisioning_method) { - case OEMCrypto_OEMCertificate: - CreateWrappedRSAKeyFromOEMCert(allowed_schemes, force); - break; - case OEMCrypto_Keybox: - CreateWrappedRSAKeyFromKeybox(allowed_schemes, force); - break; - default: - FAIL() << "Cannot generate wrapped RSA key if provision method = " - << wvoec::ProvisioningMethodName( - global_features.provisioning_method); - } + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + wrapped_rsa_key_ = provisioning_messages.wrapped_rsa_key(); } void SessionUtil::InstallKeybox(const wvoec::WidevineKeybox& keybox, @@ -89,10 +37,9 @@ void SessionUtil::InstallKeybox(const wvoec::WidevineKeybox& keybox, uint8_t wrapped[sizeof(wvoec::WidevineKeybox)]; size_t length = sizeof(wvoec::WidevineKeybox); keybox_ = keybox; - ASSERT_EQ( - OEMCrypto_SUCCESS, - OEMCrypto_WrapKeybox(reinterpret_cast(&keybox), - sizeof(keybox), wrapped, &length, NULL, 0)); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_WrapKeybox(reinterpret_cast(&keybox), + sizeof(keybox), wrapped, &length, nullptr, 0)); OEMCryptoResult sts = OEMCrypto_InstallKeybox(wrapped, sizeof(keybox)); if (good) { ASSERT_EQ(OEMCrypto_SUCCESS, sts); @@ -105,18 +52,14 @@ void SessionUtil::EnsureTestKeys() { switch (global_features.derive_key_method) { case DeviceFeatures::LOAD_TEST_KEYBOX: keybox_ = kTestKeybox; - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_LoadTestKeybox( - reinterpret_cast(&keybox_), - sizeof(keybox_))); + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_LoadTestKeybox(reinterpret_cast(&keybox_), + sizeof(keybox_))); break; case DeviceFeatures::LOAD_TEST_RSA_KEY: ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadTestRSAKey()); break; - case DeviceFeatures::FORCE_TEST_KEYBOX: - keybox_ = kTestKeybox; - InstallKeybox(keybox_, true); - break; case DeviceFeatures::TEST_PROVISION_30: // Can use oem certificate to install test rsa key. break; @@ -127,26 +70,17 @@ void SessionUtil::EnsureTestKeys() { // This makes sure that the derived keys (encryption key and two mac keys) // are installed in OEMCrypto and in the test session. -void SessionUtil::InstallTestSessionKeys(Session* s) { - if (global_features.uses_certificate) { - if (global_features.loads_certificate) { - if (wrapped_rsa_key_.size() == 0) { - // If we don't have a wrapped key yet, create one. - // This wrapped key will be shared by all sessions in the test. - ASSERT_NO_FATAL_FAILURE( - CreateWrappedRSAKey(kSign_RSASSA_PSS, true)); - } - // Load the wrapped rsa test key. - ASSERT_NO_FATAL_FAILURE( - s->InstallRSASessionTestKey(wrapped_rsa_key_)); +void SessionUtil::InstallTestRSAKey(Session* s) { + if (global_features.loads_certificate) { + if (wrapped_rsa_key_.size() == 0) { + // If we don't have a wrapped key yet, create one. + // This wrapped key will be shared by all sessions in the test. + ASSERT_NO_FATAL_FAILURE(CreateWrappedRSAKey()); } - // Test RSA key should be loaded. - ASSERT_NO_FATAL_FAILURE( - s->GenerateDerivedKeysFromSessionKey()); - } else { // Just uses keybox. Test keybox should already be installed. - ASSERT_NO_FATAL_FAILURE( - s->GenerateDerivedKeysFromKeybox(keybox_)); + // Load the wrapped rsa test key. + ASSERT_NO_FATAL_FAILURE(s->InstallRSASessionTestKey(wrapped_rsa_key_)); } + // Test RSA key should be loaded. + ASSERT_NO_FATAL_FAILURE(s->GenerateDerivedKeysFromSessionKey()); } - -} +} // namespace wvoec diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.h b/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.h index 70469bdf..10898435 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.h +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.h @@ -12,33 +12,27 @@ namespace wvoec { class SessionUtil { public: - SessionUtil() - : encoded_rsa_key_(kTestRSAPKCS8PrivateKeyInfo2_2048, - kTestRSAPKCS8PrivateKeyInfo2_2048 + - sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048)) {} + SessionUtil() + : encoded_rsa_key_(kTestRSAPKCS8PrivateKeyInfo2_2048, + kTestRSAPKCS8PrivateKeyInfo2_2048 + + sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048)) {} - // If force is true, we assert that the key loads successfully. - void CreateWrappedRSAKeyFromKeybox(uint32_t allowed_schemes, bool force); + // Create a new wrapped DRM Certificate. + void CreateWrappedRSAKey(); - // If force is true, we assert that the key loads successfully. - void CreateWrappedRSAKeyFromOEMCert(uint32_t allowed_schemes, bool force); + // This is used to force installation of a keybox. This overwrites the + // production keybox -- it does NOT use OEMCrypto_LoadTestKeybox. + void InstallKeybox(const wvoec::WidevineKeybox& keybox, bool good); - // If force is true, we assert that the key loads successfully. - void CreateWrappedRSAKey(uint32_t allowed_schemes, bool force); + // This loads the test keybox or the test RSA key, using LoadTestKeybox or + // LoadTestRSAKey as needed. + void EnsureTestKeys(); - // This is used to force installation of a keybox. This overwrites the - // production keybox -- it does NOT use OEMCrypto_LoadTestKeybox. - void InstallKeybox(const wvoec::WidevineKeybox& keybox, bool good); + void InstallTestRSAKey(Session* s); - // This loads the test keybox or the test RSA key, using LoadTestKeybox or - // LoadTestRSAKey as needed. - void EnsureTestKeys(); - - void InstallTestSessionKeys(Session* s); - - std::vector encoded_rsa_key_; - std::vector wrapped_rsa_key_; - wvoec::WidevineKeybox keybox_; + std::vector encoded_rsa_key_; + std::vector wrapped_rsa_key_; + wvoec::WidevineKeybox keybox_; }; } // namespace wvoec diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index 480149d9..9e559c5f 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #ifdef _WIN32 # include @@ -31,7 +30,9 @@ #include #include "OEMCryptoCENC.h" +#include "clock.h" #include "log.h" +#include "oec_decrypt_fallback_chain.h" #include "oec_device_features.h" #include "oec_session_util.h" #include "oec_test_data.h" @@ -39,6 +40,7 @@ #include "oemcrypto_types.h" #include "platform.h" #include "string_conversions.h" +#include "test_sleep.h" #include "wvcrc32.h" using ::testing::Bool; @@ -51,22 +53,41 @@ using namespace std; namespace std { // GTest wants PrintTo to be in the std namespace. void PrintTo(const tuple& param, + wvoec::OutputType>& param, ostream* os) { OEMCrypto_CENCEncryptPatternDesc pattern = ::testing::get<0>(param); OEMCryptoCipherMode mode = ::testing::get<1>(param); - bool decrypt_inplace = ::testing::get<2>(param); + wvoec::OutputType output = ::testing::get<2>(param); + bool decrypt_inplace = output.decrypt_inplace; + OEMCryptoBufferType type = output.type; *os << ((mode == OEMCrypto_CipherMode_CTR) ? "CTR mode" : "CBC mode") - << ", encrypt=" << pattern.encrypt << ", skip=" << pattern.skip - << ", decrypt in place = " << (decrypt_inplace ? "true" : "false"); + << ", pattern=(encrypt:" << pattern.encrypt << ", skip:" << pattern.skip + << ")"; + switch (type) { + case OEMCrypto_BufferType_Clear: + *os << ", BufferType = Clear"; + break; + case OEMCrypto_BufferType_Secure: + *os << ", BufferType = Secure"; + break; + case OEMCrypto_BufferType_Direct: + *os << ", BufferType = Direct"; + break; + default: + *os << ", type = "; + break; + } + if (decrypt_inplace) *os << " (in place)"; } } // namespace std namespace wvoec { namespace { +constexpr size_t kBufferOverrunPadding = 16; + // Resource tiers: -const size_t KiB = 1024; -const size_t MiB = 1024 * 1024; +constexpr size_t KiB = 1024; +constexpr size_t MiB = 1024 * 1024; // With OEMCrypto v15 and above, we have different resource requirements // depending on the resource rating reported by OEMCrypto. This function looks // up the required value for the specified resource for the target OEMCrypto @@ -77,20 +98,29 @@ T GetResourceValue(T (&resource_values)[N]) { if (global_features.resource_rating > N) return resource_values[N-1]; return resource_values[global_features.resource_rating-1]; } -const size_t kMaxSampleSize[] = { 1000*KiB, 2*MiB, 4*MiB}; -const size_t kMaxNumberSubsamples[] = { 10, 16, 32}; -const size_t kMaxSubsampleSize[] = { 100*KiB, 500*KiB, 1*MiB}; -const size_t kMaxGenericBuffer[] = { 10*KiB, 100*KiB, 500*KiB}; -const size_t kMaxConcurrentSession[] = { 10, 20, 20}; -const size_t kMaxKeysPerSession [] = { 4, 20, 20}; + +// After API 16, we require 300 entries in the usage table. Before API 16, we +// required 200. +size_t RequiredUsageSize() { + return global_features.api_version < 16 ? 200 : 300; +} + +// These are the maximum sizes we test. That means it is the minimum size that +// OEMCrypto must support. +// clang-format off +const size_t kMaxSampleSize[] = { 1*MiB, 2*MiB, 4*MiB, 16*MiB}; +const size_t kMaxNumberSubsamples[] = { 10, 16, 32, 64}; +const size_t kMaxSubsampleSize[] = { 100*KiB, 500*KiB, 1*MiB, 4*MiB}; +const size_t kMaxGenericBuffer[] = { 10*KiB, 100*KiB, 500*KiB, 1*MiB}; +const size_t kMaxConcurrentSession[] = { 10, 20, 20, 30}; +const size_t kMaxKeysPerSession[] = { 4, 20, 20, 30}; +const size_t kMaxTotalKeys[] = { 16, 40, 80, 90}; +const size_t kLargeMessageSize[] = { 8*KiB, 8*KiB, 16*KiB, 32*KiB}; // Note: Frame rate and simultaneous playback are specified by resource rating, // but are tested at the system level, so there are no unit tests for frame -// rate. - -int GetRandBytes(unsigned char* buf, int num) { - // returns 1 on success, -1 if not supported, or 0 if other failure. - return RAND_bytes(buf, num); -} +// rate. Similarly, number of subsamples for AV1 +// const size_t kAV1NumberSubsamples[] = { 72, 144, 288, 576}; +// clang-format on /** @return The Unix time of the given time point. */ template @@ -130,6 +160,7 @@ class OEMCryptoClientTest : public ::testing::Test, public SessionUtil { void SetUp() override { ::testing::Test::SetUp(); + wvcdm::TestSleep::SyncFakeClock(); const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); LOGD("Running test %s.%s", test_info->test_case_name(), test_info->name()); @@ -147,7 +178,7 @@ class OEMCryptoClientTest : public ::testing::Test, public SessionUtil { vector::const_iterator pos = search( message.begin(), message.end(), substring.begin(), substring.end()); if (pos == message.end()) { - return NULL; + return nullptr; } return &(*pos); } @@ -159,7 +190,7 @@ class OEMCryptoClientTest : public ::testing::Test, public SessionUtil { // tests are failing when the device has the wrong keybox installed. TEST_F(OEMCryptoClientTest, VersionNumber) { const char* level = OEMCrypto_SecurityLevel(); - ASSERT_NE((char*)NULL, level); + ASSERT_NE(nullptr, level); ASSERT_EQ('L', level[0]); cout << " OEMCrypto Security Level is " << level << endl; uint32_t version = OEMCrypto_APIVersion(); @@ -170,20 +201,23 @@ TEST_F(OEMCryptoClientTest, VersionNumber) { cout << " OEMCrypto does not support usage tables." << endl; } if (version >= 15) { + cout << " Resource Rating Tier: " + << OEMCrypto_ResourceRatingTier() << endl; const char* build_info = OEMCrypto_BuildInformation(); - ASSERT_TRUE(build_info != NULL); + ASSERT_NE(nullptr, build_info); ASSERT_TRUE(strnlen(build_info, 256) <= 256) << "BuildInformation should be a short printable string."; cout << " BuildInformation: " << build_info << endl; } ASSERT_GE(version, 8u); - ASSERT_LE(version, 15u); + ASSERT_LE(version, kCurrentAPI); } -// The resource rating is a number from 1 to 3, defined API 15. +// The resource rating is a number from 1 to 4. The first three levels were +// initiallly defined in API 15 and they were expaneded in API 16. TEST_F(OEMCryptoClientTest, ResourceRatingAPI15) { ASSERT_GE(OEMCrypto_ResourceRatingTier(), 1u); - ASSERT_LE(OEMCrypto_ResourceRatingTier(), 3u); + ASSERT_LE(OEMCrypto_ResourceRatingTier(), 4u); } // OEMCrypto must declare what type of provisioning scheme it uses. @@ -236,7 +270,7 @@ TEST_F(OEMCryptoClientTest, CheckSRMCapabilityV13) { OEMCryptoResult current_result = OEMCrypto_GetCurrentSRMVersion(&version); if (current_result == OEMCrypto_SUCCESS) { printf(" Current SRM Version: %d.\n", version); - EXPECT_NE(OEMCrypto_SUCCESS, OEMCrypto_GetCurrentSRMVersion(NULL)); + EXPECT_NE(OEMCrypto_SUCCESS, OEMCrypto_GetCurrentSRMVersion(nullptr)); } else if (current_result == OEMCrypto_LOCAL_DISPLAY_ONLY) { printf(" Current SRM Status: Local Display Only.\n"); } else { @@ -261,6 +295,13 @@ TEST_F(OEMCryptoClientTest, CheckMaxNumberOfSessionsAPI10) { ASSERT_GE(maximum, required_max); } +TEST_F(OEMCryptoClientTest, CheckUsageTableSizeAPI16) { + const size_t maximum = OEMCrypto_MaximumUsageTableHeaderSize(); + printf(" Max Usage Table Size: %zu.\n", maximum); + // A maximum of 0 means the table is constrained my dynamic memory allocation. + if (maximum > 0) ASSERT_GE(maximum, RequiredUsageSize()); +} + // // initialization tests // @@ -289,18 +330,6 @@ TEST_F(OEMCryptoClientTest, TwoSessionsOpenClose) { ASSERT_NO_FATAL_FAILURE(s2.close()); } -// This test should still pass for API v9. A better test is below, but it only -// works for API v10. -TEST_F(OEMCryptoClientTest, EightSessionsOpenClose) { - vector s(8); - for (int i = 0; i < 8; i++) { - ASSERT_NO_FATAL_FAILURE(s[i].open()); - } - for (int i = 0; i < 8; i++) { - ASSERT_NO_FATAL_FAILURE(s[i].close()); - } -} - // This test verifies that OEMCrypto can open approximately as many sessions as // it claims. TEST_F(OEMCryptoClientTest, MaxSessionsOpenCloseAPI10) { @@ -380,64 +409,29 @@ TEST_F(OEMCryptoClientTest, GenerateNonce) { s.GenerateNonce(); } -TEST_F(OEMCryptoClientTest, GenerateTwoNonces) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - s.GenerateNonce(); - uint32_t nonce1 = s.get_nonce(); - s.GenerateNonce(); - uint32_t nonce2 = s.get_nonce(); - ASSERT_TRUE(nonce1 != nonce2); // Very unlikely to be equal. -} - -// OEMCrypto should limit the number of nonces that it can generate in one -// second. A flood of nonce requests can be used for a replay attack, which we -// wish to protect against. -TEST_F(OEMCryptoClientTest, PreventNonceFloodAPI09) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - int error_counter = 0; - time_t test_start = time(NULL); - // More than 20 nonces per second should generate an error. - // To allow for some slop, we actually test for more. - const int kFloodCount = 80; - for (int i = 0; i < kFloodCount; i++) { - s.GenerateNonce(&error_counter); - } - time_t test_end = time(NULL); - int valid_counter = kFloodCount - error_counter; - // Either oemcrypto should enforce a delay, or it should return an error from - // GenerateNonce -- in either case the number of valid nonces is rate - // limited. We add two seconds to allow for round off error in both - // test_start and test_end. - EXPECT_LE(valid_counter, 20 * (test_end - test_start + 2)); - error_counter = 0; - sleep(2); // After a pause, we should be able to regenerate nonces. - s.GenerateNonce(&error_counter); - EXPECT_EQ(0, error_counter); -} - // Prevent a nonce flood even if each nonce is in a different session. -TEST_F(OEMCryptoClientTest, PreventNonceFlood2API09) { +TEST_F(OEMCryptoClientTest, PreventNonceFlood2API16) { int error_counter = 0; - time_t test_start = time(NULL); - // More than 20 nonces per second should generate an error. + const int64_t test_start = wvcdm::Clock().GetCurrentTime(); + // More than 200 nonces per second should generate an error. // To allow for some slop, we actually test for more. - const int kFloodCount = 80; - for (int i = 0; i < kFloodCount; i++) { + const int flood_cutoff = 200; + const int loop_count = flood_cutoff * 2; + for (int i = 0; i < loop_count; i++) { Session s; ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateNonce(&error_counter); } - time_t test_end = time(NULL); - int valid_counter = kFloodCount - error_counter; + const int64_t test_end = wvcdm::Clock().GetCurrentTime(); + int valid_counter = loop_count - error_counter; // Either oemcrypto should enforce a delay, or it should return an error from // GenerateNonce -- in either case the number of valid nonces is rate // limited. We add two seconds to allow for round off error in both // test_start and test_end. - EXPECT_LE(valid_counter, 20 * (test_end - test_start + 2)); + EXPECT_LE(valid_counter, flood_cutoff * (test_end - test_start + 2)); error_counter = 0; - sleep(2); // After a pause, we should be able to regenerate nonces. + // After a pause, we should be able to regenerate nonces. + wvcdm::TestSleep::Sleep(2); Session s; ASSERT_NO_FATAL_FAILURE(s.open()); s.GenerateNonce(&error_counter); @@ -448,30 +442,36 @@ TEST_F(OEMCryptoClientTest, PreventNonceFlood2API09) { // is different from the test above because there are several session open at // the same time. We want to make sure you can't get a flood of nonces by // opening a flood of sessions. -TEST_F(OEMCryptoClientTest, PreventNonceFlood3API09) { +TEST_F(OEMCryptoClientTest, PreventNonceFlood3API16) { int request_counter = 0; int error_counter = 0; - time_t test_start = time(NULL); - // More than 20 nonces per second should generate an error. + const int64_t test_start = wvcdm::Clock().GetCurrentTime(); + // More than 200 nonces per second should generate an error. // To allow for some slop, we actually test for more. - Session s[8]; - for (int i = 0; i < 8; i++) { - ASSERT_NO_FATAL_FAILURE(s[i].open()); - for (int j = 0; j < 10; j++) { + const int flood_cutoff = 200; + const size_t session_count = GetResourceValue(kMaxConcurrentSession); + const size_t loop_count = 2 * flood_cutoff / session_count + 1; + for (size_t i = 0; i < loop_count; i++) { + std::vector s(session_count); + for (size_t j = 0; j < session_count; j++) { + ASSERT_NO_FATAL_FAILURE(s[j].open()); request_counter++; - s[i].GenerateNonce(&error_counter); + s[j].GenerateNonce(&error_counter); } } - time_t test_end = time(NULL); + const int64_t test_end = wvcdm::Clock().GetCurrentTime(); int valid_counter = request_counter - error_counter; // Either oemcrypto should enforce a delay, or it should return an error from // GenerateNonce -- in either case the number of valid nonces is rate // limited. We add two seconds to allow for round off error in both // test_start and test_end. - EXPECT_LE(valid_counter, 20 * (test_end - test_start + 2)); + EXPECT_LE(valid_counter, flood_cutoff * (test_end - test_start + 2)); error_counter = 0; - sleep(2); // After a pause, we should be able to regenerate nonces. - s[0].GenerateNonce(&error_counter); + // After a pause, we should be able to regenerate nonces. + wvcdm::TestSleep::Sleep(2); + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + s.GenerateNonce(&error_counter); EXPECT_EQ(0, error_counter); } @@ -483,36 +483,37 @@ TEST_F(OEMCryptoClientTest, ClearCopyTestAPI10) { vector input_buffer(kDataSize); GetRandBytes(input_buffer.data(), input_buffer.size()); vector output_buffer(kDataSize); - OEMCrypto_DestBufferDesc dest_buffer; - dest_buffer.type = OEMCrypto_BufferType_Clear; - dest_buffer.buffer.clear.address = output_buffer.data(); - dest_buffer.buffer.clear.max_length = output_buffer.size(); + OEMCrypto_DestBufferDesc dest_buffer_descriptor; + dest_buffer_descriptor.type = OEMCrypto_BufferType_Clear; + dest_buffer_descriptor.buffer.clear.address = output_buffer.data(); + dest_buffer_descriptor.buffer.clear.address_length = output_buffer.size(); ASSERT_EQ( OEMCrypto_SUCCESS, OEMCrypto_CopyBuffer(s.session_id(), input_buffer.data(), - input_buffer.size(), &dest_buffer, + input_buffer.size(), &dest_buffer_descriptor, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); ASSERT_EQ(input_buffer, output_buffer); + ASSERT_EQ( + OEMCrypto_ERROR_INVALID_CONTEXT, + OEMCrypto_CopyBuffer(s.session_id(), nullptr, input_buffer.size(), + &dest_buffer_descriptor, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, OEMCrypto_CopyBuffer( - s.session_id(), NULL, input_buffer.size(), &dest_buffer, - OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); - ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, - OEMCrypto_CopyBuffer( - s.session_id(), input_buffer.data(), input_buffer.size(), NULL, - OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); - dest_buffer.buffer.clear.address = NULL; + s.session_id(), input_buffer.data(), input_buffer.size(), + nullptr, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); + dest_buffer_descriptor.buffer.clear.address = nullptr; ASSERT_EQ( OEMCrypto_ERROR_INVALID_CONTEXT, OEMCrypto_CopyBuffer(s.session_id(), input_buffer.data(), - input_buffer.size(), &dest_buffer, + input_buffer.size(), &dest_buffer_descriptor, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); - dest_buffer.buffer.clear.address = output_buffer.data(); - dest_buffer.buffer.clear.max_length = output_buffer.size() - 1; + dest_buffer_descriptor.buffer.clear.address = output_buffer.data(); + dest_buffer_descriptor.buffer.clear.address_length = output_buffer.size() - 1; ASSERT_EQ( OEMCrypto_ERROR_SHORT_BUFFER, OEMCrypto_CopyBuffer(s.session_id(), input_buffer.data(), - input_buffer.size(), &dest_buffer, + input_buffer.size(), &dest_buffer_descriptor, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); } @@ -524,14 +525,14 @@ TEST_F(OEMCryptoClientTest, ClearCopyTestLargeSubsample) { vector input_buffer(max_size); GetRandBytes(input_buffer.data(), input_buffer.size()); vector output_buffer(max_size); - OEMCrypto_DestBufferDesc dest_buffer; - dest_buffer.type = OEMCrypto_BufferType_Clear; - dest_buffer.buffer.clear.address = output_buffer.data(); - dest_buffer.buffer.clear.max_length = output_buffer.size(); + OEMCrypto_DestBufferDesc dest_buffer_descriptor; + dest_buffer_descriptor.type = OEMCrypto_BufferType_Clear; + dest_buffer_descriptor.buffer.clear.address = output_buffer.data(); + dest_buffer_descriptor.buffer.clear.address_length = output_buffer.size(); ASSERT_EQ( OEMCrypto_SUCCESS, OEMCrypto_CopyBuffer(s.session_id(), input_buffer.data(), - input_buffer.size(), &dest_buffer, + input_buffer.size(), &dest_buffer_descriptor, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample)); ASSERT_EQ(input_buffer, output_buffer); } @@ -573,22 +574,21 @@ TEST_F(OEMCryptoKeyboxTest, NormalGetDeviceId) { TEST_F(OEMCryptoKeyboxTest, GetDeviceIdShortBuffer) { OEMCryptoResult sts; uint8_t dev_id[128]; - uint32_t req_len = 0; for (int i = 0; i < 128; ++i) { dev_id[i] = 0x55; } dev_id[127] = '\0'; - size_t dev_id_len = req_len; + size_t dev_id_len = 0; sts = OEMCrypto_GetDeviceID(dev_id, &dev_id_len); ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); // On short buffer error, function should return minimum buffer length - ASSERT_TRUE(dev_id_len > req_len); + ASSERT_GT(dev_id_len, 0u); // Should also return short buffer if passed a zero length and a null buffer. - dev_id_len = req_len; - sts = OEMCrypto_GetDeviceID(NULL, &dev_id_len); + dev_id_len = 0; + sts = OEMCrypto_GetDeviceID(nullptr, &dev_id_len); ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); // On short buffer error, function should return minimum buffer length - ASSERT_TRUE(dev_id_len > req_len); + ASSERT_GT(dev_id_len, 0u); } TEST_F(OEMCryptoKeyboxTest, NormalGetKeyData) { @@ -606,7 +606,7 @@ TEST_F(OEMCryptoKeyboxTest, NormalGetKeyData) { TEST_F(OEMCryptoKeyboxTest, GetKeyDataNullPointer) { OEMCryptoResult sts; uint8_t key_data[256]; - sts = OEMCrypto_GetKeyData(key_data, NULL); + sts = OEMCrypto_GetKeyData(key_data, nullptr); ASSERT_NE(OEMCrypto_SUCCESS, sts); } @@ -620,10 +620,11 @@ TEST_F(OEMCryptoKeyboxTest, ProductionKeyboxValid) { TEST_F(OEMCryptoKeyboxTest, GenerateDerivedKeysFromKeyboxLargeBuffer) { Session s; ASSERT_NO_FATAL_FAILURE(s.open()); - vector mac_context(kMaxMessageSize); - vector enc_context(kMaxMessageSize); + const size_t max_size = GetResourceValue(kLargeMessageSize); + vector mac_context(max_size); + vector enc_context(max_size); // Stripe the data so the two vectors are not identical, and not all zeroes. - for (size_t i = 0; i < kMaxMessageSize; i++) { + for (size_t i = 0; i < max_size; i++) { mac_context[i] = i % 0x100; enc_context[i] = (3 * i) % 0x100; } @@ -647,12 +648,13 @@ TEST_F(OEMCryptoProv30Test, GetDeviceId) { std::vector dev_id(128, 0); size_t dev_id_len = dev_id.size(); sts = OEMCrypto_GetDeviceID(dev_id.data(), &dev_id_len); - if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) return; if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { ASSERT_GT(dev_id_len, 0u); dev_id.resize(dev_id_len); sts = OEMCrypto_GetDeviceID(dev_id.data(), &dev_id_len); } + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + dev_id.resize(dev_id_len); cout << " NormalGetDeviceId: dev_id = " << dev_id.data() << " len = " << dev_id_len << endl; ASSERT_EQ(OEMCrypto_SUCCESS, sts); @@ -671,35 +673,6 @@ TEST_F(OEMCryptoProv30Test, OEMCertValid) { ASSERT_NO_FATAL_FAILURE(s.LoadOEMCert(kVerify)); // Load and verify. } -// This verifies that an OEM Certificate can be used to generate a signature. -TEST_F(OEMCryptoProv30Test, OEMCertSignature) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.LoadOEMCert()); - OEMCryptoResult sts; - // Sign a Message - vector data(500); - GetRandBytes(data.data(), data.size()); - size_t signature_length = 0; - vector signature(1); - - sts = OEMCrypto_GenerateRSASignature(s.session_id(), data.data(), data.size(), - signature.data(), &signature_length, - kSign_RSASSA_PSS); - - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - ASSERT_NE(static_cast(0), signature_length); - signature.resize(signature_length, 0); - - sts = OEMCrypto_GenerateRSASignature(s.session_id(), data.data(), data.size(), - signature.data(), &signature_length, - kSign_RSASSA_PSS); - - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_NO_FATAL_FAILURE(s.VerifyRSASignature( - data, signature.data(), signature_length, kSign_RSASSA_PSS)); -} - // This verifies that the OEM Certificate cannot be used for other RSA padding // schemes. Those schemes should only be used by cast receiver certificates. TEST_F(OEMCryptoProv30Test, OEMCertForbiddenPaddingScheme) { @@ -732,33 +705,38 @@ TEST_F(OEMCryptoProv30Test, OEMCertForbiddenPaddingScheme) { ASSERT_EQ(zero, signature); // signature should not be computed. } -// Verify that the OEM Certificate can be used to sign a large buffer. -TEST_F(OEMCryptoProv30Test, OEMCertSignatureLargeBuffer) { +// Calling OEMCrypto_GetOEMPublicCertificate should not change the session's +// private key. +TEST_F(OEMCryptoProv30Test, GetCertOnlyAPI16) { + if (wrapped_rsa_key_.size() == 0) { + // If we don't have a wrapped key yet, create one. + // This wrapped key will be shared by all sessions in the test. + ASSERT_NO_FATAL_FAILURE(CreateWrappedRSAKey()); + } Session s; ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.LoadOEMCert()); - OEMCryptoResult sts; - // Sign a Message - vector data(kMaxMessageSize); - GetRandBytes(data.data(), data.size()); - size_t signature_length = 0; - vector signature(1); - - sts = OEMCrypto_GenerateRSASignature(s.session_id(), data.data(), data.size(), - signature.data(), &signature_length, - kSign_RSASSA_PSS); - - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - ASSERT_NE(static_cast(0), signature_length); - signature.resize(signature_length); - - sts = OEMCrypto_GenerateRSASignature(s.session_id(), data.data(), data.size(), - signature.data(), &signature_length, - kSign_RSASSA_PSS); - - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_NO_FATAL_FAILURE(s.VerifyRSASignature( - data, signature.data(), signature_length, kSign_RSASSA_PSS)); + // Install the DRM Cert's RSA key. + ASSERT_NO_FATAL_FAILURE(s.InstallRSASessionTestKey(wrapped_rsa_key_)); + // Request the OEM Cert. -- This should NOT load the OEM Private key. + vector public_cert; + size_t public_cert_length = 0; + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_GetOEMPublicCertificate(nullptr, &public_cert_length)); + ASSERT_LT(0u, public_cert_length); + public_cert.resize(public_cert_length); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_GetOEMPublicCertificate( + public_cert.data(), &public_cert_length)); + // Derive keys from the session key -- this should use the DRM Cert's key. It + // should NOT use the OEM Private key because that key should not have been + // loaded. + ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromSessionKey()); + // Now fill a message and try to load it. + LicenseRoundTrip license_messages(&s); + license_messages.set_control(0); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages.LoadResponse()); } // @@ -767,6 +745,9 @@ TEST_F(OEMCryptoProv30Test, OEMCertSignatureLargeBuffer) { // These tests will use either a test keybox or a test certificate to derive // session keys. class OEMCryptoSessionTests : public OEMCryptoClientTest { + public: + vector encrypted_usage_header_; + protected: OEMCryptoSessionTests() {} @@ -781,7 +762,7 @@ class OEMCryptoSessionTests : public OEMCryptoClientTest { void CreateUsageTableHeader(bool expect_success = true) { size_t header_buffer_length = 0; OEMCryptoResult sts = - OEMCrypto_CreateUsageTableHeader(NULL, &header_buffer_length); + OEMCrypto_CreateUsageTableHeader(nullptr, &header_buffer_length); if (expect_success) { ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); } else { @@ -797,15 +778,6 @@ class OEMCryptoSessionTests : public OEMCryptoClientTest { ASSERT_NE(OEMCrypto_SUCCESS, sts); } } - - void TearDown() override { - // If we installed a bad keybox, end with a good one installed. - if (global_features.derive_key_method == DeviceFeatures::FORCE_TEST_KEYBOX) - InstallKeybox(kTestKeybox, true); - OEMCryptoClientTest::TearDown(); - } - - vector encrypted_usage_header_; }; class OEMCryptoSessionTestKeyboxTest : public OEMCryptoSessionTests {}; @@ -814,816 +786,727 @@ TEST_F(OEMCryptoSessionTestKeyboxTest, TestKeyboxIsValid) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_IsKeyboxValid()); } -TEST_F(OEMCryptoSessionTestKeyboxTest, GoodForceKeybox) { - ASSERT_EQ(DeviceFeatures::FORCE_TEST_KEYBOX, - global_features.derive_key_method) - << "ForceKeybox tests will modify the installed keybox."; - wvoec::WidevineKeybox keybox = kTestKeybox; - OEMCryptoResult sts; - InstallKeybox(keybox, true); - sts = OEMCrypto_IsKeyboxValid(); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); +// This class is for testing a single license with the default API version +// of 16. +class OEMCryptoLicenseTestAPI16 : public OEMCryptoSessionTests { + public: + OEMCryptoLicenseTestAPI16() + : license_api_version_(kCurrentAPI), license_messages_(&session_) {} + + void SetUp() override { + OEMCryptoSessionTests::SetUp(); + ASSERT_NO_FATAL_FAILURE(session_.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&session_)); + } + + void TearDown() override { + ASSERT_NO_FATAL_FAILURE(session_.close()); + OEMCryptoSessionTests::TearDown(); + } + + protected: + Session session_; + uint32_t license_api_version_; + LicenseRoundTrip license_messages_; +}; + +// This class is used to test a license that is from a server either that is +// current or one version old. +class OEMCryptoLicenseTest : public OEMCryptoLicenseTestAPI16, + public WithParamInterface { + protected: + void SetUp() override { + // The only difference between this class and its parent is that we use a + // different license api: + license_api_version_ = GetParam(); + license_messages_.set_api_version(license_api_version_); + OEMCryptoLicenseTestAPI16::SetUp(); + } +}; + +// This class is used to test a license that is only for v15 license. +class OEMCryptoLicenseTestAPI15 : public OEMCryptoLicenseTestAPI16 { + void SetUp() override { + // The only difference between this class and its parent is that we use a + // different license api: + license_api_version_ = 15; + license_messages_.set_api_version(license_api_version_); + OEMCryptoLicenseTestAPI16::SetUp(); + } +}; + +// This class is used to test each key control block verification string in the +// range kc09-kc1?. This test is parameterized by the API number in the key +// control lock. +class OEMCryptoLicenseTestRangeAPI : public OEMCryptoLicenseTest {}; + +// Verify that a license may be signed. +TEST_P(OEMCryptoLicenseTest, SignLicenseRequest) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); } -TEST_F(OEMCryptoSessionTestKeyboxTest, BadCRCForceKeybox) { - ASSERT_EQ(DeviceFeatures::FORCE_TEST_KEYBOX, - global_features.derive_key_method) - << "ForceKeybox tests will modify the installed keybox."; - wvoec::WidevineKeybox keybox = kTestKeybox; - keybox.crc_[1] ^= 42; - OEMCryptoResult sts; - InstallKeybox(keybox, false); - sts = OEMCrypto_IsKeyboxValid(); - ASSERT_EQ(OEMCrypto_ERROR_BAD_CRC, sts); +TEST_P(OEMCryptoLicenseTest, SignLicenseRequestNoNonce) { + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); } -TEST_F(OEMCryptoSessionTestKeyboxTest, BadMagicForceKeybox) { - ASSERT_EQ(DeviceFeatures::FORCE_TEST_KEYBOX, - global_features.derive_key_method) - << "ForceKeybox tests will modify the installed keybox."; - wvoec::WidevineKeybox keybox = kTestKeybox; - keybox.magic_[1] ^= 42; - OEMCryptoResult sts; - InstallKeybox(keybox, false); - sts = OEMCrypto_IsKeyboxValid(); - ASSERT_EQ(OEMCrypto_ERROR_BAD_MAGIC, sts); -} - -TEST_F(OEMCryptoSessionTestKeyboxTest, BadDataForceKeybox) { - ASSERT_EQ(DeviceFeatures::FORCE_TEST_KEYBOX, - global_features.derive_key_method) - << "ForceKeybox tests will modify the installed keybox."; - wvoec::WidevineKeybox keybox = kTestKeybox; - keybox.data_[1] ^= 42; - OEMCryptoResult sts; - InstallKeybox(keybox, false); - sts = OEMCrypto_IsKeyboxValid(); - ASSERT_EQ(OEMCrypto_ERROR_BAD_CRC, sts); -} - -// Verify that keys can be derived from the test keybox, and then those derived -// keys can be used to sign a message. -TEST_F(OEMCryptoSessionTestKeyboxTest, GenerateSignature) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromKeybox(keybox_)); - - // Dummy context for testing signature generation. - vector context = wvcdm::a2b_hex( - "0a4c08001248000000020000101907d9ffde13aa95c122678053362136bdf840" - "8f8276e4c2d87ec52b61aa1b9f646e58734930acebe899b3e464189a14a87202" - "fb02574e70640bd22ef44b2d7e3912250a230a14080112100915007caa9b5931" - "b76a3a85f046523e10011a09393837363534333231180120002a0c3138383637" - "38373430350000"); - - static const uint32_t SignatureBufferMaxLength = 256; - vector signature(SignatureBufferMaxLength); - size_t signature_length = signature.size(); - - OEMCryptoResult sts; - sts = OEMCrypto_GenerateSignature(s.session_id(), context.data(), - context.size(), signature.data(), - &signature_length); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - - static const uint32_t SignatureExpectedLength = 32; - ASSERT_EQ(SignatureExpectedLength, signature_length); - signature.resize(signature_length); - - std::vector expected_signature; - s.ClientSignMessage(context, &expected_signature); - ASSERT_EQ(expected_signature, signature); +// Verify that a large license request may be signed. +TEST_P(OEMCryptoLicenseTest, SignLargeLicenseRequest) { + const size_t max_size = GetResourceValue(kLargeMessageSize); + license_messages_.set_message_size(max_size); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); } // Verify that a license may be loaded without a nonce. -TEST_F(OEMCryptoSessionTests, LoadKeyNoNonce) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 42)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); -} - -// Verify that a second license may be not be loaded in a session. -TEST_F(OEMCryptoSessionTests, LoadKeyNoNonceTwice) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 42)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NE( - OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys(s.session_id(), s.message_ptr(), s.message_size(), - s.signature().data(), s.signature().size(), - s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), s.pst_substr(), - GetSubstring(), OEMCrypto_ContentLicense)); +TEST_P(OEMCryptoLicenseTest, LoadKeyNoNonce) { + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.set_control(0); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } // Verify that a license may be loaded with a nonce. -TEST_F(OEMCryptoSessionTests, LoadKeyWithNonce) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE( - s.FillSimpleMessage(0, wvoec::kControlNonceEnabled, s.get_nonce())); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); +TEST_P(OEMCryptoLicenseTest, LoadKeyWithNonce) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } -// Verify that a second license may be not be loaded in a session. -TEST_F(OEMCryptoSessionTests, LoadKeyWithNonceTwice) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE( - s.FillSimpleMessage(0, wvoec::kControlNonceEnabled, s.get_nonce())); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); - ASSERT_NE( - OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys(s.session_id(), s.message_ptr(), s.message_size(), - s.signature().data(), s.signature().size(), - s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), s.pst_substr(), - GetSubstring(), OEMCrypto_ContentLicense)); +// Verify that a second license may not be loaded in a session. +TEST_P(OEMCryptoLicenseTest, LoadKeyNoNonceTwiceAPI16) { + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.set_control(0); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + // A second load, should NOT succeed. + ASSERT_EQ(OEMCrypto_ERROR_LICENSE_RELOAD, license_messages_.LoadResponse()); } -// This asks for several nonce. This simulates several license requests being -// lost. OEMCrypto is required to keep up to four nonce in the nonce table. -TEST_F(OEMCryptoSessionTests, LoadKeySeveralNonce) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - uint32_t first_nonce = - s.get_nonce(); // Nonce generated when installing keys. - s.GenerateNonce(); // two. - s.GenerateNonce(); // three. - s.GenerateNonce(); // four. - ASSERT_NO_FATAL_FAILURE( - s.FillSimpleMessage(0, wvoec::kControlNonceEnabled, first_nonce)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); -} - -// A license might update the mac keys and it might not. This tests that -// OEMCrypto keeps the old mac keys if the license does not update them. -TEST_F(OEMCryptoSessionTests, LoadKeyWithNoMAC) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys("", false)); - - vector context = wvcdm::a2b_hex( - "0a4c08001248000000020000101907d9ffde13aa95c122678053362136bdf840" - "8f8276e4c2d87ec52b61aa1b9f646e58734930acebe899b3e464189a14a87202" - "fb02574e70640bd22ef44b2d7e3912250a230a14080112100915007caa9b5931" - "b76a3a85f046523e10011a09393837363534333231180120002a0c3138383637" - "38373430350000"); - - static const uint32_t SignatureBufferMaxLength = 256; - vector signature(SignatureBufferMaxLength); - size_t signature_length = signature.size(); - - OEMCryptoResult sts; - sts = OEMCrypto_GenerateSignature(s.session_id(), context.data(), - context.size(), signature.data(), - &signature_length); - - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - - static const uint32_t SignatureExpectedLength = 32; - ASSERT_EQ(SignatureExpectedLength, signature_length); - signature.resize(signature_length); - - std::vector expected_signature; - s.ClientSignMessage(context, &expected_signature); - ASSERT_EQ(expected_signature, signature); +// Verify that a second license may not be loaded in a session. +TEST_P(OEMCryptoLicenseTest, LoadKeyWithNonceTwiceAPI16) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + // A second load, should NOT succeed. + ASSERT_EQ(OEMCrypto_ERROR_LICENSE_RELOAD, license_messages_.LoadResponse()); } // This verifies that entitlement keys and entitled content keys can be loaded. -TEST_F(OEMCryptoSessionTests, LoadEntitlementKeysAPI14) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleEntitlementMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadEntitlementTestKeys()); - s.FillEntitledKeyArray(); - ASSERT_NO_FATAL_FAILURE(s.LoadEntitledContentKeys()); - s.FillEntitledKeyArray(); - ASSERT_NO_FATAL_FAILURE(s.LoadEntitledContentKeys()); +TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysAPI14) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + license_messages_.set_license_type(OEMCrypto_EntitlementLicense); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(OEMCrypto_SUCCESS)); + EntitledMessage entitled_message_2(&license_messages_); + entitled_message_2.FillKeyArray(); + ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadKeys(OEMCrypto_SUCCESS)); } // This verifies that entitled content keys cannot be loaded if we have not yet // loaded the entitlement keys. -TEST_F(OEMCryptoSessionTests, LoadEntitlementKeysNoEntitlementKeysAPI14) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleEntitlementMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - // We do NOT call LoadEntitlementTestKeys. - s.FillEntitledKeyArray(); - s.LoadEntitledContentKeys(OEMCrypto_ERROR_INVALID_CONTEXT); +TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysNoEntitlementKeysAPI14) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + license_messages_.set_license_type(OEMCrypto_EntitlementLicense); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + ASSERT_NO_FATAL_FAILURE( + entitled_message_1.LoadKeys(OEMCrypto_ERROR_INVALID_CONTEXT)); } // This verifies that entitled content keys cannot be loaded if we have loaded // the wrong entitlement keys. -TEST_F(OEMCryptoSessionTests, LoadEntitlementKeysWrongEntitlementKeysAPI14) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleEntitlementMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadEntitlementTestKeys()); - s.FillEntitledKeyArray(); +TEST_P(OEMCryptoLicenseTest, LoadEntitlementKeysWrongEntitlementKeysAPI14) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + license_messages_.set_license_type(OEMCrypto_EntitlementLicense); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); const std::string key_id = "no_key"; - memcpy(const_cast(s.encrypted_entitled_message_ptr()) + - s.entitled_key_array()[0].entitlement_key_id.offset, - reinterpret_cast(key_id.c_str()), key_id.length()); - s.entitled_key_array()[0].entitlement_key_id.length = key_id.length(); - s.LoadEntitledContentKeys(OEMCrypto_KEY_NOT_ENTITLED); + entitled_message_1.SetEntitlementKeyId(0, key_id); + + ASSERT_NO_FATAL_FAILURE( + entitled_message_1.LoadKeys(OEMCrypto_KEY_NOT_ENTITLED)); } -// This tests GenerateSignature with an 8k licnese request. -TEST_F(OEMCryptoSessionTests, ClientSignatureLargeBuffer) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys("", false)); - - vector context(kMaxMessageSize); - for (size_t i = 0; i < kMaxMessageSize; i++) { - context[i] = i % 0x100; - } - static const uint32_t SignatureBufferMaxLength = 256; - vector signature(SignatureBufferMaxLength); - size_t signature_length = signature.size(); - - OEMCryptoResult sts; - sts = OEMCrypto_GenerateSignature(s.session_id(), context.data(), - context.size(), signature.data(), - &signature_length); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - - static const uint32_t SignatureExpectedLength = 32; - ASSERT_EQ(SignatureExpectedLength, signature_length); - signature.resize(signature_length); - - std::vector expected_signature; - s.ClientSignMessage(context, &expected_signature); - ASSERT_EQ(expected_signature, signature); +// This tests load license with an 8k license response. +TEST_P(OEMCryptoLicenseTest, LoadKeyLargeBuffer) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + const size_t max_size = GetResourceValue(kLargeMessageSize); + license_messages_.set_message_size(max_size); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } -// This tests LoadKeys with an 8k license response. -TEST_F(OEMCryptoSessionTests, LoadKeyLargeBuffer) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - s.set_message_size(kMaxMessageSize); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); +// Verify that you can't use LoadKeys on a v16 license. +TEST_F(OEMCryptoLicenseTestAPI16, UseWrongLoadAPI16) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + // After the license round trip was create for v16, we now tell it to use v15 + // so it call LoadKeys instead of LoadLicense. This means the license request + // was made from a v16 device, and the response was created and signed by a + // v16 server. So OEMCrypto should only accept it if we load it using + // LoadLicense. A call to LoadKeys should fail. + license_messages_.set_api_version(kCoreMessagesAPI - 1); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } -// Returns a string containing two times the original message in continuous -// memory. Used as part of the BadRange tests. -std::string DuplicateMessage(MessageData& message) { - std::string single_message = wvcdm::BytesToString( - reinterpret_cast(&message), sizeof(message)); - std::string double_message = single_message + single_message; - return double_message; +//---------------------------------------------------------------------------// +//---------------------------------------------------------------------------// +// Each of the following LoadKeyWithBadRange_* tests is similar. They verify +// that OEMCrypto_LoadLicense checks the range of all the pointers. It should +// reject a message if the pointer does not point into the message buffer. +//---------------------------------------------------------------------------// +//---------------------------------------------------------------------------// +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_enc_mac_keys) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // See the comment in LicenseRoundTrip::LoadResponse for why we increment by + // the message size. + license_messages_.core_response().enc_mac_keys.offset += + sizeof(license_messages_.response_data()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } -// The Bad Range tests verify that OEMCrypto_LoadKeys checks the range -// of all the pointers. It should reject a message if the pointer does -// not point into the message buffer. -TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange1) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - std::string double_message = DuplicateMessage(s.encrypted_license()); - OEMCrypto_Substring wrong_mac_keys = s.enc_mac_keys_substr(); - wrong_mac_keys.offset += s.message_size(); - - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), reinterpret_cast(double_message.data()), - s.message_size(), s.signature().data(), s.signature().size(), - s.enc_mac_keys_iv_substr(), - wrong_mac_keys, // Not within range of one message. - s.num_keys(), s.key_array(), GetSubstring(), GetSubstring(), - OEMCrypto_ContentLicense); - ASSERT_NE(OEMCrypto_SUCCESS, sts); +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_enc_mac_keys_iv) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // See the comment in LicenseRoundTrip::LoadResponse for why we increment by + // the message size. + license_messages_.core_response().enc_mac_keys_iv.offset += + sizeof(license_messages_.response_data()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } -// The Bad Range tests verify that OEMCrypto_LoadKeys checks the range -// of all the pointers. It should reject a message if the pointer does -// not point into the message buffer. -TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange2) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - std::string double_message = DuplicateMessage(s.encrypted_license()); - OEMCrypto_Substring wrong_mac_keys_iv = s.enc_mac_keys_iv_substr(); - wrong_mac_keys_iv.offset += s.message_size(); - - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), reinterpret_cast(double_message.data()), - s.message_size(), s.signature().data(), s.signature().size(), - wrong_mac_keys_iv, // bad. - s.enc_mac_keys_substr(), s.num_keys(), s.key_array(), GetSubstring(), - GetSubstring(), OEMCrypto_ContentLicense); - ASSERT_NE(OEMCrypto_SUCCESS, sts); +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_id) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // See the comment in LicenseRoundTrip::LoadResponse for why we increment by + // the message size. + license_messages_.core_response().key_array[0].key_id.offset += + sizeof(license_messages_.response_data()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } -// The Bad Range tests verify that OEMCrypto_LoadKeys checks the range -// of all the pointers. It should reject a message if the pointer does -// not point into the message buffer. -TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange3) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - std::string double_message = DuplicateMessage(s.encrypted_license()); - s.key_array()[0].key_id.offset += s.message_size(); - - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), reinterpret_cast(double_message.data()), - s.message_size(), s.signature().data(), s.signature().size(), - s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), s.num_keys(), - s.key_array(), GetSubstring(), GetSubstring(), OEMCrypto_ContentLicense); - ASSERT_NE(OEMCrypto_SUCCESS, sts); +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_data) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // See the comment in LicenseRoundTrip::LoadResponse for why we increment by + // the message size. + license_messages_.core_response().key_array[1].key_data.offset += + sizeof(license_messages_.response_data()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } -// The Bad Range tests verify that OEMCrypto_LoadKeys checks the range -// of all the pointers. It should reject a message if the pointer does -// not point into the message buffer. -TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange4) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - - std::string double_message = DuplicateMessage(s.encrypted_license()); - s.key_array()[1].key_data.offset += s.message_size(); - - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), reinterpret_cast(double_message.data()), - s.message_size(), s.signature().data(), s.signature().size(), - s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), s.num_keys(), - s.key_array(), GetSubstring(), GetSubstring(), OEMCrypto_ContentLicense); - ASSERT_NE(OEMCrypto_SUCCESS, sts); +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_data_iv) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // See the comment in LicenseRoundTrip::LoadResponse for why we increment by + // the message size. + license_messages_.core_response().key_array[1].key_data_iv.offset += + sizeof(license_messages_.response_data()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } -// The Bad Range tests verify that OEMCrypto_LoadKeys checks the range -// of all the pointers. It should reject a message if the pointer does -// not point into the message buffer. -TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange5) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - std::string double_message = DuplicateMessage(s.encrypted_license()); - s.key_array()[1].key_data_iv.offset += s.message_size(); - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), reinterpret_cast(double_message.data()), - s.message_size(), s.signature().data(), s.signature().size(), - s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), s.num_keys(), - s.key_array(), GetSubstring(), GetSubstring(), OEMCrypto_ContentLicense); - ASSERT_NE(OEMCrypto_SUCCESS, sts); +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_control) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // See the comment in LicenseRoundTrip::LoadResponse for why we increment by + // the message size. + license_messages_.core_response().key_array[2].key_control.offset += + sizeof(license_messages_.response_data()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } -// The Bad Range tests verify that OEMCrypto_LoadKeys checks the range -// of all the pointers. It should reject a message if the pointer does -// not point into the message buffer. -TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange6) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - - std::string double_message = DuplicateMessage(s.encrypted_license()); - s.key_array()[2].key_control.offset += s.message_size(); - - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), reinterpret_cast(double_message.data()), - s.message_size(), s.signature().data(), s.signature().size(), - s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), s.num_keys(), - s.key_array(), GetSubstring(), GetSubstring(), OEMCrypto_ContentLicense); - ASSERT_NE(OEMCrypto_SUCCESS, sts); +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_key_control_iv) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // See the comment in LicenseRoundTrip::LoadResponse for why we increment by + // the message size. + license_messages_.core_response().key_array[2].key_control_iv.offset += + sizeof(license_messages_.response_data()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } -// The Bad Range tests verify that OEMCrypto_LoadKeys checks the range -// of all the pointers. It should reject a message if the pointer does -// not point into the message buffer. -TEST_F(OEMCryptoSessionTests, LoadKeyWithBadRange7) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - std::string double_message = DuplicateMessage(s.encrypted_license()); - s.key_array()[2].key_control_iv.offset += s.message_size(); - - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), reinterpret_cast(double_message.data()), - s.message_size(), s.signature().data(), s.signature().size(), - s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), s.num_keys(), - s.key_array(), GetSubstring(), GetSubstring(), OEMCrypto_ContentLicense); - ASSERT_NE(OEMCrypto_SUCCESS, sts); +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadRange_pst) { + license_messages_.set_control(wvoec::kControlNonceOrEntry); + license_messages_.set_pst("my_pst"); + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // See the comment in LicenseRoundTrip::LoadResponse for why we increment by + // the message size. + license_messages_.core_response().pst.offset += + sizeof(license_messages_.response_data()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + // If we have a pst, then we need a usage entry. + ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } +//---------------------------------------------------------------------------// +//---------------------------------------------------------------------------// // The IV should not be identical to the data right before the encrypted mac -// keys. -TEST_F(OEMCryptoSessionTests, LoadKeyWithSuspiciousIV) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); +// keys. This requirement was added in 15.2, so it frequently fails on +// production devices. +TEST_F(OEMCryptoLicenseTestAPI15, LoadKeyWithSuspiciousIV) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // This is suspicious: the data right before the mac keys is identical to the // iv. - memcpy(s.license().padding, s.license().mac_key_iv, - sizeof(s.license().padding)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); + memcpy(license_messages_.response_data().padding, + license_messages_.response_data().mac_key_iv, + sizeof(license_messages_.response_data().padding)); - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), s.message_ptr(), s.message_size(), s.signature().data(), - s.signature().size(), s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), GetSubstring(), GetSubstring(), - OEMCrypto_ContentLicense); - ASSERT_NE(OEMCrypto_SUCCESS, sts); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } // Test that LoadKeys fails when a key is loaded with no key control block. -TEST_F(OEMCryptoSessionTests, LoadKeyWithNullKeyControl) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - s.key_array()[2].key_control.offset = 0; - s.key_array()[2].key_control.length = 0; - - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), s.message_ptr(), s.message_size(), s.signature().data(), - s.signature().size(), s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), GetSubstring(), GetSubstring(), - OEMCrypto_ContentLicense); - ASSERT_NE(OEMCrypto_SUCCESS, sts); +TEST_P(OEMCryptoLicenseTest, LoadKeyWithNullKeyControl) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + license_messages_.core_response().key_array[2].key_control.offset = 0; + license_messages_.core_response().key_array[2].key_control.length = 0; + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } // Test that LoadKeys fails when the key control block encryption has a null IV. -TEST_F(OEMCryptoSessionTests, LoadKeyWithNullKeyControlIv) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - s.key_array()[2].key_control_iv.offset = 0; - s.key_array()[2].key_control_iv.length = 0; - - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), s.message_ptr(), s.message_size(), s.signature().data(), - s.signature().size(), s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), GetSubstring(), GetSubstring(), - OEMCrypto_ContentLicense); - ASSERT_NE(OEMCrypto_SUCCESS, sts); +TEST_P(OEMCryptoLicenseTest, LoadKeyWithNullKeyControlIv) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + license_messages_.core_response().key_array[2].key_control_iv.offset = 0; + license_messages_.core_response().key_array[2].key_control_iv.length = 0; + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } -// Verify that LoadKeys fails when a key's nonce is not in the table. -TEST_F(OEMCryptoSessionTests, LoadKeyWithBadNonce) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, wvoec::kControlNonceEnabled, - 42)); // bad nonce. - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), s.message_ptr(), s.message_size(), s.signature().data(), - s.signature().size(), s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), GetSubstring(), GetSubstring(), - OEMCrypto_ContentLicense); +// Verify that LoadKeys fails when a key's nonce is wrong. +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadNonce) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + for (unsigned int i = 0; i < license_messages_.num_keys(); i++) + license_messages_.response_data().keys[i].control.nonce ^= 42; + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages_.LoadResponse()); +} - ASSERT_NE(OEMCrypto_SUCCESS, sts); +// Verify that LoadKeys fails when the core message's nonce is wrong. +TEST_F(OEMCryptoLicenseTestAPI16, LoadKeyWithBadNonce2) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + license_messages_.core_request().nonce ^= 42; + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages_.LoadResponse()); +} + +// Verify that LoadKeys fails when the core message's session is wrong. +TEST_F(OEMCryptoLicenseTestAPI16, LoadKeyWithBadNonce3) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + license_messages_.core_request().session_id++; + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages_.LoadResponse()); } // Verify that LoadKeys fails when an attempt is made to use a nonce twice. -TEST_F(OEMCryptoSessionTests, LoadKeyWithRepeatNonce) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - uint32_t nonce = s.get_nonce(); - ASSERT_NO_FATAL_FAILURE( - s.FillSimpleMessage(0, wvoec::kControlNonceEnabled, nonce)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); +TEST_P(OEMCryptoLicenseTest, LoadKeyWithRepeatNonce) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + const uint32_t nonce = session_.nonce(); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); // This is the first attempt. It should succeed. - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); - ASSERT_NO_FATAL_FAILURE(s.close()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, wvoec::kControlNonceEnabled, - nonce)); // same old nonce. - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - // This is the second attempt to load the keys with a repeated nonce. It - // should fail. - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), s.message_ptr(), s.message_size(), s.signature().data(), - s.signature().size(), s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), GetSubstring(), GetSubstring(), - OEMCrypto_ContentLicense); - - ASSERT_NE(OEMCrypto_SUCCESS, sts); + // Now, open a new session and try to load a license with the same nonce. + session_.close(); + ASSERT_NO_FATAL_FAILURE(session_.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&session_)); + license_messages_.skip_nonce_check(); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + // Repeat the nonce. + license_messages_.core_request().nonce = nonce; + for (unsigned int i = 0; i < license_messages_.num_keys(); i++) + license_messages_.response_data().keys[i].control.nonce = htonl(nonce); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages_.LoadResponse()); } // This tests that a nonce cannot be used in new session. This is similar to // the previous test, but does not use the nonce in the first session. The nonce -// table should be tied to a session, so generating a nonce in the first session -// and then using it in the second session should fail. -TEST_F(OEMCryptoSessionTests, LoadKeyNonceReopenSession) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - uint32_t nonce = s.get_nonce(); +// should be tied to a session, so generating a nonce in the first session and +// then using it in the second session should fail. +TEST_P(OEMCryptoLicenseTest, LoadKeyNonceReopenSession) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + uint32_t nonce = session_.nonce(); // Do not use the nonce now. Close session and use it after re-opening. - ASSERT_NO_FATAL_FAILURE(s.close()); + ASSERT_NO_FATAL_FAILURE(session_.close()); // Actually, this isn't the same session. OEMCrypto opens a new session, but // we are guarding against the possiblity that it re-uses the session data - // and might not clear out the nonce table correctly. - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, wvoec::kControlNonceEnabled, - nonce)); // same old nonce - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), s.message_ptr(), s.message_size(), s.signature().data(), - s.signature().size(), s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), GetSubstring(), GetSubstring(), - OEMCrypto_ContentLicense); - - ASSERT_NE(OEMCrypto_SUCCESS, sts); + // and might not clear out the nonce correctly. + ASSERT_NO_FATAL_FAILURE(session_.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&session_)); + license_messages_.skip_nonce_check(); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + license_messages_.core_request().nonce = nonce; + for (unsigned int i = 0; i < license_messages_.num_keys(); i++) + license_messages_.response_data().keys[i].control.nonce = htonl(nonce); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages_.LoadResponse()); } // This tests that a nonce cannot be used in wrong session. This is similar to // the previous test, except we do not close session 1 before we open session 2. -TEST_F(OEMCryptoSessionTests, LoadKeyNonceWrongSession) { - Session s1; - ASSERT_NO_FATAL_FAILURE(s1.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s1)); - uint32_t nonce = s1.get_nonce(); - // Do not use the nonce. Also, leave the session open. We want to make sure - // that s and s1 do NOT share a nonce table. This is different from the - // LoadKeyNonceReopenSession in that we do not close s1. - +TEST_P(OEMCryptoLicenseTest, LoadKeyNonceWrongSession) { + // First, open a session and generate a nonce. We don't use the nonce in this + // session. Session s2; ASSERT_NO_FATAL_FAILURE(s2.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s2)); - ASSERT_NO_FATAL_FAILURE(s2.FillSimpleMessage(0, wvoec::kControlNonceEnabled, - nonce)); // nonce from session s1 - ASSERT_NO_FATAL_FAILURE(s2.EncryptAndSign()); - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s2.session_id(), s2.message_ptr(), s2.message_size(), - s2.signature().data(), s2.signature().size(), s2.enc_mac_keys_iv_substr(), - s2.enc_mac_keys_substr(), s2.num_keys(), s2.key_array(), GetSubstring(), - GetSubstring(), OEMCrypto_ContentLicense); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s2)); + ASSERT_NO_FATAL_FAILURE(s2.GenerateNonce()); + uint32_t nonce = s2.nonce(); - ASSERT_NE(OEMCrypto_SUCCESS, sts); + // Do not use the nonce. Also, leave the session open. We want to make sure + // that session_ and s2 do NOT share a nonce. This is different from + // the LoadKeyNonceReopenSession in that we do not close s1. + + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + license_messages_.core_request().nonce = nonce; + for (unsigned int i = 0; i < license_messages_.num_keys(); i++) + license_messages_.response_data().keys[i].control.nonce = htonl(nonce); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages_.LoadResponse()); } // LoadKeys should fail if the key control block as a bad verification string. -TEST_F(OEMCryptoSessionTests, LoadKeyWithBadVerification) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - s.license().keys[1].control.verification[2] = 'Z'; - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), s.message_ptr(), s.message_size(), s.signature().data(), - s.signature().size(), s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), GetSubstring(), GetSubstring(), - OEMCrypto_ContentLicense); - - ASSERT_NE(OEMCrypto_SUCCESS, sts); +TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadVerification) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + license_messages_.response_data().keys[1].control.verification[2] = 'Z'; + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } // This test verifies that LoadKeys still works when the message is not aligned // in memory on a word (2 or 4 byte) boundary. -TEST_F(OEMCryptoSessionTests, LoadKeyUnalignedMessage) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - kDuration, wvoec::kControlNonceEnabled, s.get_nonce())); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); +TEST_P(OEMCryptoLicenseTest, LoadKeyUnalignedMessageAPI16) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + std::vector buffer(1, '0'); // A string of 1 byte long. size_t offset = buffer.size(); ASSERT_EQ(1u, offset); // We assume that vectors are allocated on as a small chunk of data that is // aligned on a word boundary. I.e. we assume buffer is word aligned. Next, // we append the message to buffer after the single padding byte. - buffer.insert(buffer.end(), s.message_ptr(), - s.message_ptr() + s.message_size()); + buffer.insert(buffer.end(), + license_messages_.encrypted_response_buffer().begin(), + license_messages_.encrypted_response_buffer().end()); // Thus, buffer[offset] is NOT word aligned. const uint8_t* unaligned_message = &buffer[offset]; - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), unaligned_message, s.message_size(), s.signature().data(), - s.signature().size(), s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), GetSubstring(), GetSubstring(), - OEMCrypto_ContentLicense); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); + if (license_api_version_ < kCoreMessagesAPI) { + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys( + session_.session_id(), unaligned_message, + license_messages_.encrypted_response_buffer().size(), + license_messages_.response_signature().data(), + license_messages_.response_signature().size(), + license_messages_.core_response().enc_mac_keys_iv, + license_messages_.core_response().enc_mac_keys, + license_messages_.core_response().key_array_length, + license_messages_.core_response().key_array, + license_messages_.core_response().pst, + license_messages_.core_response().srm_restriction_data, + static_cast( + license_messages_.core_response().license_type))); + } else { + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadLicense( + session_.session_id(), unaligned_message, + license_messages_.encrypted_response_buffer().size(), + license_messages_.serialized_core_message().size(), + license_messages_.response_signature().data(), + license_messages_.response_signature().size())); + } } -// This tests each key control block verification string in the range kc09-kc1?. -// This test is parameterized by the API number in the key control lock. -class SessionTestAlternateVerification : public OEMCryptoSessionTests, - public WithParamInterface { - public: - void SetUp() override { - OEMCryptoSessionTests::SetUp(); - target_api_ = static_cast(GetParam()); - } +// Verifies that a session can't reload a license without being closed and +// reopened. +TEST_P(OEMCryptoLicenseTest, LoadLicenseAgainFailureAPI16) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + ASSERT_EQ(OEMCrypto_ERROR_LICENSE_RELOAD, license_messages_.LoadResponse()); +} - protected: - uint32_t target_api_; -}; +TEST_P(OEMCryptoLicenseTestRangeAPI, LoadKeys) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + // Re-set the API version. The function VerifyRequestSignature sets the api to + // be a sane value. But in this test, we want to verify an unsupported version + // is rejected. + license_messages_.set_api_version(license_api_version_); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); -TEST_P(SessionTestAlternateVerification, LoadKeys) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - char buffer[5] = "kctl"; // This is the default verification string, required - // for all API versions. - if (target_api_ > 8 && target_api_ < 100) { - snprintf(buffer, 5, "kc%02d", target_api_); - } - for (unsigned int i = 0; i < s.num_keys(); i++) { - memcpy(s.license().keys[i].control.verification, buffer, 4); - } - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), s.message_ptr(), s.message_size(), s.signature().data(), - s.signature().size(), s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), GetSubstring(), GetSubstring(), - OEMCrypto_ContentLicense); // If this is a future API, then LoadKeys should fail. - if (global_features.api_version < target_api_) { - ASSERT_NE(OEMCrypto_SUCCESS, sts); + if (global_features.api_version < license_api_version_) { + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()) + << "Load License succeeded for future api kc" << license_api_version_; } else { // Otherwise, LoadKeys should succeed. - ASSERT_EQ(OEMCrypto_SUCCESS, sts) - << "LoadKeys failed for key control block kc" << target_api_; + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()) + << "Load License failed for key control block kc" + << license_api_version_; } } -// Range of API versions to test. This should start at 8, and go to -// the current API + 2. We use +2 because we want to test at least 1 +// Range of API versions to test. This should start several versions old, and +// go to the current API + 2. We use +2 because we want to test at least 1 // future API, and the ::testing::Range is not inclusive. -INSTANTIATE_TEST_CASE_P(TestAll, SessionTestAlternateVerification, - Range(8, 15 + 2)); +INSTANTIATE_TEST_CASE_P(TestAll, OEMCryptoLicenseTestRangeAPI, + Range(10, kCurrentAPI + 2)); -TEST_F(OEMCryptoSessionTests, LoadKeysBadSignature) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - s.signature()[0] ^= 42; // Bad signature. - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), s.message_ptr(), s.message_size(), s.signature().data(), - s.signature().size(), s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), GetSubstring(), GetSubstring(), - OEMCrypto_ContentLicense); - ASSERT_NE(OEMCrypto_SUCCESS, sts); +TEST_P(OEMCryptoLicenseTest, LoadKeysBadSignature) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + license_messages_.response_signature()[0] ^= 42; + ASSERT_EQ(OEMCrypto_ERROR_SIGNATURE_FAILURE, + license_messages_.LoadResponse()); } -// We should not be able to load keys if we haven't derived the mac keys. -TEST_F(OEMCryptoSessionTests, LoadKeysWithNoDerivedKeys) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - // don't do this: InstallTestSessionKeys(&s). - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), s.message_ptr(), s.message_size(), s.signature().data(), - s.signature().size(), s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), GetSubstring(), GetSubstring(), - OEMCrypto_ContentLicense); - ASSERT_NE(OEMCrypto_SUCCESS, sts); +TEST_F(OEMCryptoLicenseTestAPI16, BadCoreHashAPI16) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + license_messages_.BreakRequestHash(); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } // LoadKeys should fail if we try to load keys with no keys. -TEST_F(OEMCryptoSessionTests, LoadKeyNoKeys) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 42)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - int kNoKeys = 0; - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys( - s.session_id(), s.message_ptr(), s.message_size(), - s.signature().data(), s.signature().size(), GetSubstring(), - GetSubstring(), kNoKeys, s.key_array(), GetSubstring(), - GetSubstring(), OEMCrypto_ContentLicense)); +TEST_P(OEMCryptoLicenseTest, LoadKeyNoKeys) { + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.set_control(0); + license_messages_.set_num_keys(0); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } // Like the previous test, except we ask for a nonce first. -TEST_F(OEMCryptoSessionTests, LoadKeyNoKeyWithNonce) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE( - s.FillSimpleMessage(0, wvoec::kControlNonceEnabled, s.get_nonce())); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - int kNoKeys = 0; - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys( - s.session_id(), s.message_ptr(), s.message_size(), - s.signature().data(), s.signature().size(), GetSubstring(), - GetSubstring(), kNoKeys, s.key_array(), GetSubstring(), - GetSubstring(), OEMCrypto_ContentLicense)); +TEST_P(OEMCryptoLicenseTest, LoadKeyNoKeyWithNonce) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.set_num_keys(0); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } // SelectKey should fail if we attempt to select a key that has not been loaded. // Also, the error should be NO_CONTENT_KEY. -TEST_F(OEMCryptoSessionTests, SelectKeyNotThereAPI15) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE( - s.FillSimpleMessage(0, wvoec::kControlNonceEnabled, s.get_nonce())); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); +TEST_P(OEMCryptoLicenseTest, SelectKeyNotThereAPI15) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + const char* key_id = "no_key"; OEMCryptoResult sts = OEMCrypto_SelectKey( - s.session_id(), reinterpret_cast(key_id), strlen(key_id), - OEMCrypto_CipherMode_CTR); + session_.session_id(), reinterpret_cast(key_id), + strlen(key_id), OEMCrypto_CipherMode_CTR); if (sts != OEMCrypto_SUCCESS) { EXPECT_EQ(OEMCrypto_ERROR_NO_CONTENT_KEY, sts); } else { // Delayed error code. If select key was a success, then we should // eventually see the error when we decrypt. vector in_buffer(256); - for (size_t i = 0; i < in_buffer.size(); i++) in_buffer[i] = i % 256; - vector encryptionIv(AES_BLOCK_SIZE); - EXPECT_EQ(1, GetRandBytes(encryptionIv.data(), AES_BLOCK_SIZE)); - // Describe the output vector out_buffer(in_buffer.size()); - const bool is_encrypted = true; - OEMCrypto_DestBufferDesc destBuffer; - destBuffer.type = OEMCrypto_BufferType_Clear; - destBuffer.buffer.clear.address = out_buffer.data(); - destBuffer.buffer.clear.max_length = out_buffer.size(); - OEMCrypto_CENCEncryptPatternDesc pattern; - pattern.encrypt = 0; - pattern.skip = 0; - pattern.offset = 0; - // Decrypt the data - sts = OEMCrypto_DecryptCENC( - s.session_id(), in_buffer.data(), in_buffer.size(), is_encrypted, - encryptionIv.data(), 0, &destBuffer, &pattern, - OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); - EXPECT_EQ(OEMCrypto_ERROR_NO_CONTENT_KEY, sts); + OEMCrypto_SampleDescription sample_description; + OEMCrypto_SubSampleDescription subsample_description; + + ASSERT_NO_FATAL_FAILURE(GenerateSimpleSampleDescription( + in_buffer, out_buffer, &sample_description, &subsample_description)); + + // Generate test data + for (size_t i = 0; i < in_buffer.size(); i++) in_buffer[i] = i % 256; + + // Create the pattern description (always 0,0 for CTR) + OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0}; + + // Try to decrypt the data + sts = OEMCrypto_DecryptCENC(session_.session_id(), &sample_description, 1, + &pattern); + EXPECT_EQ(sts, OEMCrypto_ERROR_NO_CONTENT_KEY); } } +// 'cens' mode is no longer supported in v16 +TEST_P(OEMCryptoLicenseTest, RejectCensAPI16) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + OEMCryptoResult sts; + sts = OEMCrypto_SelectKey( + session_.session_id(), session_.license().keys[0].key_id, + session_.license().keys[0].key_id_length, OEMCrypto_CipherMode_CTR); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + vector in_buffer(256); + vector out_buffer(in_buffer.size()); + OEMCrypto_SampleDescription sample_description; + OEMCrypto_SubSampleDescription subsample_description; + + ASSERT_NO_FATAL_FAILURE(GenerateSimpleSampleDescription( + in_buffer, out_buffer, &sample_description, &subsample_description)); + + // Create a non-zero pattern to indicate this is 'cens' + OEMCrypto_CENCEncryptPatternDesc pattern = {1, 9}; + + // Try to decrypt the data + sts = OEMCrypto_DecryptCENC(session_.session_id(), &sample_description, 1, + &pattern); + EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts); +} + +// 'cbc1' mode is no longer supported in v16 +TEST_P(OEMCryptoLicenseTest, RejectCbc1API16) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + OEMCryptoResult sts; + sts = OEMCrypto_SelectKey( + session_.session_id(), session_.license().keys[0].key_id, + session_.license().keys[0].key_id_length, OEMCrypto_CipherMode_CBC); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + vector in_buffer(256); + vector out_buffer(in_buffer.size()); + OEMCrypto_SampleDescription sample_description; + OEMCrypto_SubSampleDescription subsample_description; + + ASSERT_NO_FATAL_FAILURE(GenerateSimpleSampleDescription( + in_buffer, out_buffer, &sample_description, &subsample_description)); + + // Create a zero pattern to indicate this is 'cbc1' + OEMCrypto_CENCEncryptPatternDesc pattern = {0, 0}; + + // Try to decrypt the data + sts = OEMCrypto_DecryptCENC(session_.session_id(), &sample_description, 1, + &pattern); + EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts); +} + +TEST_P(OEMCryptoLicenseTest, RejectCbcsWithBlockOffset) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + OEMCryptoResult sts; + sts = OEMCrypto_SelectKey( + session_.session_id(), session_.license().keys[0].key_id, + session_.license().keys[0].key_id_length, OEMCrypto_CipherMode_CBC); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + vector in_buffer(256); + vector out_buffer(in_buffer.size()); + OEMCrypto_SampleDescription sample_description; + OEMCrypto_SubSampleDescription subsample_description; + + ASSERT_NO_FATAL_FAILURE(GenerateSimpleSampleDescription( + in_buffer, out_buffer, &sample_description, &subsample_description)); + subsample_description.block_offset = 5; // Any value 1-15 will do. + + // Create a non-zero pattern to indicate this is 'cbcs'. + OEMCrypto_CENCEncryptPatternDesc pattern = {1, 9}; + + // Try to decrypt the data + sts = OEMCrypto_DecryptCENC(session_.session_id(), &sample_description, 1, + &pattern); + EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts); +} + // After loading keys, we should be able to query the key control block. If we // attempt to query a key that has not been loaded, the error should be // NO_CONTENT_KEY. -TEST_F(OEMCryptoSessionTests, QueryKeyControl) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE( - s.FillSimpleMessage(0, wvoec::kControlNonceEnabled, s.get_nonce())); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); +TEST_P(OEMCryptoLicenseTest, QueryKeyControl) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + // Note: successful cases are tested in VerifyTestKeys. KeyControlBlock block; size_t size = sizeof(block) - 1; - OEMCryptoResult sts = - OEMCrypto_QueryKeyControl(s.session_id(), s.license().keys[0].key_id, - s.license().keys[0].key_id_length, - reinterpret_cast(&block), &size); + OEMCryptoResult sts = OEMCrypto_QueryKeyControl( + session_.session_id(), license_messages_.response_data().keys[0].key_id, + license_messages_.response_data().keys[0].key_id_length, + reinterpret_cast(&block), &size); if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { return; } @@ -1632,25 +1515,20 @@ TEST_F(OEMCryptoSessionTests, QueryKeyControl) { size = sizeof(block); ASSERT_EQ(OEMCrypto_ERROR_NO_CONTENT_KEY, OEMCrypto_QueryKeyControl( - s.session_id(), reinterpret_cast(key_id), + session_.session_id(), reinterpret_cast(key_id), strlen(key_id), reinterpret_cast(&block), &size)); } // If the device says it supports anti-rollback in the hardware, then it should -// accept a key control block the anti-rollback hardware bit set. Otherwise, it -// should reject that key control block. -TEST_F(OEMCryptoSessionTests, AntiRollbackHardwareRequired) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec::kControlRequireAntiRollbackHardware, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), s.message_ptr(), s.message_size(), s.signature().data(), - s.signature().size(), s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), GetSubstring(), GetSubstring(), - OEMCrypto_ContentLicense); +// accept a key control block with the anti-rollback hardware bit set. +// Otherwise, it should reject that key control block. +TEST_P(OEMCryptoLicenseTest, AntiRollbackHardwareRequired) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.set_control(wvoec::kControlRequireAntiRollbackHardware); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + OEMCryptoResult sts = license_messages_.LoadResponse(); if (OEMCrypto_IsAntiRollbackHwPresent()) { ASSERT_EQ(OEMCrypto_SUCCESS, sts); } else { @@ -1658,6 +1536,85 @@ TEST_F(OEMCryptoSessionTests, AntiRollbackHardwareRequired) { } } +// This test verifies that OEMCrypto can load the number of keys required for +// the reported resource level. +TEST_P(OEMCryptoLicenseTest, MinimumKeys) { + const size_t num_keys = GetResourceValue(kMaxKeysPerSession); + ASSERT_LE(num_keys, kMaxNumKeys) << "Test constants need updating."; + license_messages_.set_num_keys(num_keys); + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + constexpr bool kSelectKeyFirst = true; + for (size_t key_index = 0; key_index < num_keys; key_index++) { + ASSERT_NO_FATAL_FAILURE( + session_.TestDecryptCTR(kSelectKeyFirst, OEMCrypto_SUCCESS, key_index)); + } +} + +// This test verifies that OEMCrypto can load the total number of keys required +// for the reported resource level. +void TestMaxKeys(SessionUtil* util, size_t num_keys_per_session) { + const size_t max_total_keys = GetResourceValue(kMaxTotalKeys); + ASSERT_LE(num_keys_per_session, kMaxNumKeys) << "Update test constants."; + std::vector> sessions; + std::vector> licenses; + size_t total_keys = 0; + for (size_t i = 0; total_keys < max_total_keys; i++) { + sessions.push_back(std::unique_ptr(new Session())); + licenses.push_back(std::unique_ptr( + new LicenseRoundTrip(sessions[i].get()))); + const size_t num_keys = + std::min(max_total_keys - total_keys, num_keys_per_session); + licenses[i]->set_num_keys(num_keys); + total_keys += num_keys; + ASSERT_NO_FATAL_FAILURE(sessions[i]->open()); + ASSERT_NO_FATAL_FAILURE(util->InstallTestRSAKey(sessions[i].get())); + ASSERT_NO_FATAL_FAILURE(sessions[i]->GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(licenses[i]->SignAndVerifyRequest()); + } + for (size_t i = 0; i < licenses.size(); i++) { + ASSERT_NO_FATAL_FAILURE(licenses[i]->CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(licenses[i]->EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, licenses[i]->LoadResponse()); + } + constexpr bool kSelectKeyFirst = true; + for (size_t i = 0; i < licenses.size(); i++) { + for (size_t key_index = 0; key_index < licenses[i]->num_keys(); + key_index++) { + ASSERT_NO_FATAL_FAILURE(sessions[i]->TestDecryptCTR( + kSelectKeyFirst, OEMCrypto_SUCCESS, key_index)); + } + } + // Second call to decrypt for each session. + for (size_t i = 0; i < licenses.size(); i++) { + for (size_t key_index = 0; key_index < licenses[i]->num_keys(); + key_index++) { + ASSERT_NO_FATAL_FAILURE(sessions[i]->TestDecryptCTR( + kSelectKeyFirst, OEMCrypto_SUCCESS, key_index)); + } + } +} + +// This test verifies that OEMCrypto can load the total number of keys required +// for the reported resource level. This maximizes keys per session. +TEST_P(OEMCryptoLicenseTest, MaxTotalKeysPerSession) { + const size_t max_num_keys = GetResourceValue(kMaxKeysPerSession); + TestMaxKeys(this, max_num_keys); +} + +// This test verifies that OEMCrypto can load the total number of keys required +// for the reported resource level. This maximizes number of sessions. +TEST_P(OEMCryptoLicenseTest, MaxTotalKeysManySessions) { + const size_t max_total_keys = GetResourceValue(kMaxTotalKeys); + const size_t max_sessions = GetResourceValue(kMaxConcurrentSession); + const size_t max_num_keys = max_total_keys / max_sessions + 1; + TestMaxKeys(this, max_num_keys); +} + // This test verifies that the minimum patch level can be required. The device // should accept a key control block with the current patch level, and it should // reject any key control blocks with a future patch level. @@ -1667,76 +1624,49 @@ TEST_F(OEMCryptoSessionTests, CheckMinimumPatchLevel) { { Session s; ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, patch_level << wvoec::kControlSecurityPatchLevelShift, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_EQ( - OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys(s.session_id(), s.message_ptr(), s.message_size(), - s.signature().data(), s.signature().size(), - s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), GetSubstring(), - GetSubstring(), OEMCrypto_ContentLicense)); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + LicenseRoundTrip license_messages(&s); + license_messages.set_control(patch_level + << wvoec::kControlSecurityPatchLevelShift); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + EXPECT_EQ(global_features.api_version, license_messages.api_version()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages.LoadResponse()); } // Reject any future patch levels. if (patch_level < 0x3F) { Session s; ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, (patch_level + 1) << wvoec::kControlSecurityPatchLevelShift, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_EQ( - OEMCrypto_ERROR_UNKNOWN_FAILURE, - OEMCrypto_LoadKeys(s.session_id(), s.message_ptr(), s.message_size(), - s.signature().data(), s.signature().size(), - s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), GetSubstring(), - GetSubstring(), OEMCrypto_ContentLicense)); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + LicenseRoundTrip license_messages(&s); + license_messages.set_control((patch_level + 1) + << wvoec::kControlSecurityPatchLevelShift); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_UNKNOWN_FAILURE, license_messages.LoadResponse()); } // Accept an old patch level. if (patch_level > 0) { Session s; ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, (patch_level - 1) << wvoec::kControlSecurityPatchLevelShift, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_EQ( - OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys(s.session_id(), s.message_ptr(), s.message_size(), - s.signature().data(), s.signature().size(), - s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), GetSubstring(), - GetSubstring(), OEMCrypto_ContentLicense)); - } -} - -// This test verifies that OEMCrypto can load the number of keys required for -// the reported resource level. -TEST_F(OEMCryptoSessionTests, MinimumKeysAPI12) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - size_t num_keys = GetResourceValue(kMaxKeysPerSession); - ASSERT_LE(num_keys, kMaxNumKeys) << "Test constants need updating."; - s.set_num_keys(num_keys); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); - for (size_t key_index = 0; key_index < num_keys; key_index++) { - bool kSelectKeyFirst = true; - ASSERT_NO_FATAL_FAILURE( - s.TestDecryptCTR(kSelectKeyFirst, OEMCrypto_SUCCESS, key_index)); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + LicenseRoundTrip license_messages(&s); + license_messages.set_control((patch_level - 1) + << wvoec::kControlSecurityPatchLevelShift); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages.LoadResponse()); } } // Used to test the different HDCP versions. This test is parameterized by the // required HDCP version in the key control block. -class SessionTestDecryptWithHDCP : public OEMCryptoSessionTests, - public WithParamInterface { - public: +class OEMCryptoSessionTestDecryptWithHDCP : public OEMCryptoSessionTests, + public WithParamInterface { + protected: void DecryptWithHDCP(OEMCrypto_HDCP_Capability version) { OEMCryptoResult sts; OEMCrypto_HDCP_Capability current, maximum; @@ -1744,312 +1674,271 @@ class SessionTestDecryptWithHDCP : public OEMCryptoSessionTests, ASSERT_EQ(OEMCrypto_SUCCESS, sts); Session s; ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, - (version << wvoec::kControlHDCPVersionShift) | - wvoec::kControlObserveHDCP | wvoec::kControlHDCPRequired, - 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + LicenseRoundTrip license_messages(&s); + license_messages.set_control((version << wvoec::kControlHDCPVersionShift) | + wvoec::kControlObserveHDCP | + wvoec::kControlHDCPRequired); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages.LoadResponse()); - if (version > current) { + if (version > maximum) { ASSERT_NO_FATAL_FAILURE( s.TestDecryptCTR(true, OEMCrypto_ERROR_INSUFFICIENT_HDCP)); + } else if (version > current) { + if (global_features.api_version >= 16) { + // Can provide either OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION or + // OEMCrypto_ERROR_INSUFFICIENT_HDCP. TestDecryptCTR allows either to be + // reported if OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION is expected. + ASSERT_NO_FATAL_FAILURE( + s.TestDecryptCTR(true, OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION)); + } else { + ASSERT_NO_FATAL_FAILURE( + s.TestDecryptCTR(true, OEMCrypto_ERROR_INSUFFICIENT_HDCP)); + } } else { ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(true, OEMCrypto_SUCCESS)); } } }; -TEST_P(SessionTestDecryptWithHDCP, DecryptAPI09) { +TEST_P(OEMCryptoSessionTestDecryptWithHDCP, DecryptAPI09) { // Test parameterized by HDCP version. DecryptWithHDCP(static_cast(GetParam())); } -INSTANTIATE_TEST_CASE_P(TestHDCP, SessionTestDecryptWithHDCP, Range(1, 6)); +INSTANTIATE_TEST_CASE_P(TestHDCP, OEMCryptoSessionTestDecryptWithHDCP, + Range(1, 6)); // // Load, Refresh Keys Test // -// This test is parameterized by two parameters: -// 1. A boolean that determines if the license sets a new pair of mac keys in -// the license. -// 2. The number of keys refreshed in the refresh method. If the number of keys -// is zero, then all of the keys should be refreshed. -class SessionTestRefreshKeyTest - : public OEMCryptoSessionTests, - public WithParamInterface > { - public: +class OEMCryptoRefreshTest : public OEMCryptoLicenseTest { + protected: void SetUp() override { - OEMCryptoSessionTests::SetUp(); - new_mac_keys_ = - GetParam().first; // Whether to put new mac keys in LoadKeys. - num_keys_ = static_cast(GetParam().second); // # keys in refresh. + OEMCryptoLicenseTest::SetUp(); + // These values allow us to run a few simple duration tests or just start + // playback right away. All times are in seconds since the license was + // signed. + // Soft expiry false means timers are strictly enforce. + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.soft_enforce_playback_duration = false; + // Playback may begin immediately. + timer_limits_.earliest_playback_start_seconds = 0; + // First playback may be within the first two seconds. + timer_limits_.rental_duration_seconds = kDuration; + // Once started, playback may last two seconds without a renewal. + timer_limits_.initial_renewal_duration_seconds = kDuration; + // Total playback is not limited. + timer_limits_.total_playback_duration_seconds = 0; } - protected: - bool new_mac_keys_; - size_t num_keys_; // Number of keys to refresh. + void LoadLicense() { + // If we require a nonce, then generate one. + if (license_messages_.control() & + (wvoec::kControlNonceEnabled | wvoec::kControlNonceOrEntry | + wvoec::kControlNonceRequired)) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + } + license_messages_.core_response().timer_limits = timer_limits_; + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + } + + void MakeRenewalRequest(RenewalRoundTrip* renewal_messages) { + ASSERT_NO_FATAL_FAILURE(renewal_messages->SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(renewal_messages->CreateDefaultResponse()); + } + + void LoadRenewal(RenewalRoundTrip* renewal_messages, + OEMCryptoResult expected_result) { + ASSERT_NO_FATAL_FAILURE(renewal_messages->EncryptAndSignResponse()); + ASSERT_EQ(expected_result, renewal_messages->LoadResponse()); + } + + ODK_TimerLimits timer_limits_; }; +// This class is for the refresh tests that should only be run on licenses with +// a core message. +class OEMCryptoRefreshTestAPI16 : public OEMCryptoRefreshTest {}; + +// Refresh keys should work if the license uses a nonce. +TEST_P(OEMCryptoRefreshTest, RefreshWithNonce) { + LoadLicense(); + RenewalRoundTrip renewal_messages(&license_messages_); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); +} + // Refresh keys should work if the license does not use a nonce. -TEST_P(SessionTestRefreshKeyTest, RefreshWithNonce) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - kDuration, wvoec::kControlNonceEnabled, s.get_nonce())); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys("", new_mac_keys_)); - s.GenerateNonce(); - // License renewal message is signed by client and verified by the server. - ASSERT_NO_FATAL_FAILURE(s.VerifyClientSignature()); - ASSERT_NO_FATAL_FAILURE(s.RefreshTestKeys(num_keys_, - wvoec::kControlNonceEnabled, - s.get_nonce(), OEMCrypto_SUCCESS)); +TEST_P(OEMCryptoRefreshTest, RefreshNoNonce) { + license_messages_.set_control(0); + LoadLicense(); + RenewalRoundTrip renewal_messages(&license_messages_); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); } -// Refresh keys should work if the license does use a nonce. -TEST_P(SessionTestRefreshKeyTest, RefreshNoNonce) { +// Refresh keys should NOT work if a license has not been loaded. +TEST_P(OEMCryptoRefreshTestAPI16, RefreshNoLicense) { Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys("", new_mac_keys_)); - // License renewal message is signed by client and verified by the server. - ASSERT_NO_FATAL_FAILURE(s.VerifyClientSignature()); - ASSERT_NO_FATAL_FAILURE( - s.RefreshTestKeys(num_keys_, 0, 0, OEMCrypto_SUCCESS)); + s.open(); + constexpr size_t message_size = kMaxCoreMessage + 42; + std::vector data(message_size); + for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF; + size_t gen_signature_length = 0; + size_t core_message_length = 0; + OEMCryptoResult sts = OEMCrypto_PrepAndSignRenewalRequest( + s.session_id(), data.data(), data.size(), &core_message_length, nullptr, + &gen_signature_length); + ASSERT_LT(core_message_length, message_size); + if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { + vector gen_signature(gen_signature_length); + sts = OEMCrypto_PrepAndSignRenewalRequest( + s.session_id(), data.data(), data.size(), &core_message_length, + gen_signature.data(), &gen_signature_length); + } + ASSERT_NE(OEMCrypto_SUCCESS, sts); } -// Refresh keys should fail if the nonce has already been used. -TEST_P(SessionTestRefreshKeyTest, RefreshOldNonceAPI11) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - uint32_t nonce = s.get_nonce(); - ASSERT_NO_FATAL_FAILURE( - s.FillSimpleMessage(kDuration, wvoec::kControlNonceEnabled, nonce)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys("", new_mac_keys_)); - // License renewal message is signed by client and verified by the server. - ASSERT_NO_FATAL_FAILURE(s.VerifyClientSignature()); - // Tryinng to reuse the same nonce. - ASSERT_NO_FATAL_FAILURE( - s.RefreshTestKeys(num_keys_, wvoec::kControlNonceEnabled, nonce, - OEMCrypto_ERROR_INVALID_NONCE)); +// Refresh keys should fail if the nonce is not in the session. +TEST_P(OEMCryptoRefreshTestAPI16, RefreshBadNonce) { + LoadLicense(); + RenewalRoundTrip renewal_messages(&license_messages_); + MakeRenewalRequest(&renewal_messages); + renewal_messages.core_request().nonce ^= 42; + LoadRenewal(&renewal_messages, OEMCrypto_ERROR_INVALID_NONCE); } -// Refresh keys should fail if the nonce is not in the table. -TEST_P(SessionTestRefreshKeyTest, RefreshBadNonceAPI11) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - kDuration, wvoec::kControlNonceEnabled, s.get_nonce())); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys("", new_mac_keys_)); - s.GenerateNonce(); - // License renewal message is signed by client and verified by the server. - ASSERT_NO_FATAL_FAILURE(s.VerifyClientSignature()); - uint32_t nonce = s.get_nonce() ^ 42; - ASSERT_NO_FATAL_FAILURE( - s.RefreshTestKeys(num_keys_, wvoec::kControlNonceEnabled, nonce, - OEMCrypto_ERROR_INVALID_NONCE)); +// Refresh keys should fail if the session_id does not match the license. +TEST_P(OEMCryptoRefreshTestAPI16, RefreshBadSessionID) { + LoadLicense(); + RenewalRoundTrip renewal_messages(&license_messages_); + MakeRenewalRequest(&renewal_messages); + renewal_messages.core_request().session_id += 1; + LoadRenewal(&renewal_messages, OEMCrypto_ERROR_INVALID_NONCE); } // Refresh keys should handle the maximum message size. -TEST_P(SessionTestRefreshKeyTest, RefreshLargeBuffer) { - Session s; - s.set_message_size(kMaxMessageSize); - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - kDuration, wvoec::kControlNonceEnabled, s.get_nonce())); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys("", new_mac_keys_)); - s.GenerateNonce(); - // License renewal message is signed by client and verified by the server. - // This uses a large buffer for the renewal message. - ASSERT_NO_FATAL_FAILURE(s.VerifyClientSignature(kMaxMessageSize)); - ASSERT_NO_FATAL_FAILURE(s.RefreshTestKeys(num_keys_, - wvoec::kControlNonceEnabled, - s.get_nonce(), OEMCrypto_SUCCESS)); +TEST_P(OEMCryptoRefreshTest, RefreshLargeBuffer) { + LoadLicense(); + RenewalRoundTrip renewal_messages(&license_messages_); + const size_t max_size = GetResourceValue(kLargeMessageSize); + license_messages_.set_message_size(max_size); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); } // This situation would occur if an app only uses one key in the license. When // that happens, SelectKey would be called before the first decrypt, and then // would not need to be called again, even if the license is refreshed. -TEST_P(SessionTestRefreshKeyTest, RefreshWithNoSelectKey) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - kDuration, wvoec::kControlNonceEnabled, s.get_nonce())); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys("", new_mac_keys_)); +TEST_P(OEMCryptoRefreshTest, RefreshWithNoSelectKey) { + LoadLicense(); + // Call select key before the refresh. No calls below to TestDecryptCTR with // select key set to true. - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(true)); + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(true)); - s.GenerateNonce(); - // License renewal message is signed by client and verified by the server. - ASSERT_NO_FATAL_FAILURE(s.VerifyClientSignature()); - // Note: we store the message in encrypted_license_, but the refresh key - // message is not actually encrypted. It is, however, signed. - // FillRefreshMessage fills the message with a duration of kLongDuration. - ASSERT_NO_FATAL_FAILURE(s.FillRefreshMessage( - num_keys_, wvoec::kControlNonceEnabled, s.get_nonce())); - s.ServerSignBuffer(reinterpret_cast(&s.encrypted_license()), - s.message_size(), &s.signature()); - std::vector key_array(num_keys_); - s.FillRefreshArray(key_array.data(), num_keys_); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_RefreshKeys(s.session_id(), s.message_ptr(), - s.message_size(), s.signature().data(), - s.signature().size(), num_keys_, - key_array.data())); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(false)); // This should still be valid key, even if the refresh failed, because this // is before the original license duration. - sleep(kShortSleep); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(false)); + wvcdm::TestSleep::Sleep(kShortSleep); + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(false)); + // This should be after duration of the original license, but before the - // expiration of the refresh message. This should succeed if and only if the - // refresh succeeded. - sleep(kShortSleep + kLongSleep); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(false)); + // expiration of the refresh message. This should fail until we have loaded + // the renewal. + wvcdm::TestSleep::Sleep(kShortSleep + kLongSleep); + ASSERT_NO_FATAL_FAILURE( + session_.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED)); + + RenewalRoundTrip renewal_messages(&license_messages_); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); + + // After we've loaded the renewal, decrypt should succeed again. + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(false)); } -// If only one key control block in the refesh, we update all the keys. -INSTANTIATE_TEST_CASE_P(TestRefreshAllKeys, SessionTestRefreshKeyTest, - Values(std::make_pair(true, 1), - std::make_pair(false, 1))); +INSTANTIATE_TEST_CASE_P(TestAll, OEMCryptoRefreshTest, + Range(kCurrentAPI - 1, kCurrentAPI + 1)); -// If multiple key control blocks, we update each key separately. -INSTANTIATE_TEST_CASE_P(TestRefreshEachKeys, SessionTestRefreshKeyTest, - Values(std::make_pair(true, 4), - std::make_pair(false, 4))); +// These tests only work when the license has a core message. +INSTANTIATE_TEST_CASE_P(TestAPI16, OEMCryptoRefreshTestAPI16, + Range(kCoreMessagesAPI, kCurrentAPI + 1)); // If the license does not allow a hash, then we should not compute one. -TEST_F(OEMCryptoSessionTests, HashForbiddenAPI15) { +TEST_P(OEMCryptoLicenseTest, HashForbiddenAPI15) { uint32_t hash_type = OEMCrypto_SupportsDecryptHash(); // If hash is not supported, or is vendor defined, don't try to test it. if (hash_type != OEMCrypto_CRC_Clear_Buffer) return; - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + uint32_t frame_number = 1; uint32_t hash = 42; // It is OK to set the hash before loading the keys ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SetDecryptHash(s.session_id(), frame_number, + OEMCrypto_SetDecryptHash(session_.session_id(), frame_number, reinterpret_cast(&hash), sizeof(hash))); // It is OK to select the key and decrypt. - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR()); // But the error code should be bad. ASSERT_EQ(OEMCrypto_ERROR_UNKNOWN_FAILURE, - OEMCrypto_GetHashErrorCode(s.session_id(), &frame_number)); + OEMCrypto_GetHashErrorCode(session_.session_id(), &frame_number)); } // // Decrypt Tests -- these test Decrypt CTR mode only. // -TEST_F(OEMCryptoSessionTests, Decrypt) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); +TEST_P(OEMCryptoLicenseTest, Decrypt) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR()); } // Verify that a zero duration means infinite license duration. -TEST_F(OEMCryptoSessionTests, DecryptZeroDuration) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(0, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); +TEST_P(OEMCryptoLicenseTest, DecryptZeroDuration) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = 0; + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR()); } -// Verify that several sessions may each load a license and then each may -// decrypt. -TEST_F(OEMCryptoSessionTests, SimultaneousDecrypt) { - vector s(8); - for (int i = 0; i < 8; i++) { - ASSERT_NO_FATAL_FAILURE(s[i].open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s[i])); - } - for (int i = 0; i < 8; i++) { - ASSERT_NO_FATAL_FAILURE( - s[i].FillSimpleMessage(kLongDuration, 0, s[i].get_nonce())); - ASSERT_NO_FATAL_FAILURE(s[i].EncryptAndSign()); - } - for (int i = 0; i < 8; i++) { - ASSERT_NO_FATAL_FAILURE(s[i].LoadTestKeys()); - } - for (int i = 0; i < 8; i++) { - ASSERT_NO_FATAL_FAILURE(s[i].TestDecryptCTR()); - } - // Second call to decrypt for each session. - for (int i = 0; i < 8; i++) { - ASSERT_NO_FATAL_FAILURE(s[i].TestDecryptCTR()); - } -} - -// This test generates several test keys, as if a license request was lost. -// This is only valid for (obsolete) devices that use a keybox to talk to a -// license server. -TEST_F(OEMCryptoSessionTests, SimultaneousDecryptWithLostMessageKeyboxTest) { - vector s(8); - for (int i = 0; i < 8; i++) { - ASSERT_NO_FATAL_FAILURE(s[i].open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s[i])); - } - for (int i = 0; i < 8; i++) { - ASSERT_NO_FATAL_FAILURE(s[i].GenerateDerivedKeysFromKeybox(keybox_)); - ASSERT_NO_FATAL_FAILURE( - s[i].FillSimpleMessage(kLongDuration, 0, s[i].get_nonce())); - ASSERT_NO_FATAL_FAILURE(s[i].EncryptAndSign()); - } - // First set of messages are lost. Generate second set. - for (int i = 0; i < 8; i++) { - ASSERT_NO_FATAL_FAILURE(s[i].GenerateDerivedKeysFromKeybox(keybox_)); - ASSERT_NO_FATAL_FAILURE( - s[i].FillSimpleMessage(kLongDuration, 0, s[i].get_nonce())); - ASSERT_NO_FATAL_FAILURE(s[i].EncryptAndSign()); - } - for (int i = 0; i < 8; i++) { - ASSERT_NO_FATAL_FAILURE(s[i].LoadTestKeys()); - } - for (int i = 0; i < 8; i++) { - ASSERT_NO_FATAL_FAILURE(s[i].TestDecryptCTR()); - } - // Second call to decrypt for each session. - for (int i = 0; i < 8; i++) { - ASSERT_NO_FATAL_FAILURE(s[i].TestDecryptCTR()); - } -} - -struct SampleSize { +struct SubsampleSize { size_t clear_size; size_t encrypted_size; - SampleSize(size_t clear, size_t encrypted) + SubsampleSize(size_t clear, size_t encrypted) : clear_size(clear), encrypted_size(encrypted) {} }; -struct SampleInitData { - uint8_t iv[AES_BLOCK_SIZE]; - size_t block_offset; +// Struct for holding the data for one test sample in the decrypt tests. +struct TestSample { + // Encrypted data -- this is input to OEMCrypto, and output from EncryptData. + std::vector encrypted_buffer; + std::vector clear_buffer; // OEMCrypto store clear output here. + std::vector truth_buffer; // Truth data for clear text. + + OEMCrypto_SampleDescription description; + std::vector subsamples; + int secure_buffer_fid; }; // A class of tests that test decryption for a variety of patterns and modes. @@ -2060,269 +1949,339 @@ struct SampleInitData { // output buffer is clear, it should be possible for the input and output // buffers to be the same. class OEMCryptoSessionTestsDecryptTests - : public OEMCryptoSessionTests, + : public OEMCryptoLicenseTestAPI16, public WithParamInterface > { + OEMCryptoCipherMode, OutputType>> { protected: void SetUp() override { - OEMCryptoSessionTests::SetUp(); + OEMCryptoLicenseTestAPI16::SetUp(); pattern_ = ::testing::get<0>(GetParam()); cipher_mode_ = ::testing::get<1>(GetParam()); - decrypt_inplace_ = ::testing::get<2>(GetParam()); + decrypt_inplace_ = ::testing::get<2>(GetParam()).decrypt_inplace; + output_buffer_type_ = ::testing::get<2>(GetParam()).type; verify_crc_ = global_features.supports_crc; // Pick a random key. - EXPECT_EQ(1, GetRandBytes(key_, AES_BLOCK_SIZE)); + EXPECT_EQ(GetRandBytes(key_, sizeof(key_)), 1); // Pick a random starting iv. Some tests override this before using it. - starting_iv_.resize(AES_BLOCK_SIZE); - EXPECT_EQ(1, GetRandBytes(starting_iv_.data(), starting_iv_.size())); - total_size_ = -1; + EXPECT_EQ(GetRandBytes(initial_iv_, sizeof(initial_iv_)), 1); } - virtual void TearDown() { - ASSERT_NO_FATAL_FAILURE(session_.close()); - OEMCryptoSessionTests::TearDown(); + void TearDown() override { + FreeSecureBuffers(); + OEMCryptoLicenseTestAPI16::TearDown(); } - void FindTotalSize() { - total_size_ = 0; - for (size_t i = 0; i < subsample_size_.size(); i++) { - total_size_ += - subsample_size_[i].clear_size + subsample_size_[i].encrypted_size; + void SetSubsampleSizes(std::vector subsample_sizes) { + // This is just sugar for having one sample with the given subsamples in it. + SetSampleSizes({subsample_sizes}); + } + + void SetSampleSizes(std::vector> sample_sizes) { + ASSERT_GT(sample_sizes.size(), 0u); + samples_.clear(); + samples_.reserve(sample_sizes.size()); + + // Convert all the size arrays to TestSample structs + for (const std::vector& subsample_sizes : sample_sizes) { + // This could be one line if we had C++17 + samples_.emplace_back(); + TestSample& sample = samples_.back(); + + ASSERT_GT(subsample_sizes.size(), 0u); + sample.subsamples.reserve(subsample_sizes.size()); + + // Convert all the sizes to subsample descriptions and tally the total + // sample size + size_t sample_size = 0; + size_t current_block_offset = 0; + for (const SubsampleSize& size : subsample_sizes) { + sample.subsamples.push_back(OEMCrypto_SubSampleDescription{ + size.clear_size, size.encrypted_size, + 0, // Subsample Flags, to be filled in after the loop + current_block_offset}); + + // Update the rolling variables + sample_size += size.clear_size + size.encrypted_size; + if (cipher_mode_ == OEMCrypto_CipherMode_CTR) { + current_block_offset = + (current_block_offset + size.encrypted_size) % AES_BLOCK_SIZE; + } + } + + // Set the subsample flags now that all the subsamples are processed + sample.subsamples.front().subsample_flags |= OEMCrypto_FirstSubsample; + sample.subsamples.back().subsample_flags |= OEMCrypto_LastSubsample; + + // Set related information on the sample description + sample.description.subsamples = sample.subsamples.data(); + sample.description.subsamples_length = sample.subsamples.size(); + sample.description.buffers.input_data_length = sample_size; } } - // Set up the input buffer and output buffer. - // This should be called after FindTotalSize(). + // Set up the input buffer and either a clear or secure output buffer for each + // test sample. This should be called after SetSubsampleSizes(). void MakeBuffers() { - ASSERT_GT(total_size_, 0u); - encrypted_buffer_.resize(total_size_); - truth_buffer_.resize(total_size_); - for (size_t i = 0; i < total_size_; i++) truth_buffer_[i] = i % 256; - output_descriptor_.type = OEMCrypto_BufferType_Clear; - if (decrypt_inplace_) { - output_descriptor_.buffer.clear.address = encrypted_buffer_.data(); - } else { - // Add some padding to verify there is no overrun. - clear_buffer_.resize(total_size_ + 16, 0xaa); - output_descriptor_.buffer.clear.address = clear_buffer_.data(); - } - output_descriptor_.buffer.clear.max_length = total_size_; + for (TestSample& sample : samples_) { + const size_t total_size = sample.description.buffers.input_data_length; + ASSERT_GT(total_size, 0u); + sample.encrypted_buffer.clear(); + sample.truth_buffer.clear(); + sample.clear_buffer.clear(); + sample.encrypted_buffer.resize(total_size); + sample.truth_buffer.resize(total_size); + for (size_t i = 0; i < total_size; i++) sample.truth_buffer[i] = i % 256; + + OEMCrypto_DestBufferDesc& output_descriptor = + sample.description.buffers.output_descriptor; + output_descriptor.type = output_buffer_type_; + switch (output_descriptor.type) { + case OEMCrypto_BufferType_Clear: + if (decrypt_inplace_) { + // Add some padding to verify there is no overrun. + sample.encrypted_buffer.resize(total_size + kBufferOverrunPadding, + 0xaa); + output_descriptor.buffer.clear.address = + sample.encrypted_buffer.data(); + } else { + // Add some padding to verify there is no overrun. + sample.clear_buffer.resize(total_size + kBufferOverrunPadding, + 0xaa); + output_descriptor.buffer.clear.address = sample.clear_buffer.data(); + } + output_descriptor.buffer.clear.address_length = total_size; + break; + + case OEMCrypto_BufferType_Secure: + output_descriptor.buffer.secure.handle_length = total_size; + ASSERT_EQ(OEMCrypto_AllocateSecureBuffer( + session_.session_id(), total_size, &output_descriptor, + &sample.secure_buffer_fid), + OEMCrypto_SUCCESS); + ASSERT_NE(output_descriptor.buffer.secure.handle, nullptr); + // It is OK if OEMCrypto changes the maximum size, but there must + // still be enough room for our data. + ASSERT_GE(output_descriptor.buffer.secure.handle_length, total_size); + output_descriptor.buffer.secure.offset = 0; + break; + + case OEMCrypto_BufferType_Direct: + output_descriptor.buffer.direct.is_video = false; + break; + } // switch (output_descriptor.type) + } // sample loop } - void UpdateOutputOffset(size_t offset) { - if (decrypt_inplace_) { - output_descriptor_.buffer.clear.address = - encrypted_buffer_.data() + offset; - } else { - output_descriptor_.buffer.clear.address = clear_buffer_.data() + offset; + void FreeSecureBuffers() { + for (TestSample& sample : samples_) { + OEMCrypto_DestBufferDesc& output_descriptor = + sample.description.buffers.output_descriptor; + if (output_descriptor.type == OEMCrypto_BufferType_Secure) { + ASSERT_EQ(OEMCrypto_FreeSecureBuffer(session_.session_id(), + &output_descriptor, + sample.secure_buffer_fid), + OEMCrypto_SUCCESS); + } } - output_descriptor_.buffer.clear.max_length = total_size_ - offset; } void EncryptData() { AES_KEY aes_key; AES_set_encrypt_key(key_, AES_BLOCK_SIZE * 8, &aes_key); - uint8_t iv[AES_BLOCK_SIZE]; // Current iv. - memcpy(iv, starting_iv_.data(), AES_BLOCK_SIZE); + for (TestSample& sample : samples_) { + uint8_t iv[KEY_IV_SIZE]; // Current IV + memcpy(iv, initial_iv_, KEY_IV_SIZE); + memcpy(sample.description.iv, initial_iv_, KEY_IV_SIZE); - size_t buffer_index = 0; // byte index into in and out. - size_t block_offset = 0; // byte index into current block. - for (size_t i = 0; i < subsample_size_.size(); i++) { - // Copy clear content. - if (subsample_size_[i].clear_size > 0) { - memcpy(&encrypted_buffer_[buffer_index], &truth_buffer_[buffer_index], - subsample_size_[i].clear_size); - buffer_index += subsample_size_[i].clear_size; - } - // Save the current iv and offsets for call to DecryptCENC. - sample_init_data_.push_back(SampleInitData()); - memcpy(sample_init_data_[i].iv, iv, AES_BLOCK_SIZE); - // Note: final CENC spec specifies the pattern_offset = 0 at the - // start of each subsample. - size_t pattern_offset = 0; - sample_init_data_[i].block_offset = block_offset; - - size_t subsample_end = buffer_index + subsample_size_[i].encrypted_size; - while (buffer_index < subsample_end) { - size_t size = - min(subsample_end - buffer_index, AES_BLOCK_SIZE - block_offset); - size_t pattern_length = pattern_.encrypt + pattern_.skip; - bool skip_block = - (pattern_offset >= pattern_.encrypt) && (pattern_length > 0); - if (pattern_length > 0) { - pattern_offset = (pattern_offset + 1) % pattern_length; + size_t buffer_index = 0; // byte index into in and out. + size_t block_offset = 0; // byte index into current block. + for (const OEMCrypto_SubSampleDescription& subsample : + sample.subsamples) { + // Copy clear content. + if (subsample.num_bytes_clear > 0) { + memcpy(&sample.encrypted_buffer[buffer_index], + &sample.truth_buffer[buffer_index], subsample.num_bytes_clear); + buffer_index += subsample.num_bytes_clear; } - // CBC mode should just copy a partial block at the end. If there - // is a partial block at the beginning, an error is returned, so we - // can put whatever we want in the output buffer. - if (skip_block || ((cipher_mode_ == OEMCrypto_CipherMode_CBC) && - (size < AES_BLOCK_SIZE))) { - memcpy(&encrypted_buffer_[buffer_index], &truth_buffer_[buffer_index], - size); - block_offset = 0; // Next block should be complete. - } else { - if (cipher_mode_ == OEMCrypto_CipherMode_CTR) { - uint8_t aes_output[AES_BLOCK_SIZE]; - AES_encrypt(iv, aes_output, &aes_key); - for (size_t n = 0; n < size; n++) { - encrypted_buffer_[buffer_index + n] = - aes_output[n + block_offset] ^ - truth_buffer_[buffer_index + n]; - } - if (size + block_offset < AES_BLOCK_SIZE) { - // Partial block. Don't increment iv. Compute next block offset. - block_offset = block_offset + size; + + // The IV resets at the start of each subsample in the 'cbcs' schema. + if (cipher_mode_ == OEMCrypto_CipherMode_CBC) { + memcpy(iv, initial_iv_, KEY_IV_SIZE); + } + + size_t pattern_offset = 0; + const size_t subsample_end = + buffer_index + subsample.num_bytes_encrypted; + while (buffer_index < subsample_end) { + const size_t size = + min(subsample_end - buffer_index, AES_BLOCK_SIZE - block_offset); + const size_t pattern_length = pattern_.encrypt + pattern_.skip; + const bool skip_block = + (pattern_offset >= pattern_.encrypt) && (pattern_length > 0); + if (pattern_length > 0) { + pattern_offset = (pattern_offset + 1) % pattern_length; + } + // CBC mode should just copy a partial block at the end. If there + // is a partial block at the beginning, an error is returned, so we + // can put whatever we want in the output buffer. + if (skip_block || ((cipher_mode_ == OEMCrypto_CipherMode_CBC) && + (size < AES_BLOCK_SIZE))) { + memcpy(&sample.encrypted_buffer[buffer_index], + &sample.truth_buffer[buffer_index], size); + block_offset = 0; // Next block should be complete. + } else { + if (cipher_mode_ == OEMCrypto_CipherMode_CTR) { + uint8_t aes_output[AES_BLOCK_SIZE]; + AES_encrypt(iv, aes_output, &aes_key); + for (size_t n = 0; n < size; n++) { + sample.encrypted_buffer[buffer_index + n] = + aes_output[n + block_offset] ^ + sample.truth_buffer[buffer_index + n]; + } + if (size + block_offset < AES_BLOCK_SIZE) { + // Partial block. Don't increment iv. Compute next block + // offset. + block_offset = block_offset + size; + } else { + EXPECT_EQ(block_offset + size, + static_cast(AES_BLOCK_SIZE)); + // Full block. Increment iv, and set offset to 0 for next + // block. + ctr128_inc64(1, iv); + block_offset = 0; + } } else { - EXPECT_EQ(static_cast(AES_BLOCK_SIZE), - block_offset + size); - // Full block. Increment iv, and set offset to 0 for next block. - ctr128_inc64(1, iv); + uint8_t aes_input[AES_BLOCK_SIZE]; + for (size_t n = 0; n < size; n++) { + aes_input[n] = sample.truth_buffer[buffer_index + n] ^ iv[n]; + } + AES_encrypt(aes_input, &sample.encrypted_buffer[buffer_index], + &aes_key); + memcpy(iv, &sample.encrypted_buffer[buffer_index], + AES_BLOCK_SIZE); + // CBC mode should always start on block boundary. block_offset = 0; } - } else { - uint8_t aes_input[AES_BLOCK_SIZE]; - for (size_t n = 0; n < size; n++) { - aes_input[n] = truth_buffer_[buffer_index + n] ^ iv[n]; - } - AES_encrypt(aes_input, &encrypted_buffer_[buffer_index], &aes_key); - memcpy(iv, &encrypted_buffer_[buffer_index], AES_BLOCK_SIZE); - // CBC mode should always start on block boundary. - block_offset = 0; } - } - buffer_index += size; - } - } + buffer_index += size; + } // encryption loop + } // per-subsample loop + } // per-sample loop } void LoadLicense() { - // First we open a session and load a license. - ASSERT_NO_FATAL_FAILURE(session_.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&session_)); - uint32_t control = 0; + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + uint32_t control = wvoec::kControlNonceEnabled; if (verify_crc_) control |= kControlAllowHashVerification; - ASSERT_NO_FATAL_FAILURE(session_.FillSimpleMessage( - kDuration, control, 0)); - memcpy(session_.license().keys[0].key_data, key_, sizeof(key_)); - session_.license().keys[0].cipher_mode = cipher_mode_; - ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(session_.LoadTestKeys()); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SelectKey( + if (output_buffer_type_ == OEMCrypto_BufferType_Secure) + control |= kControlObserveDataPath | kControlDataPathSecure; + license_messages_.set_control(control); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + license_messages_.core_response() + .timer_limits.initial_renewal_duration_seconds = kDuration; + memcpy(license_messages_.response_data().keys[0].key_data, key_, + sizeof(key_)); + license_messages_.response_data().keys[0].cipher_mode = cipher_mode_; + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + ASSERT_EQ(OEMCrypto_SelectKey( session_.session_id(), session_.license().keys[0].key_id, - session_.license().keys[0].key_id_length, cipher_mode_)); + session_.license().keys[0].key_id_length, cipher_mode_), + OEMCrypto_SUCCESS); } void TestDecryptCENC() { OEMCryptoResult sts; - // If supported, initialize the decrypt hash. + + // If supported, check the decrypt hashes. if (verify_crc_) { - uint32_t hash = wvcrc32(truth_buffer_.data(), truth_buffer_.size()); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SetDecryptHash( + // OEMCrypto only supports providing a decrypt hash for the first sample + // in the sample array. + const TestSample& sample = samples_[0]; + + uint32_t hash = + wvcrc32(sample.truth_buffer.data(), sample.truth_buffer.size()); + ASSERT_EQ(OEMCrypto_SetDecryptHash( session_.session_id(), 1, - reinterpret_cast(&hash), sizeof(hash))); + reinterpret_cast(&hash), sizeof(hash)), + OEMCrypto_SUCCESS); } - size_t buffer_offset = 0; - for (size_t i = 0; i < subsample_size_.size(); i++) { - OEMCrypto_CENCEncryptPatternDesc pattern = pattern_; - pattern.offset = 0; // Final CENC spec says pattern offset always 0. - bool is_encrypted = false; - size_t block_offset = 0; - uint8_t subsample_flags = 0; - if (subsample_size_[i].clear_size > 0) { - ASSERT_NO_FATAL_FAILURE(UpdateOutputOffset(buffer_offset)); - if (i == 0) subsample_flags |= OEMCrypto_FirstSubsample; - if ((i == subsample_size_.size() - 1) && - (subsample_size_[i].encrypted_size == 0)) { - subsample_flags |= OEMCrypto_LastSubsample; - } - sts = OEMCrypto_DecryptCENC( - session_.session_id(), &encrypted_buffer_[buffer_offset], - subsample_size_[i].clear_size, is_encrypted, - sample_init_data_[i].iv, block_offset, &output_descriptor_, - &pattern, subsample_flags); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - buffer_offset += subsample_size_[i].clear_size; - } - if (subsample_size_[i].encrypted_size > 0) { - ASSERT_NO_FATAL_FAILURE(UpdateOutputOffset(buffer_offset)); - is_encrypted = true; - block_offset = sample_init_data_[i].block_offset; - subsample_flags = 0; - if ((i == 0) && (subsample_size_[i].clear_size == 0)) { - subsample_flags |= OEMCrypto_FirstSubsample; - } - if (i == subsample_size_.size() - 1) { - subsample_flags |= OEMCrypto_LastSubsample; - } - sts = OEMCrypto_DecryptCENC( - session_.session_id(), &encrypted_buffer_[buffer_offset], - subsample_size_[i].encrypted_size, is_encrypted, - sample_init_data_[i].iv, block_offset, &output_descriptor_, - &pattern, subsample_flags); - // CBC mode should not accept a block offset. - if ((block_offset > 0) && (cipher_mode_ == OEMCrypto_CipherMode_CBC)) { - ASSERT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, sts) - << "CBC Mode should reject a non-zero block offset."; - return; - } - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - buffer_offset += subsample_size_[i].encrypted_size; - } + + // Build an array of just the sample descriptions. + std::vector sample_descriptions; + sample_descriptions.reserve(samples_.size()); + for (TestSample& sample : samples_) { + // This must be deferred until this point in case the test modifies the + // buffer before testing decrypt. + sample.description.buffers.input_data = sample.encrypted_buffer.data(); + // Append to the description array. + sample_descriptions.push_back(sample.description); } - if (output_descriptor_.type == OEMCrypto_BufferType_Clear) { - if (decrypt_inplace_) { - // We expect encrypted buffer to have been changed by OEMCrypto. - EXPECT_EQ(encrypted_buffer_, truth_buffer_); - } else { - // If we are not decrypting in place, then look at the one byte just - // after the data that was written. It should not have changed from the - // original 0xaa that we set in MakeBuffersession_. - EXPECT_EQ(0xaa, clear_buffer_[total_size_]) << "Buffer overrun."; - clear_buffer_.resize(total_size_); // Remove padding. - EXPECT_EQ(clear_buffer_, truth_buffer_); + + // Perform decryption using the test data that was previously set up. + sts = DecryptFallbackChain::Decrypt( + session_.session_id(), sample_descriptions.data(), + sample_descriptions.size(), cipher_mode_, &pattern_); + ASSERT_EQ(sts, OEMCrypto_SUCCESS); + + // Validate the decrypted data. + for (TestSample& sample : samples_) { + if (sample.description.buffers.output_descriptor.type == + OEMCrypto_BufferType_Clear) { + const size_t total_size = sample.description.buffers.input_data_length; + // To verify there is no buffer overrun after decrypting, look at the + // padded bytes just after the data buffer that was written. It should + // not have changed from the original 0xaa that we set in MakeBuffer + // function. + if (decrypt_inplace_) { + EXPECT_EQ(std::count(sample.encrypted_buffer.begin() + total_size, + sample.encrypted_buffer.end(), 0xaa), + static_cast(kBufferOverrunPadding)) + << "Buffer overrun."; + sample.encrypted_buffer.resize(total_size); // Remove padding. + // We expect encrypted buffer to have been changed by OEMCrypto. + EXPECT_EQ(sample.encrypted_buffer, sample.truth_buffer); + } else { + EXPECT_EQ(std::count(sample.clear_buffer.begin() + total_size, + sample.clear_buffer.end(), 0xaa), + static_cast(kBufferOverrunPadding)) + << "Buffer overrun."; + sample.clear_buffer.resize(total_size); // Remove padding. + EXPECT_EQ(sample.clear_buffer, sample.truth_buffer); + } } } if (global_features.supports_crc) { uint32_t frame; - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_GetHashErrorCode(session_.session_id(), &frame)); + ASSERT_EQ(OEMCrypto_GetHashErrorCode(session_.session_id(), &frame), + OEMCrypto_SUCCESS); } } + // Parameters of test case OEMCrypto_CENCEncryptPatternDesc pattern_; OEMCryptoCipherMode cipher_mode_; bool decrypt_inplace_; // If true, input and output buffers are the same. - vector subsample_size_; - size_t total_size_; - bool verify_crc_; - vector sample_init_data_; -// Encrypted data -- this is input to OEMCrypto, and output from EncryptData. - std::vector encrypted_buffer_; - std::vector clear_buffer_; // OEMCrypto store clear output here. - void* secure_handle_; // OEMCrypto stores secure output here. - std::vector truth_buffer_; // Truth data for clear text. - OEMCrypto_DestBufferDesc output_descriptor_; - uint8_t key_[AES_BLOCK_SIZE]; // Encryption Key. - std::vector starting_iv_; // Starting IV. - Session session_; -}; + OEMCryptoBufferType output_buffer_type_; -// Tests that generate partial ending blocks. These tests should not be used -// with CTR mode and pattern encrypt. -class OEMCryptoSessionTestsPartialBlockTests - : public OEMCryptoSessionTestsDecryptTests {}; + bool verify_crc_; + uint8_t key_[AES_BLOCK_SIZE]; // Encryption Key. + uint8_t initial_iv_[KEY_IV_SIZE]; // Starting IV for every sample. + std::vector samples_; +}; TEST_P(OEMCryptoSessionTestsDecryptTests, SingleLargeSubsample) { // This subsample size is larger than a few encrypt/skip patterns. Most // test cases use a pattern length of 160, so we'll run through at least two // full patterns if we have more than 320 -- round up to 400. - subsample_size_.push_back(SampleSize(0, 400)); - FindTotalSize(); - ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {0, 400}, + })); ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); ASSERT_NO_FATAL_FAILURE(EncryptData()); ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); } @@ -2332,34 +2291,36 @@ TEST_P(OEMCryptoSessionTestsDecryptTests, SingleLargeSubsample) { // We require the CENC standard for OEMCrypto, and let a layer above us break // samples into pieces if they wish to use the HLS standard. TEST_P(OEMCryptoSessionTestsDecryptTests, PatternPlusOneBlock) { - subsample_size_.push_back(SampleSize(0, 160 + 16)); - FindTotalSize(); - ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {0, 160 + 16}, + })); ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); ASSERT_NO_FATAL_FAILURE(EncryptData()); ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); } // Test that a single block can be decrypted. TEST_P(OEMCryptoSessionTestsDecryptTests, OneBlock) { - subsample_size_.push_back(SampleSize(0, 16)); - FindTotalSize(); - ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {0, 16}, + })); ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); ASSERT_NO_FATAL_FAILURE(EncryptData()); ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); } // This tests the ability to decrypt multiple subsamples with no offset. -// There is no offset within the block, used by CTR mode. However, there might -// be an offset in the encrypt/skip pattern. +// There is no offset within the block, used by CTR mode. TEST_P(OEMCryptoSessionTestsDecryptTests, NoOffset) { - subsample_size_.push_back(SampleSize(25, 160)); - subsample_size_.push_back(SampleSize(50, 256)); - subsample_size_.push_back(SampleSize(25, 160)); - FindTotalSize(); - ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {25, 160}, + {50, 256}, + {25, 160}, + })); ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); ASSERT_NO_FATAL_FAILURE(EncryptData()); ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); } @@ -2369,41 +2330,54 @@ TEST_P(OEMCryptoSessionTestsDecryptTests, NoOffset) { // the decrypt step. // If this test fails for CTR mode, then it is probably handling the // block_offset incorrectly. -TEST_P(OEMCryptoSessionTestsPartialBlockTests, EvenOffset) { - subsample_size_.push_back(SampleSize(25, 8)); - subsample_size_.push_back(SampleSize(25, 32)); - subsample_size_.push_back(SampleSize(25, 50)); - FindTotalSize(); - ASSERT_NO_FATAL_FAILURE(MakeBuffers()); +TEST_P(OEMCryptoSessionTestsDecryptTests, EvenOffset) { + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {25, 8}, + {25, 32}, + {25, 50}, + })); ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); // CTR Mode is self-inverse -- i.e. We can pick the encrypted data and // compute the unencrypted data. By picking the encrypted data to be all 0, // it is easier to re-encrypt the data and debug problems. Similarly, we // pick an iv = 0. - starting_iv_.assign(AES_BLOCK_SIZE, 0); - truth_buffer_.assign(total_size_, 0); + memset(initial_iv_, 0, KEY_IV_SIZE); + TestSample& sample = samples_[0]; // There is only one sample in this test + sample.truth_buffer.assign(sample.description.buffers.input_data_length, 0); ASSERT_NO_FATAL_FAILURE(EncryptData()); - truth_buffer_ = encrypted_buffer_; // truth_buffer_ = encrypted zero buffer. + if (decrypt_inplace_) { + const size_t total_size = sample.description.buffers.input_data_length; + // In case of decrypt_inplace_, encrypted_buffer contains padded bytes + // which is used for buffer overrun validation. Do not copy the padded + // bytes to truth_buffer. + sample.truth_buffer.assign(sample.encrypted_buffer.begin(), + sample.encrypted_buffer.begin() + total_size); + } else { + sample.truth_buffer = + sample.encrypted_buffer; // truth_buffer_ = encrypted zero buffer. + } // Run EncryptData to re-encrypt this buffer. For CTR mode, we should get // back to zeros. ASSERT_NO_FATAL_FAILURE(EncryptData()); ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); } -// If the EvenOffset test passes, but this one doesn't, then DecryptCTR might +// If the EvenOffset test passes, but this one doesn't, then DecryptCENC might // be using the wrong definition of block offset. Adding the block offset to // the block boundary should give you the beginning of the encrypted data. // This should only work for CTR mode, for CBC mode, the block offset must be // 0, so an error is expected in the decrypt step. // Another way to view the block offset is with the formula: // block_boundary + block_offset = beginning of subsample. -TEST_P(OEMCryptoSessionTestsPartialBlockTests, OddOffset) { - subsample_size_.push_back(SampleSize(10, 50)); - subsample_size_.push_back(SampleSize(10, 75)); - subsample_size_.push_back(SampleSize(10, 25)); - FindTotalSize(); - ASSERT_NO_FATAL_FAILURE(MakeBuffers()); +TEST_P(OEMCryptoSessionTestsDecryptTests, OddOffset) { + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {10, 50}, + {10, 75}, + {10, 75}, + })); ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); ASSERT_NO_FATAL_FAILURE(EncryptData()); ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); } @@ -2419,11 +2393,13 @@ TEST_P(OEMCryptoSessionTestsPartialBlockTests, OddOffset) { // If you start with an IV of 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, after you // increment twice, you should get 0xFFFFFFFFFFFFFFFF0000000000000000. TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptWithNearWrap) { - starting_iv_ = wvcdm::a2b_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"); - subsample_size_.push_back(SampleSize(0, 256)); - FindTotalSize(); - ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + memcpy(initial_iv_, wvcdm::a2b_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE").data(), + KEY_IV_SIZE); + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {0, 256}, + })); ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); ASSERT_NO_FATAL_FAILURE(EncryptData()); ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); } @@ -2431,56 +2407,79 @@ TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptWithNearWrap) { // This tests the case where an encrypted sample is not an even number of // blocks. For CTR mode, the partial block is encrypted. For CBC mode the // partial block should be a copy of the clear data. -TEST_P(OEMCryptoSessionTestsPartialBlockTests, PartialBlock) { +TEST_P(OEMCryptoSessionTestsDecryptTests, PartialBlock) { // Note: for more complete test coverage, we want a sample size that is in // the encrypted range for some tests, e.g. (3,7), and in the skip range for // other tests, e.g. (7, 3). 3*16 < 50 and 7*16 > 50. - subsample_size_.push_back(SampleSize(0, 50)); - FindTotalSize(); - ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {0, 50}, + })); ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); ASSERT_NO_FATAL_FAILURE(EncryptData()); ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); } -// Based on the resource rating, oemcrypto should handle at least -// kMaxNumberSubsamples na kMaxSampleSize -TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptMaxSample) { - size_t max_size = GetResourceValue(kMaxSampleSize); - size_t max_subsample_size = GetResourceValue(kMaxSubsampleSize); - size_t num_subsamples = GetResourceValue(kMaxNumberSubsamples); - if (num_subsamples * max_subsample_size > max_size) { - max_subsample_size = max_size / num_subsamples; +// Based on the resource rating, OEMCrypto should be able to handle the maximum +// amount of data that can be passed to it. This is the lesser of: +// +// 1) The maximum total sample size +// 2) The maximum number of subsamples multiplied by the maximum subsample size +TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptMaxSampleAPI16) { + const size_t max_sample_size = GetResourceValue(kMaxSampleSize); + const size_t max_subsample_size = GetResourceValue(kMaxSubsampleSize); + const size_t max_num_subsamples = GetResourceValue(kMaxNumberSubsamples); + + // The +1 on this line ensures that, even in cases where max_sample_size is + // not evenly divisible by max_num_subsamples and thus the division gets + // truncated, (max_num_subsamples * subsample_size) will be greater than + // max_sample_size. + const size_t subsample_size = + std::min(max_sample_size / max_num_subsamples + 1, max_subsample_size); + + size_t bytes_remaining = max_sample_size; + std::vector subsample_sizes; + while (bytes_remaining > 0 && subsample_sizes.size() < max_num_subsamples) { + const size_t this_subsample_size = + std::min(subsample_size, bytes_remaining); + const size_t clear_size = this_subsample_size / 2; + const size_t encrypted_size = this_subsample_size - clear_size; + + subsample_sizes.push_back({clear_size, encrypted_size}); + bytes_remaining -= this_subsample_size; } - for(size_t i = 0; i < num_subsamples/2; i += 2) { - subsample_size_.push_back(SampleSize(max_subsample_size, 0)); - subsample_size_.push_back(SampleSize(0, max_subsample_size)); - } - FindTotalSize(); - ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes(subsample_sizes)); ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); ASSERT_NO_FATAL_FAILURE(EncryptData()); ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); } -// This tests that we can decrypt the required maximum number of subsamples. +// Based on the resource rating, OEMCrypto should be able to handle the maximum +// subsample size. TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptMaxSubsample) { - size_t max_subsample_size = GetResourceValue(kMaxSubsampleSize); - subsample_size_.push_back(SampleSize(max_subsample_size, 0)); - subsample_size_.push_back(SampleSize(0, max_subsample_size)); - FindTotalSize(); - ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + const size_t max = GetResourceValue(kMaxSubsampleSize); + const size_t half_max = max / 2; + // This test assumes that the maximum sample size is always more than three + // times the maximum subsample size. + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {max, 0}, + {0, max}, + {half_max, max - half_max}, + })); ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); ASSERT_NO_FATAL_FAILURE(EncryptData()); ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); } // There are probably no frames this small, but we should handle them anyway. TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptSmallBuffer) { - subsample_size_.push_back(SampleSize(5, 5)); - FindTotalSize(); - ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {5, 5}, + })); ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); ASSERT_NO_FATAL_FAILURE(EncryptData()); ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); } @@ -2488,21 +2487,22 @@ TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptSmallBuffer) { // Test the case where there is only a clear subsample and no encrypted // subsample. TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptUnencrypted) { - subsample_size_.push_back(SampleSize(256, 0)); - FindTotalSize(); - ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {256, 0}, + })); ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); ASSERT_NO_FATAL_FAILURE(EncryptData()); ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); } TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptUnencryptedNoKey) { - ASSERT_NO_FATAL_FAILURE(session_.open()); - // Single clear subsample - subsample_size_.push_back(SampleSize(400, 0)); // Do not try to compute the CRC because we have not loaded a license. verify_crc_ = false; - FindTotalSize(); + // Single clear subsample + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes({ + {400, 0}, + })); ASSERT_NO_FATAL_FAILURE(MakeBuffers()); // Clear data should be copied even if there is no key selected, and no // license loaded. @@ -2511,103 +2511,103 @@ TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptUnencryptedNoKey) { ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); } -// Used to construct a specific pattern. -OEMCrypto_CENCEncryptPatternDesc MakePattern(size_t encrypt, size_t skip) { - OEMCrypto_CENCEncryptPatternDesc pattern; - pattern.encrypt = encrypt; - pattern.skip = skip; - pattern.offset = 0; // offset is deprecated. - return pattern; +// This tests the ability to decrypt multiple samples at once. +TEST_P(OEMCryptoSessionTestsDecryptTests, MultipleSamples) { + ASSERT_NO_FATAL_FAILURE(SetSampleSizes({ + { + {52, 160}, + {25, 256}, + {25, 320}, + }, + { + {300, 64}, + {50, 160}, + {2, 160}, + {24, 160}, + {128, 256}, + }, + { + {70, 320}, + {160, 160}, + }, + })); + ASSERT_NO_FATAL_FAILURE(LoadLicense()); + ASSERT_NO_FATAL_FAILURE(MakeBuffers()); + ASSERT_NO_FATAL_FAILURE(EncryptData()); + ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); } -INSTANTIATE_TEST_CASE_P(CTRTests, OEMCryptoSessionTestsPartialBlockTests, - Combine(Values(MakePattern(0,0)), - Values(OEMCrypto_CipherMode_CTR), - Bool())); - -// Decrypt in place for CBC tests was only required in v13. -INSTANTIATE_TEST_CASE_P( - CBCTestsAPI14, OEMCryptoSessionTestsPartialBlockTests, - Combine( - Values(MakePattern(0, 0), - MakePattern(3, 7), - // HLS Edge case. We should follow the CENC spec, not HLS spec. - MakePattern(9, 1), - MakePattern(1, 9), - MakePattern(1, 3), - MakePattern(2, 1)), - Values(OEMCrypto_CipherMode_CBC), Bool())); +// Used to construct a specific pattern. +constexpr OEMCrypto_CENCEncryptPatternDesc MakePattern(size_t encrypt, + size_t skip) { + return {encrypt, skip}; +} INSTANTIATE_TEST_CASE_P( - CTRTestsAPI11, OEMCryptoSessionTestsDecryptTests, - Combine( - Values(MakePattern(0, 0), - MakePattern(3, 7), - // Pattern length should be 10, but that is not guaranteed. - MakePattern(1, 3), - MakePattern(2, 1)), - Values(OEMCrypto_CipherMode_CTR), Bool())); + CTRTests, OEMCryptoSessionTestsDecryptTests, + Combine(Values(MakePattern(0, 0)), Values(OEMCrypto_CipherMode_CTR), + ::testing::ValuesIn(global_features.GetOutputTypes()))); // Decrypt in place for CBC tests was only required in v13. INSTANTIATE_TEST_CASE_P( CBCTestsAPI14, OEMCryptoSessionTestsDecryptTests, Combine( - Values(MakePattern(0, 0), - MakePattern(3, 7), - // HLS Edge case. We should follow the CENC spec, not HLS spec. - MakePattern(9, 1), - MakePattern(1, 9), - // Pattern length should be 10, but that is not guaranteed. - MakePattern(1, 3), - MakePattern(2, 1)), - Values(OEMCrypto_CipherMode_CBC), Bool())); + Values(MakePattern(3, 7), MakePattern(9, 1), + // HLS edge cases. We should follow the CENC spec, not HLS spec. + MakePattern(1, 9), MakePattern(1, 0), + // AV1 patterns not already covered above. + MakePattern(5, 5), MakePattern(10, 0)), + Values(OEMCrypto_CipherMode_CBC), + ::testing::ValuesIn(global_features.GetOutputTypes()))); // A request to decrypt data to a clear buffer when the key control block // requires a secure data path. -TEST_F(OEMCryptoSessionTests, DecryptSecureToClear) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - kDuration, - wvoec::kControlObserveDataPath | wvoec::kControlDataPathSecure, - 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); +TEST_P(OEMCryptoLicenseTest, DecryptSecureToClear) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.set_control(wvoec::kControlObserveDataPath | + wvoec::kControlDataPathSecure); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); ASSERT_NO_FATAL_FAILURE( - s.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + session_.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE)); } // If analog is forbidden, then decrypt to a clear buffer should be forbidden. -TEST_F(OEMCryptoSessionTests, DecryptNoAnalogToClearAPI13) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - kDuration, wvoec::kControlDisableAnalogOutput, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); +TEST_P(OEMCryptoLicenseTest, DecryptNoAnalogToClearAPI13) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.set_control(wvoec::kControlDisableAnalogOutput); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); ASSERT_NO_FATAL_FAILURE( - s.TestDecryptCTR(true, OEMCrypto_ERROR_ANALOG_OUTPUT)); + session_.TestDecryptCTR(true, OEMCrypto_ERROR_ANALOG_OUTPUT)); } // Test that key duration is honored. -TEST_F(OEMCryptoSessionTests, KeyDuration) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - kDuration, wvoec::kControlNonceEnabled, s.get_nonce())); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(true, OEMCrypto_SUCCESS)); - sleep(kShortSleep); // Should still be valid key. - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(false, OEMCrypto_SUCCESS)); - sleep(kLongSleep); // Should be expired key. - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED)); - ASSERT_NO_FATAL_FAILURE(s.TestSelectExpired(0)); +TEST_P(OEMCryptoLicenseTest, KeyDuration) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(true, OEMCrypto_SUCCESS)); + wvcdm::TestSleep::Sleep(kShortSleep); // Should still be valid key. + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR(false, OEMCrypto_SUCCESS)); + wvcdm::TestSleep::Sleep(kLongSleep); // Should be expired key. + ASSERT_NO_FATAL_FAILURE( + session_.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED)); + ASSERT_NO_FATAL_FAILURE(session_.TestSelectExpired(0)); } +INSTANTIATE_TEST_CASE_P(TestAll, OEMCryptoLicenseTest, + Range(kCurrentAPI - 1, kCurrentAPI + 1)); + // // Certificate Root of Trust Tests // @@ -2615,308 +2615,223 @@ class OEMCryptoLoadsCertificate : public OEMCryptoSessionTestKeyboxTest {}; // This test verifies that we can create a wrapped RSA key, and then reload it. TEST_F(OEMCryptoLoadsCertificate, LoadRSASessionKey) { - CreateWrappedRSAKey(kSign_RSASSA_PSS, true); + CreateWrappedRSAKey(); Session s; ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(s.InstallRSASessionTestKey(wrapped_rsa_key_)); } +TEST_F(OEMCryptoLoadsCertificate, SignProvisioningRequest) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + if (global_features.provisioning_method == OEMCrypto_OEMCertificate) { + s.LoadOEMCert(true); + } else { + EXPECT_EQ(global_features.provisioning_method, OEMCrypto_Keybox); + s.GenerateDerivedKeysFromKeybox(keybox_); + } + s.GenerateNonce(); + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); +} + +TEST_F(OEMCryptoLoadsCertificate, SignLargeProvisioningRequest) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + if (global_features.provisioning_method == OEMCrypto_OEMCertificate) { + s.LoadOEMCert(true); + } else { + EXPECT_EQ(global_features.provisioning_method, OEMCrypto_Keybox); + s.GenerateDerivedKeysFromKeybox(keybox_); + } + s.GenerateNonce(); + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + const size_t max_size = GetResourceValue(kLargeMessageSize); + provisioning_messages.set_message_size(max_size); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); +} + // This creates a wrapped RSA key, and then does the sanity check that the // unencrypted key is not found in the wrapped key. The wrapped key should be // encrypted. TEST_F(OEMCryptoLoadsCertificate, CertificateProvision) { - CreateWrappedRSAKey(kSign_RSASSA_PSS, true); + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); // We should not be able to find the rsa key in the wrapped key. It should // be encrypted. - ASSERT_EQ(NULL, find(wrapped_rsa_key_, encoded_rsa_key_)); + EXPECT_EQ(nullptr, find(provisioning_messages.wrapped_rsa_key(), + provisioning_messages.encoded_rsa_key())); } // Verify that RewrapDeviceRSAKey checks pointers are within the provisioning // message. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange1KeyboxTest) { +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange1_API16) { Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromKeybox(keybox_)); - ASSERT_NO_FATAL_FAILURE(s.VerifyClientSignature()); - struct RSAPrivateKeyMessage encrypted; - std::vector signature; - ASSERT_NO_FATAL_FAILURE(s.MakeRSACertificate(&encrypted, sizeof(encrypted), - &signature, kSign_RSASSA_PSS, - encoded_rsa_key_)); - vector wrapped_key; - const uint8_t* message_ptr = reinterpret_cast(&encrypted); - size_t wrapped_key_length = 0; - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, - OEMCrypto_RewrapDeviceRSAKey( - s.session_id(), message_ptr, sizeof(encrypted), - signature.data(), signature.size(), &encrypted.nonce, - encrypted.rsa_key, encrypted.rsa_key_length, - encrypted.rsa_key_iv, NULL, &wrapped_key_length)); - wrapped_key.clear(); - wrapped_key.assign(wrapped_key_length, 0); - uint32_t nonce = encrypted.nonce; - ASSERT_NE( - OEMCrypto_SUCCESS, - OEMCrypto_RewrapDeviceRSAKey( - s.session_id(), message_ptr, sizeof(encrypted), signature.data(), - signature.size(), &nonce, encrypted.rsa_key, encrypted.rsa_key_length, - encrypted.rsa_key_iv, &(wrapped_key.front()), &wrapped_key_length)); + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + provisioning_messages.core_response().enc_private_key.offset = + provisioning_messages.encrypted_response_buffer().size() + 1; + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); } // Verify that RewrapDeviceRSAKey checks pointers are within the provisioning // message. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange2KeyboxTest) { +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange2_API16) { Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromKeybox(keybox_)); - // Provisioning request would be signed by client and verified by server. - ASSERT_NO_FATAL_FAILURE(s.VerifyClientSignature()); - struct RSAPrivateKeyMessage encrypted; - std::vector signature; - s.MakeRSACertificate(&encrypted, sizeof(encrypted), &signature, - kSign_RSASSA_PSS, encoded_rsa_key_); - vector wrapped_key; - const uint8_t* message_ptr = reinterpret_cast(&encrypted); - size_t wrapped_key_length = 0; - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, - OEMCrypto_RewrapDeviceRSAKey( - s.session_id(), message_ptr, sizeof(encrypted), - signature.data(), signature.size(), &encrypted.nonce, - encrypted.rsa_key, encrypted.rsa_key_length, - encrypted.rsa_key_iv, NULL, &wrapped_key_length)); - wrapped_key.clear(); - wrapped_key.assign(wrapped_key_length, 0); - vector bad_buffer(encrypted.rsa_key, - encrypted.rsa_key + sizeof(encrypted.rsa_key)); - - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_RewrapDeviceRSAKey( - s.session_id(), message_ptr, sizeof(encrypted), - signature.data(), signature.size(), &encrypted.nonce, - bad_buffer.data(), encrypted.rsa_key_length, - encrypted.rsa_key_iv, wrapped_key.data(), &wrapped_key_length)); + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + provisioning_messages.core_response().enc_private_key_iv.offset = + provisioning_messages.encrypted_response_buffer().size() + 1; + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); } // Verify that RewrapDeviceRSAKey checks pointers are within the provisioning // message. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange3KeyboxTest) { +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange3_API16) { Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromKeybox(keybox_)); - // Provisioning request would be signed by client and verified by server. - ASSERT_NO_FATAL_FAILURE(s.VerifyClientSignature()); - struct RSAPrivateKeyMessage encrypted; - std::vector signature; - s.MakeRSACertificate(&encrypted, sizeof(encrypted), &signature, - kSign_RSASSA_PSS, encoded_rsa_key_); - const uint8_t* message_ptr = reinterpret_cast(&encrypted); - vector wrapped_key; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + // If the offset is before the end, but the offset+length is bigger, then + // the message should be rejected. + provisioning_messages.core_response().enc_private_key.offset = + provisioning_messages.encrypted_response_buffer().size() - 5; + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); +} - size_t wrapped_key_length = 0; - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, - OEMCrypto_RewrapDeviceRSAKey( - s.session_id(), message_ptr, sizeof(encrypted), - signature.data(), signature.size(), &encrypted.nonce, - encrypted.rsa_key, encrypted.rsa_key_length, - encrypted.rsa_key_iv, NULL, &wrapped_key_length)); - wrapped_key.clear(); - wrapped_key.assign(wrapped_key_length, 0); - vector bad_buffer(encrypted.rsa_key, - encrypted.rsa_key + sizeof(encrypted.rsa_key)); +// Verify that RewrapDeviceRSAKey checks pointers are within the provisioning +// message. +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange4_API16) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + // If the offset is before the end, but the offset+length is bigger, then + // the message should be rejected. + provisioning_messages.core_response().enc_private_key_iv.offset = + provisioning_messages.encrypted_response_buffer().size() - 5; + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); +} - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_RewrapDeviceRSAKey( - s.session_id(), message_ptr, sizeof(encrypted), - signature.data(), signature.size(), &encrypted.nonce, - encrypted.rsa_key, encrypted.rsa_key_length, bad_buffer.data(), - &(wrapped_key.front()), &wrapped_key_length)); +// Verify that RewrapDeviceRSAKey checks pointers are within the provisioning +// message. +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange5Prov30_API16) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + // If the offset is before the end, but the offset+length is bigger, then + // the message should be rejected. + provisioning_messages.core_response().encrypted_message_key.offset = + provisioning_messages.encrypted_response_buffer().size() + 1; + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); } // Test that RewrapDeviceRSAKey verifies the message signature. +// TODO(b/144186970): This test should also run on Prov 3.0 devices. TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadSignatureKeyboxTest) { Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromKeybox(keybox_)); - // Provisioning request would be signed by client and verified by server. - ASSERT_NO_FATAL_FAILURE(s.VerifyClientSignature()); - struct RSAPrivateKeyMessage encrypted; - std::vector signature; - s.MakeRSACertificate(&encrypted, sizeof(encrypted), &signature, - kSign_RSASSA_PSS, encoded_rsa_key_); - vector wrapped_key; - const uint8_t* message_ptr = reinterpret_cast(&encrypted); - - size_t wrapped_key_length = 0; - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, - OEMCrypto_RewrapDeviceRSAKey( - s.session_id(), message_ptr, sizeof(encrypted), - signature.data(), signature.size(), &encrypted.nonce, - encrypted.rsa_key, encrypted.rsa_key_length, - encrypted.rsa_key_iv, NULL, &wrapped_key_length)); - wrapped_key.clear(); - wrapped_key.assign(wrapped_key_length, 0); - signature[4] ^= 42; // bad signature. - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_RewrapDeviceRSAKey( - s.session_id(), message_ptr, sizeof(encrypted), - signature.data(), signature.size(), &encrypted.nonce, - encrypted.rsa_key, encrypted.rsa_key_length, - encrypted.rsa_key_iv, wrapped_key.data(), &wrapped_key_length)); + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + provisioning_messages.response_signature()[4] ^= 42; // bad signature. + ASSERT_EQ(OEMCrypto_ERROR_SIGNATURE_FAILURE, + provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); } // Test that RewrapDeviceRSAKey verifies the nonce is current. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadNonceKeyboxTest) { +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadNonce_API16) { Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromKeybox(keybox_)); - // Provisioning request would be signed by client and verified by server. - ASSERT_NO_FATAL_FAILURE(s.VerifyClientSignature()); - struct RSAPrivateKeyMessage encrypted; - std::vector signature; - s.MakeRSACertificate(&encrypted, sizeof(encrypted), &signature, - kSign_RSASSA_PSS, encoded_rsa_key_); - vector wrapped_key; - const uint8_t* message_ptr = reinterpret_cast(&encrypted); - - size_t wrapped_key_length = 0; - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, - OEMCrypto_RewrapDeviceRSAKey( - s.session_id(), message_ptr, sizeof(encrypted), - signature.data(), signature.size(), &encrypted.nonce, - encrypted.rsa_key, encrypted.rsa_key_length, - encrypted.rsa_key_iv, NULL, &wrapped_key_length)); - wrapped_key.clear(); - wrapped_key.assign(wrapped_key_length, 0); - encrypted.nonce ^= 42; // Almost surely a bad nonce. + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + provisioning_messages.core_request().nonce ^= 42; // bad nonce. + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, - OEMCrypto_RewrapDeviceRSAKey( - s.session_id(), message_ptr, sizeof(encrypted), - signature.data(), signature.size(), &encrypted.nonce, - encrypted.rsa_key, encrypted.rsa_key_length, - encrypted.rsa_key_iv, wrapped_key.data(), &wrapped_key_length)); + provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); } // Test that RewrapDeviceRSAKey verifies the RSA key is valid. +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRSAKey) { + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + provisioning_messages.response_data().rsa_key[4] ^= 42; // bad key. + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); +} + +// Test that RewrapDeviceRSAKey verifies the RSA key is valid. +// TODO(b/144186970): This test should also run on Prov 3.0 devices. TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRSAKeyKeyboxTest) { Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromKeybox(keybox_)); - // Provisioning request would be signed by client and verified by server. - ASSERT_NO_FATAL_FAILURE(s.VerifyClientSignature()); - struct RSAPrivateKeyMessage encrypted; - std::vector signature; - s.MakeRSACertificate(&encrypted, sizeof(encrypted), &signature, - kSign_RSASSA_PSS, encoded_rsa_key_); - vector wrapped_key; - const uint8_t* message_ptr = reinterpret_cast(&encrypted); - - size_t wrapped_key_length = 0; - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, - OEMCrypto_RewrapDeviceRSAKey( - s.session_id(), message_ptr, sizeof(encrypted), - signature.data(), signature.size(), &encrypted.nonce, - encrypted.rsa_key, encrypted.rsa_key_length, - encrypted.rsa_key_iv, NULL, &wrapped_key_length)); - wrapped_key.clear(); - wrapped_key.assign(wrapped_key_length, 0); - encrypted.rsa_key[1] ^= 42; // Almost surely a bad key. - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_RewrapDeviceRSAKey( - s.session_id(), message_ptr, sizeof(encrypted), - signature.data(), signature.size(), &encrypted.nonce, - encrypted.rsa_key, encrypted.rsa_key_length, - encrypted.rsa_key_iv, wrapped_key.data(), &wrapped_key_length)); + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + size_t rsa_offset = + provisioning_messages.core_response().enc_private_key.offset; + // Offsets are relative to the message body, after the core message. + rsa_offset += provisioning_messages.serialized_core_message().size(); + rsa_offset += 4; // Change the middle of the key. + provisioning_messages.encrypted_response_buffer()[rsa_offset] ^= 42; + ASSERT_EQ(OEMCrypto_ERROR_SIGNATURE_FAILURE, + provisioning_messages.LoadResponse()); + provisioning_messages.VerifyLoadFailed(); } // Test that RewrapDeviceRSAKey accepts the maximum message size. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionLargeBufferKeyboxTest) { +TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionLargeBuffer) { Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromKeybox(keybox_)); - // Provisioning request would be signed by client and verified by server. - ASSERT_NO_FATAL_FAILURE(s.VerifyClientSignature()); - struct LargeRSAPrivateKeyMessage : public RSAPrivateKeyMessage { - uint8_t padding[kMaxMessageSize - sizeof(RSAPrivateKeyMessage)]; - } encrypted; - std::vector signature; - s.MakeRSACertificate(&encrypted, sizeof(encrypted), &signature, - kSign_RSASSA_PSS, encoded_rsa_key_); - vector wrapped_key; - ASSERT_NO_FATAL_FAILURE(s.RewrapRSAKey(encrypted, sizeof(encrypted), - signature, &wrapped_key, true)); - // Verify that the clear key is not contained in the wrapped key. - // It should be encrypted. - ASSERT_EQ(NULL, find(wrapped_key, encoded_rsa_key_)); -} - -// Test that RewrapDeviceRSAKey30 verifies the nonce. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadNonceProv30Test) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.LoadOEMCert()); - s.GenerateNonce(); - uint32_t bad_nonce = s.get_nonce() ^ 42; - struct RSAPrivateKeyMessage encrypted; - std::vector signature; - std::vector message_key; - std::vector encrypted_message_key; - s.GenerateRSASessionKey(&message_key, &encrypted_message_key); - ASSERT_NO_FATAL_FAILURE(s.MakeRSACertificate(&encrypted, sizeof(encrypted), - &signature, kSign_RSASSA_PSS, - encoded_rsa_key_, &message_key)); - size_t wrapped_key_length = 0; - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, - OEMCrypto_RewrapDeviceRSAKey30( - s.session_id(), &bad_nonce, encrypted_message_key.data(), - encrypted_message_key.size(), encrypted.rsa_key, - encrypted.rsa_key_length, encrypted.rsa_key_iv, NULL, - &wrapped_key_length)); - vector wrapped_key(wrapped_key_length, 0); - ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, - OEMCrypto_RewrapDeviceRSAKey30( - s.session_id(), &bad_nonce, encrypted_message_key.data(), - encrypted_message_key.size(), encrypted.rsa_key, - encrypted.rsa_key_length, encrypted.rsa_key_iv, - wrapped_key.data(), &wrapped_key_length)); -} - -// Test that RewrapDeviceRSAKey30 verifies that the RSA key is valid. -TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRSAKeyProv30Test) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.LoadOEMCert()); - s.GenerateNonce(); - struct RSAPrivateKeyMessage encrypted; - std::vector signature; - std::vector message_key; - std::vector encrypted_message_key; - s.GenerateRSASessionKey(&message_key, &encrypted_message_key); - ASSERT_NO_FATAL_FAILURE(s.MakeRSACertificate(&encrypted, sizeof(encrypted), - &signature, kSign_RSASSA_PSS, - encoded_rsa_key_, &message_key)); - size_t wrapped_key_length = 0; - uint32_t nonce = s.get_nonce(); - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, - OEMCrypto_RewrapDeviceRSAKey30( - s.session_id(), &nonce, encrypted_message_key.data(), - encrypted_message_key.size(), encrypted.rsa_key, - encrypted.rsa_key_length, encrypted.rsa_key_iv, NULL, - &wrapped_key_length)); - vector wrapped_key(wrapped_key_length, 0); - encrypted.rsa_key[1] ^= 42; // Almost surely a bad key. - ASSERT_EQ(OEMCrypto_ERROR_INVALID_RSA_KEY, - OEMCrypto_RewrapDeviceRSAKey30( - s.session_id(), &nonce, encrypted_message_key.data(), - encrypted_message_key.size(), encrypted.rsa_key, - encrypted.rsa_key_length, encrypted.rsa_key_iv, - wrapped_key.data(), &wrapped_key_length)); + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + const size_t max_size = GetResourceValue(kLargeMessageSize); + provisioning_messages.set_message_size(max_size); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, provisioning_messages.LoadResponse()); + // We should not be able to find the rsa key in the wrapped key. It should + // be encrypted. + EXPECT_EQ(nullptr, find(provisioning_messages.wrapped_rsa_key(), + provisioning_messages.encoded_rsa_key())); } // Test that a wrapped RSA key can be loaded. TEST_F(OEMCryptoLoadsCertificate, LoadWrappedRSAKey) { OEMCryptoResult sts; - CreateWrappedRSAKey(kSign_RSASSA_PSS, true); - + CreateWrappedRSAKey(); Session s; ASSERT_NO_FATAL_FAILURE(s.open()); sts = OEMCrypto_LoadDeviceRSAKey(s.session_id(), wrapped_rsa_key_.data(), @@ -2924,33 +2839,24 @@ TEST_F(OEMCryptoLoadsCertificate, LoadWrappedRSAKey) { ASSERT_EQ(OEMCrypto_SUCCESS, sts); } -// This tests that a device with a keybox can also decrypt with a cert. -// Decrypt for devices that only use a cert are tested in the session tests. -TEST_F(OEMCryptoLoadsCertificate, CertificateDecrypt) { - CreateWrappedRSAKey(kSign_RSASSA_PSS, true); - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.InstallRSASessionTestKey(wrapped_rsa_key_)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); -} - // Test a 3072 bit RSA key certificate. TEST_F(OEMCryptoLoadsCertificate, TestLargeRSAKey3072) { encoded_rsa_key_.assign(kTestRSAPKCS8PrivateKeyInfo3_3072, kTestRSAPKCS8PrivateKeyInfo3_3072 + sizeof(kTestRSAPKCS8PrivateKeyInfo3_3072)); - CreateWrappedRSAKey(kSign_RSASSA_PSS, true); + CreateWrappedRSAKey(); Session s; ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(s.PreparePublicKey(encoded_rsa_key_.data(), encoded_rsa_key_.size())); ASSERT_NO_FATAL_FAILURE(s.InstallRSASessionTestKey(wrapped_rsa_key_)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + + LicenseRoundTrip license_messages(&s); + ASSERT_NO_FATAL_FAILURE(s.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages.LoadResponse()); ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); } @@ -2960,21 +2866,25 @@ TEST_F(OEMCryptoLoadsCertificate, TestCarmichaelRSAKey) { encoded_rsa_key_.assign( kTestKeyRSACarmichael_2048, kTestKeyRSACarmichael_2048 + sizeof(kTestKeyRSACarmichael_2048)); - CreateWrappedRSAKey(kSign_RSASSA_PSS, true); + CreateWrappedRSAKey(); Session s; ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(s.PreparePublicKey(encoded_rsa_key_.data(), encoded_rsa_key_.size())); ASSERT_NO_FATAL_FAILURE(s.InstallRSASessionTestKey(wrapped_rsa_key_)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage(kDuration, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys()); + + LicenseRoundTrip license_messages(&s); + ASSERT_NO_FATAL_FAILURE(s.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages.LoadResponse()); ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); } // This tests that two sessions can use different RSA keys simultaneously. TEST_F(OEMCryptoLoadsCertificate, TestMultipleRSAKeys) { - CreateWrappedRSAKey(kSign_RSASSA_PSS, true); + CreateWrappedRSAKey(); Session s1; // Session s1 loads the default rsa key, but doesn't use it // until after s2 uses its key. ASSERT_NO_FATAL_FAILURE(s1.open()); @@ -2988,22 +2898,27 @@ TEST_F(OEMCryptoLoadsCertificate, TestMultipleRSAKeys) { encoded_rsa_key_.assign(kTestRSAPKCS8PrivateKeyInfo4_2048, kTestRSAPKCS8PrivateKeyInfo4_2048 + sizeof(kTestRSAPKCS8PrivateKeyInfo4_2048)); - CreateWrappedRSAKey(kSign_RSASSA_PSS, true); + CreateWrappedRSAKey(); ASSERT_NO_FATAL_FAILURE(s2.open()); ASSERT_NO_FATAL_FAILURE(s2.PreparePublicKey(encoded_rsa_key_.data(), encoded_rsa_key_.size())); ASSERT_NO_FATAL_FAILURE(s2.InstallRSASessionTestKey(wrapped_rsa_key_)); - ASSERT_NO_FATAL_FAILURE(s2.FillSimpleMessage(kDuration, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s2.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s2.LoadTestKeys()); + LicenseRoundTrip license_messages2(&s2); + ASSERT_NO_FATAL_FAILURE(s2.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages2.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages2.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages2.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages2.LoadResponse()); ASSERT_NO_FATAL_FAILURE(s2.TestDecryptCTR()); s2.close(); // After s2 has loaded its rsa key, we continue using s1's key. - ASSERT_NO_FATAL_FAILURE(s1.GenerateDerivedKeysFromSessionKey()); - ASSERT_NO_FATAL_FAILURE(s1.FillSimpleMessage(kDuration, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s1.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s1.LoadTestKeys()); + LicenseRoundTrip license_messages1(&s1); + ASSERT_NO_FATAL_FAILURE(s1.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages1.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages1.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages1.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages1.LoadResponse()); ASSERT_NO_FATAL_FAILURE(s1.TestDecryptCTR()); } @@ -3014,6 +2929,9 @@ TEST_F(OEMCryptoLoadsCertificate, SupportsCertificatesAPI13) { << "Supported certificates is only " << OEMCrypto_SupportedCertificates(); } +// These tests are run by all L1 devices that load and use certificates. It is +// also run by a few L3 devices that use a baked in certificate, but cannot load +// a certificate. class OEMCryptoUsesCertificate : public OEMCryptoLoadsCertificate { protected: void SetUp() override { @@ -3021,16 +2939,12 @@ class OEMCryptoUsesCertificate : public OEMCryptoLoadsCertificate { ASSERT_NO_FATAL_FAILURE(session_.open()); if (global_features.derive_key_method != DeviceFeatures::LOAD_TEST_RSA_KEY) { - CreateWrappedRSAKey(kSign_RSASSA_PSS, true); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_LoadDeviceRSAKey(session_.session_id(), - wrapped_rsa_key_.data(), - wrapped_rsa_key_.size())); + InstallTestRSAKey(&session_); } } void TearDown() override { - session_.close(); + ASSERT_NO_FATAL_FAILURE(session_.close()); OEMCryptoLoadsCertificate::TearDown(); } @@ -3043,19 +2957,19 @@ TEST_F(OEMCryptoLoadsCertificate, RSAPerformance) { const std::chrono::milliseconds kTestDuration(5000); OEMCryptoResult sts; std::chrono::steady_clock clock; - sleep(2); // Make sure are not nonce limited. + wvcdm::TestSleep::Sleep(kShortSleep); // Make sure we are not nonce limited. auto start_time = clock.now(); int count = 15; for (int i = 0; i < count; i++) { // Only 20 nonce available. - CreateWrappedRSAKey(kSign_RSASSA_PSS, true); + CreateWrappedRSAKey(); } auto delta_time = clock.now() - start_time; const double provision_time = delta_time / std::chrono::milliseconds(1) / count; Session session; - CreateWrappedRSAKey(kSign_RSASSA_PSS, true); + CreateWrappedRSAKey(); start_time = clock.now(); count = 0; while (clock.now() - start_time < kTestDuration) { @@ -3069,7 +2983,7 @@ TEST_F(OEMCryptoLoadsCertificate, RSAPerformance) { GetRandBytes(licenseRequest.data(), licenseRequest.size()); size_t signature_length = 0; sts = OEMCrypto_GenerateRSASignature(s.session_id(), licenseRequest.data(), - licenseRequest.size(), NULL, + licenseRequest.size(), nullptr, &signature_length, kSign_RSASSA_PSS); ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); ASSERT_NE(static_cast(0), signature_length); @@ -3137,67 +3051,6 @@ TEST_F(OEMCryptoLoadsCertificate, RSAPerformance) { license_request_time, derive_keys_time); } -// Test that OEMCrypto can compute an RSA signature. -TEST_F(OEMCryptoUsesCertificate, RSASignature) { - OEMCryptoResult sts; - // Sign a Message - vector licenseRequest(500); - GetRandBytes(licenseRequest.data(), licenseRequest.size()); - size_t signature_length = 0; - uint8_t signature[500]; - - sts = OEMCrypto_GenerateRSASignature( - session_.session_id(), licenseRequest.data(), licenseRequest.size(), - signature, &signature_length, kSign_RSASSA_PSS); - - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - ASSERT_NE(static_cast(0), signature_length); - ASSERT_GE(sizeof(signature), signature_length); - - sts = OEMCrypto_GenerateRSASignature( - session_.session_id(), licenseRequest.data(), licenseRequest.size(), - signature, &signature_length, kSign_RSASSA_PSS); - - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - // In the real world, the signature above would just have been used to contact - // the license server to get this response. - ASSERT_NO_FATAL_FAILURE(session_.PreparePublicKey(encoded_rsa_key_.data(), - encoded_rsa_key_.size())); - ASSERT_NO_FATAL_FAILURE(session_.VerifyRSASignature( - licenseRequest, signature, signature_length, kSign_RSASSA_PSS)); -} - -// Test that OEMCrypto can compute an RSA signature of a message with the -// maximum size. -TEST_F(OEMCryptoUsesCertificate, RSASignatureLargeBuffer) { - OEMCryptoResult sts; - // Sign a Message - vector licenseRequest(kMaxMessageSize); - GetRandBytes(licenseRequest.data(), licenseRequest.size()); - size_t signature_length = 0; - uint8_t signature[500]; - - sts = OEMCrypto_GenerateRSASignature( - session_.session_id(), licenseRequest.data(), licenseRequest.size(), - signature, &signature_length, kSign_RSASSA_PSS); - - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - ASSERT_NE(static_cast(0), signature_length); - ASSERT_GE(sizeof(signature), signature_length); - - sts = OEMCrypto_GenerateRSASignature( - session_.session_id(), licenseRequest.data(), licenseRequest.size(), - signature, &signature_length, kSign_RSASSA_PSS); - - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - // In the real world, the signature above would just have been used to contact - // the license server to get this response. - ASSERT_NO_FATAL_FAILURE(session_.PreparePublicKey(encoded_rsa_key_.data(), - encoded_rsa_key_.size())); - ASSERT_NO_FATAL_FAILURE(session_.VerifyRSASignature( - licenseRequest, signature, signature_length, kSign_RSASSA_PSS)); -} - // Test DeriveKeysFromSessionKey using the maximum size for the HMAC context. TEST_F(OEMCryptoUsesCertificate, GenerateDerivedKeysLargeBuffer) { vector session_key; @@ -3205,10 +3058,11 @@ TEST_F(OEMCryptoUsesCertificate, GenerateDerivedKeysLargeBuffer) { ASSERT_NO_FATAL_FAILURE(session_.PreparePublicKey(encoded_rsa_key_.data(), encoded_rsa_key_.size())); ASSERT_TRUE(session_.GenerateRSASessionKey(&session_key, &enc_session_key)); - vector mac_context(kMaxMessageSize); - vector enc_context(kMaxMessageSize); + const size_t max_size = GetResourceValue(kLargeMessageSize); + vector mac_context(max_size); + vector enc_context(max_size); // Stripe the data so the two vectors are not identical, and not all zeroes. - for (size_t i = 0; i < kMaxMessageSize; i++) { + for (size_t i = 0; i < max_size; i++) { mac_context[i] = i % 0x100; enc_context[i] = (3 * i) % 0x100; } @@ -3270,7 +3124,7 @@ class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate { GetRandBytes(licenseRequest.data(), licenseRequest.size()); size_t signature_length = 0; sts = OEMCrypto_GenerateRSASignature(s.session_id(), licenseRequest.data(), - licenseRequest.size(), NULL, + licenseRequest.size(), nullptr, &signature_length, scheme); ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); ASSERT_NE(static_cast(0), signature_length); @@ -3315,9 +3169,22 @@ class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate { // If force is true, we assert that the key loads successfully. void LoadWithAllowedSchemes(uint32_t schemes, bool force) { - CreateWrappedRSAKey(schemes, force); + Session s; + ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + provisioning_messages.set_allowed_schemes(schemes); + provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); + OEMCryptoResult sts = provisioning_messages.LoadResponse(); + encoded_rsa_key_ = provisioning_messages.encoded_rsa_key(); + wrapped_rsa_key_ = provisioning_messages.wrapped_rsa_key(); key_loaded_ = (wrapped_rsa_key_.size() > 0); - if (force) ASSERT_TRUE(key_loaded_); + if (force) { + EXPECT_EQ(OEMCrypto_SUCCESS, sts); + EXPECT_EQ(nullptr, find(wrapped_rsa_key_, encoded_rsa_key_)); + ASSERT_TRUE(key_loaded_); + } } bool key_loaded_; @@ -3333,7 +3200,7 @@ TEST_F(OEMCryptoLoadsCertificateAlternates, DisallowForbiddenPaddingAPI09) { // The alternate padding is only required for cast receivers, but if a device // does load an alternate certificate, it should NOT use it for generating // a license request signature. -TEST_F(OEMCryptoLoadsCertificateAlternates, TestSignaturePKCS1) { +TEST_F(OEMCryptoLoadsCertificateAlternates, TestSignaturePKCS1_API16) { // Try to load an RSA key with alternative padding schemes. This signing // scheme is used by cast receivers. LoadWithAllowedSchemes(kSign_PKCS1_Block1, false); @@ -3355,25 +3222,6 @@ TEST_F(OEMCryptoLoadsCertificateAlternates, TestSignaturePKCS1) { } } -// Try to load an RSA key with alternative padding schemes. This key is allowed -// to sign with either padding scheme. Devices are not required to support both -// padding schemes. -TEST_F(OEMCryptoLoadsCertificateAlternates, TestSignatureBoth) { - LoadWithAllowedSchemes(kSign_RSASSA_PSS | kSign_PKCS1_Block1, false); - // If the device loads this key, it should process it correctly. - if (key_loaded_) { - DisallowDeriveKeys(); - // A signature with padding that is too big should fail. - DisallowForbiddenPadding(kSign_PKCS1_Block1, 84); - if (global_features.cast_receiver) { - TestSignature(kSign_RSASSA_PSS, 200); - // A signature with a valid size should succeed. - TestSignature(kSign_PKCS1_Block1, 83); - TestSignature(kSign_PKCS1_Block1, 50); - } - } -} - // This test verifies RSA signing with the alternate padding scheme used by // Android cast receivers, PKCS1 Block 1. These tests are not required for // other devices, and should be filtered out by DeviceFeatures::Initialize for @@ -3555,8 +3403,8 @@ class OEMCryptoCastReceiverTest : public OEMCryptoLoadsCertificateAlternates { // OEMCrypto will apply the padding, and encrypt to generate the signature. size_t signature_length = 0; sts = OEMCrypto_GenerateRSASignature(s.session_id(), digest.data(), - digest.size(), NULL, &signature_length, - scheme); + digest.size(), nullptr, + &signature_length, scheme); ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); ASSERT_NE(static_cast(0), signature_length); @@ -4284,42 +4132,22 @@ TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_20) { } // This class is for testing the generic crypto functionality. -class GenericCryptoTest : public OEMCryptoSessionTests { +class OEMCryptoGenericCryptoTest : public OEMCryptoRefreshTest { protected: // buffer_size_ must be a multiple of encryption block size, 16. We'll use a // reasonable number of blocks for most of the tests. - GenericCryptoTest() : buffer_size_(160) {} + OEMCryptoGenericCryptoTest() : buffer_size_(160) {} void SetUp() override { - OEMCryptoSessionTests::SetUp(); - ASSERT_NO_FATAL_FAILURE(session_.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&session_)); - ASSERT_NO_FATAL_FAILURE(MakeFourKeys()); - } - - void TearDown() override { - session_.close(); - OEMCryptoSessionTests::TearDown(); - } - - // This makes four keys, one for each of the generic operations that we want - // to test. - void MakeFourKeys(uint32_t duration = kDuration, uint32_t control = 0, - uint32_t nonce = 0, const std::string& pst = "") { + OEMCryptoRefreshTest::SetUp(); + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE( - session_.FillSimpleMessage(duration, control, nonce, pst)); - session_.license().keys[0].control.control_bits |= - htonl(wvoec::kControlAllowEncrypt); - session_.license().keys[1].control.control_bits |= - htonl(wvoec::kControlAllowDecrypt); - session_.license().keys[2].control.control_bits |= - htonl(wvoec::kControlAllowSign); - session_.license().keys[3].control.control_bits |= - htonl(wvoec::kControlAllowVerify); - - session_.license().keys[2].key_data_length = wvoec::MAC_KEY_SIZE; - session_.license().keys[3].key_data_length = wvoec::MAC_KEY_SIZE; + license_messages_.CreateResponseWithGenericCryptoKeys()); + InitializeClearBuffer(); + } + void InitializeClearBuffer() { clear_buffer_.assign(buffer_size_, 0); for (size_t i = 0; i < clear_buffer_.size(); i++) { clear_buffer_[i] = 1 + i % 250; @@ -4330,17 +4158,23 @@ class GenericCryptoTest : public OEMCryptoSessionTests { } void EncryptAndLoadKeys() { - ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); - session_.LoadTestKeys(); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } - // Encrypt the buffer with the specified key made in MakeFourKeys. + // Encrypt the buffer with the specified key made in + // CreateResponseWithGenericCryptoKeys. void EncryptBuffer(unsigned int key_index, const vector& in_buffer, vector* out_buffer) { + EncryptBufferWithKey(session_.license().keys[key_index].key_data, in_buffer, + out_buffer); + } + // Encrypt the buffer with the specified key. + void EncryptBufferWithKey(const uint8_t* key_data, + const vector& in_buffer, + vector* out_buffer) { AES_KEY aes_key; - ASSERT_EQ(0, - AES_set_encrypt_key(session_.license().keys[key_index].key_data, - AES_BLOCK_SIZE * 8, &aes_key)); + ASSERT_EQ(0, AES_set_encrypt_key(key_data, AES_BLOCK_SIZE * 8, &aes_key)); uint8_t iv_buffer[wvoec::KEY_IV_SIZE]; memcpy(iv_buffer, iv_, wvoec::KEY_IV_SIZE); out_buffer->resize(in_buffer.size()); @@ -4350,14 +4184,22 @@ class GenericCryptoTest : public OEMCryptoSessionTests { &aes_key, iv_buffer, AES_ENCRYPT); } - // Sign the buffer with the specified key. + // Sign the buffer with the specified key made in + // CreateResponseWithGenericCryptoKeys. void SignBuffer(unsigned int key_index, const vector& in_buffer, vector* signature) { + SignBufferWithKey(session_.license().keys[key_index].key_data, in_buffer, + signature); + } + + // Sign the buffer with the specified key. + void SignBufferWithKey(const uint8_t* key_data, + const vector& in_buffer, + vector* signature) { unsigned int md_len = SHA256_DIGEST_LENGTH; signature->resize(SHA256_DIGEST_LENGTH); - HMAC(EVP_sha256(), session_.license().keys[key_index].key_data, - wvoec::MAC_KEY_SIZE, in_buffer.data(), in_buffer.size(), - signature->data(), &md_len); + HMAC(EVP_sha256(), key_data, wvoec::MAC_KEY_SIZE, in_buffer.data(), + in_buffer.size(), signature->data(), &md_len); } // This asks OEMCrypto to encrypt with the specified key, and expects a @@ -4451,13 +4293,12 @@ class GenericCryptoTest : public OEMCryptoSessionTests { vector clear_buffer_; vector encrypted_buffer_; uint8_t iv_[wvoec::KEY_IV_SIZE]; - Session session_; }; -TEST_F(GenericCryptoTest, GenericKeyLoad) { EncryptAndLoadKeys(); } +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyLoad) { EncryptAndLoadKeys(); } // Test that the Generic_Encrypt function works correctly. -TEST_F(GenericCryptoTest, GenericKeyEncrypt) { +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncrypt) { EncryptAndLoadKeys(); unsigned int key_index = 0; vector expected_encrypted; @@ -4478,7 +4319,7 @@ TEST_F(GenericCryptoTest, GenericKeyEncrypt) { } // Test that the Generic_Encrypt function fails when not allowed. -TEST_F(GenericCryptoTest, GenericKeyBadEncrypt) { +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadEncrypt) { EncryptAndLoadKeys(); BadEncrypt(0, OEMCrypto_HMAC_SHA256, buffer_size_); // The buffer size must be a multiple of 16, so subtracting 10 is bad. @@ -4490,7 +4331,7 @@ TEST_F(GenericCryptoTest, GenericKeyBadEncrypt) { // Test that the Generic_Encrypt works if the input and output buffers are the // same. -TEST_F(GenericCryptoTest, GenericKeyEncryptSameBufferAPI12) { +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncryptSameBufferAPI12) { EncryptAndLoadKeys(); unsigned int key_index = 0; vector expected_encrypted; @@ -4511,7 +4352,7 @@ TEST_F(GenericCryptoTest, GenericKeyEncryptSameBufferAPI12) { } // Test Generic_Decrypt works correctly. -TEST_F(GenericCryptoTest, GenericKeyDecrypt) { +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecrypt) { EncryptAndLoadKeys(); unsigned int key_index = 1; vector encrypted; @@ -4532,7 +4373,7 @@ TEST_F(GenericCryptoTest, GenericKeyDecrypt) { // Test that Generic_Decrypt works correctly when the input and output buffers // are the same. -TEST_F(GenericCryptoTest, GenericKeyDecryptSameBufferAPI12) { +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecryptSameBufferAPI12) { EncryptAndLoadKeys(); unsigned int key_index = 1; vector encrypted; @@ -4553,9 +4394,10 @@ TEST_F(GenericCryptoTest, GenericKeyDecryptSameBufferAPI12) { // Test that Generic_Decrypt fails to decrypt to an insecure buffer if the key // requires a secure data path. -TEST_F(GenericCryptoTest, GenericSecureToClear) { - session_.license().keys[1].control.control_bits |= htonl( - wvoec::kControlObserveDataPath | wvoec::kControlDataPathSecure); +TEST_P(OEMCryptoGenericCryptoTest, GenericSecureToClear) { + license_messages_.set_control(wvoec::kControlObserveDataPath | + wvoec::kControlDataPathSecure); + license_messages_.CreateResponseWithGenericCryptoKeys(); EncryptAndLoadKeys(); unsigned int key_index = 1; vector encrypted; @@ -4575,7 +4417,7 @@ TEST_F(GenericCryptoTest, GenericSecureToClear) { } // Test that the Generic_Decrypt function fails when not allowed. -TEST_F(GenericCryptoTest, GenericKeyBadDecrypt) { +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadDecrypt) { EncryptAndLoadKeys(); BadDecrypt(1, OEMCrypto_HMAC_SHA256, buffer_size_); // The buffer size must be a multiple of 16, so subtracting 10 is bad. @@ -4585,7 +4427,7 @@ TEST_F(GenericCryptoTest, GenericKeyBadDecrypt) { BadDecrypt(3, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); } -TEST_F(GenericCryptoTest, GenericKeySign) { +TEST_P(OEMCryptoGenericCryptoTest, GenericKeySign) { EncryptAndLoadKeys(); unsigned int key_index = 2; vector expected_signature; @@ -4601,7 +4443,7 @@ TEST_F(GenericCryptoTest, GenericKeySign) { ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, OEMCrypto_Generic_Sign(session_.session_id(), clear_buffer_.data(), clear_buffer_.size(), OEMCrypto_HMAC_SHA256, - NULL, &gen_signature_length)); + nullptr, &gen_signature_length)); ASSERT_EQ(static_cast(SHA256_DIGEST_LENGTH), gen_signature_length); vector signature(SHA256_DIGEST_LENGTH); ASSERT_EQ(OEMCrypto_SUCCESS, @@ -4612,7 +4454,7 @@ TEST_F(GenericCryptoTest, GenericKeySign) { } // Test that the Generic_Sign function fails when not allowed. -TEST_F(GenericCryptoTest, GenericKeyBadSign) { +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadSign) { EncryptAndLoadKeys(); BadSign(0, OEMCrypto_HMAC_SHA256); // Can't sign with encrypt key. BadSign(1, OEMCrypto_HMAC_SHA256); // Can't sign with decrypt key. @@ -4620,7 +4462,7 @@ TEST_F(GenericCryptoTest, GenericKeyBadSign) { BadSign(2, OEMCrypto_AES_CBC_128_NO_PADDING); // Bad signing algorithm. } -TEST_F(GenericCryptoTest, GenericKeyVerify) { +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyVerify) { EncryptAndLoadKeys(); unsigned int key_index = 3; vector signature; @@ -4640,7 +4482,7 @@ TEST_F(GenericCryptoTest, GenericKeyVerify) { } // Test that the Generic_Verify function fails when not allowed. -TEST_F(GenericCryptoTest, GenericKeyBadVerify) { +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadVerify) { EncryptAndLoadKeys(); BadVerify(0, OEMCrypto_HMAC_SHA256, SHA256_DIGEST_LENGTH, false); BadVerify(1, OEMCrypto_HMAC_SHA256, SHA256_DIGEST_LENGTH, false); @@ -4652,7 +4494,7 @@ TEST_F(GenericCryptoTest, GenericKeyBadVerify) { } // Test Generic_Encrypt with the maximum buffer size. -TEST_F(GenericCryptoTest, GenericKeyEncryptLargeBuffer) { +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncryptLargeBuffer) { buffer_size_ = GetResourceValue(kMaxGenericBuffer); EncryptAndLoadKeys(); unsigned int key_index = 0; @@ -4674,7 +4516,7 @@ TEST_F(GenericCryptoTest, GenericKeyEncryptLargeBuffer) { } // Test Generic_Decrypt with the maximum buffer size. -TEST_F(GenericCryptoTest, GenericKeyDecryptLargeBuffer) { +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecryptLargeBuffer) { // Some applications are known to pass in a block that is almost 400k. buffer_size_ = GetResourceValue(kMaxGenericBuffer); EncryptAndLoadKeys(); @@ -4696,7 +4538,7 @@ TEST_F(GenericCryptoTest, GenericKeyDecryptLargeBuffer) { } // Test Generic_Sign with the maximum buffer size. -TEST_F(GenericCryptoTest, GenericKeySignLargeBuffer) { +TEST_P(OEMCryptoGenericCryptoTest, GenericKeySignLargeBuffer) { buffer_size_ = GetResourceValue(kMaxGenericBuffer); EncryptAndLoadKeys(); unsigned int key_index = 2; @@ -4713,7 +4555,7 @@ TEST_F(GenericCryptoTest, GenericKeySignLargeBuffer) { ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, OEMCrypto_Generic_Sign(session_.session_id(), clear_buffer_.data(), clear_buffer_.size(), OEMCrypto_HMAC_SHA256, - NULL, &gen_signature_length)); + nullptr, &gen_signature_length)); ASSERT_EQ(static_cast(SHA256_DIGEST_LENGTH), gen_signature_length); vector signature(SHA256_DIGEST_LENGTH); ASSERT_EQ(OEMCrypto_SUCCESS, @@ -4724,7 +4566,7 @@ TEST_F(GenericCryptoTest, GenericKeySignLargeBuffer) { } // Test Generic_Verify with the maximum buffer size. -TEST_F(GenericCryptoTest, GenericKeyVerifyLargeBuffer) { +TEST_P(OEMCryptoGenericCryptoTest, GenericKeyVerifyLargeBuffer) { buffer_size_ = GetResourceValue(kMaxGenericBuffer); EncryptAndLoadKeys(); unsigned int key_index = 3; @@ -4745,15 +4587,17 @@ TEST_F(GenericCryptoTest, GenericKeyVerifyLargeBuffer) { } // Test Generic_Encrypt when the key duration has expired. -TEST_F(GenericCryptoTest, KeyDurationEncrypt) { +TEST_P(OEMCryptoGenericCryptoTest, KeyDurationEncrypt) { + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; + license_messages_.CreateResponseWithGenericCryptoKeys(); EncryptAndLoadKeys(); vector expected_encrypted; EncryptBuffer(0, clear_buffer_, &expected_encrypted); unsigned int key_index = 0; vector encrypted(clear_buffer_.size()); - sleep(kShortSleep); // Should still be valid key. - + // Should be valid key at the start. ASSERT_EQ( OEMCrypto_SUCCESS, OEMCrypto_SelectKey(session_.session_id(), @@ -4767,22 +4611,24 @@ TEST_F(GenericCryptoTest, KeyDurationEncrypt) { encrypted.data())); ASSERT_EQ(expected_encrypted, encrypted); - sleep(kLongSleep); // Should be expired key. - + wvcdm::TestSleep::Sleep(kLongSleep + kShortSleep); // Should be expired key. encrypted.assign(clear_buffer_.size(), 0); OEMCryptoResult status = OEMCrypto_Generic_Encrypt( session_.session_id(), clear_buffer_.data(), clear_buffer_.size(), iv_, OEMCrypto_AES_CBC_128_NO_PADDING, encrypted.data()); - ASSERT_NO_FATAL_FAILURE( - session_.TestDecryptResult(OEMCrypto_ERROR_KEY_EXPIRED, status)); + ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, status); ASSERT_NE(encrypted, expected_encrypted); ASSERT_NO_FATAL_FAILURE(session_.TestSelectExpired(key_index)); } // Test Generic_Decrypt when the key duration has expired. -TEST_F(GenericCryptoTest, KeyDurationDecrypt) { +TEST_P(OEMCryptoGenericCryptoTest, KeyDurationDecrypt) { + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; + license_messages_.CreateResponseWithGenericCryptoKeys(); EncryptAndLoadKeys(); + // Should be valid key at the start. unsigned int key_index = 1; vector encrypted; EncryptBuffer(key_index, clear_buffer_, &encrypted); @@ -4792,9 +4638,6 @@ TEST_F(GenericCryptoTest, KeyDurationDecrypt) { session_.license().keys[key_index].key_id, session_.license().keys[key_index].key_id_length, OEMCrypto_CipherMode_CTR)); - - sleep(kShortSleep); // Should still be valid key. - vector resultant(encrypted.size()); ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Generic_Decrypt( @@ -4802,20 +4645,21 @@ TEST_F(GenericCryptoTest, KeyDurationDecrypt) { OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data())); ASSERT_EQ(clear_buffer_, resultant); - sleep(kLongSleep); // Should be expired key. - + wvcdm::TestSleep::Sleep(kLongSleep + kShortSleep); // Should be expired key. resultant.assign(encrypted.size(), 0); OEMCryptoResult status = OEMCrypto_Generic_Decrypt( session_.session_id(), encrypted.data(), encrypted.size(), iv_, OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data()); - ASSERT_NO_FATAL_FAILURE( - session_.TestDecryptResult(OEMCrypto_ERROR_KEY_EXPIRED, status)); + ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, status); ASSERT_NE(clear_buffer_, resultant); ASSERT_NO_FATAL_FAILURE(session_.TestSelectExpired(key_index)); } // Test Generic_Sign when the key duration has expired. -TEST_F(GenericCryptoTest, KeyDurationSign) { +TEST_P(OEMCryptoGenericCryptoTest, KeyDurationSign) { + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; + license_messages_.CreateResponseWithGenericCryptoKeys(); EncryptAndLoadKeys(); unsigned int key_index = 2; @@ -4824,98 +4668,99 @@ TEST_F(GenericCryptoTest, KeyDurationSign) { size_t signature_length = signature.size(); SignBuffer(key_index, clear_buffer_, &expected_signature); + // Should be valid key at the start. ASSERT_EQ( OEMCrypto_SUCCESS, OEMCrypto_SelectKey(session_.session_id(), session_.license().keys[key_index].key_id, session_.license().keys[key_index].key_id_length, OEMCrypto_CipherMode_CTR)); - - sleep(kShortSleep); // Should still be valid key. - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Generic_Sign(session_.session_id(), clear_buffer_.data(), clear_buffer_.size(), OEMCrypto_HMAC_SHA256, signature.data(), &signature_length)); ASSERT_EQ(expected_signature, signature); - sleep(kLongSleep); // Should be expired key. - + wvcdm::TestSleep::Sleep(kLongSleep + kShortSleep); // Should be expired key. signature.assign(SHA256_DIGEST_LENGTH, 0); OEMCryptoResult status = OEMCrypto_Generic_Sign( session_.session_id(), clear_buffer_.data(), clear_buffer_.size(), OEMCrypto_HMAC_SHA256, signature.data(), &signature_length); - ASSERT_NO_FATAL_FAILURE( - session_.TestDecryptResult(OEMCrypto_ERROR_KEY_EXPIRED, status)); + ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, status); ASSERT_NE(expected_signature, signature); ASSERT_NO_FATAL_FAILURE(session_.TestSelectExpired(key_index)); } // Test Generic_Verify when the key duration has expired. -TEST_F(GenericCryptoTest, KeyDurationVerify) { +TEST_P(OEMCryptoGenericCryptoTest, KeyDurationVerify) { + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; + license_messages_.CreateResponseWithGenericCryptoKeys(); EncryptAndLoadKeys(); unsigned int key_index = 3; vector signature; SignBuffer(key_index, clear_buffer_, &signature); + // Should be valid key at the start. ASSERT_EQ( OEMCrypto_SUCCESS, OEMCrypto_SelectKey(session_.session_id(), session_.license().keys[key_index].key_id, session_.license().keys[key_index].key_id_length, OEMCrypto_CipherMode_CTR)); - - sleep(kShortSleep); // Should still be valid key. - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Generic_Verify( session_.session_id(), clear_buffer_.data(), clear_buffer_.size(), OEMCrypto_HMAC_SHA256, signature.data(), signature.size())); - sleep(kLongSleep); // Should be expired key. - + wvcdm::TestSleep::Sleep(kLongSleep + kShortSleep); // Should be expired key. OEMCryptoResult status = OEMCrypto_Generic_Verify( session_.session_id(), clear_buffer_.data(), clear_buffer_.size(), OEMCrypto_HMAC_SHA256, signature.data(), signature.size()); - ASSERT_NO_FATAL_FAILURE( - session_.TestDecryptResult(OEMCrypto_ERROR_KEY_EXPIRED, status)); + ASSERT_EQ(OEMCrypto_ERROR_KEY_EXPIRED, status); ASSERT_NO_FATAL_FAILURE(session_.TestSelectExpired(key_index)); } const unsigned int kLongKeyId = 2; // Test that short key ids are allowed. -class GenericCryptoKeyIdLengthTest : public GenericCryptoTest { +class OEMCryptoGenericCryptoKeyIdLengthTest + : public OEMCryptoGenericCryptoTest { protected: void SetUp() override { - GenericCryptoTest::SetUp(); - const uint32_t kNoNonce = 0; - session_.set_num_keys(5); - ASSERT_NO_FATAL_FAILURE(session_.FillSimpleMessage( - kDuration, wvoec::kControlAllowDecrypt, kNoNonce)); + OEMCryptoGenericCryptoTest::SetUp(); + license_messages_.set_num_keys(5); + license_messages_.set_control(wvoec::kControlAllowDecrypt); + license_messages_.core_response() + .timer_limits.total_playback_duration_seconds = kDuration; + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); SetUniformKeyIdLength(16); // Start with all key ids being 16 bytes. // But, we are testing that the key ids do not have to have the same length. - session_.SetKeyId(0, "123456789012"); // 12 bytes (common key id length). - session_.SetKeyId(1, "12345"); // short key id. - session_.SetKeyId(2, "1234567890123456"); // 16 byte key id. (default) - session_.SetKeyId(3, "12345678901234"); // 14 byte. (uncommon) - session_.SetKeyId(4, "1"); // very short key id. + // 12 bytes (common key id length). + license_messages_.SetKeyId(0, "123456789012"); + license_messages_.SetKeyId(1, "12345"); // short key id. + // 16 byte key id. (default) + license_messages_.SetKeyId(2, "1234567890123456"); + license_messages_.SetKeyId(3, "12345678901234"); // 14 byte. (uncommon) + license_messages_.SetKeyId(4, "1"); // very short key id. ASSERT_EQ(2u, kLongKeyId); + ASSERT_NO_FATAL_FAILURE(license_messages_.FillCoreResponseSubstrings()); } // Make all four keys have the same length. void SetUniformKeyIdLength(size_t key_id_length) { - for (unsigned int i = 0; i < session_.num_keys(); i++) { + for (size_t i = 0; i < license_messages_.num_keys(); i++) { string key_id; key_id.resize(key_id_length, i + 'a'); - session_.SetKeyId(i, key_id); + license_messages_.SetKeyId(i, key_id); } + ASSERT_NO_FATAL_FAILURE(license_messages_.FillCoreResponseSubstrings()); } void TestWithKey(unsigned int key_index) { - ASSERT_LT(key_index, session_.num_keys()); + ASSERT_LT(key_index, license_messages_.num_keys()); EncryptAndLoadKeys(); vector encrypted; // To make sure OEMCrypto is not expecting the key_id to be zero padded, we @@ -4943,32 +4788,1337 @@ class GenericCryptoKeyIdLengthTest : public GenericCryptoTest { } }; -TEST_F(GenericCryptoKeyIdLengthTest, MediumKeyId) { TestWithKey(0); } +TEST_P(OEMCryptoGenericCryptoKeyIdLengthTest, MediumKeyId) { TestWithKey(0); } -TEST_F(GenericCryptoKeyIdLengthTest, ShortKeyId) { TestWithKey(1); } +TEST_P(OEMCryptoGenericCryptoKeyIdLengthTest, ShortKeyId) { TestWithKey(1); } -TEST_F(GenericCryptoKeyIdLengthTest, LongKeyId) { TestWithKey(2); } +TEST_P(OEMCryptoGenericCryptoKeyIdLengthTest, LongKeyId) { TestWithKey(2); } -TEST_F(GenericCryptoKeyIdLengthTest, FourteenByteKeyId) { TestWithKey(3); } +TEST_P(OEMCryptoGenericCryptoKeyIdLengthTest, FourteenByteKeyId) { + TestWithKey(3); +} -TEST_F(GenericCryptoKeyIdLengthTest, VeryShortKeyId) { TestWithKey(4); } +TEST_P(OEMCryptoGenericCryptoKeyIdLengthTest, VeryShortKeyId) { + TestWithKey(4); +} -TEST_F(GenericCryptoKeyIdLengthTest, UniformShortKeyId) { +TEST_P(OEMCryptoGenericCryptoKeyIdLengthTest, UniformShortKeyId) { SetUniformKeyIdLength(5); TestWithKey(2); } -TEST_F(GenericCryptoKeyIdLengthTest, UniformLongKeyId) { +TEST_P(OEMCryptoGenericCryptoKeyIdLengthTest, UniformLongKeyId) { SetUniformKeyIdLength(kTestKeyIdMaxLength); TestWithKey(2); } +INSTANTIATE_TEST_CASE_P(TestAll, OEMCryptoGenericCryptoTest, + Range(kCurrentAPI - 1, kCurrentAPI + 1)); + +INSTANTIATE_TEST_CASE_P(TestAll, OEMCryptoGenericCryptoKeyIdLengthTest, + Range(kCurrentAPI - 1, kCurrentAPI + 1)); + // Test usage table functionality. -class UsageTableTest : public GenericCryptoTest { +class LicenseWithUsageEntry { + public: + LicenseWithUsageEntry(const std::string& pst = "my_pst") + : session_(), + license_messages_(&session_), + generic_crypto_(false), + time_license_received_(0), + time_first_decrypt_(0), + time_last_decrypt_(0) { + license_messages_.set_pst(pst); + } + + void MakeAndLoadOnline(OEMCryptoSessionTests* test) { + MakeAndLoad(test, + wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired); + } + + // If status in not a nullptr, then creating a new entry is allowed to fail, + // and its error code is stored in status. + void MakeOfflineAndClose(OEMCryptoSessionTests* test, + OEMCryptoResult* status = nullptr) { + MakeAndLoad(test, wvoec::kControlNonceOrEntry, status); + if (status != nullptr && *status != OEMCrypto_SUCCESS) { + ASSERT_NO_FATAL_FAILURE(session_.close()); + return; + } + ASSERT_NO_FATAL_FAILURE( + session_.UpdateUsageEntry(&(test->encrypted_usage_header_))); + ASSERT_NO_FATAL_FAILURE(GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(session_.close()); + } + + // If status in not a nullptr, then creating a new entry is allowed to fail, + // and its error code is stored in status. + void MakeAndLoad(SessionUtil* util, uint32_t control, + OEMCryptoResult* status = nullptr) { + license_messages_.set_control(control); + ASSERT_NO_FATAL_FAILURE(session_.open()); + ASSERT_NO_FATAL_FAILURE(util->InstallTestRSAKey(&session_)); + ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + if (generic_crypto_) { + ASSERT_NO_FATAL_FAILURE( + license_messages_.CreateResponseWithGenericCryptoKeys()); + } else { + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + } + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry(status)); + if (status != nullptr && *status != OEMCrypto_SUCCESS) return; + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + time_license_received_ = wvcdm::Clock().GetCurrentTime(); + } + + void OpenAndReload(SessionUtil* util) { + ASSERT_NO_FATAL_FAILURE(session_.open()); + ASSERT_NO_FATAL_FAILURE(session_.ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(util->InstallTestRSAKey(&session_)); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + } + + // Test decrypt, and update the decrypt times for the pst report. + void TestDecryptCTR(bool select_key_first = true, + OEMCryptoResult expected_result = OEMCrypto_SUCCESS) { + session_.TestDecryptCTR(select_key_first, expected_result); + time_last_decrypt_ = wvcdm::Clock().GetCurrentTime(); + if (time_first_decrypt_ == 0) time_first_decrypt_ = time_last_decrypt_; + } + + void GenerateVerifyReport(OEMCrypto_Usage_Entry_Status status) { + ASSERT_NO_FATAL_FAILURE(session_.GenerateReport(pst())); + Test_PST_Report expected(pst(), status); + ASSERT_NO_FATAL_FAILURE( + session_.VerifyReport(expected, time_license_received_, + time_first_decrypt_, time_last_decrypt_)); + // The PST report was signed above. Below we verify that the entire message + // that is sent to the server will be signed by the right mac keys. + RenewalRoundTrip renewal_messages(&license_messages_); + renewal_messages.set_is_release(true); + ASSERT_NO_FATAL_FAILURE(renewal_messages.SignAndVerifyRequest()); + } + + void ReloadUsageEntry() { + session_.ReloadUsageEntry(); + session_.set_mac_keys(license_messages_.response_data().mac_keys); + } + + const std::string& pst() const { return license_messages_.pst(); } + void set_pst(const std::string& pst) { license_messages_.set_pst(pst); } + LicenseRoundTrip& license_messages() { return license_messages_; } + Session& session() { return session_; } + void set_generic_crypto(bool generic_crypto) { + generic_crypto_ = generic_crypto; + } + + private: + Session session_; + LicenseRoundTrip license_messages_; + bool generic_crypto_; + int64_t time_license_received_; + int64_t time_first_decrypt_; + int64_t time_last_decrypt_; +}; + +class OEMCryptoUsageTableTest : public OEMCryptoGenericCryptoTest { + public: + virtual void ShutDown() { + ASSERT_NO_FATAL_FAILURE(session_.close()); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate()); + } + + virtual void Restart() { + OEMCrypto_SetSandbox(kTestSandbox, sizeof(kTestSandbox)); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); + EnsureTestKeys(); + ASSERT_NO_FATAL_FAILURE(session_.open()); + } + + void PrintDotsWhileSleep(int64_t total_seconds, int64_t interval_seconds) { + int64_t dot_time = interval_seconds; + int64_t elapsed_time = 0; + const int64_t start_time = wvcdm::Clock().GetCurrentTime(); + do { + wvcdm::TestSleep::Sleep(1); + elapsed_time = wvcdm::Clock().GetCurrentTime() - start_time; + if (elapsed_time >= dot_time) { + cout << "."; + cout.flush(); + dot_time += interval_seconds; + } + } while (elapsed_time < total_seconds); + cout << endl; + } +}; + +// Test an online or streaming license with PST. This license requires a valid +// nonce and can only be loaded once. +TEST_P(OEMCryptoUsageTableTest, OnlineLicense) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + + // test repeated report generation + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + // Flag the entry as inactive. + ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(entry.pst())); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // It should report as inactive. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + // Decrypt should fail. + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + // We could call DeactivateUsageEntry multiple times. The state should not + // change. + ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(entry.pst())); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // It should report as inactive. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); +} + +// Test the usage report when the license is loaded but the keys are never used +// for decryption. +TEST_P(OEMCryptoUsageTableTest, OnlineLicenseUnused) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // No decrypt. We do not use this license. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + // Flag the entry as inactive. + ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(entry.pst())); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // It should report as inactive. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); + // Decrypt should fail. + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + // We could call DeactivateUsageEntry multiple times. The state should not + // change. + ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(entry.pst())); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // It should report as inactive. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); +} + +// Test that the usage table has been updated and saved before a report can be +// generated. +TEST_P(OEMCryptoUsageTableTest, ForbidReportWithNoUpdate) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + // Cannot generate a report without first updating the file. + ASSERT_NO_FATAL_FAILURE( + s.GenerateReport(entry.pst(), OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // Now it's OK. + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + // Flag the entry as inactive. + ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(entry.pst())); + // Cannot generate a report without first updating the file. + ASSERT_NO_FATAL_FAILURE( + s.GenerateReport(entry.pst(), OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE)); + // Decrypt should fail. + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + +// Test an online license with a license renewal. +TEST_P(OEMCryptoUsageTableTest, OnlineLicenseWithRefreshAPI16) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + + RenewalRoundTrip renewal_messages(&entry.license_messages()); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); +} + +// Verify that a streaming license cannot be reloaded. +TEST_P(OEMCryptoUsageTableTest, RepeatOnlineLicense) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.close()); + Session s2; + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s2)); + s2.LoadUsageEntry(s); // Use the same entry. + ASSERT_NE(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse(&s2)); +} + +// An offline license should not load on the first call if the nonce is bad. +TEST_P(OEMCryptoUsageTableTest, OnlineBadNonce) { + Session s; + LicenseRoundTrip license_messages(&s); + license_messages.set_api_version(license_api_version_); + license_messages.set_control(wvoec::kControlNonceEnabled | + wvoec::kControlNonceRequired); + license_messages.set_pst("my-pst"); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + for (uint32_t i = 0; i < license_messages.num_keys(); i++) + license_messages.response_data().keys[i].control.nonce ^= 42; + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages.LoadResponse()); +} + +// A license with non-zero replay control bits needs a valid pst. +TEST_P(OEMCryptoUsageTableTest, OnlineEmptyPST) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_NO_FATAL_FAILURE(s.GenerateNonce()); + LicenseRoundTrip license_messages(&s); + license_messages.set_api_version(license_api_version_); + license_messages.set_control(wvoec::kControlNonceEnabled | + wvoec::kControlNonceRequired); + // DO NOT SET PST: license_messages.set_pst(pst); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages.LoadResponse()); +} + +// A license with non-zero replay control bits needs a valid pst. +TEST_P(OEMCryptoUsageTableTest, OnlineMissingEntry) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_NO_FATAL_FAILURE(s.GenerateNonce()); + LicenseRoundTrip license_messages(&s); + license_messages.set_api_version(license_api_version_); + license_messages.set_control(wvoec::kControlNonceEnabled | + wvoec::kControlNonceRequired); + license_messages.set_pst("my-pst"); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + // ENTRY NOT CREATED: ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages.LoadResponse()); +} + +// Sessions should have at most one entry at a time. This tests different +// orderings of CreateNewUsageEntry and LoadUsageEntry calls. +TEST_P(OEMCryptoUsageTableTest, CreateAndLoadMultipleEntriesAPI16) { + // Entry Count: we start each test with an empty header. + uint32_t usage_entry_number; + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + Session& s = entry.session(); + // Make first entry 0. + ASSERT_NO_FATAL_FAILURE(entry.MakeOfflineAndClose(this)); + + // Load an entry, then try to create a second. + ASSERT_NO_FATAL_FAILURE(s.open()); + // Reload entry 0. + ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); + // Create new entry 1 should fail. + ASSERT_EQ(OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES, + OEMCrypto_CreateNewUsageEntry(entry.session().session_id(), + &usage_entry_number)); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Create an entry, then try to load a second. + Session s2; + ASSERT_NO_FATAL_FAILURE(s2.open()); + // Create entry 1. + ASSERT_NO_FATAL_FAILURE(s2.CreateNewUsageEntry()); + // Try to reload entry 0. + ASSERT_EQ(OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES, + OEMCrypto_LoadUsageEntry(s2.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + ASSERT_NO_FATAL_FAILURE(s2.close()); + + // Reload an entry and a license, then try to load the same entry again. + // This reloads entry 0. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); + ASSERT_EQ(OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Create an entry, then try to create a second entry. + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s2.CreateNewUsageEntry()); + ASSERT_EQ( + OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES, + OEMCrypto_CreateNewUsageEntry(s2.session_id(), &usage_entry_number)); +} + +// Test generic encrypt when the license uses a PST. +TEST_P(OEMCryptoUsageTableTest, GenericCryptoEncrypt) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.set_generic_crypto(true); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + OEMCryptoResult sts; + unsigned int key_index = 0; + vector expected_encrypted; + EncryptBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, + &expected_encrypted); + sts = OEMCrypto_SelectKey(s.session_id(), s.license().keys[key_index].key_id, + s.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + vector encrypted(clear_buffer_.size()); + sts = OEMCrypto_Generic_Encrypt( + s.session_id(), clear_buffer_.data(), clear_buffer_.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, encrypted.data()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + EXPECT_EQ(expected_encrypted, encrypted); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(entry.pst())); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + encrypted.assign(clear_buffer_.size(), 0); + sts = OEMCrypto_Generic_Encrypt( + s.session_id(), clear_buffer_.data(), clear_buffer_.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, encrypted.data()); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + EXPECT_NE(encrypted, expected_encrypted); +} + +// Test generic decrypt when the license uses a PST. +TEST_P(OEMCryptoUsageTableTest, GenericCryptoDecrypt) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.set_generic_crypto(true); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + OEMCryptoResult sts; + unsigned int key_index = 1; + vector encrypted; + EncryptBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, + &encrypted); + sts = OEMCrypto_SelectKey(s.session_id(), s.license().keys[key_index].key_id, + s.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + vector resultant(encrypted.size()); + sts = OEMCrypto_Generic_Decrypt( + s.session_id(), encrypted.data(), encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + EXPECT_EQ(clear_buffer_, resultant); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(entry.pst())); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + resultant.assign(encrypted.size(), 0); + sts = OEMCrypto_Generic_Decrypt( + s.session_id(), encrypted.data(), encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data()); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + EXPECT_NE(clear_buffer_, resultant); +} + +// Test generic sign when the license uses a PST. +TEST_P(OEMCryptoUsageTableTest, GenericCryptoSign) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.set_generic_crypto(true); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + OEMCryptoResult sts; + unsigned int key_index = 2; + vector expected_signature; + SignBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, + &expected_signature); + + sts = OEMCrypto_SelectKey(s.session_id(), s.license().keys[key_index].key_id, + s.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + size_t gen_signature_length = 0; + sts = OEMCrypto_Generic_Sign(s.session_id(), clear_buffer_.data(), + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + nullptr, &gen_signature_length); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + ASSERT_EQ(static_cast(SHA256_DIGEST_LENGTH), gen_signature_length); + vector signature(SHA256_DIGEST_LENGTH); + sts = OEMCrypto_Generic_Sign(s.session_id(), clear_buffer_.data(), + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + signature.data(), &gen_signature_length); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(expected_signature, signature); + + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(entry.pst())); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + signature.assign(SHA256_DIGEST_LENGTH, 0); + gen_signature_length = SHA256_DIGEST_LENGTH; + sts = OEMCrypto_Generic_Sign(s.session_id(), clear_buffer_.data(), + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + signature.data(), &gen_signature_length); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + ASSERT_NE(signature, expected_signature); +} + +// Test generic verify when the license uses a PST. +TEST_P(OEMCryptoUsageTableTest, GenericCryptoVerify) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.set_generic_crypto(true); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + OEMCryptoResult sts; + unsigned int key_index = 3; + vector signature; + SignBufferWithKey(s.license().keys[key_index].key_data, clear_buffer_, + &signature); + + sts = OEMCrypto_SelectKey(s.session_id(), s.license().keys[key_index].key_id, + s.license().keys[key_index].key_id_length, + OEMCrypto_CipherMode_CTR); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + sts = OEMCrypto_Generic_Verify(s.session_id(), clear_buffer_.data(), + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + signature.data(), signature.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(entry.pst())); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + sts = OEMCrypto_Generic_Verify(s.session_id(), clear_buffer_.data(), + clear_buffer_.size(), OEMCrypto_HMAC_SHA256, + signature.data(), signature.size()); + ASSERT_NE(OEMCrypto_SUCCESS, sts); +} + +// Test that an offline license can be loaded. +TEST_P(OEMCryptoUsageTableTest, OfflineLicense) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + ASSERT_NO_FATAL_FAILURE(entry.MakeOfflineAndClose(this)); +} + +// Test that an offline license can be loaded and that the license can be +// renewed. +TEST_P(OEMCryptoUsageTableTest, OfflineLicenseRefresh) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoad(this, wvoec::kControlNonceOrEntry); + Session& s = entry.session(); + + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + // License renewal message is signed by client and verified by the server. + RenewalRoundTrip renewal_messages(&entry.license_messages()); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); +} + +// Test that an offline license can be reloaded in a new session. +TEST_P(OEMCryptoUsageTableTest, ReloadOfflineLicense) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); +} + +// Test that an offline license can be reloaded in a new session, and then +// refreshed. +TEST_P(OEMCryptoUsageTableTest, ReloadOfflineLicenseWithRefresh) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + RenewalRoundTrip renewal_messages(&entry.license_messages()); + MakeRenewalRequest(&renewal_messages); + LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); +} + +// Verify that we can still reload an offline license after OEMCrypto_Terminate +// and Initialize are called. This is as close to a reboot as we can do in a +// unit test. +TEST_P(OEMCryptoUsageTableTest, ReloadOfflineLicenseWithTerminate) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + ShutDown(); // This calls OEMCrypto_Terminate. + Restart(); // This calls OEMCrypto_Initialize. + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageTableHeader(encrypted_usage_header_.data(), + encrypted_usage_header_.size())); + + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); +} + +// If we attempt to load a second license with the same usage entry as the +// first, but it has different mac keys, then the attempt should fail. This is +// how we verify that we are reloading the same license. +TEST_P(OEMCryptoUsageTableTest, BadReloadOfflineLicense) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + + // Offline license with new mac keys should fail. + Session s2; + LicenseRoundTrip license_messages2(&s2); + // Copy the response, and then change the mac keys. + license_messages2.response_data() = entry.license_messages().response_data(); + license_messages2.core_response() = entry.license_messages().core_response(); + license_messages2.response_data().mac_keys[7] ^= 42; + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s2)); + // This is a valid license: it is correctly signed. + license_messages2.EncryptAndSignResponse(); + // Load the usage entry should be OK. + ASSERT_NO_FATAL_FAILURE(s2.LoadUsageEntry(s)); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages2.LoadResponse()); + ASSERT_NO_FATAL_FAILURE(s2.close()); + + // Now we go back to the original license response. It should load OK. + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); +} + +// An offline license should not load on the first call if the nonce is bad. +TEST_P(OEMCryptoUsageTableTest, OfflineBadNonce) { + Session s; + LicenseRoundTrip license_messages(&s); + license_messages.set_api_version(license_api_version_); + license_messages.set_control(wvoec::kControlNonceOrEntry); + license_messages.set_pst("my-pst"); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.GenerateNonce()); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + for (size_t i = 0; i < license_messages.num_keys(); i++) + license_messages.response_data().keys[i].control.nonce ^= 42; + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_NONCE, license_messages.LoadResponse()); +} + +// An offline license needs a valid pst. +TEST_P(OEMCryptoUsageTableTest, OfflineEmptyPST) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_NO_FATAL_FAILURE(s.GenerateNonce()); + LicenseRoundTrip license_messages(&s); + license_messages.set_api_version(license_api_version_); + license_messages.set_control(wvoec::kControlNonceOrEntry); + // DO NOT SET PST: license_messages.set_pst(pst); + ASSERT_NO_FATAL_FAILURE(license_messages.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages.EncryptAndSignResponse()); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages.LoadResponse()); +} + +// If we try to reload a license with a different PST, the attempt should fail. +TEST_P(OEMCryptoUsageTableTest, ReloadOfflineWrongPST) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + + Session s2; + LicenseRoundTrip license_messages2(&s2); + license_messages2.response_data() = entry.license_messages().response_data(); + license_messages2.core_response() = entry.license_messages().core_response(); + // Change the middle of the pst. + license_messages2.response_data().pst[3] ^= 'Z'; + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s2)); + // This is a valid license: it is correctly signed. + license_messages2.EncryptAndSignResponse(); + // Load the usage entry should be OK. + ASSERT_NO_FATAL_FAILURE(s2.LoadUsageEntry(s)); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages2.LoadResponse()); +} + +// Once a license has been deactivated, the keys can no longer be used for +// decryption. However, we can still generate a usage report. +TEST_P(OEMCryptoUsageTableTest, DeactivateOfflineLicense) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + // Reload the offline license. + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR()); // Should be able to decrypt. + ASSERT_NO_FATAL_FAILURE( + s.DeactivateUsageEntry(entry.pst())); // Then deactivate. + // After deactivate, should not be able to decrypt. + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Offline license can not be reused if it has been deactivated. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_NE(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse(&s)); + s.close(); + + // But we can still generate a report. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(entry.ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // Sending a release from an offline license that has been deactivate will + // only work if the license server can handle v16 licenses. This is a rare + // condition, so it is OK to break it during the transition months. + entry.license_messages().set_api_version(global_features.api_version); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + // We could call DeactivateUsageEntry multiple times. The state should not + // change. + ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(entry.pst())); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); +} + +// The usage report should indicate that the keys were never used for +// decryption. +TEST_P(OEMCryptoUsageTableTest, DeactivateOfflineLicenseUnused) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + // No Decrypt. This license is unused. + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE( + s.DeactivateUsageEntry(entry.pst())); // Then deactivate. + // After deactivate, should not be able to decrypt. + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Offline license can not be reused if it has been deactivated. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_NE(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse(&s)); + s.close(); + + // But we can still generate a report. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(entry.ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + // Sending a release from an offline license that has been deactivate will + // only work if the license server can handle v16 licenses. This is a rare + // condition, so it is OK to break it during the transition months. + entry.license_messages().set_api_version(global_features.api_version); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); + // We could call DeactivateUsageEntry multiple times. The state should not + // change. + ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(entry.pst())); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUnused)); +} + +// Test update usage table fails when passed a null pointer. +TEST_P(OEMCryptoUsageTableTest, UpdateFailsWithNullPtr) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + size_t header_buffer_length = encrypted_usage_header_.size(); + size_t entry_buffer_length = s.encrypted_usage_entry().size(); + vector buffer(entry_buffer_length); + // Now try to pass in null pointers for the buffers. This should fail. + ASSERT_NE( + OEMCrypto_SUCCESS, + OEMCrypto_UpdateUsageEntry(s.session_id(), nullptr, &header_buffer_length, + buffer.data(), &entry_buffer_length)); + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_UpdateUsageEntry( + s.session_id(), encrypted_usage_header_.data(), + &header_buffer_length, nullptr, &entry_buffer_length)); +} + +// Class used to test usage table defragmentation. +class OEMCryptoUsageTableDefragTest : public OEMCryptoUsageTableTest { + protected: + void ReloadLicense(LicenseWithUsageEntry* entry) { + Session& s = entry->session(); + ASSERT_NO_FATAL_FAILURE(entry->OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry->TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry->GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(s.close()); + } + + void FailReloadLicense(LicenseWithUsageEntry* entry, + OEMCryptoResult expected_result) { + Session& s = entry->session(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_EQ(expected_result, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + + ASSERT_NE(OEMCrypto_SUCCESS, entry->license_messages().LoadResponse()); + ASSERT_NO_FATAL_FAILURE(s.close()); + } + + void ShrinkHeader(uint32_t new_size, + OEMCryptoResult expected_result = OEMCrypto_SUCCESS) { + size_t header_buffer_length = 0; + OEMCryptoResult sts = OEMCrypto_ShrinkUsageTableHeader( + new_size, nullptr, &header_buffer_length); + if (expected_result == OEMCrypto_SUCCESS) { + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + } else { + ASSERT_NE(OEMCrypto_SUCCESS, sts); + if (sts != OEMCrypto_ERROR_SHORT_BUFFER) return; + } + ASSERT_LT(0u, header_buffer_length); + encrypted_usage_header_.resize(header_buffer_length); + sts = OEMCrypto_ShrinkUsageTableHeader( + new_size, encrypted_usage_header_.data(), + &header_buffer_length); + ASSERT_EQ(expected_result, sts); + } +}; + +// Verify that usage table entries can be moved around in the table. +TEST_P(OEMCryptoUsageTableDefragTest, MoveUsageEntries) { + const size_t ENTRY_COUNT = 10; + vector entries(ENTRY_COUNT); + for (size_t i = 0; i < ENTRY_COUNT; i++) { + entries[i].set_pst("pst " + std::to_string(i)); + ASSERT_NO_FATAL_FAILURE(entries[i].MakeOfflineAndClose(this)) + << "On license " << i << " pst=" << entries[i].pst(); + wvcdm::TestSleep::SyncFakeClock(); + } + for (size_t i = 0; i < ENTRY_COUNT; i++) { + ASSERT_NO_FATAL_FAILURE(entries[i].OpenAndReload(this)) + << "On license " << i << " pst=" << entries[i].pst(); + ASSERT_NO_FATAL_FAILURE(entries[i].session().close()) + << "On license " << i << " pst=" << entries[i].pst(); + } + // Move 4 to 1. + ASSERT_NO_FATAL_FAILURE( + entries[4].session().MoveUsageEntry(1, &encrypted_usage_header_)); + // Shrink header to 3 entries 0, 1 was 4, 2. + ASSERT_NO_FATAL_FAILURE(ShrinkHeader(3)); + ShutDown(); + Restart(); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageTableHeader(encrypted_usage_header_.data(), + encrypted_usage_header_.size())); + wvcdm::TestSleep::SyncFakeClock(); + ASSERT_NO_FATAL_FAILURE(ReloadLicense(&entries[0])); + // Now has index 1. + ASSERT_NO_FATAL_FAILURE(ReloadLicense(&entries[4])); + ASSERT_NO_FATAL_FAILURE(ReloadLicense(&entries[2])); + // When 4 was moved to 1, it increased the gen. number in the header. + ASSERT_NO_FATAL_FAILURE( + FailReloadLicense(&entries[1], OEMCrypto_ERROR_GENERATION_SKEW)); + // Index 3 is beyond the end of the table. + ASSERT_NO_FATAL_FAILURE( + FailReloadLicense(&entries[3], OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + +// A usage table entry cannot be moved into an entry where an open session is +// currently using the entry. +TEST_P(OEMCryptoUsageTableDefragTest, MoveUsageEntriesToOpenSession) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + entry0.MakeOfflineAndClose(this); + LicenseWithUsageEntry entry1; + entry1.set_pst("pst 1"); + entry1.MakeOfflineAndClose(this); + + entry0.session().open(); + ASSERT_NO_FATAL_FAILURE(entry0.ReloadUsageEntry()); + // s0 currently open on index 0. Expect this to fail: + ASSERT_NO_FATAL_FAILURE(entry1.session().MoveUsageEntry( + 0, &encrypted_usage_header_, OEMCrypto_ERROR_ENTRY_IN_USE)); +} + +// The usage table cannot be shrunk if any session is using an entry that would +// be deleted. +TEST_P(OEMCryptoUsageTableDefragTest, ShrinkOverOpenSessions) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + entry0.MakeOfflineAndClose(this); + LicenseWithUsageEntry entry1; + entry1.set_pst("pst 1"); + entry1.MakeOfflineAndClose(this); + + entry0.session().open(); + ASSERT_NO_FATAL_FAILURE(entry0.ReloadUsageEntry()); + entry1.session().open(); + ASSERT_NO_FATAL_FAILURE(entry1.ReloadUsageEntry()); + // Since s0 and s1 are open, we can't shrink. + ASSERT_NO_FATAL_FAILURE(ShrinkHeader(1, OEMCrypto_ERROR_ENTRY_IN_USE)); + entry1.session().close(); // Can shrink after closing s1, even if s0 is open. + ASSERT_NO_FATAL_FAILURE(ShrinkHeader(1, OEMCrypto_SUCCESS)); +} + +// Verify the usage table size can be increased. +TEST_P(OEMCryptoUsageTableDefragTest, EnlargeHeader) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + entry0.MakeOfflineAndClose(this); + LicenseWithUsageEntry entry1; + entry1.set_pst("pst 1"); + entry1.MakeOfflineAndClose(this); + + // Can only shrink the header -- not make it bigger. + ASSERT_NO_FATAL_FAILURE(ShrinkHeader(4, OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + +// A new header can only be created while no entries are in use. +TEST_P(OEMCryptoUsageTableDefragTest, CreateNewHeaderWhileUsingOldOne) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + entry0.MakeOfflineAndClose(this); + LicenseWithUsageEntry entry1; + entry1.set_pst("pst 1"); + entry1.MakeOfflineAndClose(this); + + entry0.session().open(); + ASSERT_NO_FATAL_FAILURE(entry0.ReloadUsageEntry()); + const bool kExpectFailure = false; + ASSERT_NO_FATAL_FAILURE(CreateUsageTableHeader(kExpectFailure)); +} + +// Verify that a usage table entry can only be loaded into the correct index of +// the table. +TEST_P(OEMCryptoUsageTableDefragTest, ReloadUsageEntryWrongIndex) { + LicenseWithUsageEntry entry0; + entry0.set_pst("pst 0"); + entry0.MakeOfflineAndClose(this); + LicenseWithUsageEntry entry1; + entry1.set_pst("pst 1"); + entry1.MakeOfflineAndClose(this); + + entry0.session().set_usage_entry_number(1); + ASSERT_NO_FATAL_FAILURE( + FailReloadLicense(&entry0, OEMCrypto_ERROR_INVALID_SESSION)); +} + +// Verify that a usage table entry cannot be loaded if it has been altered. +TEST_P(OEMCryptoUsageTableDefragTest, ReloadUsageEntryBadData) { + LicenseWithUsageEntry entry; + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + vector data = s.encrypted_usage_entry(); + ASSERT_LT(0UL, data.size()); + data[0] ^= 42; + // Error could be signature or verification error. + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + data.data(), data.size())); +} + +// This verifies we can actually create the required number of usage table +// entries. +TEST_P(OEMCryptoUsageTableDefragTest, ManyUsageEntries) { + // OEMCrypto is required to store at least 300 entries in the usage table + // header, but it is allowed to store more. This test verifies that if we keep + // adding entries, the error indicates a resource limit. It then verifies + // that all of the successful entries are still valid after we throw out the + // last invalid entry. + + // After API 16, we require 300 entries in the usage table. Before API 16, we + // required 200. + const size_t required_capacity = RequiredUsageSize(); + + // We try to make a much large header, and assume there is an error at some + // point. + const size_t attempt_count = required_capacity * 5; + // Count of how many entries we successfully create. + size_t successful_count = 0; + + // These entries have licenses tied to them. + std::vector> entries; + // Store the status of the last attempt to create an entry. + OEMCryptoResult status = OEMCrypto_SUCCESS; + while (successful_count < attempt_count && status == OEMCrypto_SUCCESS) { + wvcdm::TestSleep::SyncFakeClock(); + LOGD("Creating license for entry %zd", successful_count); + entries.push_back( + std::unique_ptr(new LicenseWithUsageEntry())); + entries.back()->set_pst("pst " + std::to_string(successful_count)); + ASSERT_NO_FATAL_FAILURE(entries.back()->MakeOfflineAndClose(this, &status)) + << "Failed creating license for entry " << successful_count; + if (status != OEMCrypto_SUCCESS) { + // Remove the failed session. + entries.resize(entries.size() - 1); + break; + } + EXPECT_EQ(entries.back()->session().usage_entry_number(), successful_count); + successful_count++; + // We don't create a license for each entry. For every license, we'll + // create 10 empty entries. + constexpr size_t filler_count = 10; + for (size_t i = 0; i < filler_count; i++) { + Session s; + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry(&status)) + << "Failed creating entry " << successful_count; + if (status != OEMCrypto_SUCCESS) break; + EXPECT_EQ(s.usage_entry_number(), successful_count); + successful_count++; + } + } + LOGD("successful_count = %d", successful_count); + if (status != OEMCrypto_SUCCESS) { + // If we failed to create this many entries because of limited resources, + // then the error returned should be insufficient resources. + EXPECT_EQ(OEMCrypto_ERROR_INSUFFICIENT_RESOURCES, status) + << "Failed to create license " << successful_count + << ", with wrong error code."; + } + EXPECT_GE(successful_count, required_capacity); + wvcdm::TestSleep::SyncFakeClock(); + // Shrink the table a little. + constexpr size_t small_number = 5; + size_t smaller_size = successful_count - small_number; + ASSERT_NO_FATAL_FAILURE(ShrinkHeader(smaller_size)); + // Throw out the last license if it was in the part of the table that was + // shrunk. + if (entries.back()->session().usage_entry_number() >= smaller_size) { + entries.pop_back(); + } + // Create a few more license + for (size_t i = 0; i < small_number; i++) { + wvcdm::TestSleep::SyncFakeClock(); + entries.push_back( + std::unique_ptr(new LicenseWithUsageEntry())); + entries.back()->set_pst("new pst " + std::to_string(smaller_size + i)); + entries.back()->MakeOfflineAndClose(this); + } + // Make sure that all of the licenses can be reloaded. + for (size_t i = 0; i < entries.size(); i++) { + wvcdm::TestSleep::SyncFakeClock(); + Session& s = entries[i]->session(); + ASSERT_NO_FATAL_FAILURE(entries[i]->OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entries[i]->GenerateVerifyReport(kUnused)); + ASSERT_NO_FATAL_FAILURE(entries[i]->TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entries[i]->GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(s.close()); + } +} + +// This verifies that the usage table header can be loaded if the generation +// number is off by one, but not off by two. +TEST_P(OEMCryptoUsageTableTest, ReloadUsageTableWithSkew) { + // This also tests a few other error conditions with usage table headers. + LicenseWithUsageEntry entry; + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); + + // Reload the license, and save the header. + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + vector old_usage_header_2_ = encrypted_usage_header_; + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + vector old_usage_header_1_ = encrypted_usage_header_; + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.close()); + + ShutDown(); + Restart(); + // Null pointer generates error. + ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_LoadUsageTableHeader( + nullptr, old_usage_header_2_.size())); + ASSERT_NO_FATAL_FAILURE(s.open()); + // Cannot load an entry if header didn't load. + ASSERT_EQ( + OEMCrypto_ERROR_UNKNOWN_FAILURE, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Modified header generates error. + vector bad_header = encrypted_usage_header_; + bad_header[3] ^= 42; + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageTableHeader(bad_header.data(), + bad_header.size())); + ASSERT_NO_FATAL_FAILURE(s.open()); + // Cannot load an entry if header didn't load. + ASSERT_EQ( + OEMCrypto_ERROR_UNKNOWN_FAILURE, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Old by 2 generation numbers is error. + ASSERT_EQ(OEMCrypto_ERROR_GENERATION_SKEW, + OEMCrypto_LoadUsageTableHeader(old_usage_header_2_.data(), + old_usage_header_2_.size())); + ASSERT_NO_FATAL_FAILURE(s.open()); + // Cannot load an entry if header didn't load. + ASSERT_NE( + OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + ASSERT_NO_FATAL_FAILURE(s.close()); + + // Old by 1 generation numbers is just warning. + ASSERT_EQ(OEMCrypto_WARNING_GENERATION_SKEW, + OEMCrypto_LoadUsageTableHeader(old_usage_header_1_.data(), + old_usage_header_1_.size())); + // Everything else should still work. Skew by 1 is just a warning. + ASSERT_NO_FATAL_FAILURE(s.open()); + ASSERT_EQ( + OEMCrypto_WARNING_GENERATION_SKEW, + OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), + s.encrypted_usage_entry().data(), + s.encrypted_usage_entry().size())); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_EQ(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse()); +} + +// A usage report with the wrong pst should fail. +TEST_P(OEMCryptoUsageTableTest, GenerateReportWrongPST) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.GenerateReport("wrong_pst", + OEMCrypto_ERROR_WRONG_PST)); +} + +// Test usage table timing. +TEST_P(OEMCryptoUsageTableTest, TimingTest) { + LicenseWithUsageEntry entry1; + entry1.license_messages().set_api_version(license_api_version_); + Session& s1 = entry1.session(); + entry1.set_pst("my_pst_1"); + ASSERT_NO_FATAL_FAILURE(entry1.MakeOfflineAndClose(this)); + + LicenseWithUsageEntry entry2; + entry2.license_messages().set_api_version(license_api_version_); + Session& s2 = entry2.session(); + entry2.set_pst("my_pst_2"); + ASSERT_NO_FATAL_FAILURE(entry2.MakeOfflineAndClose(this)); + + LicenseWithUsageEntry entry3; + entry3.license_messages().set_api_version(license_api_version_); + Session& s3 = entry3.session(); + entry3.set_pst("my_pst_3"); + ASSERT_NO_FATAL_FAILURE(entry3.MakeOfflineAndClose(this)); + + ASSERT_NO_FATAL_FAILURE(entry1.MakeOfflineAndClose(this)); + ASSERT_NO_FATAL_FAILURE(entry2.MakeOfflineAndClose(this)); + ASSERT_NO_FATAL_FAILURE(entry3.MakeOfflineAndClose(this)); + + wvcdm::TestSleep::Sleep(kLongSleep); + ASSERT_NO_FATAL_FAILURE(entry1.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); + + ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(entry2.TestDecryptCTR()); + + wvcdm::TestSleep::Sleep(kLongSleep); + ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(entry2.TestDecryptCTR()); + + wvcdm::TestSleep::Sleep(kLongSleep); + ASSERT_NO_FATAL_FAILURE(s1.DeactivateUsageEntry(entry1.pst())); + ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s1.close()); + ASSERT_NO_FATAL_FAILURE(s2.close()); + + wvcdm::TestSleep::Sleep(kLongSleep); + // This is as close to reboot as we can simulate in code. + ShutDown(); + wvcdm::TestSleep::Sleep(kShortSleep); + Restart(); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadUsageTableHeader(encrypted_usage_header_.data(), + encrypted_usage_header_.size())); + + // After a reboot, we should be able to reload keys, and generate reports. + wvcdm::TestSleep::Sleep(kLongSleep); + ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(entry2.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s2.close()); + + ASSERT_NO_FATAL_FAILURE(s1.open()); + ASSERT_NO_FATAL_FAILURE(s2.open()); + ASSERT_NO_FATAL_FAILURE(s3.open()); + ASSERT_NO_FATAL_FAILURE(entry1.ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(entry2.ReloadUsageEntry()); + ASSERT_NO_FATAL_FAILURE(entry3.ReloadUsageEntry()); + // Sending a release from an offline license that has been deactivate will + // only work if the license server can handle v16 licenses. This is a rare + // condition, so it is OK to break it during the transition months. + entry1.license_messages().set_api_version(global_features.api_version); + entry2.license_messages().set_api_version(global_features.api_version); + entry3.license_messages().set_api_version(global_features.api_version); + wvcdm::TestSleep::Sleep(kLongSleep); + ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry1.GenerateVerifyReport(kInactiveUsed)); + ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry2.GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(s3.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry3.GenerateVerifyReport(kUnused)); +} + +// Verify the times in the usage report. For performance reasons, we allow the +// times in the usage report to be off by as much as kUsageTimeTolerance, which +// is 10 seconds. This acceptable error is called slop. This test needs to run +// long enough that the reported values are distinct, even after accounting for +// this slop. +TEST_P(OEMCryptoUsageTableTest, VerifyUsageTimes) { + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoadOnline(this); + Session& s = entry.session(); + + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + + const int64_t kDotIntervalInSeconds = 5; + const int64_t kIdleInSeconds = 20; + const int64_t kPlaybackLoopInSeconds = 2 * 60; + + cout << "This test verifies the elapsed time reported in the usage table " + "for a 2 minute simulated playback." + << endl; + cout << "The total time for this test is about " + << kPlaybackLoopInSeconds + 2 * kIdleInSeconds << " seconds." << endl; + cout << "Wait " << kIdleInSeconds + << " seconds to verify usage table time before playback." << endl; + + PrintDotsWhileSleep(kIdleInSeconds, kDotIntervalInSeconds); + + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kUnused)); + cout << "Start simulated playback..." << endl; + + int64_t dot_time = kDotIntervalInSeconds; + int64_t playback_time = 0; + const int64_t start_time = wvcdm::Clock().GetCurrentTime(); + do { + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + wvcdm::TestSleep::Sleep(kShortSleep); + playback_time = wvcdm::Clock().GetCurrentTime() - start_time; + ASSERT_LE(0, playback_time); + if (playback_time >= dot_time) { + cout << "."; + cout.flush(); + dot_time += kDotIntervalInSeconds; + } + } while (playback_time < kPlaybackLoopInSeconds); + cout << "\nSimulated playback time = " << playback_time << " seconds.\n"; + + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + EXPECT_NEAR(s.pst_report().seconds_since_first_decrypt() - + s.pst_report().seconds_since_last_decrypt(), + playback_time, kUsageTableTimeTolerance); + + cout << "Wait another " << kIdleInSeconds + << " seconds " + "to verify usage table time since playback ended." + << endl; + PrintDotsWhileSleep(kIdleInSeconds, kDotIntervalInSeconds); + + // At this point, this is what we expect: + // idle playback loop idle + // |-----|-------------------------|-----| + // |<--->| = seconds_since_last_decrypt + // |<----------------------------->| = seconds_since_first_decrypt + // |<------------------------------------| = seconds_since_license_received + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); + ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(entry.pst())); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + +// This test class is only used to roll back the wall clock. It is used to +// verify that OEMCrypto's system clock is monotonic. It is should only be run +// on a device that allows an application to set the clock. +class OEMCryptoUsageTableTestWallClock : public OEMCryptoUsageTableTest { public: void SetUp() override { - GenericCryptoTest::SetUp(); - new_mac_keys_ = true; + OEMCryptoUsageTableTest::SetUp(); did_change_system_time_ = false; test_start_steady_ = steady_clock_.now(); #ifdef _WIN32 @@ -4984,48 +6134,7 @@ class UsageTableTest : public GenericCryptoTest { const int64_t delta_sec = delta / std::chrono::seconds(1); ASSERT_NO_FATAL_FAILURE(SetWallTimeDelta(delta_sec)); } - GenericCryptoTest::TearDown(); - } - - virtual void ShutDown() { - ASSERT_NO_FATAL_FAILURE(session_.close()); - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate()); - } - - virtual void Restart() { - OEMCrypto_SetSandbox(kTestSandbox, sizeof(kTestSandbox)); - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); - EnsureTestKeys(); - ASSERT_NO_FATAL_FAILURE(session_.open()); - } - - void LoadOfflineLicense(Session& s, const std::string& pst) { - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec::kControlNonceOrEntry, s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kUnused)); - ASSERT_NO_FATAL_FAILURE(s.close()); - } - - void PrintDotsWhileSleep(time_t total_seconds, time_t interval_seconds) { - time_t dot_time = interval_seconds; - time_t elapsed_time = 0; - time_t start_time = time(NULL); - do { - sleep(1); - elapsed_time = time(NULL) - start_time; - if (elapsed_time >= dot_time) { - cout << "."; - cout.flush(); - dot_time += interval_seconds; - } - } while (elapsed_time < total_seconds); - cout << endl; + OEMCryptoUsageTableTest::TearDown(); } protected: @@ -5044,1396 +6153,45 @@ class UsageTableTest : public GenericCryptoTest { #endif } - bool new_mac_keys_; - bool did_change_system_time_; std::chrono::steady_clock steady_clock_; - std::chrono::time_point test_start_steady_; + bool did_change_system_time_; NativeTime test_start_wall_; + std::chrono::time_point test_start_steady_; }; -// Some usage tables we want to check a license either with or without a -// new pair of mac keys in the license response. This affects signatures after -// the license is loaded. -// This test is parameterized by a boolean which determines if the license -// installs new mac keys in LoadKeys. -class UsageTableTestWithMAC : public UsageTableTest, - public WithParamInterface { - public: - void SetUp() override { - UsageTableTest::SetUp(); - new_mac_keys_ = GetParam(); - } -}; - -// Test an online or streaming license with PST. This license requires a valid -// nonce and can only be loaded once. -TEST_P(UsageTableTestWithMAC, OnlineLicense) { - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired, - s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - // test repeated report generation - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kUnused)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kUnused)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kUnused)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kUnused)); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kActive)); - // Flag the entry as inactive. - ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(pst)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - // It should report as inactive. - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kInactiveUsed)); - // Decrypt should fail. - ASSERT_NO_FATAL_FAILURE( - s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); - // We could call DeactivateUsageEntry multiple times. The state should not - // change. - ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(pst)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - // It should report as inactive. - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kInactiveUsed)); -} - -// Test the usage report when the license is loaded but the keys are never used -// for decryption. -TEST_P(UsageTableTestWithMAC, OnlineLicenseUnused) { - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired, - s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - // No decrypt. We do not use this license. - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kUnused)); - // Flag the entry as inactive. - ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(pst)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - // It should report as inactive. - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kInactiveUnused)); - // Decrypt should fail. - ASSERT_NO_FATAL_FAILURE( - s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); - // We could call DeactivateUsageEntry multiple times. The state should not - // change. - ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(pst)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - // It should report as inactive. - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kInactiveUnused)); -} - -// Test that the usage table has been updated and saved before a report can be -// generated. -TEST_P(UsageTableTestWithMAC, ForbidReportWithNoUpdate) { - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired, - s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kUnused)); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); - // Cannot generate a report without first updating the file. - ASSERT_NO_FATAL_FAILURE( - s.GenerateReport(pst, OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - // Now it's OK. - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kActive)); - // Flag the entry as inactive. - ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(pst)); - // Cannot generate a report without first updating the file. - ASSERT_NO_FATAL_FAILURE( - s.GenerateReport(pst, OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE)); - // Decrypt should fail. - ASSERT_NO_FATAL_FAILURE( - s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); -} - -// Test an online license with a license renewal. -TEST_P(UsageTableTestWithMAC, OnlineLicenseWithRefresh) { - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired, - s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - time_t loaded = time(NULL); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); - s.GenerateNonce(); - // License renewal message is signed by client and verified by the server. - ASSERT_NO_FATAL_FAILURE(s.VerifyClientSignature()); - size_t kAllKeys = 1; - ASSERT_NO_FATAL_FAILURE(s.RefreshTestKeys( - kAllKeys, - wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired, - s.get_nonce(), OEMCrypto_SUCCESS)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE( - s.GenerateVerifyReport(pst, kActive, - loaded, // when license loaded. (not refreshed) - loaded, // first decrypt. - 0)); // last decrypt is now. -} - -// Verify that a streaming license cannot be reloaded. -TEST_F(UsageTableTest, RepeatOnlineLicense) { - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired, - s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.close()); - Session s2; - ASSERT_NO_FATAL_FAILURE(s2.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s2)); - s2.LoadUsageEntry(s); // Use the same entry. - // Trying to reuse a PST is bad. We use session ID for s2, everything else - // reused from s. - ASSERT_NE( - OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys(s2.session_id(), s.message_ptr(), s.message_size(), - s.signature().data(), s.signature().size(), - s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), s.pst_substr(), - GetSubstring(), OEMCrypto_ContentLicense)); - ASSERT_NO_FATAL_FAILURE(s2.close()); -} - -// A license with non-zero replay control bits needs a valid pst. -TEST_F(UsageTableTest, OnlineEmptyPST) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired, - s.get_nonce())); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), s.message_ptr(), s.message_size(), s.signature().data(), - s.signature().size(), s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), GetSubstring(), GetSubstring(), - OEMCrypto_ContentLicense); - ASSERT_NE(OEMCrypto_SUCCESS, sts); - ASSERT_NO_FATAL_FAILURE(s.close()); -} - -// A license with non-zero replay control bits needs a valid pst. -TEST_F(UsageTableTest, OnlineMissingEntry) { - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired, - s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - // ENTRY NOT CREATED: ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), s.message_ptr(), s.message_size(), s.signature().data(), - s.signature().size(), s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), s.pst_substr(), GetSubstring(), - OEMCrypto_ContentLicense); - ASSERT_NE(OEMCrypto_SUCCESS, sts); - ASSERT_NO_FATAL_FAILURE(s.close()); -} - -// Test generic encrypt when the license uses a PST. -TEST_P(UsageTableTestWithMAC, GenericCryptoEncrypt) { - std::string pst = "A PST"; - uint32_t nonce = session_.get_nonce(); - MakeFourKeys(0, wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired, - nonce, pst); - ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE(session_.LoadTestKeys(pst, new_mac_keys_)); - OEMCryptoResult sts; - unsigned int key_index = 0; - vector expected_encrypted; - EncryptBuffer(key_index, clear_buffer_, &expected_encrypted); - sts = OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length, - OEMCrypto_CipherMode_CTR); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - vector encrypted(clear_buffer_.size()); - sts = OEMCrypto_Generic_Encrypt( - session_.session_id(), clear_buffer_.data(), clear_buffer_.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, encrypted.data()); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - EXPECT_EQ(expected_encrypted, encrypted); - ASSERT_NO_FATAL_FAILURE(session_.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(session_.GenerateVerifyReport(pst, kActive)); - ASSERT_NO_FATAL_FAILURE(session_.DeactivateUsageEntry(pst)); - ASSERT_NO_FATAL_FAILURE(session_.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(session_.GenerateVerifyReport(pst, kInactiveUsed)); - encrypted.assign(clear_buffer_.size(), 0); - sts = OEMCrypto_Generic_Encrypt( - session_.session_id(), clear_buffer_.data(), clear_buffer_.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, encrypted.data()); - ASSERT_NE(OEMCrypto_SUCCESS, sts); - EXPECT_NE(encrypted, expected_encrypted); -} - -// Test generic decrypt when the license uses a PST. -TEST_P(UsageTableTestWithMAC, GenericCryptoDecrypt) { - std::string pst = "my_pst"; - uint32_t nonce = session_.get_nonce(); - MakeFourKeys( - 0, wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired, - nonce, pst); - ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE(session_.LoadTestKeys(pst, new_mac_keys_)); - OEMCryptoResult sts; - unsigned int key_index = 1; - vector encrypted; - EncryptBuffer(key_index, clear_buffer_, &encrypted); - sts = OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length, - OEMCrypto_CipherMode_CTR); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - vector resultant(encrypted.size()); - sts = OEMCrypto_Generic_Decrypt( - session_.session_id(), encrypted.data(), encrypted.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING,resultant.data()); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - EXPECT_EQ(clear_buffer_, resultant); - ASSERT_NO_FATAL_FAILURE(session_.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(session_.GenerateVerifyReport(pst, kActive)); - ASSERT_NO_FATAL_FAILURE(session_.DeactivateUsageEntry(pst)); - ASSERT_NO_FATAL_FAILURE(session_.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(session_.GenerateVerifyReport(pst, kInactiveUsed)); - resultant.assign(encrypted.size(), 0); - sts = OEMCrypto_Generic_Decrypt( - session_.session_id(), encrypted.data(), encrypted.size(), iv_, - OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data()); - ASSERT_NE(OEMCrypto_SUCCESS, sts); - EXPECT_NE(clear_buffer_, resultant); -} - -// Test generic sign when the license uses a PST. -TEST_P(UsageTableTestWithMAC, GenericCryptoSign) { - std::string pst = "my_pst"; - uint32_t nonce = session_.get_nonce(); - MakeFourKeys( - 0, wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired, - nonce, pst); - ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE(session_.LoadTestKeys(pst, new_mac_keys_)); - - OEMCryptoResult sts; - unsigned int key_index = 2; - vector expected_signature; - ASSERT_NO_FATAL_FAILURE( - SignBuffer(key_index, clear_buffer_, &expected_signature)); - - sts = OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length, - OEMCrypto_CipherMode_CTR); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - size_t gen_signature_length = 0; - sts = OEMCrypto_Generic_Sign(session_.session_id(), clear_buffer_.data(), - clear_buffer_.size(), OEMCrypto_HMAC_SHA256, - NULL, &gen_signature_length); - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - ASSERT_EQ(static_cast(SHA256_DIGEST_LENGTH), gen_signature_length); - vector signature(SHA256_DIGEST_LENGTH); - sts = OEMCrypto_Generic_Sign(session_.session_id(), clear_buffer_.data(), - clear_buffer_.size(), OEMCrypto_HMAC_SHA256, - signature.data(), &gen_signature_length); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_EQ(expected_signature, signature); - - ASSERT_NO_FATAL_FAILURE(session_.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(session_.GenerateVerifyReport(pst, kActive)); - ASSERT_NO_FATAL_FAILURE(session_.DeactivateUsageEntry(pst)); - ASSERT_NO_FATAL_FAILURE(session_.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(session_.GenerateVerifyReport(pst, kInactiveUsed)); - signature.assign(SHA256_DIGEST_LENGTH, 0); - gen_signature_length = SHA256_DIGEST_LENGTH; - sts = OEMCrypto_Generic_Sign(session_.session_id(), clear_buffer_.data(), - clear_buffer_.size(), OEMCrypto_HMAC_SHA256, - signature.data(), &gen_signature_length); - ASSERT_NE(OEMCrypto_SUCCESS, sts); - ASSERT_NE(signature, expected_signature); -} - -// Test generic verify when the license uses a PST. -TEST_P(UsageTableTestWithMAC, GenericCryptoVerify) { - std::string pst = "my_pst"; - uint32_t nonce = session_.get_nonce(); - MakeFourKeys( - 0, wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired, - nonce, pst); - ASSERT_NO_FATAL_FAILURE(session_.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE(session_.LoadTestKeys(pst, new_mac_keys_)); - - OEMCryptoResult sts; - unsigned int key_index = 3; - vector signature; - SignBuffer(key_index, clear_buffer_, &signature); - - sts = OEMCrypto_SelectKey(session_.session_id(), - session_.license().keys[key_index].key_id, - session_.license().keys[key_index].key_id_length, - OEMCrypto_CipherMode_CTR); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - sts = OEMCrypto_Generic_Verify(session_.session_id(), clear_buffer_.data(), - clear_buffer_.size(), OEMCrypto_HMAC_SHA256, - signature.data(), signature.size()); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - ASSERT_NO_FATAL_FAILURE(session_.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(session_.GenerateVerifyReport(pst, kActive)); - ASSERT_NO_FATAL_FAILURE(session_.DeactivateUsageEntry(pst)); - ASSERT_NO_FATAL_FAILURE(session_.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(session_.GenerateVerifyReport(pst, kInactiveUsed)); - sts = OEMCrypto_Generic_Verify(session_.session_id(), clear_buffer_.data(), - clear_buffer_.size(), OEMCrypto_HMAC_SHA256, - signature.data(), signature.size()); - ASSERT_NE(OEMCrypto_SUCCESS, sts); -} - -// Test that an offline license can be loaded. -TEST_P(UsageTableTestWithMAC, OfflineLicense) { - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); -} - -// Test that an offline license can be loaded and that the license can be -// renewed. -TEST_P(UsageTableTestWithMAC, OfflineLicenseRefresh) { - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec::kControlNonceOrEntry, s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - time_t loaded = time(NULL); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); - s.GenerateNonce(); - // License renewal message is signed by client and verified by the server. - ASSERT_NO_FATAL_FAILURE(s.VerifyClientSignature()); - size_t kAllKeys = 1; - ASSERT_NO_FATAL_FAILURE(s.RefreshTestKeys( - kAllKeys, wvoec::kControlNonceOrEntry, 0, OEMCrypto_SUCCESS)); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE( - s.GenerateVerifyReport(pst, kActive, - loaded, // license recieved. - loaded, // First decrypt when loaded, not refresh. - 0)); // last decrypt now. -} - -// Test that an offline license can be reloaded in a new session. -TEST_P(UsageTableTestWithMAC, ReloadOfflineLicense) { - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); - - ASSERT_NO_FATAL_FAILURE(s.open()); - // We will reuse the encrypted and signed message, so we don't call - // FillSimpleMessage again. - ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kUnused)); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kActive)); - ASSERT_NO_FATAL_FAILURE(s.close()); -} - -// Test that an offline license can be reloaded in a new session, and then -// refreshed. -TEST_P(UsageTableTestWithMAC, ReloadOfflineLicenseWithRefresh) { - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); - time_t loaded = time(NULL); - - ASSERT_NO_FATAL_FAILURE(s.open()); - // We will reuse the encrypted and signed message, so we don't call - // FillSimpleMessage again. - ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kUnused, loaded, 0, 0)); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); - time_t decrypt_time = time(NULL); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE( - s.GenerateVerifyReport(pst, kActive, - loaded, // license received. - decrypt_time, // first decrypt - decrypt_time)); // last decrypt - size_t kAllKeys = 1; - ASSERT_NO_FATAL_FAILURE(s.RefreshTestKeys( - kAllKeys, wvoec::kControlNonceOrEntry, 0, OEMCrypto_SUCCESS)); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kActive, - loaded, // license loaded. - decrypt_time, // first decrypt - 0)); // last decrypt - ASSERT_NO_FATAL_FAILURE(s.close()); -} - -// Verify that we can still reload an offline license after OEMCrypto_Terminate -// and Initialize are called. This is as close to a reboot as we can do in a -// unit test. -TEST_P(UsageTableTestWithMAC, ReloadOfflineLicenseWithTerminate) { - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); - ShutDown(); // This calls OEMCrypto_Terminate. - Restart(); // This calls OEMCrypto_Initialize. - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_LoadUsageTableHeader(encrypted_usage_header_.data(), - encrypted_usage_header_.size())); - - ASSERT_NO_FATAL_FAILURE(s.open()); - // We will reuse the encrypted and signed message, so we don't call - // FillSimpleMessage again. - ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kUnused)); - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kActive)); - ASSERT_NO_FATAL_FAILURE(s.close()); -} - -// If we attempt to load a second license with the same usage entry as the -// first, but it has different mac keys, then the attempt should fail. This is -// how we verify that we are reloading the same license. -TEST_P(UsageTableTestWithMAC, BadReloadOfflineLicense) { - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); - time_t loaded = time(NULL); - - // Offline license with new mac keys should fail. - Session s2; - ASSERT_NO_FATAL_FAILURE(s2.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s2)); - ASSERT_NO_FATAL_FAILURE(s2.FillSimpleMessage( - 0, wvoec::kControlNonceOrEntry, s2.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s2.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s2.LoadUsageEntry(s)); - ASSERT_NE( - OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys(s2.session_id(), s2.message_ptr(), s2.message_size(), - s2.signature().data(), s2.signature().size(), - s2.enc_mac_keys_iv_substr(), s2.enc_mac_keys_substr(), - s.num_keys(), s2.key_array(), s2.pst_substr(), - GetSubstring(), OEMCrypto_ContentLicense)); - ASSERT_NO_FATAL_FAILURE(s2.close()); - - // Offline license with same mac keys should still be OK. - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kUnused, - loaded, // license loaded. - 0, - 0)); // first and last decrypt -} - -// An offline license should not load on the first call if the nonce is bad. -TEST_P(UsageTableTestWithMAC, OfflineBadNonce) { - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE( - s.FillSimpleMessage(0, wvoec::kControlNonceOrEntry, 42, pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), s.message_ptr(), s.message_size(), s.signature().data(), - s.signature().size(), s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), s.pst_substr(), GetSubstring(), - OEMCrypto_ContentLicense); - ASSERT_NE(OEMCrypto_SUCCESS, sts); - ASSERT_NO_FATAL_FAILURE(s.close()); -} - -// An offline license needs a valid pst. -TEST_P(UsageTableTestWithMAC, OfflineEmptyPST) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE( - s.FillSimpleMessage(0, wvoec::kControlNonceOrEntry, s.get_nonce())); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - OEMCryptoResult sts = OEMCrypto_LoadKeys( - s.session_id(), s.message_ptr(), s.message_size(), s.signature().data(), - s.signature().size(), s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), GetSubstring(), GetSubstring(), - OEMCrypto_ContentLicense); - ASSERT_NE(OEMCrypto_SUCCESS, sts); - ASSERT_NO_FATAL_FAILURE(s.close()); -} - -// If we try to reload a license with a different PST, the attempt should fail. -TEST_P(UsageTableTestWithMAC, ReloadOfflineWrongPST) { - std::string pst = "my_pst1"; - Session s; - ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); - - std::string bad_pst = "my_pst2"; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); - memcpy(s.license().pst, bad_pst.c_str(), bad_pst.length()); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys( - s.session_id(), s.message_ptr(), s.message_size(), - s.signature().data(), s.signature().size(), GetSubstring(), - GetSubstring(), s.num_keys(), s.key_array(), s.pst_substr(), - GetSubstring(), OEMCrypto_ContentLicense)); -} - -// Once a license has been deactivated, the keys can no longer be used for -// decryption. However, we can still generate a usage report. -TEST_P(UsageTableTestWithMAC, DeactivateOfflineLicense) { - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); - - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE( - s.LoadTestKeys(pst, new_mac_keys_)); // Reload the license - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); // Should be able to decrypt. - ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(pst)); // Then deactivate. - // After deactivate, should not be able to decrypt. - ASSERT_NO_FATAL_FAILURE( - s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kInactiveUsed)); - ASSERT_NO_FATAL_FAILURE(s.close()); - - Session s2; - ASSERT_NO_FATAL_FAILURE(s2.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s2)); - ASSERT_NO_FATAL_FAILURE(s2.LoadUsageEntry(s)); - // Offile license can not be reused if it has been deactivated. - EXPECT_NE( - OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys(s2.session_id(), s.message_ptr(), s.message_size(), - s.signature().data(), s.signature().size(), - s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), s.pst_substr(), - GetSubstring(), OEMCrypto_ContentLicense)); - s2.close(); - // But we can still generate a report. - Session s3; - ASSERT_NO_FATAL_FAILURE(s3.open()); - ASSERT_NO_FATAL_FAILURE(s3.LoadUsageEntry(s)); - ASSERT_NO_FATAL_FAILURE(s3.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s3.GenerateReport(pst, OEMCrypto_SUCCESS, &s)); - EXPECT_EQ(kInactiveUsed, s3.pst_report().status()); - // We could call DeactivateUsageEntry multiple times. The state should not - // change. - ASSERT_NO_FATAL_FAILURE(s3.DeactivateUsageEntry(pst)); - ASSERT_NO_FATAL_FAILURE(s3.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s3.GenerateReport(pst, OEMCrypto_SUCCESS, &s)); - EXPECT_EQ(kInactiveUsed, s3.pst_report().status()); -} - -// The usage report should indicate that the keys were never used for -// decryption. -TEST_P(UsageTableTestWithMAC, DeactivateOfflineLicenseUnused) { - std::string pst = "my_pst"; - Session s1; - ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s1, pst)); - - ASSERT_NO_FATAL_FAILURE(s1.open()); - ASSERT_NO_FATAL_FAILURE(s1.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s1)); - ASSERT_NO_FATAL_FAILURE( - s1.LoadTestKeys(pst, new_mac_keys_)); // Reload the license - // No Decrypt. This license is unused. - ASSERT_NO_FATAL_FAILURE(s1.DeactivateUsageEntry(pst)); // Then deactivate. - // After deactivate, should not be able to decrypt. - ASSERT_NO_FATAL_FAILURE( - s1.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); - ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s1.GenerateVerifyReport(pst, kInactiveUnused)); - ASSERT_NO_FATAL_FAILURE(s1.close()); - - Session s2; - ASSERT_NO_FATAL_FAILURE(s2.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s2)); - ASSERT_NO_FATAL_FAILURE(s2.LoadUsageEntry(s1)); - // Offline license can not be reused if it has been deactivated. - EXPECT_NE( - OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys(s2.session_id(), s1.message_ptr(), s1.message_size(), - s1.signature().data(), s1.signature().size(), - s1.enc_mac_keys_iv_substr(), s1.enc_mac_keys_substr(), - s1.num_keys(), s1.key_array(), s1.pst_substr(), - GetSubstring(), OEMCrypto_ContentLicense)); - s2.close(); - // But we can still generate a report. - Session s3; - ASSERT_NO_FATAL_FAILURE(s3.open()); - ASSERT_NO_FATAL_FAILURE(s3.LoadUsageEntry(s1)); - ASSERT_NO_FATAL_FAILURE(s3.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s3.GenerateReport(pst, OEMCrypto_SUCCESS, &s1)); - EXPECT_EQ(kInactiveUnused, s3.pst_report().status()); - // We could call DeactivateUsageEntry multiple times. The state should not - // change. - ASSERT_NO_FATAL_FAILURE(s3.DeactivateUsageEntry(pst)); - ASSERT_NO_FATAL_FAILURE(s3.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s3.GenerateReport(pst, OEMCrypto_SUCCESS, &s1)); - EXPECT_EQ(kInactiveUnused, s3.pst_report().status()); -} - -// If the PST pointers are not contained in the message, then LoadKeys should -// reject the attempt. -TEST_P(UsageTableTestWithMAC, BadRange) { - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(session_.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec::kControlNonceOrEntry, s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - std::string double_message = DuplicateMessage(s.encrypted_license()); - OEMCrypto_Substring wrong_pst = s.pst_substr(); - wrong_pst.offset += s.message_size(); - ASSERT_NE( - OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys( - s.session_id(), - reinterpret_cast(double_message.data()), - s.message_size(), s.signature().data(), s.signature().size(), - s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), s.num_keys(), - s.key_array(), wrong_pst, GetSubstring(), OEMCrypto_ContentLicense)); -} - -// Test update usage table fails when passed a null pointer. -TEST_F(UsageTableTest, UpdateFailsWithNullPtr) { - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired, - s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - size_t header_buffer_length = encrypted_usage_header_.size(); - size_t entry_buffer_length = s.encrypted_usage_entry().size(); - vector buffer(entry_buffer_length); - // Now try to pass in null pointers for the buffers. This should fail. - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_UpdateUsageEntry( - s.session_id(), NULL, &header_buffer_length, - buffer.data(), &entry_buffer_length)); - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_UpdateUsageEntry( - s.session_id(), encrypted_usage_header_.data(), - &header_buffer_length, NULL, &entry_buffer_length)); -} - -// Class used to test usage table defragmentation. -class UsageTableDefragTest : public UsageTableTest { - protected: - void LoadFirstLicense(Session* s, uint32_t index) { - ASSERT_NO_FATAL_FAILURE(s->open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(s)); - std::string pst = "pst "; - char c1 = 'A' + (index / 26); - char c2 = 'A' + (index % 26); - pst = pst + c1 + c2; - ASSERT_NO_FATAL_FAILURE(s->FillSimpleMessage( - 0, wvoec::kControlNonceOrEntry, s->get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s->EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s->CreateNewUsageEntry()); - ASSERT_EQ(s->usage_entry_number(), index); - ASSERT_NO_FATAL_FAILURE(s->LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s->TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s->UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s->close()); - } - - void ReloadLicense(Session* s, time_t start) { - ASSERT_NO_FATAL_FAILURE(s->open()); - ASSERT_NO_FATAL_FAILURE(s->ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(s)); - ASSERT_NO_FATAL_FAILURE(s->LoadTestKeys(s->pst(), new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s->UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s->TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s->UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s->GenerateVerifyReport(s->pst(), kActive, - start, start, 0)); - ASSERT_NO_FATAL_FAILURE(s->close()); - } - - void FailReload(Session* s, OEMCryptoResult expected_result) { - ASSERT_NO_FATAL_FAILURE(s->open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(s)); - ASSERT_EQ(expected_result, - OEMCrypto_LoadUsageEntry(s->session_id(), s->usage_entry_number(), - s->encrypted_usage_entry().data(), - s->encrypted_usage_entry().size())); - - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys( - s->session_id(), s->message_ptr(), s->message_size(), - s->signature().data(), s->signature().size(), - s->enc_mac_keys_iv_substr(), s->enc_mac_keys_substr(), - s->num_keys(), s->key_array(), s->pst_substr(), - GetSubstring(), OEMCrypto_ContentLicense)); - ASSERT_NO_FATAL_FAILURE(s->close()); - } - - void ShrinkHeader(uint32_t new_size, - OEMCryptoResult expected_result = OEMCrypto_SUCCESS) { - size_t header_buffer_length = 0; - OEMCryptoResult sts = - OEMCrypto_ShrinkUsageTableHeader(new_size, NULL, &header_buffer_length); - if (expected_result == OEMCrypto_SUCCESS) { - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - } else { - ASSERT_NE(OEMCrypto_SUCCESS, sts); - if (sts != OEMCrypto_ERROR_SHORT_BUFFER) return; - } - ASSERT_LT(0u, header_buffer_length); - encrypted_usage_header_.resize(header_buffer_length); - sts = OEMCrypto_ShrinkUsageTableHeader( - new_size, encrypted_usage_header_.data(), - &header_buffer_length); - ASSERT_EQ(expected_result, sts); - } -}; - -// Verify that usage table entries can be moved around in the table. -TEST_F(UsageTableDefragTest, MoveUsageEntries) { - const size_t ENTRY_COUNT = 10; - vector sessions(ENTRY_COUNT); - vector start(ENTRY_COUNT); - for (size_t i = 0; i < ENTRY_COUNT; i++) { - ASSERT_NO_FATAL_FAILURE(LoadFirstLicense(&sessions[i], i)) - << "On license " << i << " pst=" << sessions[i].pst(); - start[i] = time(NULL); - } - for (size_t i = 0; i < ENTRY_COUNT; i++) { - ASSERT_NO_FATAL_FAILURE(ReloadLicense(&sessions[i], start[i])) - << "On license " << i << " pst=" << sessions[i].pst(); - } - // Move 4 to 1. - ASSERT_NO_FATAL_FAILURE( - sessions[4].MoveUsageEntry(1, &encrypted_usage_header_)); - // Shrink header to 3 entries 0, 1 was 4, 2. - ASSERT_NO_FATAL_FAILURE(ShrinkHeader(3)); - ShutDown(); - Restart(); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_LoadUsageTableHeader(encrypted_usage_header_.data(), - encrypted_usage_header_.size())); - ASSERT_NO_FATAL_FAILURE(ReloadLicense(&sessions[0], start[0])); - // Now has index 1. - ASSERT_NO_FATAL_FAILURE(ReloadLicense(&sessions[4], start[4])); - ASSERT_NO_FATAL_FAILURE(ReloadLicense(&sessions[2], start[2])); - // When 4 was moved to 1, it increased the gen. number in the header. - ASSERT_NO_FATAL_FAILURE( - FailReload(&sessions[1], OEMCrypto_ERROR_GENERATION_SKEW)); - // Index 3 is beyond the end of the table. - ASSERT_NO_FATAL_FAILURE( - FailReload(&sessions[3], OEMCrypto_ERROR_UNKNOWN_FAILURE)); -} - -// A usage table entry cannot be moved into an entry where an open session is -// currently using the entry. -TEST_F(UsageTableDefragTest, MoveUsageEntriesToOpenSession) { - Session s0; - Session s1; - LoadFirstLicense(&s0, 0); - LoadFirstLicense(&s1, 1); - s0.open(); - ASSERT_NO_FATAL_FAILURE(s0.ReloadUsageEntry()); - // s0 currently open on index 0. Expect this to fail: - ASSERT_NO_FATAL_FAILURE(s1.MoveUsageEntry(0, &encrypted_usage_header_, - OEMCrypto_ERROR_ENTRY_IN_USE)); -} - -// The usage table cannot be shrunk if any session is using an entry that would -// be deleted. -TEST_F(UsageTableDefragTest, ShrinkOverOpenSessions) { - Session s0; - Session s1; - LoadFirstLicense(&s0, 0); - LoadFirstLicense(&s1, 1); - s0.open(); - ASSERT_NO_FATAL_FAILURE(s0.ReloadUsageEntry()); - s1.open(); - ASSERT_NO_FATAL_FAILURE(s1.ReloadUsageEntry()); - // Since s0 and s1 are open, we can't shrink. - ASSERT_NO_FATAL_FAILURE(ShrinkHeader(1, OEMCrypto_ERROR_ENTRY_IN_USE)); - s1.close(); // Can shrink after closing s1, even if s0 is open. - ASSERT_NO_FATAL_FAILURE(ShrinkHeader(1, OEMCrypto_SUCCESS)); -} - -// Verify the usage table size can be increased. -TEST_F(UsageTableDefragTest, EnlargeHeader) { - Session s0; - Session s1; - LoadFirstLicense(&s0, 0); - LoadFirstLicense(&s1, 1); - // Can only shrink the header -- not make it bigger. - ASSERT_NO_FATAL_FAILURE(ShrinkHeader(4, OEMCrypto_ERROR_UNKNOWN_FAILURE)); -} - -// A new header can only be created while no entries are in use. -TEST_F(UsageTableDefragTest, CreateNewHeaderWhileUsingOldOne) { - Session s0; - Session s1; - LoadFirstLicense(&s0, 0); - LoadFirstLicense(&s1, 1); - s0.open(); - ASSERT_NO_FATAL_FAILURE(s0.ReloadUsageEntry()); - const bool kExpectFailure = false; - ASSERT_NO_FATAL_FAILURE(CreateUsageTableHeader(kExpectFailure)); -} - -// Verify that a usage table entry can only be loaded into the correct index of -// the table. -TEST_F(UsageTableDefragTest, ReloadUsageEntryWrongIndex) { - Session s0; - Session s1; - LoadFirstLicense(&s0, 0); - LoadFirstLicense(&s1, 1); - s0.set_usage_entry_number(1); - ASSERT_NO_FATAL_FAILURE( - FailReload(&s0, OEMCrypto_ERROR_INVALID_SESSION)); -} - -// Verify that a usage table entry cannot be loaded if it has been altered. -TEST_F(UsageTableDefragTest, ReloadUsageEntryBadData) { - Session s; - LoadFirstLicense(&s, 0); - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - vector data = s.encrypted_usage_entry(); - ASSERT_LT(0UL, data.size()); - data[0] ^= 42; - // Error could be signature or verification error. - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), - data.data(), data.size())); -} - -static std::string MakePST(size_t n) { - return "pst-" + std::to_string(n); -} - -// This verifies we can actually create two hundered usage table entries. -TEST_F(UsageTableDefragTest, TwoHundredEntries) { - // OEMCrypto is required to store at least 200 entries in the usage table - // header, but it is allowed to store more. This test verifies that if we keep - // adding entries, the error indicates a resource limit. It then verifies - // that all of the successful entries are still valid after we throw out the - // last invalid entry. - const size_t ENTRY_COUNT = 2000; - vector sessions(ENTRY_COUNT); - size_t successful_count = 0; - for (size_t i = 0; i < ENTRY_COUNT; i++) { - if (i % 50 == 0) LOGD("Creating license %zd", i); - ASSERT_NO_FATAL_FAILURE(sessions[i].open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&sessions[i])); - std::string pst = MakePST(i); - ASSERT_NO_FATAL_FAILURE(sessions[i].FillSimpleMessage( - 0, wvoec::kControlNonceOrEntry, sessions[i].get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(sessions[i].EncryptAndSign()); - // We attempt to create a new usage table entry for this session. - OEMCryptoResult status; - ASSERT_NO_FATAL_FAILURE(sessions[i].CreateNewUsageEntry(&status)); - if (status == OEMCrypto_SUCCESS) { - ASSERT_EQ(sessions[i].usage_entry_number(), i); - ASSERT_NO_FATAL_FAILURE(sessions[i].LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE( - sessions[i].UpdateUsageEntry(&encrypted_usage_header_)); - successful_count++; - } else { - // If we failed to create this many entries because of limited resources, - // then the error returned should be insufficient resources. - EXPECT_EQ(OEMCrypto_ERROR_INSUFFICIENT_RESOURCES, status) - << "Failed to create license " << i << " with pst = " << pst; - break; - } - ASSERT_NO_FATAL_FAILURE(sessions[i].close()); - } - LOGD("successful_count = %d", successful_count); - EXPECT_GE(successful_count, 200u); - sleep(kShortSleep); - // Now we will loop through each valid entry, and verify that we can still - // reload the license and perform a decrypt. - for (size_t i = 0; i < successful_count; i++) { - if (i % 50 == 0) LOGD("Reloading license %zd", i); - ASSERT_NO_FATAL_FAILURE(sessions[i].open()); - std::string pst = MakePST(i); - // Reuse license message created above. - ASSERT_NO_FATAL_FAILURE(sessions[i].ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&sessions[i])); - ASSERT_NO_FATAL_FAILURE(sessions[i].LoadTestKeys(pst, new_mac_keys_)) - << "Failed to reload license " << i << " with pst = " << pst; - ASSERT_NO_FATAL_FAILURE( - sessions[i].UpdateUsageEntry(&encrypted_usage_header_)) - << "Failed to update license " << i << " with pst = " << pst; - ASSERT_NO_FATAL_FAILURE(sessions[i].TestDecryptCTR()) - << "Failed to use license " << i << " with pst = " << pst; - ASSERT_NO_FATAL_FAILURE( - sessions[i].UpdateUsageEntry(&encrypted_usage_header_)) - << "Failed to update license " << i << " with pst = " << pst; - ASSERT_NO_FATAL_FAILURE(sessions[i].close()); - } - // We also need to verify that a full table can be shrunk, and the remaining - // licenses still work. - size_t smaller_size = 10u; // 10 is smaller than 200. - ASSERT_NO_FATAL_FAILURE(ShrinkHeader(smaller_size)); - for (size_t i = 0; i < smaller_size; i++) { - ASSERT_NO_FATAL_FAILURE(sessions[i].open()); - std::string pst = MakePST(i); - // Reuse license message created above. - ASSERT_NO_FATAL_FAILURE(sessions[i].ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&sessions[i])); - ASSERT_NO_FATAL_FAILURE(sessions[i].LoadTestKeys(pst, new_mac_keys_)) - << "Failed to reload license " << i << " with pst = " << pst; - ASSERT_NO_FATAL_FAILURE( - sessions[i].UpdateUsageEntry(&encrypted_usage_header_)) - << "Failed to update license " << i << " with pst = " << pst; - ASSERT_NO_FATAL_FAILURE(sessions[i].TestDecryptCTR()) - << "Failed to use license " << i << " with pst = " << pst; - ASSERT_NO_FATAL_FAILURE( - sessions[i].UpdateUsageEntry(&encrypted_usage_header_)) - << "Failed to update license " << i << " with pst = " << pst; - ASSERT_NO_FATAL_FAILURE(sessions[i].close()); - } -} - -// This verifies that copying the old usage table to the new one works. -TEST_F(UsageTableTest, CopyOldEntries) { - // First create three old entries. We open sessions first to force creation - // of the mac keys. - - Session s1; - ASSERT_NO_FATAL_FAILURE(s1.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s1)); - ASSERT_NO_FATAL_FAILURE(s1.FillSimpleMessage(0, 0, 0, "pst number 1")); - ASSERT_NO_FATAL_FAILURE(s1.EncryptAndSign()); - - Test_PST_Report report1(s1.pst(), kUnused); - report1.seconds_since_license_received = 30; - report1.seconds_since_first_decrypt = 20; - report1.seconds_since_last_decrypt = 10; - ASSERT_NO_FATAL_FAILURE(s1.CreateOldEntry(report1)); - - Session s2; - ASSERT_NO_FATAL_FAILURE(s2.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s2)); - ASSERT_NO_FATAL_FAILURE(s2.FillSimpleMessage(0, 0, 0, "pst number 2")); - ASSERT_NO_FATAL_FAILURE(s2.EncryptAndSign()); - - Test_PST_Report report2(s2.pst(), kActive); - report2.seconds_since_license_received = 60; - report2.seconds_since_first_decrypt = 50; - report2.seconds_since_last_decrypt = 40; - ASSERT_NO_FATAL_FAILURE(s2.CreateOldEntry(report2)); - - Session s3; - ASSERT_NO_FATAL_FAILURE(s3.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s3)); - ASSERT_NO_FATAL_FAILURE(s3.FillSimpleMessage(0, 0, 0, "pst number 3")); - ASSERT_NO_FATAL_FAILURE(s3.EncryptAndSign()); - - Test_PST_Report report3(s3.pst(), kInactive); - report3.seconds_since_license_received = 90; - report3.seconds_since_first_decrypt = 80; - report3.seconds_since_last_decrypt = 70; - ASSERT_NO_FATAL_FAILURE(s3.CreateOldEntry(report3)); - - // Now we copy and verify each one. The order is changed to make - // sure there are no order dependecies. - ASSERT_NO_FATAL_FAILURE( - s2.CopyAndVerifyOldEntry(report2, &encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE( - s1.CopyAndVerifyOldEntry(report1, &encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE( - s3.CopyAndVerifyOldEntry(report3, &encrypted_usage_header_)); -} - -// This verifies that the usage table header can be loaded if the generation -// number is off by one, but not off by two. -TEST_F(UsageTableTest, ReloadUsageTableWithSkew) { - // This also tests a few other error conditions with usage table headers. - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); - - // Reload the license, and save the header. - ASSERT_NO_FATAL_FAILURE(s.open()); - // We will reuse the encrypted and signed message, so we don't call - // FillSimpleMessage again. - ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - vector old_usage_header_2_ = encrypted_usage_header_; - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - vector old_usage_header_1_ = encrypted_usage_header_; - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.close()); - - ShutDown(); - Restart(); - // Null pointer generates error. - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_LoadUsageTableHeader(NULL, - old_usage_header_2_.size())); - ASSERT_NO_FATAL_FAILURE(s.open()); - // Cannot load an entry with if header didn't load. - ASSERT_EQ( - OEMCrypto_ERROR_UNKNOWN_FAILURE, - OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), - s.encrypted_usage_entry().data(), - s.encrypted_usage_entry().size())); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.close()); - - // Modified header generates error. - vector bad_header = encrypted_usage_header_; - bad_header[3] ^= 42; - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_LoadUsageTableHeader(bad_header.data(), - bad_header.size())); - ASSERT_NO_FATAL_FAILURE(s.open()); - // Cannot load an entry with if header didn't load. - ASSERT_EQ( - OEMCrypto_ERROR_UNKNOWN_FAILURE, - OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), - s.encrypted_usage_entry().data(), - s.encrypted_usage_entry().size())); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.close()); - - // Old by 2 generation numbers is error. - ASSERT_EQ(OEMCrypto_ERROR_GENERATION_SKEW, - OEMCrypto_LoadUsageTableHeader(old_usage_header_2_.data(), - old_usage_header_2_.size())); - ASSERT_NO_FATAL_FAILURE(s.open()); - // Cannot load an entry with if header didn't load. - ASSERT_NE( - OEMCrypto_SUCCESS, - OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), - s.encrypted_usage_entry().data(), - s.encrypted_usage_entry().size())); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.close()); - - // Old by 1 generation numbers is just warning. - ASSERT_EQ(OEMCrypto_WARNING_GENERATION_SKEW, - OEMCrypto_LoadUsageTableHeader(old_usage_header_1_.data(), - old_usage_header_1_.size())); - // Everything else should still work. Skew by 1 is just a warning. - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_EQ( - OEMCrypto_WARNING_GENERATION_SKEW, - OEMCrypto_LoadUsageEntry(s.session_id(), s.usage_entry_number(), - s.encrypted_usage_entry().data(), - s.encrypted_usage_entry().size())); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.close()); -} - -// A usage report with the wrong pst should fail. -TEST_F(UsageTableTest, GenerateReportWrongPST) { - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec::kControlNonceOrEntry, s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateReport("wrong_pst", - OEMCrypto_ERROR_WRONG_PST)); -} - -// Test usage table timing. -TEST_F(UsageTableTest, TimingTest) { - std::string pst1 = "my_pst_1"; - std::string pst2 = "my_pst_2"; - std::string pst3 = "my_pst_3"; - Session s1; - Session s2; - Session s3; - ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s1, pst1)); - time_t loaded1 = time(NULL); - ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s2, pst2)); - time_t loaded2 = time(NULL); - ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s3, pst3)); - time_t loaded3 = time(NULL); - - sleep(kLongSleep); - ASSERT_NO_FATAL_FAILURE(s1.open()); - ASSERT_NO_FATAL_FAILURE(s1.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s1)); - ASSERT_NO_FATAL_FAILURE(s1.LoadTestKeys(pst1, new_mac_keys_)); - time_t first_decrypt1 = time(NULL); - ASSERT_NO_FATAL_FAILURE(s1.TestDecryptCTR()); - - ASSERT_NO_FATAL_FAILURE(s2.open()); - ASSERT_NO_FATAL_FAILURE(s2.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s2)); - ASSERT_NO_FATAL_FAILURE(s2.LoadTestKeys(pst2, new_mac_keys_)); - time_t first_decrypt2 = time(NULL); - ASSERT_NO_FATAL_FAILURE(s2.TestDecryptCTR()); - - sleep(kLongSleep); - time_t second_decrypt = time(NULL); - ASSERT_NO_FATAL_FAILURE(s1.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s2.TestDecryptCTR()); - - sleep(kLongSleep); - ASSERT_NO_FATAL_FAILURE(s1.DeactivateUsageEntry(pst1)); - ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s1.close()); - ASSERT_NO_FATAL_FAILURE(s2.close()); - - sleep(kLongSleep); - // This is as close to reboot as we can simulate in code. - ShutDown(); - sleep(kShortSleep); - Restart(); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_LoadUsageTableHeader(encrypted_usage_header_.data(), - encrypted_usage_header_.size())); - - // After a reboot, we should be able to reload keys, and generate reports. - sleep(kLongSleep); - time_t third_decrypt = time(NULL); - ASSERT_NO_FATAL_FAILURE(s2.open()); - ASSERT_NO_FATAL_FAILURE(s2.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s2)); - ASSERT_NO_FATAL_FAILURE(s2.LoadTestKeys(pst2, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s2.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s2.close()); - - ASSERT_NO_FATAL_FAILURE(s1.open()); - ASSERT_NO_FATAL_FAILURE(s2.open()); - ASSERT_NO_FATAL_FAILURE(s3.open()); - ASSERT_NO_FATAL_FAILURE(s1.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s2.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s3.ReloadUsageEntry()); - sleep(kLongSleep); - ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s1.GenerateVerifyReport(pst1, kInactiveUsed, - loaded1, - first_decrypt1, - second_decrypt)); - ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s2.GenerateVerifyReport(pst2, kActive, - loaded2, - first_decrypt2, - third_decrypt)); - ASSERT_NO_FATAL_FAILURE(s3.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s3.GenerateVerifyReport(pst3, kUnused, loaded3)); -} - -// Verify the times in the usage report. For performance reasons, we allow the -// times in the usage report to be off by as much as kUsageTimeTolerance, which -// is 10 seconds. This acceptable error is called slop. This test needs to run -// long enough that the reported values are distinct, even after accounting for -// this slop. -TEST_F(UsageTableTest, VerifyUsageTimes) { - std::string pst = "my_pst"; - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); - ASSERT_NO_FATAL_FAILURE(s.FillSimpleMessage( - 0, wvoec::kControlNonceEnabled | wvoec::kControlNonceRequired, - s.get_nonce(), pst)); - ASSERT_NO_FATAL_FAILURE(s.EncryptAndSign()); - ASSERT_NO_FATAL_FAILURE(s.CreateNewUsageEntry()); - ASSERT_NO_FATAL_FAILURE(s.LoadTestKeys(pst, new_mac_keys_)); - time_t load_time = time(NULL); - - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kUnused)); - - const time_t kDotIntervalInSeconds = 5; - const time_t kIdleInSeconds = 20; - const time_t kPlaybackLoopInSeconds = 2 * 60; - - cout << "This test verifies the elapsed time reported in the usage table " - "for a 2 minute simulated playback." - << endl; - cout << "The total time for this test is about " - << kPlaybackLoopInSeconds + 2 * kIdleInSeconds << " seconds." << endl; - cout << "Wait " << kIdleInSeconds - << " seconds to verify usage table time before playback." << endl; - - PrintDotsWhileSleep(kIdleInSeconds, kDotIntervalInSeconds); - - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kUnused, load_time)); - cout << "Start simulated playback..." << endl; - - time_t dot_time = kDotIntervalInSeconds; - time_t playback_time = 0; - time_t start_time = time(NULL); - do { - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kActive, - load_time, - start_time, - 0)); // last decrypt = now. - playback_time = time(NULL) - start_time; - ASSERT_LE(0, playback_time); - if (playback_time >= dot_time) { - cout << "."; - cout.flush(); - dot_time += kDotIntervalInSeconds; - } - } while (playback_time < kPlaybackLoopInSeconds); - cout << "\nSimulated playback time = " << playback_time << " seconds.\n"; - - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kActive, - load_time, - start_time, - 0)); // last decrypt = now. - EXPECT_NEAR(s.pst_report().seconds_since_first_decrypt() - - s.pst_report().seconds_since_last_decrypt(), - playback_time, kUsageTableTimeTolerance); - - cout << "Wait another " << kIdleInSeconds - << " seconds " - "to verify usage table time since playback ended." - << endl; - PrintDotsWhileSleep(kIdleInSeconds, kDotIntervalInSeconds); - - // At this point, this is what we expect: - // idle playback loop idle - // |-----|-------------------------|-----| - // |<--->| = seconds_since_last_decrypt - // |<----------------------------->| = seconds_since_first_decrypt - // |<------------------------------------| = seconds_since_license_received - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kActive, - load_time, - start_time, - kIdleInSeconds)); - ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(pst)); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kInactiveUsed, - load_time, - start_time, - kIdleInSeconds)); - ASSERT_NO_FATAL_FAILURE( - s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); -} - // NOTE: This test needs root access since clock_settime messes with the system // time in order to verify that OEMCrypto protects against rollbacks in usage // entries. Therefore, this test is filtered if not run as root. // We don't test roll-forward protection or instances where the user rolls back // the time to the last decrypt call since this requires hardware-secure clocks // to guarantee. -TEST_F(UsageTableTest, TimeRollbackPrevention) { - std::string pst = "my_pst"; - Session s1; +TEST_P(OEMCryptoUsageTableTestWallClock, TimeRollbackPrevention) { cout << "This test temporarily rolls back the system time in order to verify " << "that the usage report accounts for the change. It then rolls " << "the time back forward to the absolute time." << endl; + LicenseWithUsageEntry entry; + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); std::chrono::system_clock wall_clock; std::chrono::steady_clock monotonic_clock; const auto loaded = wall_clock.now(); - ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s1, pst)); - ASSERT_NO_FATAL_FAILURE(s1.open()); - ASSERT_NO_FATAL_FAILURE(s1.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s1)); - ASSERT_NO_FATAL_FAILURE(s1.LoadTestKeys(pst, new_mac_keys_)); + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); const auto first_decrypt = wall_clock.now(); // Monotonic clock can't be changed. We use this since system clock will be // unreliable. const auto first_decrypt_monotonic = monotonic_clock.now(); - ASSERT_NO_FATAL_FAILURE(s1.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s1.close()); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.close()); // Imitate playback. - sleep(kLongDuration * 2); + wvcdm::TestSleep::Sleep(kLongDuration * 2); - ASSERT_NO_FATAL_FAILURE(s1.open()); - ASSERT_NO_FATAL_FAILURE(s1.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s1)); - ASSERT_NO_FATAL_FAILURE(s1.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_NO_FATAL_FAILURE(s1.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s1.close()); + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.close()); // Rollback the wall clock time. cout << "Rolling the system time back..." << endl; @@ -6441,15 +6199,12 @@ TEST_F(UsageTableTest, TimeRollbackPrevention) { -static_cast(kLongDuration) * 10)); // Try to playback again. - ASSERT_NO_FATAL_FAILURE(s1.open()); - ASSERT_NO_FATAL_FAILURE(s1.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s1)); - ASSERT_NO_FATAL_FAILURE(s1.LoadTestKeys(pst, new_mac_keys_)); + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); const auto third_decrypt_monotonic = monotonic_clock.now(); - ASSERT_NO_FATAL_FAILURE(s1.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s1.GenerateReport(pst)); - Test_PST_Report expected(pst, kActive); + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s.close()); + Test_PST_Report expected(entry.pst(), kActive); // Restore wall clock to its original position to verify that OEMCrypto does // not report negative times. @@ -6462,54 +6217,40 @@ TEST_F(UsageTableTest, TimeRollbackPrevention) { expected.time_created = UnixTime(wall_clock.now()); const auto end_time = first_decrypt + test_duration; - ASSERT_NO_FATAL_FAILURE( - s1.VerifyReport(expected, UnixTime(loaded), UnixTime(first_decrypt), - UnixTime(end_time))); - ASSERT_NO_FATAL_FAILURE(s1.close()); + ASSERT_NO_FATAL_FAILURE(s.VerifyReport( + expected, UnixTime(loaded), UnixTime(first_decrypt), UnixTime(end_time))); + ASSERT_NO_FATAL_FAILURE(s.close()); } // Verify that a large PST can be used with usage table entries. -TEST_F(UsageTableTest, PSTLargeBuffer) { +TEST_P(OEMCryptoUsageTableTest, PSTLargeBuffer) { std::string pst(kMaxPSTLength, 'a'); // A large PST. - Session s; - ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s, pst)); + LicenseWithUsageEntry entry(pst); + entry.MakeOfflineAndClose(this); + Session& s = entry.session(); - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.ReloadUsageEntry()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s)); + ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); ASSERT_NO_FATAL_FAILURE( - s.LoadTestKeys(pst, new_mac_keys_)); // Reload the license - ASSERT_NO_FATAL_FAILURE(s.TestDecryptCTR()); // Should be able to decrypt. - ASSERT_NO_FATAL_FAILURE(s.DeactivateUsageEntry(pst)); // Then deactivate. + entry.TestDecryptCTR()); // Should be able to decrypt. + ASSERT_NO_FATAL_FAILURE( + s.DeactivateUsageEntry(entry.pst())); // Then deactivate. // After deactivate, should not be able to decrypt. ASSERT_NO_FATAL_FAILURE( - s.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); + entry.TestDecryptCTR(false, OEMCrypto_ERROR_UNKNOWN_FAILURE)); ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.GenerateVerifyReport(pst, kInactiveUsed)); + ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kInactiveUsed)); ASSERT_NO_FATAL_FAILURE(s.close()); - - Session s2; - ASSERT_NO_FATAL_FAILURE(s2.open()); - ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s2)); - // Offile license can not be reused if it has been deactivated. - ASSERT_NO_FATAL_FAILURE(s2.LoadUsageEntry(s)); - EXPECT_NE( - OEMCrypto_SUCCESS, - OEMCrypto_LoadKeys(s2.session_id(), s.message_ptr(), s.message_size(), - s.signature().data(), s.signature().size(), - s.enc_mac_keys_iv_substr(), s.enc_mac_keys_substr(), - s.num_keys(), s.key_array(), s.pst_substr(), - GetSubstring(), OEMCrypto_ContentLicense)); - s2.close(); - // But we can still generate a report. - Session s3; - ASSERT_NO_FATAL_FAILURE(s3.open()); - ASSERT_NO_FATAL_FAILURE(s3.LoadUsageEntry(s)); - ASSERT_NO_FATAL_FAILURE(s3.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s3.GenerateReport(pst, OEMCrypto_SUCCESS, &s)); - EXPECT_EQ(kInactiveUsed, s3.pst_report().status()); } -INSTANTIATE_TEST_CASE_P(TestUsageTables, UsageTableTestWithMAC, - Values(true, false)); // With and without new_mac_keys. +INSTANTIATE_TEST_CASE_P(TestAll, OEMCryptoUsageTableTest, + Range(kCurrentAPI - 1, kCurrentAPI + 1)); + +// These tests only work when the license has a core message. +INSTANTIATE_TEST_CASE_P(TestAPI16, OEMCryptoUsageTableDefragTest, + Values(kCurrentAPI)); + +// These tests only work when the license has a core message. +INSTANTIATE_TEST_CASE_P(TestAPI16, OEMCryptoUsageTableTestWallClock, + Values(kCurrentAPI)); + } // namespace wvoec diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test_android.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test_android.cpp index e0739ea6..1d05553a 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test_android.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test_android.cpp @@ -60,38 +60,27 @@ TEST_F(OEMCryptoAndroidLMPTest, ValidKeyboxTest) { } TEST_F(OEMCryptoAndroidLMPTest, RewrapDeviceRSAKeyImplemented) { - if (OEMCrypto_Keybox == OEMCrypto_GetProvisioningMethod()) { - ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, - OEMCrypto_RewrapDeviceRSAKey(0, NULL, 0, NULL, 0, NULL, NULL, 0, - NULL, NULL, NULL)); - } else { - ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, - OEMCrypto_RewrapDeviceRSAKey30(0, NULL, NULL, 0, NULL, 0, NULL, - NULL, NULL)); - } -} - -// This verifies that the device can load a DRM Certificate. -TEST_F(OEMCryptoAndroidLMPTest, RSASignatureImplemented) { - ASSERT_NE( - OEMCrypto_ERROR_NOT_IMPLEMENTED, - OEMCrypto_GenerateRSASignature(0, NULL, 0, NULL, NULL, kSign_RSASSA_PSS)); + ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, + OEMCrypto_LoadProvisioning(0, nullptr, 0, 0, nullptr, 0, nullptr, + nullptr)); } // The Generic Crypto API functions are required for Android. TEST_F(OEMCryptoAndroidLMPTest, GenericCryptoImplemented) { - ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, - OEMCrypto_Generic_Encrypt(0, NULL, 0, NULL, - OEMCrypto_AES_CBC_128_NO_PADDING, NULL)); - ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, - OEMCrypto_Generic_Decrypt(0, NULL, 0, NULL, - OEMCrypto_AES_CBC_128_NO_PADDING, NULL)); ASSERT_NE( OEMCrypto_ERROR_NOT_IMPLEMENTED, - OEMCrypto_Generic_Sign(0, NULL, 0, OEMCrypto_HMAC_SHA256, NULL, NULL)); + OEMCrypto_Generic_Encrypt(0, nullptr, 0, nullptr, + OEMCrypto_AES_CBC_128_NO_PADDING, nullptr)); ASSERT_NE( OEMCrypto_ERROR_NOT_IMPLEMENTED, - OEMCrypto_Generic_Verify(0, NULL, 0, OEMCrypto_HMAC_SHA256, NULL, 0)); + OEMCrypto_Generic_Decrypt(0, nullptr, 0, nullptr, + OEMCrypto_AES_CBC_128_NO_PADDING, nullptr)); + ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, + OEMCrypto_Generic_Sign(0, nullptr, 0, OEMCrypto_HMAC_SHA256, + nullptr, nullptr)); + ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, + OEMCrypto_Generic_Verify(0, nullptr, 0, OEMCrypto_HMAC_SHA256, + nullptr, 0)); } // Android requires support of usage table. The usage table is used for Secure @@ -134,15 +123,15 @@ TEST_F(OEMCryptoAndroidMNCTest, LoadsTestKeyboxImplemented) { // Android requires implementation of these functions. TEST_F(OEMCryptoAndroidMNCTest, NumberOfSessionsImplemented) { ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, - OEMCrypto_GetNumberOfOpenSessions(NULL)); + OEMCrypto_GetNumberOfOpenSessions(nullptr)); ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, - OEMCrypto_GetMaxNumberOfSessions(NULL)); + OEMCrypto_GetMaxNumberOfSessions(nullptr)); } // Android requires implementation of these functions. TEST_F(OEMCryptoAndroidMNCTest, QueryKeyControlImplemented) { ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, - OEMCrypto_QueryKeyControl(0, NULL, 0, NULL, NULL)); + OEMCrypto_QueryKeyControl(0, nullptr, 0, nullptr, nullptr)); } // These tests are required for N Android devices. diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test_main.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test_main.cpp index 2b5f40cb..1cf4a598 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test_main.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test_main.cpp @@ -4,6 +4,7 @@ #include "OEMCryptoCENC.h" #include "log.h" #include "oec_device_features.h" +#include "test_sleep.h" static void acknowledge_cast() { std::cout @@ -16,9 +17,7 @@ static void acknowledge_cast() { // because we need to initialize the list of features supported by the device. // Also, the test filter is updated based on the feature list. int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); bool is_cast_receiver = false; - bool force_load_test_keybox = false; bool filter_tests = true; int verbosity = 0; // Skip the first element, which is the program name. @@ -31,14 +30,21 @@ int main(int argc, char** argv) { is_cast_receiver = true; } if (arg == "--force_load_test_keybox") { - force_load_test_keybox = true; + std::cerr << "The argument --force_load_test_keybox is obsolete.\n"; + return 1; } if (arg == "--no_filter") { filter_tests = false; } + if (arg == "--fake_sleep") { + wvcdm::TestSleep::set_real_sleep(false); + } } wvcdm::g_cutoff = static_cast(verbosity); - wvoec::global_features.Initialize(is_cast_receiver, force_load_test_keybox); + wvoec::global_features.Initialize(); + wvoec::global_features.set_cast_receiver(is_cast_receiver); + // Init GTest after device properties has been initialized. + ::testing::InitGoogleTest(&argc, argv); // If the user requests --no_filter, we don't change the filter, otherwise, we // filter out features that are not supported. if (filter_tests) { diff --git a/libwvdrmengine/run_all_unit_tests.sh b/libwvdrmengine/run_all_unit_tests.sh index 968c2da4..9d550312 100755 --- a/libwvdrmengine/run_all_unit_tests.sh +++ b/libwvdrmengine/run_all_unit_tests.sh @@ -124,6 +124,7 @@ adb_shell_run libwvdrmmediacrypto_hidl_test adb_shell_run libwvdrmmediacrypto_test adb_shell_run license_keys_unittest adb_shell_run license_unittest +adb_shell_run odk_test adb_shell_run policy_engine_constraints_unittest adb_shell_run policy_engine_unittest adb_shell_run rw_lock_test diff --git a/libwvdrmengine/tools/metrics_dump/src/wv_metrics.cpp b/libwvdrmengine/tools/metrics_dump/src/wv_metrics.cpp index 208bdc64..4d21001f 100644 --- a/libwvdrmengine/tools/metrics_dump/src/wv_metrics.cpp +++ b/libwvdrmengine/tools/metrics_dump/src/wv_metrics.cpp @@ -10,8 +10,8 @@ #include #include -#include "metrics.pb.h" #include "OEMCryptoCENC.h" +#include "metrics.pb.h" #include "test_printers.h" #include "wv_cdm_types.h"