Add OEMCrypto tests for Cast prov 4 flow

Expected flow, which begins with a device that has already been
provisioned with Prov 4 stage 1:
1. OEMCrypto_InstallOEMPrivateKey()
2. OEMCrypto_GenerateCertificateKeyPair() -> wrapped_csr_priv
3. OEMCrypto_LoadDRMPrivateKey(wrapped_csr_priv)
4. OEMCrypto_PrepAndSignProvisioningRequest() to create a Prov 4
   provisioning request message type with a CAST request in the
   message body
5. Server sends a Prov 2 response. Server side derivation uses CSR keys
   to derive session key, mac keys, and encryption keys.
6. OEMCrypto_DeriveKeysFromSessionKey(), same derivation as server side
7. OEMCrypto_LoadProvisioning(), use derived keys to verify + decrypt

The OEMCrypto_LoadDRMPrivateKey() step can happen before or after the
PrepAndSignProvisioningRequest() call.

Test: tests fail
Bug: 259452440

Merged from https://widevine-internal-review.googlesource.com/172310

Change-Id: Id5e6737b187339ec93e3d0d03c28e2b379d60747
This commit is contained in:
Matt Feddersen
2023-04-25 23:21:33 +00:00
committed by Robert Shih
parent 5a17d8ebd9
commit 27421a9161
6 changed files with 297 additions and 0 deletions

View File

