Merge changes I7d760136,Id45d40d9,Ib2f6ef61,I51a118ce into vic-widevine-dev
* changes: Use std::move for key strings. Changed Prov4.0 handler to accept only recent requests. Separate OEM unprovisioning from DRM unprovisioing. Added state to CertificateProvisioning.
This commit is contained in:
@@ -212,6 +212,15 @@ class CdmEngine {
|
||||
// system. This will force the device to reprovision itself.
|
||||
virtual CdmResponseType Unprovision(CdmSecurityLevel security_level);
|
||||
|
||||
// Remove the system's REE-side OEM certificate for the specified
|
||||
// |security_level|.
|
||||
// Only effects two-stage provisioning devices which have an OEM cert
|
||||
// in the REE side file system.
|
||||
// Removing the OEM certificate will cause all DRM certificates tied to
|
||||
// the OEM certificate to be invalidated and unloadable to future
|
||||
// sessions.
|
||||
virtual CdmResponseType UnprovisionOemCert(CdmSecurityLevel security_level);
|
||||
|
||||
// Return the list of key_set_ids stored on the current (origin-specific)
|
||||
// file system.
|
||||
virtual CdmResponseType ListStoredLicenses(
|
||||
|
||||
@@ -72,6 +72,28 @@ class CertificateProvisioning {
|
||||
// |default_url| by GetProvisioningRequest().
|
||||
static void GetProvisioningServerUrl(std::string* default_url);
|
||||
|
||||
enum State {
|
||||
// Freshly created, not yet initialized.
|
||||
kUninitialized,
|
||||
// A successful call to Init() has been made.
|
||||
kInitialized,
|
||||
// Has generated a DRM request; apps are allowed generate
|
||||
// another one even if a response has not been received.
|
||||
kDrmRequestSent,
|
||||
// Has received (and successfully loaded) a DRM response.
|
||||
kDrmResponseReceived,
|
||||
// Has generated an OEM (Prov 4.0) request; apps are allowed
|
||||
// generate another one even if a response has not been
|
||||
// received.
|
||||
kOemRequestSent,
|
||||
// Has received (and successfully loaded) an OEM response.
|
||||
kOemResponseReceived,
|
||||
};
|
||||
static const char* StateToString(State state);
|
||||
|
||||
// State setter for testing only.
|
||||
void SetStateForTesting(State state) { state_ = state; }
|
||||
|
||||
private:
|
||||
#if defined(UNIT_TEST)
|
||||
friend class CertificateProvisioningTest;
|
||||
@@ -122,22 +144,31 @@ class CertificateProvisioning {
|
||||
CdmResponseType CloseSessionOnError(CdmResponseType status);
|
||||
void CloseSession();
|
||||
|
||||
// Tracks the state of CertificateProvisioning.
|
||||
State state_ = kUninitialized;
|
||||
|
||||
std::unique_ptr<CryptoSession> crypto_session_;
|
||||
CdmCertificateType cert_type_;
|
||||
std::unique_ptr<ServiceCertificate> service_certificate_;
|
||||
std::string request_;
|
||||
|
||||
// == Provisioning 4.0 Variables ==
|
||||
// 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_;
|
||||
// Store the last provisioning request message
|
||||
std::string provisioning_request_message_;
|
||||
CryptoWrappedKey prov40_wrapped_private_key_;
|
||||
// Cache of the most recently sent OEM/DRM public key sent. Used
|
||||
// to match the response with the request.
|
||||
// This MUST be matched with the current |prov40_wrapped_private_key_|.
|
||||
std::string prov40_public_key_;
|
||||
|
||||
// Store the last provisioning request message.
|
||||
// This is the serialized ProvisioningRequest.
|
||||
// Used for X.509 responses which require the original
|
||||
// request to verify the signature of the response.
|
||||
std::string prov40_request_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(CertificateProvisioning);
|
||||
};
|
||||
|
||||
}; // class CertificateProvisioning
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_CERTIFICATE_PROVISIONING_H_
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#define WVCDM_CORE_CRYPTO_WRAPPED_KEY_H_
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
@@ -18,6 +19,8 @@ class CryptoWrappedKey {
|
||||
CryptoWrappedKey() {}
|
||||
CryptoWrappedKey(Type type, const std::string& key)
|
||||
: type_(type), key_(key) {}
|
||||
CryptoWrappedKey(Type type, std::string&& key)
|
||||
: type_(type), key_(std::move(key)) {}
|
||||
|
||||
Type type() const { return type_; }
|
||||
void set_type(Type type) { type_ = type; }
|
||||
@@ -26,6 +29,7 @@ class CryptoWrappedKey {
|
||||
// Mutable reference getter for passing to OMECrypto.
|
||||
std::string& key() { return key_; }
|
||||
void set_key(const std::string& key) { key_ = key; }
|
||||
void set_key(std::string&& key) { key_ = std::move(key); }
|
||||
|
||||
void Clear() {
|
||||
type_ = kUninitialized;
|
||||
|
||||
@@ -465,6 +465,9 @@ enum CdmResponseEnum : int32_t {
|
||||
GET_DEVICE_INFORMATION_ERROR = 398,
|
||||
GET_DEVICE_SIGNED_CSR_PAYLOAD_ERROR = 399,
|
||||
GET_TOKEN_FROM_EMBEDDED_CERT_ERROR = 400,
|
||||
PROVISIONING_UNEXPECTED_RESPONSE_ERROR = 402,
|
||||
PROVISIONING_4_STALE_RESPONSE = 403,
|
||||
PROVISIONING_4_FAILED_TO_VERIFY_CERT_KEY = 404,
|
||||
// Don't forget to add new values to
|
||||
// * core/src/wv_cdm_types.cpp
|
||||
// * android/include/mapErrors-inl.h
|
||||
|
||||
@@ -1286,6 +1286,13 @@ CdmResponseType CdmEngine::HandleProvisioningResponse(
|
||||
LOGE("Device has been revoked, cannot provision: status = %s",
|
||||
ret.ToString().c_str());
|
||||
cert_provisioning_.reset();
|
||||
} else if (ret == PROVISIONING_4_STALE_RESPONSE) {
|
||||
// The response is considered "stale" (likely from generating multiple
|
||||
// requests, and providing out of order responses).
|
||||
// Drop message without returning error or resetting
|
||||
// provisioning context.
|
||||
LOGW("Stale response, app may try again");
|
||||
return CdmResponseType(NO_ERROR);
|
||||
} else {
|
||||
// It is possible that a provisioning attempt was made after this one was
|
||||
// requested but before the response was received, which will cause this
|
||||
@@ -1332,8 +1339,7 @@ CdmProvisioningStatus CdmEngine::GetProvisioningStatus(
|
||||
return kUnknownProvisionStatus;
|
||||
}
|
||||
|
||||
UsagePropertySet property_set;
|
||||
if (handle.HasCertificate(property_set.use_atsc_mode())) {
|
||||
if (handle.HasCertificate(/* atsc_mode_enabled = */ false)) {
|
||||
return kProvisioned;
|
||||
}
|
||||
if (crypto_session->GetPreProvisionTokenType() == kClientTokenBootCertChain) {
|
||||
@@ -1356,8 +1362,8 @@ CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) {
|
||||
LOGD("OKP fallback to L3");
|
||||
security_level = kSecurityLevelL3;
|
||||
}
|
||||
// Devices with baked-in DRM certs cannot be reprovisioned and therefore must
|
||||
// not be unprovisioned.
|
||||
// Devices with baked-in DRM certs cannot be reprovisioned
|
||||
// and therefore must not be unprovisioned.
|
||||
std::unique_ptr<CryptoSession> crypto_session(
|
||||
CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics()));
|
||||
CdmClientTokenType token_type = kClientTokenUninitialized;
|
||||
@@ -1376,18 +1382,78 @@ CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) {
|
||||
LOGE("Unable to initialize device files");
|
||||
return CdmResponseType(UNPROVISION_ERROR_1);
|
||||
}
|
||||
|
||||
// TODO(b/141705730): Remove usage entries during unprovisioning.
|
||||
if (!file_system_->IsGlobal()) {
|
||||
if (!handle.RemoveCertificate() || !handle.RemoveOemCertificate()) {
|
||||
LOGE("Unable to delete certificate");
|
||||
// This if statement is misleading. There is no consistent
|
||||
// concept of "global" vs "per-app/origin" storage in the
|
||||
// core library. Android vs CE CDM behave very different.
|
||||
// On CE device:
|
||||
// file_system_->IsGlobal() is always true, even if app/origin
|
||||
// specific.
|
||||
// On Android:
|
||||
// file_system_->IsGlobal() is always false, except for some C++
|
||||
// test code.
|
||||
// TODO(b/142280599): Refactor this once CE CDM SPOIDs are supported
|
||||
// by the file system. May require moving platform-dependent behavior
|
||||
// to the platform-dependent layer. Only have this remove the
|
||||
// certificate and nothing else.
|
||||
if (!file_system_->IsGlobal()) { // AKA is Android
|
||||
// TODO(b/141705730): Remove usage entries during unprovisioning.
|
||||
// Not considered an error if no certificate exists.
|
||||
if (handle.HasCertificate(/* atsc_mode_enabled = */ false) &&
|
||||
!handle.RemoveCertificate()) {
|
||||
LOGE("Unable to delete DRM certificate");
|
||||
return CdmResponseType(UNPROVISION_ERROR_2);
|
||||
}
|
||||
// Maintaining old behavior expected by Android.
|
||||
const CdmResponseType oem_cert_status = UnprovisionOemCert(security_level);
|
||||
if (oem_cert_status != NO_ERROR) return oem_cert_status;
|
||||
} else { // AKA is CE CDM (or some Android tests)
|
||||
// On CE CDM, deleting all files only deletes the app/origin
|
||||
// specific files.
|
||||
// On Android, this will delete all files (only possible
|
||||
// during testing).
|
||||
if (!handle.DeleteAllFiles()) {
|
||||
LOGE("Unable to delete files");
|
||||
return CdmResponseType(UNPROVISION_ERROR_3);
|
||||
}
|
||||
}
|
||||
return CdmResponseType(NO_ERROR);
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::UnprovisionOemCert(CdmSecurityLevel security_level) {
|
||||
LOGI("security_level = %s", CdmSecurityLevelToString(security_level));
|
||||
if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) {
|
||||
LOGD("OKP fallback to L3");
|
||||
security_level = kSecurityLevelL3;
|
||||
}
|
||||
// Only BCC-based system have an OEM certificate that can
|
||||
// unprovisioned.
|
||||
// Prov 3.0 system's OEM certs are built into the TEE.
|
||||
std::unique_ptr<CryptoSession> crypto_session(
|
||||
CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics()));
|
||||
CdmClientTokenType token_type = kClientTokenUninitialized;
|
||||
const CdmResponseType res = crypto_session->GetProvisioningMethod(
|
||||
security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault,
|
||||
&token_type);
|
||||
if (res != NO_ERROR) {
|
||||
return res;
|
||||
}
|
||||
if (token_type != kClientTokenBootCertChain) {
|
||||
LOGD("Device does not support OEM certificate unprovisioning");
|
||||
return CdmResponseType(NO_ERROR);
|
||||
}
|
||||
if (!handle.DeleteAllFiles()) {
|
||||
LOGE("Unable to delete files");
|
||||
return CdmResponseType(UNPROVISION_ERROR_3);
|
||||
// For Prov 4.0 devices, this will cause every app/origin client
|
||||
// to lose their offline content for the same TEE security level.
|
||||
wvutil::FileSystem global_file_system;
|
||||
DeviceFiles global_handle(&global_file_system);
|
||||
if (!global_handle.Init(security_level)) {
|
||||
LOGE("Unable to initialize global device files");
|
||||
return CdmResponseType(UNPROVISION_ERROR_1);
|
||||
}
|
||||
// Not considered an error if no certificate exists.
|
||||
if (global_handle.HasOemCertificate() &&
|
||||
!global_handle.RemoveOemCertificate()) {
|
||||
LOGE("Unable to delete OEM certificate");
|
||||
return CdmResponseType(UNPROVISION_ERROR_2);
|
||||
}
|
||||
return CdmResponseType(NO_ERROR);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// 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 "certificate_provisioning.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "client_identification.h"
|
||||
#include "crypto_wrapped_key.h"
|
||||
#include "device_files.h"
|
||||
@@ -87,6 +88,127 @@ bool RetrieveOemCertificateAndLoadPrivateKey(CryptoSession& crypto_session,
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks if any instances of |needle| sequences found in the |haystack|.
|
||||
//
|
||||
// Special cases:
|
||||
// - An empty |needle| is always present, even if |haystack| is empty.
|
||||
// Note: This is a convention used by many string utility
|
||||
// libraries.
|
||||
bool StringContains(const std::string& haystack, const std::string& needle) {
|
||||
if (needle.empty()) return true;
|
||||
if (haystack.size() < needle.size()) return false;
|
||||
return haystack.find(needle) != std::string::npos;
|
||||
}
|
||||
|
||||
// Checks if the |needle| sequences found at the end of |haystack|.
|
||||
//
|
||||
// Special cases:
|
||||
// - An empty |needle| is always present, even if |haystack| is empty.
|
||||
// Note: This is a convention used by many string utility
|
||||
// libraries.
|
||||
bool StringEndsWith(const std::string& haystack, const std::string& needle) {
|
||||
if (haystack.size() < needle.size()) return false;
|
||||
return std::equal(haystack.rbegin(), haystack.rbegin() + needle.size(),
|
||||
needle.rbegin(), needle.rend());
|
||||
}
|
||||
|
||||
// Checks the actual length of an ASN.1 DER encoded message
|
||||
// roughly matches the expected length from within the message.
|
||||
// Technically, the DER message may contain some trailing
|
||||
// end-of-contents bytes (at most 2).
|
||||
//
|
||||
// Parameters:
|
||||
// |actual_length| - The real length of the DER message
|
||||
// |expected_length| - The reported length of the DER message plus
|
||||
// the header bytes parsed.
|
||||
bool IsAsn1ExpectedLength(size_t actual_length, size_t expected_length) {
|
||||
return actual_length >= expected_length &&
|
||||
actual_length <= (expected_length + 2);
|
||||
}
|
||||
|
||||
// Checks if the provided |message| resembles ASN.1 DER encoded
|
||||
// message.
|
||||
// This is a light check, it verifies the type (SEQUENCE) and that
|
||||
// the encoded length matches the total message length.
|
||||
bool IsAsn1DerSequenceLike(const std::string& message) {
|
||||
// Anything less than 3 bytes will not be an ASN.1 sequence.
|
||||
if (message.size() < 3) return false;
|
||||
// Verify type header
|
||||
// class = universal(0) - bits 6-7
|
||||
// p/c = constructed(1) - bit 5
|
||||
// tag = sequence(0x10) - bits 0-4
|
||||
static constexpr uint8_t kUniversal = (0 << 6);
|
||||
static constexpr uint8_t kConstructBit = (1 << 5);
|
||||
static constexpr uint8_t kSequenceTag = 0x10;
|
||||
static constexpr uint8_t kSequenceHeader =
|
||||
kUniversal | kConstructBit | kSequenceTag;
|
||||
const uint8_t type_header = static_cast<uint8_t>(message.front());
|
||||
if (type_header != kSequenceHeader) return false;
|
||||
|
||||
// Verify length.
|
||||
const uint8_t length_header = static_cast<uint8_t>(message[1]);
|
||||
// A reserved length is never used. If |length_header| is
|
||||
// reserved length, then this is not an ASN.1 message.
|
||||
static constexpr uint8_t kReservedLength = 0xff;
|
||||
if (length_header == kReservedLength) return false;
|
||||
|
||||
static constexpr uint8_t kIndefiniteLength = 0x80;
|
||||
if (length_header == kIndefiniteLength) {
|
||||
// If length is indefinite, then search for two "end of contents"
|
||||
// octets at the end.
|
||||
static constexpr uint8_t kAsnEndOfContents = 0x00;
|
||||
const std::string kDoubleEoc(2, kAsnEndOfContents);
|
||||
return StringEndsWith(message, kDoubleEoc);
|
||||
}
|
||||
|
||||
// Definite lengths may be long or short (most likely long for our case).
|
||||
static constexpr uint8_t kLongLengthBit = 0x80;
|
||||
|
||||
if ((length_header & kLongLengthBit) != kLongLengthBit) {
|
||||
// Short length (unlikely, but check anyways).
|
||||
// For short lengths, the value component of the length
|
||||
// header is the payload length.
|
||||
static constexpr uint8_t kShortLengthMask = 0x7f;
|
||||
const size_t payload_length =
|
||||
static_cast<size_t>(length_header & kShortLengthMask);
|
||||
|
||||
// The total message is: type header + length header + payload.
|
||||
const size_t total_length = 2 + payload_length;
|
||||
return IsAsn1ExpectedLength(message.size(), total_length);
|
||||
}
|
||||
|
||||
// Long length.
|
||||
// |length_header| contains the number of bytes following the
|
||||
// length header containing the payload length.
|
||||
static constexpr uint8_t kLengthSizeMask = 0x7f;
|
||||
const size_t length_length =
|
||||
static_cast<size_t>(length_header & kLengthSizeMask);
|
||||
// For long-lengths, the first two bytes were type header and
|
||||
// length header.
|
||||
static constexpr size_t kPayloadLengthOffset = 2;
|
||||
// If the message is smaller than needed to obtain the length,
|
||||
// it is either not ASN.1 (or an incomplete message, which is still
|
||||
// invalid).
|
||||
if ((message.size()) < (length_length + kPayloadLengthOffset)) return false;
|
||||
// DER encoding should use the minimum number of bytes necessary
|
||||
// to encode the length, and if the number of bytes to encode the
|
||||
// length is more than 3 (payload is larged than 16 MB) which is much
|
||||
// larger than any expected certificate chain.
|
||||
if (length_length > 3) return false;
|
||||
|
||||
// Decode the length as big-endian.
|
||||
size_t payload_length = 0;
|
||||
for (size_t i = 0; i < length_length; i++) {
|
||||
// Casting from char to uint8_t to size_t is necessary.
|
||||
const uint8_t length_byte =
|
||||
static_cast<uint8_t>(message[kPayloadLengthOffset + i]);
|
||||
payload_length = (payload_length << 8) + static_cast<size_t>(length_byte);
|
||||
}
|
||||
|
||||
// Total message is: type header + length header + payload length + payload.
|
||||
const size_t total_length = 2 + length_length + payload_length;
|
||||
return IsAsn1ExpectedLength(message.size(), total_length);
|
||||
}
|
||||
} // namespace
|
||||
// Protobuf generated classes.
|
||||
using video_widevine::DrmCertificate;
|
||||
@@ -98,6 +220,25 @@ using video_widevine::PublicKeyToCertify;
|
||||
using video_widevine::SignedDrmCertificate;
|
||||
using video_widevine::SignedProvisioningMessage;
|
||||
|
||||
// static
|
||||
const char* CertificateProvisioning::StateToString(State state) {
|
||||
switch (state) {
|
||||
case kUninitialized:
|
||||
return "Uninitialized";
|
||||
case kInitialized:
|
||||
return "Initialized";
|
||||
case kDrmRequestSent:
|
||||
return "DrmRequestSent";
|
||||
case kDrmResponseReceived:
|
||||
return "DrmResponseReceived";
|
||||
case kOemRequestSent:
|
||||
return "OemRequestSent";
|
||||
case kOemResponseReceived:
|
||||
return "OemResponseReceived";
|
||||
}
|
||||
return "<unknown>";
|
||||
}
|
||||
|
||||
// static
|
||||
void CertificateProvisioning::GetProvisioningServerUrl(
|
||||
std::string* default_url) {
|
||||
@@ -114,7 +255,11 @@ CdmResponseType CertificateProvisioning::Init(
|
||||
service_certificate.empty()
|
||||
? wvutil::a2bs_hex(kCpProductionServiceCertificate)
|
||||
: service_certificate;
|
||||
return service_certificate_->Init(certificate);
|
||||
const CdmResponseType result = service_certificate_->Init(certificate);
|
||||
if (result == NO_ERROR) {
|
||||
state_ = kInitialized;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Fill in the appropriate SPOID (Stable Per-Origin IDentifier) option.
|
||||
@@ -206,11 +351,18 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal(
|
||||
|
||||
default_url->assign(kProvisioningServerUrl);
|
||||
|
||||
if (state_ != kInitialized) {
|
||||
LOGD("Overriding old request: state = %s", StateToString(state_));
|
||||
// Once the previous session is closed, there is no way to complete
|
||||
// an in-flight request.
|
||||
state_ = kInitialized;
|
||||
}
|
||||
|
||||
CloseSession();
|
||||
CdmResponseType status = crypto_session_->Open(requested_security_level);
|
||||
if (NO_ERROR != status) {
|
||||
LOGE("Failed to create a crypto session: status = %d",
|
||||
static_cast<int>(status));
|
||||
LOGE("Failed to create a crypto session: status = %s",
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -299,6 +451,7 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal(
|
||||
} else {
|
||||
*request = std::move(serialized_request);
|
||||
}
|
||||
state_ = kDrmRequestSent;
|
||||
return CdmResponseType(NO_ERROR);
|
||||
}
|
||||
|
||||
@@ -324,7 +477,11 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
|
||||
return CdmResponseType(PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES);
|
||||
}
|
||||
|
||||
ProvisioningRequest provisioning_request;
|
||||
if (!service_certificate_) {
|
||||
LOGE("Service certificate not set");
|
||||
return CdmResponseType(CERT_PROVISIONING_EMPTY_SERVICE_CERTIFICATE);
|
||||
}
|
||||
|
||||
// Determine the current stage by checking if OEM cert exists.
|
||||
std::string stored_oem_cert;
|
||||
if (global_file_handle.HasOemCertificate()) {
|
||||
@@ -341,9 +498,11 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
|
||||
}
|
||||
}
|
||||
}
|
||||
const bool is_oem_prov_request = stored_oem_cert.empty();
|
||||
|
||||
// Retrieve the Spoid, but put it to the client identification instead, so it
|
||||
// is encrypted.
|
||||
ProvisioningRequest provisioning_request;
|
||||
CdmAppParameterMap additional_parameter;
|
||||
CdmResponseType status =
|
||||
SetSpoidParameter(origin, spoid, &provisioning_request);
|
||||
@@ -363,7 +522,7 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
|
||||
provisioning_request.clear_stable_id();
|
||||
}
|
||||
|
||||
if (stored_oem_cert.empty()) {
|
||||
if (is_oem_prov_request) {
|
||||
// This is the first stage provisioning.
|
||||
default_url->assign(std::string(kProvisioningServerUrl) +
|
||||
kProv40FirstStageServerUrlSuffix);
|
||||
@@ -377,8 +536,8 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
|
||||
|
||||
// Since |stored_oem_cert| is empty, the client identification token will be
|
||||
// retrieved from OEMCrypto, which is the BCC in this case.
|
||||
status = FillEncryptedClientId(stored_oem_cert, provisioning_request,
|
||||
wv_service_cert);
|
||||
status = FillEncryptedClientId(/* client_token = */ std::string(),
|
||||
provisioning_request, wv_service_cert);
|
||||
if (status != NO_ERROR) return status;
|
||||
} else {
|
||||
// This is the second stage provisioning.
|
||||
@@ -416,25 +575,24 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
|
||||
|
||||
std::string public_key;
|
||||
std::string public_key_signature;
|
||||
provisioning_40_wrapped_private_key_.clear();
|
||||
provisioning_40_key_type_ = CryptoWrappedKey::kUninitialized;
|
||||
std::string wrapped_private_key;
|
||||
CryptoWrappedKey::Type private_key_type = CryptoWrappedKey::kUninitialized;
|
||||
status = crypto_session_->GenerateCertificateKeyPair(
|
||||
&public_key, &public_key_signature, &provisioning_40_wrapped_private_key_,
|
||||
&provisioning_40_key_type_);
|
||||
&public_key, &public_key_signature, &wrapped_private_key,
|
||||
&private_key_type);
|
||||
if (status != NO_ERROR) 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
|
||||
key_to_certify->set_key_type(private_key_type == CryptoWrappedKey::kRsa
|
||||
? PublicKeyToCertify::RSA
|
||||
: PublicKeyToCertify::ECC);
|
||||
|
||||
std::string serialized_message;
|
||||
provisioning_request.SerializeToString(&serialized_message);
|
||||
provisioning_request_message_ = serialized_message;
|
||||
prov40_request_ = serialized_message;
|
||||
|
||||
SignedProvisioningMessage signed_provisioning_msg;
|
||||
signed_provisioning_msg.set_message(serialized_message);
|
||||
@@ -490,6 +648,15 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
|
||||
*request = std::move(serialized_request);
|
||||
}
|
||||
request_ = std::move(serialized_message);
|
||||
// Need the wrapped Prov 4.0 private key to store once the response
|
||||
// is received. The wrapped key is not available in the response.
|
||||
prov40_wrapped_private_key_ =
|
||||
CryptoWrappedKey(private_key_type, std::move(wrapped_private_key));
|
||||
// Store the public key from the request. This is used to match
|
||||
// up the response with the most recently generated request.
|
||||
prov40_public_key_ = std::move(public_key);
|
||||
|
||||
state_ = is_oem_prov_request ? kOemRequestSent : kDrmRequestSent;
|
||||
return CdmResponseType(NO_ERROR);
|
||||
}
|
||||
|
||||
@@ -552,6 +719,18 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
|
||||
return CdmResponseType(PROVISIONING_4_RESPONSE_HAS_ERROR_STATUS);
|
||||
}
|
||||
}
|
||||
if (state_ == kOemResponseReceived || state_ == kDrmResponseReceived) {
|
||||
// A response has already been received (successfully), this
|
||||
// response can be silently dropped.
|
||||
LOGW("Response already received: state = %s", StateToString(state_));
|
||||
return CdmResponseType(NO_ERROR);
|
||||
}
|
||||
if (state_ != kOemRequestSent && state_ != kDrmRequestSent) {
|
||||
LOGE("Not expecting a response: state = %s", StateToString(state_));
|
||||
return CdmResponseType(PROVISIONING_UNEXPECTED_RESPONSE_ERROR);
|
||||
}
|
||||
LOGD("Handling response: state = %s", StateToString(state_));
|
||||
const bool is_oem_prov_response = (state_ == kOemRequestSent);
|
||||
|
||||
const std::string& device_certificate =
|
||||
provisioning_response.device_certificate();
|
||||
@@ -560,17 +739,16 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
|
||||
return CdmResponseType(PROVISIONING_4_RESPONSE_HAS_NO_CERTIFICATE);
|
||||
}
|
||||
|
||||
if (provisioning_40_wrapped_private_key_.empty()) {
|
||||
LOGE("No private key was generated");
|
||||
if (!prov40_wrapped_private_key_.IsValid() || prov40_public_key_.empty()) {
|
||||
LOGE("No %s key was generated",
|
||||
!prov40_wrapped_private_key_.IsValid() ? "private" : "public");
|
||||
return CdmResponseType(PROVISIONING_4_NO_PRIVATE_KEY);
|
||||
}
|
||||
|
||||
const CryptoWrappedKey private_key(provisioning_40_key_type_,
|
||||
provisioning_40_wrapped_private_key_);
|
||||
|
||||
if (cert_type_ == kCertificateX509) {
|
||||
// Load csr private key to decrypt session key
|
||||
auto status = crypto_session_->LoadCertificatePrivateKey(private_key);
|
||||
auto status =
|
||||
crypto_session_->LoadCertificatePrivateKey(prov40_wrapped_private_key_);
|
||||
if (status != NO_ERROR) {
|
||||
LOGE("Failed to load x509 certificate.");
|
||||
return status;
|
||||
@@ -581,9 +759,8 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
|
||||
const std::string& signature = signed_response.signature();
|
||||
const std::string& core_message = signed_response.oemcrypto_core_message();
|
||||
status = crypto_session_->LoadProvisioningCast(
|
||||
signed_response.session_key(), provisioning_request_message_,
|
||||
response_message, core_message, signature,
|
||||
&cast_cert_private_key.key());
|
||||
signed_response.session_key(), prov40_request_, response_message,
|
||||
core_message, signature, &cast_cert_private_key.key());
|
||||
if (status != NO_ERROR) {
|
||||
LOGE("Failed to generate wrapped key for cast cert.");
|
||||
return status;
|
||||
@@ -593,11 +770,79 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
|
||||
|
||||
*cert = device_certificate;
|
||||
*wrapped_key = cast_cert_private_key.key();
|
||||
state_ = is_oem_prov_response ? kOemResponseReceived : kDrmResponseReceived;
|
||||
prov40_wrapped_private_key_.Clear();
|
||||
prov40_public_key_.clear();
|
||||
return CdmResponseType(NO_ERROR);
|
||||
}
|
||||
|
||||
// Verify that the response contains the same key as the request.
|
||||
// It is possible that multiple requests were generated, the CDM
|
||||
// can only accept the response from the most recently generated
|
||||
// one.
|
||||
//
|
||||
// Check the first few bytes to determine the type of message.
|
||||
// OEM responses:
|
||||
// ASN.1 DER encoded ContentInfo (containing an X.509 certificate).
|
||||
// DRM responses:
|
||||
// Protobuf SignedDrmCertificate
|
||||
if (is_oem_prov_response) {
|
||||
// Here |device_certificate| (haystack) is an X.509 cert chain, and
|
||||
// |prov40_public_key_| (needle) is a SubjectPublicKeyInfo.
|
||||
// The cert chain should contain a byte-for-byte copy of the
|
||||
// public key.
|
||||
// TODO(b/391469176): Use RSA/ECC key loading to detected mismatched
|
||||
// keys.
|
||||
if (!StringContains(/* haystack = */ device_certificate,
|
||||
/* needle */ prov40_public_key_)) {
|
||||
LOGD("OEM response is stale");
|
||||
return CdmResponseType(PROVISIONING_4_STALE_RESPONSE);
|
||||
}
|
||||
} else { // Is DRM response
|
||||
video_widevine::SignedDrmCertificate signed_certificate;
|
||||
if (!signed_certificate.ParseFromString(device_certificate)) {
|
||||
// Check if ASN.1 like.
|
||||
if (IsAsn1DerSequenceLike(device_certificate)) {
|
||||
// This might be a late OEM certificate response
|
||||
// generated from before the DRM response was received.
|
||||
LOGD("Received late OEM certificate response");
|
||||
return CdmResponseType(PROVISIONING_4_STALE_RESPONSE);
|
||||
}
|
||||
LOGE("Unable to parse Signed DRM certificate");
|
||||
return CdmResponseType(PROVISIONING_4_FAILED_TO_VERIFY_CERT_KEY);
|
||||
}
|
||||
video_widevine::DrmCertificate drm_certificate;
|
||||
if (!drm_certificate.ParseFromString(
|
||||
signed_certificate.drm_certificate())) {
|
||||
LOGE("Unable to parse DRM certificate");
|
||||
return CdmResponseType(PROVISIONING_4_FAILED_TO_VERIFY_CERT_KEY);
|
||||
}
|
||||
// The sent public key is of the format SubjectPublicKeyInfo;
|
||||
// however, the received format is RSAPublicKey (RSA only) or
|
||||
// SubjectPublicKeyInfo (ECC, and future RSA).
|
||||
// Here |prov40_public_key_| (haystack) is SubjectPublicKeyInfo,
|
||||
// and |drm_certificate.public_key()| (needle) may be
|
||||
// SubjectPublicKeyInfo or RSAPublicKey.
|
||||
// If the DRM cert's public key is in SubjectPublicKeyInfo format
|
||||
// it should be a byte-for-byte copy. If the DRM cert's public key
|
||||
// is RSAPublicKey format then hopefully a byte-for-byte copy is
|
||||
// found within the SubjectPublicKeyInfo. Note: SubjectPublicKeyInfo
|
||||
// containing an RSA public key uses RSAPublicKey to store the
|
||||
// key fields.
|
||||
// TODO(b/391469176): Use RSA/ECC key loading to detected mismatched
|
||||
// keys.
|
||||
if (!StringContains(/* haystack = */ prov40_public_key_,
|
||||
/* needle = */ drm_certificate.public_key())) {
|
||||
// This might be a response from a previously generated DRM
|
||||
// certificate response.
|
||||
LOGD("DRM response is stale");
|
||||
return CdmResponseType(PROVISIONING_4_STALE_RESPONSE);
|
||||
}
|
||||
}
|
||||
// Can clear the |prov40_public_key_| after validating.
|
||||
prov40_public_key_.clear();
|
||||
|
||||
const CdmSecurityLevel security_level = crypto_session_->GetSecurityLevel();
|
||||
CloseSession();
|
||||
wvutil::FileSystem global_file_system;
|
||||
DeviceFiles global_file_handle(&global_file_system);
|
||||
if (!global_file_handle.Init(security_level)) {
|
||||
@@ -607,28 +852,45 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
|
||||
|
||||
// Check the stage of the provisioning by checking if an OEM cert is already
|
||||
// stored in the file system.
|
||||
if (!global_file_handle.HasOemCertificate()) {
|
||||
if (is_oem_prov_response) {
|
||||
if (global_file_handle.HasOemCertificate()) {
|
||||
// Possible that concurrent apps were generated provisioning
|
||||
// requests, and this one arrived after an other one.
|
||||
LOGI("CDM has already received an OEM certificate");
|
||||
CloseSession();
|
||||
state_ = kOemResponseReceived;
|
||||
prov40_wrapped_private_key_.Clear();
|
||||
prov40_public_key_.clear();
|
||||
return CdmResponseType(NO_ERROR);
|
||||
}
|
||||
|
||||
// No OEM cert already stored => the response is expected to be an OEM cert.
|
||||
if (!global_file_handle.StoreOemCertificate(device_certificate,
|
||||
private_key)) {
|
||||
prov40_wrapped_private_key_)) {
|
||||
LOGE("Failed to store provisioning 4 OEM certificate");
|
||||
return CdmResponseType(PROVISIONING_4_FAILED_TO_STORE_OEM_CERTIFICATE);
|
||||
}
|
||||
} else {
|
||||
// The response is assumed to be an DRM cert.
|
||||
DeviceFiles per_origin_file_handle(file_system);
|
||||
if (!per_origin_file_handle.Init(security_level)) {
|
||||
LOGE("Failed to initialize per-origin DeviceFiles");
|
||||
return CdmResponseType(
|
||||
PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES_3);
|
||||
}
|
||||
if (!per_origin_file_handle.StoreCertificate(device_certificate,
|
||||
private_key)) {
|
||||
LOGE("Failed to store provisioning 4 DRM certificate");
|
||||
return CdmResponseType(PROVISIONING_4_FAILED_TO_STORE_DRM_CERTIFICATE);
|
||||
}
|
||||
CloseSession();
|
||||
state_ = kOemResponseReceived;
|
||||
prov40_wrapped_private_key_.Clear();
|
||||
prov40_public_key_.clear();
|
||||
return CdmResponseType(NO_ERROR);
|
||||
}
|
||||
|
||||
// The response is assumed to be a DRM cert.
|
||||
DeviceFiles per_origin_file_handle(file_system);
|
||||
if (!per_origin_file_handle.Init(security_level)) {
|
||||
LOGE("Failed to initialize per-origin DeviceFiles");
|
||||
return CdmResponseType(PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES_3);
|
||||
}
|
||||
if (!per_origin_file_handle.StoreCertificate(device_certificate,
|
||||
prov40_wrapped_private_key_)) {
|
||||
LOGE("Failed to store provisioning 4 DRM certificate");
|
||||
return CdmResponseType(PROVISIONING_4_FAILED_TO_STORE_DRM_CERTIFICATE);
|
||||
}
|
||||
CloseSession();
|
||||
state_ = kDrmResponseReceived;
|
||||
prov40_wrapped_private_key_.Clear();
|
||||
prov40_public_key_.clear();
|
||||
return CdmResponseType(NO_ERROR);
|
||||
}
|
||||
|
||||
@@ -678,6 +940,15 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
|
||||
wrapped_key);
|
||||
}
|
||||
|
||||
if (state_ == kDrmResponseReceived) {
|
||||
LOGD("Response already received");
|
||||
return CdmResponseType(NO_ERROR);
|
||||
}
|
||||
if (state_ != kDrmRequestSent) {
|
||||
LOGE("Not expecting a response: state = %s", StateToString(state_));
|
||||
return CdmResponseType(PROVISIONING_UNEXPECTED_RESPONSE_ERROR);
|
||||
}
|
||||
|
||||
bool error = false;
|
||||
if (!signed_response.has_signature()) {
|
||||
LOGE("Signed response does not have signature");
|
||||
@@ -753,6 +1024,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
|
||||
if (cert_type_ == kCertificateX509) {
|
||||
*cert = device_cert_data;
|
||||
*wrapped_key = private_key.key();
|
||||
state_ = kDrmResponseReceived;
|
||||
return CdmResponseType(NO_ERROR);
|
||||
}
|
||||
|
||||
@@ -799,6 +1071,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
|
||||
return CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_8);
|
||||
}
|
||||
|
||||
state_ = kDrmResponseReceived;
|
||||
return CdmResponseType(NO_ERROR);
|
||||
}
|
||||
|
||||
|
||||
@@ -709,17 +709,26 @@ bool DeviceFiles::RemoveCertificate() {
|
||||
RETURN_FALSE_IF_UNINITIALIZED()
|
||||
|
||||
std::string certificate_file_name;
|
||||
if (GetCertificateFileName(kCertificateLegacy, &certificate_file_name))
|
||||
RemoveFile(certificate_file_name);
|
||||
if (GetCertificateFileName(kCertificateDefault, &certificate_file_name))
|
||||
return RemoveFile(certificate_file_name);
|
||||
return true;
|
||||
// Return true so long as at least one certificate was removed.
|
||||
// This is to compliment the behavior of HasCertificate() which
|
||||
// returns true if at least one certificate exists.
|
||||
bool result = false;
|
||||
if (GetCertificateFileName(kCertificateLegacy, &certificate_file_name)) {
|
||||
LOGI("Removing legacy DRM cert");
|
||||
result |= RemoveFile(certificate_file_name);
|
||||
}
|
||||
if (GetCertificateFileName(kCertificateDefault, &certificate_file_name)) {
|
||||
LOGI("Removing DRM cert");
|
||||
result |= RemoveFile(certificate_file_name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DeviceFiles::RemoveOemCertificate() {
|
||||
RETURN_FALSE_IF_UNINITIALIZED()
|
||||
std::string certificate_file_name;
|
||||
if (GetOemCertificateFileName(&certificate_file_name)) {
|
||||
LOGI("Removing OEM certificate");
|
||||
return RemoveFile(certificate_file_name);
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -879,6 +879,12 @@ const char* CdmResponseEnumToString(CdmResponseEnum cdm_response_enum) {
|
||||
return "SESSION_NOT_FOUND_GENERIC_CRYPTO";
|
||||
case SESSION_NOT_FOUND_24:
|
||||
return "SESSION_NOT_FOUND_24";
|
||||
case PROVISIONING_UNEXPECTED_RESPONSE_ERROR:
|
||||
return "PROVISIONING_UNEXPECTED_RESPONSE_ERROR";
|
||||
case PROVISIONING_4_STALE_RESPONSE:
|
||||
return "PROVISIONING_4_STALE_RESPONSE";
|
||||
case PROVISIONING_4_FAILED_TO_VERIFY_CERT_KEY:
|
||||
return "PROVISIONING_4_FAILED_TO_VERIFY_CERT_KEY";
|
||||
}
|
||||
return UnknownValueRep(cdm_response_enum);
|
||||
}
|
||||
|
||||
@@ -413,6 +413,9 @@ TEST_P(CertificateProvisioningTest, ProvisioningRequestFailsEmptySignature) {
|
||||
TEST_P(CertificateProvisioningTest,
|
||||
ProvisioningResponseFailsWithEmptyResponse) {
|
||||
certificate_provisioning_->Init("");
|
||||
// Must set state if not generating request.
|
||||
certificate_provisioning_->SetStateForTesting(
|
||||
CertificateProvisioning::kDrmRequestSent);
|
||||
|
||||
MockFileSystem file_system;
|
||||
std::string certificate;
|
||||
@@ -425,6 +428,9 @@ TEST_P(CertificateProvisioningTest,
|
||||
TEST_P(CertificateProvisioningTest,
|
||||
ProvisioningResponseFailsIfDeviceIsRevoked) {
|
||||
certificate_provisioning_->Init("");
|
||||
// Must set state if not generating request.
|
||||
certificate_provisioning_->SetStateForTesting(
|
||||
CertificateProvisioning::kDrmRequestSent);
|
||||
|
||||
MockFileSystem file_system;
|
||||
std::string response_certificate;
|
||||
@@ -445,6 +451,10 @@ TEST_P(CertificateProvisioningTest,
|
||||
|
||||
TEST_P(CertificateProvisioningTest, ProvisioningResponseSuccess) {
|
||||
certificate_provisioning_->Init("");
|
||||
// Must set state if not generating request.
|
||||
certificate_provisioning_->SetStateForTesting(
|
||||
CertificateProvisioning::kDrmRequestSent);
|
||||
|
||||
std::string expected_certificate;
|
||||
std::string response;
|
||||
ASSERT_TRUE(MakeSignedDrmCertificate(kFakePublicKey, kSerialNumber, kSystemId,
|
||||
|
||||
@@ -279,4 +279,539 @@ TEST_F(CoreIntegrationTest, NeedKeyBeforeLicenseLoad) {
|
||||
EXPECT_EQ(NEED_KEY, holder.Decrypt(key_id));
|
||||
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
|
||||
}
|
||||
|
||||
class Prov40IntegrationTest : public WvCdmTestBaseWithEngine {
|
||||
public:
|
||||
void SetUp() override {
|
||||
WvCdmTestBaseWithEngine::SetUp();
|
||||
// Ensure CDM is operating using Provisioning 4.0.
|
||||
std::string prov_model;
|
||||
CdmResponseType status = cdm_engine_.QueryStatus(
|
||||
kLevelDefault, QUERY_KEY_PROVISIONING_MODEL, &prov_model);
|
||||
ASSERT_EQ(status, NO_ERROR) << "Failed to determine provisioning model";
|
||||
if (prov_model != QUERY_VALUE_BOOT_CERTIFICATE_CHAIN) {
|
||||
GTEST_SKIP() << "Test is for Prov4.0 only";
|
||||
return;
|
||||
}
|
||||
// Ensure CDM is not provisioned.
|
||||
if (IsProvisioned()) {
|
||||
status = cdm_engine_.Unprovision(kSecurityLevelL1);
|
||||
ASSERT_EQ(status, NO_ERROR) << "Failed to unprovision DRM cert";
|
||||
status = cdm_engine_.UnprovisionOemCert(kSecurityLevelL1);
|
||||
ASSERT_EQ(status, NO_ERROR) << "Failed to unprovision OEM cert";
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsOemCertProvisioning);
|
||||
}
|
||||
}
|
||||
|
||||
CdmProvisioningStatus GetProvisioningStatus() {
|
||||
return cdm_engine_.GetProvisioningStatus(kSecurityLevelL1);
|
||||
}
|
||||
|
||||
bool IsProvisioned() { return cdm_engine_.IsProvisioned(kSecurityLevelL1); }
|
||||
|
||||
void PreDrmProvisioningCheck() {
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsOemCertProvisioning)
|
||||
<< "Not in valid state for pre DRM provisioning check";
|
||||
ProvisioningHolder provisioner(&cdm_engine_, config_);
|
||||
// OEM provisioning.
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.Provision(binary_provisioning_))
|
||||
<< "OEM Certificate provisioning attempt failed";
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsDrmCertProvisioning)
|
||||
<< "OEM Certificate provisioning was not completed";
|
||||
}
|
||||
|
||||
void PostIncompleteOemProvisioningCheck() {
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsOemCertProvisioning)
|
||||
<< "Not in valid state for post incomplete OEM provisioning check";
|
||||
ProvisioningHolder provisioner(&cdm_engine_, config_);
|
||||
// OEM provisioning.
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.Provision(binary_provisioning_))
|
||||
<< "OEM Certificate provisioning attempt failed";
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsDrmCertProvisioning)
|
||||
<< "OEM Certificate provisioning was not completed";
|
||||
// DRM provisioning.
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.Provision(binary_provisioning_))
|
||||
<< "DRM Certificate provisioning attempt failed";
|
||||
ASSERT_EQ(GetProvisioningStatus(), kProvisioned)
|
||||
<< "DRM Certificate provisioning was not completed";
|
||||
// Remaining is the same as post DRM provisioning.
|
||||
ASSERT_NO_FATAL_FAILURE(PostDrmProvisioningCheck())
|
||||
<< "Failed post incomplete OEM provisioning check after DRM "
|
||||
"provisioning";
|
||||
}
|
||||
|
||||
void PostOemProvisioningCheck() {
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsDrmCertProvisioning)
|
||||
<< "Not in valid state for post OEM provisioning check";
|
||||
ProvisioningHolder provisioner(&cdm_engine_, config_);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.Provision(binary_provisioning_))
|
||||
<< "DRM Certificate provisioning failed";
|
||||
ASSERT_EQ(GetProvisioningStatus(), kProvisioned)
|
||||
<< "DRM Certificate provisioning was not completed";
|
||||
// Remaining is the same as post DRM provisioning.
|
||||
ASSERT_NO_FATAL_FAILURE(PostDrmProvisioningCheck())
|
||||
<< "Failed post OEM provisioning check after DRM provisioning";
|
||||
}
|
||||
|
||||
void PostIncompleteDrmProvisioningCheck() {
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsDrmCertProvisioning)
|
||||
<< "Not in valid state for post incomplete DRM provisioning check";
|
||||
ProvisioningHolder provisioner(&cdm_engine_, config_);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.Provision(binary_provisioning_))
|
||||
<< "DRM Certificate provisioning failed";
|
||||
ASSERT_EQ(GetProvisioningStatus(), kProvisioned)
|
||||
<< "DRM Certificate provisioning was not completed";
|
||||
// Remaining is the same as post DRM provisioning.
|
||||
ASSERT_NO_FATAL_FAILURE(PostDrmProvisioningCheck())
|
||||
<< "Failed post incomplete DRM provisioning check after DRM "
|
||||
"provisioning";
|
||||
}
|
||||
|
||||
void PostDrmProvisioningCheck() {
|
||||
ASSERT_EQ(GetProvisioningStatus(), kProvisioned)
|
||||
<< "Not in valid state for post DRM provisioning check";
|
||||
LicenseHolder holder("CDM_Streaming", &cdm_engine_, config_);
|
||||
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.FetchLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.LoadLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
|
||||
}
|
||||
}; // class Prov40IntegrationTest
|
||||
|
||||
// Expected flow of an app; 1 OEM request-response, 1 DRM request-response.
|
||||
//
|
||||
// Case: OemReq1, OemResp1, DrmReq1, DrmResp1
|
||||
//
|
||||
// Notes:
|
||||
// This is Widevine's expected behavior by an app.
|
||||
//
|
||||
// Post-Case: Load license
|
||||
TEST_F(Prov40IntegrationTest, UsualOrder_LoadOem1_LoadDrm1) {
|
||||
ProvisioningHolder provisioner(&cdm_engine_, config_);
|
||||
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsOemCertProvisioning);
|
||||
|
||||
// Round 1 - OEM provisioning (OemReq1, OemResp1).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.Provision(binary_provisioning_))
|
||||
<< "OEM Certificate provisioning failed";
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsDrmCertProvisioning);
|
||||
|
||||
// Round 2 - DRM provisioning (DrmReq1, DrmResp1).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.Provision(binary_provisioning_))
|
||||
<< "DRM Certificate provisioning failed";
|
||||
ASSERT_EQ(GetProvisioningStatus(), kProvisioned);
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(PostDrmProvisioningCheck());
|
||||
}
|
||||
|
||||
// Case: OemReq1, OemReq2, OemResp1 (OemResp2 is never acquired)
|
||||
// Expectation:
|
||||
// CDM handles OemResp1, but does not complete OEM
|
||||
// provisioning.
|
||||
//
|
||||
// Notes:
|
||||
// This is undesirable behavior by the app, but can be partially
|
||||
// handle by the CDM.
|
||||
// Apps that encounter this situation are likely generating many
|
||||
// provisioning requests and loading them in whatever order they
|
||||
// arrive.
|
||||
//
|
||||
// Post-Case: OEM provisioning, DRM provisioning, load license
|
||||
TEST_F(Prov40IntegrationTest, UnusualOrder_DropOem2_LoadOem1) {
|
||||
ProvisioningHolder provisioner(&cdm_engine_, config_);
|
||||
|
||||
// OEM provisioning.
|
||||
// Generate first request (OemReq1).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.GenerateRequest(binary_provisioning_));
|
||||
const std::string oem_request1 = provisioner.request();
|
||||
|
||||
// Generate second request (OemReq2).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.GenerateRequest(binary_provisioning_));
|
||||
// Never send for the second request.
|
||||
|
||||
// Use first request for fetching/loading response (OemResp1).
|
||||
// CDM may or may not return an error, but OEM provisioning is still
|
||||
// needed.
|
||||
provisioner.set_request(oem_request1);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.FetchResponse());
|
||||
// Do not enforce any particular error (including NO_ERROR).
|
||||
provisioner.LoadResponseReturnStatus(binary_provisioning_);
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsOemCertProvisioning);
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(PostIncompleteOemProvisioningCheck());
|
||||
}
|
||||
|
||||
// Case: OemReq1, OemReq2, OemResp2 (OemResp1 is never acquired)
|
||||
// Expectation:
|
||||
// CDM handles OemReq2 (NO_ERROR), and OEM provisioning is
|
||||
// completed.
|
||||
//
|
||||
// Notes:
|
||||
// This is OK behavior by the app.
|
||||
// Only the OEM response from the most recent OEM request will
|
||||
// complete provisioning.
|
||||
//
|
||||
// Post-Case: OEM provisioning, DRM provisioning, load license
|
||||
TEST_F(Prov40IntegrationTest, UnusualOrder_DropOem1_LoadOem2) {
|
||||
ProvisioningHolder provisioner(&cdm_engine_, config_);
|
||||
|
||||
// OEM provisioning.
|
||||
// Generate first request (OemReq1).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.GenerateRequest(binary_provisioning_));
|
||||
// Never send for the first request.
|
||||
|
||||
// Generate, fetch and load second request (OemReq2, OemResp2).
|
||||
// This should complete OEM provisioning.
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.Provision(binary_provisioning_))
|
||||
<< "OEM Certificate provisioning failed";
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsDrmCertProvisioning);
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(PostOemProvisioningCheck());
|
||||
}
|
||||
|
||||
// Case: OemReq1, OemReq2, OemResp1, OemResp2
|
||||
// Expectation:
|
||||
// OemResp1 is handled by the CDM, but does not complete
|
||||
// provisioning. OemResp2 is accepted by the CDM
|
||||
// and completes provisioning.
|
||||
//
|
||||
// Notes:
|
||||
// This is undesirable behavior by the app, but can be partially
|
||||
// handle by the CDM.
|
||||
// Only the OEM response from the most recent OEM request will
|
||||
// complete provisioning.
|
||||
//
|
||||
// Post-Case: DRM provisioning, load license
|
||||
TEST_F(Prov40IntegrationTest, UnusualOrder_LoadOem1_LoadOem2) {
|
||||
ProvisioningHolder provisioner(&cdm_engine_, config_);
|
||||
|
||||
// OEM provisioning.
|
||||
// Generate first request, store it for later (OemReq1).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.GenerateRequest(binary_provisioning_));
|
||||
const std::string oem_request1 = provisioner.request();
|
||||
|
||||
// Generate second request, store it for later (OemReq2).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.GenerateRequest(binary_provisioning_));
|
||||
const std::string oem_request2 = provisioner.request();
|
||||
|
||||
// Use first request for fetching/loading response (OemResp1).
|
||||
// CDM may or may not return an error, but OEM provisioning is still
|
||||
// needed.
|
||||
provisioner.set_request(oem_request1);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.FetchResponse());
|
||||
// Do not enforce any particular error (including NO_ERROR).
|
||||
provisioner.LoadResponseReturnStatus(binary_provisioning_);
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsOemCertProvisioning);
|
||||
|
||||
// Use second request for fetching/loading response (OemResp2).
|
||||
// CDM should accept the second response as valid (so long as
|
||||
// a third was not generated).
|
||||
provisioner.set_request(oem_request2);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.FetchResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.LoadResponse(binary_provisioning_))
|
||||
<< "OEM Certificate provisioning failed";
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsDrmCertProvisioning);
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(PostOemProvisioningCheck());
|
||||
}
|
||||
|
||||
// Case: OemReq1, OemReq2, OemResp2, OemResp1
|
||||
// Expectation:
|
||||
// OemResp2 is accepted by the CDM and comletes OEM provisioning.
|
||||
// OemResp1 does not cause the CDM to be corrupted.
|
||||
//
|
||||
// Notes:
|
||||
// This is undesirable behavior by the app, cannot be handle
|
||||
// by the CDM.
|
||||
// In single-staged provisioning, the CDM silently drops
|
||||
// any additional provisioning responses; but in two-stage
|
||||
// this cannot easily by determine that the response is a
|
||||
// late OEM response.
|
||||
//
|
||||
// Post-Case: DRM provisioning, load license
|
||||
TEST_F(Prov40IntegrationTest, UnusualOrder_LoadOem2_LoadOem1) {
|
||||
ProvisioningHolder provisioner(&cdm_engine_, config_);
|
||||
|
||||
// OEM provisioning.
|
||||
// Generate first request, store it for later (OemReq1).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.GenerateRequest(binary_provisioning_));
|
||||
const std::string oem_request1 = provisioner.request();
|
||||
|
||||
// Generate, fetch and load second request (OemReq2, OemResp2).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.Provision(binary_provisioning_))
|
||||
<< "OEM Certificate provisioning failed";
|
||||
// Provisioning should be complete.
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsDrmCertProvisioning);
|
||||
|
||||
// Use first request for fetching/loading response (OemResp1).
|
||||
// CDM may or may not return an error, but DRM provisioning
|
||||
// should still be allowed after.
|
||||
provisioner.set_request(oem_request1);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.FetchResponse());
|
||||
// Do not enforce any particular error (including NO_ERROR).
|
||||
provisioner.LoadResponseReturnStatus(binary_provisioning_);
|
||||
// Should not effect existing provisioning state.
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsDrmCertProvisioning)
|
||||
<< "Late OEM Certificate response invalidated original response";
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(PostOemProvisioningCheck());
|
||||
}
|
||||
|
||||
// Case: DrmReq1, DrmReq2, DrmResp1, (DrmResp2 is never acquired)
|
||||
// Expectation:
|
||||
// DrmResp1 is handled by the CDM, but does not complete
|
||||
// provisioning.
|
||||
//
|
||||
// Notes:
|
||||
// This is undesirable behavior by the app, but can be partially
|
||||
// handle by the CDM.
|
||||
// Apps that encounter this situation are likely generating many
|
||||
// provisioning requests and loading them in whatever order they
|
||||
// arrive.
|
||||
// For single-stage, this situation usually returns a signature
|
||||
// failure.
|
||||
//
|
||||
// Pre-Case: OEM provisioning
|
||||
// Post-Case: DRM provisioning, load license
|
||||
TEST_F(Prov40IntegrationTest, UnusualOrder_DropDrm2_LoadDrm1) {
|
||||
ASSERT_NO_FATAL_FAILURE(PreDrmProvisioningCheck());
|
||||
|
||||
ProvisioningHolder provisioner(&cdm_engine_, config_);
|
||||
// DRM provisioning.
|
||||
// Generate first request, store it for later (DrmReq1).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.GenerateRequest(binary_provisioning_));
|
||||
const std::string drm_request1 = provisioner.request();
|
||||
|
||||
// Generate second request (DrmReq2).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.GenerateRequest(binary_provisioning_));
|
||||
// Never send for the second request.
|
||||
|
||||
// Use first request for fetching/loading response (DrmResp1).
|
||||
// CDM may or may not return an error, but DRM provisioning is still
|
||||
// needed.
|
||||
provisioner.set_request(drm_request1);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.FetchResponse());
|
||||
// Do not enforce any particular error (including NO_ERROR).
|
||||
provisioner.LoadResponseReturnStatus(binary_provisioning_);
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsDrmCertProvisioning);
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(PostIncompleteDrmProvisioningCheck());
|
||||
}
|
||||
|
||||
// Case: DrmReq1, DrmReq2, DrmResp2 (DrmResp1 is never acquired)
|
||||
// Expectation:
|
||||
// CDM accepts DrmReq2 (NO_ERROR), and DRM provisioning is
|
||||
// completed.
|
||||
//
|
||||
// Notes:
|
||||
// This is OK behavior by the app.
|
||||
// Only the DRM response from the most recent DRM request will
|
||||
// complete provisioning.
|
||||
//
|
||||
// Pre-Case: OEM provisioning
|
||||
// Post-Case: Load license
|
||||
TEST_F(Prov40IntegrationTest, UnusualOrder_DropDrm1_LoadDrm2) {
|
||||
ASSERT_NO_FATAL_FAILURE(PreDrmProvisioningCheck());
|
||||
|
||||
ProvisioningHolder provisioner(&cdm_engine_, config_);
|
||||
// DRM provisioning.
|
||||
// Generate first request (DrmReq1).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.GenerateRequest(binary_provisioning_));
|
||||
// Never send for the first request.
|
||||
|
||||
// Generate, fetch and load second request (DrmReq2, DrmResp2).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.Provision(binary_provisioning_))
|
||||
<< "DRM Certificate provisioning failed";
|
||||
ASSERT_TRUE(IsProvisioned());
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(PostDrmProvisioningCheck());
|
||||
}
|
||||
|
||||
// Case: DrmReq1, DrmReq2, DrmResp1, DrmResp2
|
||||
// Expectation:
|
||||
// DrmResp1 is handled by the CDM, but does not complete
|
||||
// provisioning. DrmResp2 is accepted by the CDM and
|
||||
// completes provisioning.
|
||||
//
|
||||
// Notes:
|
||||
// This is undesirable behavior by the app, but can be partially
|
||||
// handle by the CDM.
|
||||
// Only the DRM response from the most recent DRM request will
|
||||
// complete provisioning.
|
||||
//
|
||||
// Pre-Case: OEM provisioning
|
||||
// Post-Case: Load license
|
||||
TEST_F(Prov40IntegrationTest, UnusualOrder_LoadDrm1_LoadDrm2) {
|
||||
ASSERT_NO_FATAL_FAILURE(PreDrmProvisioningCheck());
|
||||
|
||||
ProvisioningHolder provisioner(&cdm_engine_, config_);
|
||||
// DRM provisioning.
|
||||
// Generate first request, store it for later (DrmReq1).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.GenerateRequest(binary_provisioning_));
|
||||
const std::string drm_request1 = provisioner.request();
|
||||
|
||||
// Generate second request, store it for later (DrmReq2).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.GenerateRequest(binary_provisioning_));
|
||||
const std::string drm_request2 = provisioner.request();
|
||||
|
||||
// Use first request for fetching/loading response (DrmResp1).
|
||||
// CDM may or may not return an error, but DRM provisioning is still
|
||||
// needed.
|
||||
provisioner.set_request(drm_request1);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.FetchResponse());
|
||||
// Do not enforce any particular error (including NO_ERROR).
|
||||
provisioner.LoadResponseReturnStatus(binary_provisioning_);
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsDrmCertProvisioning);
|
||||
|
||||
// Use second request for fetching/loading response (DrmResp2).
|
||||
// CDM should accept the second response as valid (so long as
|
||||
// a third was not generated).
|
||||
provisioner.set_request(drm_request2);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.FetchResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.LoadResponse(binary_provisioning_))
|
||||
<< "DRM Certificate provisioning failed";
|
||||
ASSERT_TRUE(IsProvisioned());
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(PostDrmProvisioningCheck());
|
||||
}
|
||||
|
||||
// Case: DrmReq1, DrmReq2, DrmResp2, DrmResp1
|
||||
// Expectation:
|
||||
// DrmResp2 is accepted by the CDM (NO_ERROR) and completes
|
||||
// provisioning. DrmResp1 is handled by the CDM, but is dropped
|
||||
// without causing issues with existing certificates.
|
||||
//
|
||||
// Notes:
|
||||
// This is undesirable behavior by the app, but can be partially
|
||||
// handle by the CDM.
|
||||
//
|
||||
// Pre-Case: OEM provisioning
|
||||
// Post-Case: Load license
|
||||
TEST_F(Prov40IntegrationTest, UnusualOrder_LoadDrm2_LoadDrm1) {
|
||||
ASSERT_NO_FATAL_FAILURE(PreDrmProvisioningCheck());
|
||||
|
||||
ProvisioningHolder provisioner(&cdm_engine_, config_);
|
||||
// DRM provisioning.
|
||||
// Generate first request, store it for later (DrmReq1).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.GenerateRequest(binary_provisioning_));
|
||||
const std::string drm_request1 = provisioner.request();
|
||||
|
||||
// Generate, fetch and load second request (DrmReq2, DrmResp2).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.Provision(binary_provisioning_))
|
||||
<< "DRM Certificate provisioning failed";
|
||||
ASSERT_TRUE(IsProvisioned());
|
||||
|
||||
// Use first request for fetching/loading response (DrmResp1).
|
||||
// CDM may or may not return an error, and the CDM should still
|
||||
// be considered provisioned.
|
||||
provisioner.set_request(drm_request1);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.FetchResponse());
|
||||
// Do not enforce any particular error (including NO_ERROR).
|
||||
provisioner.LoadResponseReturnStatus(binary_provisioning_);
|
||||
// Should not effect existing provisioning state.
|
||||
ASSERT_TRUE(IsProvisioned())
|
||||
<< "Late DRM Certificate response invalidated original response";
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(PostDrmProvisioningCheck());
|
||||
}
|
||||
|
||||
// Case: OemReq1, OemReq2, OemResp2, DrmReq1, OemResp1, DrmResp1
|
||||
// Expectation:
|
||||
// OemResp2 will complete OEM provisioning, allowing the
|
||||
// creation of DrmReq1.
|
||||
// OemResp1 (being received after OEM provisioning is completed,
|
||||
// and DRM provisioning initiated) is handled by the CDM
|
||||
// and does not prevent the completion of DRM provisioning.
|
||||
//
|
||||
//
|
||||
// Notes:
|
||||
// This is undesirable behavior by the app, but can be partially
|
||||
// handle by the CDM.
|
||||
// Stale OEM responses should not interrupt DRM provisioning in
|
||||
// progress.
|
||||
//
|
||||
// Post-Case: Load license
|
||||
TEST_F(Prov40IntegrationTest, UnusualOrder_LoadOem2_LoadDrm1_LoadOem1AsDrm) {
|
||||
ProvisioningHolder provisioner(&cdm_engine_, config_);
|
||||
|
||||
// Round 1 - OEM provisioning.
|
||||
// Generated and stored first OEM request (OemReq1)
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.GenerateRequest(binary_provisioning_));
|
||||
const std::string oem_request1 = provisioner.request();
|
||||
|
||||
// Complete provisioning on the second attempt (OemReq2, OemResp2).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.Provision(binary_provisioning_));
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsDrmCertProvisioning);
|
||||
|
||||
// Round 2 - DRM provisioning.
|
||||
// Generate DRM certificate request (DrmReq1).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.GenerateRequest(binary_provisioning_));
|
||||
const std::string drm_request1 = provisioner.request();
|
||||
|
||||
// Use OEM request 1 to get an OEM response (OemResp1).
|
||||
// CDM should detect that the OEM response is no longer needed
|
||||
// and should drop the response with or without errors.
|
||||
provisioner.set_request(oem_request1);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.FetchResponse());
|
||||
// Do not enforce any particular error (including NO_ERROR).
|
||||
provisioner.LoadResponseReturnStatus(binary_provisioning_);
|
||||
// Should not effect existing provisioning state.
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsDrmCertProvisioning);
|
||||
|
||||
// Use DRM request 1 to get a DRM response (DrmResp1).
|
||||
provisioner.set_request(drm_request1);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.FetchResponse());
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.LoadResponse(binary_provisioning_))
|
||||
<< "Real DRM Certificate provisioning failed";
|
||||
ASSERT_TRUE(IsProvisioned());
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(PostDrmProvisioningCheck());
|
||||
}
|
||||
|
||||
// Case: OemReq1, OemReq2, OemResp2, DrmReq1, DrmResp1, OemResp1
|
||||
// Expectation:
|
||||
// OemResp2 will complete OEM provisioning, allowing the
|
||||
// creation of DrmReq1.
|
||||
// DrmResp1 will complete DRM provisioning.
|
||||
// OemResp1 (being received after OEM provisioning is completed,
|
||||
// and after DRM provisioning is complete) is handled by the CDM
|
||||
// and does not cause any other issue.
|
||||
//
|
||||
// Notes:
|
||||
// This is undesirable behavior by the app, but can be partially
|
||||
// handle by the CDM.
|
||||
// Any provisioning response received after DRM provisioning
|
||||
// is completed is ignored.
|
||||
//
|
||||
// Post-Case: Load license
|
||||
TEST_F(Prov40IntegrationTest, UnusualOrder_LoadOem2_LoadOem1AsDrm_LoadDrm1) {
|
||||
ProvisioningHolder provisioner(&cdm_engine_, config_);
|
||||
|
||||
// Round 1 - OEM provisioning.
|
||||
// Generated and stored first OEM request (OemReq1)
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.GenerateRequest(binary_provisioning_));
|
||||
const std::string oem_request1 = provisioner.request();
|
||||
|
||||
// Complete provisioning on the second attempt (OemReq2, OemResp2).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.Provision(binary_provisioning_));
|
||||
ASSERT_EQ(GetProvisioningStatus(), kNeedsDrmCertProvisioning);
|
||||
|
||||
// Round 2 - DRM provisioning (DrmReq1, DrmReq2).
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.Provision(binary_provisioning_));
|
||||
ASSERT_TRUE(IsProvisioned());
|
||||
|
||||
// Use OEM request 1 to get an OEM response (OemResp2).
|
||||
// CDM should detect that CDM is fully provisioned and should drop
|
||||
// the response with or without errors.
|
||||
provisioner.set_request(oem_request1);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.FetchResponse());
|
||||
// Do not enforce any particular error (including NO_ERROR).
|
||||
provisioner.LoadResponseReturnStatus(binary_provisioning_);
|
||||
// Should not effect existing provisioning state.
|
||||
ASSERT_TRUE(IsProvisioned())
|
||||
<< "Late OEM Certificate response invalidated DRM certificate";
|
||||
;
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(PostDrmProvisioningCheck());
|
||||
}
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -346,6 +346,8 @@ CdmResponseType WvContentDecryptionModule::Unprovision(
|
||||
// Enable immediate OEMCrypto termination and re-initalization on
|
||||
// unprovisioning.
|
||||
CryptoSession::DisableDelayedTermination();
|
||||
// Android unprovisioning has historically allowed for both
|
||||
// DRM (app/origin-specific) and OEM (global) unprovisioning.
|
||||
return cdm_engine->Unprovision(level);
|
||||
}
|
||||
|
||||
|
||||
@@ -270,6 +270,8 @@ static inline WvStatus mapCdmResponseType(wvcdm::CdmResponseType res) {
|
||||
case wvcdm::USAGE_INVALID_PARAMETERS_2:
|
||||
case wvcdm::USAGE_STORE_ENTRY_RETRIEVE_INVALID_STORAGE_TYPE:
|
||||
case wvcdm::CLIENT_TOKEN_NOT_SET:
|
||||
// Stale responses should have been caught by the CDM engine.
|
||||
case wvcdm::PROVISIONING_4_STALE_RESPONSE:
|
||||
err = Status::GENERAL_PLUGIN_ERROR;
|
||||
break;
|
||||
case wvcdm::CLIENT_ID_GENERATE_RANDOM_ERROR:
|
||||
@@ -299,6 +301,9 @@ static inline WvStatus mapCdmResponseType(wvcdm::CdmResponseType res) {
|
||||
case wvcdm::INVALID_QUERY_KEY:
|
||||
case wvcdm::KEY_NOT_FOUND_1:
|
||||
case wvcdm::SAMPLE_AND_SUBSAMPLE_SIZE_MISMATCH:
|
||||
// Client provided a provisioning response without
|
||||
// generating a provisioning request.
|
||||
case wvcdm::PROVISIONING_UNEXPECTED_RESPONSE_ERROR:
|
||||
err = Status::BAD_VALUE;
|
||||
break;
|
||||
case wvcdm::KEY_NOT_FOUND_3:
|
||||
@@ -400,6 +405,9 @@ static inline WvStatus mapCdmResponseType(wvcdm::CdmResponseType res) {
|
||||
case wvcdm::CERT_PROVISIONING_RESPONSE_ERROR_4:
|
||||
case wvcdm::CERT_PROVISIONING_RESPONSE_ERROR_9:
|
||||
case wvcdm::LOAD_PROVISIONING_ERROR:
|
||||
// Failure to verify provisioning cert key is always
|
||||
// due to a malformed response.
|
||||
case wvcdm::PROVISIONING_4_FAILED_TO_VERIFY_CERT_KEY:
|
||||
err = Status::PROVISIONING_PARSE_ERROR;
|
||||
break;
|
||||
case wvcdm::CERT_PROVISIONING_RESPONSE_ERROR_10:
|
||||
|
||||
Reference in New Issue
Block a user