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:
committed by
Robert Shih
parent
5a17d8ebd9
commit
27421a9161
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
Reference in New Issue
Block a user