Add Provisioning 4 support

Widevine provisioning 4 support is added in this patch.
This commit is contained in:
Lu Chen
2025-02-25 13:49:37 -08:00
parent 5f209e6980
commit 41829ca1e5
37 changed files with 2915 additions and 356 deletions

View File

@@ -3,7 +3,7 @@
// License Agreement. // 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 * 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 * 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_GetDeviceSignedCsrPayload _oecc141
#define OEMCrypto_FactoryInstallBCCSignature _oecc142 #define OEMCrypto_FactoryInstallBCCSignature _oecc142
#define OEMCrypto_GetEmbeddedDrmCertificate _oecc143 #define OEMCrypto_GetEmbeddedDrmCertificate _oecc143
#define OEMCrypto_UseSecondaryKey _oecc144
#define OEMCrypto_MarkOfflineSession _oecc153
#define OEMCrypto_WrapClearPrivateKey _oecc154
#define OEMCrypto_SetSessionUsage _oecc155
// clang-format on // clang-format on
/// @addtogroup initcontrol /// @addtogroup initcontrol
@@ -1938,6 +1942,33 @@ OEMCryptoResult OEMCrypto_GetOEMKeyToken(OEMCrypto_SESSION key_session,
uint8_t* key_token, uint8_t* key_token,
size_t* key_token_length); 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 /// @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, * 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. * 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 of OEMCrypto_CENCEncryptPatternDesc may be zero. If the skip
* the skip field is zero, then patterns are not in use and all crypto blocks * field is zero, then patterns are not in use and all crypto blocks in the
* in the encrypted part of the subsample are encrypted. It is not valid for * encrypted part of the subsample are encrypted, except for any partial crypto
* the encrypt field to be zero. * 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, * 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 * 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, OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* device_id,
size_t* device_id_length); 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 /// @addtogroup keybox
@@ -5862,6 +5948,36 @@ OEMCryptoResult OEMCrypto_Generic_Verify_V17(
OEMCryptoResult OEMCrypto_GetEmbeddedDrmCertificate(uint8_t* public_cert, OEMCryptoResult OEMCrypto_GetEmbeddedDrmCertificate(uint8_t* public_cert,
size_t* public_cert_length); 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 #ifdef __cplusplus
} }
#endif #endif

View File

