Changed Prov4.0 handler to accept only recent requests.
[ Cherry-pick of v19 http://go/wvgerrit/219291 ] [ Merge of http://go/wvgerrit/219432 ] If the same app/origin generates multiple provisioning 4.0 requests it is possible that a mismatch between the OEM/DRM certificate and the wrapped OEM/DRM private key occurs. The CDM would use the OEM/DRM certificate of the first response one received, and the wrapped private key of the last request generated. To avoid this issue, the public key from the most recent request is cached and checked against the responses received. If the keys match, that response is accepted; if the keys don't match than the response is assumed "stale" and the response is dropped. In an attempt to maintain existing behavior of the CDM, "stale" responses will return NO_ERROR to the app. Note: This was tested using both RSA and ECC cert key types. VIC-specific: Needed to add implementation of StringContains() and StringEndsWith(). Bug: 391469176 Test: run_prov40_tests Change-Id: Id45d40d9af355c46a61c3cc2c19c252cf17c7489
This commit is contained in:
@@ -151,18 +151,24 @@ class CertificateProvisioning {
|
||||
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_
|
||||
|
||||
@@ -466,6 +466,8 @@ enum CdmResponseEnum : int32_t {
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
@@ -355,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()) {
|
||||
@@ -376,6 +502,7 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal(
|
||||
|
||||
// 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);
|
||||
@@ -448,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);
|
||||
@@ -522,6 +648,13 @@ 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, 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_ = public_key;
|
||||
|
||||
state_ = is_oem_prov_request ? kOemRequestSent : kDrmRequestSent;
|
||||
return CdmResponseType(NO_ERROR);
|
||||
@@ -606,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;
|
||||
@@ -627,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;
|
||||
@@ -640,11 +771,78 @@ 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)) {
|
||||
@@ -659,17 +857,23 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
|
||||
// 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);
|
||||
}
|
||||
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.
|
||||
@@ -679,11 +883,14 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response(
|
||||
return CdmResponseType(PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES_3);
|
||||
}
|
||||
if (!per_origin_file_handle.StoreCertificate(device_certificate,
|
||||
private_key)) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -881,6 +881,10 @@ const char* CdmResponseEnumToString(CdmResponseEnum cdm_response_enum) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
@@ -403,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