@@ -603,6 +603,161 @@ OEMCryptoResult Provisioning40RoundTrip::LoadDRMCertResponse() {
wrapped_drm_key_.size()); wrapped_drm_key_.size());
} }
void Provisioning40CastRoundTrip::PrepareSession() {
const size_t buffer_size = 5000; // Make sure it is large enough.
std::vector<uint8_t> public_key(buffer_size);
size_t public_key_size = buffer_size;
std::vector<uint8_t> public_key_signature(buffer_size);
size_t public_key_signature_size = buffer_size;
std::vector<uint8_t> wrapped_private_key(buffer_size);
size_t wrapped_private_key_size = buffer_size;
OEMCrypto_PrivateKeyType key_type;
ASSERT_EQ(
OEMCrypto_SUCCESS,
OEMCrypto_GenerateCertificateKeyPair(
session()->session_id(), public_key.data(), &public_key_size,
public_key_signature.data(), &public_key_signature_size,
wrapped_private_key.data(), &wrapped_private_key_size, &key_type));
wrapped_private_key.resize(wrapped_private_key_size);
public_key.resize(public_key_size);
wrapped_drm_key_ = wrapped_private_key;
drm_public_key_ = public_key;
drm_key_type_ = key_type;
}
void Provisioning40CastRoundTrip::LoadDRMPrivateKey() {
ASSERT_EQ(OEMCrypto_SUCCESS,
OEMCrypto_LoadDRMPrivateKey(session()->session_id(), drm_key_type_,
wrapped_drm_key_.data(),
wrapped_drm_key_.size()));
}
void Provisioning40CastRoundTrip::FillAndVerifyCoreRequest(
const std::string& core_message_string) {
EXPECT_TRUE(
oemcrypto_core_message::deserialize::CoreProvisioning40RequestFromMessage(
core_message_string, &core_request_));
EXPECT_EQ(global_features.api_version, core_request_.api_major_version);
EXPECT_EQ(session()->nonce(), core_request_.nonce);
EXPECT_EQ(session()->session_id(), core_request_.session_id);
}
void Provisioning40CastRoundTrip::VerifyRequestSignature(
const vector<uint8_t>& data, const vector<uint8_t>& generated_signature,
size_t /* core_message_length */) {
ASSERT_NO_FATAL_FAILURE(
session()->VerifySignature(data, generated_signature.data(),
generated_signature.size(), kSign_RSASSA_PSS));
}
// Creates a prov2 response
void Provisioning40CastRoundTrip::CreateDefaultResponse() {
uint32_t algorithm_n = htonl(kSign_RSASSA_PSS);
memcpy(response_data_.rsa_key, "SIGN", 4);
memcpy(response_data_.rsa_key + 4, &algorithm_n, 4);
memcpy(response_data_.rsa_key + 8, encoded_rsa_key_.data(),
encoded_rsa_key_.size());
response_data_.rsa_key_length = 8 + encoded_rsa_key_.size();
response_data_.nonce = session_->nonce();
response_data_.enc_message_key_length = 0;
core_response_.key_type = OEMCrypto_RSA_Private_Key;
core_response_.enc_private_key =
FindSubstring(response_data_.rsa_key, response_data_.rsa_key_length);
core_response_.enc_private_key_iv = FindSubstring(
response_data_.rsa_key_iv, sizeof(response_data_.rsa_key_iv));
core_response_.encrypted_message_key = FindSubstring(
response_data_.enc_message_key, response_data_.enc_message_key_length);
}
void Provisioning40CastRoundTrip::EncryptAndSignResponse() {
session()->key_deriver().PadAndEncryptProvisioningMessage(&response_data_,
&encrypted_response_data_);
core_response_.enc_private_key.length =
encrypted_response_data_.rsa_key_length;
SignResponse();
}
void Provisioning40CastRoundTrip::SignResponse() {
CoreMessageFeatures features =
CoreMessageFeatures::DefaultFeatures(ODK_MAJOR_VERSION);
// Create prov 2 request struct from prov 4 request
oemcrypto_core_message::ODK_ProvisioningRequest core_request_prov2;
core_request_prov2.api_minor_version = core_request_.api_minor_version;
core_request_prov2.api_major_version = core_request_.api_major_version;
core_request_prov2.nonce = core_request_.nonce;
core_request_prov2.session_id = core_request_.session_id;
memcpy(&core_request_prov2.counter_info, &core_request_.counter_info,
sizeof(core_request_.counter_info));
ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreProvisioningResponse(
features, core_response_, core_request_prov2, &serialized_core_message_));
// Resizing for huge core message length unit tests.
serialized_core_message_.resize(
std::max(required_core_message_size_, serialized_core_message_.size()));
// Make the message buffer a just big enough, or the
// required size, whichever is larger.
const size_t message_size =
std::max(required_message_size_, serialized_core_message_.size() +
sizeof(encrypted_response_data_));
// Stripe the encrypted message.
encrypted_response_.resize(message_size);
for (size_t i = 0; i < encrypted_response_.size(); i++) {
encrypted_response_[i] = i & 0xFF;
}
ASSERT_GE(encrypted_response_.size(), serialized_core_message_.size());
memcpy(encrypted_response_.data(), serialized_core_message_.data(),
serialized_core_message_.size());
ASSERT_GE(encrypted_response_.size(),
serialized_core_message_.size() + sizeof(encrypted_response_data_));
memcpy(encrypted_response_.data() + serialized_core_message_.size(),
reinterpret_cast<const uint8_t*>(&encrypted_response_data_),
sizeof(encrypted_response_data_));
session()->key_deriver().ServerSignBuffer(encrypted_response_.data(),
encrypted_response_.size(),
&response_signature_);
SetEncryptAndSignResponseLengths();
}
OEMCryptoResult Provisioning40CastRoundTrip::LoadResponse(Session* session) {
EXPECT_NE(session, nullptr);
// Write corpus for oemcrypto_load_provisioning_fuzz. Fuzz script expects
// unencrypted response from provisioning server as input corpus data.
// Data will be encrypted and signed again explicitly by fuzzer script after
// mutations.
if (ShouldGenerateCorpus()) {
const std::string file_name =
GetFileName("oemcrypto_load_provisioning_fuzz_seed_corpus");
// Corpus for license response fuzzer should be in the format:
// unencrypted (core_response + response_data).
AppendToFile(file_name, reinterpret_cast<const char*>(&core_response_),
sizeof(ODK_ParsedProvisioning));
AppendToFile(file_name, reinterpret_cast<const char*>(&response_data_),
sizeof(response_data_));
}
size_t wrapped_key_length = 0;
OEMCryptoResult sts = LoadResponseNoRetry(session, &wrapped_key_length);
if (sts != OEMCrypto_ERROR_SHORT_BUFFER) return sts;
wrapped_rsa_key_.assign(wrapped_key_length, 0);
sts = LoadResponseNoRetry(session, &wrapped_key_length);
if (sts == OEMCrypto_SUCCESS) {
wrapped_rsa_key_.resize(wrapped_key_length);
}
return sts;
}
OEMCryptoResult Provisioning40CastRoundTrip::LoadResponseNoRetry(
Session* session, size_t* wrapped_key_length) {
EXPECT_NE(session, nullptr);
VerifyEncryptAndSignResponseLengths();
return OEMCrypto_LoadProvisioning(
session->session_id(), encrypted_response_.data(),
encrypted_response_.size(), serialized_core_message_.size(),
response_signature_.data(), response_signature_.size(),
wrapped_rsa_key_.data(), wrapped_key_length);
}
void LicenseRoundTrip::VerifyRequestSignature( void LicenseRoundTrip::VerifyRequestSignature(
const vector<uint8_t>& data, const vector<uint8_t>& generated_signature, const vector<uint8_t>& data, const vector<uint8_t>& generated_signature,
size_t core_message_length) { size_t core_message_length) {

View File

@@ -368,6 +368,56 @@ class Provisioning40RoundTrip
OEMCrypto_PrivateKeyType drm_key_type_; OEMCrypto_PrivateKeyType drm_key_type_;
}; };
class Provisioning40CastRoundTrip
: public RoundTrip<
/* CoreRequest */ oemcrypto_core_message::ODK_Provisioning40Request,
OEMCrypto_PrepAndSignProvisioningRequest,
/* CoreResponse */ ODK_ParsedProvisioning,
/* ResponseData */ RSAPrivateKeyMessage> {
public:
Provisioning40CastRoundTrip(Session* session,
const std::vector<uint8_t>& encoded_rsa_key)
: RoundTrip(session), encryptor_(),
encoded_rsa_key_(encoded_rsa_key) {}
void PrepareSession();
void LoadDRMPrivateKey();
void CreateDefaultResponse() override;
void SignResponse();
void EncryptAndSignResponse() override;
OEMCryptoResult LoadResponse() override { return LoadResponse(session_); }
OEMCryptoResult LoadResponse(Session* session) override;
OEMCryptoResult LoadResponseNoRetry(Session* session, size_t* wrapped_key_length) ;
// Returned
const std::vector<uint8_t>& wrapped_drm_key() { return wrapped_drm_key_; }
const std::vector<uint8_t>& drm_public_key() { return drm_public_key_; }
OEMCrypto_PrivateKeyType drm_key_type() { return drm_key_type_; }
void set_allowed_schemes(uint32_t allowed_schemes) {
allowed_schemes_ = allowed_schemes;
}
protected:
bool RequestHasNonce() override { return true; }
void VerifyRequestSignature(const vector<uint8_t>& data,
const vector<uint8_t>& generated_signature,
size_t core_message_length) override;
// Verify the values of the core response.
virtual void FillAndVerifyCoreRequest(
const std::string& core_message_string) override;
uint32_t allowed_schemes_;
Encryptor encryptor_;
std::vector<uint8_t> wrapped_oem_key_;
std::vector<uint8_t> oem_public_key_;
OEMCrypto_PrivateKeyType oem_key_type_;
std::vector<uint8_t> wrapped_drm_key_;
std::vector<uint8_t> drm_public_key_;
OEMCrypto_PrivateKeyType drm_key_type_;
std::vector<uint8_t> encoded_rsa_key_;
std::vector<uint8_t> wrapped_rsa_key_;
};
class LicenseRoundTrip class LicenseRoundTrip
: public RoundTrip< : public RoundTrip<
/* CoreRequest */ oemcrypto_core_message::ODK_LicenseRequest, /* CoreRequest */ oemcrypto_core_message::ODK_LicenseRequest,

