Create general certificate provisioning tests

Creates parameterized certificate provisioning tests to prepare for DRM
reprovisioning implementation.
- Create parameterized certificate provisioning test suite.
- Change RETURN_IF_NOT_OPEN macro to call IsOpen instead of checking
  the |open_| variable to make mocking of CryptoSession methods easier.

Bug: b/305093063
Merged from https://widevine-internal-review.googlesource.com/188051

Change-Id: Ic1c344af64073a8ff5626530a0864bfeea90fc6e
This commit is contained in:
Geoffrey Alexander
2023-11-21 00:48:35 +00:00
committed by Robert Shih
parent 442ee78db1
commit 62ba0133cf
3 changed files with 292 additions and 6 deletions

View File

@@ -73,6 +73,9 @@ class CertificateProvisioning {
static void GetProvisioningServerUrl(std::string* default_url);
private:
#if defined(UNIT_TEST)
friend class CertificateProvisioningTest;
#endif
CdmResponseType GetProvisioningRequestInternal(
wvutil::FileSystem* file_system,
RequestedSecurityLevel requested_security_level,

View File

@@ -62,7 +62,7 @@ OEMCryptoResult WrapIfNecessary(OEMCryptoResult ret_value) { return ret_value; }
}
#define RETURN_IF_NOT_OPEN(ret_value) \
if (!open_) { \
if (!IsOpen()) { \
LOGE("Crypto session is not open"); \
return WrapIfNecessary(ret_value); \
}

View File

@@ -10,12 +10,20 @@
#include <string>
#include "crypto_session.h"
#include "license_protocol.pb.h"
#include "metrics_collections.h"
#include "string_conversions.h"
#include "string_format.h"
#include "test_base.h"
#include "wv_cdm_types.h"
namespace {
using video_widevine::DrmCertificate;
using video_widevine::ProvisioningResponse;
using video_widevine::SignedDrmCertificate;
using video_widevine::SignedProvisioningMessage;
const std::string kSignedDeviceCertificate = wvutil::a2bs_hex(
"0A3D0802121B7769646576696E655F746573745F73657269616C5F6E756D62657218C09A0C"
"28D2093A11746573742E7769646576696E652E636F6D6080B51812097369676E617475726"
@@ -50,11 +58,108 @@ const int64_t kCreationTime = 200000;
const int64_t kExpirationTime = 400000;
const std::string kSerialNumber = "widevine_test_serial_number";
const uint32_t kSystemId = 1234;
const uint32_t kNonce = 0x49e81305;
const std::string kNonceString = "\x49\xe8\x13\x05";
const std::string kEmptyString;
const std::string kFakeCoreMessage = wvutil::a2bs_hex("DEADBEEF");
const std::string kFakeProvisioningRequestSignature =
"a very real provisioning request signature";
const std::string kFakeBuildInfo = "Mock Crypto Session - License Test";
const std::string kFakeAuthority = "a fake authority";
const std::string kOrigin = "an origin for device id";
const std::string kSpoid = "a spoid for identifying a device";
const std::string kFakeCertificateToken = "a testing certificate token";
const std::string kFakePublicKey = "a very real public key for a certificate";
const std::string kFakeSignature = "a very real certificate signature";
const std::string kWrappedPrivateKey = "a wrapped private key";
bool MakeSignedDrmCertificate(const std::string& public_key,
const std::string& serial_number,
uint32_t system_id, const std::string& signature,
std::string* certificate) {
DrmCertificate drm_certificate;
drm_certificate.set_public_key(public_key);
drm_certificate.set_serial_number(serial_number);
drm_certificate.set_system_id(system_id);
drm_certificate.set_type(DrmCertificate::DEVICE);
drm_certificate.set_creation_time_seconds(kCreationTime);
std::string serialized_drm_certificate;
drm_certificate.SerializeToString(&serialized_drm_certificate);
SignedDrmCertificate signed_drm_certificate;
signed_drm_certificate.set_drm_certificate(serialized_drm_certificate);
signed_drm_certificate.set_signature(signature);
return signed_drm_certificate.SerializeToString(certificate);
}
bool MakeProvisioningResponseJson(
const std::string& certificate, const std::string& nonce,
ProvisioningResponse::ProvisioningStatus status,
const std::string& signature, const std::string& core_message,
std::string* response) {
ProvisioningResponse provisioning_response;
provisioning_response.set_device_certificate(certificate);
provisioning_response.set_nonce(nonce);
provisioning_response.set_status(status);
std::string provisioning_response_string;
if (!provisioning_response.SerializeToString(&provisioning_response_string)) {
return false;
}
SignedProvisioningMessage signed_response;
signed_response.set_message(provisioning_response_string);
signed_response.set_signature(signature);
signed_response.set_oemcrypto_core_message(core_message);
signed_response.set_protocol_version(SignedProvisioningMessage::VERSION_1_1);
signed_response.set_provisioning_type(
SignedProvisioningMessage::DRM_REPROVISIONING);
std::string signed_response_string;
if (!signed_response.SerializeToString(&signed_response_string)) {
return false;
}
std::string response_base64 =
wvutil::Base64SafeEncode(signed_response_string);
return wvutil::FormatString(response, "{ \"signedResponse\": \"%s\" }",
response_base64.c_str());
}
} // unnamed namespace
namespace wvcdm {
using ::testing::_;
using ::testing::ByMove;
using ::testing::DoAll;
using ::testing::NiceMock;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::ReturnArg;
using ::testing::SaveArg;
using ::testing::SetArgPointee;
using ::testing::SetArgReferee;
using ::testing::StrEq;
using wvutil::File;
class MockFile : public File {
public:
MockFile() {}
~MockFile() override {}
MOCK_METHOD(ssize_t, Read, (char*, size_t), (override));
MOCK_METHOD(ssize_t, Write, (const char*, size_t), (override));
};
class MockFileSystem : public wvutil::FileSystem {
public:
MockFileSystem() {}
~MockFileSystem() override {}
MOCK_METHOD(std::unique_ptr<File>, Open, (const std::string&, int),
(override));
};
class MockCryptoSession : public TestCryptoSession {
public:
MockCryptoSession(metrics::CryptoMetrics* metrics)
@@ -78,17 +183,39 @@ class MockCryptoSession : public TestCryptoSession {
MOCK_METHOD(CdmResponseType, UpdateUsageEntry,
(UsageTableHeader*, UsageEntry*), (override));
MOCK_METHOD(CdmResponseType, MoveUsageEntry, (UsageEntryIndex), (override));
MOCK_METHOD(bool, IsOpen, (), (override));
MOCK_METHOD(CdmClientTokenType, GetPreProvisionTokenType, (), (override));
MOCK_METHOD(CdmResponseType, GetProvisioningToken,
(std::string*, std::string*), (override));
MOCK_METHOD(CdmResponseType, GenerateNonce, (uint32_t*), (override));
MOCK_METHOD(CdmResponseType, PrepareAndSignProvisioningRequest,
(const std::string&, std::string*, std::string*, bool&,
OEMCrypto_SignatureHashAlgorithm&),
(override));
MOCK_METHOD(bool, GetSupportedCertificateTypes, (SupportedCertificateTypes*),
(override));
MOCK_METHOD(bool, GetApiVersion, (uint32_t*), (override));
MOCK_METHOD(bool, GetResourceRatingTier, (uint32_t*), (override));
MOCK_METHOD(bool, GetBuildInformation, (std::string*), (override));
MOCK_METHOD(CdmSecurityLevel, GetSecurityLevel, (), (override));
MOCK_METHOD(CdmResponseType, LoadProvisioning,
(const std::string&, const std::string&, const std::string&,
std::string*),
(override));
};
class TestStubCryptoSessionFactory : public CryptoSessionFactory {
CryptoSession* MakeCryptoSession(
metrics::CryptoMetrics* crypto_metrics) override {
return new MockCryptoSession(crypto_metrics);
return new NiceMock<MockCryptoSession>(crypto_metrics);
}
};
class CertificateProvisioningTest : public WvCdmTestBase {
protected:
class CertificateProvisioningTest
: public WvCdmTestBase,
public testing::WithParamInterface<CdmClientTokenType> {
public:
void SetUp() override {
WvCdmTestBase::SetUp();
CryptoSession::SetCryptoSessionFactory(new TestStubCryptoSessionFactory());
@@ -96,20 +223,52 @@ class CertificateProvisioningTest : public WvCdmTestBase {
metrics_.reset(new metrics::CryptoMetrics());
certificate_provisioning_.reset(
new CertificateProvisioning(metrics_.get()));
GetCryptoSession()->pre_provision_token_type_ = GetParam();
}
void TearDown() override {}
protected:
MockCryptoSession* GetCryptoSession() {
return static_cast<MockCryptoSession*>(
certificate_provisioning_->crypto_session_.get());
}
std::unique_ptr<metrics::CryptoMetrics> metrics_;
std::unique_ptr<CertificateProvisioning> certificate_provisioning_;
};
void MockValidCryptoSession(MockCryptoSession* crypto_session,
CdmClientTokenType token_type) {
const uint32_t crypto_session_api_version = 18;
const uint32_t resource_rating_tier = RESOURCE_RATING_TIER_LOW;
ON_CALL(*crypto_session, IsOpen()).WillByDefault(Return(true));
ON_CALL(*crypto_session, Open(_))
.WillByDefault(Return(CdmResponseType(NO_ERROR)));
ON_CALL(*crypto_session, GenerateNonce(NotNull()))
.WillByDefault(
DoAll(SetArgPointee<0>(kNonce), Return(CdmResponseType(NO_ERROR))));
ON_CALL(*crypto_session, GetApiVersion(NotNull()))
.WillByDefault(
DoAll(SetArgPointee<0>(crypto_session_api_version), Return(true)));
ON_CALL(*crypto_session, GetResourceRatingTier(NotNull()))
.WillByDefault(
DoAll(SetArgPointee<0>(resource_rating_tier), Return(true)));
ON_CALL(*crypto_session, GetBuildInformation(NotNull()))
.WillByDefault(DoAll(SetArgPointee<0>(kFakeBuildInfo), Return(true)));
ON_CALL(*crypto_session, GetSupportedCertificateTypes(NotNull()))
.WillByDefault(Return(false));
ON_CALL(*crypto_session, GetPreProvisionTokenType())
.WillByDefault(Return(token_type));
}
// Tests ExtractDeviceInfo failure scenarios
// * invalid output parmeters
// * invalid signed drm device certificate
// * signed drm device certificate contains no drm certificate
// * drm certificate has an invalid certificate type
TEST_F(CertificateProvisioningTest, ExtractDeviceInfo_InvalidInput) {
TEST_P(CertificateProvisioningTest, ExtractDeviceInfo_InvalidInput) {
std::string serial_number;
uint32_t system_id;
@@ -134,7 +293,7 @@ TEST_F(CertificateProvisioningTest, ExtractDeviceInfo_InvalidInput) {
// * able to extract both |serial_number| and |system_id|
// * able to extract only |serial_number|
// * able to extract only |system_id|
TEST_F(CertificateProvisioningTest, ExtractDeviceInfo) {
TEST_P(CertificateProvisioningTest, ExtractDeviceInfo) {
std::string serial_number;
uint32_t system_id;
int64_t creation_time_seconds, expiration_time_seconds;
@@ -205,4 +364,128 @@ TEST_F(CertificateProvisioningTest, ExtractDeviceInfo) {
EXPECT_EQ(kExpirationTime, expiration_time_seconds);
}
TEST_P(CertificateProvisioningTest, ProvisioningRequestIsValid) {
certificate_provisioning_->Init("");
MockFileSystem file_system;
MockCryptoSession* crypto_session = GetCryptoSession();
MockValidCryptoSession(crypto_session, GetParam());
ON_CALL(*crypto_session, GetProvisioningToken(NotNull(), _))
.WillByDefault(DoAll(SetArgPointee<0>(kFakeCertificateToken),
Return(CdmResponseType(NO_ERROR))));
EXPECT_CALL(*crypto_session,
PrepareAndSignProvisioningRequest(_, NotNull(), NotNull(), _, _))
.WillOnce(DoAll(SetArgPointee<1>(kFakeCoreMessage),
SetArgPointee<2>(kFakeProvisioningRequestSignature),
SetArgReferee<3>(false),
Return(CdmResponseType(NO_ERROR))));
CdmProvisioningRequest request;
std::string default_url;
EXPECT_EQ(NO_ERROR,
certificate_provisioning_->GetProvisioningRequest(
&file_system, kLevelDefault, kCertificateWidevine,
kFakeAuthority, kOrigin, kSpoid, &request, &default_url));
}
TEST_P(CertificateProvisioningTest, ProvisioningRequestFailsEmptySignature) {
certificate_provisioning_->Init("");
MockFileSystem file_system;
MockCryptoSession* crypto_session = GetCryptoSession();
MockValidCryptoSession(crypto_session, GetParam());
ON_CALL(*crypto_session, GetProvisioningToken(NotNull(), _))
.WillByDefault(DoAll(SetArgPointee<0>(kFakeCertificateToken),
Return(CdmResponseType(NO_ERROR))));
EXPECT_CALL(*crypto_session,
PrepareAndSignProvisioningRequest(_, NotNull(), NotNull(), _, _))
.WillOnce(DoAll(SetArgPointee<1>(kFakeCoreMessage),
SetArgPointee<2>(kEmptyString), SetArgReferee<3>(false),
Return(CdmResponseType(NO_ERROR))));
CdmProvisioningRequest request;
std::string default_url;
EXPECT_EQ(CERT_PROVISIONING_REQUEST_ERROR_4,
certificate_provisioning_->GetProvisioningRequest(
&file_system, kLevelDefault, kCertificateWidevine,
kFakeAuthority, kOrigin, kSpoid, &request, &default_url));
}
TEST_P(CertificateProvisioningTest,
ProvisioningResponseFailsWithEmptyResponse) {
certificate_provisioning_->Init("");
MockFileSystem file_system;
std::string certificate;
std::string wrapped_key;
EXPECT_EQ(CERT_PROVISIONING_RESPONSE_ERROR_1,
certificate_provisioning_->HandleProvisioningResponse(
&file_system, /*response=*/"", &certificate, &wrapped_key));
}
TEST_P(CertificateProvisioningTest,
ProvisioningResponseFailsIfDeviceIsRevoked) {
certificate_provisioning_->Init("");
MockFileSystem file_system;
std::string response_certificate;
std::string response;
ASSERT_TRUE(MakeSignedDrmCertificate(kFakePublicKey, kSerialNumber, kSystemId,
kFakeSignature, &response_certificate));
ASSERT_TRUE(MakeProvisioningResponseJson(
response_certificate, kNonceString,
ProvisioningResponse::REVOKED_DEVICE_CREDENTIALS, kFakeSignature,
kFakeCoreMessage, &response));
std::string certificate;
std::string wrapped_key;
EXPECT_EQ(DEVICE_REVOKED,
certificate_provisioning_->HandleProvisioningResponse(
&file_system, response, &certificate, &wrapped_key));
}
TEST_P(CertificateProvisioningTest, ProvisioningResponseSuccess) {
certificate_provisioning_->Init("");
std::string expected_certificate;
std::string response;
ASSERT_TRUE(MakeSignedDrmCertificate(kFakePublicKey, kSerialNumber, kSystemId,
kFakeSignature, &expected_certificate));
ASSERT_TRUE(MakeProvisioningResponseJson(
expected_certificate, kNonceString, ProvisioningResponse::NO_ERROR,
kFakeSignature, kFakeCoreMessage, &response));
MockCryptoSession* crypto_session = GetCryptoSession();
ON_CALL(*crypto_session, IsOpen()).WillByDefault(Return(true));
ON_CALL(*crypto_session, GetSecurityLevel())
.WillByDefault(Return(kSecurityLevelL3));
EXPECT_CALL(*crypto_session, LoadProvisioning)
.Times(1)
.WillOnce(DoAll(SetArgPointee<3>(kWrappedPrivateKey),
Return(CdmResponseType(NO_ERROR))));
MockFile* file = new MockFile();
std::string stored_certificate;
EXPECT_CALL(*file, Write(_, _))
.Times(1)
.WillOnce(DoAll(SaveArg<0>(&stored_certificate), ReturnArg<1>()));
MockFileSystem file_system;
EXPECT_CALL(file_system, Open(StrEq(wvutil::kLegacyCertificateFileName), _))
.Times(1)
.WillOnce(Return(ByMove(std::unique_ptr<File>(file))));
std::string certificate;
std::string wrapped_key;
EXPECT_EQ(NO_ERROR, certificate_provisioning_->HandleProvisioningResponse(
&file_system, response, &certificate, &wrapped_key));
EXPECT_NE(std::string::npos, stored_certificate.find(expected_certificate));
}
INSTANTIATE_TEST_SUITE_P(
CertificateProvisioningTests, CertificateProvisioningTest,
// TODO: b/305093063 - Add Drm Reprovisioning to Values once implemented.
testing::Values(kClientTokenKeybox, kClientTokenOemCert),
[](const testing::TestParamInfo<CertificateProvisioningTest::ParamType>&
param_type) {
return CdmClientTokenTypeToString(param_type.param);
});
} // namespace wvcdm