Add Provisioning 4 support
Widevine provisioning 4 support is added in this patch.
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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: [
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
50
plugin/include/crypto_wrapped_key.h
Normal file
50
plugin/include/crypto_wrapped_key.h
Normal 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_
|
||||||
35
plugin/include/file_util.h
Normal file
35
plugin/include/file_util.h
Normal 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_
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
86
plugin/include/privacy_crypto.h
Normal file
86
plugin/include/privacy_crypto.h
Normal 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_
|
||||||
64
plugin/include/service_certificate.h
Normal file
64
plugin/include/service_certificate.h
Normal 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_
|
||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
270
plugin/src/file_util.cpp
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
489
plugin/src/privacy_crypto_boringssl.cpp
Normal file
489
plugin/src/privacy_crypto_boringssl.cpp
Normal 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
|
||||||
284
plugin/src/service_certificate.cpp
Normal file
284
plugin/src/service_certificate.cpp
Normal 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
|
||||||
@@ -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());
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
41
tests/src/mock_file.h
Normal 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
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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_
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user