Add Provisioning 4 support

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

View File

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

View File

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

View File

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

View File

@@ -24,10 +24,10 @@ enum class LicenseType {
typedef enum {
ProvisioningError = 0, // Device cannot be provisioned.
DrmCertificate = 1, // Device has baked in DRM certificate
// (level 3 only)
DrmCertificate = 1, // Device has baked in DRM certificate (level 3 only)
Keybox = 2, // Device has factory installed unique keybox.
OEMCertificate = 3 // Device has factory installed OEM certificate.
OEMCertificate = 3, // Device has factory installed OEM certificate.
BootCertificateChain = 4, // Provisioning 4.0
} CasProvisioningMethod;
enum class CryptoMode {

View File

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

View File

@@ -0,0 +1,50 @@
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#ifndef WIDEVINE_CAS_CRYPTO_WRAPPED_KEY_H_
#define WIDEVINE_CAS_CRYPTO_WRAPPED_KEY_H_
#include <string>
namespace wvcas {
// Represents OEMCrypto's wrapped private DRM key. As of v16, it is
// possible for OEMCrypto to support ECC-based DRM certificates. The
// format of the wrapped key is vendor specific; however, the API
// requires that the CDM track whether the wrapped key is RSA or ECC.
class CryptoWrappedKey {
public:
enum Type : int32_t { kUninitialized = 0, kRsa = 1, kEcc = 2 };
CryptoWrappedKey() = default;
CryptoWrappedKey(Type type, const std::string& key)
: type_(type), key_(key) {}
Type type() const { return type_; }
void set_type(Type type) { type_ = type; }
const std::string& key() const { return key_; }
// Mutable reference getter for passing to OMECrypto.
std::string& key() { return key_; }
void set_key(const std::string& key) { key_ = key; }
void Clear() {
type_ = kUninitialized;
key_.clear();
}
// A valid key must have a known key type and have key data.
bool IsValid() const { return type_ != kUninitialized && !key_.empty(); }
// Equality operator is for testing only. Real keys may have
// different meta data but the same logical key.
bool IsEqualTo(const CryptoWrappedKey& other) const {
return type_ == other.type_ && key_ == other.key_;
}
private:
// DRM key type of the wrapped key. For wrapped keys which the type
// of key is unknown, assume it to be RSA.
Type type_ = kUninitialized;
// Vendor-specific wrapped DRM key.
std::string key_;
}; // class CryptoWrappedKey
} // namespace wvcas
#endif // WIDEVINE_CAS_CRYPTO_WRAPPED_KEY_H_

View File

@@ -0,0 +1,35 @@
// Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#ifndef FILE_UTIL_H_
#define FILE_UTIL_H_
#include <string>
#include "cas_status.h"
#include "crypto_wrapped_key.h"
#include "device_files.pb.h"
#include "file_store.h"
namespace wvcas {
bool ReadFileFromStorage(wvutil::FileSystem& file_system,
const std::string& filename, std::string* file_data);
bool RemoveFile(wvutil::FileSystem& file_system, const std::string& filename);
bool StoreFile(wvutil::FileSystem& file_system, const std::string& filename,
const std::string& file_data);
bool StoreFileWithHash(wvutil::FileSystem& file_system, const std::string& name,
const std::string& serialized_file);
bool StoreOemCertificate(wvutil::FileSystem& file_system,
const std::string& certificate,
const CryptoWrappedKey& private_key);
bool StoreCertificate(wvutil::FileSystem& file_system,
const std::string& certificate,
const CryptoWrappedKey& private_key);
CasStatus RetrieveHashedFile(const std::string& certificate,
video_widevine_client::sdk::File& file);
bool RetrieveOemCertificate(wvutil::FileSystem& file_system,
std::string& certificate,
CryptoWrappedKey* wrapped_private_key);
} // namespace wvcas
#endif // FILE_UTIL_H_

View File

@@ -21,7 +21,7 @@ struct InputStreamParams {
size_t data_length;
bool is_encrypted;
InputStreamParams(){};
InputStreamParams() {};
InputStreamParams(const uint8_t* data_addr, size_t data_length,
bool is_encrypted)
: data_addr(data_addr),
@@ -124,6 +124,20 @@ class OEMCryptoInterface {
virtual OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm(
OEMCrypto_SESSION session,
OEMCrypto_SignatureHashAlgorithm* algorithm) const;
virtual OEMCryptoResult OEMCrypto_GetBootCertificateChain(
uint8_t* bcc, size_t* bcc_length, uint8_t* additional_signature,
size_t* additional_signature_length);
virtual OEMCryptoResult OEMCrypto_GenerateCertificateKeyPair(
OEMCrypto_SESSION session, uint8_t* public_key, size_t* public_key_length,
uint8_t* public_key_signature, size_t* public_key_signature_length,
uint8_t* wrapped_private_key, size_t* wrapped_private_key_length,
OEMCrypto_PrivateKeyType* key_type);
virtual OEMCryptoResult OEMCrypto_InstallOemPrivateKey(
OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type,
const uint8_t* wrapped_private_key, size_t wrapped_private_key_length);
virtual uint8_t OEMCrypto_Security_Patch_Level();
virtual OEMCryptoResult OEMCrypto_BuildInformation(char* buffer,
size_t* buffer_length);
OEMCryptoInterface(const OEMCryptoInterface&) = delete;
OEMCryptoInterface& operator=(const OEMCryptoInterface&) = delete;

View File

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

View File

@@ -0,0 +1,86 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
//
// Description:
// Declaration of classes representing AES and RSA public keys used
// for signature verification and encryption.
//
// AES encryption details:
// Algorithm: AES-CBC
//
// RSA signature details:
// Algorithm: RSASSA-PSS
// Hash algorithm: SHA1
// Mask generation function: mgf1SHA1
// Salt length: 20 bytes
// Trailer field: 0xbc
//
// RSA encryption details:
// Algorithm: RSA-OAEP
// Mask generation function: mgf1SHA1
// Label (encoding paramter): empty string
//
#ifndef WIDEVINE_CAS_PRIVACY_CRYPTO_H_
#define WIDEVINE_CAS_PRIVACY_CRYPTO_H_
#include <cstdint>
#include <string>
namespace wvcas {
class AesCbcKey {
public:
AesCbcKey();
~AesCbcKey();
bool Init(const std::string& key);
bool Encrypt(const std::string& in, const std::string& iv, std::string* out,
bool has_padding = true);
bool Decrypt(const std::string& in, const std::string& iv, std::string* out,
bool has_padding = true);
private:
std::string key_;
}; // class AesCbcKey
class RsaPublicKey {
public:
RsaPublicKey();
~RsaPublicKey();
// Initializes an RsaPublicKey object using a DER encoded PKCS#1 RSAPublicKey
bool Init(const std::string& serialized_key);
// Encrypt a message using RSA-OAEP. Caller retains ownership of all
// parameters. Returns true if successful, false otherwise.
bool Encrypt(const std::string& plaintext, std::string* ciphertext);
// Verify RSASSA-PSS signature. Caller retains ownership of all parameters.
// Returns true if validation succeeds, false otherwise.
bool VerifySignature(const std::string& message,
const std::string& signature);
private:
std::string serialized_key_;
}; // class RsaPublicKey
/**
* Extracts an integer value from the extensions in a certificate.
* @param cert A PKCS7 encoded X.509 certificate chain.
* @param extension_oid The ID of the extension to get.
* @param cert_index The zero-based index of the certificate in the chain to
* fetch from.
* @param value [OUT] Will contain the extracted value.
* @return True on success, false on error.
*/
bool ExtractExtensionValueFromCertificate(const std::string& cert,
const std::string& extension_oid,
size_t cert_index, uint32_t* value);
std::string Md5Hash(const std::string& data);
std::string Sha1Hash(const std::string& data);
std::string Sha256Hash(const std::string& data);
std::string Sha512Hash(const std::string& data);
} // namespace wvcas
#endif // WIDEVINE_CAS_PRIVACY_CRYPTO_H_

View File

@@ -0,0 +1,64 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#ifndef WIDEVINE_CAS_SERVICE_CERTIFICATE_H_
#define WIDEVINE_CAS_SERVICE_CERTIFICATE_H_
#include <memory>
#include <string>
#include "cas_status.h"
#include "license_protocol.pb.h"
#include "privacy_crypto.h"
namespace wvcas {
// Service Certificates are used to encrypt the ClientIdentification message
// that is part of Device Provisioning, License, Renewal, and Release requests.
class ServiceCertificate {
public:
ServiceCertificate() = default;
virtual ~ServiceCertificate() {}
// Set up a new service certificate.
// Accept a serialized video_widevine::SignedDrmDeviceCertificate message.
virtual CasStatus Init(const std::string& signed_certificate);
bool HasSignedCertificate() const { return !signed_certificate_.empty(); }
const std::string& signed_certificate() const { return signed_certificate_; }
const std::string& provider_id() const { return provider_id_; }
// Encrypt the ClientIdentification message for a provisioning or
// licensing request. Encryption is performed using the current
// service certificate. Return a failure if the service certificate is
// not present, not valid, or if some other error occurs.
// The routine should not be called if privacy mode is off or if the
// certificate is empty.
CasStatus EncryptClientId(
const video_widevine::ClientIdentification* clear_client_id,
video_widevine::EncryptedClientIdentification* encrypted_client_id) const;
private:
// Encrypt data using RSA with OAEP padding.
// |plaintext| is the data to be encrypted. |ciphertext| is a pointer to a
// string to contain the decrypted data on return, and may not be null.
// returns NO_ERROR if successful or an appropriate error code otherwise.
virtual CasStatus EncryptRsaOaep(const std::string& plaintext,
std::string* ciphertext) const;
// Proto serialized SignedDrmCertificate.
// Verified by Init() to be valid.
std::string signed_certificate_;
// Certificate serial number.
std::string serial_number_;
// Provider ID, extracted from certificate message.
std::string provider_id_;
// Public key.
std::unique_ptr<RsaPublicKey> public_key_;
}; // class ServiceCertificate
} // namespace wvcas
#endif // WIDEVINE_CAS_SERVICE_CERTIFICATE_H_

View File

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

View File

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

View File

@@ -15,32 +15,39 @@
#include "cas_properties.h"
#include "cas_util.h"
#include "crypto_session.h"
#include "crypto_wrapped_key.h"
#include "device_files.pb.h"
#include "file_util.h"
#include "license_protocol.pb.h"
#include "log.h"
#include "service_certificate.h"
#include "string_conversions.h"
using video_widevine_client::sdk::DeviceCertificate;
using video_widevine_client::sdk::File;
using video_widevine_client::sdk::HashedFile;
using wvutil::Base64SafeDecode;
using wvutil::Base64SafeEncodeNoPad;
using video_widevine::CASDrmLicenseRequest;
using video_widevine::ClientIdentification;
using video_widevine::ClientIdentification_ClientCapabilities;
using video_widevine::
ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_2048;
ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_2048;
using video_widevine::
ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_3072;
ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_3072;
using video_widevine::ClientIdentification_NameValue;
using video_widevine::DrmCertificate;
using video_widevine::HashAlgorithmProto;
using video_widevine::License;
using video_widevine::License_KeyContainer;
using video_widevine::LicenseRequest;
using video_widevine::ProvisioningRequest;
using video_widevine::ProvisioningResponse;
using video_widevine::PublicKeyToCertify;
using video_widevine::SignedDrmCertificate;
using video_widevine::SignedMessage;
using video_widevine::SignedProvisioningMessage;
using video_widevine_client::sdk::DeviceCertificate;
using video_widevine_client::sdk::File;
using video_widevine_client::sdk::HashedFile;
using video_widevine_client::sdk::OemCertificate;
using wvutil::Base64SafeDecode;
using wvutil::Base64SafeEncodeNoPad;
static constexpr size_t kMacKeySizeBytes = 32;
static constexpr size_t kMacKeyPaddingSizeBytes = 16;
@@ -48,6 +55,7 @@ static constexpr size_t kIvSizeBytes = 16;
static constexpr size_t kCertificateDataSizeBytes = 4 * 1024;
static constexpr uint32_t kRsaSignatureSizeBytes = 256;
static constexpr size_t kKeyboxTokenSizeBytes = 72;
constexpr uint32_t OEM_CRYPTO_API_VERSION_SUPPORTS_PROV40_CORE_MESSAGE = 18;
namespace wvcas {
@@ -57,10 +65,43 @@ namespace {
constexpr char kKeyCompanyName[] = "company_name";
constexpr char kKeyModelName[] = "model_name";
constexpr char kKeyModelYear[] = "model_year";
constexpr char kKeyArchitectureName[] = "architecture_name";
constexpr char kKeyDeviceName[] = "device_name";
constexpr char kKeyProductName[] = "product_name";
constexpr char kKeyBuildInfo[] = "build_info";
constexpr char kKeyWvCasVersion[] = "widevine_cdm_version";
constexpr char kKeyOemCryptoSecurityPatchLevel[] =
"oem_crypto_security_patch_level";
constexpr char kKeyOemCryptoBuildInformation[] = "oem_crypto_build_information";
constexpr char kBasePathPrefix[] = "/data/vendor/mediacas/IDM/widevine/";
constexpr char kOemCertificateFileName[] = "oemcert.bin";
constexpr char kOemCertificateFilePath[] =
"/data/vendor/mediacas/IDM/widevine/oemcert.bin";
const char kCpProductionServiceCertificate[] =
"0ab9020803121051434fe2a44c763bcc2c826a2d6ef9a718f7d793d005228e02"
"3082010a02820101009e27088659dbd9126bc6ed594caf652b0eaab82abb9862"
"ada1ee6d2cb5247e94b28973fef5a3e11b57d0b0872c930f351b5694354a8c77"
"ed4ee69834d2630372b5331c5710f38bdbb1ec3024cfadb2a8ac94d977d391b7"
"d87c20c5c046e9801a9bffaf49a36a9ee6c5163eff5cdb63bfc750cf4a218618"
"984e485e23a10f08587ec5d990e9ab0de71460dfc334925f3fb9b55761c61e28"
"8398c387a0925b6e4dcaa1b36228d9feff7e789ba6e5ef6cf3d97e6ae05525db"
"38f826e829e9b8764c9e2c44530efe6943df4e048c3c5900ca2042c5235dc80d"
"443789e734bf8e59a55804030061ed48e7d139b521fbf35524b3000b3e2f6de0"
"001f5eeb99e9ec635f02030100013a0c7769646576696e652e636f6d12800332"
"2c2f3fedc47f8b7ba88a135a355466e378ed56a6fc29ce21f0cafc7fb253b073"
"c55bed253d8650735417aad02afaefbe8d5687902b56a164490d83d590947515"
"68860e7200994d322b5de07f82ef98204348a6c2c9619092340eb87df26f63bf"
"56c191dc069b80119eb3060d771afaaeb2d30b9da399ef8a41d16f45fd121e09"
"a0c5144da8f8eb46652c727225537ad65e2a6a55799909bbfb5f45b5775a1d1e"
"ac4e06116c57adfa9ce0672f19b70b876f88e8b9fbc4f96ccc500c676cfb173c"
"b6f52601573e2e45af1d9d2a17ef1487348c05cfc6d638ec2cae3fadb655e943"
"1330a75d2ceeaa54803e371425111e20248b334a3a50c8eca683c448b8ac402c"
"76e6f76e2751fbefb669f05703cec8c64cf7a62908d5fb870375eb0cc96c508e"
"26e0c050f3fd3ebe68cef9903ef6405b25fc6e31f93559fcff05657662b3653a"
"8598ed5751b38694419242a875d9e00d5a5832933024b934859ec8be78adccbb"
"1ec7127ae9afeef9c5cd2e15bd3048e8ce652f7d8c5d595a0323238c598a28";
// TODO(jfore): These variables are currently unused and are flagged as build
// errors in android. These values will be used in a future cl.
@@ -218,6 +259,29 @@ bool OecAlgorithmToProtoAlgorithm(
return false;
}
// Retrieves |stored_oem_cert| from |file_handle|, and load the OEM private key
// to |crypto_session|. Returns true if all operations are successful.
bool RetrieveOemCertificateAndLoadPrivateKey(wvutil::FileSystem& file_system,
CryptoSession& crypto_session,
std::string& stored_oem_cert) {
stored_oem_cert.clear();
CryptoWrappedKey wrapped_private_key;
if (!RetrieveOemCertificate(file_system, stored_oem_cert,
&wrapped_private_key)) {
LOGE("An invalid stored OEM certificated is retrieved");
stored_oem_cert.clear();
return false;
}
if (crypto_session.LoadOemCertificatePrivateKey(wrapped_private_key)
.status_code() != CasStatusCode::kNoError) {
LOGE("Can not load the OEM private key");
stored_oem_cert.clear();
return false;
}
return true;
}
} // namespace
CasStatus CasLicense::initialize(std::shared_ptr<CryptoSession> crypto_session,
@@ -229,11 +293,18 @@ CasStatus CasLicense::initialize(std::shared_ptr<CryptoSession> crypto_session,
policy_engine_->initialize(crypto_session, this);
crypto_session_ = crypto_session;
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;
}
CasStatus CasLicense::GenerateDeviceProvisioningRequest(
std::string* serialized_provisioning_request) const {
wvutil::FileSystem& file_system,
std::string* serialized_provisioning_request) {
if (!crypto_session_) {
return CasStatus(CasStatusCode::kCasLicenseError, "no crypto session");
}
@@ -247,6 +318,9 @@ CasStatus CasLicense::GenerateDeviceProvisioningRequest(
case Keybox:
return GenerateDeviceProvisioningRequestWithKeybox(
serialized_provisioning_request);
case BootCertificateChain:
return GetProvisioning40RequestInternal(file_system,
serialized_provisioning_request);
default:
return CasStatus(CasStatusCode::kCasLicenseError,
"unknown provisioning method");
@@ -256,10 +330,11 @@ CasStatus CasLicense::GenerateDeviceProvisioningRequest(
}
CasStatus CasLicense::HandleDeviceProvisioningResponse(
wvutil::FileSystem* file_system,
const std::string& signed_provisioning_response,
std::string* device_certificate, std::string* wrapped_rsa_key,
std::string* device_file) const {
if (nullptr == device_certificate || nullptr == wrapped_rsa_key) {
std::string* device_certificate,
CryptoWrappedKey* wrapped_private_key) const {
if (device_certificate == nullptr || wrapped_private_key == nullptr) {
return CasStatus(CasStatusCode::kCasLicenseError,
"missing device credentials");
}
@@ -278,6 +353,13 @@ CasStatus CasLicense::HandleDeviceProvisioningResponse(
return CasStatus(CasStatusCode::kIndividualizationError,
"unable to parse response");
}
if (signed_response.protocol_version() ==
SignedProvisioningMessage::PROVISIONING_40) {
return HandleProvisioning40Response(
file_system, signed_response, device_certificate, wrapped_private_key);
}
if (!signed_response.has_signature()) {
return CasStatus(CasStatusCode::kIndividualizationError,
"signature not found");
@@ -303,37 +385,175 @@ CasStatus CasLicense::HandleDeviceProvisioningResponse(
"unable to parse signed message");
}
wrapped_private_key->Clear();
CasStatus status = crypto_session_->LoadProvisioning(
signed_message, core_message, signature, wrapped_rsa_key);
signed_message, core_message, signature, &wrapped_private_key->key());
if (status.status_code() != CasStatusCode::kNoError) {
return CasStatus(CasStatusCode::kIndividualizationError,
("rewrapCertificate fails"));
}
if (nullptr != device_file) {
File file;
file.set_type(File::DEVICE_CERTIFICATE);
file.mutable_device_certificate()->set_certificate(
provisioning_response.device_certificate());
file.mutable_device_certificate()->set_wrapped_private_key(
*wrapped_rsa_key);
HashedFile hash_file;
file.SerializeToString(hash_file.mutable_file());
if (!Hash(hash_file.file(), hash_file.mutable_hash())) {
return CasStatus(CasStatusCode::kIndividualizationError,
("generating file hash fails"));
// This is the entire certificate (SignedDrmCertificate).
const std::string& device_cert_data =
provisioning_response.device_certificate();
// Need to parse cert for key type.
SignedDrmCertificate signed_device_cert;
if (!signed_device_cert.ParseFromString(device_cert_data)) {
LOGE("Failed to parse signed DRM certificate");
return CasStatus(CasStatusCode::kIndividualizationError,
"Failed to parse signed DRM certificate");
}
video_widevine::DrmCertificate device_cert;
if (!device_cert.ParseFromString(signed_device_cert.drm_certificate())) {
LOGE("Failed to parse DRM certificate");
return CasStatus(CasStatusCode::kIndividualizationError,
"Failed to parse DRM certificate");
}
if (!device_cert.has_algorithm()) {
LOGW("DRM certificate does not specify algorithm type, assuming RSA");
wrapped_private_key->set_type(CryptoWrappedKey::kRsa);
} else {
switch (device_cert.algorithm()) {
case video_widevine::DrmCertificate::RSA:
wrapped_private_key->set_type(CryptoWrappedKey::kRsa);
break;
case video_widevine::DrmCertificate::ECC_SECP256R1:
case video_widevine::DrmCertificate::ECC_SECP384R1:
case video_widevine::DrmCertificate::ECC_SECP521R1:
wrapped_private_key->set_type(CryptoWrappedKey::kEcc);
break;
default:
LOGE("Unknown DRM certificate algorithm: algorithm = %d",
static_cast<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();
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(
const std::string& init_data, const std::string& device_certificate,
const std::string& wrapped_rsa_key, LicenseType license_type,
const CryptoWrappedKey& private_key, LicenseType license_type,
std::string* signed_license_request) {
if (!signed_license_request) {
return CasStatus(CasStatusCode::kCasLicenseError, "invalid parameters");
@@ -344,69 +564,12 @@ CasStatus CasLicense::GenerateEntitlementRequest(
client_id->set_type(ClientIdentification::DRM_DEVICE_CERTIFICATE);
client_id->set_token(device_certificate);
// Call OEMCrypto_GetOEMPublicCertificate before OEMCrypto_LoadDeviceRSAKey
// so it caches the OEMCrypto Public Key and then throw away result. (Prov3.0)
std::vector<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());
CasStatus status = crypto_session_->LoadCertificatePrivateKey(private_key);
if (!status.ok()) {
return status;
}
std::string value;
ClientIdentification_NameValue* client_info = nullptr;
if (Properties::GetCompanyName(&value)) {
client_info = client_id->add_client_info();
client_info->set_name(kKeyCompanyName);
client_info->set_value(value);
}
if (Properties::GetModelName(&value)) {
client_info = client_id->add_client_info();
client_info->set_name(kKeyModelName);
client_info->set_value(value);
}
if (Properties::GetProductName(&value)) {
client_info = client_id->add_client_info();
client_info->set_name(kKeyProductName);
client_info->set_value(value);
}
if (Properties::GetArchitectureName(&value)) {
client_info = client_id->add_client_info();
client_info->set_name(kKeyArchitectureName);
client_info->set_value(value);
}
if (Properties::GetDeviceName(&value)) {
client_info = client_id->add_client_info();
client_info->set_name(kKeyDeviceName);
client_info->set_value(value);
}
if (Properties::GetWvCasPluginVersion(value)) {
client_info = client_id->add_client_info();
client_info->set_name(kKeyWvCasVersion);
client_info->set_value(value);
}
ClientIdentification_ClientCapabilities* client_capabilities =
client_id->mutable_client_capabilities();
// TODO(jfore):
// client_capabilities->set_session_token(supports_usage_information)? Is this
// for usage tables?
// TODO(jfore): Anti rollback support?
uint32_t api_version = 0;
status = crypto_session_->APIVersion(&api_version);
if (!status.ok()) {
return status;
}
client_capabilities->set_oem_crypto_api_version(api_version);
// TODO(jfore): Handle hdcp capabilities.
FillClientProperties(*client_id);
video_widevine::LicenseRequest_ContentIdentification* content_id =
license_request.mutable_content_id();
@@ -474,8 +637,9 @@ CasStatus CasLicense::GenerateEntitlementRequest(
return CasStatusCode::kNoError;
}
CasStatus CasLicense::HandleStoredLicense(const std::string& wrapped_rsa_key,
const std::string& license_file) {
CasStatus CasLicense::HandleStoredLicense(
const CryptoWrappedKey& wrapped_private_key,
const std::string& license_file) {
HashedFile hash_file;
if (!hash_file.ParseFromString(license_file)) {
return CasStatus(CasStatusCode::kLicenseFileParseError,
@@ -520,10 +684,9 @@ CasStatus CasLicense::HandleStoredLicense(const std::string& wrapped_rsa_key,
"unable to parse license");
}
// Install the rsa provisioning key.
CasStatus status = crypto_session_->LoadDeviceRSAKey(
reinterpret_cast<const uint8_t*>(wrapped_rsa_key.data()),
wrapped_rsa_key.size());
// Install the provisioning key.
CasStatus status =
crypto_session_->LoadCertificatePrivateKey(wrapped_private_key);
if (!status.ok()) {
return status;
}
@@ -555,8 +718,10 @@ CasStatus CasLicense::HandleStoredLicense(const std::string& wrapped_rsa_key,
// Reset the flag so expiration callback is enabled.
is_renewal_in_license_file_ = false;
// Expiration event is expected to be fired if license is shown expired.
OnLicenseExpiration();
return status;
if (!status.ok()) {
OnLicenseExpiration();
return status;
}
}
policy_engine_->RestorePlaybackTimes(license.playback_start_time(),
license.last_playback_time(),
@@ -767,6 +932,155 @@ CasStatus CasLicense::GenerateDeviceProvisioningRequestWithKeybox(
return CasStatusCode::kNoError;
}
CasStatus CasLicense::GetProvisioning40RequestInternal(
wvutil::FileSystem& file_system,
std::string* serialized_provisioning_request) {
ProvisioningRequest provisioning_request;
const std::string oem_cert_path = std::string(kOemCertificateFilePath);
// Determine the current stage by checking if OEM cert exists.
std::string stored_oem_cert;
if (file_system.Exists(oem_cert_path)) {
// This is second stage requesting for DRM cert. We try to use the stored
// OEM cert. In case of error, we just fall back to the first stage
// provisioning (request for an OEM cert).
if (!RetrieveOemCertificateAndLoadPrivateKey(file_system, *crypto_session_,
stored_oem_cert)) {
stored_oem_cert.clear();
LOGD("Deleting the stored OEM certificate due to unsuccessful read");
if (!RemoveFile(file_system, oem_cert_path)) {
// This should not happen.
LOGE("Failed to delete the OEM certificate certificate");
}
}
}
CasStatus status = FillEncryptedClientId(
stored_oem_cert, provisioning_request, *wv_service_cert_);
if (!status.ok()) {
return status;
}
std::string public_key;
std::string public_key_signature;
provisioning_40_wrapped_private_key_.clear();
provisioning_40_key_type_ = CryptoWrappedKey::kUninitialized;
status = crypto_session_->GenerateCertificateKeyPair(
&public_key, &public_key_signature, &provisioning_40_wrapped_private_key_,
&provisioning_40_key_type_);
if (!status.ok()) {
LOGE("Fail to GenerateCertificateKeyPair %s",
status.error_string().c_str());
return status;
}
PublicKeyToCertify* key_to_certify =
provisioning_request.mutable_certificate_public_key();
key_to_certify->set_public_key(public_key);
key_to_certify->set_signature(public_key_signature);
key_to_certify->set_key_type(provisioning_40_key_type_ ==
CryptoWrappedKey::kRsa
? PublicKeyToCertify::RSA
: PublicKeyToCertify::ECC);
std::string serialized_message;
provisioning_request.SerializeToString(&serialized_message);
SignedProvisioningMessage signed_provisioning_msg;
signed_provisioning_msg.set_message(serialized_message);
signed_provisioning_msg.set_protocol_version(
SignedProvisioningMessage::PROVISIONING_40);
// Core message and request signature are added to the provisioning request
// starting OEMCrypto v18
uint32_t api_version = 0;
const bool core_message_signature_required =
crypto_session_->APIVersion(&api_version).ok() &&
(api_version >= OEM_CRYPTO_API_VERSION_SUPPORTS_PROV40_CORE_MESSAGE);
if (core_message_signature_required) {
std::string core_message;
std::string request_signature;
bool should_specify_algorithm;
OEMCrypto_SignatureHashAlgorithm oec_algorithm = OEMCrypto_SHA1;
status = crypto_session_->PrepareAndSignProvisioningRequest(
serialized_message, &core_message, &request_signature,
should_specify_algorithm, oec_algorithm);
if (!status.ok()) {
LOGE("Failed to prepare provisioning 4.0 request: status = %s",
status.error_string().c_str());
return status;
}
if (core_message.empty()) {
LOGE("Core message is empty");
return CasStatus(CasStatusCode::kProvisioningError,
"Core message is empty");
}
if (request_signature.empty()) {
LOGE("Request signature is empty");
return CasStatus(CasStatusCode::kProvisioningError,
"Request signature is empty");
}
signed_provisioning_msg.set_oemcrypto_core_message(core_message);
signed_provisioning_msg.set_signature(request_signature);
if (should_specify_algorithm) {
HashAlgorithmProto proto_algorithm =
HashAlgorithmProto::HASH_ALGORITHM_UNSPECIFIED;
if (!OecAlgorithmToProtoAlgorithm(oec_algorithm, proto_algorithm)) {
return CasStatus(CasStatusCode::kProvisioningError,
"UNSUPPORTED_SIGNATURE_HASH_ALGORITHM");
}
signed_provisioning_msg.set_hash_algorithm(proto_algorithm);
}
}
std::string serialized_request;
signed_provisioning_msg.SerializeToString(&serialized_request);
// Return request as web-safe base64 string
*serialized_provisioning_request = Base64SafeEncodeNoPad(serialized_request);
return CasStatusCode::kNoError;
}
CasStatus CasLicense::FillEncryptedClientId(
const std::string& client_token, ProvisioningRequest& provisioning_request,
const ServiceCertificate& service_certificate) const {
ClientIdentification client_id;
if (!client_token.empty()) {
// A token has been provided. This can only
// happen in provisioning 4 (second stage) where an OEM cert is provided.
client_id.set_type(
video_widevine::ClientIdentification::OEM_DEVICE_CERTIFICATE);
client_id.set_token(client_token);
} else {
client_id.set_type(
video_widevine::ClientIdentification::BOOT_CERTIFICATE_CHAIN);
std::string token;
std::string additional_token;
CasStatus status =
crypto_session_->GetBootCertificateChain(&token, &additional_token);
if (!status.ok()) {
LOGE("Failed to get provisioning token: %s",
status.error_string().c_str());
return CasStatus(CasStatusCode::kProvisioningError,
"Failed to get provisioning token");
}
client_id.set_token(token);
if (!additional_token.empty()) {
client_id.mutable_device_credentials()->set_token(additional_token);
}
}
FillClientProperties(client_id);
if (!service_certificate.HasSignedCertificate()) {
LOGE("Service certificate not staged");
return CasStatus(CasStatusCode::kProvisioningError,
"Service certificate not staged");
}
// Encrypt client identification
return service_certificate.EncryptClientId(
&client_id, provisioning_request.mutable_encrypted_client_id());
}
// TODO(jfore): Currently not functional, implement me as needed.
CasStatus CasLicense::GenerateDeviceProvisioningRequestWithOEMCert() const {
ProvisioningRequest provisioning_request;
@@ -858,31 +1172,15 @@ CasStatus CasLicense::InstallLicenseRenewal(
CasStatus CasLicense::HandleStoredDrmCert(const std::string& certificate,
std::string* device_certificate,
std::string* wrapped_rsa_key) {
if (nullptr == device_certificate || nullptr == wrapped_rsa_key) {
CryptoWrappedKey* private_key) {
if (nullptr == device_certificate || private_key == nullptr) {
return CasStatus(CasStatusCode::kIndividualizationError,
("missing output parameters"));
}
HashedFile hash_file;
if (!hash_file.ParseFromString(certificate)) {
return CasStatus(CasStatusCode::kIndividualizationError,
("unable to parse the certificate data"));
}
std::string file_hash;
if (!Hash(hash_file.file(), &file_hash)) {
return CasStatus(CasStatusCode::kIndividualizationError,
("generating file hash fails"));
}
if (hash_file.hash() != file_hash) {
return CasStatus(CasStatusCode::kIndividualizationError,
("corrupt certificate file data"));
}
File file;
if (!file.ParseFromString(hash_file.file())) {
return CasStatus(CasStatusCode::kIndividualizationError,
("unable to parse the file data"));
video_widevine_client::sdk::File file;
CasStatus status = RetrieveHashedFile(certificate, file);
if (status.status_code() != CasStatusCode::kNoError) {
return status;
}
if (file.type() != File::DEVICE_CERTIFICATE) {
return CasStatus(CasStatusCode::kIndividualizationError,
@@ -896,7 +1194,30 @@ CasStatus CasLicense::HandleStoredDrmCert(const std::string& certificate,
}
*device_certificate = file.device_certificate().certificate();
*wrapped_rsa_key = file.device_certificate().wrapped_private_key();
private_key->Clear();
private_key->set_key(file.device_certificate().wrapped_private_key());
if (file.device_certificate().has_key_type()) {
const DeviceCertificate::PrivateKeyType key_type =
file.device_certificate().key_type();
switch (key_type) {
case DeviceCertificate::RSA:
private_key->set_type(CryptoWrappedKey::kRsa);
break;
case DeviceCertificate::ECC:
private_key->set_type(CryptoWrappedKey::kEcc);
break;
default:
LOGW("Unknown DRM key type, defaulting to RSA: type = %d", key_type);
private_key->set_type(CryptoWrappedKey::kRsa);
break;
}
} else {
// Possible that device certificate is from V15, in this case, the
// only supported key of at that time was RSA.
LOGD("No key type info, assuming RSA");
private_key->set_type(CryptoWrappedKey::kRsa);
}
return CasStatusCode::kNoError;
}

View File

@@ -10,8 +10,19 @@
#include <sstream>
#include "cas_util.h"
#include "crypto_wrapped_key.h"
#include "log.h"
// Stringify turns macro arguments into static C strings.
// Example: STRINGIFY(this_argument) -> "this_argument"
#define STRINGIFY(PARAM) #PARAM
#define RETURN_IF_NULL(PARAM) \
if ((PARAM) == nullptr) { \
LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \
return CasStatus(CasStatusCode::kCryptoSessionError, "parameter is null"); \
}
namespace wvcas {
namespace {
constexpr uint32_t kExpectedOEMCryptoVersion = 18;
@@ -75,41 +86,46 @@ void FillEntitledContentKeyObjectFromKeyData(
dest->cipher_mode = CipherModeFromKeyData(src.cipher_mode);
}
uint8_t* MutableStringDataPointer(std::string* s) {
if (s == nullptr) return nullptr;
if (s->empty()) return nullptr;
return reinterpret_cast<uint8_t*>(&s->front());
}
} // namespace
wvutil::shared_mutex CryptoLock::static_field_mutex_;
wvutil::shared_mutex CryptoLock::oem_crypto_mutex_;
template <class Func>
auto CryptoLock::WithStaticFieldWriteLock(const char* tag,
Func body) -> decltype(body()) {
auto CryptoLock::WithStaticFieldWriteLock(const char* tag, Func body)
-> decltype(body()) {
LOGV("Static field write lock: %s", tag);
std::unique_lock<wvutil::shared_mutex> auto_lock(static_field_mutex_);
return body();
}
template <class Func>
auto CryptoLock::WithStaticFieldReadLock(const char* tag,
Func body) -> decltype(body()) {
auto CryptoLock::WithStaticFieldReadLock(const char* tag, Func body)
-> decltype(body()) {
LOGV("Static field read lock: %s", tag);
wvutil::shared_lock<wvutil::shared_mutex> auto_lock(static_field_mutex_);
return body();
}
template <class Func>
auto CryptoLock::WithOecWriteLock(const char* tag,
Func body) -> decltype(body()) {
auto CryptoLock::WithOecWriteLock(const char* tag, Func body)
-> decltype(body()) {
LOGV("OEMCrypto write lock: %s", tag);
std::unique_lock<wvutil::shared_mutex> auto_lock(oem_crypto_mutex_);
return body();
}
template <class Func>
auto CryptoLock::WithOecReadLock(const char* tag,
Func body) -> decltype(body()) {
auto CryptoLock::WithOecReadLock(const char* tag, Func body)
-> decltype(body()) {
LOGV("OEMCrypto read lock: %s", tag);
wvutil::shared_lock<wvutil::shared_mutex> auto_lock(oem_crypto_mutex_);
return body();
}
template <class Func>
auto CryptoLock::WithOecSessionLock(const char* tag,
Func body) -> decltype(body()) {
auto CryptoLock::WithOecSessionLock(const char* tag, Func body)
-> decltype(body()) {
LOGV("OEMCrypto Session Lock - %s", tag);
wvutil::shared_lock<wvutil::shared_mutex> oec_auto_lock(oem_crypto_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(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
uint8_t* signature, size_t* signature_length,
@@ -354,6 +384,37 @@ OEMCryptoResult CryptoInterface::OEMCrypto_GetSignatureHashAlgorithm(
});
}
OEMCryptoResult CryptoInterface::OEMCrypto_GetBootCertificateChain(
uint8_t* bcc, size_t* bcc_length, uint8_t* additional_signature,
size_t* additional_signature_length) {
return lock_->WithOecReadLock("GetBootCertificateChain", [&] {
return oemcrypto_interface_->OEMCrypto_GetBootCertificateChain(
bcc, bcc_length, additional_signature, additional_signature_length);
});
}
OEMCryptoResult CryptoInterface::OEMCrypto_GenerateCertificateKeyPair(
OEMCrypto_SESSION session, uint8_t* public_key, size_t* public_key_length,
uint8_t* public_key_signature, size_t* public_key_signature_length,
uint8_t* wrapped_private_key, size_t* wrapped_private_key_length,
OEMCrypto_PrivateKeyType* key_type) {
return lock_->WithOecSessionLock("GenerateCertificateKeyPair", [&] {
return oemcrypto_interface_->OEMCrypto_GenerateCertificateKeyPair(
session, public_key, public_key_length, public_key_signature,
public_key_signature_length, wrapped_private_key,
wrapped_private_key_length, key_type);
});
}
OEMCryptoResult CryptoInterface::OEMCrypto_InstallOemPrivateKey(
OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type,
const uint8_t* wrapped_private_key, size_t wrapped_private_key_length) {
return lock_->WithOecSessionLock("InstallOemPrivateKey", [&] {
return oemcrypto_interface_->OEMCrypto_InstallOemPrivateKey(
session, key_type, wrapped_private_key, wrapped_private_key_length);
});
}
OEMCryptoResult CryptoInterface::create_internal(
OEMCryptoInterface* oemcrypto_interface,
std::unique_ptr<CryptoInterface>* init) {
@@ -462,6 +523,8 @@ CasProvisioningMethod CryptoSession::provisioning_method() {
return OEMCertificate;
case OEMCrypto_DrmCertificate:
return DrmCertificate;
case OEMCrypto_BootCertificateChain:
return BootCertificateChain;
default:
return ProvisioningError;
}
@@ -661,9 +724,36 @@ CasStatus CryptoSession::PrepareAndSignProvisioningRequest(
"Missing core_message or signature.");
}
CasProvisioningMethod prov_method = provisioning_method();
if (prov_method == Keybox) {
// Keybox based provisioning does not need hash algorithm.
should_specify_algorithm = false;
} else if (prov_method == BootCertificateChain) {
should_specify_algorithm = true;
// The key to signing the provisioning 4.0 request for each
// stage has been loaded already when it was generated by OEMCrypto.
} else {
// CAS only supports provisioning 2 and 4.
LOGE("Unknown provisioning method %d", prov_method);
return CasStatus(CasStatusCode::kCryptoSessionError,
"Unknown provisioning method.");
}
OEMCryptoResult sts;
// Keybox based provisioning does not need hash algorithm.
should_specify_algorithm = false;
if (should_specify_algorithm) {
sts = crypto_interface_->OEMCrypto_GetSignatureHashAlgorithm(session_,
&algorithm);
if (sts == OEMCrypto_SUCCESS) {
// Nothing to do.
} else if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
should_specify_algorithm = false;
} else {
std::ostringstream err_string;
err_string << "OEMCrypto_GetSignatureHashAlgorithm returned " << sts;
return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str());
}
}
size_t signature_length = 0;
size_t core_message_length = 0;
*core_message = "";
@@ -768,26 +858,6 @@ CasStatus CryptoSession::GetOEMPublicCertificate(uint8_t* public_cert,
return CasStatus::OkStatus();
}
CasStatus CryptoSession::LoadDeviceRSAKey(const uint8_t* wrapped_rsa_key,
size_t wrapped_rsa_key_length) {
if (!crypto_interface_) {
return CasStatus(CasStatusCode::kCryptoSessionError,
"missing crypto interface");
}
if (!wrapped_rsa_key) {
return CasStatus(CasStatusCode::kCryptoSessionError, "invalid argument");
}
OEMCryptoResult result = crypto_interface_->OEMCrypto_LoadDRMPrivateKey(
session_, OEMCrypto_RSA_Private_Key, wrapped_rsa_key,
wrapped_rsa_key_length);
if (result != OEMCrypto_SUCCESS) {
std::ostringstream err_string;
err_string << "OEMCrypto_LoadDRMPrivateKey returned " << result;
return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str());
}
return CasStatus::OkStatus();
}
CasStatus CryptoSession::GenerateRSASignature(
const uint8_t* message, size_t message_length, uint8_t* signature,
size_t* signature_length, RSA_Padding_Scheme padding_scheme) {
@@ -1080,4 +1150,178 @@ CasStatus CryptoSession::GetOEMKeyToken(
return CasStatus::OkStatus();
}
CasStatus CryptoSession::GetBootCertificateChain(
std::string* bcc, std::string* additional_signature) {
RETURN_IF_NULL(bcc);
RETURN_IF_NULL(additional_signature);
if (!crypto_interface_) {
return CasStatus(CasStatusCode::kCryptoSessionError,
"missing crypto interface");
}
if (provisioning_method() != BootCertificateChain) {
return CasStatus(CasStatusCode::kCryptoSessionError,
"provisioning type is not BCC");
}
size_t bcc_length = 0;
size_t additional_signature_length = 0;
OEMCryptoResult sts = crypto_interface_->OEMCrypto_GetBootCertificateChain(
nullptr, &bcc_length, nullptr, &additional_signature_length);
if (sts == OEMCrypto_ERROR_SHORT_BUFFER) {
bcc->resize(bcc_length);
additional_signature->resize(additional_signature_length);
sts = crypto_interface_->OEMCrypto_GetBootCertificateChain(
MutableStringDataPointer(bcc), &bcc_length,
MutableStringDataPointer(additional_signature),
&additional_signature_length);
}
if (sts != OEMCrypto_SUCCESS) {
std::ostringstream err_string;
err_string << "OEMCrypto_GetBootCertificateChain returned " << sts;
return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str());
}
bcc->resize(bcc_length);
additional_signature->resize(additional_signature_length);
return CasStatus::OkStatus();
}
CasStatus CryptoSession::GenerateCertificateKeyPair(
std::string* public_key, std::string* public_key_signature,
std::string* wrapped_private_key, CryptoWrappedKey::Type* key_type) {
RETURN_IF_NULL(public_key);
RETURN_IF_NULL(public_key_signature);
RETURN_IF_NULL(wrapped_private_key);
RETURN_IF_NULL(key_type);
if (!crypto_interface_) {
return CasStatus(CasStatusCode::kCryptoSessionError,
"missing crypto interface");
}
// Round 1, get the size of all the fields.
size_t public_key_length = 0;
size_t public_key_signature_length = 0;
size_t wrapped_private_key_length = 0;
OEMCrypto_PrivateKeyType oemcrypto_key_type;
OEMCryptoResult status =
crypto_interface_->OEMCrypto_GenerateCertificateKeyPair(
session_, nullptr, &public_key_length, nullptr,
&public_key_signature_length, nullptr, &wrapped_private_key_length,
&oemcrypto_key_type);
if (status != OEMCrypto_ERROR_SHORT_BUFFER) {
std::ostringstream err_string;
err_string << "OEMCrypto_GenerateCertificateKeyPair returned " << status;
return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str());
}
public_key->resize(public_key_length);
public_key_signature->resize(public_key_signature_length);
wrapped_private_key->resize(wrapped_private_key_length);
status = crypto_interface_->OEMCrypto_GenerateCertificateKeyPair(
session_, MutableStringDataPointer(public_key), &public_key_length,
MutableStringDataPointer(public_key_signature),
&public_key_signature_length,
MutableStringDataPointer(wrapped_private_key),
&wrapped_private_key_length, &oemcrypto_key_type);
if (status != OEMCrypto_SUCCESS) {
std::ostringstream err_string;
err_string << "OEMCrypto_GenerateCertificateKeyPair returned " << status;
return CasStatus(CasStatusCode::kCryptoSessionError, err_string.str());
}
public_key->resize(public_key_length);
public_key_signature->resize(public_key_signature_length);
wrapped_private_key->resize(wrapped_private_key_length);
if (oemcrypto_key_type == OEMCrypto_RSA_Private_Key) {
*key_type = CryptoWrappedKey::kRsa;
} else if (oemcrypto_key_type == OEMCrypto_ECC_Private_Key) {
*key_type = CryptoWrappedKey::kEcc;
} else {
LOGE("Unexpected key type returned from GenerateCertificateKeyPair: %d",
static_cast<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

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

@@ -0,0 +1,270 @@
// Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
#include "file_util.h"
#include <openssl/sha.h>
#include "log.h"
#include "crypto_wrapped_key.h"
#include "device_files.pb.h"
#include "file_util.h"
#include "license_protocol.pb.h"
#include "service_certificate.h"
#include "string_conversions.h"
namespace wvcas {
namespace {
using video_widevine_client::sdk::DeviceCertificate;
using video_widevine_client::sdk::File;
using video_widevine_client::sdk::HashedFile;
using video_widevine_client::sdk::OemCertificate;
constexpr char kOemCertificateFilePath[] =
"/data/vendor/mediacas/IDM/widevine/oemcert.bin";
constexpr char kDrmCertificateFilePath[] =
"/data/vendor/mediacas/IDM/widevine/cert.bin";
bool Hash(const std::string& data, std::string* hash) {
if (!hash) return false;
hash->resize(SHA256_DIGEST_LENGTH);
const unsigned char* input =
reinterpret_cast<const unsigned char*>(data.data());
unsigned char* output = reinterpret_cast<unsigned char*>(&(*hash)[0]);
SHA256(input, data.size(), output);
return true;
}
} // namespace
bool ReadFileFromStorage(wvutil::FileSystem& file_system,
const std::string& filename, std::string* file_data) {
if (nullptr == file_data) {
return false;
}
if (!file_system.Exists(filename)) {
return false;
}
size_t filesize = file_system.FileSize(filename);
if (0 == filesize) {
return false;
}
file_data->resize(filesize);
std::unique_ptr<wvutil::File> file =
file_system.Open(filename, wvutil::FileSystem::kReadOnly);
if (nullptr == file) {
return false;
}
size_t bytes_read = file->Read(&(*file_data)[0], file_data->size());
if (bytes_read != filesize) {
return false;
}
return true;
}
bool RemoveFile(wvutil::FileSystem& file_system, const std::string& filename) {
if (!file_system.Exists(filename)) {
return false;
}
if (!file_system.Remove(filename)) {
return false;
}
return true;
}
bool StoreFile(wvutil::FileSystem& file_system, const std::string& filename,
const std::string& file_data) {
std::unique_ptr<wvutil::File> file(file_system.Open(
filename, wvutil::FileSystem::kTruncate | wvutil::FileSystem::kCreate));
if (nullptr == file) {
return false;
}
size_t bytes_written = file->Write(file_data.data(), file_data.size());
if (bytes_written != file_data.size()) {
return false;
}
return true;
}
bool StoreFileWithHash(wvutil::FileSystem& file_system, const std::string& name,
const std::string& serialized_file) {
std::string hash = Sha256Hash(serialized_file);
// Fill in hashed file data
video_widevine_client::sdk::HashedFile hash_file;
hash_file.set_file(serialized_file);
hash_file.set_hash(hash);
std::string serialized_hash_file;
hash_file.SerializeToString(&serialized_hash_file);
return StoreFile(file_system, name, serialized_hash_file);
}
bool StoreOemCertificate(wvutil::FileSystem& file_system,
const std::string& certificate,
const CryptoWrappedKey& private_key) {
if (certificate.empty()) {
LOGE("Missing certificate information");
return false;
}
if (!private_key.IsValid()) {
LOGE("Private key is invalid");
return false;
}
std::string certificate_file_name = std::string(kOemCertificateFilePath);
// Fill in file information
video_widevine_client::sdk::File file;
file.set_type(video_widevine_client::sdk::File::OEM_CERTIFICATE);
file.set_version(video_widevine_client::sdk::File::VERSION_1);
video_widevine_client::sdk::OemCertificate* oem_certificate = file.mutable_oem_certificate();
oem_certificate->set_certificate(certificate);
oem_certificate->set_wrapped_private_key(private_key.key());
switch (private_key.type()) {
case wvcas::CryptoWrappedKey::kRsa:
oem_certificate->set_key_type(video_widevine_client::sdk::OemCertificate::RSA);
break;
case wvcas::CryptoWrappedKey::kEcc:
oem_certificate->set_key_type(video_widevine_client::sdk::OemCertificate::ECC);
break;
case wvcas::CryptoWrappedKey::kUninitialized:
default:
LOGE("Unexpected key type: %d", private_key.type());
return false;
}
std::string serialized_file;
file.SerializeToString(&serialized_file);
return StoreFileWithHash(file_system, certificate_file_name, serialized_file);
}
bool StoreCertificate(wvutil::FileSystem& file_system,
const std::string& certificate,
const CryptoWrappedKey& private_key) {
if (certificate.empty()) {
LOGE("Missing certificate information");
return false;
}
if (!private_key.IsValid()) {
LOGE("Private key is invalid");
return false;
}
// Fill in file information
video_widevine_client::sdk::File file;
file.set_type(video_widevine_client::sdk::File::DEVICE_CERTIFICATE);
file.set_version(video_widevine_client::sdk::File::VERSION_1);
DeviceCertificate* mutable_device_certificate =
file.mutable_device_certificate();
mutable_device_certificate->set_certificate(certificate);
mutable_device_certificate->set_wrapped_private_key(private_key.key());
switch (private_key.type()) {
case CryptoWrappedKey::kRsa:
mutable_device_certificate->set_key_type(DeviceCertificate::RSA);
break;
case CryptoWrappedKey::kEcc:
mutable_device_certificate->set_key_type(DeviceCertificate::ECC);
break;
case CryptoWrappedKey::kUninitialized: // Suppress compiler
// warnings.
default:
LOGE("Unexpected key type: %d", private_key.type());
return false;
}
std::string serialized_file;
file.SerializeToString(&serialized_file);
std::string certificate_file_name = std::string(kDrmCertificateFilePath);
return StoreFileWithHash(file_system, certificate_file_name, serialized_file);
}
CasStatus RetrieveHashedFile(const std::string& certificate, video_widevine_client::sdk::File& file) {
video_widevine_client::sdk::HashedFile hash_file;
if (!hash_file.ParseFromString(certificate)) {
return CasStatus(CasStatusCode::kIndividualizationError,
("unable to parse the certificate data"));
}
std::string file_hash;
if (!Hash(hash_file.file(), &file_hash)) {
return CasStatus(CasStatusCode::kIndividualizationError,
("generating file hash fails"));
}
if (hash_file.hash() != file_hash) {
return CasStatus(CasStatusCode::kIndividualizationError,
("corrupt certificate file data"));
}
if (!file.ParseFromString(hash_file.file())) {
return CasStatus(CasStatusCode::kIndividualizationError,
("unable to parse the file data"));
}
return CasStatusCode::kNoError;
}
bool RetrieveOemCertificate(wvutil::FileSystem& file_system,
std::string& certificate,
CryptoWrappedKey* wrapped_private_key) {
std::string file_data;
if (!ReadFileFromStorage(file_system, std::string(kOemCertificateFilePath),
&file_data)) {
LOGW("Unable to read OEM cert file from storage");
return false;
}
File file;
CasStatus status = RetrieveHashedFile(file_data, file);
if (status.status_code() != CasStatusCode::kNoError) {
LOGW("Unable to retrieve certificate file: %s",
status.error_string().c_str());
return false;
}
if (file.type() != File::OEM_CERTIFICATE) {
LOGE("Certificate file is of incorrect file type: type = %d",
static_cast<int>(file.type()));
return false;
}
if (file.version() != File::VERSION_1) {
LOGE("Certificate file is of incorrect file version: version = %d",
static_cast<int>(file.version()));
return false;
}
if (!file.has_oem_certificate()) {
LOGE("Certificate not present");
return false;
}
const video_widevine_client::sdk::OemCertificate& oem_certificate = file.oem_certificate();
if (oem_certificate.certificate().empty() ||
oem_certificate.wrapped_private_key().empty()) {
LOGE("Empty certificate or private key");
return false;
}
certificate = oem_certificate.certificate();
wrapped_private_key->Clear();
wrapped_private_key->set_key(oem_certificate.wrapped_private_key());
switch (oem_certificate.key_type()) {
case video_widevine_client::sdk::OemCertificate::RSA:
wrapped_private_key->set_type(CryptoWrappedKey::kRsa);
break;
case video_widevine_client::sdk::OemCertificate::ECC:
wrapped_private_key->set_type(CryptoWrappedKey::kEcc);
break;
default:
LOGW("Unknown key type, defaulting to RSA: type = %d",
oem_certificate.key_type());
wrapped_private_key->set_type(CryptoWrappedKey::kRsa);
break;
}
return true;
}
} // namespace wvcas

View File

@@ -115,6 +115,20 @@ class OEMCryptoInterface::Impl {
size_t* key_token_length);
typedef OEMCryptoResult (*GetSignatureHashAlgorithm_t)(
OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm);
typedef OEMCryptoResult (*GetBootCertificateChain_t)(
uint8_t* bcc, size_t* bcc_length, uint8_t* additional_signature,
size_t* additional_signature_length);
typedef OEMCryptoResult (*GenerateCertificateKeyPair_t)(
OEMCrypto_SESSION session, uint8_t* public_key, size_t* public_key_length,
uint8_t* public_key_signature, size_t* public_key_signature_length,
uint8_t* wrapped_private_key, size_t* wrapped_private_key_length,
OEMCrypto_PrivateKeyType* key_type);
typedef OEMCryptoResult (*InstallOemPrivateKey_t)(
OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type,
const uint8_t* wrapped_private_key, size_t wrapped_private_key_length);
typedef uint8_t (*Security_Patch_Level_t)();
typedef OEMCryptoResult (*BuildInformation_t)(char* buffer,
size_t* buffer_length);
Initialize_t Initialize = nullptr;
Terminate_t Terminate = nullptr;
@@ -146,6 +160,11 @@ class OEMCryptoInterface::Impl {
APIVersion_t APIVersion = nullptr;
GetOEMKeyToken_t GetOEMKeyToken = nullptr;
GetSignatureHashAlgorithm_t GetSignatureHashAlgorithm = nullptr;
GetBootCertificateChain_t GetBootCertificateChain = nullptr;
GenerateCertificateKeyPair_t GenerateCertificateKeyPair = nullptr;
InstallOemPrivateKey_t InstallOemPrivateKey = nullptr;
Security_Patch_Level_t Security_Patch_Level = nullptr;
BuildInformation_t BuildInformation = nullptr;
private:
bool initialize() {
@@ -190,6 +209,11 @@ class OEMCryptoInterface::Impl {
LOAD_SYM(ReassociateEntitledKeySession);
TRY_LOAD_SYM(GetOEMKeyToken);
TRY_LOAD_SYM(GetSignatureHashAlgorithm);
LOAD_SYM(GetBootCertificateChain);
LOAD_SYM(GenerateCertificateKeyPair);
LOAD_SYM(InstallOemPrivateKey);
LOAD_SYM(Security_Patch_Level);
LOAD_SYM(BuildInformation);
// Optional methods that may be available.
TRY_LOAD_SYM(LoadTestKeybox);
@@ -404,10 +428,44 @@ OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetSignatureHashAlgorithm(
OEMCrypto_SESSION session,
OEMCrypto_SignatureHashAlgorithm* algorithm) const {
// Optional method. Handle missing method.
if (impl_->GetOEMKeyToken == nullptr) {
if (impl_->GetSignatureHashAlgorithm == nullptr) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
return impl_->GetSignatureHashAlgorithm(session, algorithm);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GetBootCertificateChain(
uint8_t* bcc, size_t* bcc_length, uint8_t* additional_signature,
size_t* additional_signature_length) {
return impl_->GetBootCertificateChain(bcc, bcc_length, additional_signature,
additional_signature_length);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_GenerateCertificateKeyPair(
OEMCrypto_SESSION session, uint8_t* public_key, size_t* public_key_length,
uint8_t* public_key_signature, size_t* public_key_signature_length,
uint8_t* wrapped_private_key, size_t* wrapped_private_key_length,
OEMCrypto_PrivateKeyType* key_type) {
return impl_->GenerateCertificateKeyPair(
session, public_key, public_key_length, public_key_signature,
public_key_signature_length, wrapped_private_key,
wrapped_private_key_length, key_type);
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_InstallOemPrivateKey(
OEMCrypto_SESSION session, OEMCrypto_PrivateKeyType key_type,
const uint8_t* wrapped_private_key, size_t wrapped_private_key_length) {
return impl_->InstallOemPrivateKey(session, key_type, wrapped_private_key,
wrapped_private_key_length);
}
uint8_t OEMCryptoInterface::OEMCrypto_Security_Patch_Level() {
return impl_->Security_Patch_Level();
}
OEMCryptoResult OEMCryptoInterface::OEMCrypto_BuildInformation(
char* buffer, size_t* buffer_length) {
return impl_->BuildInformation(buffer, buffer_length);
}
} // namespace wvcas

View File

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

View File

@@ -0,0 +1,489 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
//
// Description:
// Definition of classes representing RSA public keys used
// for signature verification and encryption and decryption.
//
#include "privacy_crypto.h"
#include <openssl/aes.h>
#include <openssl/asn1.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/md5.h>
#include <openssl/pem.h>
#include <openssl/pkcs7.h>
#include <openssl/rsa.h>
#include <openssl/sha.h>
#include <openssl/x509v3.h>
#include "log.h"
namespace {
const int kPssSaltLength = 20;
const int kRsaPkcs1OaepPaddingLength = 41;
RSA* GetKey(const std::string& serialized_key) {
BIO* bio = BIO_new_mem_buf(const_cast<char*>(serialized_key.data()),
static_cast<int>(serialized_key.size()));
if (bio == nullptr) {
LOGE("BIO_new_mem_buf failed: returned null");
return nullptr;
}
RSA* key = d2i_RSAPublicKey_bio(bio, nullptr);
if (key == nullptr) {
LOGE("RSA key deserialization failure: %s",
ERR_error_string(ERR_get_error(), nullptr));
BIO_free(bio);
return nullptr;
}
BIO_free(bio);
return key;
}
void FreeKey(RSA* key) {
if (key != nullptr) {
RSA_free(key);
}
}
template <typename T, void (*func)(T*)>
class boringssl_ptr {
public:
explicit boringssl_ptr(T* p = nullptr) : ptr_(p) {}
~boringssl_ptr() {
if (ptr_) func(ptr_);
}
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
T* get() const { return ptr_; }
explicit operator bool() const { return ptr_ != nullptr; }
private:
T* ptr_;
}; // class boringssl_ptr
} // namespace
namespace wvcas {
AesCbcKey::AesCbcKey() {}
AesCbcKey::~AesCbcKey() {}
bool AesCbcKey::Init(const std::string& key) {
if (key.size() != AES_BLOCK_SIZE) {
LOGE("Unexpected key size: size = %zu, expected = %d", key.size(),
AES_BLOCK_SIZE);
return false;
}
key_ = key;
return true;
}
bool AesCbcKey::Encrypt(const std::string& in, const std::string& iv,
std::string* out, bool has_padding) {
if (in.empty()) {
LOGE("No cleartext provided");
return false;
}
if (iv.size() != AES_BLOCK_SIZE) {
LOGE("Invalid IV size: %zu", iv.size());
return false;
}
if (out == nullptr) {
LOGE("Ciphertext output parameter |out| not provided");
return false;
}
if (key_.empty()) {
LOGE("AES key not initialized");
return false;
}
EVP_CIPHER_CTX* evp_cipher_ctx = EVP_CIPHER_CTX_new();
if (EVP_EncryptInit(evp_cipher_ctx, EVP_aes_128_cbc(),
reinterpret_cast<const uint8_t*>(&key_[0]),
reinterpret_cast<const uint8_t*>(&iv[0])) == 0) {
LOGE("AES CBC setup failure: %s",
ERR_error_string(ERR_get_error(), nullptr));
EVP_CIPHER_CTX_free(evp_cipher_ctx);
return false;
}
if (!has_padding) EVP_CIPHER_CTX_set_padding(evp_cipher_ctx, 0);
out->resize(in.size() + AES_BLOCK_SIZE);
int out_length = static_cast<int>(out->size());
if (EVP_EncryptUpdate(evp_cipher_ctx, reinterpret_cast<uint8_t*>(&(*out)[0]),
&out_length,
reinterpret_cast<const uint8_t*>(in.data()),
static_cast<int>(in.size())) == 0) {
LOGE("AES CBC encryption failure: %s",
ERR_error_string(ERR_get_error(), nullptr));
EVP_CIPHER_CTX_free(evp_cipher_ctx);
return false;
}
int padding = 0;
if (EVP_EncryptFinal_ex(evp_cipher_ctx,
reinterpret_cast<uint8_t*>(&(*out)[out_length]),
&padding) == 0) {
LOGE("PKCS7 padding failure: %s",
ERR_error_string(ERR_get_error(), nullptr));
EVP_CIPHER_CTX_free(evp_cipher_ctx);
return false;
}
EVP_CIPHER_CTX_free(evp_cipher_ctx);
out->resize(out_length + padding);
return true;
}
bool AesCbcKey::Decrypt(const std::string& in, const std::string& iv,
std::string* out, bool has_padding) {
if (in.empty()) {
LOGE("No ciphertext provided");
return false;
}
if (iv.size() != AES_BLOCK_SIZE) {
LOGE("Invalid IV size: %zu", iv.size());
return false;
}
if (out == nullptr) {
LOGE("Ciphertext output parameter |out| not provided");
return false;
}
if (key_.empty()) {
LOGE("AES key not initialized");
return false;
}
EVP_CIPHER_CTX* evp_cipher_ctx = EVP_CIPHER_CTX_new();
if (EVP_DecryptInit(evp_cipher_ctx, EVP_aes_128_cbc(),
reinterpret_cast<const uint8_t*>(&key_[0]),
reinterpret_cast<const uint8_t*>(&iv[0])) == 0) {
LOGE("AES CBC setup failure: %s",
ERR_error_string(ERR_get_error(), nullptr));
EVP_CIPHER_CTX_free(evp_cipher_ctx);
return false;
}
if (!has_padding) EVP_CIPHER_CTX_set_padding(evp_cipher_ctx, 0);
out->resize(in.size() + AES_BLOCK_SIZE);
int out_length = static_cast<int>(out->size());
if (EVP_DecryptUpdate(evp_cipher_ctx, reinterpret_cast<uint8_t*>(&(*out)[0]),
&out_length,
reinterpret_cast<const uint8_t*>(in.data()),
static_cast<int>(in.size())) == 0) {
LOGE("AES CBC encryption failure: %s",
ERR_error_string(ERR_get_error(), nullptr));
EVP_CIPHER_CTX_free(evp_cipher_ctx);
return false;
}
int padding = 0;
if (EVP_DecryptFinal_ex(evp_cipher_ctx,
reinterpret_cast<uint8_t*>(&(*out)[out_length]),
&padding) == 0) {
LOGE("PKCS7 padding failure: %s",
ERR_error_string(ERR_get_error(), nullptr));
EVP_CIPHER_CTX_free(evp_cipher_ctx);
return false;
}
EVP_CIPHER_CTX_free(evp_cipher_ctx);
out->resize(out_length + padding);
return true;
}
RsaPublicKey::RsaPublicKey() {}
RsaPublicKey::~RsaPublicKey() {}
bool RsaPublicKey::Init(const std::string& serialized_key) {
if (serialized_key.empty()) {
LOGE("No serialized key provided");
return false;
}
serialized_key_ = serialized_key;
return true;
}
bool RsaPublicKey::Encrypt(const std::string& clear_message,
std::string* encrypted_message) {
if (clear_message.empty()) {
LOGE("Message to be encrypted is empty");
return false;
}
if (encrypted_message == nullptr) {
LOGE("Output parameter |encrypted_message| not provided");
return false;
}
if (serialized_key_.empty()) {
LOGE("RSA key not initialized");
return false;
}
RSA* key = GetKey(serialized_key_);
if (key == nullptr) {
// Error already logged by GetKey.
return false;
}
int rsa_size = RSA_size(key);
if (static_cast<int>(clear_message.size()) >
rsa_size - kRsaPkcs1OaepPaddingLength) {
LOGE("Message too large to be encrypted: message_size = %zu, max = %d",
clear_message.size(), rsa_size - kRsaPkcs1OaepPaddingLength);
FreeKey(key);
return false;
}
encrypted_message->assign(rsa_size, 0);
if (RSA_public_encrypt(
static_cast<int>(clear_message.size()),
const_cast<unsigned char*>(
reinterpret_cast<const unsigned char*>(clear_message.data())),
reinterpret_cast<unsigned char*>(&(*encrypted_message)[0]), key,
RSA_PKCS1_OAEP_PADDING) != rsa_size) {
LOGE("RSA encryption failure: %s",
ERR_error_string(ERR_get_error(), nullptr));
FreeKey(key);
return false;
}
FreeKey(key);
return true;
}
// LogBoringSSLError is a callback from BoringSSL which is called with each
// error in the thread's error queue.
static int LogBoringSSLError(const char* msg, size_t /* length */,
void* /* user_data */) {
LOGE(" BoringSSL Error: %s", msg);
return 1;
}
static bool VerifyPSSSignature(EVP_PKEY* pkey, const std::string& message,
const std::string& signature) {
EVP_MD_CTX* evp_md_ctx = EVP_MD_CTX_new();
EVP_PKEY_CTX* pctx = nullptr;
if (EVP_DigestVerifyInit(evp_md_ctx, &pctx, EVP_sha1(),
nullptr /* no ENGINE */, pkey) != 1) {
LOGE("EVP_DigestVerifyInit failed");
goto err;
}
if (EVP_PKEY_CTX_set_signature_md(pctx, const_cast<EVP_MD*>(EVP_sha1())) !=
1) {
LOGE("EVP_PKEY_CTX_set_signature_md failed");
goto err;
}
if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) != 1) {
LOGE("EVP_PKEY_CTX_set_rsa_padding failed");
goto err;
}
if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, kPssSaltLength) != 1) {
LOGE("EVP_PKEY_CTX_set_rsa_pss_saltlen failed");
goto err;
}
if (EVP_DigestVerifyUpdate(evp_md_ctx, message.data(), message.size()) != 1) {
LOGE("EVP_DigestVerifyUpdate failed");
goto err;
}
if (EVP_DigestVerifyFinal(
evp_md_ctx,
const_cast<uint8_t*>(
reinterpret_cast<const uint8_t*>(signature.data())),
signature.size()) != 1) {
LOGE("EVP_DigestVerifyFinal failed (Probably a bad signature)");
goto err;
}
EVP_MD_CTX_free(evp_md_ctx);
return true;
err:
ERR_print_errors_cb(LogBoringSSLError, nullptr);
EVP_MD_CTX_free(evp_md_ctx);
return false;
}
bool RsaPublicKey::VerifySignature(const std::string& message,
const std::string& signature) {
if (serialized_key_.empty()) {
LOGE("RSA key not initialized");
return false;
}
if (message.empty()) {
LOGE("Signed message is empty");
return false;
}
RSA* rsa_key = GetKey(serialized_key_);
if (rsa_key == nullptr) {
// Error already logged by GetKey.
return false;
}
EVP_PKEY* pkey = EVP_PKEY_new();
if (pkey == nullptr) {
LOGE("EVP_PKEY allocation failed");
FreeKey(rsa_key);
return false;
}
if (EVP_PKEY_set1_RSA(pkey, rsa_key) != 1) {
LOGE("Failed to wrap key in an EVP_PKEY");
FreeKey(rsa_key);
EVP_PKEY_free(pkey);
return false;
}
FreeKey(rsa_key);
const bool ok = VerifyPSSSignature(pkey, message, signature);
EVP_PKEY_free(pkey);
if (!ok) {
LOGE("RSA verify failure");
return false;
}
return true;
}
bool ExtractExtensionValueFromCertificate(const std::string& cert,
const std::string& expected_oid,
size_t cert_index, uint32_t* value) {
// Convert the OID to OBJ form
boringssl_ptr<ASN1_OBJECT, ASN1_OBJECT_free> expected_obj(
OBJ_txt2obj(expected_oid.c_str(), true));
// The cert is a PKCS7 signed data type. First, parse it into an OpenSSL
// structure and find the certificate list.
//
// We must make defensive copies of cert's properties because of how
// d2i_PKCS7() works.
const unsigned char* cert_data =
reinterpret_cast<const unsigned char*>(cert.data());
long cert_size = static_cast<long>(cert.size());
boringssl_ptr<PKCS7, PKCS7_free> pkcs7(
d2i_PKCS7(nullptr, &cert_data, cert_size));
if (!pkcs7) {
LOGE("Error parsing PKCS7 message");
return false;
} else if (!PKCS7_type_is_signed(pkcs7.get())) {
LOGE("Unexpected PKCS7 message type");
return false;
}
STACK_OF(X509)* certs = pkcs7->d.sign->cert;
// Find the desired certificate from the stack.
const size_t num_certs = static_cast<size_t>(sk_X509_num(certs));
if (cert_index >= num_certs) {
LOGE(
"Unexpected number of certificates in chain: "
"count = %zu, minimum = %zu",
num_certs, cert_index + 1);
return false;
}
X509* const intermediate_cert =
sk_X509_value(certs, static_cast<int>(cert_index));
if (intermediate_cert == nullptr) {
LOGE("Unable to get intermediate cert");
return false;
}
// Find the Widevine System ID extension in the intermediate cert
const int extension_count = X509_get_ext_count(intermediate_cert);
for (int i = 0; i < extension_count; ++i) {
X509_EXTENSION* const extension = X509_get_ext(intermediate_cert, i);
if (extension == nullptr) {
LOGE("Unable to get cert extension: cert_index = %d", i);
continue;
}
ASN1_OBJECT* const extension_obj = X509_EXTENSION_get_object(extension);
if (extension_obj == nullptr) {
LOGE("Unable to get cert extension OID: cert_index = %d", i);
continue;
}
if (OBJ_cmp(extension_obj, expected_obj.get()) != 0) {
// This extension is not the Widevine System ID, so we should move on to
// the next one.
continue;
}
ASN1_OCTET_STRING* const octet_str = X509_EXTENSION_get_data(extension);
if (octet_str == nullptr) {
LOGE("Unable to get data of cert extension");
return false;
}
const unsigned char* data = octet_str->data;
if (data == nullptr) {
LOGE("Null data in cert extension");
return false;
}
ASN1_INTEGER* const asn1_integer =
d2i_ASN1_INTEGER(nullptr, &data, octet_str->length);
if (asn1_integer == nullptr) {
LOGE("Unable to decode data in cert extension");
return false;
}
const long system_id_long = ASN1_INTEGER_get(asn1_integer);
ASN1_INTEGER_free(asn1_integer);
if (system_id_long == -1) {
LOGE("Unable to decode ASN integer in extension");
return false;
}
*value = static_cast<uint32_t>(system_id_long);
return true;
}
LOGE("Cert extension not found");
return false;
}
std::string Md5Hash(const std::string& data) {
std::string hash(MD5_DIGEST_LENGTH, '\0');
MD5(reinterpret_cast<const uint8_t*>(data.data()), data.size(),
reinterpret_cast<uint8_t*>(&hash[0]));
return hash;
}
std::string Sha1Hash(const std::string& data) {
std::string hash(SHA_DIGEST_LENGTH, '\0');
SHA1(reinterpret_cast<const uint8_t*>(data.data()), data.size(),
reinterpret_cast<uint8_t*>(&hash[0]));
return hash;
}
std::string Sha256Hash(const std::string& data) {
std::string hash(SHA256_DIGEST_LENGTH, '\0');
SHA256(reinterpret_cast<const uint8_t*>(data.data()), data.size(),
reinterpret_cast<uint8_t*>(&hash[0]));
return hash;
}
std::string Sha512Hash(const std::string& data) {
std::string hash(SHA512_DIGEST_LENGTH, '\0');
SHA512(reinterpret_cast<const uint8_t*>(data.data()), data.size(),
reinterpret_cast<uint8_t*>(&hash[0]));
return hash;
}
} // namespace wvcas

View File

@@ -0,0 +1,284 @@
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#include "service_certificate.h"
#include <stdlib.h>
#include <algorithm>
#include <random>
#include "crypto_session.h"
#include "license_protocol.pb.h"
#include "log.h"
#include "privacy_crypto.h"
namespace {
// Root certificate for all Google/Widevine certificates. I.e. all service
// certificates and DRM certificates are signed by this cert, or have this cert
// as the root of a signing chain.
// clang-format off
const uint8_t kRootCertForProd[] = {
0x0a, 0x9c, 0x03, 0x08, 0x00, 0x12, 0x01, 0x00,
0x18, 0xdd, 0x94, 0x88, 0x8b, 0x05, 0x22, 0x8e,
0x03, 0x30, 0x82, 0x01, 0x8a, 0x02, 0x82, 0x01,
0x81, 0x00, 0xb4, 0xfe, 0x39, 0xc3, 0x65, 0x90,
0x03, 0xdb, 0x3c, 0x11, 0x97, 0x09, 0xe8, 0x68,
0xcd, 0xf2, 0xc3, 0x5e, 0x9b, 0xf2, 0xe7, 0x4d,
0x23, 0xb1, 0x10, 0xdb, 0x87, 0x65, 0xdf, 0xdc,
0xfb, 0x9f, 0x35, 0xa0, 0x57, 0x03, 0x53, 0x4c,
0xf6, 0x6d, 0x35, 0x7d, 0xa6, 0x78, 0xdb, 0xb3,
0x36, 0xd2, 0x3f, 0x9c, 0x40, 0xa9, 0x95, 0x26,
0x72, 0x7f, 0xb8, 0xbe, 0x66, 0xdf, 0xc5, 0x21,
0x98, 0x78, 0x15, 0x16, 0x68, 0x5d, 0x2f, 0x46,
0x0e, 0x43, 0xcb, 0x8a, 0x84, 0x39, 0xab, 0xfb,
0xb0, 0x35, 0x80, 0x22, 0xbe, 0x34, 0x23, 0x8b,
0xab, 0x53, 0x5b, 0x72, 0xec, 0x4b, 0xb5, 0x48,
0x69, 0x53, 0x3e, 0x47, 0x5f, 0xfd, 0x09, 0xfd,
0xa7, 0x76, 0x13, 0x8f, 0x0f, 0x92, 0xd6, 0x4c,
0xdf, 0xae, 0x76, 0xa9, 0xba, 0xd9, 0x22, 0x10,
0xa9, 0x9d, 0x71, 0x45, 0xd6, 0xd7, 0xe1, 0x19,
0x25, 0x85, 0x9c, 0x53, 0x9a, 0x97, 0xeb, 0x84,
0xd7, 0xcc, 0xa8, 0x88, 0x82, 0x20, 0x70, 0x26,
0x20, 0xfd, 0x7e, 0x40, 0x50, 0x27, 0xe2, 0x25,
0x93, 0x6f, 0xbc, 0x3e, 0x72, 0xa0, 0xfa, 0xc1,
0xbd, 0x29, 0xb4, 0x4d, 0x82, 0x5c, 0xc1, 0xb4,
0xcb, 0x9c, 0x72, 0x7e, 0xb0, 0xe9, 0x8a, 0x17,
0x3e, 0x19, 0x63, 0xfc, 0xfd, 0x82, 0x48, 0x2b,
0xb7, 0xb2, 0x33, 0xb9, 0x7d, 0xec, 0x4b, 0xba,
0x89, 0x1f, 0x27, 0xb8, 0x9b, 0x88, 0x48, 0x84,
0xaa, 0x18, 0x92, 0x0e, 0x65, 0xf5, 0xc8, 0x6c,
0x11, 0xff, 0x6b, 0x36, 0xe4, 0x74, 0x34, 0xca,
0x8c, 0x33, 0xb1, 0xf9, 0xb8, 0x8e, 0xb4, 0xe6,
0x12, 0xe0, 0x02, 0x98, 0x79, 0x52, 0x5e, 0x45,
0x33, 0xff, 0x11, 0xdc, 0xeb, 0xc3, 0x53, 0xba,
0x7c, 0x60, 0x1a, 0x11, 0x3d, 0x00, 0xfb, 0xd2,
0xb7, 0xaa, 0x30, 0xfa, 0x4f, 0x5e, 0x48, 0x77,
0x5b, 0x17, 0xdc, 0x75, 0xef, 0x6f, 0xd2, 0x19,
0x6d, 0xdc, 0xbe, 0x7f, 0xb0, 0x78, 0x8f, 0xdc,
0x82, 0x60, 0x4c, 0xbf, 0xe4, 0x29, 0x06, 0x5e,
0x69, 0x8c, 0x39, 0x13, 0xad, 0x14, 0x25, 0xed,
0x19, 0xb2, 0xf2, 0x9f, 0x01, 0x82, 0x0d, 0x56,
0x44, 0x88, 0xc8, 0x35, 0xec, 0x1f, 0x11, 0xb3,
0x24, 0xe0, 0x59, 0x0d, 0x37, 0xe4, 0x47, 0x3c,
0xea, 0x4b, 0x7f, 0x97, 0x31, 0x1c, 0x81, 0x7c,
0x94, 0x8a, 0x4c, 0x7d, 0x68, 0x15, 0x84, 0xff,
0xa5, 0x08, 0xfd, 0x18, 0xe7, 0xe7, 0x2b, 0xe4,
0x47, 0x27, 0x12, 0x11, 0xb8, 0x23, 0xec, 0x58,
0x93, 0x3c, 0xac, 0x12, 0xd2, 0x88, 0x6d, 0x41,
0x3d, 0xc5, 0xfe, 0x1c, 0xdc, 0xb9, 0xf8, 0xd4,
0x51, 0x3e, 0x07, 0xe5, 0x03, 0x6f, 0xa7, 0x12,
0xe8, 0x12, 0xf7, 0xb5, 0xce, 0xa6, 0x96, 0x55,
0x3f, 0x78, 0xb4, 0x64, 0x82, 0x50, 0xd2, 0x33,
0x5f, 0x91, 0x02, 0x03, 0x01, 0x00, 0x01, 0x12,
0x80, 0x03, 0x58, 0xf1, 0xd6, 0x4d, 0x04, 0x09,
0x7b, 0xdf, 0xd7, 0xef, 0x5d, 0x3b, 0x02, 0x39,
0x17, 0xfa, 0x14, 0x36, 0x75, 0x4a, 0x38, 0x67,
0x85, 0x57, 0x12, 0xa7, 0x14, 0xee, 0x35, 0x16,
0xd5, 0x3d, 0xbf, 0x42, 0x86, 0xf6, 0x69, 0x00,
0x76, 0xcd, 0x93, 0xf4, 0x7c, 0xb2, 0xdf, 0x9e,
0x44, 0xcd, 0x4c, 0xd4, 0xae, 0x09, 0x18, 0x53,
0x44, 0x32, 0xec, 0xe0, 0x61, 0x1b, 0xe5, 0xda,
0x13, 0xd3, 0x55, 0xc5, 0xdd, 0x1a, 0xcb, 0x90,
0x1e, 0x7e, 0x5b, 0xc6, 0xe9, 0x0f, 0x22, 0x9f,
0xbe, 0x85, 0x02, 0xfe, 0x90, 0x31, 0xcc, 0x6b,
0x03, 0x84, 0xbd, 0x22, 0xc4, 0x55, 0xfa, 0xf5,
0xf2, 0x08, 0xcd, 0x65, 0x41, 0x58, 0xe8, 0x7d,
0x29, 0xda, 0x04, 0x58, 0x82, 0xf5, 0x37, 0x69,
0xbc, 0xf3, 0x5a, 0x57, 0x84, 0x17, 0x7b, 0x32,
0x87, 0x70, 0xb2, 0xb0, 0x76, 0x9c, 0xb2, 0xc3,
0x15, 0xd1, 0x11, 0x26, 0x2a, 0x23, 0x75, 0x99,
0x3e, 0xb9, 0x77, 0x22, 0x32, 0x0d, 0xbc, 0x1a,
0x19, 0xc1, 0xd5, 0x65, 0x90, 0x76, 0x55, 0x74,
0x0f, 0x0e, 0x69, 0x4d, 0x5f, 0x4d, 0x8f, 0x19,
0xaf, 0xdf, 0xd6, 0x16, 0x31, 0x94, 0xa8, 0x92,
0x5f, 0x4f, 0xbc, 0x7a, 0x31, 0xf8, 0xae, 0x8e,
0xad, 0x33, 0xb7, 0xe9, 0x30, 0xd0, 0x8c, 0x0a,
0x8a, 0x6c, 0x83, 0x35, 0xf8, 0x8a, 0x81, 0xb2,
0xfe, 0x1c, 0x88, 0xac, 0x2a, 0x66, 0xc5, 0xff,
0xbd, 0xe6, 0x17, 0xd0, 0x62, 0x0b, 0xdc, 0x8a,
0x45, 0xf7, 0xb0, 0x3e, 0x5a, 0xc8, 0x1e, 0x4a,
0x24, 0x2f, 0x6c, 0xa5, 0xe3, 0x1c, 0x88, 0x14,
0x83, 0xd5, 0xc5, 0xef, 0x5e, 0x9f, 0x3d, 0x85,
0x45, 0x73, 0xe2, 0x6b, 0x50, 0x52, 0x57, 0x4c,
0xfb, 0x92, 0x6c, 0x66, 0x75, 0x8a, 0xd6, 0x0d,
0x1b, 0xae, 0xf3, 0xec, 0xaf, 0x51, 0x22, 0x03,
0x5d, 0x0a, 0x2e, 0x63, 0x93, 0x9c, 0x0b, 0x01,
0x20, 0xa8, 0xa9, 0x84, 0x2e, 0x17, 0xca, 0xae,
0x73, 0xec, 0x22, 0x1b, 0x79, 0xae, 0xf6, 0xa0,
0x72, 0x2c, 0xdf, 0x07, 0x47, 0xdb, 0x88, 0x86,
0x30, 0x14, 0x78, 0x21, 0x11, 0x22, 0x88, 0xac,
0xd7, 0x54, 0x74, 0xf9, 0xf3, 0x26, 0xc2, 0xa5,
0x56, 0xc8, 0x56, 0x4f, 0x00, 0x29, 0x1d, 0x08,
0x7b, 0x7a, 0xfb, 0x95, 0x89, 0xc3, 0xee, 0x98,
0x54, 0x9e, 0x3c, 0x6b, 0x94, 0x05, 0x13, 0x12,
0xf6, 0x71, 0xb9, 0xab, 0x13, 0xc3, 0x0c, 0x9b,
0x46, 0x08, 0x7b, 0x3d, 0x32, 0x6a, 0x68, 0xca,
0x1e, 0x9c, 0x90, 0x62, 0xc5, 0xed, 0x10, 0xb9,
0x1f, 0x17, 0x25, 0xce, 0x90, 0xb9, 0x6d, 0xcd,
0xc4, 0x46, 0xf5, 0xa3, 0x62, 0x13, 0x74, 0x02,
0xa7, 0x62, 0xa4, 0xfa, 0x55, 0xd9, 0xde, 0xcf,
0xa2, 0xe6, 0x80, 0x74, 0x55, 0x06, 0x49, 0xd5,
0x02, 0x0c
};
// clang-format on
constexpr size_t kRootCertForProdSize = sizeof(kRootCertForProd);
constexpr size_t KEY_IV_SIZE = 16;
constexpr size_t SERVICE_KEY_SIZE = 16;
unsigned int GetDeviceRandomSeed() {
static std::random_device rdev;
return rdev();
}
std::string RandomData(size_t length) {
std::uniform_int_distribution<short> dist(0, 255); // Range of [0, 255].
std::string random_data(length, '\0');
std::default_random_engine generator(GetDeviceRandomSeed());
std::generate(random_data.begin(), random_data.end(),
[&]() { return static_cast<char>(dist(generator)); });
return random_data;
}
} // namespace
namespace wvcas {
// Protobuf generated classes.
using video_widevine::ClientIdentification;
using video_widevine::DrmCertificate;
using video_widevine::EncryptedClientIdentification;
using video_widevine::LicenseError;
using video_widevine::SignedDrmCertificate;
using video_widevine::SignedMessage;
CasStatus ServiceCertificate::Init(const std::string& signed_certificate) {
const std::string root_cert_str(kRootCertForProd,
kRootCertForProd + kRootCertForProdSize);
// Load root cert public key. Don't bother verifying it.
SignedDrmCertificate signed_root_cert;
if (!signed_root_cert.ParseFromString(root_cert_str)) {
LOGE("Failed to deserialize signed root certificate");
return CasStatus(CasStatusCode::kDeviceCertificateError,
"Failed to deserialize signed root certificate");
}
video_widevine::DrmCertificate root_cert;
if (!root_cert.ParseFromString(signed_root_cert.drm_certificate())) {
LOGE("Failed to deserialize root certificate");
return CasStatus(CasStatusCode::kDeviceCertificateError,
"Failed to deserialize root certificate");
}
RsaPublicKey root_key;
if (!root_key.Init(root_cert.public_key())) {
LOGE("Failed to load root certificate public key");
return CasStatus(CasStatusCode::kDeviceCertificateError,
"Failed to load root certificate public key");
}
// Load the provided service certificate.
// First, parse it and verify its signature.
SignedDrmCertificate signed_service_cert;
if (!signed_service_cert.ParseFromString(signed_certificate)) {
LOGE("Failed to parse signed service certificate");
return CasStatus(CasStatusCode::kDeviceCertificateError,
"Failed to parse signed service certificate");
}
if (!root_key.VerifySignature(signed_service_cert.drm_certificate(),
signed_service_cert.signature())) {
LOGE("Failed to verify service certificate signature");
return CasStatus(CasStatusCode::kDeviceCertificateError,
"Failed to verify service certificate signature");
}
video_widevine::DrmCertificate service_cert;
if (!service_cert.ParseFromString(signed_service_cert.drm_certificate())) {
LOGE("Failed to parse service certificate");
return CasStatus(CasStatusCode::kDeviceCertificateError,
"Failed to parse service certificate");
}
if (service_cert.type() != video_widevine::DrmCertificate_Type_SERVICE) {
LOGE(
"DRM device certificate type is not a service certificate: "
"type = %d, expected_type = %d",
static_cast<int>(service_cert.type()),
static_cast<int>(video_widevine::DrmCertificate_Type_SERVICE));
return CasStatus(
CasStatusCode::kDeviceCertificateError,
"DRM device certificate type is not a service certificate");
}
// Service certificate passes all checks - set up its RSA public key.
public_key_ = std::make_unique<RsaPublicKey>();
if (!public_key_->Init(service_cert.public_key())) {
public_key_.reset();
LOGE("Failed to load service certificate public key");
return CasStatus(CasStatusCode::kDeviceCertificateError,
"Failed to load service certificate public key");
}
// Have service certificate and its public key - keep relevant fields.
signed_certificate_ = signed_certificate;
serial_number_ = service_cert.serial_number();
provider_id_ = service_cert.provider_id();
return CasStatusCode::kNoError;
}
CasStatus ServiceCertificate::EncryptRsaOaep(const std::string& plaintext,
std::string* ciphertext) const {
if (!public_key_) {
LOGE("Service certificate not set");
return CasStatus(CasStatusCode::kDeviceCertificateError,
"Service certificate not set");
}
if (!public_key_->Encrypt(plaintext, ciphertext)) {
return CasStatus(CasStatusCode::kDeviceCertificateError,
"CLIENT_ID_RSA_ENCRYPT_ERROR");
}
return CasStatusCode::kNoError;
}
CasStatus ServiceCertificate::EncryptClientId(
const ClientIdentification* clear_client_id,
EncryptedClientIdentification* encrypted_client_id) const {
encrypted_client_id->set_provider_id(provider_id_);
encrypted_client_id->set_service_certificate_serial_number(serial_number_);
std::string iv = RandomData(KEY_IV_SIZE);
std::string key = RandomData(SERVICE_KEY_SIZE);
if (key.length() != SERVICE_KEY_SIZE || iv.length() != KEY_IV_SIZE) {
LOGE("RandomData failed for key or iv.");
return CasStatus(CasStatusCode::kClientIdEncryptionError,
"RandomData failed for key or iv.");
}
std::string id, enc_id, enc_key;
clear_client_id->SerializeToString(&id);
AesCbcKey aes;
if (!aes.Init(key)) {
return CasStatus(CasStatusCode::kClientIdEncryptionError,
"CLIENT_ID_AES_INIT_ERROR");
}
if (!aes.Encrypt(id, iv, &enc_id)) {
return CasStatus(CasStatusCode::kClientIdEncryptionError,
"CLIENT_ID_AES_ENCRYPT_ERROR");
}
CasStatus encrypt_result = EncryptRsaOaep(key, &enc_key);
if (!encrypt_result.ok()) {
return encrypt_result;
}
encrypted_client_id->set_encrypted_client_id_iv(iv);
encrypted_client_id->set_encrypted_privacy_key(enc_key);
encrypted_client_id->set_encrypted_client_id(enc_id);
return CasStatusCode::kNoError;
}
} // namespace wvcas

View File

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

View File

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