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());
|
||||
}
|
||||
|
||||
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(
|
||||
const vector<uint8_t>& data, const vector<uint8_t>& generated_signature,
|
||||
size_t core_message_length) {
|
||||
|
||||
@@ -368,6 +368,56 @@ class Provisioning40RoundTrip
|
||||
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
|
||||
: public RoundTrip<
|
||||
/* CoreRequest */ oemcrypto_core_message::ODK_LicenseRequest,
|
||||
|
||||
@@ -620,6 +620,42 @@ TEST_F(OEMCryptoProv40Test, ProvisionDrmCert) {
|
||||
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) {
|
||||
ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey());
|
||||
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
|
||||
//
|
||||
|
||||
@@ -167,4 +167,45 @@ void SessionUtil::CreateProv4DRMKey() {
|
||||
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
|
||||
|
||||
@@ -44,6 +44,8 @@ class SessionUtil {
|
||||
// Create a new DRM Cert. Only for provisioning 4.0
|
||||
void CreateProv4DRMKey();
|
||||
|
||||
void CreateProv4CastKey(Session *s, bool load_drm_before_prov_req);
|
||||
|
||||
// Used by prov2.0, prov3.0, and prov 4.0
|
||||
std::vector<uint8_t> encoded_rsa_key_;
|
||||
std::vector<uint8_t> wrapped_drm_key_;
|
||||
|
||||
Reference in New Issue
Block a user