From 41829ca1e531d8f354806c8bd013be8305465b5f Mon Sep 17 00:00:00 2001 From: Lu Chen Date: Tue, 25 Feb 2025 13:49:37 -0800 Subject: [PATCH] Add Provisioning 4 support Widevine provisioning 4 support is added in this patch. --- oemcrypto/include/OEMCryptoCENC.h | 126 ++++- oemcrypto/include/level3.h | 15 +- plugin/Android.bp | 3 + plugin/include/cas_license.h | 40 +- plugin/include/cas_status.h | 3 + plugin/include/cas_types.h | 6 +- plugin/include/crypto_session.h | 32 +- plugin/include/crypto_wrapped_key.h | 50 ++ plugin/include/file_util.h | 35 ++ plugin/include/oemcrypto_interface.h | 16 +- plugin/include/policy_engine.h | 6 +- plugin/include/privacy_crypto.h | 86 ++++ plugin/include/service_certificate.h | 64 +++ plugin/include/widevine_cas_api.h | 2 +- plugin/include/widevine_media_cas_plugin.h | 3 +- plugin/src/cas_license.cpp | 555 ++++++++++++++++----- plugin/src/crypto_session.cpp | 308 ++++++++++-- plugin/src/file_util.cpp | 270 ++++++++++ plugin/src/oemcrypto_interface.cpp | 60 ++- plugin/src/policy_engine.cpp | 4 +- plugin/src/privacy_crypto_boringssl.cpp | 489 ++++++++++++++++++ plugin/src/service_certificate.cpp | 284 +++++++++++ plugin/src/widevine_cas_api.cpp | 86 +--- plugin/src/widevine_media_cas_plugin.cpp | 15 +- protos/device_files.proto | 22 +- protos/license_protocol.proto | 196 ++++++++ tests/src/cas_license_test.cpp | 244 +++++++-- tests/src/crypto_session_test.cpp | 29 +- tests/src/mock_crypto_session.h | 31 +- tests/src/mock_file.h | 41 ++ tests/src/test_properties.cpp | 9 + tests/src/widevine_cas_api_test.cpp | 15 +- wvutil/include/cas_properties.h | 3 + wvutil/include/file_store.h | 91 +++- wvutil/include/wv_attributes.h | 8 + wvutil/src/android_properties.cpp | 8 + wvutil/src/string_conversions.cpp | 16 +- 37 files changed, 2915 insertions(+), 356 deletions(-) create mode 100644 plugin/include/crypto_wrapped_key.h create mode 100644 plugin/include/file_util.h create mode 100644 plugin/include/privacy_crypto.h create mode 100644 plugin/include/service_certificate.h create mode 100644 plugin/src/file_util.cpp create mode 100644 plugin/src/privacy_crypto_boringssl.cpp create mode 100644 plugin/src/service_certificate.cpp create mode 100644 tests/src/mock_file.h diff --git a/oemcrypto/include/OEMCryptoCENC.h b/oemcrypto/include/OEMCryptoCENC.h index 7a10cfc..7c27ae0 100644 --- a/oemcrypto/include/OEMCryptoCENC.h +++ b/oemcrypto/include/OEMCryptoCENC.h @@ -3,7 +3,7 @@ // License Agreement. /** - * @mainpage OEMCrypto API v18.4 + * @mainpage OEMCrypto API v18.8 * * OEMCrypto is the low level library implemented by the OEM to provide key and * content protection, usually in a separate secure memory or process space. The @@ -718,6 +718,10 @@ typedef enum OEMCrypto_SignatureHashAlgorithm { #define OEMCrypto_GetDeviceSignedCsrPayload _oecc141 #define OEMCrypto_FactoryInstallBCCSignature _oecc142 #define OEMCrypto_GetEmbeddedDrmCertificate _oecc143 +#define OEMCrypto_UseSecondaryKey _oecc144 +#define OEMCrypto_MarkOfflineSession _oecc153 +#define OEMCrypto_WrapClearPrivateKey _oecc154 +#define OEMCrypto_SetSessionUsage _oecc155 // clang-format on /// @addtogroup initcontrol @@ -1938,6 +1942,33 @@ OEMCryptoResult OEMCrypto_GetOEMKeyToken(OEMCrypto_SESSION key_session, uint8_t* key_token, size_t* key_token_length); +/** + * Sets the session's usage information and scrambling mode, allowing the + * descrambler to be set up to decode one or more streams encrypted by the + * Conditional Access System (CAS). This method is currently used exclusively by + * CAS. + * + * @param[in] session: session id. + * @param[in] intent: session usage information. A constant defined by MediaCaS. + * @param[in] mode: scrambling mode. A constant defined by MediaCaS. + * + * @retval OEMCrypto_SUCCESS on success + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * + * @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 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 19. + */ +OEMCryptoResult OEMCrypto_SetSessionUsage(OEMCrypto_SESSION session, + uint32_t intent, uint32_t mode); + /// @} /// @addtogroup decryption @@ -2233,10 +2264,20 @@ OEMCryptoResult OEMCrypto_GetKeyHandle(OEMCrypto_SESSION session, * 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 skip field of OEMCrypto_CENCEncryptPatternDesc may 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, except for any partial crypto + * blocks at the end. The most common pattern with a skip field of zero is + * (10,0), but all patterns with a skip field of zero are functionally the same. + * + * If the skip field of OEMCrypto_CENCEncryptPatternDesc is zero, the encrypt + * field may also be zero. This pattern sometimes appears in content, + * particularly in audio tracks. This (0,0) pattern should be treated as + * equivalent to the pattern (10,0). e.g. All complete crypto blocks should be + * decrypted. + * + * It is not valid for the encrypt field of OEMCrypto_CENCEncryptPatternDesc to + * be zero if the skip field is non-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 @@ -3139,6 +3180,51 @@ OEMCryptoResult OEMCrypto_IsKeyboxOrOEMCertValid(void); OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* device_id, size_t* device_id_length); +/** + * Encrypts a clear device RSA/ECC key with an internal key (such as the OEM + * key or Widevine Keybox key) and a generated IV using AES-128-CBC with PKCS#5 + * padding. + * + * Copies the wrapped key to the buffer specified by |wrapped_private_key| and + * sets the size of the wrapped key to |wrapped_private_key_length|. + * + * The clear private key is encoded in PKCS#8 binary DER format. The OEMCrypto + * library shall verify that this RSA key is valid. + * + * The clear key should be encrypted using the same device specific key used in + * OEMCrypto_LoadProvisioning. The wrapped private key will be unwrapped in the + * function OEMCrypto_LoadDRMPrivateKey. + * + * This function should only be implemented for factory builds. + * + * @param[in] clear_private_key_bytes: pointer to memory containing the + * unencrypted private key data. + * @param[in] clear_private_key_length: the length of the private key data. + * @param[out] wrapped_private_key: pointer to buffer in which the encrypted + * private key should be stored. May be null on the first call in order to + * find required buffer size. + * @param[in,out] wrapped_private_key_length: (in) length of the encrypted + * private key, in bytes. (out) actual length of the encrypted private key, + * or required length if provided length is too small. + * + * @retval OEMCrypto_SUCCESS on success + * @retval OEMCrypto_ERROR_INVALID_CONTEXT clear_private_key_bytes is NULL, or + * clear private key fails to parse as PKCS#8 + * @retval OEMCrypto_ERROR_SHORT_BUFFER wrapped_private_key_length is too small, + * or wrapped_private_key is NULL + * + * @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 18.6. + */ +OEMCryptoResult OEMCrypto_WrapClearPrivateKey( + const uint8_t* clear_private_key_bytes, size_t clear_private_key_length, + uint8_t* wrapped_private_key, size_t* wrapped_private_key_length); + /// @} /// @addtogroup keybox @@ -5862,6 +5948,36 @@ OEMCryptoResult OEMCrypto_Generic_Verify_V17( OEMCryptoResult OEMCrypto_GetEmbeddedDrmCertificate(uint8_t* public_cert, size_t* public_cert_length); +/** + * Marks the given session as using a secondary key. + * + * @param[in] session_id: handle for the session to be used. + * @param[in] dual_key: whether this session uses a secondary key. + * + * @ignore + * @retval OEMCrypto_SUCCESS on success + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_SESSION_STATE_LOST + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + */ +OEMCryptoResult OEMCrypto_UseSecondaryKey(OEMCrypto_SESSION session_id, + bool dual_key); + +/** + * Marks the given session as being used for existing offline licenses. + * + * @param[in] session: session id for operation. + * + * @ignore + * @retval OEMCrypto_SUCCESS on success + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_SESSION_STATE_LOST + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + */ +OEMCryptoResult OEMCrypto_MarkOfflineSession(OEMCrypto_SESSION session); + #ifdef __cplusplus } #endif diff --git a/oemcrypto/include/level3.h b/oemcrypto/include/level3.h index 826c657..3085523 100644 --- a/oemcrypto/include/level3.h +++ b/oemcrypto/include/level3.h @@ -117,7 +117,11 @@ #define Level3_GetSignatureHashAlgorithm _lcc139 #define Level3_EnterTestMode _lcc140 #define Level3_GetDeviceSignedCsrPayload _lcc141 +#define Level3_UseSecondaryKey _lcc142 #define Level3_GetEmbeddedDrmCertificate _lcc143 +#define Level3_MarkOfflineSession _lcc144 +// Added in OEMCrypto v19.3, but back ported to v18 +#define Level3_SetSessionUsage _lcc155 #else #define Level3_Initialize _oecc01 #define Level3_Terminate _oecc02 @@ -220,6 +224,10 @@ #define Level3_GetDeviceSignedCsrPayload _oecc141 // Internal-only. #define Level3_GetEmbeddedDrmCertificate _oecc143 +#define Level3_UseSecondaryKey _oecc144 +#define Level3_MarkOfflineSession _oecc145 +// Added in OEMCrypto v19.3, but back ported to v18 +#define Level3_SetSessionUsage _oecc155 #endif #define Level3_GetInitializationState _oecl3o01 @@ -444,6 +452,8 @@ OEMCrypto_WatermarkingSupport Level3_GetWatermarkingSupport(); OEMCryptoResult Level3_GetOEMKeyToken(OEMCrypto_SESSION key_session, uint8_t* key_token, size_t* key_token_length); +OEMCryptoResult Level3_SetSessionUsage(OEMCrypto_SESSION session, + uint32_t intent, uint32_t mode); OEMCryptoResult Level3_GetDeviceInformation(uint8_t* device_info, size_t* device_info_length); OEMCryptoResult Level3_GetDeviceSignedCsrPayload( @@ -493,7 +503,10 @@ OEMCryptoResult Level3_GetSignatureHashAlgorithm( OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm); OEMCryptoResult Level3_EnterTestMode(void); OEMCryptoResult Level3_GetEmbeddedDrmCertificate(uint8_t* public_cert, - size_t* public_cert_length); + size_t* public_cert_length); +OEMCryptoResult Level3_UseSecondaryKey(OEMCrypto_SESSION session_id, + bool dual_key); +OEMCryptoResult Level3_MarkOfflineSession(OEMCrypto_SESSION session_id); // The following are specific to Google's Level 3 implementation and are not // required. diff --git a/plugin/Android.bp b/plugin/Android.bp index f64f89c..e463709 100644 --- a/plugin/Android.bp +++ b/plugin/Android.bp @@ -13,12 +13,15 @@ cc_library_static { "src/ecm_parser_v2.cpp", "src/ecm_parser_v3.cpp", "src/emm_parser.cpp", + "src/file_util.cpp", "src/license_key_status.cpp", "src/oemcrypto_interface.cpp", "src/policy_engine.cpp", "src/widevine_cas_session.cpp", "src/widevine_media_cas_plugin.cpp", "src/widevine_cas_api.cpp", + "src/privacy_crypto_boringssl.cpp", + "src/service_certificate.cpp", ], proprietary: true, shared_libs: [ diff --git a/plugin/include/cas_license.h b/plugin/include/cas_license.h index b79acc3..cc0dc61 100644 --- a/plugin/include/cas_license.h +++ b/plugin/include/cas_license.h @@ -10,7 +10,10 @@ #include "cas_status.h" #include "crypto_session.h" +#include "crypto_wrapped_key.h" +#include "file_store.h" #include "policy_engine.h" +#include "service_certificate.h" #include "timer.h" namespace wvcas { @@ -28,32 +31,33 @@ class CasLicense : public wvutil::TimerHandler, public wvcas::CasEventListener { // Generate a request to obtain a device certificate for requesting // entitlements. The generated message is set in |provisioning_request|. virtual CasStatus GenerateDeviceProvisioningRequest( - std::string* provisioning_request) const; + wvutil::FileSystem& file_system, std::string* provisioning_request); // Process a server response containing a device certificate for use in // requesting entitlements. The contained certificate data will be extracted // and wrapped for storage. The public key cert will be returned in // |device_certificate|. The private key information will be wrapped by the - // crypto session and returned in |wrapped_rsa_key|. + // crypto session and returned in |wrapped_private_key|. // A secure binary file image containing the device cert is returned in // |device_file| if not nullptr. This file is suitable for storage on a device virtual CasStatus HandleDeviceProvisioningResponse( + wvutil::FileSystem* file_system, const std::string& signed_provisioning_response, - std::string* device_certificate, std::string* wrapped_rsa_key, - std::string* device_file) const; + std::string* device_certificate, + CryptoWrappedKey* wrapped_private_key) const; // Generate a request to obtain an EMM (Entitlement Management Message) to // use to enable processing of ECM(s) (Encryption Management Message). // |init_data| is widevine metadata about the stream needed in the request. - // |wrapped_rsa_key| and |signed_license_request| are the device certificate + // |private_key| and |signed_license_request| are the device certificate // obtained by HandleDeviceProvisioningResponse. virtual CasStatus GenerateEntitlementRequest( const std::string& init_data, const std::string& device_certificate, - const std::string& wrapped_rsa_key, LicenseType license_type, + const CryptoWrappedKey& private_key, LicenseType license_type, std::string* signed_license_request); // Restores a stored license making the keys available for use. - virtual CasStatus HandleStoredLicense(const std::string& wrapped_rsa_key, + virtual CasStatus HandleStoredLicense(const CryptoWrappedKey& private_key, const std::string& license_file); // Process a server response containing a EMM for use in the processing of @@ -68,7 +72,7 @@ class CasLicense : public wvutil::TimerHandler, public wvcas::CasEventListener { // for use in an EMM request. virtual CasStatus HandleStoredDrmCert(const std::string& certificate, std::string* device_certificate, - std::string* wrapped_rsa_key); + CryptoWrappedKey* private_key); // Generate an entitlement renewal request message in // |signed_renewal_request|. @@ -156,6 +160,19 @@ class CasLicense : public wvutil::TimerHandler, public wvcas::CasEventListener { private: CasStatus GenerateDeviceProvisioningRequestWithKeybox( std::string* provisioning_request) const; + CasStatus GetProvisioning40RequestInternal( + wvutil::FileSystem& file_system, + std::string* serialized_provisioning_request); + CasStatus FillEncryptedClientId( + const std::string& client_token, + video_widevine::ProvisioningRequest& provisioning_request, + const ServiceCertificate& service_certificate) const; + void FillClientProperties( + video_widevine::ClientIdentification& client_id) const; + CasStatus HandleProvisioning40Response( + wvutil::FileSystem* file_system, + const video_widevine::SignedProvisioningMessage& signed_response, + std::string* cert, CryptoWrappedKey* wrapped_key) const; CasStatus GenerateDeviceProvisioningRequestWithOEMCert() const; CasStatus InstallLicense(const std::string& session_key, const std::string& serialized_license, @@ -177,6 +194,13 @@ class CasLicense : public wvutil::TimerHandler, public wvcas::CasEventListener { std::string renewal_response_; std::string init_data_; bool is_renewal_in_license_file_ = false; + std::unique_ptr wv_service_cert_; + // The wrapped private key in provisioning 4 generated by calling + // GenerateCertificateKeyPair. It will be saved to file system if a valid + // response is received. + std::string provisioning_40_wrapped_private_key_; + // Key type of the generated key pair in provisioning 4. + CryptoWrappedKey::Type provisioning_40_key_type_; }; } // namespace wvcas diff --git a/plugin/include/cas_status.h b/plugin/include/cas_status.h index be8ec1a..4890135 100644 --- a/plugin/include/cas_status.h +++ b/plugin/include/cas_status.h @@ -29,6 +29,9 @@ enum class CasStatusCode : int32_t { kAccessDeniedByParentalControl = 15, kUnknownEvent = 16, kOEMCryptoVersionMismatch = 17, + kDeviceCertificateError = 18, + kClientIdEncryptionError = 19, + kProvisioningError = 20, }; class CasStatus { diff --git a/plugin/include/cas_types.h b/plugin/include/cas_types.h index bfa5370..4a220d8 100644 --- a/plugin/include/cas_types.h +++ b/plugin/include/cas_types.h @@ -24,10 +24,10 @@ enum class LicenseType { typedef enum { ProvisioningError = 0, // Device cannot be provisioned. - DrmCertificate = 1, // Device has baked in DRM certificate - // (level 3 only) + DrmCertificate = 1, // Device has baked in DRM certificate (level 3 only) Keybox = 2, // Device has factory installed unique keybox. - OEMCertificate = 3 // Device has factory installed OEM certificate. + OEMCertificate = 3, // Device has factory installed OEM certificate. + BootCertificateChain = 4, // Provisioning 4.0 } CasProvisioningMethod; enum class CryptoMode { diff --git a/plugin/include/crypto_session.h b/plugin/include/crypto_session.h index 60cef05..e42dfd1 100644 --- a/plugin/include/crypto_session.h +++ b/plugin/include/crypto_session.h @@ -13,6 +13,7 @@ #include "OEMCryptoCENC.h" #include "cas_status.h" #include "cas_types.h" +#include "crypto_wrapped_key.h" #include "oemcrypto_interface.h" #include "rw_lock.h" @@ -36,7 +37,7 @@ typedef OEMCrypto_HDCP_Capability HdcpCapability; class CryptoLock { public: - CryptoLock(){}; + CryptoLock() {}; // These methods should be used to take the various CryptoSession mutexes in // preference to taking the mutexes directly. // @@ -175,7 +176,21 @@ class CryptoInterface { OEMCrypto_SESSION key_session, uint8_t* key_token, size_t* key_token_length); virtual OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm( - OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm); + OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm); + virtual OEMCryptoResult OEMCrypto_GetBootCertificateChain( + uint8_t* bcc, size_t* bcc_length, uint8_t* additional_signature, + size_t* additional_signature_length); + virtual OEMCryptoResult OEMCrypto_GenerateCertificateKeyPair( + OEMCrypto_SESSION session, uint8_t* public_key, size_t* public_key_length, + uint8_t* public_key_signature, size_t* public_key_signature_length, + uint8_t* wrapped_private_key, size_t* wrapped_private_key_length, + OEMCrypto_PrivateKeyType* key_type); + virtual OEMCryptoResult OEMCrypto_InstallOemPrivateKey( + OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type, + const uint8_t* wrapped_private_key, size_t wrapped_private_key_length); + virtual uint8_t OEMCrypto_Security_Patch_Level(); + virtual OEMCryptoResult OEMCrypto_BuildInformation(char* buffer, + size_t* buffer_length); // This is the factory method used to enable the oemcrypto interface. static OEMCryptoResult create(std::unique_ptr* init) { @@ -252,8 +267,6 @@ class CryptoSession { std::string* wrapped_private_key); virtual CasStatus GetOEMPublicCertificate(uint8_t* public_cert, size_t* public_cert_length); - virtual CasStatus LoadDeviceRSAKey(const uint8_t* wrapped_rsa_key, - size_t wrapped_rsa_key_length); virtual CasStatus GenerateRSASignature(const uint8_t* message, size_t message_length, uint8_t* signature, @@ -291,6 +304,17 @@ class CryptoSession { virtual CasStatus APIVersion(uint32_t* api_version); virtual CasStatus GetOEMKeyToken(OEMCrypto_SESSION entitled_key_session_id, std::vector& token); + virtual CasStatus GetBootCertificateChain(std::string* bcc, + std::string* additional_signature); + virtual CasStatus GenerateCertificateKeyPair( + std::string* public_key, std::string* public_key_signature, + std::string* wrapped_private_key, CryptoWrappedKey::Type* key_type); + virtual CasStatus LoadOemCertificatePrivateKey( + const CryptoWrappedKey& private_key); + virtual CasStatus LoadCertificatePrivateKey( + const CryptoWrappedKey& private_key); + virtual uint8_t GetSecurityPatchLevel(); + virtual bool GetBuildInformation(std::string* info); CryptoSession(const CryptoSession&) = delete; CryptoSession& operator=(const CryptoSession&) = delete; diff --git a/plugin/include/crypto_wrapped_key.h b/plugin/include/crypto_wrapped_key.h new file mode 100644 index 0000000..355ad8f --- /dev/null +++ b/plugin/include/crypto_wrapped_key.h @@ -0,0 +1,50 @@ +// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WIDEVINE_CAS_CRYPTO_WRAPPED_KEY_H_ +#define WIDEVINE_CAS_CRYPTO_WRAPPED_KEY_H_ + +#include + + +namespace wvcas { +// Represents OEMCrypto's wrapped private DRM key. As of v16, it is +// possible for OEMCrypto to support ECC-based DRM certificates. The +// format of the wrapped key is vendor specific; however, the API +// requires that the CDM track whether the wrapped key is RSA or ECC. +class CryptoWrappedKey { + public: + enum Type : int32_t { kUninitialized = 0, kRsa = 1, kEcc = 2 }; + CryptoWrappedKey() = default; + CryptoWrappedKey(Type type, const std::string& key) + : type_(type), key_(key) {} + + Type type() const { return type_; } + void set_type(Type type) { type_ = type; } + + const std::string& key() const { return key_; } + // Mutable reference getter for passing to OMECrypto. + std::string& key() { return key_; } + void set_key(const std::string& key) { key_ = key; } + + void Clear() { + type_ = kUninitialized; + key_.clear(); + } + // A valid key must have a known key type and have key data. + bool IsValid() const { return type_ != kUninitialized && !key_.empty(); } + // Equality operator is for testing only. Real keys may have + // different meta data but the same logical key. + bool IsEqualTo(const CryptoWrappedKey& other) const { + return type_ == other.type_ && key_ == other.key_; + } + + private: + // DRM key type of the wrapped key. For wrapped keys which the type + // of key is unknown, assume it to be RSA. + Type type_ = kUninitialized; + // Vendor-specific wrapped DRM key. + std::string key_; +}; // class CryptoWrappedKey +} // namespace wvcas +#endif // WIDEVINE_CAS_CRYPTO_WRAPPED_KEY_H_ \ No newline at end of file diff --git a/plugin/include/file_util.h b/plugin/include/file_util.h new file mode 100644 index 0000000..ff39dd7 --- /dev/null +++ b/plugin/include/file_util.h @@ -0,0 +1,35 @@ +// Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. +#ifndef FILE_UTIL_H_ +#define FILE_UTIL_H_ + +#include + +#include "cas_status.h" +#include "crypto_wrapped_key.h" +#include "device_files.pb.h" +#include "file_store.h" + +namespace wvcas { +bool ReadFileFromStorage(wvutil::FileSystem& file_system, + const std::string& filename, std::string* file_data); +bool RemoveFile(wvutil::FileSystem& file_system, const std::string& filename); +bool StoreFile(wvutil::FileSystem& file_system, const std::string& filename, + const std::string& file_data); +bool StoreFileWithHash(wvutil::FileSystem& file_system, const std::string& name, + const std::string& serialized_file); +bool StoreOemCertificate(wvutil::FileSystem& file_system, + const std::string& certificate, + const CryptoWrappedKey& private_key); +bool StoreCertificate(wvutil::FileSystem& file_system, + const std::string& certificate, + const CryptoWrappedKey& private_key); +CasStatus RetrieveHashedFile(const std::string& certificate, + video_widevine_client::sdk::File& file); +bool RetrieveOemCertificate(wvutil::FileSystem& file_system, + std::string& certificate, + CryptoWrappedKey* wrapped_private_key); + +} // namespace wvcas +#endif // FILE_UTIL_H_ diff --git a/plugin/include/oemcrypto_interface.h b/plugin/include/oemcrypto_interface.h index 62dbc5f..fe3604e 100644 --- a/plugin/include/oemcrypto_interface.h +++ b/plugin/include/oemcrypto_interface.h @@ -21,7 +21,7 @@ struct InputStreamParams { size_t data_length; bool is_encrypted; - InputStreamParams(){}; + InputStreamParams() {}; InputStreamParams(const uint8_t* data_addr, size_t data_length, bool is_encrypted) : data_addr(data_addr), @@ -124,6 +124,20 @@ class OEMCryptoInterface { virtual OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm( OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm) const; + virtual OEMCryptoResult OEMCrypto_GetBootCertificateChain( + uint8_t* bcc, size_t* bcc_length, uint8_t* additional_signature, + size_t* additional_signature_length); + virtual OEMCryptoResult OEMCrypto_GenerateCertificateKeyPair( + OEMCrypto_SESSION session, uint8_t* public_key, size_t* public_key_length, + uint8_t* public_key_signature, size_t* public_key_signature_length, + uint8_t* wrapped_private_key, size_t* wrapped_private_key_length, + OEMCrypto_PrivateKeyType* key_type); + virtual OEMCryptoResult OEMCrypto_InstallOemPrivateKey( + OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type, + const uint8_t* wrapped_private_key, size_t wrapped_private_key_length); + virtual uint8_t OEMCrypto_Security_Patch_Level(); + virtual OEMCryptoResult OEMCrypto_BuildInformation(char* buffer, + size_t* buffer_length); OEMCryptoInterface(const OEMCryptoInterface&) = delete; OEMCryptoInterface& operator=(const OEMCryptoInterface&) = delete; diff --git a/plugin/include/policy_engine.h b/plugin/include/policy_engine.h index f4419ab..33a254e 100644 --- a/plugin/include/policy_engine.h +++ b/plugin/include/policy_engine.h @@ -108,9 +108,9 @@ class PolicyEngine : public wvutil::TimerHandler { int64_t GetPlaybackStartTime() const { return playback_start_time_; } int64_t GetLastPlaybackTime() const { return last_playback_time_; } int64_t GetGracePeriodEndTime() const { return grace_period_end_time_; } - void RestorePlaybackTimes(int64_t playback_start_time, - int64_t last_playback_time, - int64_t grace_period_end_time); + virtual void RestorePlaybackTimes(int64_t playback_start_time, + int64_t last_playback_time, + int64_t grace_period_end_time); PolicyEngine(const PolicyEngine&) = delete; PolicyEngine& operator=(const PolicyEngine&) = delete; diff --git a/plugin/include/privacy_crypto.h b/plugin/include/privacy_crypto.h new file mode 100644 index 0000000..de67966 --- /dev/null +++ b/plugin/include/privacy_crypto.h @@ -0,0 +1,86 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Description: +// Declaration of classes representing AES and RSA public keys used +// for signature verification and encryption. +// +// AES encryption details: +// Algorithm: AES-CBC +// +// RSA signature details: +// Algorithm: RSASSA-PSS +// Hash algorithm: SHA1 +// Mask generation function: mgf1SHA1 +// Salt length: 20 bytes +// Trailer field: 0xbc +// +// RSA encryption details: +// Algorithm: RSA-OAEP +// Mask generation function: mgf1SHA1 +// Label (encoding paramter): empty string +// +#ifndef WIDEVINE_CAS_PRIVACY_CRYPTO_H_ +#define WIDEVINE_CAS_PRIVACY_CRYPTO_H_ + +#include +#include + +namespace wvcas { + +class AesCbcKey { + public: + AesCbcKey(); + ~AesCbcKey(); + + bool Init(const std::string& key); + bool Encrypt(const std::string& in, const std::string& iv, std::string* out, + bool has_padding = true); + bool Decrypt(const std::string& in, const std::string& iv, std::string* out, + bool has_padding = true); + + private: + std::string key_; +}; // class AesCbcKey + +class RsaPublicKey { + public: + RsaPublicKey(); + ~RsaPublicKey(); + + // Initializes an RsaPublicKey object using a DER encoded PKCS#1 RSAPublicKey + bool Init(const std::string& serialized_key); + + // Encrypt a message using RSA-OAEP. Caller retains ownership of all + // parameters. Returns true if successful, false otherwise. + bool Encrypt(const std::string& plaintext, std::string* ciphertext); + + // Verify RSASSA-PSS signature. Caller retains ownership of all parameters. + // Returns true if validation succeeds, false otherwise. + bool VerifySignature(const std::string& message, + const std::string& signature); + + private: + std::string serialized_key_; +}; // class RsaPublicKey + +/** + * Extracts an integer value from the extensions in a certificate. + * @param cert A PKCS7 encoded X.509 certificate chain. + * @param extension_oid The ID of the extension to get. + * @param cert_index The zero-based index of the certificate in the chain to + * fetch from. + * @param value [OUT] Will contain the extracted value. + * @return True on success, false on error. + */ +bool ExtractExtensionValueFromCertificate(const std::string& cert, + const std::string& extension_oid, + size_t cert_index, uint32_t* value); + +std::string Md5Hash(const std::string& data); +std::string Sha1Hash(const std::string& data); +std::string Sha256Hash(const std::string& data); +std::string Sha512Hash(const std::string& data); +} // namespace wvcas +#endif // WIDEVINE_CAS_PRIVACY_CRYPTO_H_ \ No newline at end of file diff --git a/plugin/include/service_certificate.h b/plugin/include/service_certificate.h new file mode 100644 index 0000000..8c8be9b --- /dev/null +++ b/plugin/include/service_certificate.h @@ -0,0 +1,64 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WIDEVINE_CAS_SERVICE_CERTIFICATE_H_ +#define WIDEVINE_CAS_SERVICE_CERTIFICATE_H_ + +#include +#include + +#include "cas_status.h" +#include "license_protocol.pb.h" +#include "privacy_crypto.h" + +namespace wvcas { + +// Service Certificates are used to encrypt the ClientIdentification message +// that is part of Device Provisioning, License, Renewal, and Release requests. +class ServiceCertificate { + public: + ServiceCertificate() = default; + virtual ~ServiceCertificate() {} + + // Set up a new service certificate. + // Accept a serialized video_widevine::SignedDrmDeviceCertificate message. + virtual CasStatus Init(const std::string& signed_certificate); + + bool HasSignedCertificate() const { return !signed_certificate_.empty(); } + const std::string& signed_certificate() const { return signed_certificate_; } + const std::string& provider_id() const { return provider_id_; } + + // Encrypt the ClientIdentification message for a provisioning or + // licensing request. Encryption is performed using the current + // service certificate. Return a failure if the service certificate is + // not present, not valid, or if some other error occurs. + // The routine should not be called if privacy mode is off or if the + // certificate is empty. + CasStatus EncryptClientId( + const video_widevine::ClientIdentification* clear_client_id, + video_widevine::EncryptedClientIdentification* encrypted_client_id) const; + + private: + // Encrypt data using RSA with OAEP padding. + // |plaintext| is the data to be encrypted. |ciphertext| is a pointer to a + // string to contain the decrypted data on return, and may not be null. + // returns NO_ERROR if successful or an appropriate error code otherwise. + virtual CasStatus EncryptRsaOaep(const std::string& plaintext, + std::string* ciphertext) const; + + // Proto serialized SignedDrmCertificate. + // Verified by Init() to be valid. + std::string signed_certificate_; + + // Certificate serial number. + std::string serial_number_; + + // Provider ID, extracted from certificate message. + std::string provider_id_; + + // Public key. + std::unique_ptr public_key_; +}; // class ServiceCertificate + +} // namespace wvcas +#endif // WIDEVINE_CAS_SERVICE_CERTIFICATE_H_ \ No newline at end of file diff --git a/plugin/include/widevine_cas_api.h b/plugin/include/widevine_cas_api.h index 0d086f9..5e5cf9d 100644 --- a/plugin/include/widevine_cas_api.h +++ b/plugin/include/widevine_cas_api.h @@ -160,7 +160,7 @@ class WidevineCas : public wvutil::TimerHandler { std::unique_ptr cas_license_; std::unique_ptr file_system_; std::string device_certificate_; - std::string wrapped_rsa_key_; + std::unique_ptr wrapped_private_key_; CasEventListener* event_listener_ = nullptr; std::mutex lock_; wvutil::Timer policy_timer_; diff --git a/plugin/include/widevine_media_cas_plugin.h b/plugin/include/widevine_media_cas_plugin.h index c3cd8f3..9de06c9 100644 --- a/plugin/include/widevine_media_cas_plugin.h +++ b/plugin/include/widevine_media_cas_plugin.h @@ -91,7 +91,7 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener { std::unique_ptr widevine_cas_api) { widevine_cas_api_ = std::move(widevine_cas_api); } - WidevineCasPlugin(){}; + WidevineCasPlugin() {}; private: // |sessionId| is nullptr if the event is not a session event. @@ -107,6 +107,7 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener { CasStatus HandleAssignLicenseID(const CasData& license_id); CasStatus HandlePluginVersionQuery(); CasStatus HandleEntitlementPeriodUpdateResponse(const CasData& response); + status_t provisionInternal(); // Returns true if the device has been provisioned with a device certificate. bool is_provisioned() const; diff --git a/plugin/src/cas_license.cpp b/plugin/src/cas_license.cpp index dd0a70b..c73637a 100644 --- a/plugin/src/cas_license.cpp +++ b/plugin/src/cas_license.cpp @@ -15,32 +15,39 @@ #include "cas_properties.h" #include "cas_util.h" #include "crypto_session.h" +#include "crypto_wrapped_key.h" #include "device_files.pb.h" +#include "file_util.h" #include "license_protocol.pb.h" #include "log.h" +#include "service_certificate.h" #include "string_conversions.h" -using video_widevine_client::sdk::DeviceCertificate; -using video_widevine_client::sdk::File; -using video_widevine_client::sdk::HashedFile; -using wvutil::Base64SafeDecode; -using wvutil::Base64SafeEncodeNoPad; using video_widevine::CASDrmLicenseRequest; using video_widevine::ClientIdentification; using video_widevine::ClientIdentification_ClientCapabilities; using video_widevine:: -ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_2048; + ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_2048; using video_widevine:: -ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_3072; + ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_3072; using video_widevine::ClientIdentification_NameValue; +using video_widevine::DrmCertificate; using video_widevine::HashAlgorithmProto; using video_widevine::License; using video_widevine::License_KeyContainer; using video_widevine::LicenseRequest; using video_widevine::ProvisioningRequest; using video_widevine::ProvisioningResponse; +using video_widevine::PublicKeyToCertify; +using video_widevine::SignedDrmCertificate; using video_widevine::SignedMessage; using video_widevine::SignedProvisioningMessage; +using video_widevine_client::sdk::DeviceCertificate; +using video_widevine_client::sdk::File; +using video_widevine_client::sdk::HashedFile; +using video_widevine_client::sdk::OemCertificate; +using wvutil::Base64SafeDecode; +using wvutil::Base64SafeEncodeNoPad; static constexpr size_t kMacKeySizeBytes = 32; static constexpr size_t kMacKeyPaddingSizeBytes = 16; @@ -48,6 +55,7 @@ static constexpr size_t kIvSizeBytes = 16; static constexpr size_t kCertificateDataSizeBytes = 4 * 1024; static constexpr uint32_t kRsaSignatureSizeBytes = 256; static constexpr size_t kKeyboxTokenSizeBytes = 72; +constexpr uint32_t OEM_CRYPTO_API_VERSION_SUPPORTS_PROV40_CORE_MESSAGE = 18; namespace wvcas { @@ -57,10 +65,43 @@ namespace { constexpr char kKeyCompanyName[] = "company_name"; constexpr char kKeyModelName[] = "model_name"; +constexpr char kKeyModelYear[] = "model_year"; constexpr char kKeyArchitectureName[] = "architecture_name"; constexpr char kKeyDeviceName[] = "device_name"; constexpr char kKeyProductName[] = "product_name"; +constexpr char kKeyBuildInfo[] = "build_info"; constexpr char kKeyWvCasVersion[] = "widevine_cdm_version"; +constexpr char kKeyOemCryptoSecurityPatchLevel[] = + "oem_crypto_security_patch_level"; +constexpr char kKeyOemCryptoBuildInformation[] = "oem_crypto_build_information"; +constexpr char kBasePathPrefix[] = "/data/vendor/mediacas/IDM/widevine/"; +constexpr char kOemCertificateFileName[] = "oemcert.bin"; +constexpr char kOemCertificateFilePath[] = + "/data/vendor/mediacas/IDM/widevine/oemcert.bin"; + +const char kCpProductionServiceCertificate[] = + "0ab9020803121051434fe2a44c763bcc2c826a2d6ef9a718f7d793d005228e02" + "3082010a02820101009e27088659dbd9126bc6ed594caf652b0eaab82abb9862" + "ada1ee6d2cb5247e94b28973fef5a3e11b57d0b0872c930f351b5694354a8c77" + "ed4ee69834d2630372b5331c5710f38bdbb1ec3024cfadb2a8ac94d977d391b7" + "d87c20c5c046e9801a9bffaf49a36a9ee6c5163eff5cdb63bfc750cf4a218618" + "984e485e23a10f08587ec5d990e9ab0de71460dfc334925f3fb9b55761c61e28" + "8398c387a0925b6e4dcaa1b36228d9feff7e789ba6e5ef6cf3d97e6ae05525db" + "38f826e829e9b8764c9e2c44530efe6943df4e048c3c5900ca2042c5235dc80d" + "443789e734bf8e59a55804030061ed48e7d139b521fbf35524b3000b3e2f6de0" + "001f5eeb99e9ec635f02030100013a0c7769646576696e652e636f6d12800332" + "2c2f3fedc47f8b7ba88a135a355466e378ed56a6fc29ce21f0cafc7fb253b073" + "c55bed253d8650735417aad02afaefbe8d5687902b56a164490d83d590947515" + "68860e7200994d322b5de07f82ef98204348a6c2c9619092340eb87df26f63bf" + "56c191dc069b80119eb3060d771afaaeb2d30b9da399ef8a41d16f45fd121e09" + "a0c5144da8f8eb46652c727225537ad65e2a6a55799909bbfb5f45b5775a1d1e" + "ac4e06116c57adfa9ce0672f19b70b876f88e8b9fbc4f96ccc500c676cfb173c" + "b6f52601573e2e45af1d9d2a17ef1487348c05cfc6d638ec2cae3fadb655e943" + "1330a75d2ceeaa54803e371425111e20248b334a3a50c8eca683c448b8ac402c" + "76e6f76e2751fbefb669f05703cec8c64cf7a62908d5fb870375eb0cc96c508e" + "26e0c050f3fd3ebe68cef9903ef6405b25fc6e31f93559fcff05657662b3653a" + "8598ed5751b38694419242a875d9e00d5a5832933024b934859ec8be78adccbb" + "1ec7127ae9afeef9c5cd2e15bd3048e8ce652f7d8c5d595a0323238c598a28"; // TODO(jfore): These variables are currently unused and are flagged as build // errors in android. These values will be used in a future cl. @@ -218,6 +259,29 @@ bool OecAlgorithmToProtoAlgorithm( return false; } +// Retrieves |stored_oem_cert| from |file_handle|, and load the OEM private key +// to |crypto_session|. Returns true if all operations are successful. +bool RetrieveOemCertificateAndLoadPrivateKey(wvutil::FileSystem& file_system, + CryptoSession& crypto_session, + std::string& stored_oem_cert) { + stored_oem_cert.clear(); + CryptoWrappedKey wrapped_private_key; + if (!RetrieveOemCertificate(file_system, stored_oem_cert, + &wrapped_private_key)) { + LOGE("An invalid stored OEM certificated is retrieved"); + stored_oem_cert.clear(); + return false; + } + + if (crypto_session.LoadOemCertificatePrivateKey(wrapped_private_key) + .status_code() != CasStatusCode::kNoError) { + LOGE("Can not load the OEM private key"); + stored_oem_cert.clear(); + return false; + } + return true; +} + } // namespace CasStatus CasLicense::initialize(std::shared_ptr crypto_session, @@ -229,11 +293,18 @@ CasStatus CasLicense::initialize(std::shared_ptr crypto_session, policy_engine_->initialize(crypto_session, this); crypto_session_ = crypto_session; event_listener_ = listener; + wv_service_cert_ = std::make_unique(); + CasStatus status = + wv_service_cert_->Init(wvutil::a2bs_hex(kCpProductionServiceCertificate)); + if (!status.ok()) { + return status; + } return CasStatusCode::kNoError; } CasStatus CasLicense::GenerateDeviceProvisioningRequest( - std::string* serialized_provisioning_request) const { + wvutil::FileSystem& file_system, + std::string* serialized_provisioning_request) { if (!crypto_session_) { return CasStatus(CasStatusCode::kCasLicenseError, "no crypto session"); } @@ -247,6 +318,9 @@ CasStatus CasLicense::GenerateDeviceProvisioningRequest( case Keybox: return GenerateDeviceProvisioningRequestWithKeybox( serialized_provisioning_request); + case BootCertificateChain: + return GetProvisioning40RequestInternal(file_system, + serialized_provisioning_request); default: return CasStatus(CasStatusCode::kCasLicenseError, "unknown provisioning method"); @@ -256,10 +330,11 @@ CasStatus CasLicense::GenerateDeviceProvisioningRequest( } CasStatus CasLicense::HandleDeviceProvisioningResponse( + wvutil::FileSystem* file_system, const std::string& signed_provisioning_response, - std::string* device_certificate, std::string* wrapped_rsa_key, - std::string* device_file) const { - if (nullptr == device_certificate || nullptr == wrapped_rsa_key) { + std::string* device_certificate, + CryptoWrappedKey* wrapped_private_key) const { + if (device_certificate == nullptr || wrapped_private_key == nullptr) { return CasStatus(CasStatusCode::kCasLicenseError, "missing device credentials"); } @@ -278,6 +353,13 @@ CasStatus CasLicense::HandleDeviceProvisioningResponse( return CasStatus(CasStatusCode::kIndividualizationError, "unable to parse response"); } + + if (signed_response.protocol_version() == + SignedProvisioningMessage::PROVISIONING_40) { + return HandleProvisioning40Response( + file_system, signed_response, device_certificate, wrapped_private_key); + } + if (!signed_response.has_signature()) { return CasStatus(CasStatusCode::kIndividualizationError, "signature not found"); @@ -303,37 +385,175 @@ CasStatus CasLicense::HandleDeviceProvisioningResponse( "unable to parse signed message"); } + wrapped_private_key->Clear(); CasStatus status = crypto_session_->LoadProvisioning( - signed_message, core_message, signature, wrapped_rsa_key); + signed_message, core_message, signature, &wrapped_private_key->key()); if (status.status_code() != CasStatusCode::kNoError) { return CasStatus(CasStatusCode::kIndividualizationError, ("rewrapCertificate fails")); } - if (nullptr != device_file) { - File file; - file.set_type(File::DEVICE_CERTIFICATE); - file.mutable_device_certificate()->set_certificate( - provisioning_response.device_certificate()); - file.mutable_device_certificate()->set_wrapped_private_key( - *wrapped_rsa_key); - - HashedFile hash_file; - file.SerializeToString(hash_file.mutable_file()); - if (!Hash(hash_file.file(), hash_file.mutable_hash())) { - return CasStatus(CasStatusCode::kIndividualizationError, - ("generating file hash fails")); + // This is the entire certificate (SignedDrmCertificate). + const std::string& device_cert_data = + provisioning_response.device_certificate(); + // Need to parse cert for key type. + SignedDrmCertificate signed_device_cert; + if (!signed_device_cert.ParseFromString(device_cert_data)) { + LOGE("Failed to parse signed DRM certificate"); + return CasStatus(CasStatusCode::kIndividualizationError, + "Failed to parse signed DRM certificate"); + } + video_widevine::DrmCertificate device_cert; + if (!device_cert.ParseFromString(signed_device_cert.drm_certificate())) { + LOGE("Failed to parse DRM certificate"); + return CasStatus(CasStatusCode::kIndividualizationError, + "Failed to parse DRM certificate"); + } + if (!device_cert.has_algorithm()) { + LOGW("DRM certificate does not specify algorithm type, assuming RSA"); + wrapped_private_key->set_type(CryptoWrappedKey::kRsa); + } else { + switch (device_cert.algorithm()) { + case video_widevine::DrmCertificate::RSA: + wrapped_private_key->set_type(CryptoWrappedKey::kRsa); + break; + case video_widevine::DrmCertificate::ECC_SECP256R1: + case video_widevine::DrmCertificate::ECC_SECP384R1: + case video_widevine::DrmCertificate::ECC_SECP521R1: + wrapped_private_key->set_type(CryptoWrappedKey::kEcc); + break; + default: + LOGE("Unknown DRM certificate algorithm: algorithm = %d", + static_cast(device_cert.algorithm())); + return CasStatus(CasStatusCode::kIndividualizationError, + "Unknown DRM certificate algorithm"); } - hash_file.SerializeToString(device_file); + } + + // The certificate will be stored to the device as the final step in + // the device provisioning process. + if (!StoreCertificate(*file_system, device_cert_data, *wrapped_private_key)) { + LOGE("Failed to store provisioning certificate"); + return CasStatus(CasStatusCode::kIndividualizationError, + "Failed to store provisioning certificate"); } *device_certificate = provisioning_response.device_certificate(); return crypto_session_->reset(); } +CasStatus CasLicense::HandleProvisioning40Response( + wvutil::FileSystem* file_system, + const SignedProvisioningMessage& signed_response, std::string* cert, + CryptoWrappedKey* wrapped_key) const { + ProvisioningResponse provisioning_response; + const std::string& response_message = signed_response.message(); + if (response_message.empty() || + !provisioning_response.ParseFromString(response_message)) { + return CasStatus(CasStatusCode::kIndividualizationError, + "unable to parse response"); + } + + const std::string& device_certificate = + provisioning_response.device_certificate(); + if (device_certificate.empty()) { + LOGE("Provisioning response has no certificate"); + return CasStatus(CasStatusCode::kIndividualizationError, + "Provisioning response has no certificate"); + } + + if (provisioning_40_wrapped_private_key_.empty()) { + LOGE("No private key was generated"); + return CasStatus(CasStatusCode::kIndividualizationError, + "No private key was generated"); + } + + const CryptoWrappedKey private_key(provisioning_40_key_type_, + provisioning_40_wrapped_private_key_); + + // Check the stage of the provisioning by checking if an OEM cert is already + // stored in the file system. + if (!file_system->Exists(std::string(kOemCertificateFilePath))) { + // No OEM cert already stored => the response is expected to be an OEM cert. + if (!StoreOemCertificate(*file_system, device_certificate, private_key)) { + LOGE("Failed to store provisioning 4 OEM certificate"); + return CasStatus(CasStatusCode::kIndividualizationError, + "Failed to store provisioning 4 OEM certificate"); + } + } else { + // The response is assumed to be an DRM cert. + if (!StoreCertificate(*file_system, device_certificate, private_key)) { + LOGE("Failed to store provisioning 4 DRM certificate"); + return CasStatus(CasStatusCode::kIndividualizationError, + "Failed to store provisioning 4 DRM certificate"); + } + *cert = device_certificate; + wrapped_key->set_key(provisioning_40_wrapped_private_key_); + wrapped_key->set_type(provisioning_40_key_type_); + } + return crypto_session_->reset(); +} + +void CasLicense::FillClientProperties(ClientIdentification& client_id) const { + std::string value; + ClientIdentification_NameValue* client_info = nullptr; + if (Properties::GetCompanyName(&value)) { + client_info = client_id.add_client_info(); + client_info->set_name(kKeyCompanyName); + client_info->set_value(value); + } + if (Properties::GetModelName(&value)) { + client_info = client_id.add_client_info(); + client_info->set_name(kKeyModelName); + client_info->set_value(value); + } + if (Properties::GetProductName(&value)) { + client_info = client_id.add_client_info(); + client_info->set_name(kKeyProductName); + client_info->set_value(value); + } + if (Properties::GetArchitectureName(&value)) { + client_info = client_id.add_client_info(); + client_info->set_name(kKeyArchitectureName); + client_info->set_value(value); + } + if (Properties::GetDeviceName(&value)) { + client_info = client_id.add_client_info(); + client_info->set_name(kKeyDeviceName); + client_info->set_value(value); + } + if (Properties::GetBuildInfo(&value)) { + client_info = client_id.add_client_info(); + client_info->set_name(kKeyBuildInfo); + client_info->set_value(value); + } + if (Properties::GetWvCasPluginVersion(value)) { + client_info = client_id.add_client_info(); + client_info->set_name(kKeyWvCasVersion); + client_info->set_value(value); + } + client_info = client_id.add_client_info(); + client_info->set_name(kKeyOemCryptoSecurityPatchLevel); + client_info->set_value(std::to_string( + static_cast(crypto_session_->GetSecurityPatchLevel()))); + std::string oec_build_info; + if (crypto_session_->GetBuildInformation(&oec_build_info)) { + client_info = client_id.add_client_info(); + client_info->set_name(kKeyOemCryptoBuildInformation); + client_info->set_value(oec_build_info); + } + + ClientIdentification_ClientCapabilities* client_capabilities = + client_id.mutable_client_capabilities(); + uint32_t api_version = 0; + if (crypto_session_->APIVersion(&api_version).ok()) { + client_capabilities->set_oem_crypto_api_version(api_version); + } +} + CasStatus CasLicense::GenerateEntitlementRequest( const std::string& init_data, const std::string& device_certificate, - const std::string& wrapped_rsa_key, LicenseType license_type, + const CryptoWrappedKey& private_key, LicenseType license_type, std::string* signed_license_request) { if (!signed_license_request) { return CasStatus(CasStatusCode::kCasLicenseError, "invalid parameters"); @@ -344,69 +564,12 @@ CasStatus CasLicense::GenerateEntitlementRequest( client_id->set_type(ClientIdentification::DRM_DEVICE_CERTIFICATE); client_id->set_token(device_certificate); - // Call OEMCrypto_GetOEMPublicCertificate before OEMCrypto_LoadDeviceRSAKey - // so it caches the OEMCrypto Public Key and then throw away result. (Prov3.0) - std::vector buf(kCertificateDataSizeBytes, 0); - size_t buf_size = buf.size(); - CasStatus status = - crypto_session_->GetOEMPublicCertificate(buf.data(), &buf_size); - status = crypto_session_->LoadDeviceRSAKey( - reinterpret_cast(wrapped_rsa_key.data()), - wrapped_rsa_key.size()); + CasStatus status = crypto_session_->LoadCertificatePrivateKey(private_key); if (!status.ok()) { return status; } - std::string value; - ClientIdentification_NameValue* client_info = nullptr; - if (Properties::GetCompanyName(&value)) { - client_info = client_id->add_client_info(); - client_info->set_name(kKeyCompanyName); - client_info->set_value(value); - } - if (Properties::GetModelName(&value)) { - client_info = client_id->add_client_info(); - client_info->set_name(kKeyModelName); - client_info->set_value(value); - } - if (Properties::GetProductName(&value)) { - client_info = client_id->add_client_info(); - client_info->set_name(kKeyProductName); - client_info->set_value(value); - } - if (Properties::GetArchitectureName(&value)) { - client_info = client_id->add_client_info(); - client_info->set_name(kKeyArchitectureName); - client_info->set_value(value); - } - if (Properties::GetDeviceName(&value)) { - client_info = client_id->add_client_info(); - client_info->set_name(kKeyDeviceName); - client_info->set_value(value); - } - if (Properties::GetWvCasPluginVersion(value)) { - client_info = client_id->add_client_info(); - client_info->set_name(kKeyWvCasVersion); - client_info->set_value(value); - } - - ClientIdentification_ClientCapabilities* client_capabilities = - client_id->mutable_client_capabilities(); - - // TODO(jfore): - // client_capabilities->set_session_token(supports_usage_information)? Is this - // for usage tables? - - // TODO(jfore): Anti rollback support? - - uint32_t api_version = 0; - status = crypto_session_->APIVersion(&api_version); - if (!status.ok()) { - return status; - } - client_capabilities->set_oem_crypto_api_version(api_version); - - // TODO(jfore): Handle hdcp capabilities. + FillClientProperties(*client_id); video_widevine::LicenseRequest_ContentIdentification* content_id = license_request.mutable_content_id(); @@ -474,8 +637,9 @@ CasStatus CasLicense::GenerateEntitlementRequest( return CasStatusCode::kNoError; } -CasStatus CasLicense::HandleStoredLicense(const std::string& wrapped_rsa_key, - const std::string& license_file) { +CasStatus CasLicense::HandleStoredLicense( + const CryptoWrappedKey& wrapped_private_key, + const std::string& license_file) { HashedFile hash_file; if (!hash_file.ParseFromString(license_file)) { return CasStatus(CasStatusCode::kLicenseFileParseError, @@ -520,10 +684,9 @@ CasStatus CasLicense::HandleStoredLicense(const std::string& wrapped_rsa_key, "unable to parse license"); } - // Install the rsa provisioning key. - CasStatus status = crypto_session_->LoadDeviceRSAKey( - reinterpret_cast(wrapped_rsa_key.data()), - wrapped_rsa_key.size()); + // Install the provisioning key. + CasStatus status = + crypto_session_->LoadCertificatePrivateKey(wrapped_private_key); if (!status.ok()) { return status; } @@ -555,8 +718,10 @@ CasStatus CasLicense::HandleStoredLicense(const std::string& wrapped_rsa_key, // Reset the flag so expiration callback is enabled. is_renewal_in_license_file_ = false; // Expiration event is expected to be fired if license is shown expired. - OnLicenseExpiration(); - return status; + if (!status.ok()) { + OnLicenseExpiration(); + return status; + } } policy_engine_->RestorePlaybackTimes(license.playback_start_time(), license.last_playback_time(), @@ -767,6 +932,155 @@ CasStatus CasLicense::GenerateDeviceProvisioningRequestWithKeybox( return CasStatusCode::kNoError; } +CasStatus CasLicense::GetProvisioning40RequestInternal( + wvutil::FileSystem& file_system, + std::string* serialized_provisioning_request) { + ProvisioningRequest provisioning_request; + const std::string oem_cert_path = std::string(kOemCertificateFilePath); + // Determine the current stage by checking if OEM cert exists. + std::string stored_oem_cert; + if (file_system.Exists(oem_cert_path)) { + // This is second stage requesting for DRM cert. We try to use the stored + // OEM cert. In case of error, we just fall back to the first stage + // provisioning (request for an OEM cert). + if (!RetrieveOemCertificateAndLoadPrivateKey(file_system, *crypto_session_, + stored_oem_cert)) { + stored_oem_cert.clear(); + LOGD("Deleting the stored OEM certificate due to unsuccessful read"); + if (!RemoveFile(file_system, oem_cert_path)) { + // This should not happen. + LOGE("Failed to delete the OEM certificate certificate"); + } + } + } + + CasStatus status = FillEncryptedClientId( + stored_oem_cert, provisioning_request, *wv_service_cert_); + if (!status.ok()) { + return status; + } + + std::string public_key; + std::string public_key_signature; + provisioning_40_wrapped_private_key_.clear(); + provisioning_40_key_type_ = CryptoWrappedKey::kUninitialized; + status = crypto_session_->GenerateCertificateKeyPair( + &public_key, &public_key_signature, &provisioning_40_wrapped_private_key_, + &provisioning_40_key_type_); + if (!status.ok()) { + LOGE("Fail to GenerateCertificateKeyPair %s", + status.error_string().c_str()); + return status; + } + + PublicKeyToCertify* key_to_certify = + provisioning_request.mutable_certificate_public_key(); + key_to_certify->set_public_key(public_key); + key_to_certify->set_signature(public_key_signature); + key_to_certify->set_key_type(provisioning_40_key_type_ == + CryptoWrappedKey::kRsa + ? PublicKeyToCertify::RSA + : PublicKeyToCertify::ECC); + + std::string serialized_message; + provisioning_request.SerializeToString(&serialized_message); + + SignedProvisioningMessage signed_provisioning_msg; + signed_provisioning_msg.set_message(serialized_message); + signed_provisioning_msg.set_protocol_version( + SignedProvisioningMessage::PROVISIONING_40); + + // Core message and request signature are added to the provisioning request + // starting OEMCrypto v18 + uint32_t api_version = 0; + const bool core_message_signature_required = + crypto_session_->APIVersion(&api_version).ok() && + (api_version >= OEM_CRYPTO_API_VERSION_SUPPORTS_PROV40_CORE_MESSAGE); + if (core_message_signature_required) { + std::string core_message; + std::string request_signature; + bool should_specify_algorithm; + OEMCrypto_SignatureHashAlgorithm oec_algorithm = OEMCrypto_SHA1; + status = crypto_session_->PrepareAndSignProvisioningRequest( + serialized_message, &core_message, &request_signature, + should_specify_algorithm, oec_algorithm); + if (!status.ok()) { + LOGE("Failed to prepare provisioning 4.0 request: status = %s", + status.error_string().c_str()); + return status; + } + if (core_message.empty()) { + LOGE("Core message is empty"); + return CasStatus(CasStatusCode::kProvisioningError, + "Core message is empty"); + } + if (request_signature.empty()) { + LOGE("Request signature is empty"); + return CasStatus(CasStatusCode::kProvisioningError, + "Request signature is empty"); + } + signed_provisioning_msg.set_oemcrypto_core_message(core_message); + signed_provisioning_msg.set_signature(request_signature); + if (should_specify_algorithm) { + HashAlgorithmProto proto_algorithm = + HashAlgorithmProto::HASH_ALGORITHM_UNSPECIFIED; + if (!OecAlgorithmToProtoAlgorithm(oec_algorithm, proto_algorithm)) { + return CasStatus(CasStatusCode::kProvisioningError, + "UNSUPPORTED_SIGNATURE_HASH_ALGORITHM"); + } + signed_provisioning_msg.set_hash_algorithm(proto_algorithm); + } + } + + std::string serialized_request; + signed_provisioning_msg.SerializeToString(&serialized_request); + // Return request as web-safe base64 string + *serialized_provisioning_request = Base64SafeEncodeNoPad(serialized_request); + + return CasStatusCode::kNoError; +} + +CasStatus CasLicense::FillEncryptedClientId( + const std::string& client_token, ProvisioningRequest& provisioning_request, + const ServiceCertificate& service_certificate) const { + ClientIdentification client_id; + if (!client_token.empty()) { + // A token has been provided. This can only + // happen in provisioning 4 (second stage) where an OEM cert is provided. + client_id.set_type( + video_widevine::ClientIdentification::OEM_DEVICE_CERTIFICATE); + client_id.set_token(client_token); + } else { + client_id.set_type( + video_widevine::ClientIdentification::BOOT_CERTIFICATE_CHAIN); + std::string token; + std::string additional_token; + CasStatus status = + crypto_session_->GetBootCertificateChain(&token, &additional_token); + if (!status.ok()) { + LOGE("Failed to get provisioning token: %s", + status.error_string().c_str()); + return CasStatus(CasStatusCode::kProvisioningError, + "Failed to get provisioning token"); + } + client_id.set_token(token); + if (!additional_token.empty()) { + client_id.mutable_device_credentials()->set_token(additional_token); + } + } + FillClientProperties(client_id); + + if (!service_certificate.HasSignedCertificate()) { + LOGE("Service certificate not staged"); + return CasStatus(CasStatusCode::kProvisioningError, + "Service certificate not staged"); + } + + // Encrypt client identification + return service_certificate.EncryptClientId( + &client_id, provisioning_request.mutable_encrypted_client_id()); +} + // TODO(jfore): Currently not functional, implement me as needed. CasStatus CasLicense::GenerateDeviceProvisioningRequestWithOEMCert() const { ProvisioningRequest provisioning_request; @@ -858,31 +1172,15 @@ CasStatus CasLicense::InstallLicenseRenewal( CasStatus CasLicense::HandleStoredDrmCert(const std::string& certificate, std::string* device_certificate, - std::string* wrapped_rsa_key) { - if (nullptr == device_certificate || nullptr == wrapped_rsa_key) { + CryptoWrappedKey* private_key) { + if (nullptr == device_certificate || private_key == nullptr) { return CasStatus(CasStatusCode::kIndividualizationError, ("missing output parameters")); } - HashedFile hash_file; - if (!hash_file.ParseFromString(certificate)) { - return CasStatus(CasStatusCode::kIndividualizationError, - ("unable to parse the certificate data")); - } - std::string file_hash; - if (!Hash(hash_file.file(), &file_hash)) { - return CasStatus(CasStatusCode::kIndividualizationError, - ("generating file hash fails")); - } - - if (hash_file.hash() != file_hash) { - return CasStatus(CasStatusCode::kIndividualizationError, - ("corrupt certificate file data")); - } - - File file; - if (!file.ParseFromString(hash_file.file())) { - return CasStatus(CasStatusCode::kIndividualizationError, - ("unable to parse the file data")); + video_widevine_client::sdk::File file; + CasStatus status = RetrieveHashedFile(certificate, file); + if (status.status_code() != CasStatusCode::kNoError) { + return status; } if (file.type() != File::DEVICE_CERTIFICATE) { return CasStatus(CasStatusCode::kIndividualizationError, @@ -896,7 +1194,30 @@ CasStatus CasLicense::HandleStoredDrmCert(const std::string& certificate, } *device_certificate = file.device_certificate().certificate(); - *wrapped_rsa_key = file.device_certificate().wrapped_private_key(); + private_key->Clear(); + private_key->set_key(file.device_certificate().wrapped_private_key()); + if (file.device_certificate().has_key_type()) { + const DeviceCertificate::PrivateKeyType key_type = + file.device_certificate().key_type(); + switch (key_type) { + case DeviceCertificate::RSA: + private_key->set_type(CryptoWrappedKey::kRsa); + break; + case DeviceCertificate::ECC: + private_key->set_type(CryptoWrappedKey::kEcc); + break; + default: + LOGW("Unknown DRM key type, defaulting to RSA: type = %d", key_type); + private_key->set_type(CryptoWrappedKey::kRsa); + break; + } + } else { + // Possible that device certificate is from V15, in this case, the + // only supported key of at that time was RSA. + LOGD("No key type info, assuming RSA"); + private_key->set_type(CryptoWrappedKey::kRsa); + } + return CasStatusCode::kNoError; } diff --git a/plugin/src/crypto_session.cpp b/plugin/src/crypto_session.cpp index 3e22d54..f22782d 100644 --- a/plugin/src/crypto_session.cpp +++ b/plugin/src/crypto_session.cpp @@ -10,8 +10,19 @@ #include #include "cas_util.h" +#include "crypto_wrapped_key.h" #include "log.h" +// Stringify turns macro arguments into static C strings. +// Example: STRINGIFY(this_argument) -> "this_argument" +#define STRINGIFY(PARAM) #PARAM + +#define RETURN_IF_NULL(PARAM) \ + if ((PARAM) == nullptr) { \ + LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \ + return CasStatus(CasStatusCode::kCryptoSessionError, "parameter is null"); \ + } + namespace wvcas { namespace { constexpr uint32_t kExpectedOEMCryptoVersion = 18; @@ -75,41 +86,46 @@ void FillEntitledContentKeyObjectFromKeyData( dest->cipher_mode = CipherModeFromKeyData(src.cipher_mode); } +uint8_t* MutableStringDataPointer(std::string* s) { + if (s == nullptr) return nullptr; + if (s->empty()) return nullptr; + return reinterpret_cast(&s->front()); +} } // namespace wvutil::shared_mutex CryptoLock::static_field_mutex_; wvutil::shared_mutex CryptoLock::oem_crypto_mutex_; template -auto CryptoLock::WithStaticFieldWriteLock(const char* tag, - Func body) -> decltype(body()) { +auto CryptoLock::WithStaticFieldWriteLock(const char* tag, Func body) + -> decltype(body()) { LOGV("Static field write lock: %s", tag); std::unique_lock auto_lock(static_field_mutex_); return body(); } template -auto CryptoLock::WithStaticFieldReadLock(const char* tag, - Func body) -> decltype(body()) { +auto CryptoLock::WithStaticFieldReadLock(const char* tag, Func body) + -> decltype(body()) { LOGV("Static field read lock: %s", tag); wvutil::shared_lock auto_lock(static_field_mutex_); return body(); } template -auto CryptoLock::WithOecWriteLock(const char* tag, - Func body) -> decltype(body()) { +auto CryptoLock::WithOecWriteLock(const char* tag, Func body) + -> decltype(body()) { LOGV("OEMCrypto write lock: %s", tag); std::unique_lock auto_lock(oem_crypto_mutex_); return body(); } template -auto CryptoLock::WithOecReadLock(const char* tag, - Func body) -> decltype(body()) { +auto CryptoLock::WithOecReadLock(const char* tag, Func body) + -> decltype(body()) { LOGV("OEMCrypto read lock: %s", tag); wvutil::shared_lock auto_lock(oem_crypto_mutex_); return body(); } template -auto CryptoLock::WithOecSessionLock(const char* tag, - Func body) -> decltype(body()) { +auto CryptoLock::WithOecSessionLock(const char* tag, Func body) + -> decltype(body()) { LOGV("OEMCrypto Session Lock - %s", tag); wvutil::shared_lock oec_auto_lock(oem_crypto_mutex_); std::unique_lock session_auto_lock(oem_crypto_session_mutex_); @@ -232,6 +248,20 @@ OEMCryptoResult CryptoInterface::OEMCrypto_LoadDRMPrivateKey( }); } +uint8_t CryptoInterface::OEMCrypto_Security_Patch_Level() { + return lock_->WithOecReadLock("OEMCrypto_Security_Patch_Level", [&] { + return oemcrypto_interface_->OEMCrypto_Security_Patch_Level(); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_BuildInformation( + char* buffer, size_t* buffer_length) { + return lock_->WithOecReadLock("OEMCrypto_Security_Patch_Level", [&] { + return oemcrypto_interface_->OEMCrypto_BuildInformation(buffer, + buffer_length); + }); +} + OEMCryptoResult CryptoInterface::OEMCrypto_GenerateRSASignature( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, uint8_t* signature, size_t* signature_length, @@ -354,6 +384,37 @@ OEMCryptoResult CryptoInterface::OEMCrypto_GetSignatureHashAlgorithm( }); } +OEMCryptoResult CryptoInterface::OEMCrypto_GetBootCertificateChain( + uint8_t* bcc, size_t* bcc_length, uint8_t* additional_signature, + size_t* additional_signature_length) { + return lock_->WithOecReadLock("GetBootCertificateChain", [&] { + return oemcrypto_interface_->OEMCrypto_GetBootCertificateChain( + bcc, bcc_length, additional_signature, additional_signature_length); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_GenerateCertificateKeyPair( + OEMCrypto_SESSION session, uint8_t* public_key, size_t* public_key_length, + uint8_t* public_key_signature, size_t* public_key_signature_length, + uint8_t* wrapped_private_key, size_t* wrapped_private_key_length, + OEMCrypto_PrivateKeyType* key_type) { + return lock_->WithOecSessionLock("GenerateCertificateKeyPair", [&] { + return oemcrypto_interface_->OEMCrypto_GenerateCertificateKeyPair( + session, public_key, public_key_length, public_key_signature, + public_key_signature_length, wrapped_private_key, + wrapped_private_key_length, key_type); + }); +} + +OEMCryptoResult CryptoInterface::OEMCrypto_InstallOemPrivateKey( + OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type, + const uint8_t* wrapped_private_key, size_t wrapped_private_key_length) { + return lock_->WithOecSessionLock("InstallOemPrivateKey", [&] { + return oemcrypto_interface_->OEMCrypto_InstallOemPrivateKey( + session, key_type, wrapped_private_key, wrapped_private_key_length); + }); +} + OEMCryptoResult CryptoInterface::create_internal( OEMCryptoInterface* oemcrypto_interface, std::unique_ptr* init) { @@ -462,6 +523,8 @@ CasProvisioningMethod CryptoSession::provisioning_method() { return OEMCertificate; case OEMCrypto_DrmCertificate: return DrmCertificate; + case OEMCrypto_BootCertificateChain: + return BootCertificateChain; default: return ProvisioningError; } @@ -661,9 +724,36 @@ CasStatus CryptoSession::PrepareAndSignProvisioningRequest( "Missing core_message or signature."); } + CasProvisioningMethod prov_method = provisioning_method(); + if (prov_method == Keybox) { + // Keybox based provisioning does not need hash algorithm. + should_specify_algorithm = false; + } else if (prov_method == BootCertificateChain) { + should_specify_algorithm = true; + // The key to signing the provisioning 4.0 request for each + // stage has been loaded already when it was generated by OEMCrypto. + } else { + // CAS only supports provisioning 2 and 4. + LOGE("Unknown provisioning method %d", prov_method); + return CasStatus(CasStatusCode::kCryptoSessionError, + "Unknown provisioning method."); + } + OEMCryptoResult sts; - // Keybox based provisioning does not need hash algorithm. - should_specify_algorithm = false; + if (should_specify_algorithm) { + sts = crypto_interface_->OEMCrypto_GetSignatureHashAlgorithm(session_, + &algorithm); + if (sts == OEMCrypto_SUCCESS) { + // Nothing to do. + } else if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + should_specify_algorithm = false; + } else { + std::ostringstream err_string; + err_string << "OEMCrypto_GetSignatureHashAlgorithm returned " << sts; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + } + size_t signature_length = 0; size_t core_message_length = 0; *core_message = ""; @@ -768,26 +858,6 @@ CasStatus CryptoSession::GetOEMPublicCertificate(uint8_t* public_cert, return CasStatus::OkStatus(); } -CasStatus CryptoSession::LoadDeviceRSAKey(const uint8_t* wrapped_rsa_key, - size_t wrapped_rsa_key_length) { - if (!crypto_interface_) { - return CasStatus(CasStatusCode::kCryptoSessionError, - "missing crypto interface"); - } - if (!wrapped_rsa_key) { - return CasStatus(CasStatusCode::kCryptoSessionError, "invalid argument"); - } - OEMCryptoResult result = crypto_interface_->OEMCrypto_LoadDRMPrivateKey( - session_, OEMCrypto_RSA_Private_Key, wrapped_rsa_key, - wrapped_rsa_key_length); - if (result != OEMCrypto_SUCCESS) { - std::ostringstream err_string; - err_string << "OEMCrypto_LoadDRMPrivateKey returned " << result; - return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); - } - return CasStatus::OkStatus(); -} - CasStatus CryptoSession::GenerateRSASignature( const uint8_t* message, size_t message_length, uint8_t* signature, size_t* signature_length, RSA_Padding_Scheme padding_scheme) { @@ -1080,4 +1150,178 @@ CasStatus CryptoSession::GetOEMKeyToken( return CasStatus::OkStatus(); } +CasStatus CryptoSession::GetBootCertificateChain( + std::string* bcc, std::string* additional_signature) { + RETURN_IF_NULL(bcc); + RETURN_IF_NULL(additional_signature); + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + if (provisioning_method() != BootCertificateChain) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "provisioning type is not BCC"); + } + + size_t bcc_length = 0; + size_t additional_signature_length = 0; + OEMCryptoResult sts = crypto_interface_->OEMCrypto_GetBootCertificateChain( + nullptr, &bcc_length, nullptr, &additional_signature_length); + if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { + bcc->resize(bcc_length); + additional_signature->resize(additional_signature_length); + sts = crypto_interface_->OEMCrypto_GetBootCertificateChain( + MutableStringDataPointer(bcc), &bcc_length, + MutableStringDataPointer(additional_signature), + &additional_signature_length); + } + if (sts != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_GetBootCertificateChain returned " << sts; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + bcc->resize(bcc_length); + additional_signature->resize(additional_signature_length); + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::GenerateCertificateKeyPair( + std::string* public_key, std::string* public_key_signature, + std::string* wrapped_private_key, CryptoWrappedKey::Type* key_type) { + RETURN_IF_NULL(public_key); + RETURN_IF_NULL(public_key_signature); + RETURN_IF_NULL(wrapped_private_key); + RETURN_IF_NULL(key_type); + if (!crypto_interface_) { + return CasStatus(CasStatusCode::kCryptoSessionError, + "missing crypto interface"); + } + + // Round 1, get the size of all the fields. + size_t public_key_length = 0; + size_t public_key_signature_length = 0; + size_t wrapped_private_key_length = 0; + OEMCrypto_PrivateKeyType oemcrypto_key_type; + OEMCryptoResult status = + crypto_interface_->OEMCrypto_GenerateCertificateKeyPair( + session_, nullptr, &public_key_length, nullptr, + &public_key_signature_length, nullptr, &wrapped_private_key_length, + &oemcrypto_key_type); + + if (status != OEMCrypto_ERROR_SHORT_BUFFER) { + std::ostringstream err_string; + err_string << "OEMCrypto_GenerateCertificateKeyPair returned " << status; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + + public_key->resize(public_key_length); + public_key_signature->resize(public_key_signature_length); + wrapped_private_key->resize(wrapped_private_key_length); + status = crypto_interface_->OEMCrypto_GenerateCertificateKeyPair( + session_, MutableStringDataPointer(public_key), &public_key_length, + MutableStringDataPointer(public_key_signature), + &public_key_signature_length, + MutableStringDataPointer(wrapped_private_key), + &wrapped_private_key_length, &oemcrypto_key_type); + if (status != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_GenerateCertificateKeyPair returned " << status; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + + public_key->resize(public_key_length); + public_key_signature->resize(public_key_signature_length); + wrapped_private_key->resize(wrapped_private_key_length); + + if (oemcrypto_key_type == OEMCrypto_RSA_Private_Key) { + *key_type = CryptoWrappedKey::kRsa; + } else if (oemcrypto_key_type == OEMCrypto_ECC_Private_Key) { + *key_type = CryptoWrappedKey::kEcc; + } else { + LOGE("Unexpected key type returned from GenerateCertificateKeyPair: %d", + static_cast(oemcrypto_key_type)); + // TODO(b/261185349): add OEMCrypto_PrivateKeyType to CdmResponseType + return CasStatus(CasStatusCode::kCryptoSessionError, "Unexpected key type"); + } + + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::LoadOemCertificatePrivateKey( + const CryptoWrappedKey& private_key) { + const OEMCrypto_PrivateKeyType key_type = + (private_key.type() == CryptoWrappedKey::kEcc) + ? OEMCrypto_ECC_Private_Key + : OEMCrypto_RSA_Private_Key; + const std::string& wrapped_private_key = private_key.key(); + + OEMCryptoResult status = crypto_interface_->OEMCrypto_InstallOemPrivateKey( + session_, key_type, + reinterpret_cast(wrapped_private_key.data()), + wrapped_private_key.size()); + if (status != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_InstallOemPrivateKey returned " << status; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +CasStatus CryptoSession::LoadCertificatePrivateKey( + const CryptoWrappedKey& private_key) { + const OEMCrypto_PrivateKeyType key_type = + (private_key.type() == CryptoWrappedKey::kEcc) + ? OEMCrypto_ECC_Private_Key + : OEMCrypto_RSA_Private_Key; + const std::string& wrapped_private_key = private_key.key(); + + OEMCryptoResult status = crypto_interface_->OEMCrypto_LoadDRMPrivateKey( + session_, key_type, + reinterpret_cast(wrapped_private_key.data()), + wrapped_private_key.size()); + if (status != OEMCrypto_SUCCESS) { + std::ostringstream err_string; + err_string << "OEMCrypto_LoadDRMPrivateKey returned " << status; + return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str()); + } + return CasStatus::OkStatus(); +} + +uint8_t CryptoSession::GetSecurityPatchLevel() { + return crypto_interface_->OEMCrypto_Security_Patch_Level(); +} + +bool CryptoSession::GetBuildInformation(std::string* info) { + if (info == nullptr) { + return false; + } + size_t info_length = 128; + info->assign(info_length, '\0'); + OEMCryptoResult result = crypto_interface_->OEMCrypto_BuildInformation( + &info->front(), &info_length); + if (result == OEMCrypto_ERROR_SHORT_BUFFER) { + info->assign(info_length, '\0'); + result = crypto_interface_->OEMCrypto_BuildInformation(&info->front(), + &info_length); + } + if (result != OEMCrypto_SUCCESS) { + LOGE("GetBuildInformation failed: result = %d", result); + info->clear(); + return false; + } + info->resize(info_length); + // Some OEMCrypto implementations may include trailing null + // bytes in the output. Trim them here. + while (!info->empty() && info->back() == '\0') { + info->pop_back(); + } + if (info->empty()) { + LOGE("BuildInformation() returned corrupted data: length = %zu", + info_length); + return false; + } + + return true; +} + } // namespace wvcas diff --git a/plugin/src/file_util.cpp b/plugin/src/file_util.cpp new file mode 100644 index 0000000..9796707 --- /dev/null +++ b/plugin/src/file_util.cpp @@ -0,0 +1,270 @@ +// Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "file_util.h" + +#include + +#include "log.h" +#include "crypto_wrapped_key.h" +#include "device_files.pb.h" +#include "file_util.h" +#include "license_protocol.pb.h" +#include "service_certificate.h" +#include "string_conversions.h" + + +namespace wvcas { + +namespace { +using video_widevine_client::sdk::DeviceCertificate; +using video_widevine_client::sdk::File; +using video_widevine_client::sdk::HashedFile; +using video_widevine_client::sdk::OemCertificate; + +constexpr char kOemCertificateFilePath[] = + "/data/vendor/mediacas/IDM/widevine/oemcert.bin"; +constexpr char kDrmCertificateFilePath[] = + "/data/vendor/mediacas/IDM/widevine/cert.bin"; + +bool Hash(const std::string& data, std::string* hash) { + if (!hash) return false; + hash->resize(SHA256_DIGEST_LENGTH); + const unsigned char* input = + reinterpret_cast(data.data()); + unsigned char* output = reinterpret_cast(&(*hash)[0]); + SHA256(input, data.size(), output); + return true; +} + +} // namespace + +bool ReadFileFromStorage(wvutil::FileSystem& file_system, + const std::string& filename, std::string* file_data) { + if (nullptr == file_data) { + return false; + } + if (!file_system.Exists(filename)) { + return false; + } + size_t filesize = file_system.FileSize(filename); + if (0 == filesize) { + return false; + } + file_data->resize(filesize); + std::unique_ptr file = + file_system.Open(filename, wvutil::FileSystem::kReadOnly); + if (nullptr == file) { + return false; + } + size_t bytes_read = file->Read(&(*file_data)[0], file_data->size()); + if (bytes_read != filesize) { + return false; + } + return true; +} + +bool RemoveFile(wvutil::FileSystem& file_system, const std::string& filename) { + if (!file_system.Exists(filename)) { + return false; + } + if (!file_system.Remove(filename)) { + return false; + } + return true; +} + +bool StoreFile(wvutil::FileSystem& file_system, const std::string& filename, + const std::string& file_data) { + std::unique_ptr file(file_system.Open( + filename, wvutil::FileSystem::kTruncate | wvutil::FileSystem::kCreate)); + if (nullptr == file) { + return false; + } + size_t bytes_written = file->Write(file_data.data(), file_data.size()); + if (bytes_written != file_data.size()) { + return false; + } + return true; +} + +bool StoreFileWithHash(wvutil::FileSystem& file_system, const std::string& name, + const std::string& serialized_file) { + std::string hash = Sha256Hash(serialized_file); + + // Fill in hashed file data + video_widevine_client::sdk::HashedFile hash_file; + hash_file.set_file(serialized_file); + hash_file.set_hash(hash); + + std::string serialized_hash_file; + hash_file.SerializeToString(&serialized_hash_file); + + return StoreFile(file_system, name, serialized_hash_file); +} + +bool StoreOemCertificate(wvutil::FileSystem& file_system, + const std::string& certificate, + const CryptoWrappedKey& private_key) { + if (certificate.empty()) { + LOGE("Missing certificate information"); + return false; + } + if (!private_key.IsValid()) { + LOGE("Private key is invalid"); + return false; + } + + std::string certificate_file_name = std::string(kOemCertificateFilePath); + + // Fill in file information + video_widevine_client::sdk::File file; + file.set_type(video_widevine_client::sdk::File::OEM_CERTIFICATE); + file.set_version(video_widevine_client::sdk::File::VERSION_1); + video_widevine_client::sdk::OemCertificate* oem_certificate = file.mutable_oem_certificate(); + oem_certificate->set_certificate(certificate); + oem_certificate->set_wrapped_private_key(private_key.key()); + switch (private_key.type()) { + case wvcas::CryptoWrappedKey::kRsa: + oem_certificate->set_key_type(video_widevine_client::sdk::OemCertificate::RSA); + break; + case wvcas::CryptoWrappedKey::kEcc: + oem_certificate->set_key_type(video_widevine_client::sdk::OemCertificate::ECC); + break; + case wvcas::CryptoWrappedKey::kUninitialized: + default: + LOGE("Unexpected key type: %d", private_key.type()); + return false; + } + + std::string serialized_file; + file.SerializeToString(&serialized_file); + return StoreFileWithHash(file_system, certificate_file_name, serialized_file); +} + +bool StoreCertificate(wvutil::FileSystem& file_system, + const std::string& certificate, + const CryptoWrappedKey& private_key) { + if (certificate.empty()) { + LOGE("Missing certificate information"); + return false; + } + if (!private_key.IsValid()) { + LOGE("Private key is invalid"); + return false; + } + + // Fill in file information + video_widevine_client::sdk::File file; + + file.set_type(video_widevine_client::sdk::File::DEVICE_CERTIFICATE); + file.set_version(video_widevine_client::sdk::File::VERSION_1); + + DeviceCertificate* mutable_device_certificate = + file.mutable_device_certificate(); + mutable_device_certificate->set_certificate(certificate); + mutable_device_certificate->set_wrapped_private_key(private_key.key()); + switch (private_key.type()) { + case CryptoWrappedKey::kRsa: + mutable_device_certificate->set_key_type(DeviceCertificate::RSA); + break; + case CryptoWrappedKey::kEcc: + mutable_device_certificate->set_key_type(DeviceCertificate::ECC); + break; + case CryptoWrappedKey::kUninitialized: // Suppress compiler + // warnings. + default: + LOGE("Unexpected key type: %d", private_key.type()); + return false; + } + + std::string serialized_file; + file.SerializeToString(&serialized_file); + + std::string certificate_file_name = std::string(kDrmCertificateFilePath); + + return StoreFileWithHash(file_system, certificate_file_name, serialized_file); +} + + +CasStatus RetrieveHashedFile(const std::string& certificate, video_widevine_client::sdk::File& file) { + video_widevine_client::sdk::HashedFile hash_file; + if (!hash_file.ParseFromString(certificate)) { + return CasStatus(CasStatusCode::kIndividualizationError, + ("unable to parse the certificate data")); + } + std::string file_hash; + if (!Hash(hash_file.file(), &file_hash)) { + return CasStatus(CasStatusCode::kIndividualizationError, + ("generating file hash fails")); + } + if (hash_file.hash() != file_hash) { + return CasStatus(CasStatusCode::kIndividualizationError, + ("corrupt certificate file data")); + } + if (!file.ParseFromString(hash_file.file())) { + return CasStatus(CasStatusCode::kIndividualizationError, + ("unable to parse the file data")); + } + return CasStatusCode::kNoError; +} + +bool RetrieveOemCertificate(wvutil::FileSystem& file_system, + std::string& certificate, + CryptoWrappedKey* wrapped_private_key) { + std::string file_data; + if (!ReadFileFromStorage(file_system, std::string(kOemCertificateFilePath), + &file_data)) { + LOGW("Unable to read OEM cert file from storage"); + return false; + } + File file; + CasStatus status = RetrieveHashedFile(file_data, file); + if (status.status_code() != CasStatusCode::kNoError) { + LOGW("Unable to retrieve certificate file: %s", + status.error_string().c_str()); + return false; + } + if (file.type() != File::OEM_CERTIFICATE) { + LOGE("Certificate file is of incorrect file type: type = %d", + static_cast(file.type())); + return false; + } + if (file.version() != File::VERSION_1) { + LOGE("Certificate file is of incorrect file version: version = %d", + static_cast(file.version())); + return false; + } + if (!file.has_oem_certificate()) { + LOGE("Certificate not present"); + return false; + } + + const video_widevine_client::sdk::OemCertificate& oem_certificate = file.oem_certificate(); + if (oem_certificate.certificate().empty() || + oem_certificate.wrapped_private_key().empty()) { + LOGE("Empty certificate or private key"); + return false; + } + + certificate = oem_certificate.certificate(); + wrapped_private_key->Clear(); + wrapped_private_key->set_key(oem_certificate.wrapped_private_key()); + switch (oem_certificate.key_type()) { + case video_widevine_client::sdk::OemCertificate::RSA: + wrapped_private_key->set_type(CryptoWrappedKey::kRsa); + break; + case video_widevine_client::sdk::OemCertificate::ECC: + wrapped_private_key->set_type(CryptoWrappedKey::kEcc); + break; + default: + LOGW("Unknown key type, defaulting to RSA: type = %d", + oem_certificate.key_type()); + wrapped_private_key->set_type(CryptoWrappedKey::kRsa); + break; + } + return true; +} + +} // namespace wvcas \ No newline at end of file diff --git a/plugin/src/oemcrypto_interface.cpp b/plugin/src/oemcrypto_interface.cpp index bd915c7..ef2b26b 100644 --- a/plugin/src/oemcrypto_interface.cpp +++ b/plugin/src/oemcrypto_interface.cpp @@ -115,6 +115,20 @@ class OEMCryptoInterface::Impl { size_t* key_token_length); typedef OEMCryptoResult (*GetSignatureHashAlgorithm_t)( OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm); + typedef OEMCryptoResult (*GetBootCertificateChain_t)( + uint8_t* bcc, size_t* bcc_length, uint8_t* additional_signature, + size_t* additional_signature_length); + typedef OEMCryptoResult (*GenerateCertificateKeyPair_t)( + OEMCrypto_SESSION session, uint8_t* public_key, size_t* public_key_length, + uint8_t* public_key_signature, size_t* public_key_signature_length, + uint8_t* wrapped_private_key, size_t* wrapped_private_key_length, + OEMCrypto_PrivateKeyType* key_type); + typedef OEMCryptoResult (*InstallOemPrivateKey_t)( + OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type, + const uint8_t* wrapped_private_key, size_t wrapped_private_key_length); + typedef uint8_t (*Security_Patch_Level_t)(); + typedef OEMCryptoResult (*BuildInformation_t)(char* buffer, + size_t* buffer_length); Initialize_t Initialize = nullptr; Terminate_t Terminate = nullptr; @@ -146,6 +160,11 @@ class OEMCryptoInterface::Impl { APIVersion_t APIVersion = nullptr; GetOEMKeyToken_t GetOEMKeyToken = nullptr; GetSignatureHashAlgorithm_t GetSignatureHashAlgorithm = nullptr; + GetBootCertificateChain_t GetBootCertificateChain = nullptr; + GenerateCertificateKeyPair_t GenerateCertificateKeyPair = nullptr; + InstallOemPrivateKey_t InstallOemPrivateKey = nullptr; + Security_Patch_Level_t Security_Patch_Level = nullptr; + BuildInformation_t BuildInformation = nullptr; private: bool initialize() { @@ -190,6 +209,11 @@ class OEMCryptoInterface::Impl { LOAD_SYM(ReassociateEntitledKeySession); TRY_LOAD_SYM(GetOEMKeyToken); TRY_LOAD_SYM(GetSignatureHashAlgorithm); + LOAD_SYM(GetBootCertificateChain); + LOAD_SYM(GenerateCertificateKeyPair); + LOAD_SYM(InstallOemPrivateKey); + LOAD_SYM(Security_Patch_Level); + LOAD_SYM(BuildInformation); // Optional methods that may be available. TRY_LOAD_SYM(LoadTestKeybox); @@ -404,10 +428,44 @@ OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetSignatureHashAlgorithm( OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm) const { // Optional method. Handle missing method. - if (impl_->GetOEMKeyToken == nullptr) { + if (impl_->GetSignatureHashAlgorithm == nullptr) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } return impl_->GetSignatureHashAlgorithm(session, algorithm); } +OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetBootCertificateChain( + uint8_t* bcc, size_t* bcc_length, uint8_t* additional_signature, + size_t* additional_signature_length) { + return impl_->GetBootCertificateChain(bcc, bcc_length, additional_signature, + additional_signature_length); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_GenerateCertificateKeyPair( + OEMCrypto_SESSION session, uint8_t* public_key, size_t* public_key_length, + uint8_t* public_key_signature, size_t* public_key_signature_length, + uint8_t* wrapped_private_key, size_t* wrapped_private_key_length, + OEMCrypto_PrivateKeyType* key_type) { + return impl_->GenerateCertificateKeyPair( + session, public_key, public_key_length, public_key_signature, + public_key_signature_length, wrapped_private_key, + wrapped_private_key_length, key_type); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_InstallOemPrivateKey( + OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type, + const uint8_t* wrapped_private_key, size_t wrapped_private_key_length) { + return impl_->InstallOemPrivateKey(session, key_type, wrapped_private_key, + wrapped_private_key_length); +} + +uint8_t OEMCryptoInterface::OEMCrypto_Security_Patch_Level() { + return impl_->Security_Patch_Level(); +} + +OEMCryptoResult OEMCryptoInterface::OEMCrypto_BuildInformation( + char* buffer, size_t* buffer_length) { + return impl_->BuildInformation(buffer, buffer_length); +} + } // namespace wvcas diff --git a/plugin/src/policy_engine.cpp b/plugin/src/policy_engine.cpp index eb885d3..ac9c771 100644 --- a/plugin/src/policy_engine.cpp +++ b/plugin/src/policy_engine.cpp @@ -12,8 +12,8 @@ #include "string_conversions.h" // TODO(vtarasov): -//#include "properties.h" -//#include "cas_event_listener.h" +// #include "properties.h" +// #include "cas_event_listener.h" using video_widevine::License; diff --git a/plugin/src/privacy_crypto_boringssl.cpp b/plugin/src/privacy_crypto_boringssl.cpp new file mode 100644 index 0000000..322641f --- /dev/null +++ b/plugin/src/privacy_crypto_boringssl.cpp @@ -0,0 +1,489 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Description: +// Definition of classes representing RSA public keys used +// for signature verification and encryption and decryption. +// +#include "privacy_crypto.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" + +namespace { +const int kPssSaltLength = 20; +const int kRsaPkcs1OaepPaddingLength = 41; + +RSA* GetKey(const std::string& serialized_key) { + BIO* bio = BIO_new_mem_buf(const_cast(serialized_key.data()), + static_cast(serialized_key.size())); + if (bio == nullptr) { + LOGE("BIO_new_mem_buf failed: returned null"); + return nullptr; + } + RSA* key = d2i_RSAPublicKey_bio(bio, nullptr); + + if (key == nullptr) { + LOGE("RSA key deserialization failure: %s", + ERR_error_string(ERR_get_error(), nullptr)); + BIO_free(bio); + return nullptr; + } + + BIO_free(bio); + return key; +} + +void FreeKey(RSA* key) { + if (key != nullptr) { + RSA_free(key); + } +} + +template +class boringssl_ptr { + public: + explicit boringssl_ptr(T* p = nullptr) : ptr_(p) {} + ~boringssl_ptr() { + if (ptr_) func(ptr_); + } + T& operator*() const { return *ptr_; } + T* operator->() const { return ptr_; } + T* get() const { return ptr_; } + explicit operator bool() const { return ptr_ != nullptr; } + + private: + T* ptr_; +}; // class boringssl_ptr +} // namespace + +namespace wvcas { + +AesCbcKey::AesCbcKey() {} + +AesCbcKey::~AesCbcKey() {} + +bool AesCbcKey::Init(const std::string& key) { + if (key.size() != AES_BLOCK_SIZE) { + LOGE("Unexpected key size: size = %zu, expected = %d", key.size(), + AES_BLOCK_SIZE); + return false; + } + + key_ = key; + return true; +} + +bool AesCbcKey::Encrypt(const std::string& in, const std::string& iv, + std::string* out, bool has_padding) { + if (in.empty()) { + LOGE("No cleartext provided"); + return false; + } + if (iv.size() != AES_BLOCK_SIZE) { + LOGE("Invalid IV size: %zu", iv.size()); + return false; + } + if (out == nullptr) { + LOGE("Ciphertext output parameter |out| not provided"); + return false; + } + if (key_.empty()) { + LOGE("AES key not initialized"); + return false; + } + + EVP_CIPHER_CTX* evp_cipher_ctx = EVP_CIPHER_CTX_new(); + if (EVP_EncryptInit(evp_cipher_ctx, EVP_aes_128_cbc(), + reinterpret_cast(&key_[0]), + reinterpret_cast(&iv[0])) == 0) { + LOGE("AES CBC setup failure: %s", + ERR_error_string(ERR_get_error(), nullptr)); + EVP_CIPHER_CTX_free(evp_cipher_ctx); + return false; + } + if (!has_padding) EVP_CIPHER_CTX_set_padding(evp_cipher_ctx, 0); + + out->resize(in.size() + AES_BLOCK_SIZE); + int out_length = static_cast(out->size()); + if (EVP_EncryptUpdate(evp_cipher_ctx, reinterpret_cast(&(*out)[0]), + &out_length, + reinterpret_cast(in.data()), + static_cast(in.size())) == 0) { + LOGE("AES CBC encryption failure: %s", + ERR_error_string(ERR_get_error(), nullptr)); + EVP_CIPHER_CTX_free(evp_cipher_ctx); + return false; + } + + int padding = 0; + if (EVP_EncryptFinal_ex(evp_cipher_ctx, + reinterpret_cast(&(*out)[out_length]), + &padding) == 0) { + LOGE("PKCS7 padding failure: %s", + ERR_error_string(ERR_get_error(), nullptr)); + EVP_CIPHER_CTX_free(evp_cipher_ctx); + return false; + } + + EVP_CIPHER_CTX_free(evp_cipher_ctx); + out->resize(out_length + padding); + return true; +} + +bool AesCbcKey::Decrypt(const std::string& in, const std::string& iv, + std::string* out, bool has_padding) { + if (in.empty()) { + LOGE("No ciphertext provided"); + return false; + } + if (iv.size() != AES_BLOCK_SIZE) { + LOGE("Invalid IV size: %zu", iv.size()); + return false; + } + if (out == nullptr) { + LOGE("Ciphertext output parameter |out| not provided"); + return false; + } + if (key_.empty()) { + LOGE("AES key not initialized"); + return false; + } + + EVP_CIPHER_CTX* evp_cipher_ctx = EVP_CIPHER_CTX_new(); + if (EVP_DecryptInit(evp_cipher_ctx, EVP_aes_128_cbc(), + reinterpret_cast(&key_[0]), + reinterpret_cast(&iv[0])) == 0) { + LOGE("AES CBC setup failure: %s", + ERR_error_string(ERR_get_error(), nullptr)); + EVP_CIPHER_CTX_free(evp_cipher_ctx); + return false; + } + if (!has_padding) EVP_CIPHER_CTX_set_padding(evp_cipher_ctx, 0); + + out->resize(in.size() + AES_BLOCK_SIZE); + int out_length = static_cast(out->size()); + if (EVP_DecryptUpdate(evp_cipher_ctx, reinterpret_cast(&(*out)[0]), + &out_length, + reinterpret_cast(in.data()), + static_cast(in.size())) == 0) { + LOGE("AES CBC encryption failure: %s", + ERR_error_string(ERR_get_error(), nullptr)); + EVP_CIPHER_CTX_free(evp_cipher_ctx); + return false; + } + + int padding = 0; + if (EVP_DecryptFinal_ex(evp_cipher_ctx, + reinterpret_cast(&(*out)[out_length]), + &padding) == 0) { + LOGE("PKCS7 padding failure: %s", + ERR_error_string(ERR_get_error(), nullptr)); + EVP_CIPHER_CTX_free(evp_cipher_ctx); + return false; + } + + EVP_CIPHER_CTX_free(evp_cipher_ctx); + out->resize(out_length + padding); + return true; +} + +RsaPublicKey::RsaPublicKey() {} + +RsaPublicKey::~RsaPublicKey() {} + +bool RsaPublicKey::Init(const std::string& serialized_key) { + if (serialized_key.empty()) { + LOGE("No serialized key provided"); + return false; + } + + serialized_key_ = serialized_key; + return true; +} + +bool RsaPublicKey::Encrypt(const std::string& clear_message, + std::string* encrypted_message) { + if (clear_message.empty()) { + LOGE("Message to be encrypted is empty"); + return false; + } + if (encrypted_message == nullptr) { + LOGE("Output parameter |encrypted_message| not provided"); + return false; + } + if (serialized_key_.empty()) { + LOGE("RSA key not initialized"); + return false; + } + + RSA* key = GetKey(serialized_key_); + if (key == nullptr) { + // Error already logged by GetKey. + return false; + } + + int rsa_size = RSA_size(key); + if (static_cast(clear_message.size()) > + rsa_size - kRsaPkcs1OaepPaddingLength) { + LOGE("Message too large to be encrypted: message_size = %zu, max = %d", + clear_message.size(), rsa_size - kRsaPkcs1OaepPaddingLength); + FreeKey(key); + return false; + } + + encrypted_message->assign(rsa_size, 0); + if (RSA_public_encrypt( + static_cast(clear_message.size()), + const_cast( + reinterpret_cast(clear_message.data())), + reinterpret_cast(&(*encrypted_message)[0]), key, + RSA_PKCS1_OAEP_PADDING) != rsa_size) { + LOGE("RSA encryption failure: %s", + ERR_error_string(ERR_get_error(), nullptr)); + FreeKey(key); + return false; + } + + FreeKey(key); + return true; +} + +// LogBoringSSLError is a callback from BoringSSL which is called with each +// error in the thread's error queue. +static int LogBoringSSLError(const char* msg, size_t /* length */, + void* /* user_data */) { + LOGE(" BoringSSL Error: %s", msg); + return 1; +} + +static bool VerifyPSSSignature(EVP_PKEY* pkey, const std::string& message, + const std::string& signature) { + EVP_MD_CTX* evp_md_ctx = EVP_MD_CTX_new(); + EVP_PKEY_CTX* pctx = nullptr; + + if (EVP_DigestVerifyInit(evp_md_ctx, &pctx, EVP_sha1(), + nullptr /* no ENGINE */, pkey) != 1) { + LOGE("EVP_DigestVerifyInit failed"); + goto err; + } + + if (EVP_PKEY_CTX_set_signature_md(pctx, const_cast(EVP_sha1())) != + 1) { + LOGE("EVP_PKEY_CTX_set_signature_md failed"); + goto err; + } + + if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) != 1) { + LOGE("EVP_PKEY_CTX_set_rsa_padding failed"); + goto err; + } + + if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, kPssSaltLength) != 1) { + LOGE("EVP_PKEY_CTX_set_rsa_pss_saltlen failed"); + goto err; + } + + if (EVP_DigestVerifyUpdate(evp_md_ctx, message.data(), message.size()) != 1) { + LOGE("EVP_DigestVerifyUpdate failed"); + goto err; + } + + if (EVP_DigestVerifyFinal( + evp_md_ctx, + const_cast( + reinterpret_cast(signature.data())), + signature.size()) != 1) { + LOGE("EVP_DigestVerifyFinal failed (Probably a bad signature)"); + goto err; + } + + EVP_MD_CTX_free(evp_md_ctx); + return true; + +err: + ERR_print_errors_cb(LogBoringSSLError, nullptr); + EVP_MD_CTX_free(evp_md_ctx); + return false; +} + +bool RsaPublicKey::VerifySignature(const std::string& message, + const std::string& signature) { + if (serialized_key_.empty()) { + LOGE("RSA key not initialized"); + return false; + } + if (message.empty()) { + LOGE("Signed message is empty"); + return false; + } + RSA* rsa_key = GetKey(serialized_key_); + if (rsa_key == nullptr) { + // Error already logged by GetKey. + return false; + } + EVP_PKEY* pkey = EVP_PKEY_new(); + if (pkey == nullptr) { + LOGE("EVP_PKEY allocation failed"); + FreeKey(rsa_key); + return false; + } + if (EVP_PKEY_set1_RSA(pkey, rsa_key) != 1) { + LOGE("Failed to wrap key in an EVP_PKEY"); + FreeKey(rsa_key); + EVP_PKEY_free(pkey); + return false; + } + FreeKey(rsa_key); + + const bool ok = VerifyPSSSignature(pkey, message, signature); + EVP_PKEY_free(pkey); + + if (!ok) { + LOGE("RSA verify failure"); + return false; + } + + return true; +} + +bool ExtractExtensionValueFromCertificate(const std::string& cert, + const std::string& expected_oid, + size_t cert_index, uint32_t* value) { + // Convert the OID to OBJ form + boringssl_ptr expected_obj( + OBJ_txt2obj(expected_oid.c_str(), true)); + + // The cert is a PKCS7 signed data type. First, parse it into an OpenSSL + // structure and find the certificate list. + // + // We must make defensive copies of cert's properties because of how + // d2i_PKCS7() works. + const unsigned char* cert_data = + reinterpret_cast(cert.data()); + long cert_size = static_cast(cert.size()); + boringssl_ptr pkcs7( + d2i_PKCS7(nullptr, &cert_data, cert_size)); + if (!pkcs7) { + LOGE("Error parsing PKCS7 message"); + return false; + } else if (!PKCS7_type_is_signed(pkcs7.get())) { + LOGE("Unexpected PKCS7 message type"); + return false; + } + + STACK_OF(X509)* certs = pkcs7->d.sign->cert; + + // Find the desired certificate from the stack. + const size_t num_certs = static_cast(sk_X509_num(certs)); + if (cert_index >= num_certs) { + LOGE( + "Unexpected number of certificates in chain: " + "count = %zu, minimum = %zu", + num_certs, cert_index + 1); + return false; + } + + X509* const intermediate_cert = + sk_X509_value(certs, static_cast(cert_index)); + if (intermediate_cert == nullptr) { + LOGE("Unable to get intermediate cert"); + return false; + } + + // Find the Widevine System ID extension in the intermediate cert + const int extension_count = X509_get_ext_count(intermediate_cert); + for (int i = 0; i < extension_count; ++i) { + X509_EXTENSION* const extension = X509_get_ext(intermediate_cert, i); + if (extension == nullptr) { + LOGE("Unable to get cert extension: cert_index = %d", i); + continue; + } + + ASN1_OBJECT* const extension_obj = X509_EXTENSION_get_object(extension); + if (extension_obj == nullptr) { + LOGE("Unable to get cert extension OID: cert_index = %d", i); + continue; + } + + if (OBJ_cmp(extension_obj, expected_obj.get()) != 0) { + // This extension is not the Widevine System ID, so we should move on to + // the next one. + continue; + } + + ASN1_OCTET_STRING* const octet_str = X509_EXTENSION_get_data(extension); + if (octet_str == nullptr) { + LOGE("Unable to get data of cert extension"); + return false; + } + + const unsigned char* data = octet_str->data; + if (data == nullptr) { + LOGE("Null data in cert extension"); + return false; + } + + ASN1_INTEGER* const asn1_integer = + d2i_ASN1_INTEGER(nullptr, &data, octet_str->length); + if (asn1_integer == nullptr) { + LOGE("Unable to decode data in cert extension"); + return false; + } + + const long system_id_long = ASN1_INTEGER_get(asn1_integer); + ASN1_INTEGER_free(asn1_integer); + if (system_id_long == -1) { + LOGE("Unable to decode ASN integer in extension"); + return false; + } + + *value = static_cast(system_id_long); + return true; + } + + LOGE("Cert extension not found"); + return false; +} + +std::string Md5Hash(const std::string& data) { + std::string hash(MD5_DIGEST_LENGTH, '\0'); + MD5(reinterpret_cast(data.data()), data.size(), + reinterpret_cast(&hash[0])); + return hash; +} + +std::string Sha1Hash(const std::string& data) { + std::string hash(SHA_DIGEST_LENGTH, '\0'); + SHA1(reinterpret_cast(data.data()), data.size(), + reinterpret_cast(&hash[0])); + return hash; +} + +std::string Sha256Hash(const std::string& data) { + std::string hash(SHA256_DIGEST_LENGTH, '\0'); + SHA256(reinterpret_cast(data.data()), data.size(), + reinterpret_cast(&hash[0])); + return hash; +} + +std::string Sha512Hash(const std::string& data) { + std::string hash(SHA512_DIGEST_LENGTH, '\0'); + SHA512(reinterpret_cast(data.data()), data.size(), + reinterpret_cast(&hash[0])); + return hash; +} + +} // namespace wvcas \ No newline at end of file diff --git a/plugin/src/service_certificate.cpp b/plugin/src/service_certificate.cpp new file mode 100644 index 0000000..021c10a --- /dev/null +++ b/plugin/src/service_certificate.cpp @@ -0,0 +1,284 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#include "service_certificate.h" + +#include + +#include +#include + +#include "crypto_session.h" +#include "license_protocol.pb.h" +#include "log.h" +#include "privacy_crypto.h" + +namespace { +// Root certificate for all Google/Widevine certificates. I.e. all service +// certificates and DRM certificates are signed by this cert, or have this cert +// as the root of a signing chain. + +// clang-format off +const uint8_t kRootCertForProd[] = { + 0x0a, 0x9c, 0x03, 0x08, 0x00, 0x12, 0x01, 0x00, + 0x18, 0xdd, 0x94, 0x88, 0x8b, 0x05, 0x22, 0x8e, + 0x03, 0x30, 0x82, 0x01, 0x8a, 0x02, 0x82, 0x01, + 0x81, 0x00, 0xb4, 0xfe, 0x39, 0xc3, 0x65, 0x90, + 0x03, 0xdb, 0x3c, 0x11, 0x97, 0x09, 0xe8, 0x68, + 0xcd, 0xf2, 0xc3, 0x5e, 0x9b, 0xf2, 0xe7, 0x4d, + 0x23, 0xb1, 0x10, 0xdb, 0x87, 0x65, 0xdf, 0xdc, + 0xfb, 0x9f, 0x35, 0xa0, 0x57, 0x03, 0x53, 0x4c, + 0xf6, 0x6d, 0x35, 0x7d, 0xa6, 0x78, 0xdb, 0xb3, + 0x36, 0xd2, 0x3f, 0x9c, 0x40, 0xa9, 0x95, 0x26, + 0x72, 0x7f, 0xb8, 0xbe, 0x66, 0xdf, 0xc5, 0x21, + 0x98, 0x78, 0x15, 0x16, 0x68, 0x5d, 0x2f, 0x46, + 0x0e, 0x43, 0xcb, 0x8a, 0x84, 0x39, 0xab, 0xfb, + 0xb0, 0x35, 0x80, 0x22, 0xbe, 0x34, 0x23, 0x8b, + 0xab, 0x53, 0x5b, 0x72, 0xec, 0x4b, 0xb5, 0x48, + 0x69, 0x53, 0x3e, 0x47, 0x5f, 0xfd, 0x09, 0xfd, + 0xa7, 0x76, 0x13, 0x8f, 0x0f, 0x92, 0xd6, 0x4c, + 0xdf, 0xae, 0x76, 0xa9, 0xba, 0xd9, 0x22, 0x10, + 0xa9, 0x9d, 0x71, 0x45, 0xd6, 0xd7, 0xe1, 0x19, + 0x25, 0x85, 0x9c, 0x53, 0x9a, 0x97, 0xeb, 0x84, + 0xd7, 0xcc, 0xa8, 0x88, 0x82, 0x20, 0x70, 0x26, + 0x20, 0xfd, 0x7e, 0x40, 0x50, 0x27, 0xe2, 0x25, + 0x93, 0x6f, 0xbc, 0x3e, 0x72, 0xa0, 0xfa, 0xc1, + 0xbd, 0x29, 0xb4, 0x4d, 0x82, 0x5c, 0xc1, 0xb4, + 0xcb, 0x9c, 0x72, 0x7e, 0xb0, 0xe9, 0x8a, 0x17, + 0x3e, 0x19, 0x63, 0xfc, 0xfd, 0x82, 0x48, 0x2b, + 0xb7, 0xb2, 0x33, 0xb9, 0x7d, 0xec, 0x4b, 0xba, + 0x89, 0x1f, 0x27, 0xb8, 0x9b, 0x88, 0x48, 0x84, + 0xaa, 0x18, 0x92, 0x0e, 0x65, 0xf5, 0xc8, 0x6c, + 0x11, 0xff, 0x6b, 0x36, 0xe4, 0x74, 0x34, 0xca, + 0x8c, 0x33, 0xb1, 0xf9, 0xb8, 0x8e, 0xb4, 0xe6, + 0x12, 0xe0, 0x02, 0x98, 0x79, 0x52, 0x5e, 0x45, + 0x33, 0xff, 0x11, 0xdc, 0xeb, 0xc3, 0x53, 0xba, + 0x7c, 0x60, 0x1a, 0x11, 0x3d, 0x00, 0xfb, 0xd2, + 0xb7, 0xaa, 0x30, 0xfa, 0x4f, 0x5e, 0x48, 0x77, + 0x5b, 0x17, 0xdc, 0x75, 0xef, 0x6f, 0xd2, 0x19, + 0x6d, 0xdc, 0xbe, 0x7f, 0xb0, 0x78, 0x8f, 0xdc, + 0x82, 0x60, 0x4c, 0xbf, 0xe4, 0x29, 0x06, 0x5e, + 0x69, 0x8c, 0x39, 0x13, 0xad, 0x14, 0x25, 0xed, + 0x19, 0xb2, 0xf2, 0x9f, 0x01, 0x82, 0x0d, 0x56, + 0x44, 0x88, 0xc8, 0x35, 0xec, 0x1f, 0x11, 0xb3, + 0x24, 0xe0, 0x59, 0x0d, 0x37, 0xe4, 0x47, 0x3c, + 0xea, 0x4b, 0x7f, 0x97, 0x31, 0x1c, 0x81, 0x7c, + 0x94, 0x8a, 0x4c, 0x7d, 0x68, 0x15, 0x84, 0xff, + 0xa5, 0x08, 0xfd, 0x18, 0xe7, 0xe7, 0x2b, 0xe4, + 0x47, 0x27, 0x12, 0x11, 0xb8, 0x23, 0xec, 0x58, + 0x93, 0x3c, 0xac, 0x12, 0xd2, 0x88, 0x6d, 0x41, + 0x3d, 0xc5, 0xfe, 0x1c, 0xdc, 0xb9, 0xf8, 0xd4, + 0x51, 0x3e, 0x07, 0xe5, 0x03, 0x6f, 0xa7, 0x12, + 0xe8, 0x12, 0xf7, 0xb5, 0xce, 0xa6, 0x96, 0x55, + 0x3f, 0x78, 0xb4, 0x64, 0x82, 0x50, 0xd2, 0x33, + 0x5f, 0x91, 0x02, 0x03, 0x01, 0x00, 0x01, 0x12, + 0x80, 0x03, 0x58, 0xf1, 0xd6, 0x4d, 0x04, 0x09, + 0x7b, 0xdf, 0xd7, 0xef, 0x5d, 0x3b, 0x02, 0x39, + 0x17, 0xfa, 0x14, 0x36, 0x75, 0x4a, 0x38, 0x67, + 0x85, 0x57, 0x12, 0xa7, 0x14, 0xee, 0x35, 0x16, + 0xd5, 0x3d, 0xbf, 0x42, 0x86, 0xf6, 0x69, 0x00, + 0x76, 0xcd, 0x93, 0xf4, 0x7c, 0xb2, 0xdf, 0x9e, + 0x44, 0xcd, 0x4c, 0xd4, 0xae, 0x09, 0x18, 0x53, + 0x44, 0x32, 0xec, 0xe0, 0x61, 0x1b, 0xe5, 0xda, + 0x13, 0xd3, 0x55, 0xc5, 0xdd, 0x1a, 0xcb, 0x90, + 0x1e, 0x7e, 0x5b, 0xc6, 0xe9, 0x0f, 0x22, 0x9f, + 0xbe, 0x85, 0x02, 0xfe, 0x90, 0x31, 0xcc, 0x6b, + 0x03, 0x84, 0xbd, 0x22, 0xc4, 0x55, 0xfa, 0xf5, + 0xf2, 0x08, 0xcd, 0x65, 0x41, 0x58, 0xe8, 0x7d, + 0x29, 0xda, 0x04, 0x58, 0x82, 0xf5, 0x37, 0x69, + 0xbc, 0xf3, 0x5a, 0x57, 0x84, 0x17, 0x7b, 0x32, + 0x87, 0x70, 0xb2, 0xb0, 0x76, 0x9c, 0xb2, 0xc3, + 0x15, 0xd1, 0x11, 0x26, 0x2a, 0x23, 0x75, 0x99, + 0x3e, 0xb9, 0x77, 0x22, 0x32, 0x0d, 0xbc, 0x1a, + 0x19, 0xc1, 0xd5, 0x65, 0x90, 0x76, 0x55, 0x74, + 0x0f, 0x0e, 0x69, 0x4d, 0x5f, 0x4d, 0x8f, 0x19, + 0xaf, 0xdf, 0xd6, 0x16, 0x31, 0x94, 0xa8, 0x92, + 0x5f, 0x4f, 0xbc, 0x7a, 0x31, 0xf8, 0xae, 0x8e, + 0xad, 0x33, 0xb7, 0xe9, 0x30, 0xd0, 0x8c, 0x0a, + 0x8a, 0x6c, 0x83, 0x35, 0xf8, 0x8a, 0x81, 0xb2, + 0xfe, 0x1c, 0x88, 0xac, 0x2a, 0x66, 0xc5, 0xff, + 0xbd, 0xe6, 0x17, 0xd0, 0x62, 0x0b, 0xdc, 0x8a, + 0x45, 0xf7, 0xb0, 0x3e, 0x5a, 0xc8, 0x1e, 0x4a, + 0x24, 0x2f, 0x6c, 0xa5, 0xe3, 0x1c, 0x88, 0x14, + 0x83, 0xd5, 0xc5, 0xef, 0x5e, 0x9f, 0x3d, 0x85, + 0x45, 0x73, 0xe2, 0x6b, 0x50, 0x52, 0x57, 0x4c, + 0xfb, 0x92, 0x6c, 0x66, 0x75, 0x8a, 0xd6, 0x0d, + 0x1b, 0xae, 0xf3, 0xec, 0xaf, 0x51, 0x22, 0x03, + 0x5d, 0x0a, 0x2e, 0x63, 0x93, 0x9c, 0x0b, 0x01, + 0x20, 0xa8, 0xa9, 0x84, 0x2e, 0x17, 0xca, 0xae, + 0x73, 0xec, 0x22, 0x1b, 0x79, 0xae, 0xf6, 0xa0, + 0x72, 0x2c, 0xdf, 0x07, 0x47, 0xdb, 0x88, 0x86, + 0x30, 0x14, 0x78, 0x21, 0x11, 0x22, 0x88, 0xac, + 0xd7, 0x54, 0x74, 0xf9, 0xf3, 0x26, 0xc2, 0xa5, + 0x56, 0xc8, 0x56, 0x4f, 0x00, 0x29, 0x1d, 0x08, + 0x7b, 0x7a, 0xfb, 0x95, 0x89, 0xc3, 0xee, 0x98, + 0x54, 0x9e, 0x3c, 0x6b, 0x94, 0x05, 0x13, 0x12, + 0xf6, 0x71, 0xb9, 0xab, 0x13, 0xc3, 0x0c, 0x9b, + 0x46, 0x08, 0x7b, 0x3d, 0x32, 0x6a, 0x68, 0xca, + 0x1e, 0x9c, 0x90, 0x62, 0xc5, 0xed, 0x10, 0xb9, + 0x1f, 0x17, 0x25, 0xce, 0x90, 0xb9, 0x6d, 0xcd, + 0xc4, 0x46, 0xf5, 0xa3, 0x62, 0x13, 0x74, 0x02, + 0xa7, 0x62, 0xa4, 0xfa, 0x55, 0xd9, 0xde, 0xcf, + 0xa2, 0xe6, 0x80, 0x74, 0x55, 0x06, 0x49, 0xd5, + 0x02, 0x0c +}; +// clang-format on +constexpr size_t kRootCertForProdSize = sizeof(kRootCertForProd); + +constexpr size_t KEY_IV_SIZE = 16; +constexpr size_t SERVICE_KEY_SIZE = 16; + +unsigned int GetDeviceRandomSeed() { + static std::random_device rdev; + return rdev(); +} + +std::string RandomData(size_t length) { + std::uniform_int_distribution dist(0, 255); // Range of [0, 255]. + std::string random_data(length, '\0'); + std::default_random_engine generator(GetDeviceRandomSeed()); + std::generate(random_data.begin(), random_data.end(), + [&]() { return static_cast(dist(generator)); }); + return random_data; +} + +} // namespace + +namespace wvcas { + +// Protobuf generated classes. +using video_widevine::ClientIdentification; +using video_widevine::DrmCertificate; +using video_widevine::EncryptedClientIdentification; +using video_widevine::LicenseError; +using video_widevine::SignedDrmCertificate; +using video_widevine::SignedMessage; + +CasStatus ServiceCertificate::Init(const std::string& signed_certificate) { + const std::string root_cert_str(kRootCertForProd, + kRootCertForProd + kRootCertForProdSize); + + // Load root cert public key. Don't bother verifying it. + SignedDrmCertificate signed_root_cert; + if (!signed_root_cert.ParseFromString(root_cert_str)) { + LOGE("Failed to deserialize signed root certificate"); + return CasStatus(CasStatusCode::kDeviceCertificateError, + "Failed to deserialize signed root certificate"); + } + video_widevine::DrmCertificate root_cert; + if (!root_cert.ParseFromString(signed_root_cert.drm_certificate())) { + LOGE("Failed to deserialize root certificate"); + return CasStatus(CasStatusCode::kDeviceCertificateError, + "Failed to deserialize root certificate"); + } + RsaPublicKey root_key; + if (!root_key.Init(root_cert.public_key())) { + LOGE("Failed to load root certificate public key"); + return CasStatus(CasStatusCode::kDeviceCertificateError, + "Failed to load root certificate public key"); + } + + // Load the provided service certificate. + // First, parse it and verify its signature. + SignedDrmCertificate signed_service_cert; + if (!signed_service_cert.ParseFromString(signed_certificate)) { + LOGE("Failed to parse signed service certificate"); + return CasStatus(CasStatusCode::kDeviceCertificateError, + "Failed to parse signed service certificate"); + } + + if (!root_key.VerifySignature(signed_service_cert.drm_certificate(), + signed_service_cert.signature())) { + LOGE("Failed to verify service certificate signature"); + return CasStatus(CasStatusCode::kDeviceCertificateError, + "Failed to verify service certificate signature"); + } + + video_widevine::DrmCertificate service_cert; + if (!service_cert.ParseFromString(signed_service_cert.drm_certificate())) { + LOGE("Failed to parse service certificate"); + return CasStatus(CasStatusCode::kDeviceCertificateError, + "Failed to parse service certificate"); + } + if (service_cert.type() != video_widevine::DrmCertificate_Type_SERVICE) { + LOGE( + "DRM device certificate type is not a service certificate: " + "type = %d, expected_type = %d", + static_cast(service_cert.type()), + static_cast(video_widevine::DrmCertificate_Type_SERVICE)); + return CasStatus( + CasStatusCode::kDeviceCertificateError, + "DRM device certificate type is not a service certificate"); + } + + // Service certificate passes all checks - set up its RSA public key. + public_key_ = std::make_unique(); + if (!public_key_->Init(service_cert.public_key())) { + public_key_.reset(); + LOGE("Failed to load service certificate public key"); + return CasStatus(CasStatusCode::kDeviceCertificateError, + "Failed to load service certificate public key"); + } + + // Have service certificate and its public key - keep relevant fields. + signed_certificate_ = signed_certificate; + serial_number_ = service_cert.serial_number(); + provider_id_ = service_cert.provider_id(); + + return CasStatusCode::kNoError; +} + +CasStatus ServiceCertificate::EncryptRsaOaep(const std::string& plaintext, + std::string* ciphertext) const { + if (!public_key_) { + LOGE("Service certificate not set"); + return CasStatus(CasStatusCode::kDeviceCertificateError, + "Service certificate not set"); + } + + if (!public_key_->Encrypt(plaintext, ciphertext)) { + return CasStatus(CasStatusCode::kDeviceCertificateError, + "CLIENT_ID_RSA_ENCRYPT_ERROR"); + } + return CasStatusCode::kNoError; +} + +CasStatus ServiceCertificate::EncryptClientId( + const ClientIdentification* clear_client_id, + EncryptedClientIdentification* encrypted_client_id) const { + encrypted_client_id->set_provider_id(provider_id_); + encrypted_client_id->set_service_certificate_serial_number(serial_number_); + + std::string iv = RandomData(KEY_IV_SIZE); + std::string key = RandomData(SERVICE_KEY_SIZE); + if (key.length() != SERVICE_KEY_SIZE || iv.length() != KEY_IV_SIZE) { + LOGE("RandomData failed for key or iv."); + return CasStatus(CasStatusCode::kClientIdEncryptionError, + "RandomData failed for key or iv."); + } + std::string id, enc_id, enc_key; + clear_client_id->SerializeToString(&id); + + AesCbcKey aes; + if (!aes.Init(key)) { + return CasStatus(CasStatusCode::kClientIdEncryptionError, + "CLIENT_ID_AES_INIT_ERROR"); + } + if (!aes.Encrypt(id, iv, &enc_id)) { + return CasStatus(CasStatusCode::kClientIdEncryptionError, + "CLIENT_ID_AES_ENCRYPT_ERROR"); + } + + CasStatus encrypt_result = EncryptRsaOaep(key, &enc_key); + if (!encrypt_result.ok()) { + return encrypt_result; + } + + encrypted_client_id->set_encrypted_client_id_iv(iv); + encrypted_client_id->set_encrypted_privacy_key(enc_key); + encrypted_client_id->set_encrypted_client_id(enc_id); + return CasStatusCode::kNoError; +} + +} // namespace wvcas \ No newline at end of file diff --git a/plugin/src/widevine_cas_api.cpp b/plugin/src/widevine_cas_api.cpp index 6b89e93..f17d3eb 100644 --- a/plugin/src/widevine_cas_api.cpp +++ b/plugin/src/widevine_cas_api.cpp @@ -7,6 +7,7 @@ #include "cas_events.h" #include "cas_util.h" +#include "file_util.h" #include "license_protocol.pb.h" #include "log.h" #include "media_cas.pb.h" @@ -18,55 +19,6 @@ constexpr char kCertFileBase[] = "cert.bin"; constexpr char kLicenseFileNameSuffix[] = ".lic"; namespace { -bool ReadFileFromStorage(wvutil::FileSystem& file_system, - const std::string& filename, std::string* file_data) { - if (nullptr == file_data) { - return false; - } - if (!file_system.Exists(filename)) { - return false; - } - size_t filesize = file_system.FileSize(filename); - if (0 == filesize) { - return false; - } - file_data->resize(filesize); - std::unique_ptr file = - file_system.Open(filename, wvutil::FileSystem::kReadOnly); - if (nullptr == file) { - return false; - } - size_t bytes_read = file->Read(&(*file_data)[0], file_data->size()); - if (bytes_read != filesize) { - return false; - } - return true; -} - -bool RemoveFile(wvutil::FileSystem& file_system, const std::string& filename) { - if (!file_system.Exists(filename)) { - return false; - } - if (!file_system.Remove(filename)) { - return false; - } - return true; -} - -bool StoreFile(wvutil::FileSystem& file_system, const std::string& filename, - const std::string& file_data) { - std::unique_ptr file(file_system.Open( - filename, wvutil::FileSystem::kTruncate | wvutil::FileSystem::kCreate)); - if (nullptr == file) { - return false; - } - size_t bytes_written = file->Write(file_data.data(), file_data.size()); - if (bytes_written != file_data.size()) { - return false; - } - return true; -} - std::string GenerateLicenseFilename(const std::string& content_id, const std::string& provider_id) { std::string data(content_id + provider_id); @@ -215,6 +167,7 @@ void WidevineCas::OnTimerEvent() { CasStatus WidevineCas::initialize(CasEventListener* event_listener) { std::unique_lock locker(lock_); crypto_session_ = getCryptoSession(); + wrapped_private_key_ = std::make_unique(); // For session name generation. srand(time(nullptr)); // Setup an oemcrypto session. @@ -241,7 +194,7 @@ CasStatus WidevineCas::initialize(CasEventListener* event_listener) { if (ReadFileFromStorage(*file_system_, cert_filename_path, &cert_file)) { LOGI("read cert.bin successfully"); if (!HandleStoredDrmCert(cert_file).ok()) { - return CasStatusCode::kNoError; + LOGW("HandleStoredDrmCert failed"); } } @@ -404,7 +357,8 @@ CasStatus WidevineCas::generateDeviceProvisioningRequest( return CasStatus(CasStatusCode::kInvalidParameter, "missing output buffer for provisioning request"); } - return cas_license_->GenerateDeviceProvisioningRequest(provisioning_request); + return cas_license_->GenerateDeviceProvisioningRequest(*file_system_, + provisioning_request); } CasStatus WidevineCas::handleProvisioningResponse(const std::string& response) { @@ -412,21 +366,10 @@ CasStatus WidevineCas::handleProvisioningResponse(const std::string& response) { return CasStatus(CasStatusCode::kCasLicenseError, "empty individualization response"); } - std::string device_file; std::unique_lock locker(lock_); - CasStatus status = cas_license_->HandleDeviceProvisioningResponse( - response, &device_certificate_, &wrapped_rsa_key_, &device_file); - - if (!status.ok()) { - return status; - } - - if (!device_file.empty()) { - std::string cert_filename(std::string(kBasePathPrefix) + - std::string(kCertFileBase)); - StoreFile(*file_system_, cert_filename, device_file); - } - return CasStatusCode::kNoError; + return cas_license_->HandleDeviceProvisioningResponse( + file_system_.get(), response, &device_certificate_, + wrapped_private_key_.get()); } CasStatus WidevineCas::generateEntitlementRequest( @@ -465,6 +408,7 @@ CasStatus WidevineCas::generateEntitlementRequest( return CasStatus(CasStatusCode::kCasLicenseError, "license is expired, unable to process emm"); } + license_id = filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix)); if (cas_license_->IsGroupLicense()) { @@ -483,9 +427,8 @@ CasStatus WidevineCas::generateEntitlementRequest( return CasStatus(CasStatusCode::kInvalidParameter, "missing output buffer for entitlement request"); } - std::unique_lock locker(lock_); return cas_license_->GenerateEntitlementRequest( - init_data, device_certificate_, wrapped_rsa_key_, license_type_, + init_data, device_certificate_, *wrapped_private_key_, license_type_, entitlement_request); } @@ -609,7 +552,7 @@ CasStatus WidevineCas::generateEntitlementPeriodUpdateRequest( std::string entitlement_request; status = next_cas_license_->GenerateEntitlementRequest( - init_data, device_certificate_, wrapped_rsa_key_, license_type_, + init_data, device_certificate_, *wrapped_private_key_, license_type_, &entitlement_request); if (!status.ok()) { LOGE("WidevineCas generate entitlement request failed: %d", @@ -699,7 +642,8 @@ CasStatus WidevineCas::RemoveLicense(const std::string& file_name) { } bool WidevineCas::is_provisioned() const { - return (!(device_certificate_.empty() || wrapped_rsa_key_.empty())); + return !device_certificate_.empty() && wrapped_private_key_ != nullptr && + wrapped_private_key_->IsValid(); } CasStatus WidevineCas::ProcessCAPrivateData(const CasData& private_data, @@ -749,7 +693,7 @@ CasStatus WidevineCas::HandleStoredDrmCert(const std::string& certificate) { return CasStatus(CasStatusCode::kCasLicenseError, "empty certificate data"); } CasStatus status = cas_license_->HandleStoredDrmCert( - certificate, &device_certificate_, &wrapped_rsa_key_); + certificate, &device_certificate_, wrapped_private_key_.get()); return status; } @@ -893,7 +837,7 @@ bool WidevineCas::TryReuseStoredLicense(const std::string& filename) { // Load the stored license to the session. CasStatus status = - cas_license_->HandleStoredLicense(wrapped_rsa_key_, license_file); + cas_license_->HandleStoredLicense(*wrapped_private_key_, license_file); if (!status.ok()) { LOGW("Failed to load stored license. code: %d, message: %s", status.status_code(), status.error_string().c_str()); diff --git a/plugin/src/widevine_media_cas_plugin.cpp b/plugin/src/widevine_media_cas_plugin.cpp index 863ec5a..9434642 100644 --- a/plugin/src/widevine_media_cas_plugin.cpp +++ b/plugin/src/widevine_media_cas_plugin.cpp @@ -224,6 +224,10 @@ status_t WidevineCasPlugin::provision(const String8& provisionString) { return OK; } + return provisionInternal(); +} + +status_t WidevineCasPlugin::provisionInternal() { std::string provisioning_request; CasStatus status = widevine_cas_api_->generateDeviceProvisioningRequest( &provisioning_request); @@ -247,8 +251,8 @@ status_t WidevineCasPlugin::requestLicense(const std::string& init_data) { CasStatus status = widevine_cas_api_->generateEntitlementRequest( init_data, &signed_license_request, license_id); if (!status.ok()) { - LOGE("WidevineCas generate entitlement request failed: %d", - status.status_code()); + LOGE("WidevineCas generate entitlement request failed: %d: %s", + status.status_code(), status.error_string().c_str()); return INVALID_OPERATION; } @@ -317,6 +321,13 @@ CasStatus WidevineCasPlugin::HandleIndividualizationResponse( return status; } + // Provisioning 4 has two stages of provisioning. + if (!is_provisioned()) { + // Return value ingored here. + provisionInternal(); + return CasStatusCode::kNoError; + } + CallBack(reinterpret_cast(app_data_), INDIVIDUALIZATION_COMPLETE, 0, nullptr, 0, nullptr); diff --git a/protos/device_files.proto b/protos/device_files.proto index 9ca0b99..c50d152 100644 --- a/protos/device_files.proto +++ b/protos/device_files.proto @@ -20,9 +20,25 @@ message NameValue { optional string value = 2; } -message DeviceCertificate { +message OemCertificate { + enum PrivateKeyType { + RSA = 0; + ECC = 1; + } optional bytes certificate = 1; optional bytes wrapped_private_key = 2; + optional PrivateKeyType key_type = 3 [default = RSA]; +} + +// DRM certificate. +message DeviceCertificate { + enum PrivateKeyType { + RSA = 0; + ECC = 1; + } + optional bytes certificate = 1; + optional bytes wrapped_private_key = 2; + optional PrivateKeyType key_type = 3 [default = RSA]; } message License { @@ -95,6 +111,8 @@ message File { USAGE_INFO = 3; HLS_ATTRIBUTES = 4; USAGE_TABLE_INFO = 5; + OKP_INFO = 6; + OEM_CERTIFICATE = 7; } enum FileVersion { VERSION_1 = 1; } @@ -106,6 +124,8 @@ message File { optional UsageInfo usage_info = 5; optional HlsAttributes hls_attributes = 6; optional UsageTableInfo usage_table_info = 7; + reserved 8; + optional OemCertificate oem_certificate = 9; } message HashedFile { diff --git a/protos/license_protocol.proto b/protos/license_protocol.proto index 0ec2482..aaa8ed4 100644 --- a/protos/license_protocol.proto +++ b/protos/license_protocol.proto @@ -580,6 +580,30 @@ message GroupKeys { // Public protocol buffer definitions for Widevine Device Certificate // Provisioning protocol. +// A KeyToCertify contains a client generated public key to be incorporated into +// a signed certificate. +message PublicKeyToCertify { + // A KeyType indicates a high level key type. + enum KeyType { + KEY_TYPE_UNSPECIFIED = 0; + RSA = 1; + ECC = 2; + } + + // |public_key| contains the bytes of an ASN.1 DER-encoded + // SubjectPublicKeyInfo as specified in RFC 5280. + optional bytes public_key = 1; + // KeyType contains a highlevel hint to use in parsing the serialized key + // contained in |public_key|. If the key is an EC key, curve parameters can be + // extracted from the deserialized key. + // Keys are expected to match the certificate key type in the device record. + optional KeyType key_type = 2; + // The signature of |public_key|. + // Keys that are signed using ECDSA or RSA should hash the message using + // SHA-256. + optional bytes signature = 3; +} + // ProvisioningOptions specifies the type of certificate to specify and // in the case of X509 certificates, the certificate authority to use. message ProvisioningOptions { @@ -642,6 +666,10 @@ message ProvisioningRequest { // SessionKeys encrypted using a service cert public key. // Required for keybox provisioning. optional EncryptedSessionKeys encrypted_session_keys = 8; + // Specifies the public key that should be certified by the provisioning + // server. The client holds the private key. If specified, the response no + // longer needs to contain server generated |device_rsa_key|. + optional PublicKeyToCertify certificate_public_key = 10; } // Provisioning response sent by the provisioning server to client devices. @@ -758,6 +786,7 @@ message ClientIdentification { DRM_DEVICE_CERTIFICATE = 1; REMOTE_ATTESTATION_CERTIFICATE = 2; OEM_DEVICE_CERTIFICATE = 3; + BOOT_CERTIFICATE_CHAIN = 4; } message NameValue { @@ -810,6 +839,25 @@ message ClientIdentification { optional bool can_disable_analog_output = 11 [default = false]; } + message ClientCredentials { + // Deprecated. Use credential_type instead. + optional TokenType type = 1 [default = KEYBOX]; + optional bytes token = 2; + + // Additional types of credentials that may be present in the client + // identification. + enum CredentialType { + CREDENTIAL_TYPE_UNKNOWN = 0; + // CBOR format used by the Provisioning 4.0 phase 3 uploading model. + CREDENTIAL_TYPE_BCC_SIGNATURE_CBOR = 1; + // PKCS7 format, used by Provisioning 4.0 signing model. + CREDENTIAL_TYPE_BCC_SIGNATURE_PKCS7 = 2; + } + + // The type of the token. + optional CredentialType credential_type = 3; + } + // Type of factory-provisioned device root of trust. Optional. optional TokenType type = 1 [default = KEYBOX]; // Factory-provisioned device root of trust. Required. @@ -825,6 +873,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. + optional ClientCredentials device_credentials = 8; } // EncryptedClientIdentification message used to hold ClientIdentification @@ -1259,3 +1309,149 @@ message SignedCASDrmResponse { optional bytes response = 1; optional bytes signature = 2; } + +// ---------------------------------------------------------------------------- +// drm_certificate.proto +// ---------------------------------------------------------------------------- +// Description of section: +// Definition of the root of trust identifier proto. The proto message contains +// the EC-IES encrypted identifier (e.g. keybox unique id) for a device and +// an associated hash. These can be used by Widevine to identify the root of +// trust that was used to acquire a DRM certificate. +// +// In addition to the encrypted part and the hash, the proto contains the +// version of the root of trust id which implies the EC key algorithm that was +// used. +// Next id: 5 +message RootOfTrustId { + // The version specifies the EC algorithm that was used to generate the + // root of trust id. + enum RootOfTrustIdVersion { + // Should not be used. + ROOT_OF_TRUST_ID_VERSION_UNSPECIFIED = 0; + // Version 1 of the ID uses EC-IES with SECP256R1 curve. + ROOT_OF_TRUST_ID_VERSION_1 = 1; + } + optional RootOfTrustIdVersion version = 1; + // The key_id is used for key rotation. It indicates which key was used to + // generate the root of trust id. + optional uint32 key_id = 2; + + // The EC-IES encrypted message containing the unique_id. The bytes are + // a concatenation of + // 1) The ephemeral public key. Uncompressed keypoint format per X9.62. + // 2) The plaintext encrypted with the derived AES key using AES CBC, + // PKCS7 padding and a zerio iv. + // 3) The HMAC SHA256 of the cipher text. + optional bytes encrypted_unique_id = 3; + + // The hash of encrypted unique id and other values. + // unique_id_hash = SHA256( + // encrypted_unique_id || system_id || SHA256(unique_id || secret_sauce)). + optional bytes unique_id_hash = 4; +} + +// DRM certificate definition for user devices, intermediate, service, and root +// certificates. +// Next id: 13 +message DrmCertificate { + enum Type { + ROOT = 0; // ProtoBestPractices: ignore. + DEVICE_MODEL = 1; + DEVICE = 2; + SERVICE = 3; + PROVISIONER = 4; + // Only used by internal L3 CDMs with baked-in (embedded) certificates that + // support the Drm Reprovisioning method for individualization of embedded + // certificates. + DEVICE_EMBEDDED = 5; + } + enum ServiceType { + UNKNOWN_SERVICE_TYPE = 0; + LICENSE_SERVER_SDK = 1; + LICENSE_SERVER_PROXY_SDK = 2; + PROVISIONING_SDK = 3; + CAS_PROXY_SDK = 4; + } + enum Algorithm { + UNKNOWN_ALGORITHM = 0; + RSA = 1; + ECC_SECP256R1 = 2; + ECC_SECP384R1 = 3; + ECC_SECP521R1 = 4; + } + + message EncryptionKey { + // Device public key. PKCS#1 ASN.1 DER-encoded. Required. + optional bytes public_key = 1; + // Required. The algorithm field contains the curve used to create the + // |public_key| if algorithm is one of the ECC types. + // The |algorithm| is used for both to determine the if the certificate is + // ECC or RSA. The |algorithm| also specifies the parameters that were used + // to create |public_key| and are used to create an ephemeral session key. + optional Algorithm algorithm = 2 [default = RSA]; + } + + // Type of certificate. Required. + optional Type type = 1; + // 128-bit globally unique serial number of certificate. + // Value is 0 for root certificate. Required. + optional bytes serial_number = 2; + // POSIX time, in seconds, when the certificate was created. Required. + optional uint32 creation_time_seconds = 3; + // POSIX time, in seconds, when the certificate should expire. Value of zero + // denotes indefinite expiry time. For more information on limited lifespan + // DRM certificates see (go/limited-lifespan-drm-certificates). + optional uint32 expiration_time_seconds = 12; + // Device public key. PKCS#1 ASN.1 DER-encoded. Required. + optional bytes public_key = 4; + // Widevine system ID for the device. Required for intermediate and + // user device certificates. + optional uint32 system_id = 5; + // Deprecated field, which used to indicate whether the device was a test + // (non-production) device. The test_device field in ProvisionedDeviceInfo + // below should be observed instead. + optional bool test_device_deprecated = 6 [deprecated = true]; + // Service identifier (web origin) for the provider which owns the + // certificate. Required for service and provisioner certificates. + optional string provider_id = 7; + // This field is used only when type = SERVICE to specify which SDK uses + // service certificate. This repeated field is treated as a set. A certificate + // may be used for the specified service SDK if the appropriate ServiceType + // is specified in this field. + repeated ServiceType service_types = 8; + // Required. The algorithm field contains the curve used to create the + // |public_key| if algorithm is one of the ECC types. + // The |algorithm| is used for both to determine the if the certificate is ECC + // or RSA. The |algorithm| also specifies the parameters that were used to + // create |public_key| and are used to create an ephemeral session key. + optional Algorithm algorithm = 9 [default = RSA]; + // Optional. May be present in DEVICE certificate types. This is the root + // of trust identifier that holds an encrypted value that identifies the + // keybox or other root of trust that was used to provision a DEVICE drm + // certificate. + optional RootOfTrustId rot_id = 10; + // Optional. May be present in devices that explicitly support dual keys. When + // present the |public_key| is used for verification of received license + // request messages. + optional EncryptionKey encryption_key = 11; +} + +// ---------------------------------------------------------------------------- +// signed_drm_certificate.proto +// ---------------------------------------------------------------------------- +// Description of section: +// Signed device certificate definition. + +// DrmCertificate signed by a higher (CA) DRM certificate. +message SignedDrmCertificate { + // Serialized certificate. Required. + optional bytes drm_certificate = 1; + // Signature of certificate. Signed with root or intermediate + // certificate specified below. Required. + optional bytes signature = 2; + // SignedDrmCertificate used to sign this certificate. + optional SignedDrmCertificate signer = 3; + // Optional field that indicates the hash algorithm used in signature scheme. + optional HashAlgorithmProto hash_algorithm = 4; +} diff --git a/tests/src/cas_license_test.cpp b/tests/src/cas_license_test.cpp index aa32ad2..3a121a2 100644 --- a/tests/src/cas_license_test.cpp +++ b/tests/src/cas_license_test.cpp @@ -13,8 +13,10 @@ #include "cas_status.h" #include "cas_util.h" #include "device_files.pb.h" +#include "file_util.h" #include "license_protocol.pb.h" #include "mock_crypto_session.h" +#include "mock_file.h" #include "string_conversions.h" namespace wvcas { @@ -22,6 +24,8 @@ namespace { using ::testing::_; using ::testing::AllOf; +using ::testing::AtLeast; +using ::testing::ByMove; using ::testing::DoAll; using ::testing::Eq; using ::testing::Invoke; @@ -32,6 +36,7 @@ using ::testing::Return; using ::testing::ReturnRef; using ::testing::SetArgPointee; using ::testing::SetArgReferee; +using ::testing::SetArrayArgument; using ::testing::StrictMock; using wvutil::Base64Decode; @@ -41,6 +46,7 @@ using wvutil::Base64SafeEncodeNoPad; using video_widevine_client::sdk::DeviceCertificate; using video_widevine_client::sdk::File; using video_widevine_client::sdk::HashedFile; +using video_widevine_client::sdk::OemCertificate; static constexpr char kKeyboxToken[] = "KeyBoxToken"; static constexpr char kExpectedRenewalRequest[] = "ExpectedRenewalRequest"; @@ -76,6 +82,14 @@ static constexpr char kKeyOemCryptoSecurityPatchLevel[] = "oem_crypto_security_patch_level"; static constexpr char kRenewalSereverURL[] = "ExpectedRenewalURL"; static constexpr char kCoreMessage[] = "CoreMessage"; +constexpr char kOemCertificateFilePath[] = + "/data/vendor/mediacas/IDM/widevine/oemcert.bin"; +constexpr char kBccToken[] = "Bcc token"; +constexpr char kPublicKeyToCertify[] = "public_key_to_certify"; +constexpr char kOemCertificate[] = "oem cert"; +constexpr char kOemCertPrivateKey[] = "wrapped_private_key"; +constexpr char kDrmCertificateFilePath[] = + "/data/vendor/mediacas/IDM/widevine/cert.bin"; typedef StrictMock StrictMockCryptoSession; @@ -93,13 +107,17 @@ class MockPolicyEngine : public wvcas::PolicyEngine { MOCK_CONST_METHOD0(IsExpired, bool()); MOCK_CONST_METHOD0(CanPersist, bool()); MOCK_CONST_METHOD0(always_include_client_id, bool()); + MOCK_METHOD(void, RestorePlaybackTimes, + (int64_t playback_start_time, int64_t last_playback_time, + int64_t grace_period_end_time), + (override)); }; typedef StrictMock StrictMockPolicyEngine; class TestCasLicense : public wvcas::CasLicense { public: explicit TestCasLicense() {} - ~TestCasLicense() override{}; + ~TestCasLicense() override {}; std::unique_ptr GetPolicyEngine() override { policy_engine_ = pass_thru_.get(); return std::move(pass_thru_); @@ -124,6 +142,8 @@ class CasLicenseTest : public ::testing::TestWithParam { std::shared_ptr strict_mock_; std::string wrapped_rsa_key_; std::string device_certificate_; + wvutil::FileSystem file_system_; + CryptoWrappedKey wrapped_private_key_; }; std::string CasLicenseTest::CreateProvisioningResponse() { @@ -138,9 +158,14 @@ std::string CasLicenseTest::CreateProvisioningResponse() { response.set_device_rsa_key(kDeviceRsaKey); response.set_device_rsa_key_iv(kDeviceRsaKeyIV); + video_widevine::SignedDrmCertificate signed_device_cert; + video_widevine::DrmCertificate device_cert; + device_cert.set_serial_number("serial_number"); + device_cert.SerializeToString(signed_device_cert.mutable_drm_certificate()); + signed_device_cert.SerializeToString(response.mutable_device_certificate()); response.SerializeToString(signed_message.mutable_message()); - std::vector b64_message(signed_message.ByteSize()); + std::vector b64_message(signed_message.ByteSizeLong()); signed_message.SerializeToArray(&b64_message[0], b64_message.size()); return kJsonStartSubstr + Base64SafeEncodeNoPad(b64_message) + kJsonEndSubstr; @@ -207,6 +232,21 @@ std::string CreateLicenseFileData() { return hashed_file.SerializeAsString(); } +std::string CreateOemCertFileData() { + File file; + file.set_type(File::OEM_CERTIFICATE); + OemCertificate* oem_certificate = file.mutable_oem_certificate(); + oem_certificate->set_certificate(kOemCertificate); + oem_certificate->set_wrapped_private_key(kOemCertPrivateKey); + oem_certificate->set_key_type(OemCertificate::ECC); + + HashedFile hashed_file; + file.SerializeToString(hashed_file.mutable_file()); + Hash(hashed_file.file(), hashed_file.mutable_hash()); + + return hashed_file.SerializeAsString(); +} + TEST_F(CasLicenseTest, GenerateDeviceProvisioningRequest) { strict_mock_ = std::make_shared(); TestCasLicense cas_license; @@ -238,7 +278,7 @@ TEST_F(CasLicenseTest, GenerateDeviceProvisioningRequest) { .WillOnce(Return(wvcas::CasStatusCode::kNoError)); wvcas::CasStatus status = cas_license.GenerateDeviceProvisioningRequest( - &serialized_provisioning_request); + file_system_, &serialized_provisioning_request); EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); // Verify the provisioning request. @@ -263,6 +303,127 @@ TEST_F(CasLicenseTest, GenerateDeviceProvisioningRequest) { provisioning_request.nonce().data())); } +TEST_F(CasLicenseTest, GenerateDeviceProvisioning4FirstStageRequest) { + strict_mock_ = std::make_shared(); + TestCasLicense cas_license; + EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); + EXPECT_EQ(cas_license.initialize(strict_mock_, nullptr).status_code(), + wvcas::CasStatusCode::kNoError); + EXPECT_CALL(*strict_mock_, provisioning_method()) + .WillRepeatedly(Return(wvcas::BootCertificateChain)); + EXPECT_CALL(*strict_mock_, GetBootCertificateChain(NotNull(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(kBccToken), Return(CasStatusCode::kNoError))); + EXPECT_CALL(*strict_mock_, APIVersion(NotNull())) + .WillRepeatedly( + DoAll(SetArgPointee<0>(18), Return(CasStatusCode::kNoError))); + EXPECT_CALL(*strict_mock_, GetSecurityPatchLevel()).WillOnce(Return(1)); + EXPECT_CALL(*strict_mock_, GetBuildInformation(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>("build info"), Return(true))); + EXPECT_CALL(*strict_mock_, GenerateCertificateKeyPair) + .WillOnce(DoAll(SetArgPointee<0>(kPublicKeyToCertify), + SetArgPointee<1>("public_key_signature"), + SetArgPointee<2>("wrapped_private_key"), + SetArgPointee<3>(CryptoWrappedKey::kEcc), + Return(wvcas::CasStatusCode::kNoError))); + EXPECT_CALL(*strict_mock_, + PrepareAndSignProvisioningRequest(_, NotNull(), NotNull(), _, _)) + .WillOnce(DoAll(SetArgPointee<1>("core message"), + SetArgPointee<2>(kExpectedSignature), + SetArgReferee<3>(false), + Return(wvcas::CasStatusCode::kNoError))); + std::string serialized_provisioning_request; + + wvcas::CasStatus status = cas_license.GenerateDeviceProvisioningRequest( + file_system_, &serialized_provisioning_request); + + EXPECT_EQ(status.status_code(), wvcas::CasStatusCode::kNoError); + // Verify the provisioning request. + video_widevine::SignedProvisioningMessage signed_message; + auto data = Base64SafeDecode(serialized_provisioning_request); + EXPECT_TRUE(signed_message.ParseFromArray(&data[0], data.size())); + EXPECT_EQ(signed_message.signature(), kExpectedSignature); + EXPECT_EQ(signed_message.protocol_version(), + video_widevine::SignedProvisioningMessage::PROVISIONING_40); + video_widevine::ProvisioningRequest provisioning_request; + EXPECT_TRUE(provisioning_request.ParseFromString(signed_message.message())); + EXPECT_TRUE(provisioning_request.has_encrypted_client_id()); + EXPECT_EQ(provisioning_request.certificate_public_key().public_key(), + kPublicKeyToCertify); + EXPECT_EQ(provisioning_request.certificate_public_key().key_type(), + video_widevine::PublicKeyToCertify::ECC); +} + +TEST_F(CasLicenseTest, GenerateDeviceProvisioning4SecondStageRequest) { + strict_mock_ = std::make_shared(); + TestCasLicense cas_license; + EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); + EXPECT_EQ(cas_license.initialize(strict_mock_, nullptr).status_code(), + wvcas::CasStatusCode::kNoError); + // Setup the test OEM certificate. + const std::string data = CreateOemCertFileData(); + const int data_size = data.size(); + auto file = std::make_unique(); + EXPECT_CALL(*file, Read(NotNull(), Eq(data_size))) + .Times(1) + .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), + Return(data_size))); + wvutil::MockFileSystem file_system; + EXPECT_CALL(file_system, Exists(std::string(kOemCertificateFilePath))) + .Times(AtLeast(1)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(std::string(kOemCertificateFilePath))) + .Times(AtLeast(1)) + .WillOnce(Return(data_size)); + EXPECT_CALL(file_system, Open(std::string(kOemCertificateFilePath), _)) + .WillOnce(Return(ByMove(std::move(file)))); + EXPECT_CALL(*strict_mock_, provisioning_method()) + .WillRepeatedly(Return(wvcas::BootCertificateChain)); + EXPECT_CALL(*strict_mock_, GetBootCertificateChain).Times(0); + EXPECT_CALL(*strict_mock_, GetSecurityPatchLevel()).WillOnce(Return(1)); + EXPECT_CALL(*strict_mock_, GetBuildInformation(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>("build info"), Return(true))); + EXPECT_CALL(*strict_mock_, APIVersion(NotNull())) + .WillRepeatedly( + DoAll(SetArgPointee<0>(18), Return(CasStatusCode::kNoError))); + EXPECT_CALL(*strict_mock_, GenerateCertificateKeyPair) + .WillOnce(DoAll(SetArgPointee<0>(kPublicKeyToCertify), + SetArgPointee<1>("public_key_signature"), + SetArgPointee<2>("wrapped_private_key"), + SetArgPointee<3>(CryptoWrappedKey::kEcc), + Return(wvcas::CasStatusCode::kNoError))); + EXPECT_CALL(*strict_mock_, + PrepareAndSignProvisioningRequest(_, NotNull(), NotNull(), _, _)) + .WillOnce(DoAll(SetArgPointee<1>("core message"), + SetArgPointee<2>(kExpectedSignature), + SetArgReferee<3>(false), + Return(wvcas::CasStatusCode::kNoError))); + EXPECT_CALL(*strict_mock_, LoadOemCertificatePrivateKey) + .WillOnce(Return(wvcas::CasStatusCode::kNoError)); + std::string serialized_provisioning_request; + + wvcas::CasStatus status = cas_license.GenerateDeviceProvisioningRequest( + file_system, &serialized_provisioning_request); + + EXPECT_EQ(status.status_code(), wvcas::CasStatusCode::kNoError); + // Verify the provisioning request. + video_widevine::SignedProvisioningMessage signed_message; + auto decoded_provisioning_request = + Base64SafeDecode(serialized_provisioning_request); + EXPECT_TRUE(signed_message.ParseFromArray( + &decoded_provisioning_request[0], decoded_provisioning_request.size())); + EXPECT_EQ(signed_message.signature(), kExpectedSignature); + EXPECT_EQ(signed_message.protocol_version(), + video_widevine::SignedProvisioningMessage::PROVISIONING_40); + video_widevine::ProvisioningRequest provisioning_request; + EXPECT_TRUE(provisioning_request.ParseFromString(signed_message.message())); + EXPECT_TRUE(provisioning_request.has_encrypted_client_id()); + EXPECT_EQ(provisioning_request.certificate_public_key().public_key(), + kPublicKeyToCertify); + EXPECT_EQ(provisioning_request.certificate_public_key().key_type(), + video_widevine::PublicKeyToCertify::ECC); +} + TEST_F(CasLicenseTest, HandleProvisioningResponse) { const std::string provisioning_response = CreateProvisioningResponse(); @@ -274,47 +435,34 @@ TEST_F(CasLicenseTest, HandleProvisioningResponse) { EXPECT_CALL(*strict_mock_, reset()) .WillOnce(Return(wvcas::CasStatusCode::kNoError)); - EXPECT_CALL(*strict_mock_, LoadProvisioning(_, _, _, _)); + EXPECT_CALL(*strict_mock_, LoadProvisioning(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<3>("private_key"), + Return(wvcas::CasStatusCode::kNoError))); + auto file = std::make_unique(); + EXPECT_CALL(*file, Write(NotNull(), _)) + .Times(1) + .WillOnce([&](const char*, size_t data_size) { return data_size; }); + wvutil::MockFileSystem file_system; + EXPECT_CALL(file_system, Open(std::string(kDrmCertificateFilePath), _)) + .WillOnce(Return(ByMove(std::move(file)))); wvcas::CasStatus status = cas_license.HandleDeviceProvisioningResponse( - provisioning_response, &device_certificate_, &wrapped_rsa_key_, nullptr); + &file_system, provisioning_response, &device_certificate_, + &wrapped_private_key_); EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); } -TEST_F(CasLicenseTest, HandleProvisioningResponseWithDeviceFileOutput) { - const std::string provisioning_response = CreateProvisioningResponse(); - - strict_mock_ = std::make_shared(); - TestCasLicense cas_license; - EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); - EXPECT_EQ(wvcas::CasStatusCode::kNoError, - cas_license.initialize(strict_mock_, nullptr).status_code()); - - EXPECT_CALL(*strict_mock_, reset()) - .WillOnce(Return(wvcas::CasStatusCode::kNoError)); - EXPECT_CALL(*strict_mock_, LoadProvisioning(_, _, _, _)); - - std::string device_cert_file; - wvcas::CasStatus status = cas_license.HandleDeviceProvisioningResponse( - provisioning_response, &device_certificate_, &wrapped_rsa_key_, - &device_cert_file); - EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); - EXPECT_FALSE(device_cert_file.empty()); -} - TEST_F(CasLicenseTest, GenerateEntitlementRequest) { strict_mock_ = std::make_shared(); TestCasLicense cas_license; EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); EXPECT_EQ(wvcas::CasStatusCode::kNoError, cas_license.initialize(strict_mock_, nullptr).status_code()); - - EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _)) - .WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError)); - + EXPECT_CALL(*strict_mock_, GetSecurityPatchLevel()).WillOnce(Return(1)); + EXPECT_CALL(*strict_mock_, GetBuildInformation(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>("build info"), Return(true))); EXPECT_CALL(*strict_mock_, APIVersion(_)) .WillOnce(Return(wvcas::CasStatusCode::kNoError)); - EXPECT_CALL(*strict_mock_, GenerateNonce(_)) .WillOnce(Return(wvcas::CasStatusCode::kNoError)); EXPECT_CALL(*strict_mock_, @@ -322,11 +470,11 @@ TEST_F(CasLicenseTest, GenerateEntitlementRequest) { .WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature), SetArgReferee<3>(false), Return(wvcas::CasStatusCode::kNoError))); - EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _)); + EXPECT_CALL(*strict_mock_, LoadCertificatePrivateKey); std::string serialized_entitlement_request; wvcas::CasStatus status = cas_license.GenerateEntitlementRequest( - kInitializationData, device_certificate_, wrapped_rsa_key_, + kInitializationData, device_certificate_, wrapped_private_key_, wvcas::LicenseType::kStreaming, &serialized_entitlement_request); EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); } @@ -338,12 +486,11 @@ TEST_F(CasLicenseTest, HandleEntitlementResponse) { EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); EXPECT_EQ(wvcas::CasStatusCode::kNoError, cas_license.initialize(strict_mock_, nullptr).status_code()); - - EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _)) - .WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError)); + EXPECT_CALL(*strict_mock_, GetSecurityPatchLevel()).WillOnce(Return(1)); + EXPECT_CALL(*strict_mock_, GetBuildInformation(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>("build info"), Return(true))); EXPECT_CALL(*strict_mock_, APIVersion(_)) .WillOnce(Return(wvcas::CasStatusCode::kNoError)); - EXPECT_CALL(*strict_mock_, GenerateNonce(_)) .WillOnce(Return(wvcas::CasStatusCode::kNoError)); EXPECT_CALL(*strict_mock_, @@ -351,11 +498,11 @@ TEST_F(CasLicenseTest, HandleEntitlementResponse) { .WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature), SetArgReferee<3>(false), Return(wvcas::CasStatusCode::kNoError))); - EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _)); + EXPECT_CALL(*strict_mock_, LoadCertificatePrivateKey); std::string serialized_entitlement_request; wvcas::CasStatus status = cas_license.GenerateEntitlementRequest( - kInitializationData, device_certificate_, wrapped_rsa_key_, + kInitializationData, device_certificate_, wrapped_private_key_, wvcas::LicenseType::kStreaming, &serialized_entitlement_request); EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); @@ -592,7 +739,7 @@ TEST_F(CasLicenseTest, RestoreLicense) { cas_license.initialize(strict_mock_, nullptr).status_code()); std::string license_file_data = CreateLicenseFileData(); - EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _)); + EXPECT_CALL(*strict_mock_, LoadCertificatePrivateKey); EXPECT_CALL(*strict_mock_, DeriveKeysFromSessionKey(_, _, _, _, _, _)) .WillRepeatedly(Return(wvcas::CasStatusCode::kNoError)); EXPECT_CALL(*strict_mock_, LoadLicense(_, _, _)) @@ -601,9 +748,11 @@ TEST_F(CasLicenseTest, RestoreLicense) { .WillRepeatedly(Return(wvcas::CasStatusCode::kNoError)); EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_)); EXPECT_CALL(*cas_license.policy_engine_, UpdateLicense(_)); - EXPECT_EQ(wvcas::CasStatusCode::kNoError, - cas_license.HandleStoredLicense(wrapped_rsa_key_, license_file_data) - .status_code()); + EXPECT_CALL(*cas_license.policy_engine_, RestorePlaybackTimes); + EXPECT_EQ( + wvcas::CasStatusCode::kNoError, + cas_license.HandleStoredLicense(wrapped_private_key_, license_file_data) + .status_code()); } TEST_F(CasLicenseTest, HandleMultiContentEntitlementResponse) { @@ -612,8 +761,9 @@ TEST_F(CasLicenseTest, HandleMultiContentEntitlementResponse) { EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); EXPECT_EQ(wvcas::CasStatusCode::kNoError, cas_license.initialize(strict_mock_, nullptr).status_code()); - EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _)) - .WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError)); + EXPECT_CALL(*strict_mock_, GetSecurityPatchLevel()).WillOnce(Return(1)); + EXPECT_CALL(*strict_mock_, GetBuildInformation(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>("build info"), Return(true))); EXPECT_CALL(*strict_mock_, APIVersion(_)) .WillOnce(Return(wvcas::CasStatusCode::kNoError)); EXPECT_CALL(*strict_mock_, GenerateNonce(_)) @@ -623,11 +773,11 @@ TEST_F(CasLicenseTest, HandleMultiContentEntitlementResponse) { .WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature), SetArgReferee<3>(false), Return(wvcas::CasStatusCode::kNoError))); - EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _)); + EXPECT_CALL(*strict_mock_, LoadCertificatePrivateKey); std::string serialized_entitlement_request; wvcas::CasStatus status = cas_license.GenerateEntitlementRequest( - kInitializationData, device_certificate_, wrapped_rsa_key_, + kInitializationData, device_certificate_, wrapped_private_key_, wvcas::LicenseType::kStreaming, &serialized_entitlement_request); EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); diff --git a/tests/src/crypto_session_test.cpp b/tests/src/crypto_session_test.cpp index c444d2b..e481671 100644 --- a/tests/src/crypto_session_test.cpp +++ b/tests/src/crypto_session_test.cpp @@ -666,7 +666,8 @@ TEST_F(CryptoSessionTest, PrepareAndSignProvisioningRequest) { DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); EXPECT_EQ(wvcas::CasStatusCode::kNoError, crypto_session.initialize().status_code()); - + EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_GetProvisioningMethod()) + .WillRepeatedly(Return(OEMCrypto_BootCertificateChain)); const std::string message = "message"; std::string core_message; std::string signature; @@ -688,6 +689,10 @@ TEST_F(CryptoSessionTest, PrepareAndSignProvisioningRequest) { .WillOnce(DoAll( Invoke(this, &CryptoSessionTest::SetOutputCoreMessageAndSignature), Return(OEMCrypto_SUCCESS))); + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_GetSignatureHashAlgorithm(kOemcSessionId, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(OEMCrypto_SHA2_256), + Return(OEMCrypto_SUCCESS))); EXPECT_EQ(wvcas::CasStatusCode::kNoError, crypto_session .PrepareAndSignProvisioningRequest( @@ -696,7 +701,7 @@ TEST_F(CryptoSessionTest, PrepareAndSignProvisioningRequest) { .status_code()); EXPECT_EQ(core_message, std::string(kExpectedCoreMessage)); EXPECT_EQ(signature, std::string(kExpectedSignature)); - EXPECT_FALSE(should_specify_algorithm); + EXPECT_TRUE(should_specify_algorithm); } TEST_F(CryptoSessionTest, LoadProvisioning) { @@ -785,7 +790,7 @@ TEST_F(CryptoSessionTest, GetOEMPublicCertificate) { EXPECT_EQ(cert_size, public_cert_length); } -TEST_F(CryptoSessionTest, LoadDeviceRSAKey) { +TEST_F(CryptoSessionTest, LoadCertificatePrivateKey) { TestCryptoSession > crypto_session( nice_oemcrypto_interface_); EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) @@ -795,18 +800,16 @@ TEST_F(CryptoSessionTest, LoadDeviceRSAKey) { crypto_session.initialize().status_code()); // Test data for use in handling the expected calls. - uint8_t wrapped_rsa_key = 0; - size_t wrapped_rsa_key_length = 75; + std::string key = "key"; + CryptoWrappedKey wrapped_key; + wrapped_key.set_key(key); - EXPECT_CALL( - nice_oemcrypto_interface_, - OEMCrypto_LoadDRMPrivateKey(kOemcSessionId, OEMCrypto_RSA_Private_Key, - &wrapped_rsa_key, wrapped_rsa_key_length)) + EXPECT_CALL(nice_oemcrypto_interface_, + OEMCrypto_LoadDRMPrivateKey( + kOemcSessionId, OEMCrypto_RSA_Private_Key, _, key.size())) .WillOnce(Return(OEMCrypto_SUCCESS)); - EXPECT_EQ( - wvcas::CasStatusCode::kNoError, - crypto_session.LoadDeviceRSAKey(&wrapped_rsa_key, wrapped_rsa_key_length) - .status_code()); + EXPECT_EQ(crypto_session.LoadCertificatePrivateKey(wrapped_key).status_code(), + wvcas::CasStatusCode::kNoError); } TEST_F(CryptoSessionTest, GenerateRSASignature) { diff --git a/tests/src/mock_crypto_session.h b/tests/src/mock_crypto_session.h index f93d206..6fc829d 100644 --- a/tests/src/mock_crypto_session.h +++ b/tests/src/mock_crypto_session.h @@ -9,6 +9,7 @@ #include #include "crypto_session.h" +#include "crypto_wrapped_key.h" class MockCryptoSession : public wvcas::CryptoSession { public: @@ -30,21 +31,19 @@ class MockCryptoSession : public wvcas::CryptoSession { const uint8_t* enc_key_context, uint32_t enc_key_context_length)); MOCK_METHOD5(PrepareAndSignLicenseRequest, - wvcas::CasStatus(const std::string& message, - std::string* core_message, - std::string* signature, bool&, - OEMCrypto_SignatureHashAlgorithm&) - ); + wvcas::CasStatus(const std::string& message, + std::string* core_message, + std::string* signature, bool&, + OEMCrypto_SignatureHashAlgorithm&)); MOCK_METHOD3(PrepareAndSignRenewalRequest, wvcas::CasStatus(const std::string& message, std::string* core_message, std::string* signature)); MOCK_METHOD5(PrepareAndSignProvisioningRequest, - wvcas::CasStatus(const std::string& message, - std::string* core_message, - std::string* signature, bool&, - OEMCrypto_SignatureHashAlgorithm&) - ); + wvcas::CasStatus(const std::string& message, + std::string* core_message, + std::string* signature, bool&, + OEMCrypto_SignatureHashAlgorithm&)); MOCK_METHOD4(LoadProvisioning, wvcas::CasStatus(const std::string& signed_message, const std::string& core_message, @@ -88,6 +87,18 @@ class MockCryptoSession : public wvcas::CryptoSession { MOCK_METHOD(wvcas::CasStatus, GetOEMKeyToken, (OEMCrypto_SESSION entitled_key_session_id, std::vector& token)); + MOCK_METHOD(wvcas::CasStatus, GetBootCertificateChain, + (std::string * bcc, std::string* additional_signature)); + MOCK_METHOD(wvcas::CasStatus, GenerateCertificateKeyPair, + (std::string * public_key, std::string* public_key_signature, + std::string* wrapped_private_key, + wvcas::CryptoWrappedKey::Type* key_type)); + MOCK_METHOD(wvcas::CasStatus, LoadOemCertificatePrivateKey, + (const wvcas::CryptoWrappedKey& private_key)); + MOCK_METHOD(wvcas::CasStatus, LoadCertificatePrivateKey, + (const wvcas::CryptoWrappedKey& private_key)); + MOCK_METHOD(uint8_t, GetSecurityPatchLevel, ()); + MOCK_METHOD(bool, GetBuildInformation, (std::string * info)); }; #endif // MOCK_CRYPTO_SESSION_H diff --git a/tests/src/mock_file.h b/tests/src/mock_file.h new file mode 100644 index 0000000..fb6e47e --- /dev/null +++ b/tests/src/mock_file.h @@ -0,0 +1,41 @@ +// Copyright 2021 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_CAS_MOCK_FILE_H +#define WIDEVINE_CAS_MOCK_FILE_H + +#include +#include + +#include "file_store.h" + +namespace wvutil { + +class MockFile : public File { + public: + MockFile() {} + ~MockFile() override {} + + MOCK_METHOD(ssize_t, Read, (char*, size_t), (override)); + MOCK_METHOD(ssize_t, Write, (const char*, size_t), (override)); +}; + +class MockFileSystem : public wvutil::FileSystem { + public: + MockFileSystem() {} + ~MockFileSystem() override {} + + MOCK_METHOD(std::unique_ptr, Open, (const std::string&, int flags), + (override)); + MOCK_METHOD(bool, Exists, (const std::string&), (override)); + MOCK_METHOD(bool, Exists, (const std::string&, int*), (override)); + MOCK_METHOD(bool, Remove, (const std::string&), (override)); + MOCK_METHOD(ssize_t, FileSize, (const std::string&), (override)); + MOCK_METHOD(bool, List, (const std::string&, std::vector*), + (override)); +}; + +} // namespace wvutil + +#endif // MOCK_ECM_PARSER_H diff --git a/tests/src/test_properties.cpp b/tests/src/test_properties.cpp index 53281ac..634dce6 100644 --- a/tests/src/test_properties.cpp +++ b/tests/src/test_properties.cpp @@ -11,6 +11,7 @@ static constexpr char kGenericModelName[] = "www"; static constexpr char kProductName[] = "WidevineCasTests"; static constexpr char kKeyArchitectureName[] = "architecture_name"; static constexpr char kKeyDeviceName[] = "device_name"; +static constexpr char kBuildInfo[] = "build_info"; static constexpr char kOemcPath[] = "cas_oemc_path.so"; namespace wvcas { @@ -55,6 +56,14 @@ bool Properties::GetDeviceName(std::string* device_name) { return true; } +bool Properties::GetBuildInfo(std::string* build_info) { + if (build_info == nullptr) { + return false; + } + *build_info = kBuildInfo; + return true; +} + bool Properties::GetOEMCryptoPath(std::string* path) { if (path == nullptr) { return false; diff --git a/tests/src/widevine_cas_api_test.cpp b/tests/src/widevine_cas_api_test.cpp index c9f84e1..6e34248 100644 --- a/tests/src/widevine_cas_api_test.cpp +++ b/tests/src/widevine_cas_api_test.cpp @@ -56,14 +56,15 @@ class MockLicense : public wvcas::CasLicense { ~MockLicense() override {} MOCK_CONST_METHOD0(IsExpired, bool()); - MOCK_METHOD5(GenerateEntitlementRequest, - wvcas::CasStatus(const std::string& init_data, - const std::string& device_certificate, - const std::string& wrapped_rsa_key, - wvcas::LicenseType license_type, - std::string* signed_license_request)); + MOCK_METHOD(wvcas::CasStatus, GenerateEntitlementRequest, + (const std::string& init_data, + const std::string& device_certificate, + const CryptoWrappedKey& private_key, LicenseType license_type, + std::string* signed_license_request), + (override)); MOCK_METHOD(wvcas::CasStatus, HandleStoredLicense, - (const std::string&, const std::string&), (override)); + (const CryptoWrappedKey& wrapped_private_key, const std::string&), + (override)); MOCK_METHOD2(GenerateEntitlementRenewalRequest, wvcas::CasStatus(const std::string& device_certificate, std::string* signed_renewal_request)); diff --git a/wvutil/include/cas_properties.h b/wvutil/include/cas_properties.h index 202f199..4799f3b 100644 --- a/wvutil/include/cas_properties.h +++ b/wvutil/include/cas_properties.h @@ -31,6 +31,9 @@ class Properties { // Sets the |device_name| field value to be populated in and EMM license // request. Returns false if unable to set the value. static bool GetDeviceName(std::string* device_name); + // Sets the |build_info| field value to be populated in and EMM license + // request. Returns false if unable to set the value. + static bool GetBuildInfo(std::string* build_info); // Returns a path to CAS oemcrypto library, either default, // or overridden through system property. // Returned path could be either absolute or relative. diff --git a/wvutil/include/file_store.h b/wvutil/include/file_store.h index 9563b5e..73de53d 100644 --- a/wvutil/include/file_store.h +++ b/wvutil/include/file_store.h @@ -7,7 +7,8 @@ #ifndef WVCDM_UTIL_FILE_STORE_H_ #define WVCDM_UTIL_FILE_STORE_H_ -#include +#include + #include #include #include @@ -18,16 +19,35 @@ namespace wvutil { +// Fixed filename for ATSC DRM certificate pre-installed +// on ATSC devices for ATSC licenses. static const std::string kAtscCertificateFileName = "atsccert.bin"; +// General filename for either global or unmapped app-origin +// DRM certificates. static const std::string kCertificateFileName = "cert1.bin"; +// File extension for DRM and OEM certificate files. static const std::string kCertificateFileNameExt = ".bin"; -static const std::string kCertificateFileNamePrefix = "cert1_"; +// Filename prefix for mapped (scoped) DRM certificate filenames +// specific to a particular app-origin. +static const std::string kScopedCertificateFilenamePrefix = "cert1_"; +// TODO(b/376533901): Replace this constant with +// kScopedCertificateFilenamePrefix in source code.. +static const std::string kCertificateFileNamePrefix = + kScopedCertificateFilenamePrefix; +// Legacy general filename for either global or unmapped app-origin +// DRM certificates. static const std::string kLegacyCertificateFileName = "cert.bin"; -static const std::string kLegacyCertificateFileNamePrefix = "cert"; +// Legacy filename prefix for mapped (scoped) DRM certificate filenames +// specific to a particular app-origin. +static const std::string kLegacyScopedCertificateFilenamePrefix = "cert"; +// TODO(b/376533901): Replace this constant with +// kLegacyScopedCertificateFilenamePrefix in source code.. +static const std::string kLegacyCertificateFileNamePrefix = + kLegacyScopedCertificateFilenamePrefix; +// Filename for global OEM certificates. static const std::string kOemCertificateFileName = "oemcert.bin"; -static const std::string kOemCertificateFileNamePrefix = "oemcert_"; -// File class. The implementation is platform dependent. +// File interface. The implementation is platform dependent. class File { public: File() {} @@ -35,35 +55,70 @@ class File { virtual ssize_t Read(char* buffer, size_t bytes) = 0; virtual ssize_t Write(const char* buffer, size_t bytes) = 0; - friend class FileSystem; CORE_DISALLOW_COPY_AND_ASSIGN(File); }; +// File system base class. The implementation is platform dependent. class FileSystem { public: FileSystem(); FileSystem(const std::string& origin, void* extra_data); virtual ~FileSystem(); + // Concreate implementation of FileSystem. + // Depending on the platform, this may be vendor or Widevine implemented. class Impl; - // defines as bit flag - enum OpenFlags { - kNoFlags = 0, - kCreate = 1, - kReadOnly = 2, // defaults to read and write access - kTruncate = 4 - }; + // Flags for calls to Open. + static constexpr int kNoFlags = 0; + // Create file if does not already exist, open file if it does exist. + static constexpr int kCreate = (1 << 0); + // Open file as read-only; typically should not be used with kCreate. + static constexpr int kReadOnly = (1 << 1); + // Open file and truncated. May be used with kCreate; should not + // be used with kReadOnly. + static constexpr int kTruncate = (1 << 2); virtual std::unique_ptr Open(const std::string& file_path, int flags); - virtual bool Exists(const std::string& file_path); - virtual bool Exists(const std::string& file_path, int* errno_value); - virtual bool Remove(const std::string& file_path); + // Checks if the |path| exists. The |path| may be a file or directory. + // Return true if an entry in the file system exists; false otherwise. + virtual bool Exists(const std::string& path); + // Same as above, except the optional parameter of |errno_value| should + // be set to 0 or the value of C errno when attempting to check + // the existence of a file. + virtual bool Exists(const std::string& path, int* errno_value); + + // Removes the specified |path|. + // + // If |path| is a regular file, the file should be removed. + // If |path| is a directory, both the directory and the directory + // contents should be removed. + // + // Implementation must support a |path| containing a single wildcard + // character in the filename component of the path. + // + // Return value: + // - true : File/directory was removed, or file/directory did not exist + // - false : File/directory could not be removed, or other error. + virtual bool Remove(const std::string& path); + + // Obtain the size of a file in bytes. |file_path| must be a file, + // and not a directory. + // + // Return value: + // - non-negative : size of file in bytes if file exists + // - negative : file does not exist, or error occurred. virtual ssize_t FileSize(const std::string& file_path); - // Return the filenames stored at dir_path. - // dir_path will be stripped from the returned names. + // Return the entries stored at |dir_path| (includes both files + // and directories). + // + // Return value: + // - true : Directory exists, and directory entry names are stored + // in |names|; |names| may be empty if directory was empty. + // - false : Directory does not exist, |dir_path| is not a directory, + // or error was encountered. virtual bool List(const std::string& dir_path, std::vector* names); diff --git a/wvutil/include/wv_attributes.h b/wvutil/include/wv_attributes.h index c817f1c..c890c93 100644 --- a/wvutil/include/wv_attributes.h +++ b/wvutil/include/wv_attributes.h @@ -13,4 +13,12 @@ # endif #endif +#ifndef WEAK +# if defined(__GNUC__) || defined(__clang__) +# define WEAK __attribute__((weak)) +# else +# define WEAK +# endif +#endif + #endif // WVCDM_UTIL_WV_ATTRIBUTES_H_ diff --git a/wvutil/src/android_properties.cpp b/wvutil/src/android_properties.cpp index 68de0c5..3d8e6b9 100644 --- a/wvutil/src/android_properties.cpp +++ b/wvutil/src/android_properties.cpp @@ -71,6 +71,14 @@ bool Properties::GetProductName(std::string* product_name) { return GetAndroidProperty("ro.product.name", product_name); } +bool Properties::GetBuildInfo(std::string* build_info) { + if (!build_info) { + LOGW("Properties::GetBuildInfo: Invalid parameter"); + return false; + } + return GetAndroidProperty("ro.build.fingerprint", build_info); +} + bool Properties::GetOEMCryptoPath(std::string* path) { if (path == nullptr) { LOGW("Properties::GetOEMCryptoPath: Invalid parameter"); diff --git a/wvutil/src/string_conversions.cpp b/wvutil/src/string_conversions.cpp index 37ac2ef..0470d3a 100644 --- a/wvutil/src/string_conversions.cpp +++ b/wvutil/src/string_conversions.cpp @@ -32,12 +32,12 @@ const char kBase64SafeCodes[] = // Decodes a single Base64 encoded character into its 6-bit value. // The provided |codes| must be a Base64 character map. -int DecodeBase64Char(char c, const char* codes) { +int32_t DecodeBase64Char(char c, const char* codes) { const char* c_in_codes = strchr(codes, c); if (c_in_codes == nullptr) return -1; const uintptr_t c_in_codes_int = reinterpret_cast(c_in_codes); const uintptr_t codes_int = reinterpret_cast(codes); - return static_cast(c_in_codes_int - codes_int); + return static_cast(c_in_codes_int - codes_int); } bool DecodeHexChar(char ch, uint8_t* digit) { @@ -124,7 +124,7 @@ std::vector Base64DecodeInternal(const char* encoded, size_t length, break; } - const int decoded = DecodeBase64Char(encoded[i], codes); + const int32_t decoded = DecodeBase64Char(encoded[i], codes); if (decoded < 0) { LOGE("base64Decode failed"); return std::vector(); @@ -167,8 +167,8 @@ std::vector a2b_hex(const std::string& byte) { } for (size_t i = 0; i < count / 2; ++i) { - unsigned char msb = 0; // most significant 4 bits - unsigned char lsb = 0; // least significant 4 bits + uint8_t msb = 0; // most significant 4 bits + uint8_t lsb = 0; // least significant 4 bits if (!DecodeHexChar(byte[i * 2], &msb) || !DecodeHexChar(byte[i * 2 + 1], &lsb)) { LOGE("Invalid hex value %c%c at index %zu", byte[i * 2], byte[i * 2 + 1], @@ -219,7 +219,7 @@ std::string unlimited_b2a_hex(const std::string& byte) { } std::string HexEncode(const uint8_t* in_buffer, size_t size) { - constexpr unsigned int kMaxSafeSize = 2048; + constexpr size_t kMaxSafeSize = 2048; if (size > kMaxSafeSize) size = kMaxSafeSize; return UnlimitedHexEncode(in_buffer, size); } @@ -229,7 +229,7 @@ std::string UnlimitedHexEncode(const uint8_t* in_buffer, size_t size) { if (size == 0) return ""; // Each input byte creates two output hex characters. std::string out_buffer(size * 2, '\0'); - for (unsigned int i = 0; i < size; ++i) { + for (size_t i = 0; i < size; ++i) { char byte = in_buffer[i]; out_buffer[(i << 1)] = kHexChars[(byte >> 4) & 0xf]; out_buffer[(i << 1) + 1] = kHexChars[byte & 0xf]; @@ -331,7 +331,7 @@ int64_t htonll64(int64_t x) { } // Encode unsigned integer into a big endian formatted string -std::string EncodeUint32(unsigned int u) { +std::string EncodeUint32(uint32_t u) { std::string s; s.push_back((u >> 24) & 0xFF); s.push_back((u >> 16) & 0xFF);