Add Provisioning 4 support
Widevine provisioning 4 support is added in this patch.
This commit is contained in:
@@ -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: [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -29,6 +29,9 @@ enum class CasStatusCode : int32_t {
|
||||
kAccessDeniedByParentalControl = 15,
|
||||
kUnknownEvent = 16,
|
||||
kOEMCryptoVersionMismatch = 17,
|
||||
kDeviceCertificateError = 18,
|
||||
kClientIdEncryptionError = 19,
|
||||
kProvisioningError = 20,
|
||||
};
|
||||
|
||||
class CasStatus {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
50
plugin/include/crypto_wrapped_key.h
Normal file
50
plugin/include/crypto_wrapped_key.h
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
#ifndef WIDEVINE_CAS_CRYPTO_WRAPPED_KEY_H_
|
||||
#define WIDEVINE_CAS_CRYPTO_WRAPPED_KEY_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace wvcas {
|
||||
// Represents OEMCrypto's wrapped private DRM key. As of v16, it is
|
||||
// possible for OEMCrypto to support ECC-based DRM certificates. The
|
||||
// format of the wrapped key is vendor specific; however, the API
|
||||
// requires that the CDM track whether the wrapped key is RSA or ECC.
|
||||
class CryptoWrappedKey {
|
||||
public:
|
||||
enum Type : int32_t { kUninitialized = 0, kRsa = 1, kEcc = 2 };
|
||||
CryptoWrappedKey() = default;
|
||||
CryptoWrappedKey(Type type, const std::string& key)
|
||||
: type_(type), key_(key) {}
|
||||
|
||||
Type type() const { return type_; }
|
||||
void set_type(Type type) { type_ = type; }
|
||||
|
||||
const std::string& key() const { return key_; }
|
||||
// Mutable reference getter for passing to OMECrypto.
|
||||
std::string& key() { return key_; }
|
||||
void set_key(const std::string& key) { key_ = key; }
|
||||
|
||||
void Clear() {
|
||||
type_ = kUninitialized;
|
||||
key_.clear();
|
||||
}
|
||||
// A valid key must have a known key type and have key data.
|
||||
bool IsValid() const { return type_ != kUninitialized && !key_.empty(); }
|
||||
// Equality operator is for testing only. Real keys may have
|
||||
// different meta data but the same logical key.
|
||||
bool IsEqualTo(const CryptoWrappedKey& other) const {
|
||||
return type_ == other.type_ && key_ == other.key_;
|
||||
}
|
||||
|
||||
private:
|
||||
// DRM key type of the wrapped key. For wrapped keys which the type
|
||||
// of key is unknown, assume it to be RSA.
|
||||
Type type_ = kUninitialized;
|
||||
// Vendor-specific wrapped DRM key.
|
||||
std::string key_;
|
||||
}; // class CryptoWrappedKey
|
||||
} // namespace wvcas
|
||||
#endif // WIDEVINE_CAS_CRYPTO_WRAPPED_KEY_H_
|
||||
35
plugin/include/file_util.h
Normal file
35
plugin/include/file_util.h
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
#ifndef FILE_UTIL_H_
|
||||
#define FILE_UTIL_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "cas_status.h"
|
||||
#include "crypto_wrapped_key.h"
|
||||
#include "device_files.pb.h"
|
||||
#include "file_store.h"
|
||||
|
||||
namespace wvcas {
|
||||
bool ReadFileFromStorage(wvutil::FileSystem& file_system,
|
||||
const std::string& filename, std::string* file_data);
|
||||
bool RemoveFile(wvutil::FileSystem& file_system, const std::string& filename);
|
||||
bool StoreFile(wvutil::FileSystem& file_system, const std::string& filename,
|
||||
const std::string& file_data);
|
||||
bool StoreFileWithHash(wvutil::FileSystem& file_system, const std::string& name,
|
||||
const std::string& serialized_file);
|
||||
bool StoreOemCertificate(wvutil::FileSystem& file_system,
|
||||
const std::string& certificate,
|
||||
const CryptoWrappedKey& private_key);
|
||||
bool StoreCertificate(wvutil::FileSystem& file_system,
|
||||
const std::string& certificate,
|
||||
const CryptoWrappedKey& private_key);
|
||||
CasStatus RetrieveHashedFile(const std::string& certificate,
|
||||
video_widevine_client::sdk::File& file);
|
||||
bool RetrieveOemCertificate(wvutil::FileSystem& file_system,
|
||||
std::string& certificate,
|
||||
CryptoWrappedKey* wrapped_private_key);
|
||||
|
||||
} // namespace wvcas
|
||||
#endif // FILE_UTIL_H_
|
||||
@@ -21,7 +21,7 @@ struct InputStreamParams {
|
||||
size_t data_length;
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
86
plugin/include/privacy_crypto.h
Normal file
86
plugin/include/privacy_crypto.h
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
//
|
||||
// Description:
|
||||
// Declaration of classes representing AES and RSA public keys used
|
||||
// for signature verification and encryption.
|
||||
//
|
||||
// AES encryption details:
|
||||
// Algorithm: AES-CBC
|
||||
//
|
||||
// RSA signature details:
|
||||
// Algorithm: RSASSA-PSS
|
||||
// Hash algorithm: SHA1
|
||||
// Mask generation function: mgf1SHA1
|
||||
// Salt length: 20 bytes
|
||||
// Trailer field: 0xbc
|
||||
//
|
||||
// RSA encryption details:
|
||||
// Algorithm: RSA-OAEP
|
||||
// Mask generation function: mgf1SHA1
|
||||
// Label (encoding paramter): empty string
|
||||
//
|
||||
#ifndef WIDEVINE_CAS_PRIVACY_CRYPTO_H_
|
||||
#define WIDEVINE_CAS_PRIVACY_CRYPTO_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
class AesCbcKey {
|
||||
public:
|
||||
AesCbcKey();
|
||||
~AesCbcKey();
|
||||
|
||||
bool Init(const std::string& key);
|
||||
bool Encrypt(const std::string& in, const std::string& iv, std::string* out,
|
||||
bool has_padding = true);
|
||||
bool Decrypt(const std::string& in, const std::string& iv, std::string* out,
|
||||
bool has_padding = true);
|
||||
|
||||
private:
|
||||
std::string key_;
|
||||
}; // class AesCbcKey
|
||||
|
||||
class RsaPublicKey {
|
||||
public:
|
||||
RsaPublicKey();
|
||||
~RsaPublicKey();
|
||||
|
||||
// Initializes an RsaPublicKey object using a DER encoded PKCS#1 RSAPublicKey
|
||||
bool Init(const std::string& serialized_key);
|
||||
|
||||
// Encrypt a message using RSA-OAEP. Caller retains ownership of all
|
||||
// parameters. Returns true if successful, false otherwise.
|
||||
bool Encrypt(const std::string& plaintext, std::string* ciphertext);
|
||||
|
||||
// Verify RSASSA-PSS signature. Caller retains ownership of all parameters.
|
||||
// Returns true if validation succeeds, false otherwise.
|
||||
bool VerifySignature(const std::string& message,
|
||||
const std::string& signature);
|
||||
|
||||
private:
|
||||
std::string serialized_key_;
|
||||
}; // class RsaPublicKey
|
||||
|
||||
/**
|
||||
* Extracts an integer value from the extensions in a certificate.
|
||||
* @param cert A PKCS7 encoded X.509 certificate chain.
|
||||
* @param extension_oid The ID of the extension to get.
|
||||
* @param cert_index The zero-based index of the certificate in the chain to
|
||||
* fetch from.
|
||||
* @param value [OUT] Will contain the extracted value.
|
||||
* @return True on success, false on error.
|
||||
*/
|
||||
bool ExtractExtensionValueFromCertificate(const std::string& cert,
|
||||
const std::string& extension_oid,
|
||||
size_t cert_index, uint32_t* value);
|
||||
|
||||
std::string Md5Hash(const std::string& data);
|
||||
std::string Sha1Hash(const std::string& data);
|
||||
std::string Sha256Hash(const std::string& data);
|
||||
std::string Sha512Hash(const std::string& data);
|
||||
} // namespace wvcas
|
||||
#endif // WIDEVINE_CAS_PRIVACY_CRYPTO_H_
|
||||
64
plugin/include/service_certificate.h
Normal file
64
plugin/include/service_certificate.h
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
#ifndef WIDEVINE_CAS_SERVICE_CERTIFICATE_H_
|
||||
#define WIDEVINE_CAS_SERVICE_CERTIFICATE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "cas_status.h"
|
||||
#include "license_protocol.pb.h"
|
||||
#include "privacy_crypto.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
// Service Certificates are used to encrypt the ClientIdentification message
|
||||
// that is part of Device Provisioning, License, Renewal, and Release requests.
|
||||
class ServiceCertificate {
|
||||
public:
|
||||
ServiceCertificate() = default;
|
||||
virtual ~ServiceCertificate() {}
|
||||
|
||||
// Set up a new service certificate.
|
||||
// Accept a serialized video_widevine::SignedDrmDeviceCertificate message.
|
||||
virtual CasStatus Init(const std::string& signed_certificate);
|
||||
|
||||
bool HasSignedCertificate() const { return !signed_certificate_.empty(); }
|
||||
const std::string& signed_certificate() const { return signed_certificate_; }
|
||||
const std::string& provider_id() const { return provider_id_; }
|
||||
|
||||
// Encrypt the ClientIdentification message for a provisioning or
|
||||
// licensing request. Encryption is performed using the current
|
||||
// service certificate. Return a failure if the service certificate is
|
||||
// not present, not valid, or if some other error occurs.
|
||||
// The routine should not be called if privacy mode is off or if the
|
||||
// certificate is empty.
|
||||
CasStatus EncryptClientId(
|
||||
const video_widevine::ClientIdentification* clear_client_id,
|
||||
video_widevine::EncryptedClientIdentification* encrypted_client_id) const;
|
||||
|
||||
private:
|
||||
// Encrypt data using RSA with OAEP padding.
|
||||
// |plaintext| is the data to be encrypted. |ciphertext| is a pointer to a
|
||||
// string to contain the decrypted data on return, and may not be null.
|
||||
// returns NO_ERROR if successful or an appropriate error code otherwise.
|
||||
virtual CasStatus EncryptRsaOaep(const std::string& plaintext,
|
||||
std::string* ciphertext) const;
|
||||
|
||||
// Proto serialized SignedDrmCertificate.
|
||||
// Verified by Init() to be valid.
|
||||
std::string signed_certificate_;
|
||||
|
||||
// Certificate serial number.
|
||||
std::string serial_number_;
|
||||
|
||||
// Provider ID, extracted from certificate message.
|
||||
std::string provider_id_;
|
||||
|
||||
// Public key.
|
||||
std::unique_ptr<RsaPublicKey> public_key_;
|
||||
}; // class ServiceCertificate
|
||||
|
||||
} // namespace wvcas
|
||||
#endif // WIDEVINE_CAS_SERVICE_CERTIFICATE_H_
|
||||
@@ -160,7 +160,7 @@ class WidevineCas : public wvutil::TimerHandler {
|
||||
std::unique_ptr<CasLicense> cas_license_;
|
||||
std::unique_ptr<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_;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
270
plugin/src/file_util.cpp
Normal file
@@ -0,0 +1,270 @@
|
||||
// Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "file_util.h"
|
||||
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "crypto_wrapped_key.h"
|
||||
#include "device_files.pb.h"
|
||||
#include "file_util.h"
|
||||
#include "license_protocol.pb.h"
|
||||
#include "service_certificate.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
namespace {
|
||||
using video_widevine_client::sdk::DeviceCertificate;
|
||||
using video_widevine_client::sdk::File;
|
||||
using video_widevine_client::sdk::HashedFile;
|
||||
using video_widevine_client::sdk::OemCertificate;
|
||||
|
||||
constexpr char kOemCertificateFilePath[] =
|
||||
"/data/vendor/mediacas/IDM/widevine/oemcert.bin";
|
||||
constexpr char kDrmCertificateFilePath[] =
|
||||
"/data/vendor/mediacas/IDM/widevine/cert.bin";
|
||||
|
||||
bool Hash(const std::string& data, std::string* hash) {
|
||||
if (!hash) return false;
|
||||
hash->resize(SHA256_DIGEST_LENGTH);
|
||||
const unsigned char* input =
|
||||
reinterpret_cast<const unsigned char*>(data.data());
|
||||
unsigned char* output = reinterpret_cast<unsigned char*>(&(*hash)[0]);
|
||||
SHA256(input, data.size(), output);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ReadFileFromStorage(wvutil::FileSystem& file_system,
|
||||
const std::string& filename, std::string* file_data) {
|
||||
if (nullptr == file_data) {
|
||||
return false;
|
||||
}
|
||||
if (!file_system.Exists(filename)) {
|
||||
return false;
|
||||
}
|
||||
size_t filesize = file_system.FileSize(filename);
|
||||
if (0 == filesize) {
|
||||
return false;
|
||||
}
|
||||
file_data->resize(filesize);
|
||||
std::unique_ptr<wvutil::File> file =
|
||||
file_system.Open(filename, wvutil::FileSystem::kReadOnly);
|
||||
if (nullptr == file) {
|
||||
return false;
|
||||
}
|
||||
size_t bytes_read = file->Read(&(*file_data)[0], file_data->size());
|
||||
if (bytes_read != filesize) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RemoveFile(wvutil::FileSystem& file_system, const std::string& filename) {
|
||||
if (!file_system.Exists(filename)) {
|
||||
return false;
|
||||
}
|
||||
if (!file_system.Remove(filename)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StoreFile(wvutil::FileSystem& file_system, const std::string& filename,
|
||||
const std::string& file_data) {
|
||||
std::unique_ptr<wvutil::File> file(file_system.Open(
|
||||
filename, wvutil::FileSystem::kTruncate | wvutil::FileSystem::kCreate));
|
||||
if (nullptr == file) {
|
||||
return false;
|
||||
}
|
||||
size_t bytes_written = file->Write(file_data.data(), file_data.size());
|
||||
if (bytes_written != file_data.size()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StoreFileWithHash(wvutil::FileSystem& file_system, const std::string& name,
|
||||
const std::string& serialized_file) {
|
||||
std::string hash = Sha256Hash(serialized_file);
|
||||
|
||||
// Fill in hashed file data
|
||||
video_widevine_client::sdk::HashedFile hash_file;
|
||||
hash_file.set_file(serialized_file);
|
||||
hash_file.set_hash(hash);
|
||||
|
||||
std::string serialized_hash_file;
|
||||
hash_file.SerializeToString(&serialized_hash_file);
|
||||
|
||||
return StoreFile(file_system, name, serialized_hash_file);
|
||||
}
|
||||
|
||||
bool StoreOemCertificate(wvutil::FileSystem& file_system,
|
||||
const std::string& certificate,
|
||||
const CryptoWrappedKey& private_key) {
|
||||
if (certificate.empty()) {
|
||||
LOGE("Missing certificate information");
|
||||
return false;
|
||||
}
|
||||
if (!private_key.IsValid()) {
|
||||
LOGE("Private key is invalid");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string certificate_file_name = std::string(kOemCertificateFilePath);
|
||||
|
||||
// Fill in file information
|
||||
video_widevine_client::sdk::File file;
|
||||
file.set_type(video_widevine_client::sdk::File::OEM_CERTIFICATE);
|
||||
file.set_version(video_widevine_client::sdk::File::VERSION_1);
|
||||
video_widevine_client::sdk::OemCertificate* oem_certificate = file.mutable_oem_certificate();
|
||||
oem_certificate->set_certificate(certificate);
|
||||
oem_certificate->set_wrapped_private_key(private_key.key());
|
||||
switch (private_key.type()) {
|
||||
case wvcas::CryptoWrappedKey::kRsa:
|
||||
oem_certificate->set_key_type(video_widevine_client::sdk::OemCertificate::RSA);
|
||||
break;
|
||||
case wvcas::CryptoWrappedKey::kEcc:
|
||||
oem_certificate->set_key_type(video_widevine_client::sdk::OemCertificate::ECC);
|
||||
break;
|
||||
case wvcas::CryptoWrappedKey::kUninitialized:
|
||||
default:
|
||||
LOGE("Unexpected key type: %d", private_key.type());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string serialized_file;
|
||||
file.SerializeToString(&serialized_file);
|
||||
return StoreFileWithHash(file_system, certificate_file_name, serialized_file);
|
||||
}
|
||||
|
||||
bool StoreCertificate(wvutil::FileSystem& file_system,
|
||||
const std::string& certificate,
|
||||
const CryptoWrappedKey& private_key) {
|
||||
if (certificate.empty()) {
|
||||
LOGE("Missing certificate information");
|
||||
return false;
|
||||
}
|
||||
if (!private_key.IsValid()) {
|
||||
LOGE("Private key is invalid");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fill in file information
|
||||
video_widevine_client::sdk::File file;
|
||||
|
||||
file.set_type(video_widevine_client::sdk::File::DEVICE_CERTIFICATE);
|
||||
file.set_version(video_widevine_client::sdk::File::VERSION_1);
|
||||
|
||||
DeviceCertificate* mutable_device_certificate =
|
||||
file.mutable_device_certificate();
|
||||
mutable_device_certificate->set_certificate(certificate);
|
||||
mutable_device_certificate->set_wrapped_private_key(private_key.key());
|
||||
switch (private_key.type()) {
|
||||
case CryptoWrappedKey::kRsa:
|
||||
mutable_device_certificate->set_key_type(DeviceCertificate::RSA);
|
||||
break;
|
||||
case CryptoWrappedKey::kEcc:
|
||||
mutable_device_certificate->set_key_type(DeviceCertificate::ECC);
|
||||
break;
|
||||
case CryptoWrappedKey::kUninitialized: // Suppress compiler
|
||||
// warnings.
|
||||
default:
|
||||
LOGE("Unexpected key type: %d", private_key.type());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string serialized_file;
|
||||
file.SerializeToString(&serialized_file);
|
||||
|
||||
std::string certificate_file_name = std::string(kDrmCertificateFilePath);
|
||||
|
||||
return StoreFileWithHash(file_system, certificate_file_name, serialized_file);
|
||||
}
|
||||
|
||||
|
||||
CasStatus RetrieveHashedFile(const std::string& certificate, video_widevine_client::sdk::File& file) {
|
||||
video_widevine_client::sdk::HashedFile hash_file;
|
||||
if (!hash_file.ParseFromString(certificate)) {
|
||||
return CasStatus(CasStatusCode::kIndividualizationError,
|
||||
("unable to parse the certificate data"));
|
||||
}
|
||||
std::string file_hash;
|
||||
if (!Hash(hash_file.file(), &file_hash)) {
|
||||
return CasStatus(CasStatusCode::kIndividualizationError,
|
||||
("generating file hash fails"));
|
||||
}
|
||||
if (hash_file.hash() != file_hash) {
|
||||
return CasStatus(CasStatusCode::kIndividualizationError,
|
||||
("corrupt certificate file data"));
|
||||
}
|
||||
if (!file.ParseFromString(hash_file.file())) {
|
||||
return CasStatus(CasStatusCode::kIndividualizationError,
|
||||
("unable to parse the file data"));
|
||||
}
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
bool RetrieveOemCertificate(wvutil::FileSystem& file_system,
|
||||
std::string& certificate,
|
||||
CryptoWrappedKey* wrapped_private_key) {
|
||||
std::string file_data;
|
||||
if (!ReadFileFromStorage(file_system, std::string(kOemCertificateFilePath),
|
||||
&file_data)) {
|
||||
LOGW("Unable to read OEM cert file from storage");
|
||||
return false;
|
||||
}
|
||||
File file;
|
||||
CasStatus status = RetrieveHashedFile(file_data, file);
|
||||
if (status.status_code() != CasStatusCode::kNoError) {
|
||||
LOGW("Unable to retrieve certificate file: %s",
|
||||
status.error_string().c_str());
|
||||
return false;
|
||||
}
|
||||
if (file.type() != File::OEM_CERTIFICATE) {
|
||||
LOGE("Certificate file is of incorrect file type: type = %d",
|
||||
static_cast<int>(file.type()));
|
||||
return false;
|
||||
}
|
||||
if (file.version() != File::VERSION_1) {
|
||||
LOGE("Certificate file is of incorrect file version: version = %d",
|
||||
static_cast<int>(file.version()));
|
||||
return false;
|
||||
}
|
||||
if (!file.has_oem_certificate()) {
|
||||
LOGE("Certificate not present");
|
||||
return false;
|
||||
}
|
||||
|
||||
const video_widevine_client::sdk::OemCertificate& oem_certificate = file.oem_certificate();
|
||||
if (oem_certificate.certificate().empty() ||
|
||||
oem_certificate.wrapped_private_key().empty()) {
|
||||
LOGE("Empty certificate or private key");
|
||||
return false;
|
||||
}
|
||||
|
||||
certificate = oem_certificate.certificate();
|
||||
wrapped_private_key->Clear();
|
||||
wrapped_private_key->set_key(oem_certificate.wrapped_private_key());
|
||||
switch (oem_certificate.key_type()) {
|
||||
case video_widevine_client::sdk::OemCertificate::RSA:
|
||||
wrapped_private_key->set_type(CryptoWrappedKey::kRsa);
|
||||
break;
|
||||
case video_widevine_client::sdk::OemCertificate::ECC:
|
||||
wrapped_private_key->set_type(CryptoWrappedKey::kEcc);
|
||||
break;
|
||||
default:
|
||||
LOGW("Unknown key type, defaulting to RSA: type = %d",
|
||||
oem_certificate.key_type());
|
||||
wrapped_private_key->set_type(CryptoWrappedKey::kRsa);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
@@ -115,6 +115,20 @@ class OEMCryptoInterface::Impl {
|
||||
size_t* key_token_length);
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
489
plugin/src/privacy_crypto_boringssl.cpp
Normal file
489
plugin/src/privacy_crypto_boringssl.cpp
Normal file
@@ -0,0 +1,489 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
//
|
||||
// Description:
|
||||
// Definition of classes representing RSA public keys used
|
||||
// for signature verification and encryption and decryption.
|
||||
//
|
||||
#include "privacy_crypto.h"
|
||||
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/asn1.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/md5.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/pkcs7.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace {
|
||||
const int kPssSaltLength = 20;
|
||||
const int kRsaPkcs1OaepPaddingLength = 41;
|
||||
|
||||
RSA* GetKey(const std::string& serialized_key) {
|
||||
BIO* bio = BIO_new_mem_buf(const_cast<char*>(serialized_key.data()),
|
||||
static_cast<int>(serialized_key.size()));
|
||||
if (bio == nullptr) {
|
||||
LOGE("BIO_new_mem_buf failed: returned null");
|
||||
return nullptr;
|
||||
}
|
||||
RSA* key = d2i_RSAPublicKey_bio(bio, nullptr);
|
||||
|
||||
if (key == nullptr) {
|
||||
LOGE("RSA key deserialization failure: %s",
|
||||
ERR_error_string(ERR_get_error(), nullptr));
|
||||
BIO_free(bio);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BIO_free(bio);
|
||||
return key;
|
||||
}
|
||||
|
||||
void FreeKey(RSA* key) {
|
||||
if (key != nullptr) {
|
||||
RSA_free(key);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, void (*func)(T*)>
|
||||
class boringssl_ptr {
|
||||
public:
|
||||
explicit boringssl_ptr(T* p = nullptr) : ptr_(p) {}
|
||||
~boringssl_ptr() {
|
||||
if (ptr_) func(ptr_);
|
||||
}
|
||||
T& operator*() const { return *ptr_; }
|
||||
T* operator->() const { return ptr_; }
|
||||
T* get() const { return ptr_; }
|
||||
explicit operator bool() const { return ptr_ != nullptr; }
|
||||
|
||||
private:
|
||||
T* ptr_;
|
||||
}; // class boringssl_ptr
|
||||
} // namespace
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
AesCbcKey::AesCbcKey() {}
|
||||
|
||||
AesCbcKey::~AesCbcKey() {}
|
||||
|
||||
bool AesCbcKey::Init(const std::string& key) {
|
||||
if (key.size() != AES_BLOCK_SIZE) {
|
||||
LOGE("Unexpected key size: size = %zu, expected = %d", key.size(),
|
||||
AES_BLOCK_SIZE);
|
||||
return false;
|
||||
}
|
||||
|
||||
key_ = key;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AesCbcKey::Encrypt(const std::string& in, const std::string& iv,
|
||||
std::string* out, bool has_padding) {
|
||||
if (in.empty()) {
|
||||
LOGE("No cleartext provided");
|
||||
return false;
|
||||
}
|
||||
if (iv.size() != AES_BLOCK_SIZE) {
|
||||
LOGE("Invalid IV size: %zu", iv.size());
|
||||
return false;
|
||||
}
|
||||
if (out == nullptr) {
|
||||
LOGE("Ciphertext output parameter |out| not provided");
|
||||
return false;
|
||||
}
|
||||
if (key_.empty()) {
|
||||
LOGE("AES key not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX* evp_cipher_ctx = EVP_CIPHER_CTX_new();
|
||||
if (EVP_EncryptInit(evp_cipher_ctx, EVP_aes_128_cbc(),
|
||||
reinterpret_cast<const uint8_t*>(&key_[0]),
|
||||
reinterpret_cast<const uint8_t*>(&iv[0])) == 0) {
|
||||
LOGE("AES CBC setup failure: %s",
|
||||
ERR_error_string(ERR_get_error(), nullptr));
|
||||
EVP_CIPHER_CTX_free(evp_cipher_ctx);
|
||||
return false;
|
||||
}
|
||||
if (!has_padding) EVP_CIPHER_CTX_set_padding(evp_cipher_ctx, 0);
|
||||
|
||||
out->resize(in.size() + AES_BLOCK_SIZE);
|
||||
int out_length = static_cast<int>(out->size());
|
||||
if (EVP_EncryptUpdate(evp_cipher_ctx, reinterpret_cast<uint8_t*>(&(*out)[0]),
|
||||
&out_length,
|
||||
reinterpret_cast<const uint8_t*>(in.data()),
|
||||
static_cast<int>(in.size())) == 0) {
|
||||
LOGE("AES CBC encryption failure: %s",
|
||||
ERR_error_string(ERR_get_error(), nullptr));
|
||||
EVP_CIPHER_CTX_free(evp_cipher_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
int padding = 0;
|
||||
if (EVP_EncryptFinal_ex(evp_cipher_ctx,
|
||||
reinterpret_cast<uint8_t*>(&(*out)[out_length]),
|
||||
&padding) == 0) {
|
||||
LOGE("PKCS7 padding failure: %s",
|
||||
ERR_error_string(ERR_get_error(), nullptr));
|
||||
EVP_CIPHER_CTX_free(evp_cipher_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_free(evp_cipher_ctx);
|
||||
out->resize(out_length + padding);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AesCbcKey::Decrypt(const std::string& in, const std::string& iv,
|
||||
std::string* out, bool has_padding) {
|
||||
if (in.empty()) {
|
||||
LOGE("No ciphertext provided");
|
||||
return false;
|
||||
}
|
||||
if (iv.size() != AES_BLOCK_SIZE) {
|
||||
LOGE("Invalid IV size: %zu", iv.size());
|
||||
return false;
|
||||
}
|
||||
if (out == nullptr) {
|
||||
LOGE("Ciphertext output parameter |out| not provided");
|
||||
return false;
|
||||
}
|
||||
if (key_.empty()) {
|
||||
LOGE("AES key not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX* evp_cipher_ctx = EVP_CIPHER_CTX_new();
|
||||
if (EVP_DecryptInit(evp_cipher_ctx, EVP_aes_128_cbc(),
|
||||
reinterpret_cast<const uint8_t*>(&key_[0]),
|
||||
reinterpret_cast<const uint8_t*>(&iv[0])) == 0) {
|
||||
LOGE("AES CBC setup failure: %s",
|
||||
ERR_error_string(ERR_get_error(), nullptr));
|
||||
EVP_CIPHER_CTX_free(evp_cipher_ctx);
|
||||
return false;
|
||||
}
|
||||
if (!has_padding) EVP_CIPHER_CTX_set_padding(evp_cipher_ctx, 0);
|
||||
|
||||
out->resize(in.size() + AES_BLOCK_SIZE);
|
||||
int out_length = static_cast<int>(out->size());
|
||||
if (EVP_DecryptUpdate(evp_cipher_ctx, reinterpret_cast<uint8_t*>(&(*out)[0]),
|
||||
&out_length,
|
||||
reinterpret_cast<const uint8_t*>(in.data()),
|
||||
static_cast<int>(in.size())) == 0) {
|
||||
LOGE("AES CBC encryption failure: %s",
|
||||
ERR_error_string(ERR_get_error(), nullptr));
|
||||
EVP_CIPHER_CTX_free(evp_cipher_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
int padding = 0;
|
||||
if (EVP_DecryptFinal_ex(evp_cipher_ctx,
|
||||
reinterpret_cast<uint8_t*>(&(*out)[out_length]),
|
||||
&padding) == 0) {
|
||||
LOGE("PKCS7 padding failure: %s",
|
||||
ERR_error_string(ERR_get_error(), nullptr));
|
||||
EVP_CIPHER_CTX_free(evp_cipher_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_free(evp_cipher_ctx);
|
||||
out->resize(out_length + padding);
|
||||
return true;
|
||||
}
|
||||
|
||||
RsaPublicKey::RsaPublicKey() {}
|
||||
|
||||
RsaPublicKey::~RsaPublicKey() {}
|
||||
|
||||
bool RsaPublicKey::Init(const std::string& serialized_key) {
|
||||
if (serialized_key.empty()) {
|
||||
LOGE("No serialized key provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
serialized_key_ = serialized_key;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RsaPublicKey::Encrypt(const std::string& clear_message,
|
||||
std::string* encrypted_message) {
|
||||
if (clear_message.empty()) {
|
||||
LOGE("Message to be encrypted is empty");
|
||||
return false;
|
||||
}
|
||||
if (encrypted_message == nullptr) {
|
||||
LOGE("Output parameter |encrypted_message| not provided");
|
||||
return false;
|
||||
}
|
||||
if (serialized_key_.empty()) {
|
||||
LOGE("RSA key not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
RSA* key = GetKey(serialized_key_);
|
||||
if (key == nullptr) {
|
||||
// Error already logged by GetKey.
|
||||
return false;
|
||||
}
|
||||
|
||||
int rsa_size = RSA_size(key);
|
||||
if (static_cast<int>(clear_message.size()) >
|
||||
rsa_size - kRsaPkcs1OaepPaddingLength) {
|
||||
LOGE("Message too large to be encrypted: message_size = %zu, max = %d",
|
||||
clear_message.size(), rsa_size - kRsaPkcs1OaepPaddingLength);
|
||||
FreeKey(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
encrypted_message->assign(rsa_size, 0);
|
||||
if (RSA_public_encrypt(
|
||||
static_cast<int>(clear_message.size()),
|
||||
const_cast<unsigned char*>(
|
||||
reinterpret_cast<const unsigned char*>(clear_message.data())),
|
||||
reinterpret_cast<unsigned char*>(&(*encrypted_message)[0]), key,
|
||||
RSA_PKCS1_OAEP_PADDING) != rsa_size) {
|
||||
LOGE("RSA encryption failure: %s",
|
||||
ERR_error_string(ERR_get_error(), nullptr));
|
||||
FreeKey(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
FreeKey(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
// LogBoringSSLError is a callback from BoringSSL which is called with each
|
||||
// error in the thread's error queue.
|
||||
static int LogBoringSSLError(const char* msg, size_t /* length */,
|
||||
void* /* user_data */) {
|
||||
LOGE(" BoringSSL Error: %s", msg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool VerifyPSSSignature(EVP_PKEY* pkey, const std::string& message,
|
||||
const std::string& signature) {
|
||||
EVP_MD_CTX* evp_md_ctx = EVP_MD_CTX_new();
|
||||
EVP_PKEY_CTX* pctx = nullptr;
|
||||
|
||||
if (EVP_DigestVerifyInit(evp_md_ctx, &pctx, EVP_sha1(),
|
||||
nullptr /* no ENGINE */, pkey) != 1) {
|
||||
LOGE("EVP_DigestVerifyInit failed");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (EVP_PKEY_CTX_set_signature_md(pctx, const_cast<EVP_MD*>(EVP_sha1())) !=
|
||||
1) {
|
||||
LOGE("EVP_PKEY_CTX_set_signature_md failed");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) != 1) {
|
||||
LOGE("EVP_PKEY_CTX_set_rsa_padding failed");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, kPssSaltLength) != 1) {
|
||||
LOGE("EVP_PKEY_CTX_set_rsa_pss_saltlen failed");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (EVP_DigestVerifyUpdate(evp_md_ctx, message.data(), message.size()) != 1) {
|
||||
LOGE("EVP_DigestVerifyUpdate failed");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (EVP_DigestVerifyFinal(
|
||||
evp_md_ctx,
|
||||
const_cast<uint8_t*>(
|
||||
reinterpret_cast<const uint8_t*>(signature.data())),
|
||||
signature.size()) != 1) {
|
||||
LOGE("EVP_DigestVerifyFinal failed (Probably a bad signature)");
|
||||
goto err;
|
||||
}
|
||||
|
||||
EVP_MD_CTX_free(evp_md_ctx);
|
||||
return true;
|
||||
|
||||
err:
|
||||
ERR_print_errors_cb(LogBoringSSLError, nullptr);
|
||||
EVP_MD_CTX_free(evp_md_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RsaPublicKey::VerifySignature(const std::string& message,
|
||||
const std::string& signature) {
|
||||
if (serialized_key_.empty()) {
|
||||
LOGE("RSA key not initialized");
|
||||
return false;
|
||||
}
|
||||
if (message.empty()) {
|
||||
LOGE("Signed message is empty");
|
||||
return false;
|
||||
}
|
||||
RSA* rsa_key = GetKey(serialized_key_);
|
||||
if (rsa_key == nullptr) {
|
||||
// Error already logged by GetKey.
|
||||
return false;
|
||||
}
|
||||
EVP_PKEY* pkey = EVP_PKEY_new();
|
||||
if (pkey == nullptr) {
|
||||
LOGE("EVP_PKEY allocation failed");
|
||||
FreeKey(rsa_key);
|
||||
return false;
|
||||
}
|
||||
if (EVP_PKEY_set1_RSA(pkey, rsa_key) != 1) {
|
||||
LOGE("Failed to wrap key in an EVP_PKEY");
|
||||
FreeKey(rsa_key);
|
||||
EVP_PKEY_free(pkey);
|
||||
return false;
|
||||
}
|
||||
FreeKey(rsa_key);
|
||||
|
||||
const bool ok = VerifyPSSSignature(pkey, message, signature);
|
||||
EVP_PKEY_free(pkey);
|
||||
|
||||
if (!ok) {
|
||||
LOGE("RSA verify failure");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExtractExtensionValueFromCertificate(const std::string& cert,
|
||||
const std::string& expected_oid,
|
||||
size_t cert_index, uint32_t* value) {
|
||||
// Convert the OID to OBJ form
|
||||
boringssl_ptr<ASN1_OBJECT, ASN1_OBJECT_free> expected_obj(
|
||||
OBJ_txt2obj(expected_oid.c_str(), true));
|
||||
|
||||
// The cert is a PKCS7 signed data type. First, parse it into an OpenSSL
|
||||
// structure and find the certificate list.
|
||||
//
|
||||
// We must make defensive copies of cert's properties because of how
|
||||
// d2i_PKCS7() works.
|
||||
const unsigned char* cert_data =
|
||||
reinterpret_cast<const unsigned char*>(cert.data());
|
||||
long cert_size = static_cast<long>(cert.size());
|
||||
boringssl_ptr<PKCS7, PKCS7_free> pkcs7(
|
||||
d2i_PKCS7(nullptr, &cert_data, cert_size));
|
||||
if (!pkcs7) {
|
||||
LOGE("Error parsing PKCS7 message");
|
||||
return false;
|
||||
} else if (!PKCS7_type_is_signed(pkcs7.get())) {
|
||||
LOGE("Unexpected PKCS7 message type");
|
||||
return false;
|
||||
}
|
||||
|
||||
STACK_OF(X509)* certs = pkcs7->d.sign->cert;
|
||||
|
||||
// Find the desired certificate from the stack.
|
||||
const size_t num_certs = static_cast<size_t>(sk_X509_num(certs));
|
||||
if (cert_index >= num_certs) {
|
||||
LOGE(
|
||||
"Unexpected number of certificates in chain: "
|
||||
"count = %zu, minimum = %zu",
|
||||
num_certs, cert_index + 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
X509* const intermediate_cert =
|
||||
sk_X509_value(certs, static_cast<int>(cert_index));
|
||||
if (intermediate_cert == nullptr) {
|
||||
LOGE("Unable to get intermediate cert");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the Widevine System ID extension in the intermediate cert
|
||||
const int extension_count = X509_get_ext_count(intermediate_cert);
|
||||
for (int i = 0; i < extension_count; ++i) {
|
||||
X509_EXTENSION* const extension = X509_get_ext(intermediate_cert, i);
|
||||
if (extension == nullptr) {
|
||||
LOGE("Unable to get cert extension: cert_index = %d", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
ASN1_OBJECT* const extension_obj = X509_EXTENSION_get_object(extension);
|
||||
if (extension_obj == nullptr) {
|
||||
LOGE("Unable to get cert extension OID: cert_index = %d", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (OBJ_cmp(extension_obj, expected_obj.get()) != 0) {
|
||||
// This extension is not the Widevine System ID, so we should move on to
|
||||
// the next one.
|
||||
continue;
|
||||
}
|
||||
|
||||
ASN1_OCTET_STRING* const octet_str = X509_EXTENSION_get_data(extension);
|
||||
if (octet_str == nullptr) {
|
||||
LOGE("Unable to get data of cert extension");
|
||||
return false;
|
||||
}
|
||||
|
||||
const unsigned char* data = octet_str->data;
|
||||
if (data == nullptr) {
|
||||
LOGE("Null data in cert extension");
|
||||
return false;
|
||||
}
|
||||
|
||||
ASN1_INTEGER* const asn1_integer =
|
||||
d2i_ASN1_INTEGER(nullptr, &data, octet_str->length);
|
||||
if (asn1_integer == nullptr) {
|
||||
LOGE("Unable to decode data in cert extension");
|
||||
return false;
|
||||
}
|
||||
|
||||
const long system_id_long = ASN1_INTEGER_get(asn1_integer);
|
||||
ASN1_INTEGER_free(asn1_integer);
|
||||
if (system_id_long == -1) {
|
||||
LOGE("Unable to decode ASN integer in extension");
|
||||
return false;
|
||||
}
|
||||
|
||||
*value = static_cast<uint32_t>(system_id_long);
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGE("Cert extension not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string Md5Hash(const std::string& data) {
|
||||
std::string hash(MD5_DIGEST_LENGTH, '\0');
|
||||
MD5(reinterpret_cast<const uint8_t*>(data.data()), data.size(),
|
||||
reinterpret_cast<uint8_t*>(&hash[0]));
|
||||
return hash;
|
||||
}
|
||||
|
||||
std::string Sha1Hash(const std::string& data) {
|
||||
std::string hash(SHA_DIGEST_LENGTH, '\0');
|
||||
SHA1(reinterpret_cast<const uint8_t*>(data.data()), data.size(),
|
||||
reinterpret_cast<uint8_t*>(&hash[0]));
|
||||
return hash;
|
||||
}
|
||||
|
||||
std::string Sha256Hash(const std::string& data) {
|
||||
std::string hash(SHA256_DIGEST_LENGTH, '\0');
|
||||
SHA256(reinterpret_cast<const uint8_t*>(data.data()), data.size(),
|
||||
reinterpret_cast<uint8_t*>(&hash[0]));
|
||||
return hash;
|
||||
}
|
||||
|
||||
std::string Sha512Hash(const std::string& data) {
|
||||
std::string hash(SHA512_DIGEST_LENGTH, '\0');
|
||||
SHA512(reinterpret_cast<const uint8_t*>(data.data()), data.size(),
|
||||
reinterpret_cast<uint8_t*>(&hash[0]));
|
||||
return hash;
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
284
plugin/src/service_certificate.cpp
Normal file
284
plugin/src/service_certificate.cpp
Normal file
@@ -0,0 +1,284 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#include "service_certificate.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
|
||||
#include "crypto_session.h"
|
||||
#include "license_protocol.pb.h"
|
||||
#include "log.h"
|
||||
#include "privacy_crypto.h"
|
||||
|
||||
namespace {
|
||||
// Root certificate for all Google/Widevine certificates. I.e. all service
|
||||
// certificates and DRM certificates are signed by this cert, or have this cert
|
||||
// as the root of a signing chain.
|
||||
|
||||
// clang-format off
|
||||
const uint8_t kRootCertForProd[] = {
|
||||
0x0a, 0x9c, 0x03, 0x08, 0x00, 0x12, 0x01, 0x00,
|
||||
0x18, 0xdd, 0x94, 0x88, 0x8b, 0x05, 0x22, 0x8e,
|
||||
0x03, 0x30, 0x82, 0x01, 0x8a, 0x02, 0x82, 0x01,
|
||||
0x81, 0x00, 0xb4, 0xfe, 0x39, 0xc3, 0x65, 0x90,
|
||||
0x03, 0xdb, 0x3c, 0x11, 0x97, 0x09, 0xe8, 0x68,
|
||||
0xcd, 0xf2, 0xc3, 0x5e, 0x9b, 0xf2, 0xe7, 0x4d,
|
||||
0x23, 0xb1, 0x10, 0xdb, 0x87, 0x65, 0xdf, 0xdc,
|
||||
0xfb, 0x9f, 0x35, 0xa0, 0x57, 0x03, 0x53, 0x4c,
|
||||
0xf6, 0x6d, 0x35, 0x7d, 0xa6, 0x78, 0xdb, 0xb3,
|
||||
0x36, 0xd2, 0x3f, 0x9c, 0x40, 0xa9, 0x95, 0x26,
|
||||
0x72, 0x7f, 0xb8, 0xbe, 0x66, 0xdf, 0xc5, 0x21,
|
||||
0x98, 0x78, 0x15, 0x16, 0x68, 0x5d, 0x2f, 0x46,
|
||||
0x0e, 0x43, 0xcb, 0x8a, 0x84, 0x39, 0xab, 0xfb,
|
||||
0xb0, 0x35, 0x80, 0x22, 0xbe, 0x34, 0x23, 0x8b,
|
||||
0xab, 0x53, 0x5b, 0x72, 0xec, 0x4b, 0xb5, 0x48,
|
||||
0x69, 0x53, 0x3e, 0x47, 0x5f, 0xfd, 0x09, 0xfd,
|
||||
0xa7, 0x76, 0x13, 0x8f, 0x0f, 0x92, 0xd6, 0x4c,
|
||||
0xdf, 0xae, 0x76, 0xa9, 0xba, 0xd9, 0x22, 0x10,
|
||||
0xa9, 0x9d, 0x71, 0x45, 0xd6, 0xd7, 0xe1, 0x19,
|
||||
0x25, 0x85, 0x9c, 0x53, 0x9a, 0x97, 0xeb, 0x84,
|
||||
0xd7, 0xcc, 0xa8, 0x88, 0x82, 0x20, 0x70, 0x26,
|
||||
0x20, 0xfd, 0x7e, 0x40, 0x50, 0x27, 0xe2, 0x25,
|
||||
0x93, 0x6f, 0xbc, 0x3e, 0x72, 0xa0, 0xfa, 0xc1,
|
||||
0xbd, 0x29, 0xb4, 0x4d, 0x82, 0x5c, 0xc1, 0xb4,
|
||||
0xcb, 0x9c, 0x72, 0x7e, 0xb0, 0xe9, 0x8a, 0x17,
|
||||
0x3e, 0x19, 0x63, 0xfc, 0xfd, 0x82, 0x48, 0x2b,
|
||||
0xb7, 0xb2, 0x33, 0xb9, 0x7d, 0xec, 0x4b, 0xba,
|
||||
0x89, 0x1f, 0x27, 0xb8, 0x9b, 0x88, 0x48, 0x84,
|
||||
0xaa, 0x18, 0x92, 0x0e, 0x65, 0xf5, 0xc8, 0x6c,
|
||||
0x11, 0xff, 0x6b, 0x36, 0xe4, 0x74, 0x34, 0xca,
|
||||
0x8c, 0x33, 0xb1, 0xf9, 0xb8, 0x8e, 0xb4, 0xe6,
|
||||
0x12, 0xe0, 0x02, 0x98, 0x79, 0x52, 0x5e, 0x45,
|
||||
0x33, 0xff, 0x11, 0xdc, 0xeb, 0xc3, 0x53, 0xba,
|
||||
0x7c, 0x60, 0x1a, 0x11, 0x3d, 0x00, 0xfb, 0xd2,
|
||||
0xb7, 0xaa, 0x30, 0xfa, 0x4f, 0x5e, 0x48, 0x77,
|
||||
0x5b, 0x17, 0xdc, 0x75, 0xef, 0x6f, 0xd2, 0x19,
|
||||
0x6d, 0xdc, 0xbe, 0x7f, 0xb0, 0x78, 0x8f, 0xdc,
|
||||
0x82, 0x60, 0x4c, 0xbf, 0xe4, 0x29, 0x06, 0x5e,
|
||||
0x69, 0x8c, 0x39, 0x13, 0xad, 0x14, 0x25, 0xed,
|
||||
0x19, 0xb2, 0xf2, 0x9f, 0x01, 0x82, 0x0d, 0x56,
|
||||
0x44, 0x88, 0xc8, 0x35, 0xec, 0x1f, 0x11, 0xb3,
|
||||
0x24, 0xe0, 0x59, 0x0d, 0x37, 0xe4, 0x47, 0x3c,
|
||||
0xea, 0x4b, 0x7f, 0x97, 0x31, 0x1c, 0x81, 0x7c,
|
||||
0x94, 0x8a, 0x4c, 0x7d, 0x68, 0x15, 0x84, 0xff,
|
||||
0xa5, 0x08, 0xfd, 0x18, 0xe7, 0xe7, 0x2b, 0xe4,
|
||||
0x47, 0x27, 0x12, 0x11, 0xb8, 0x23, 0xec, 0x58,
|
||||
0x93, 0x3c, 0xac, 0x12, 0xd2, 0x88, 0x6d, 0x41,
|
||||
0x3d, 0xc5, 0xfe, 0x1c, 0xdc, 0xb9, 0xf8, 0xd4,
|
||||
0x51, 0x3e, 0x07, 0xe5, 0x03, 0x6f, 0xa7, 0x12,
|
||||
0xe8, 0x12, 0xf7, 0xb5, 0xce, 0xa6, 0x96, 0x55,
|
||||
0x3f, 0x78, 0xb4, 0x64, 0x82, 0x50, 0xd2, 0x33,
|
||||
0x5f, 0x91, 0x02, 0x03, 0x01, 0x00, 0x01, 0x12,
|
||||
0x80, 0x03, 0x58, 0xf1, 0xd6, 0x4d, 0x04, 0x09,
|
||||
0x7b, 0xdf, 0xd7, 0xef, 0x5d, 0x3b, 0x02, 0x39,
|
||||
0x17, 0xfa, 0x14, 0x36, 0x75, 0x4a, 0x38, 0x67,
|
||||
0x85, 0x57, 0x12, 0xa7, 0x14, 0xee, 0x35, 0x16,
|
||||
0xd5, 0x3d, 0xbf, 0x42, 0x86, 0xf6, 0x69, 0x00,
|
||||
0x76, 0xcd, 0x93, 0xf4, 0x7c, 0xb2, 0xdf, 0x9e,
|
||||
0x44, 0xcd, 0x4c, 0xd4, 0xae, 0x09, 0x18, 0x53,
|
||||
0x44, 0x32, 0xec, 0xe0, 0x61, 0x1b, 0xe5, 0xda,
|
||||
0x13, 0xd3, 0x55, 0xc5, 0xdd, 0x1a, 0xcb, 0x90,
|
||||
0x1e, 0x7e, 0x5b, 0xc6, 0xe9, 0x0f, 0x22, 0x9f,
|
||||
0xbe, 0x85, 0x02, 0xfe, 0x90, 0x31, 0xcc, 0x6b,
|
||||
0x03, 0x84, 0xbd, 0x22, 0xc4, 0x55, 0xfa, 0xf5,
|
||||
0xf2, 0x08, 0xcd, 0x65, 0x41, 0x58, 0xe8, 0x7d,
|
||||
0x29, 0xda, 0x04, 0x58, 0x82, 0xf5, 0x37, 0x69,
|
||||
0xbc, 0xf3, 0x5a, 0x57, 0x84, 0x17, 0x7b, 0x32,
|
||||
0x87, 0x70, 0xb2, 0xb0, 0x76, 0x9c, 0xb2, 0xc3,
|
||||
0x15, 0xd1, 0x11, 0x26, 0x2a, 0x23, 0x75, 0x99,
|
||||
0x3e, 0xb9, 0x77, 0x22, 0x32, 0x0d, 0xbc, 0x1a,
|
||||
0x19, 0xc1, 0xd5, 0x65, 0x90, 0x76, 0x55, 0x74,
|
||||
0x0f, 0x0e, 0x69, 0x4d, 0x5f, 0x4d, 0x8f, 0x19,
|
||||
0xaf, 0xdf, 0xd6, 0x16, 0x31, 0x94, 0xa8, 0x92,
|
||||
0x5f, 0x4f, 0xbc, 0x7a, 0x31, 0xf8, 0xae, 0x8e,
|
||||
0xad, 0x33, 0xb7, 0xe9, 0x30, 0xd0, 0x8c, 0x0a,
|
||||
0x8a, 0x6c, 0x83, 0x35, 0xf8, 0x8a, 0x81, 0xb2,
|
||||
0xfe, 0x1c, 0x88, 0xac, 0x2a, 0x66, 0xc5, 0xff,
|
||||
0xbd, 0xe6, 0x17, 0xd0, 0x62, 0x0b, 0xdc, 0x8a,
|
||||
0x45, 0xf7, 0xb0, 0x3e, 0x5a, 0xc8, 0x1e, 0x4a,
|
||||
0x24, 0x2f, 0x6c, 0xa5, 0xe3, 0x1c, 0x88, 0x14,
|
||||
0x83, 0xd5, 0xc5, 0xef, 0x5e, 0x9f, 0x3d, 0x85,
|
||||
0x45, 0x73, 0xe2, 0x6b, 0x50, 0x52, 0x57, 0x4c,
|
||||
0xfb, 0x92, 0x6c, 0x66, 0x75, 0x8a, 0xd6, 0x0d,
|
||||
0x1b, 0xae, 0xf3, 0xec, 0xaf, 0x51, 0x22, 0x03,
|
||||
0x5d, 0x0a, 0x2e, 0x63, 0x93, 0x9c, 0x0b, 0x01,
|
||||
0x20, 0xa8, 0xa9, 0x84, 0x2e, 0x17, 0xca, 0xae,
|
||||
0x73, 0xec, 0x22, 0x1b, 0x79, 0xae, 0xf6, 0xa0,
|
||||
0x72, 0x2c, 0xdf, 0x07, 0x47, 0xdb, 0x88, 0x86,
|
||||
0x30, 0x14, 0x78, 0x21, 0x11, 0x22, 0x88, 0xac,
|
||||
0xd7, 0x54, 0x74, 0xf9, 0xf3, 0x26, 0xc2, 0xa5,
|
||||
0x56, 0xc8, 0x56, 0x4f, 0x00, 0x29, 0x1d, 0x08,
|
||||
0x7b, 0x7a, 0xfb, 0x95, 0x89, 0xc3, 0xee, 0x98,
|
||||
0x54, 0x9e, 0x3c, 0x6b, 0x94, 0x05, 0x13, 0x12,
|
||||
0xf6, 0x71, 0xb9, 0xab, 0x13, 0xc3, 0x0c, 0x9b,
|
||||
0x46, 0x08, 0x7b, 0x3d, 0x32, 0x6a, 0x68, 0xca,
|
||||
0x1e, 0x9c, 0x90, 0x62, 0xc5, 0xed, 0x10, 0xb9,
|
||||
0x1f, 0x17, 0x25, 0xce, 0x90, 0xb9, 0x6d, 0xcd,
|
||||
0xc4, 0x46, 0xf5, 0xa3, 0x62, 0x13, 0x74, 0x02,
|
||||
0xa7, 0x62, 0xa4, 0xfa, 0x55, 0xd9, 0xde, 0xcf,
|
||||
0xa2, 0xe6, 0x80, 0x74, 0x55, 0x06, 0x49, 0xd5,
|
||||
0x02, 0x0c
|
||||
};
|
||||
// clang-format on
|
||||
constexpr size_t kRootCertForProdSize = sizeof(kRootCertForProd);
|
||||
|
||||
constexpr size_t KEY_IV_SIZE = 16;
|
||||
constexpr size_t SERVICE_KEY_SIZE = 16;
|
||||
|
||||
unsigned int GetDeviceRandomSeed() {
|
||||
static std::random_device rdev;
|
||||
return rdev();
|
||||
}
|
||||
|
||||
std::string RandomData(size_t length) {
|
||||
std::uniform_int_distribution<short> dist(0, 255); // Range of [0, 255].
|
||||
std::string random_data(length, '\0');
|
||||
std::default_random_engine generator(GetDeviceRandomSeed());
|
||||
std::generate(random_data.begin(), random_data.end(),
|
||||
[&]() { return static_cast<char>(dist(generator)); });
|
||||
return random_data;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
// Protobuf generated classes.
|
||||
using video_widevine::ClientIdentification;
|
||||
using video_widevine::DrmCertificate;
|
||||
using video_widevine::EncryptedClientIdentification;
|
||||
using video_widevine::LicenseError;
|
||||
using video_widevine::SignedDrmCertificate;
|
||||
using video_widevine::SignedMessage;
|
||||
|
||||
CasStatus ServiceCertificate::Init(const std::string& signed_certificate) {
|
||||
const std::string root_cert_str(kRootCertForProd,
|
||||
kRootCertForProd + kRootCertForProdSize);
|
||||
|
||||
// Load root cert public key. Don't bother verifying it.
|
||||
SignedDrmCertificate signed_root_cert;
|
||||
if (!signed_root_cert.ParseFromString(root_cert_str)) {
|
||||
LOGE("Failed to deserialize signed root certificate");
|
||||
return CasStatus(CasStatusCode::kDeviceCertificateError,
|
||||
"Failed to deserialize signed root certificate");
|
||||
}
|
||||
video_widevine::DrmCertificate root_cert;
|
||||
if (!root_cert.ParseFromString(signed_root_cert.drm_certificate())) {
|
||||
LOGE("Failed to deserialize root certificate");
|
||||
return CasStatus(CasStatusCode::kDeviceCertificateError,
|
||||
"Failed to deserialize root certificate");
|
||||
}
|
||||
RsaPublicKey root_key;
|
||||
if (!root_key.Init(root_cert.public_key())) {
|
||||
LOGE("Failed to load root certificate public key");
|
||||
return CasStatus(CasStatusCode::kDeviceCertificateError,
|
||||
"Failed to load root certificate public key");
|
||||
}
|
||||
|
||||
// Load the provided service certificate.
|
||||
// First, parse it and verify its signature.
|
||||
SignedDrmCertificate signed_service_cert;
|
||||
if (!signed_service_cert.ParseFromString(signed_certificate)) {
|
||||
LOGE("Failed to parse signed service certificate");
|
||||
return CasStatus(CasStatusCode::kDeviceCertificateError,
|
||||
"Failed to parse signed service certificate");
|
||||
}
|
||||
|
||||
if (!root_key.VerifySignature(signed_service_cert.drm_certificate(),
|
||||
signed_service_cert.signature())) {
|
||||
LOGE("Failed to verify service certificate signature");
|
||||
return CasStatus(CasStatusCode::kDeviceCertificateError,
|
||||
"Failed to verify service certificate signature");
|
||||
}
|
||||
|
||||
video_widevine::DrmCertificate service_cert;
|
||||
if (!service_cert.ParseFromString(signed_service_cert.drm_certificate())) {
|
||||
LOGE("Failed to parse service certificate");
|
||||
return CasStatus(CasStatusCode::kDeviceCertificateError,
|
||||
"Failed to parse service certificate");
|
||||
}
|
||||
if (service_cert.type() != video_widevine::DrmCertificate_Type_SERVICE) {
|
||||
LOGE(
|
||||
"DRM device certificate type is not a service certificate: "
|
||||
"type = %d, expected_type = %d",
|
||||
static_cast<int>(service_cert.type()),
|
||||
static_cast<int>(video_widevine::DrmCertificate_Type_SERVICE));
|
||||
return CasStatus(
|
||||
CasStatusCode::kDeviceCertificateError,
|
||||
"DRM device certificate type is not a service certificate");
|
||||
}
|
||||
|
||||
// Service certificate passes all checks - set up its RSA public key.
|
||||
public_key_ = std::make_unique<RsaPublicKey>();
|
||||
if (!public_key_->Init(service_cert.public_key())) {
|
||||
public_key_.reset();
|
||||
LOGE("Failed to load service certificate public key");
|
||||
return CasStatus(CasStatusCode::kDeviceCertificateError,
|
||||
"Failed to load service certificate public key");
|
||||
}
|
||||
|
||||
// Have service certificate and its public key - keep relevant fields.
|
||||
signed_certificate_ = signed_certificate;
|
||||
serial_number_ = service_cert.serial_number();
|
||||
provider_id_ = service_cert.provider_id();
|
||||
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus ServiceCertificate::EncryptRsaOaep(const std::string& plaintext,
|
||||
std::string* ciphertext) const {
|
||||
if (!public_key_) {
|
||||
LOGE("Service certificate not set");
|
||||
return CasStatus(CasStatusCode::kDeviceCertificateError,
|
||||
"Service certificate not set");
|
||||
}
|
||||
|
||||
if (!public_key_->Encrypt(plaintext, ciphertext)) {
|
||||
return CasStatus(CasStatusCode::kDeviceCertificateError,
|
||||
"CLIENT_ID_RSA_ENCRYPT_ERROR");
|
||||
}
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
CasStatus ServiceCertificate::EncryptClientId(
|
||||
const ClientIdentification* clear_client_id,
|
||||
EncryptedClientIdentification* encrypted_client_id) const {
|
||||
encrypted_client_id->set_provider_id(provider_id_);
|
||||
encrypted_client_id->set_service_certificate_serial_number(serial_number_);
|
||||
|
||||
std::string iv = RandomData(KEY_IV_SIZE);
|
||||
std::string key = RandomData(SERVICE_KEY_SIZE);
|
||||
if (key.length() != SERVICE_KEY_SIZE || iv.length() != KEY_IV_SIZE) {
|
||||
LOGE("RandomData failed for key or iv.");
|
||||
return CasStatus(CasStatusCode::kClientIdEncryptionError,
|
||||
"RandomData failed for key or iv.");
|
||||
}
|
||||
std::string id, enc_id, enc_key;
|
||||
clear_client_id->SerializeToString(&id);
|
||||
|
||||
AesCbcKey aes;
|
||||
if (!aes.Init(key)) {
|
||||
return CasStatus(CasStatusCode::kClientIdEncryptionError,
|
||||
"CLIENT_ID_AES_INIT_ERROR");
|
||||
}
|
||||
if (!aes.Encrypt(id, iv, &enc_id)) {
|
||||
return CasStatus(CasStatusCode::kClientIdEncryptionError,
|
||||
"CLIENT_ID_AES_ENCRYPT_ERROR");
|
||||
}
|
||||
|
||||
CasStatus encrypt_result = EncryptRsaOaep(key, &enc_key);
|
||||
if (!encrypt_result.ok()) {
|
||||
return encrypt_result;
|
||||
}
|
||||
|
||||
encrypted_client_id->set_encrypted_client_id_iv(iv);
|
||||
encrypted_client_id->set_encrypted_privacy_key(enc_key);
|
||||
encrypted_client_id->set_encrypted_client_id(enc_id);
|
||||
return CasStatusCode::kNoError;
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "cas_events.h"
|
||||
#include "cas_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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user