From 27421a9161b1ba6ff53056f3dda62dfdbfd40d0c Mon Sep 17 00:00:00 2001 From: Matt Feddersen Date: Tue, 25 Apr 2023 23:21:33 +0000 Subject: [PATCH] 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 --- .../oemcrypto/test/oec_session_util.cpp | 155 ++++++++++++++++++ .../oemcrypto/test/oec_session_util.h | 50 ++++++ .../test/oemcrypto_provisioning_test.cpp | 36 ++++ .../test/oemcrypto_provisioning_test.h | 13 ++ .../test/oemcrypto_session_tests_helper.cpp | 41 +++++ .../test/oemcrypto_session_tests_helper.h | 2 + 6 files changed, 297 insertions(+) diff --git a/libwvdrmengine/oemcrypto/test/oec_session_util.cpp b/libwvdrmengine/oemcrypto/test/oec_session_util.cpp index 1664666e..040472f6 100644 --- a/libwvdrmengine/oemcrypto/test/oec_session_util.cpp +++ b/libwvdrmengine/oemcrypto/test/oec_session_util.cpp @@ -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 public_key(buffer_size); + size_t public_key_size = buffer_size; + std::vector public_key_signature(buffer_size); + size_t public_key_signature_size = buffer_size; + std::vector 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& data, const vector& 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(&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(&core_response_), + sizeof(ODK_ParsedProvisioning)); + AppendToFile(file_name, reinterpret_cast(&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& data, const vector& generated_signature, size_t core_message_length) { diff --git a/libwvdrmengine/oemcrypto/test/oec_session_util.h b/libwvdrmengine/oemcrypto/test/oec_session_util.h index c366ce52..5980be8f 100644 --- a/libwvdrmengine/oemcrypto/test/oec_session_util.h +++ b/libwvdrmengine/oemcrypto/test/oec_session_util.h @@ -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& 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& wrapped_drm_key() { return wrapped_drm_key_; } + const std::vector& 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& data, + const vector& 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 wrapped_oem_key_; + std::vector oem_public_key_; + OEMCrypto_PrivateKeyType oem_key_type_; + std::vector wrapped_drm_key_; + std::vector drm_public_key_; + OEMCrypto_PrivateKeyType drm_key_type_; + std::vector encoded_rsa_key_; + std::vector wrapped_rsa_key_; +}; + class LicenseRoundTrip : public RoundTrip< /* CoreRequest */ oemcrypto_core_message::ODK_LicenseRequest, diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp index cce63156..f3bc963b 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.cpp @@ -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 public_key(public_key_size); + size_t public_key_signature_size = 10000; + std::vector public_key_signature(public_key_signature_size); + size_t wrapped_private_key_size = 10000; + std::vector 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; diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h index b78629a6..3053c2bc 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_provisioning_test.h @@ -58,6 +58,19 @@ class OEMCryptoProv40Test : public OEMCryptoClientTest { } }; +class OEMCryptoProv40CastTest : public OEMCryptoClientTest, + public testing::WithParamInterface { + 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 // diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.cpp index 48e163fb..2a3d5250 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.cpp @@ -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 diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.h b/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.h index b29f37ee..1b352392 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.h +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_session_tests_helper.h @@ -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 encoded_rsa_key_; std::vector wrapped_drm_key_;