@@ -117,7 +117,11 @@
#define Level3_GetSignatureHashAlgorithm _lcc139 #define Level3_GetSignatureHashAlgorithm _lcc139
#define Level3_EnterTestMode _lcc140 #define Level3_EnterTestMode _lcc140
#define Level3_GetDeviceSignedCsrPayload _lcc141 #define Level3_GetDeviceSignedCsrPayload _lcc141
#define Level3_UseSecondaryKey _lcc142
#define Level3_GetEmbeddedDrmCertificate _lcc143 #define Level3_GetEmbeddedDrmCertificate _lcc143
#define Level3_MarkOfflineSession _lcc144
// Added in OEMCrypto v19.3, but back ported to v18
#define Level3_SetSessionUsage _lcc155
#else #else
#define Level3_Initialize _oecc01 #define Level3_Initialize _oecc01
#define Level3_Terminate _oecc02 #define Level3_Terminate _oecc02
@@ -220,6 +224,10 @@
#define Level3_GetDeviceSignedCsrPayload _oecc141 #define Level3_GetDeviceSignedCsrPayload _oecc141
// Internal-only. // Internal-only.
#define Level3_GetEmbeddedDrmCertificate _oecc143 #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 #endif
#define Level3_GetInitializationState _oecl3o01 #define Level3_GetInitializationState _oecl3o01
@@ -444,6 +452,8 @@ OEMCrypto_WatermarkingSupport Level3_GetWatermarkingSupport();
OEMCryptoResult Level3_GetOEMKeyToken(OEMCrypto_SESSION key_session, OEMCryptoResult Level3_GetOEMKeyToken(OEMCrypto_SESSION key_session,
uint8_t* key_token, uint8_t* key_token,
size_t* key_token_length); size_t* key_token_length);
OEMCryptoResult Level3_SetSessionUsage(OEMCrypto_SESSION session,
uint32_t intent, uint32_t mode);
OEMCryptoResult Level3_GetDeviceInformation(uint8_t* device_info, OEMCryptoResult Level3_GetDeviceInformation(uint8_t* device_info,
size_t* device_info_length); size_t* device_info_length);
OEMCryptoResult Level3_GetDeviceSignedCsrPayload( OEMCryptoResult Level3_GetDeviceSignedCsrPayload(
@@ -493,7 +503,10 @@ OEMCryptoResult Level3_GetSignatureHashAlgorithm(
OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm); OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm);
OEMCryptoResult Level3_EnterTestMode(void); OEMCryptoResult Level3_EnterTestMode(void);
OEMCryptoResult Level3_GetEmbeddedDrmCertificate(uint8_t* public_cert, 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 // The following are specific to Google's Level 3 implementation and are not
// required. // required.

View File

@@ -13,12 +13,15 @@ cc_library_static {
"src/ecm_parser_v2.cpp", "src/ecm_parser_v2.cpp",
"src/ecm_parser_v3.cpp", "src/ecm_parser_v3.cpp",
"src/emm_parser.cpp", "src/emm_parser.cpp",
"src/file_util.cpp",
"src/license_key_status.cpp", "src/license_key_status.cpp",
"src/oemcrypto_interface.cpp", "src/oemcrypto_interface.cpp",
"src/policy_engine.cpp", "src/policy_engine.cpp",
"src/widevine_cas_session.cpp", "src/widevine_cas_session.cpp",
"src/widevine_media_cas_plugin.cpp", "src/widevine_media_cas_plugin.cpp",
"src/widevine_cas_api.cpp", "src/widevine_cas_api.cpp",
"src/privacy_crypto_boringssl.cpp",
"src/service_certificate.cpp",
], ],
proprietary: true, proprietary: true,
shared_libs: [ shared_libs: [

View File

@@ -10,7 +10,10 @@
#include "cas_status.h" #include "cas_status.h"
#include "crypto_session.h" #include "crypto_session.h"
#include "crypto_wrapped_key.h"
#include "file_store.h"
#include "policy_engine.h" #include "policy_engine.h"
#include "service_certificate.h"
#include "timer.h" #include "timer.h"
namespace wvcas { namespace wvcas {
@@ -28,32 +31,33 @@ class CasLicense : public wvutil::TimerHandler, public wvcas::CasEventListener {
// Generate a request to obtain a device certificate for requesting // Generate a request to obtain a device certificate for requesting
// entitlements. The generated message is set in |provisioning_request|. // entitlements. The generated message is set in |provisioning_request|.
virtual CasStatus GenerateDeviceProvisioningRequest( 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 // Process a server response containing a device certificate for use in
// requesting entitlements. The contained certificate data will be extracted // requesting entitlements. The contained certificate data will be extracted
// and wrapped for storage. The public key cert will be returned in // and wrapped for storage. The public key cert will be returned in
// |device_certificate|. The private key information will be wrapped by the // |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 // 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 // |device_file| if not nullptr. This file is suitable for storage on a device
virtual CasStatus HandleDeviceProvisioningResponse( virtual CasStatus HandleDeviceProvisioningResponse(
wvutil::FileSystem* file_system,
const std::string& signed_provisioning_response, const std::string& signed_provisioning_response,
std::string* device_certificate, std::string* wrapped_rsa_key, std::string* device_certificate,
std::string* device_file) const; CryptoWrappedKey* wrapped_private_key) const;
// Generate a request to obtain an EMM (Entitlement Management Message) to // Generate a request to obtain an EMM (Entitlement Management Message) to
// use to enable processing of ECM(s) (Encryption Management Message). // use to enable processing of ECM(s) (Encryption Management Message).
// |init_data| is widevine metadata about the stream needed in the request. // |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. // obtained by HandleDeviceProvisioningResponse.
virtual CasStatus GenerateEntitlementRequest( virtual CasStatus GenerateEntitlementRequest(
const std::string& init_data, const std::string& device_certificate, 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); std::string* signed_license_request);
// Restores a stored license making the keys available for use. // 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); const std::string& license_file);
// Process a server response containing a EMM for use in the processing of // 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. // for use in an EMM request.
virtual CasStatus HandleStoredDrmCert(const std::string& certificate, virtual CasStatus HandleStoredDrmCert(const std::string& certificate,
std::string* device_certificate, std::string* device_certificate,
std::string* wrapped_rsa_key); CryptoWrappedKey* private_key);
// Generate an entitlement renewal request message in // Generate an entitlement renewal request message in
// |signed_renewal_request|. // |signed_renewal_request|.
@@ -156,6 +160,19 @@ class CasLicense : public wvutil::TimerHandler, public wvcas::CasEventListener {
private: private:
CasStatus GenerateDeviceProvisioningRequestWithKeybox( CasStatus GenerateDeviceProvisioningRequestWithKeybox(
std::string* provisioning_request) const; 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 GenerateDeviceProvisioningRequestWithOEMCert() const;
CasStatus InstallLicense(const std::string& session_key, CasStatus InstallLicense(const std::string& session_key,
const std::string& serialized_license, const std::string& serialized_license,
@@ -177,6 +194,13 @@ class CasLicense : public wvutil::TimerHandler, public wvcas::CasEventListener {
std::string renewal_response_; std::string renewal_response_;
std::string init_data_; std::string init_data_;
bool is_renewal_in_license_file_ = false; bool is_renewal_in_license_file_ = false;
std::unique_ptr<ServiceCertificate> 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 } // namespace wvcas

View File

@@ -29,6 +29,9 @@ enum class CasStatusCode : int32_t {
kAccessDeniedByParentalControl = 15, kAccessDeniedByParentalControl = 15,
kUnknownEvent = 16, kUnknownEvent = 16,
kOEMCryptoVersionMismatch = 17, kOEMCryptoVersionMismatch = 17,
kDeviceCertificateError = 18,
kClientIdEncryptionError = 19,
kProvisioningError = 20,
}; };
class CasStatus { class CasStatus {

View File

@@ -24,10 +24,10 @@ enum class LicenseType {
typedef enum { typedef enum {
ProvisioningError = 0, // Device cannot be provisioned. ProvisioningError = 0, // Device cannot be provisioned.
DrmCertificate = 1, // Device has baked in DRM certificate DrmCertificate = 1, // Device has baked in DRM certificate (level 3 only)
// (level 3 only)
Keybox = 2, // Device has factory installed unique keybox. 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; } CasProvisioningMethod;
enum class CryptoMode { enum class CryptoMode {

View File

@@ -13,6 +13,7 @@
#include "OEMCryptoCENC.h" #include "OEMCryptoCENC.h"
#include "cas_status.h" #include "cas_status.h"
#include "cas_types.h" #include "cas_types.h"
#include "crypto_wrapped_key.h"
#include "oemcrypto_interface.h" #include "oemcrypto_interface.h"
#include "rw_lock.h" #include "rw_lock.h"
@@ -36,7 +37,7 @@ typedef OEMCrypto_HDCP_Capability HdcpCapability;
class CryptoLock { class CryptoLock {
public: public:
CryptoLock(){}; CryptoLock() {};
// These methods should be used to take the various CryptoSession mutexes in // These methods should be used to take the various CryptoSession mutexes in
// preference to taking the mutexes directly. // preference to taking the mutexes directly.
// //
@@ -175,7 +176,21 @@ class CryptoInterface {
OEMCrypto_SESSION key_session, uint8_t* key_token, OEMCrypto_SESSION key_session, uint8_t* key_token,
size_t* key_token_length); size_t* key_token_length);
virtual OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm( 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. // This is the factory method used to enable the oemcrypto interface.
static OEMCryptoResult create(std::unique_ptr<CryptoInterface>* init) { static OEMCryptoResult create(std::unique_ptr<CryptoInterface>* init) {
@@ -252,8 +267,6 @@ class CryptoSession {
std::string* wrapped_private_key); std::string* wrapped_private_key);
virtual CasStatus GetOEMPublicCertificate(uint8_t* public_cert, virtual CasStatus GetOEMPublicCertificate(uint8_t* public_cert,
size_t* public_cert_length); 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, virtual CasStatus GenerateRSASignature(const uint8_t* message,
size_t message_length, size_t message_length,
uint8_t* signature, uint8_t* signature,
@@ -291,6 +304,17 @@ class CryptoSession {
virtual CasStatus APIVersion(uint32_t* api_version); virtual CasStatus APIVersion(uint32_t* api_version);
virtual CasStatus GetOEMKeyToken(OEMCrypto_SESSION entitled_key_session_id, virtual CasStatus GetOEMKeyToken(OEMCrypto_SESSION entitled_key_session_id,
std::vector<uint8_t>& token); std::vector<uint8_t>& 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(const CryptoSession&) = delete;
CryptoSession& operator=(const CryptoSession&) = delete; CryptoSession& operator=(const CryptoSession&) = delete;

View File

@@ -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 <string>
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_

View File

@@ -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 <string>
#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_

View File

@@ -21,7 +21,7 @@ struct InputStreamParams {
size_t data_length; size_t data_length;
bool is_encrypted; bool is_encrypted;
InputStreamParams(){}; InputStreamParams() {};
InputStreamParams(const uint8_t* data_addr, size_t data_length, InputStreamParams(const uint8_t* data_addr, size_t data_length,
bool is_encrypted) bool is_encrypted)
: data_addr(data_addr), : data_addr(data_addr),
@@ -124,6 +124,20 @@ class OEMCryptoInterface {
virtual OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm( virtual OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm(
OEMCrypto_SESSION session, OEMCrypto_SESSION session,
OEMCrypto_SignatureHashAlgorithm* algorithm) const; 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(const OEMCryptoInterface&) = delete;
OEMCryptoInterface& operator=(const OEMCryptoInterface&) = delete; OEMCryptoInterface& operator=(const OEMCryptoInterface&) = delete;

View File

@@ -108,9 +108,9 @@ class PolicyEngine : public wvutil::TimerHandler {
int64_t GetPlaybackStartTime() const { return playback_start_time_; } int64_t GetPlaybackStartTime() const { return playback_start_time_; }
int64_t GetLastPlaybackTime() const { return last_playback_time_; } int64_t GetLastPlaybackTime() const { return last_playback_time_; }
int64_t GetGracePeriodEndTime() const { return grace_period_end_time_; } int64_t GetGracePeriodEndTime() const { return grace_period_end_time_; }
void RestorePlaybackTimes(int64_t playback_start_time, virtual void RestorePlaybackTimes(int64_t playback_start_time,
int64_t last_playback_time, int64_t last_playback_time,
int64_t grace_period_end_time); int64_t grace_period_end_time);
PolicyEngine(const PolicyEngine&) = delete; PolicyEngine(const PolicyEngine&) = delete;
PolicyEngine& operator=(const PolicyEngine&) = delete; PolicyEngine& operator=(const PolicyEngine&) = delete;

View File

@@ -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 <cstdint>
#include <string>
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_

View File

@@ -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 <memory>
#include <string>
#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<RsaPublicKey> public_key_;
}; // class ServiceCertificate
} // namespace wvcas
#endif // WIDEVINE_CAS_SERVICE_CERTIFICATE_H_

View File

@@ -160,7 +160,7 @@ class WidevineCas : public wvutil::TimerHandler {
std::unique_ptr<CasLicense> cas_license_; std::unique_ptr<CasLicense> cas_license_;
std::unique_ptr<wvutil::FileSystem> file_system_; std::unique_ptr<wvutil::FileSystem> file_system_;
std::string device_certificate_; std::string device_certificate_;
std::string wrapped_rsa_key_; std::unique_ptr<CryptoWrappedKey> wrapped_private_key_;
CasEventListener* event_listener_ = nullptr; CasEventListener* event_listener_ = nullptr;
std::mutex lock_; std::mutex lock_;
wvutil::Timer policy_timer_; wvutil::Timer policy_timer_;

View File

@@ -91,7 +91,7 @@ class WidevineCasPlugin : public CasPlugin, public CasEventListener {
std::unique_ptr<WidevineCas> widevine_cas_api) { std::unique_ptr<WidevineCas> widevine_cas_api) {
widevine_cas_api_ = std::move(widevine_cas_api); widevine_cas_api_ = std::move(widevine_cas_api);
} }
WidevineCasPlugin(){}; WidevineCasPlugin() {};
private: private:
// |sessionId| is nullptr if the event is not a session event. // |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 HandleAssignLicenseID(const CasData& license_id);
CasStatus HandlePluginVersionQuery(); CasStatus HandlePluginVersionQuery();
CasStatus HandleEntitlementPeriodUpdateResponse(const CasData& response); CasStatus HandleEntitlementPeriodUpdateResponse(const CasData& response);
status_t provisionInternal();
// Returns true if the device has been provisioned with a device certificate. // Returns true if the device has been provisioned with a device certificate.
bool is_provisioned() const; bool is_provisioned() const;

View File

@@ -15,32 +15,39 @@
#include "cas_properties.h" #include "cas_properties.h"
#include "cas_util.h" #include "cas_util.h"
#include "crypto_session.h" #include "crypto_session.h"
#include "crypto_wrapped_key.h"
#include "device_files.pb.h" #include "device_files.pb.h"
#include "file_util.h"
#include "license_protocol.pb.h" #include "license_protocol.pb.h"
#include "log.h" #include "log.h"
#include "service_certificate.h"
#include "string_conversions.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::CASDrmLicenseRequest;
using video_widevine::ClientIdentification; using video_widevine::ClientIdentification;
using video_widevine::ClientIdentification_ClientCapabilities; using video_widevine::ClientIdentification_ClientCapabilities;
using video_widevine:: using video_widevine::
ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_2048; ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_2048;
using video_widevine:: using video_widevine::
ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_3072; ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_3072;
using video_widevine::ClientIdentification_NameValue; using video_widevine::ClientIdentification_NameValue;
using video_widevine::DrmCertificate;
using video_widevine::HashAlgorithmProto; using video_widevine::HashAlgorithmProto;
using video_widevine::License; using video_widevine::License;
using video_widevine::License_KeyContainer; using video_widevine::License_KeyContainer;
using video_widevine::LicenseRequest; using video_widevine::LicenseRequest;
using video_widevine::ProvisioningRequest; using video_widevine::ProvisioningRequest;
using video_widevine::ProvisioningResponse; using video_widevine::ProvisioningResponse;
using video_widevine::PublicKeyToCertify;
using video_widevine::SignedDrmCertificate;
using video_widevine::SignedMessage; using video_widevine::SignedMessage;
using video_widevine::SignedProvisioningMessage; 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 kMacKeySizeBytes = 32;
static constexpr size_t kMacKeyPaddingSizeBytes = 16; 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 size_t kCertificateDataSizeBytes = 4 * 1024;
static constexpr uint32_t kRsaSignatureSizeBytes = 256; static constexpr uint32_t kRsaSignatureSizeBytes = 256;
static constexpr size_t kKeyboxTokenSizeBytes = 72; static constexpr size_t kKeyboxTokenSizeBytes = 72;
constexpr uint32_t OEM_CRYPTO_API_VERSION_SUPPORTS_PROV40_CORE_MESSAGE = 18;
namespace wvcas { namespace wvcas {
@@ -57,10 +65,43 @@ namespace {
constexpr char kKeyCompanyName[] = "company_name"; constexpr char kKeyCompanyName[] = "company_name";
constexpr char kKeyModelName[] = "model_name"; constexpr char kKeyModelName[] = "model_name";
constexpr char kKeyModelYear[] = "model_year";
constexpr char kKeyArchitectureName[] = "architecture_name"; constexpr char kKeyArchitectureName[] = "architecture_name";
constexpr char kKeyDeviceName[] = "device_name"; constexpr char kKeyDeviceName[] = "device_name";
constexpr char kKeyProductName[] = "product_name"; constexpr char kKeyProductName[] = "product_name";
constexpr char kKeyBuildInfo[] = "build_info";
constexpr char kKeyWvCasVersion[] = "widevine_cdm_version"; 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 // TODO(jfore): These variables are currently unused and are flagged as build
// errors in android. These values will be used in a future cl. // errors in android. These values will be used in a future cl.
@@ -218,6 +259,29 @@ bool OecAlgorithmToProtoAlgorithm(
return false; 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 } // namespace
CasStatus CasLicense::initialize(std::shared_ptr<CryptoSession> crypto_session, CasStatus CasLicense::initialize(std::shared_ptr<CryptoSession> crypto_session,
@@ -229,11 +293,18 @@ CasStatus CasLicense::initialize(std::shared_ptr<CryptoSession> crypto_session,
policy_engine_->initialize(crypto_session, this); policy_engine_->initialize(crypto_session, this);
crypto_session_ = crypto_session; crypto_session_ = crypto_session;
event_listener_ = listener; event_listener_ = listener;
wv_service_cert_ = std::make_unique<ServiceCertificate>();
CasStatus status =
wv_service_cert_->Init(wvutil::a2bs_hex(kCpProductionServiceCertificate));
if (!status.ok()) {
return status;
}
return CasStatusCode::kNoError; return CasStatusCode::kNoError;
} }
CasStatus CasLicense::GenerateDeviceProvisioningRequest( CasStatus CasLicense::GenerateDeviceProvisioningRequest(
std::string* serialized_provisioning_request) const { wvutil::FileSystem& file_system,
std::string* serialized_provisioning_request) {
if (!crypto_session_) { if (!crypto_session_) {
return CasStatus(CasStatusCode::kCasLicenseError, "no crypto session"); return CasStatus(CasStatusCode::kCasLicenseError, "no crypto session");
} }
@@ -247,6 +318,9 @@ CasStatus CasLicense::GenerateDeviceProvisioningRequest(
case Keybox: case Keybox:
return GenerateDeviceProvisioningRequestWithKeybox( return GenerateDeviceProvisioningRequestWithKeybox(
serialized_provisioning_request); serialized_provisioning_request);
case BootCertificateChain:
return GetProvisioning40RequestInternal(file_system,
serialized_provisioning_request);
default: default:
return CasStatus(CasStatusCode::kCasLicenseError, return CasStatus(CasStatusCode::kCasLicenseError,
"unknown provisioning method"); "unknown provisioning method");
@@ -256,10 +330,11 @@ CasStatus CasLicense::GenerateDeviceProvisioningRequest(
} }
CasStatus CasLicense::HandleDeviceProvisioningResponse( CasStatus CasLicense::HandleDeviceProvisioningResponse(
wvutil::FileSystem* file_system,
const std::string& signed_provisioning_response, const std::string& signed_provisioning_response,
std::string* device_certificate, std::string* wrapped_rsa_key, std::string* device_certificate,
std::string* device_file) const { CryptoWrappedKey* wrapped_private_key) const {
if (nullptr == device_certificate || nullptr == wrapped_rsa_key) { if (device_certificate == nullptr || wrapped_private_key == nullptr) {
return CasStatus(CasStatusCode::kCasLicenseError, return CasStatus(CasStatusCode::kCasLicenseError,
"missing device credentials"); "missing device credentials");
} }
@@ -278,6 +353,13 @@ CasStatus CasLicense::HandleDeviceProvisioningResponse(
return CasStatus(CasStatusCode::kIndividualizationError, return CasStatus(CasStatusCode::kIndividualizationError,
"unable to parse response"); "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()) { if (!signed_response.has_signature()) {
return CasStatus(CasStatusCode::kIndividualizationError, return CasStatus(CasStatusCode::kIndividualizationError,
"signature not found"); "signature not found");
@@ -303,37 +385,175 @@ CasStatus CasLicense::HandleDeviceProvisioningResponse(
"unable to parse signed message"); "unable to parse signed message");
} }
wrapped_private_key->Clear();
CasStatus status = crypto_session_->LoadProvisioning( 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) { if (status.status_code() != CasStatusCode::kNoError) {
return CasStatus(CasStatusCode::kIndividualizationError, return CasStatus(CasStatusCode::kIndividualizationError,
("rewrapCertificate fails")); ("rewrapCertificate fails"));
} }
if (nullptr != device_file) { // This is the entire certificate (SignedDrmCertificate).
File file; const std::string& device_cert_data =
file.set_type(File::DEVICE_CERTIFICATE); provisioning_response.device_certificate();
file.mutable_device_certificate()->set_certificate( // Need to parse cert for key type.
provisioning_response.device_certificate()); SignedDrmCertificate signed_device_cert;
file.mutable_device_certificate()->set_wrapped_private_key( if (!signed_device_cert.ParseFromString(device_cert_data)) {
*wrapped_rsa_key); LOGE("Failed to parse signed DRM certificate");
return CasStatus(CasStatusCode::kIndividualizationError,
HashedFile hash_file; "Failed to parse signed DRM certificate");
file.SerializeToString(hash_file.mutable_file()); }
if (!Hash(hash_file.file(), hash_file.mutable_hash())) { video_widevine::DrmCertificate device_cert;
return CasStatus(CasStatusCode::kIndividualizationError, if (!device_cert.ParseFromString(signed_device_cert.drm_certificate())) {
("generating file hash fails")); 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<int>(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(); *device_certificate = provisioning_response.device_certificate();
return crypto_session_->reset(); 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<uint32_t>(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( CasStatus CasLicense::GenerateEntitlementRequest(
const std::string& init_data, const std::string& device_certificate, 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) { std::string* signed_license_request) {
if (!signed_license_request) { if (!signed_license_request) {
return CasStatus(CasStatusCode::kCasLicenseError, "invalid parameters"); return CasStatus(CasStatusCode::kCasLicenseError, "invalid parameters");
@@ -344,69 +564,12 @@ CasStatus CasLicense::GenerateEntitlementRequest(
client_id->set_type(ClientIdentification::DRM_DEVICE_CERTIFICATE); client_id->set_type(ClientIdentification::DRM_DEVICE_CERTIFICATE);
client_id->set_token(device_certificate); client_id->set_token(device_certificate);
// Call OEMCrypto_GetOEMPublicCertificate before OEMCrypto_LoadDeviceRSAKey CasStatus status = crypto_session_->LoadCertificatePrivateKey(private_key);
// so it caches the OEMCrypto Public Key and then throw away result. (Prov3.0)
std::vector<uint8_t> buf(kCertificateDataSizeBytes, 0);
size_t buf_size = buf.size();
CasStatus status =
crypto_session_->GetOEMPublicCertificate(buf.data(), &buf_size);
status = crypto_session_->LoadDeviceRSAKey(
reinterpret_cast<const uint8_t*>(wrapped_rsa_key.data()),
wrapped_rsa_key.size());
if (!status.ok()) { if (!status.ok()) {
return status; return status;
} }
std::string value; FillClientProperties(*client_id);
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.
video_widevine::LicenseRequest_ContentIdentification* content_id = video_widevine::LicenseRequest_ContentIdentification* content_id =
license_request.mutable_content_id(); license_request.mutable_content_id();
@@ -474,8 +637,9 @@ CasStatus CasLicense::GenerateEntitlementRequest(
return CasStatusCode::kNoError; return CasStatusCode::kNoError;
} }
CasStatus CasLicense::HandleStoredLicense(const std::string& wrapped_rsa_key, CasStatus CasLicense::HandleStoredLicense(
const std::string& license_file) { const CryptoWrappedKey& wrapped_private_key,
const std::string& license_file) {
HashedFile hash_file; HashedFile hash_file;
if (!hash_file.ParseFromString(license_file)) { if (!hash_file.ParseFromString(license_file)) {
return CasStatus(CasStatusCode::kLicenseFileParseError, return CasStatus(CasStatusCode::kLicenseFileParseError,
@@ -520,10 +684,9 @@ CasStatus CasLicense::HandleStoredLicense(const std::string& wrapped_rsa_key,
"unable to parse license"); "unable to parse license");
} }
// Install the rsa provisioning key. // Install the provisioning key.
CasStatus status = crypto_session_->LoadDeviceRSAKey( CasStatus status =
reinterpret_cast<const uint8_t*>(wrapped_rsa_key.data()), crypto_session_->LoadCertificatePrivateKey(wrapped_private_key);
wrapped_rsa_key.size());
if (!status.ok()) { if (!status.ok()) {
return status; return status;
} }
@@ -555,8 +718,10 @@ CasStatus CasLicense::HandleStoredLicense(const std::string& wrapped_rsa_key,
// Reset the flag so expiration callback is enabled. // Reset the flag so expiration callback is enabled.
is_renewal_in_license_file_ = false; is_renewal_in_license_file_ = false;
// Expiration event is expected to be fired if license is shown expired. // Expiration event is expected to be fired if license is shown expired.
OnLicenseExpiration(); if (!status.ok()) {
return status; OnLicenseExpiration();
return status;
}
} }
policy_engine_->RestorePlaybackTimes(license.playback_start_time(), policy_engine_->RestorePlaybackTimes(license.playback_start_time(),
license.last_playback_time(), license.last_playback_time(),
@@ -767,6 +932,155 @@ CasStatus CasLicense::GenerateDeviceProvisioningRequestWithKeybox(
return CasStatusCode::kNoError; 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. // TODO(jfore): Currently not functional, implement me as needed.
CasStatus CasLicense::GenerateDeviceProvisioningRequestWithOEMCert() const { CasStatus CasLicense::GenerateDeviceProvisioningRequestWithOEMCert() const {
ProvisioningRequest provisioning_request; ProvisioningRequest provisioning_request;
@@ -858,31 +1172,15 @@ CasStatus CasLicense::InstallLicenseRenewal(
CasStatus CasLicense::HandleStoredDrmCert(const std::string& certificate, CasStatus CasLicense::HandleStoredDrmCert(const std::string& certificate,
std::string* device_certificate, std::string* device_certificate,
std::string* wrapped_rsa_key) { CryptoWrappedKey* private_key) {
if (nullptr == device_certificate || nullptr == wrapped_rsa_key) { if (nullptr == device_certificate || private_key == nullptr) {
return CasStatus(CasStatusCode::kIndividualizationError, return CasStatus(CasStatusCode::kIndividualizationError,
("missing output parameters")); ("missing output parameters"));
} }
HashedFile hash_file; video_widevine_client::sdk::File file;
if (!hash_file.ParseFromString(certificate)) { CasStatus status = RetrieveHashedFile(certificate, file);
return CasStatus(CasStatusCode::kIndividualizationError, if (status.status_code() != CasStatusCode::kNoError) {
("unable to parse the certificate data")); return status;
}
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"));
} }
if (file.type() != File::DEVICE_CERTIFICATE) { if (file.type() != File::DEVICE_CERTIFICATE) {
return CasStatus(CasStatusCode::kIndividualizationError, return CasStatus(CasStatusCode::kIndividualizationError,
@@ -896,7 +1194,30 @@ CasStatus CasLicense::HandleStoredDrmCert(const std::string& certificate,
} }
*device_certificate = file.device_certificate().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; return CasStatusCode::kNoError;
} }

View File

@@ -10,8 +10,19 @@
#include <sstream> #include <sstream>
#include "cas_util.h" #include "cas_util.h"
#include "crypto_wrapped_key.h"
#include "log.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 wvcas {
namespace { namespace {
constexpr uint32_t kExpectedOEMCryptoVersion = 18; constexpr uint32_t kExpectedOEMCryptoVersion = 18;
@@ -75,41 +86,46 @@ void FillEntitledContentKeyObjectFromKeyData(
dest->cipher_mode = CipherModeFromKeyData(src.cipher_mode); 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<uint8_t*>(&s->front());
}
} // namespace } // namespace
wvutil::shared_mutex CryptoLock::static_field_mutex_; wvutil::shared_mutex CryptoLock::static_field_mutex_;
wvutil::shared_mutex CryptoLock::oem_crypto_mutex_; wvutil::shared_mutex CryptoLock::oem_crypto_mutex_;
template <class Func> template <class Func>
auto CryptoLock::WithStaticFieldWriteLock(const char* tag, auto CryptoLock::WithStaticFieldWriteLock(const char* tag, Func body)
Func body) -> decltype(body()) { -> decltype(body()) {
LOGV("Static field write lock: %s", tag); LOGV("Static field write lock: %s", tag);
std::unique_lock<wvutil::shared_mutex> auto_lock(static_field_mutex_); std::unique_lock<wvutil::shared_mutex> auto_lock(static_field_mutex_);
return body(); return body();
} }
template <class Func> template <class Func>
auto CryptoLock::WithStaticFieldReadLock(const char* tag, auto CryptoLock::WithStaticFieldReadLock(const char* tag, Func body)
Func body) -> decltype(body()) { -> decltype(body()) {
LOGV("Static field read lock: %s", tag); LOGV("Static field read lock: %s", tag);
wvutil::shared_lock<wvutil::shared_mutex> auto_lock(static_field_mutex_); wvutil::shared_lock<wvutil::shared_mutex> auto_lock(static_field_mutex_);
return body(); return body();
} }
template <class Func> template <class Func>
auto CryptoLock::WithOecWriteLock(const char* tag, auto CryptoLock::WithOecWriteLock(const char* tag, Func body)
Func body) -> decltype(body()) { -> decltype(body()) {
LOGV("OEMCrypto write lock: %s", tag); LOGV("OEMCrypto write lock: %s", tag);
std::unique_lock<wvutil::shared_mutex> auto_lock(oem_crypto_mutex_); std::unique_lock<wvutil::shared_mutex> auto_lock(oem_crypto_mutex_);
return body(); return body();
} }
template <class Func> template <class Func>
auto CryptoLock::WithOecReadLock(const char* tag, auto CryptoLock::WithOecReadLock(const char* tag, Func body)
Func body) -> decltype(body()) { -> decltype(body()) {
LOGV("OEMCrypto read lock: %s", tag); LOGV("OEMCrypto read lock: %s", tag);
wvutil::shared_lock<wvutil::shared_mutex> auto_lock(oem_crypto_mutex_); wvutil::shared_lock<wvutil::shared_mutex> auto_lock(oem_crypto_mutex_);
return body(); return body();
} }
template <class Func> template <class Func>
auto CryptoLock::WithOecSessionLock(const char* tag, auto CryptoLock::WithOecSessionLock(const char* tag, Func body)
Func body) -> decltype(body()) { -> decltype(body()) {
LOGV("OEMCrypto Session Lock - %s", tag); LOGV("OEMCrypto Session Lock - %s", tag);
wvutil::shared_lock<wvutil::shared_mutex> oec_auto_lock(oem_crypto_mutex_); wvutil::shared_lock<wvutil::shared_mutex> oec_auto_lock(oem_crypto_mutex_);
std::unique_lock<std::mutex> session_auto_lock(oem_crypto_session_mutex_); std::unique_lock<std::mutex> 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( OEMCryptoResult CryptoInterface::OEMCrypto_GenerateRSASignature(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
uint8_t* signature, size_t* signature_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( OEMCryptoResult CryptoInterface::create_internal(
OEMCryptoInterface* oemcrypto_interface, OEMCryptoInterface* oemcrypto_interface,
std::unique_ptr<CryptoInterface>* init) { std::unique_ptr<CryptoInterface>* init) {
@@ -462,6 +523,8 @@ CasProvisioningMethod CryptoSession::provisioning_method() {
return OEMCertificate; return OEMCertificate;
case OEMCrypto_DrmCertificate: case OEMCrypto_DrmCertificate:
return DrmCertificate; return DrmCertificate;
case OEMCrypto_BootCertificateChain:
return BootCertificateChain;
default: default:
return ProvisioningError; return ProvisioningError;
} }
@@ -661,9 +724,36 @@ CasStatus CryptoSession::PrepareAndSignProvisioningRequest(
"Missing core_message or signature."); "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; OEMCryptoResult sts;
// Keybox based provisioning does not need hash algorithm. if (should_specify_algorithm) {
should_specify_algorithm = false; 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 signature_length = 0;
size_t core_message_length = 0; size_t core_message_length = 0;
*core_message = ""; *core_message = "";
@@ -768,26 +858,6 @@ CasStatus CryptoSession::GetOEMPublicCertificate(uint8_t* public_cert,
return CasStatus::OkStatus(); 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( CasStatus CryptoSession::GenerateRSASignature(
const uint8_t* message, size_t message_length, uint8_t* signature, const uint8_t* message, size_t message_length, uint8_t* signature,
size_t* signature_length, RSA_Padding_Scheme padding_scheme) { size_t* signature_length, RSA_Padding_Scheme padding_scheme) {
@@ -1080,4 +1150,178 @@ CasStatus CryptoSession::GetOEMKeyToken(
return CasStatus::OkStatus(); 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<int>(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<const uint8_t*>(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<const uint8_t*>(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 } // namespace wvcas

270
plugin/src/file_util.cpp Normal file
View File

@@ -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 <openssl/sha.h>
#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<const unsigned char*>(data.data());
unsigned char* output = reinterpret_cast<unsigned char*>(&(*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<wvutil::File> 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<wvutil::File> 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<int>(file.type()));
return false;
}
if (file.version() != File::VERSION_1) {
LOGE("Certificate file is of incorrect file version: version = %d",
static_cast<int>(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

View File

@@ -115,6 +115,20 @@ class OEMCryptoInterface::Impl {
size_t* key_token_length); size_t* key_token_length);
typedef OEMCryptoResult (*GetSignatureHashAlgorithm_t)( typedef OEMCryptoResult (*GetSignatureHashAlgorithm_t)(
OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm); 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; Initialize_t Initialize = nullptr;
Terminate_t Terminate = nullptr; Terminate_t Terminate = nullptr;
@@ -146,6 +160,11 @@ class OEMCryptoInterface::Impl {
APIVersion_t APIVersion = nullptr; APIVersion_t APIVersion = nullptr;
GetOEMKeyToken_t GetOEMKeyToken = nullptr; GetOEMKeyToken_t GetOEMKeyToken = nullptr;
GetSignatureHashAlgorithm_t GetSignatureHashAlgorithm = 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: private:
bool initialize() { bool initialize() {
@@ -190,6 +209,11 @@ class OEMCryptoInterface::Impl {
LOAD_SYM(ReassociateEntitledKeySession); LOAD_SYM(ReassociateEntitledKeySession);
TRY_LOAD_SYM(GetOEMKeyToken); TRY_LOAD_SYM(GetOEMKeyToken);
TRY_LOAD_SYM(GetSignatureHashAlgorithm); 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. // Optional methods that may be available.
TRY_LOAD_SYM(LoadTestKeybox); TRY_LOAD_SYM(LoadTestKeybox);
@@ -404,10 +428,44 @@ OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetSignatureHashAlgorithm(
OEMCrypto_SESSION session, OEMCrypto_SESSION session,
OEMCrypto_SignatureHashAlgorithm* algorithm) const { OEMCrypto_SignatureHashAlgorithm* algorithm) const {
// Optional method. Handle missing method. // Optional method. Handle missing method.
if (impl_->GetOEMKeyToken == nullptr) { if (impl_->GetSignatureHashAlgorithm == nullptr) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED; return OEMCrypto_ERROR_NOT_IMPLEMENTED;
} }
return impl_->GetSignatureHashAlgorithm(session, algorithm); 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 } // namespace wvcas

View File

@@ -12,8 +12,8 @@
#include "string_conversions.h" #include "string_conversions.h"
// TODO(vtarasov): // TODO(vtarasov):
//#include "properties.h" // #include "properties.h"
//#include "cas_event_listener.h" // #include "cas_event_listener.h"
using video_widevine::License; using video_widevine::License;

View File

@@ -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 <openssl/aes.h>
#include <openssl/asn1.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/md5.h>
#include <openssl/pem.h>
#include <openssl/pkcs7.h>
#include <openssl/rsa.h>
#include <openssl/sha.h>
#include <openssl/x509v3.h>
#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<char*>(serialized_key.data()),
static_cast<int>(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 <typename T, void (*func)(T*)>
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<const uint8_t*>(&key_[0]),
reinterpret_cast<const uint8_t*>(&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<int>(out->size());
if (EVP_EncryptUpdate(evp_cipher_ctx, reinterpret_cast<uint8_t*>(&(*out)[0]),
&out_length,
reinterpret_cast<const uint8_t*>(in.data()),
static_cast<int>(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<uint8_t*>(&(*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<const uint8_t*>(&key_[0]),
reinterpret_cast<const uint8_t*>(&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<int>(out->size());
if (EVP_DecryptUpdate(evp_cipher_ctx, reinterpret_cast<uint8_t*>(&(*out)[0]),
&out_length,
reinterpret_cast<const uint8_t*>(in.data()),
static_cast<int>(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<uint8_t*>(&(*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<int>(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<int>(clear_message.size()),
const_cast<unsigned char*>(
reinterpret_cast<const unsigned char*>(clear_message.data())),
reinterpret_cast<unsigned char*>(&(*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_MD*>(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<uint8_t*>(
reinterpret_cast<const uint8_t*>(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<ASN1_OBJECT, ASN1_OBJECT_free> 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<const unsigned char*>(cert.data());
long cert_size = static_cast<long>(cert.size());
boringssl_ptr<PKCS7, PKCS7_free> 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<size_t>(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<int>(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<uint32_t>(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<const uint8_t*>(data.data()), data.size(),
reinterpret_cast<uint8_t*>(&hash[0]));
return hash;
}
std::string Sha1Hash(const std::string& data) {
std::string hash(SHA_DIGEST_LENGTH, '\0');
SHA1(reinterpret_cast<const uint8_t*>(data.data()), data.size(),
reinterpret_cast<uint8_t*>(&hash[0]));
return hash;
}
std::string Sha256Hash(const std::string& data) {
std::string hash(SHA256_DIGEST_LENGTH, '\0');
SHA256(reinterpret_cast<const uint8_t*>(data.data()), data.size(),
reinterpret_cast<uint8_t*>(&hash[0]));
return hash;
}
std::string Sha512Hash(const std::string& data) {
std::string hash(SHA512_DIGEST_LENGTH, '\0');
SHA512(reinterpret_cast<const uint8_t*>(data.data()), data.size(),
reinterpret_cast<uint8_t*>(&hash[0]));
return hash;
}
} // namespace wvcas

View File

@@ -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 <stdlib.h>
#include <algorithm>
#include <random>
#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<short> 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<char>(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<int>(service_cert.type()),
static_cast<int>(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<RsaPublicKey>();
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

View File

@@ -7,6 +7,7 @@
#include "cas_events.h" #include "cas_events.h"
#include "cas_util.h" #include "cas_util.h"
#include "file_util.h"
#include "license_protocol.pb.h" #include "license_protocol.pb.h"
#include "log.h" #include "log.h"
#include "media_cas.pb.h" #include "media_cas.pb.h"
@@ -18,55 +19,6 @@ constexpr char kCertFileBase[] = "cert.bin";
constexpr char kLicenseFileNameSuffix[] = ".lic"; constexpr char kLicenseFileNameSuffix[] = ".lic";
namespace { 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<wvutil::File> 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<wvutil::File> 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, std::string GenerateLicenseFilename(const std::string& content_id,
const std::string& provider_id) { const std::string& provider_id) {
std::string data(content_id + provider_id); std::string data(content_id + provider_id);
@@ -215,6 +167,7 @@ void WidevineCas::OnTimerEvent() {
CasStatus WidevineCas::initialize(CasEventListener* event_listener) { CasStatus WidevineCas::initialize(CasEventListener* event_listener) {
std::unique_lock<std::mutex> locker(lock_); std::unique_lock<std::mutex> locker(lock_);
crypto_session_ = getCryptoSession(); crypto_session_ = getCryptoSession();
wrapped_private_key_ = std::make_unique<CryptoWrappedKey>();
// For session name generation. // For session name generation.
srand(time(nullptr)); srand(time(nullptr));
// Setup an oemcrypto session. // Setup an oemcrypto session.
@@ -241,7 +194,7 @@ CasStatus WidevineCas::initialize(CasEventListener* event_listener) {
if (ReadFileFromStorage(*file_system_, cert_filename_path, &cert_file)) { if (ReadFileFromStorage(*file_system_, cert_filename_path, &cert_file)) {
LOGI("read cert.bin successfully"); LOGI("read cert.bin successfully");
if (!HandleStoredDrmCert(cert_file).ok()) { if (!HandleStoredDrmCert(cert_file).ok()) {
return CasStatusCode::kNoError; LOGW("HandleStoredDrmCert failed");
} }
} }
@@ -404,7 +357,8 @@ CasStatus WidevineCas::generateDeviceProvisioningRequest(
return CasStatus(CasStatusCode::kInvalidParameter, return CasStatus(CasStatusCode::kInvalidParameter,
"missing output buffer for provisioning request"); "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) { CasStatus WidevineCas::handleProvisioningResponse(const std::string& response) {
@@ -412,21 +366,10 @@ CasStatus WidevineCas::handleProvisioningResponse(const std::string& response) {
return CasStatus(CasStatusCode::kCasLicenseError, return CasStatus(CasStatusCode::kCasLicenseError,
"empty individualization response"); "empty individualization response");
} }
std::string device_file;
std::unique_lock<std::mutex> locker(lock_); std::unique_lock<std::mutex> locker(lock_);
CasStatus status = cas_license_->HandleDeviceProvisioningResponse( return cas_license_->HandleDeviceProvisioningResponse(
response, &device_certificate_, &wrapped_rsa_key_, &device_file); file_system_.get(), response, &device_certificate_,
wrapped_private_key_.get());
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;
} }
CasStatus WidevineCas::generateEntitlementRequest( CasStatus WidevineCas::generateEntitlementRequest(
@@ -465,6 +408,7 @@ CasStatus WidevineCas::generateEntitlementRequest(
return CasStatus(CasStatusCode::kCasLicenseError, return CasStatus(CasStatusCode::kCasLicenseError,
"license is expired, unable to process emm"); "license is expired, unable to process emm");
} }
license_id = license_id =
filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix)); filename.substr(0, filename.size() - strlen(kLicenseFileNameSuffix));
if (cas_license_->IsGroupLicense()) { if (cas_license_->IsGroupLicense()) {
@@ -483,9 +427,8 @@ CasStatus WidevineCas::generateEntitlementRequest(
return CasStatus(CasStatusCode::kInvalidParameter, return CasStatus(CasStatusCode::kInvalidParameter,
"missing output buffer for entitlement request"); "missing output buffer for entitlement request");
} }
std::unique_lock<std::mutex> locker(lock_);
return cas_license_->GenerateEntitlementRequest( return cas_license_->GenerateEntitlementRequest(
init_data, device_certificate_, wrapped_rsa_key_, license_type_, init_data, device_certificate_, *wrapped_private_key_, license_type_,
entitlement_request); entitlement_request);
} }
@@ -609,7 +552,7 @@ CasStatus WidevineCas::generateEntitlementPeriodUpdateRequest(
std::string entitlement_request; std::string entitlement_request;
status = next_cas_license_->GenerateEntitlementRequest( 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); &entitlement_request);
if (!status.ok()) { if (!status.ok()) {
LOGE("WidevineCas generate entitlement request failed: %d", LOGE("WidevineCas generate entitlement request failed: %d",
@@ -699,7 +642,8 @@ CasStatus WidevineCas::RemoveLicense(const std::string& file_name) {
} }
bool WidevineCas::is_provisioned() const { 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, 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"); return CasStatus(CasStatusCode::kCasLicenseError, "empty certificate data");
} }
CasStatus status = cas_license_->HandleStoredDrmCert( CasStatus status = cas_license_->HandleStoredDrmCert(
certificate, &device_certificate_, &wrapped_rsa_key_); certificate, &device_certificate_, wrapped_private_key_.get());
return status; return status;
} }
@@ -893,7 +837,7 @@ bool WidevineCas::TryReuseStoredLicense(const std::string& filename) {
// Load the stored license to the session. // Load the stored license to the session.
CasStatus status = CasStatus status =
cas_license_->HandleStoredLicense(wrapped_rsa_key_, license_file); cas_license_->HandleStoredLicense(*wrapped_private_key_, license_file);
if (!status.ok()) { if (!status.ok()) {
LOGW("Failed to load stored license. code: %d, message: %s", LOGW("Failed to load stored license. code: %d, message: %s",
status.status_code(), status.error_string().c_str()); status.status_code(), status.error_string().c_str());

View File

@@ -224,6 +224,10 @@ status_t WidevineCasPlugin::provision(const String8& provisionString) {
return OK; return OK;
} }
return provisionInternal();
}
status_t WidevineCasPlugin::provisionInternal() {
std::string provisioning_request; std::string provisioning_request;
CasStatus status = widevine_cas_api_->generateDeviceProvisioningRequest( CasStatus status = widevine_cas_api_->generateDeviceProvisioningRequest(
&provisioning_request); &provisioning_request);
@@ -247,8 +251,8 @@ status_t WidevineCasPlugin::requestLicense(const std::string& init_data) {
CasStatus status = widevine_cas_api_->generateEntitlementRequest( CasStatus status = widevine_cas_api_->generateEntitlementRequest(
init_data, &signed_license_request, license_id); init_data, &signed_license_request, license_id);
if (!status.ok()) { if (!status.ok()) {
LOGE("WidevineCas generate entitlement request failed: %d", LOGE("WidevineCas generate entitlement request failed: %d: %s",
status.status_code()); status.status_code(), status.error_string().c_str());
return INVALID_OPERATION; return INVALID_OPERATION;
} }
@@ -317,6 +321,13 @@ CasStatus WidevineCasPlugin::HandleIndividualizationResponse(
return status; return status;
} }
// Provisioning 4 has two stages of provisioning.
if (!is_provisioned()) {
// Return value ingored here.
provisionInternal();
return CasStatusCode::kNoError;
}
CallBack(reinterpret_cast<void*>(app_data_), INDIVIDUALIZATION_COMPLETE, 0, CallBack(reinterpret_cast<void*>(app_data_), INDIVIDUALIZATION_COMPLETE, 0,
nullptr, 0, nullptr); nullptr, 0, nullptr);

View File

@@ -20,9 +20,25 @@ message NameValue {
optional string value = 2; optional string value = 2;
} }
message DeviceCertificate { message OemCertificate {
enum PrivateKeyType {
RSA = 0;
ECC = 1;
}
optional bytes certificate = 1; optional bytes certificate = 1;
optional bytes wrapped_private_key = 2; 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 { message License {
@@ -95,6 +111,8 @@ message File {
USAGE_INFO = 3; USAGE_INFO = 3;
HLS_ATTRIBUTES = 4; HLS_ATTRIBUTES = 4;
USAGE_TABLE_INFO = 5; USAGE_TABLE_INFO = 5;
OKP_INFO = 6;
OEM_CERTIFICATE = 7;
} }
enum FileVersion { VERSION_1 = 1; } enum FileVersion { VERSION_1 = 1; }
@@ -106,6 +124,8 @@ message File {
optional UsageInfo usage_info = 5; optional UsageInfo usage_info = 5;
optional HlsAttributes hls_attributes = 6; optional HlsAttributes hls_attributes = 6;
optional UsageTableInfo usage_table_info = 7; optional UsageTableInfo usage_table_info = 7;
reserved 8;
optional OemCertificate oem_certificate = 9;
} }
message HashedFile { message HashedFile {

View File

@@ -580,6 +580,30 @@ message GroupKeys {
// Public protocol buffer definitions for Widevine Device Certificate // Public protocol buffer definitions for Widevine Device Certificate
// Provisioning protocol. // 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 // ProvisioningOptions specifies the type of certificate to specify and
// in the case of X509 certificates, the certificate authority to use. // in the case of X509 certificates, the certificate authority to use.
message ProvisioningOptions { message ProvisioningOptions {
@@ -642,6 +666,10 @@ message ProvisioningRequest {
// SessionKeys encrypted using a service cert public key. // SessionKeys encrypted using a service cert public key.
// Required for keybox provisioning. // Required for keybox provisioning.
optional EncryptedSessionKeys encrypted_session_keys = 8; 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. // Provisioning response sent by the provisioning server to client devices.
@@ -758,6 +786,7 @@ message ClientIdentification {
DRM_DEVICE_CERTIFICATE = 1; DRM_DEVICE_CERTIFICATE = 1;
REMOTE_ATTESTATION_CERTIFICATE = 2; REMOTE_ATTESTATION_CERTIFICATE = 2;
OEM_DEVICE_CERTIFICATE = 3; OEM_DEVICE_CERTIFICATE = 3;
BOOT_CERTIFICATE_CHAIN = 4;
} }
message NameValue { message NameValue {
@@ -810,6 +839,25 @@ message ClientIdentification {
optional bool can_disable_analog_output = 11 [default = false]; 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. // Type of factory-provisioned device root of trust. Optional.
optional TokenType type = 1 [default = KEYBOX]; optional TokenType type = 1 [default = KEYBOX];
// Factory-provisioned device root of trust. Required. // Factory-provisioned device root of trust. Required.
@@ -825,6 +873,8 @@ message ClientIdentification {
optional ClientCapabilities client_capabilities = 6; optional ClientCapabilities client_capabilities = 6;
// Serialized VmpData message. Optional. // Serialized VmpData message. Optional.
optional bytes vmp_data = 7; optional bytes vmp_data = 7;
// Optional field that may contain additional provisioning credentials.
optional ClientCredentials device_credentials = 8;
} }
// EncryptedClientIdentification message used to hold ClientIdentification // EncryptedClientIdentification message used to hold ClientIdentification
@@ -1259,3 +1309,149 @@ message SignedCASDrmResponse {
optional bytes response = 1; optional bytes response = 1;
optional bytes signature = 2; 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;
}

View File

@@ -13,8 +13,10 @@
#include "cas_status.h" #include "cas_status.h"
#include "cas_util.h" #include "cas_util.h"
#include "device_files.pb.h" #include "device_files.pb.h"
#include "file_util.h"
#include "license_protocol.pb.h" #include "license_protocol.pb.h"
#include "mock_crypto_session.h" #include "mock_crypto_session.h"
#include "mock_file.h"
#include "string_conversions.h" #include "string_conversions.h"
namespace wvcas { namespace wvcas {
@@ -22,6 +24,8 @@ namespace {
using ::testing::_; using ::testing::_;
using ::testing::AllOf; using ::testing::AllOf;
using ::testing::AtLeast;
using ::testing::ByMove;
using ::testing::DoAll; using ::testing::DoAll;
using ::testing::Eq; using ::testing::Eq;
using ::testing::Invoke; using ::testing::Invoke;
@@ -32,6 +36,7 @@ using ::testing::Return;
using ::testing::ReturnRef; using ::testing::ReturnRef;
using ::testing::SetArgPointee; using ::testing::SetArgPointee;
using ::testing::SetArgReferee; using ::testing::SetArgReferee;
using ::testing::SetArrayArgument;
using ::testing::StrictMock; using ::testing::StrictMock;
using wvutil::Base64Decode; using wvutil::Base64Decode;
@@ -41,6 +46,7 @@ using wvutil::Base64SafeEncodeNoPad;
using video_widevine_client::sdk::DeviceCertificate; using video_widevine_client::sdk::DeviceCertificate;
using video_widevine_client::sdk::File; using video_widevine_client::sdk::File;
using video_widevine_client::sdk::HashedFile; using video_widevine_client::sdk::HashedFile;
using video_widevine_client::sdk::OemCertificate;
static constexpr char kKeyboxToken[] = "KeyBoxToken"; static constexpr char kKeyboxToken[] = "KeyBoxToken";
static constexpr char kExpectedRenewalRequest[] = "ExpectedRenewalRequest"; static constexpr char kExpectedRenewalRequest[] = "ExpectedRenewalRequest";
@@ -76,6 +82,14 @@ static constexpr char kKeyOemCryptoSecurityPatchLevel[] =
"oem_crypto_security_patch_level"; "oem_crypto_security_patch_level";
static constexpr char kRenewalSereverURL[] = "ExpectedRenewalURL"; static constexpr char kRenewalSereverURL[] = "ExpectedRenewalURL";
static constexpr char kCoreMessage[] = "CoreMessage"; 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<MockCryptoSession> StrictMockCryptoSession; typedef StrictMock<MockCryptoSession> StrictMockCryptoSession;
@@ -93,13 +107,17 @@ class MockPolicyEngine : public wvcas::PolicyEngine {
MOCK_CONST_METHOD0(IsExpired, bool()); MOCK_CONST_METHOD0(IsExpired, bool());
MOCK_CONST_METHOD0(CanPersist, bool()); MOCK_CONST_METHOD0(CanPersist, bool());
MOCK_CONST_METHOD0(always_include_client_id, 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<MockPolicyEngine> StrictMockPolicyEngine; typedef StrictMock<MockPolicyEngine> StrictMockPolicyEngine;
class TestCasLicense : public wvcas::CasLicense { class TestCasLicense : public wvcas::CasLicense {
public: public:
explicit TestCasLicense() {} explicit TestCasLicense() {}
~TestCasLicense() override{}; ~TestCasLicense() override {};
std::unique_ptr<wvcas::PolicyEngine> GetPolicyEngine() override { std::unique_ptr<wvcas::PolicyEngine> GetPolicyEngine() override {
policy_engine_ = pass_thru_.get(); policy_engine_ = pass_thru_.get();
return std::move(pass_thru_); return std::move(pass_thru_);
@@ -124,6 +142,8 @@ class CasLicenseTest : public ::testing::TestWithParam<bool> {
std::shared_ptr<StrictMockCryptoSession> strict_mock_; std::shared_ptr<StrictMockCryptoSession> strict_mock_;
std::string wrapped_rsa_key_; std::string wrapped_rsa_key_;
std::string device_certificate_; std::string device_certificate_;
wvutil::FileSystem file_system_;
CryptoWrappedKey wrapped_private_key_;
}; };
std::string CasLicenseTest::CreateProvisioningResponse() { std::string CasLicenseTest::CreateProvisioningResponse() {
@@ -138,9 +158,14 @@ std::string CasLicenseTest::CreateProvisioningResponse() {
response.set_device_rsa_key(kDeviceRsaKey); response.set_device_rsa_key(kDeviceRsaKey);
response.set_device_rsa_key_iv(kDeviceRsaKeyIV); 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()); response.SerializeToString(signed_message.mutable_message());
std::vector<uint8_t> b64_message(signed_message.ByteSize()); std::vector<uint8_t> b64_message(signed_message.ByteSizeLong());
signed_message.SerializeToArray(&b64_message[0], b64_message.size()); signed_message.SerializeToArray(&b64_message[0], b64_message.size());
return kJsonStartSubstr + Base64SafeEncodeNoPad(b64_message) + kJsonEndSubstr; return kJsonStartSubstr + Base64SafeEncodeNoPad(b64_message) + kJsonEndSubstr;
@@ -207,6 +232,21 @@ std::string CreateLicenseFileData() {
return hashed_file.SerializeAsString(); 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) { TEST_F(CasLicenseTest, GenerateDeviceProvisioningRequest) {
strict_mock_ = std::make_shared<StrictMockCryptoSession>(); strict_mock_ = std::make_shared<StrictMockCryptoSession>();
TestCasLicense cas_license; TestCasLicense cas_license;
@@ -238,7 +278,7 @@ TEST_F(CasLicenseTest, GenerateDeviceProvisioningRequest) {
.WillOnce(Return(wvcas::CasStatusCode::kNoError)); .WillOnce(Return(wvcas::CasStatusCode::kNoError));
wvcas::CasStatus status = cas_license.GenerateDeviceProvisioningRequest( wvcas::CasStatus status = cas_license.GenerateDeviceProvisioningRequest(
&serialized_provisioning_request); file_system_, &serialized_provisioning_request);
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
// Verify the provisioning request. // Verify the provisioning request.
@@ -263,6 +303,127 @@ TEST_F(CasLicenseTest, GenerateDeviceProvisioningRequest) {
provisioning_request.nonce().data())); provisioning_request.nonce().data()));
} }
TEST_F(CasLicenseTest, GenerateDeviceProvisioning4FirstStageRequest) {
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
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<StrictMockCryptoSession>();
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<wvutil::MockFile>();
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) { TEST_F(CasLicenseTest, HandleProvisioningResponse) {
const std::string provisioning_response = CreateProvisioningResponse(); const std::string provisioning_response = CreateProvisioningResponse();
@@ -274,47 +435,34 @@ TEST_F(CasLicenseTest, HandleProvisioningResponse) {
EXPECT_CALL(*strict_mock_, reset()) EXPECT_CALL(*strict_mock_, reset())
.WillOnce(Return(wvcas::CasStatusCode::kNoError)); .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<wvutil::MockFile>();
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( 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()); EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
} }
TEST_F(CasLicenseTest, HandleProvisioningResponseWithDeviceFileOutput) {
const std::string provisioning_response = CreateProvisioningResponse();
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
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) { TEST_F(CasLicenseTest, GenerateEntitlementRequest) {
strict_mock_ = std::make_shared<StrictMockCryptoSession>(); strict_mock_ = std::make_shared<StrictMockCryptoSession>();
TestCasLicense cas_license; TestCasLicense cas_license;
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
EXPECT_EQ(wvcas::CasStatusCode::kNoError, EXPECT_EQ(wvcas::CasStatusCode::kNoError,
cas_license.initialize(strict_mock_, nullptr).status_code()); cas_license.initialize(strict_mock_, nullptr).status_code());
EXPECT_CALL(*strict_mock_, GetSecurityPatchLevel()).WillOnce(Return(1));
EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _)) EXPECT_CALL(*strict_mock_, GetBuildInformation(NotNull()))
.WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError)); .WillOnce(DoAll(SetArgPointee<0>("build info"), Return(true)));
EXPECT_CALL(*strict_mock_, APIVersion(_)) EXPECT_CALL(*strict_mock_, APIVersion(_))
.WillOnce(Return(wvcas::CasStatusCode::kNoError)); .WillOnce(Return(wvcas::CasStatusCode::kNoError));
EXPECT_CALL(*strict_mock_, GenerateNonce(_)) EXPECT_CALL(*strict_mock_, GenerateNonce(_))
.WillOnce(Return(wvcas::CasStatusCode::kNoError)); .WillOnce(Return(wvcas::CasStatusCode::kNoError));
EXPECT_CALL(*strict_mock_, EXPECT_CALL(*strict_mock_,
@@ -322,11 +470,11 @@ TEST_F(CasLicenseTest, GenerateEntitlementRequest) {
.WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature), .WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature),
SetArgReferee<3>(false), SetArgReferee<3>(false),
Return(wvcas::CasStatusCode::kNoError))); Return(wvcas::CasStatusCode::kNoError)));
EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _)); EXPECT_CALL(*strict_mock_, LoadCertificatePrivateKey);
std::string serialized_entitlement_request; std::string serialized_entitlement_request;
wvcas::CasStatus status = cas_license.GenerateEntitlementRequest( wvcas::CasStatus status = cas_license.GenerateEntitlementRequest(
kInitializationData, device_certificate_, wrapped_rsa_key_, kInitializationData, device_certificate_, wrapped_private_key_,
wvcas::LicenseType::kStreaming, &serialized_entitlement_request); wvcas::LicenseType::kStreaming, &serialized_entitlement_request);
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
} }
@@ -338,12 +486,11 @@ TEST_F(CasLicenseTest, HandleEntitlementResponse) {
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
EXPECT_EQ(wvcas::CasStatusCode::kNoError, EXPECT_EQ(wvcas::CasStatusCode::kNoError,
cas_license.initialize(strict_mock_, nullptr).status_code()); cas_license.initialize(strict_mock_, nullptr).status_code());
EXPECT_CALL(*strict_mock_, GetSecurityPatchLevel()).WillOnce(Return(1));
EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _)) EXPECT_CALL(*strict_mock_, GetBuildInformation(NotNull()))
.WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError)); .WillOnce(DoAll(SetArgPointee<0>("build info"), Return(true)));
EXPECT_CALL(*strict_mock_, APIVersion(_)) EXPECT_CALL(*strict_mock_, APIVersion(_))
.WillOnce(Return(wvcas::CasStatusCode::kNoError)); .WillOnce(Return(wvcas::CasStatusCode::kNoError));
EXPECT_CALL(*strict_mock_, GenerateNonce(_)) EXPECT_CALL(*strict_mock_, GenerateNonce(_))
.WillOnce(Return(wvcas::CasStatusCode::kNoError)); .WillOnce(Return(wvcas::CasStatusCode::kNoError));
EXPECT_CALL(*strict_mock_, EXPECT_CALL(*strict_mock_,
@@ -351,11 +498,11 @@ TEST_F(CasLicenseTest, HandleEntitlementResponse) {
.WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature), .WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature),
SetArgReferee<3>(false), SetArgReferee<3>(false),
Return(wvcas::CasStatusCode::kNoError))); Return(wvcas::CasStatusCode::kNoError)));
EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _)); EXPECT_CALL(*strict_mock_, LoadCertificatePrivateKey);
std::string serialized_entitlement_request; std::string serialized_entitlement_request;
wvcas::CasStatus status = cas_license.GenerateEntitlementRequest( wvcas::CasStatus status = cas_license.GenerateEntitlementRequest(
kInitializationData, device_certificate_, wrapped_rsa_key_, kInitializationData, device_certificate_, wrapped_private_key_,
wvcas::LicenseType::kStreaming, &serialized_entitlement_request); wvcas::LicenseType::kStreaming, &serialized_entitlement_request);
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
@@ -592,7 +739,7 @@ TEST_F(CasLicenseTest, RestoreLicense) {
cas_license.initialize(strict_mock_, nullptr).status_code()); cas_license.initialize(strict_mock_, nullptr).status_code());
std::string license_file_data = CreateLicenseFileData(); std::string license_file_data = CreateLicenseFileData();
EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _)); EXPECT_CALL(*strict_mock_, LoadCertificatePrivateKey);
EXPECT_CALL(*strict_mock_, DeriveKeysFromSessionKey(_, _, _, _, _, _)) EXPECT_CALL(*strict_mock_, DeriveKeysFromSessionKey(_, _, _, _, _, _))
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError)); .WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
EXPECT_CALL(*strict_mock_, LoadLicense(_, _, _)) EXPECT_CALL(*strict_mock_, LoadLicense(_, _, _))
@@ -601,9 +748,11 @@ TEST_F(CasLicenseTest, RestoreLicense) {
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError)); .WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_)); EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
EXPECT_CALL(*cas_license.policy_engine_, UpdateLicense(_)); EXPECT_CALL(*cas_license.policy_engine_, UpdateLicense(_));
EXPECT_EQ(wvcas::CasStatusCode::kNoError, EXPECT_CALL(*cas_license.policy_engine_, RestorePlaybackTimes);
cas_license.HandleStoredLicense(wrapped_rsa_key_, license_file_data) EXPECT_EQ(
.status_code()); wvcas::CasStatusCode::kNoError,
cas_license.HandleStoredLicense(wrapped_private_key_, license_file_data)
.status_code());
} }
TEST_F(CasLicenseTest, HandleMultiContentEntitlementResponse) { TEST_F(CasLicenseTest, HandleMultiContentEntitlementResponse) {
@@ -612,8 +761,9 @@ TEST_F(CasLicenseTest, HandleMultiContentEntitlementResponse) {
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _)); EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
EXPECT_EQ(wvcas::CasStatusCode::kNoError, EXPECT_EQ(wvcas::CasStatusCode::kNoError,
cas_license.initialize(strict_mock_, nullptr).status_code()); cas_license.initialize(strict_mock_, nullptr).status_code());
EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _)) EXPECT_CALL(*strict_mock_, GetSecurityPatchLevel()).WillOnce(Return(1));
.WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError)); EXPECT_CALL(*strict_mock_, GetBuildInformation(NotNull()))
.WillOnce(DoAll(SetArgPointee<0>("build info"), Return(true)));
EXPECT_CALL(*strict_mock_, APIVersion(_)) EXPECT_CALL(*strict_mock_, APIVersion(_))
.WillOnce(Return(wvcas::CasStatusCode::kNoError)); .WillOnce(Return(wvcas::CasStatusCode::kNoError));
EXPECT_CALL(*strict_mock_, GenerateNonce(_)) EXPECT_CALL(*strict_mock_, GenerateNonce(_))
@@ -623,11 +773,11 @@ TEST_F(CasLicenseTest, HandleMultiContentEntitlementResponse) {
.WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature), .WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature),
SetArgReferee<3>(false), SetArgReferee<3>(false),
Return(wvcas::CasStatusCode::kNoError))); Return(wvcas::CasStatusCode::kNoError)));
EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _)); EXPECT_CALL(*strict_mock_, LoadCertificatePrivateKey);
std::string serialized_entitlement_request; std::string serialized_entitlement_request;
wvcas::CasStatus status = cas_license.GenerateEntitlementRequest( wvcas::CasStatus status = cas_license.GenerateEntitlementRequest(
kInitializationData, device_certificate_, wrapped_rsa_key_, kInitializationData, device_certificate_, wrapped_private_key_,
wvcas::LicenseType::kStreaming, &serialized_entitlement_request); wvcas::LicenseType::kStreaming, &serialized_entitlement_request);
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code()); EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());

View File

@@ -666,7 +666,8 @@ TEST_F(CryptoSessionTest, PrepareAndSignProvisioningRequest) {
DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS))); DoAll(SetArgPointee<0>(kOemcSessionId), Return(OEMCrypto_SUCCESS)));
EXPECT_EQ(wvcas::CasStatusCode::kNoError, EXPECT_EQ(wvcas::CasStatusCode::kNoError,
crypto_session.initialize().status_code()); crypto_session.initialize().status_code());
EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_GetProvisioningMethod())
.WillRepeatedly(Return(OEMCrypto_BootCertificateChain));
const std::string message = "message"; const std::string message = "message";
std::string core_message; std::string core_message;
std::string signature; std::string signature;
@@ -688,6 +689,10 @@ TEST_F(CryptoSessionTest, PrepareAndSignProvisioningRequest) {
.WillOnce(DoAll( .WillOnce(DoAll(
Invoke(this, &CryptoSessionTest::SetOutputCoreMessageAndSignature), Invoke(this, &CryptoSessionTest::SetOutputCoreMessageAndSignature),
Return(OEMCrypto_SUCCESS))); 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, EXPECT_EQ(wvcas::CasStatusCode::kNoError,
crypto_session crypto_session
.PrepareAndSignProvisioningRequest( .PrepareAndSignProvisioningRequest(
@@ -696,7 +701,7 @@ TEST_F(CryptoSessionTest, PrepareAndSignProvisioningRequest) {
.status_code()); .status_code());
EXPECT_EQ(core_message, std::string(kExpectedCoreMessage)); EXPECT_EQ(core_message, std::string(kExpectedCoreMessage));
EXPECT_EQ(signature, std::string(kExpectedSignature)); EXPECT_EQ(signature, std::string(kExpectedSignature));
EXPECT_FALSE(should_specify_algorithm); EXPECT_TRUE(should_specify_algorithm);
} }
TEST_F(CryptoSessionTest, LoadProvisioning) { TEST_F(CryptoSessionTest, LoadProvisioning) {
@@ -785,7 +790,7 @@ TEST_F(CryptoSessionTest, GetOEMPublicCertificate) {
EXPECT_EQ(cert_size, public_cert_length); EXPECT_EQ(cert_size, public_cert_length);
} }
TEST_F(CryptoSessionTest, LoadDeviceRSAKey) { TEST_F(CryptoSessionTest, LoadCertificatePrivateKey) {
TestCryptoSession<NiceMock<MockedOEMCrypto> > crypto_session( TestCryptoSession<NiceMock<MockedOEMCrypto> > crypto_session(
nice_oemcrypto_interface_); nice_oemcrypto_interface_);
EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_)) EXPECT_CALL(nice_oemcrypto_interface_, OEMCrypto_OpenSession(_))
@@ -795,18 +800,16 @@ TEST_F(CryptoSessionTest, LoadDeviceRSAKey) {
crypto_session.initialize().status_code()); crypto_session.initialize().status_code());
// Test data for use in handling the expected calls. // Test data for use in handling the expected calls.
uint8_t wrapped_rsa_key = 0; std::string key = "key";
size_t wrapped_rsa_key_length = 75; CryptoWrappedKey wrapped_key;
wrapped_key.set_key(key);
EXPECT_CALL( EXPECT_CALL(nice_oemcrypto_interface_,
nice_oemcrypto_interface_, OEMCrypto_LoadDRMPrivateKey(
OEMCrypto_LoadDRMPrivateKey(kOemcSessionId, OEMCrypto_RSA_Private_Key, kOemcSessionId, OEMCrypto_RSA_Private_Key, _, key.size()))
&wrapped_rsa_key, wrapped_rsa_key_length))
.WillOnce(Return(OEMCrypto_SUCCESS)); .WillOnce(Return(OEMCrypto_SUCCESS));
EXPECT_EQ( EXPECT_EQ(crypto_session.LoadCertificatePrivateKey(wrapped_key).status_code(),
wvcas::CasStatusCode::kNoError, wvcas::CasStatusCode::kNoError);
crypto_session.LoadDeviceRSAKey(&wrapped_rsa_key, wrapped_rsa_key_length)
.status_code());
} }
TEST_F(CryptoSessionTest, GenerateRSASignature) { TEST_F(CryptoSessionTest, GenerateRSASignature) {

View File

@@ -9,6 +9,7 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "crypto_session.h" #include "crypto_session.h"
#include "crypto_wrapped_key.h"
class MockCryptoSession : public wvcas::CryptoSession { class MockCryptoSession : public wvcas::CryptoSession {
public: public:
@@ -30,21 +31,19 @@ class MockCryptoSession : public wvcas::CryptoSession {
const uint8_t* enc_key_context, const uint8_t* enc_key_context,
uint32_t enc_key_context_length)); uint32_t enc_key_context_length));
MOCK_METHOD5(PrepareAndSignLicenseRequest, MOCK_METHOD5(PrepareAndSignLicenseRequest,
wvcas::CasStatus(const std::string& message, wvcas::CasStatus(const std::string& message,
std::string* core_message, std::string* core_message,
std::string* signature, bool&, std::string* signature, bool&,
OEMCrypto_SignatureHashAlgorithm&) OEMCrypto_SignatureHashAlgorithm&));
);
MOCK_METHOD3(PrepareAndSignRenewalRequest, MOCK_METHOD3(PrepareAndSignRenewalRequest,
wvcas::CasStatus(const std::string& message, wvcas::CasStatus(const std::string& message,
std::string* core_message, std::string* core_message,
std::string* signature)); std::string* signature));
MOCK_METHOD5(PrepareAndSignProvisioningRequest, MOCK_METHOD5(PrepareAndSignProvisioningRequest,
wvcas::CasStatus(const std::string& message, wvcas::CasStatus(const std::string& message,
std::string* core_message, std::string* core_message,
std::string* signature, bool&, std::string* signature, bool&,
OEMCrypto_SignatureHashAlgorithm&) OEMCrypto_SignatureHashAlgorithm&));
);
MOCK_METHOD4(LoadProvisioning, MOCK_METHOD4(LoadProvisioning,
wvcas::CasStatus(const std::string& signed_message, wvcas::CasStatus(const std::string& signed_message,
const std::string& core_message, const std::string& core_message,
@@ -88,6 +87,18 @@ class MockCryptoSession : public wvcas::CryptoSession {
MOCK_METHOD(wvcas::CasStatus, GetOEMKeyToken, MOCK_METHOD(wvcas::CasStatus, GetOEMKeyToken,
(OEMCrypto_SESSION entitled_key_session_id, (OEMCrypto_SESSION entitled_key_session_id,
std::vector<uint8_t>& token)); std::vector<uint8_t>& 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 #endif // MOCK_CRYPTO_SESSION_H

41
tests/src/mock_file.h Normal file
View File

@@ -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 <gmock/gmock.h>
#include <gtest/gtest.h>
#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<File>, 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<std::string>*),
(override));
};
} // namespace wvutil
#endif // MOCK_ECM_PARSER_H

View File

@@ -11,6 +11,7 @@ static constexpr char kGenericModelName[] = "www";
static constexpr char kProductName[] = "WidevineCasTests"; static constexpr char kProductName[] = "WidevineCasTests";
static constexpr char kKeyArchitectureName[] = "architecture_name"; static constexpr char kKeyArchitectureName[] = "architecture_name";
static constexpr char kKeyDeviceName[] = "device_name"; static constexpr char kKeyDeviceName[] = "device_name";
static constexpr char kBuildInfo[] = "build_info";
static constexpr char kOemcPath[] = "cas_oemc_path.so"; static constexpr char kOemcPath[] = "cas_oemc_path.so";
namespace wvcas { namespace wvcas {
@@ -55,6 +56,14 @@ bool Properties::GetDeviceName(std::string* device_name) {
return true; 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) { bool Properties::GetOEMCryptoPath(std::string* path) {
if (path == nullptr) { if (path == nullptr) {
return false; return false;

View File

@@ -56,14 +56,15 @@ class MockLicense : public wvcas::CasLicense {
~MockLicense() override {} ~MockLicense() override {}
MOCK_CONST_METHOD0(IsExpired, bool()); MOCK_CONST_METHOD0(IsExpired, bool());
MOCK_METHOD5(GenerateEntitlementRequest, MOCK_METHOD(wvcas::CasStatus, GenerateEntitlementRequest,
wvcas::CasStatus(const std::string& init_data, (const std::string& init_data,
const std::string& device_certificate, const std::string& device_certificate,
const std::string& wrapped_rsa_key, const CryptoWrappedKey& private_key, LicenseType license_type,
wvcas::LicenseType license_type, std::string* signed_license_request),
std::string* signed_license_request)); (override));
MOCK_METHOD(wvcas::CasStatus, HandleStoredLicense, MOCK_METHOD(wvcas::CasStatus, HandleStoredLicense,
(const std::string&, const std::string&), (override)); (const CryptoWrappedKey& wrapped_private_key, const std::string&),
(override));
MOCK_METHOD2(GenerateEntitlementRenewalRequest, MOCK_METHOD2(GenerateEntitlementRenewalRequest,
wvcas::CasStatus(const std::string& device_certificate, wvcas::CasStatus(const std::string& device_certificate,
std::string* signed_renewal_request)); std::string* signed_renewal_request));

View File

@@ -31,6 +31,9 @@ class Properties {
// Sets the |device_name| field value to be populated in and EMM license // Sets the |device_name| field value to be populated in and EMM license
// request. Returns false if unable to set the value. // request. Returns false if unable to set the value.
static bool GetDeviceName(std::string* device_name); 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, // Returns a path to CAS oemcrypto library, either default,
// or overridden through system property. // or overridden through system property.
// Returned path could be either absolute or relative. // Returned path could be either absolute or relative.

View File

@@ -7,7 +7,8 @@
#ifndef WVCDM_UTIL_FILE_STORE_H_ #ifndef WVCDM_UTIL_FILE_STORE_H_
#define WVCDM_UTIL_FILE_STORE_H_ #define WVCDM_UTIL_FILE_STORE_H_
#include <cstdint> #include <stddef.h>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -18,16 +19,35 @@
namespace wvutil { namespace wvutil {
// Fixed filename for ATSC DRM certificate pre-installed
// on ATSC devices for ATSC licenses.
static const std::string kAtscCertificateFileName = "atsccert.bin"; 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"; 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 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 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 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 { class File {
public: public:
File() {} File() {}
@@ -35,35 +55,70 @@ class File {
virtual ssize_t Read(char* buffer, size_t bytes) = 0; virtual ssize_t Read(char* buffer, size_t bytes) = 0;
virtual ssize_t Write(const 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); CORE_DISALLOW_COPY_AND_ASSIGN(File);
}; };
// File system base class. The implementation is platform dependent.
class FileSystem { class FileSystem {
public: public:
FileSystem(); FileSystem();
FileSystem(const std::string& origin, void* extra_data); FileSystem(const std::string& origin, void* extra_data);
virtual ~FileSystem(); virtual ~FileSystem();
// Concreate implementation of FileSystem.
// Depending on the platform, this may be vendor or Widevine implemented.
class Impl; class Impl;
// defines as bit flag // Flags for calls to Open.
enum OpenFlags { static constexpr int kNoFlags = 0;
kNoFlags = 0, // Create file if does not already exist, open file if it does exist.
kCreate = 1, static constexpr int kCreate = (1 << 0);
kReadOnly = 2, // defaults to read and write access // Open file as read-only; typically should not be used with kCreate.
kTruncate = 4 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<File> Open(const std::string& file_path, int flags); virtual std::unique_ptr<File> Open(const std::string& file_path, int flags);
virtual bool Exists(const std::string& file_path); // Checks if the |path| exists. The |path| may be a file or directory.
virtual bool Exists(const std::string& file_path, int* errno_value); // Return true if an entry in the file system exists; false otherwise.
virtual bool Remove(const std::string& file_path); 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); virtual ssize_t FileSize(const std::string& file_path);
// Return the filenames stored at dir_path. // Return the entries stored at |dir_path| (includes both files
// dir_path will be stripped from the returned names. // 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, virtual bool List(const std::string& dir_path,
std::vector<std::string>* names); std::vector<std::string>* names);

View File

@@ -13,4 +13,12 @@
# endif # endif
#endif #endif
#ifndef WEAK
# if defined(__GNUC__) || defined(__clang__)
# define WEAK __attribute__((weak))
# else
# define WEAK
# endif
#endif
#endif // WVCDM_UTIL_WV_ATTRIBUTES_H_ #endif // WVCDM_UTIL_WV_ATTRIBUTES_H_

View File

@@ -71,6 +71,14 @@ bool Properties::GetProductName(std::string* product_name) {
return GetAndroidProperty("ro.product.name", 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) { bool Properties::GetOEMCryptoPath(std::string* path) {
if (path == nullptr) { if (path == nullptr) {
LOGW("Properties::GetOEMCryptoPath: Invalid parameter"); LOGW("Properties::GetOEMCryptoPath: Invalid parameter");

View File

@@ -32,12 +32,12 @@ const char kBase64SafeCodes[] =
// Decodes a single Base64 encoded character into its 6-bit value. // Decodes a single Base64 encoded character into its 6-bit value.
// The provided |codes| must be a Base64 character map. // 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); const char* c_in_codes = strchr(codes, c);
if (c_in_codes == nullptr) return -1; if (c_in_codes == nullptr) return -1;
const uintptr_t c_in_codes_int = reinterpret_cast<uintptr_t>(c_in_codes); const uintptr_t c_in_codes_int = reinterpret_cast<uintptr_t>(c_in_codes);
const uintptr_t codes_int = reinterpret_cast<uintptr_t>(codes); const uintptr_t codes_int = reinterpret_cast<uintptr_t>(codes);
return static_cast<int>(c_in_codes_int - codes_int); return static_cast<int32_t>(c_in_codes_int - codes_int);
} }
bool DecodeHexChar(char ch, uint8_t* digit) { bool DecodeHexChar(char ch, uint8_t* digit) {
@@ -124,7 +124,7 @@ std::vector<uint8_t> Base64DecodeInternal(const char* encoded, size_t length,
break; break;
} }
const int decoded = DecodeBase64Char(encoded[i], codes); const int32_t decoded = DecodeBase64Char(encoded[i], codes);
if (decoded < 0) { if (decoded < 0) {
LOGE("base64Decode failed"); LOGE("base64Decode failed");
return std::vector<uint8_t>(); return std::vector<uint8_t>();
@@ -167,8 +167,8 @@ std::vector<uint8_t> a2b_hex(const std::string& byte) {
} }
for (size_t i = 0; i < count / 2; ++i) { for (size_t i = 0; i < count / 2; ++i) {
unsigned char msb = 0; // most significant 4 bits uint8_t msb = 0; // most significant 4 bits
unsigned char lsb = 0; // least significant 4 bits uint8_t lsb = 0; // least significant 4 bits
if (!DecodeHexChar(byte[i * 2], &msb) || if (!DecodeHexChar(byte[i * 2], &msb) ||
!DecodeHexChar(byte[i * 2 + 1], &lsb)) { !DecodeHexChar(byte[i * 2 + 1], &lsb)) {
LOGE("Invalid hex value %c%c at index %zu", byte[i * 2], byte[i * 2 + 1], 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) { 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; if (size > kMaxSafeSize) size = kMaxSafeSize;
return UnlimitedHexEncode(in_buffer, size); return UnlimitedHexEncode(in_buffer, size);
} }
@@ -229,7 +229,7 @@ std::string UnlimitedHexEncode(const uint8_t* in_buffer, size_t size) {
if (size == 0) return ""; if (size == 0) return "";
// Each input byte creates two output hex characters. // Each input byte creates two output hex characters.
std::string out_buffer(size * 2, '\0'); 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]; char byte = in_buffer[i];
out_buffer[(i << 1)] = kHexChars[(byte >> 4) & 0xf]; out_buffer[(i << 1)] = kHexChars[(byte >> 4) & 0xf];
out_buffer[(i << 1) + 1] = kHexChars[byte & 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 // Encode unsigned integer into a big endian formatted string
std::string EncodeUint32(unsigned int u) { std::string EncodeUint32(uint32_t u) {
std::string s; std::string s;
s.push_back((u >> 24) & 0xFF); s.push_back((u >> 24) & 0xFF);
s.push_back((u >> 16) & 0xFF); s.push_back((u >> 16) & 0xFF);