View File

@@ -620,6 +620,42 @@ TEST_F(OEMCryptoProv40Test, ProvisionDrmCert) {
ASSERT_EQ(s.IsPublicKeySet(), true); ASSERT_EQ(s.IsPublicKeySet(), true);
} }
TEST_P(OEMCryptoProv40CastTest, ProvisionCastWorks) {
// Generate an OEM key first, to load into next session
Session s;
ASSERT_NO_FATAL_FAILURE(s.open());
size_t public_key_size = 10000;
std::vector<uint8_t> public_key(public_key_size);
size_t public_key_signature_size = 10000;
std::vector<uint8_t> public_key_signature(public_key_signature_size);
size_t wrapped_private_key_size = 10000;
std::vector<uint8_t> wrapped_private_key(wrapped_private_key_size);
OEMCrypto_PrivateKeyType key_type;
ASSERT_EQ(
OEMCrypto_GenerateCertificateKeyPair(
s.session_id(), public_key.data(), &public_key_size,
public_key_signature.data(), &public_key_signature_size,
wrapped_private_key.data(), &wrapped_private_key_size, &key_type),
OEMCrypto_SUCCESS);
public_key.resize(public_key_size);
public_key_signature.resize(public_key_signature_size);
wrapped_private_key.resize(wrapped_private_key_size);
ASSERT_NO_FATAL_FAILURE(s.close());
// Install OEM key and get cast RSA
Session s1;
ASSERT_NO_FATAL_FAILURE(s1.open());
ASSERT_EQ(OEMCrypto_InstallOemPrivateKey(s1.session_id(), key_type,
wrapped_private_key.data(),
wrapped_private_key_size),
OEMCrypto_SUCCESS);
ASSERT_NO_FATAL_FAILURE(CreateProv4CastKey(&s1, GetParam()));
}
INSTANTIATE_TEST_SUITE_P(Prov4CastProvisioningBasic, OEMCryptoProv40CastTest,
testing::Values(true, false));
TEST_F(OEMCryptoLoadsCertificate, PrepAndSignLicenseRequestCounterAPI18) { TEST_F(OEMCryptoLoadsCertificate, PrepAndSignLicenseRequestCounterAPI18) {
ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey()); ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey());
Session s; Session s;

View File

@@ -58,6 +58,19 @@ class OEMCryptoProv40Test : public OEMCryptoClientTest {
} }
}; };
class OEMCryptoProv40CastTest : public OEMCryptoClientTest,
public testing::WithParamInterface<bool> {
void SetUp() override {
OEMCryptoClientTest::SetUp();
if (!global_features.cast_receiver) {
GTEST_SKIP() << "Test for cast devices only.";
}
if (global_features.provisioning_method != OEMCrypto_BootCertificateChain) {
GTEST_SKIP() << "Test for Prov 4.0 devices only.";
}
}
};
// //
// Certificate Root of Trust Tests // Certificate Root of Trust Tests
// //

View File

@@ -167,4 +167,45 @@ void SessionUtil::CreateProv4DRMKey() {
drm_public_key_ = provisioning_messages.drm_public_key(); drm_public_key_ = provisioning_messages.drm_public_key();
} }
// Requires stage 1 prov4 to be complete, ie OEM key is available
void SessionUtil::CreateProv4CastKey(Session* s,
bool load_drm_before_prov_req) {
if (global_features.provisioning_method != OEMCrypto_BootCertificateChain) {
FAIL() << "Provisioning 4.0 is required.";
}
Provisioning40CastRoundTrip prov_cast(s, encoded_rsa_key_);
// Calls GenerateCertificateKeyPair(). Generated keys stored in
// prov_cast.drm_public_key_ and prov_cast.wrapped_drm_key_
ASSERT_NO_FATAL_FAILURE(prov_cast.PrepareSession());
// Can choose to load DRM key before preparing the provisioning request, or
// after
if (load_drm_before_prov_req) {
ASSERT_NO_FATAL_FAILURE(prov_cast.LoadDRMPrivateKey());
}
ASSERT_NO_FATAL_FAILURE(s->SetPublicKeyFromSubjectPublicKey(
prov_cast.drm_key_type(), prov_cast.drm_public_key().data(),
prov_cast.drm_public_key().size()));
ASSERT_NO_FATAL_FAILURE(prov_cast.SignAndVerifyRequest());
if (!load_drm_before_prov_req) {
ASSERT_NO_FATAL_FAILURE(prov_cast.LoadDRMPrivateKey());
}
// Generate derived keys in order to verify and decrypt response.
// We are cheating a little bit here since this GenerateDerivedKeys helper
// simulates work on both client side (calls
// OEMCrypto_GenerateDerivedKeysFromSessionKey) and server side (sets
// key_deriver() keys used to create response)
ASSERT_NO_FATAL_FAILURE(s->GenerateDerivedKeysFromSessionKey());
// Response is provisioning 2 with CAST key
ASSERT_NO_FATAL_FAILURE(prov_cast.CreateDefaultResponse());
ASSERT_NO_FATAL_FAILURE(prov_cast.EncryptAndSignResponse());
// Should parse and load successfully
ASSERT_EQ(OEMCrypto_SUCCESS, prov_cast.LoadResponse());
}
} // namespace wvoec } // namespace wvoec

View File

@@ -44,6 +44,8 @@ class SessionUtil {
// Create a new DRM Cert. Only for provisioning 4.0 // Create a new DRM Cert. Only for provisioning 4.0
void CreateProv4DRMKey(); void CreateProv4DRMKey();
void CreateProv4CastKey(Session *s, bool load_drm_before_prov_req);
// Used by prov2.0, prov3.0, and prov 4.0 // Used by prov2.0, prov3.0, and prov 4.0
std::vector<uint8_t> encoded_rsa_key_; std::vector<uint8_t> encoded_rsa_key_;
std::vector<uint8_t> wrapped_drm_key_; std::vector<uint8_t> wrapped_drm_key_;