From 5ad2bd7ba334778253cdda5fb7412012fbbc0f13 Mon Sep 17 00:00:00 2001 From: Alex Dale Date: Thu, 18 Feb 2021 14:33:15 -0800 Subject: [PATCH] Added unittests for reference RSA implementation. [ Merge of http://go/wvgerrit/115546 ] Included a set of unittests for RSA keys which ensure client-server RSA operations work as expected. Bug: 135283522 Test: oemcrypto_unittests Change-Id: I8363a82403d0780f3074a05c64c804e700c2b779 --- .../ref/test/oemcrypto_rsa_key_unittest.cpp | 406 ++++++++++++++++++ 1 file changed, 406 insertions(+) create mode 100644 libwvdrmengine/oemcrypto/ref/test/oemcrypto_rsa_key_unittest.cpp diff --git a/libwvdrmengine/oemcrypto/ref/test/oemcrypto_rsa_key_unittest.cpp b/libwvdrmengine/oemcrypto/ref/test/oemcrypto_rsa_key_unittest.cpp new file mode 100644 index 00000000..021a6e51 --- /dev/null +++ b/libwvdrmengine/oemcrypto/ref/test/oemcrypto_rsa_key_unittest.cpp @@ -0,0 +1,406 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Reference implementation of OEMCrypto APIs +// +#include + +#include + +#include + +#include "OEMCryptoCENC.h" +#include "log.h" +#include "oemcrypto_ref_test_utils.h" +#include "oemcrypto_rsa_key.h" + +namespace wvoec_ref { +constexpr size_t kMessageSize = 4 * 1024; // 4 kB +constexpr size_t kCastMessageSize = 83; // Special max size. + +class OEMCryptoRsaKeyTest : public ::testing::TestWithParam { + public: + void SetUp() override { + // RSA key generation is slow (~2 seconds) compared to the + // operations they perform (<50 ms). Each key type is generated + // once and globally stored in serialized form. + // Caching the instance may result in test failures for + // memory-leak detection. + const RsaFieldSize field_size = GetParam(); + std::lock_guard rsa_key_lock(rsa_key_mutex_); + // Use of a switch case is intentional to cause compiler warnings + // if a new field size is introduced without updating the test. + switch (field_size) { + case kRsa2048Bit: { + if (!rsa_2048_key_data_.empty()) { + key_ = RsaPrivateKey::Load(rsa_2048_key_data_); + } + if (!key_) { + key_ = RsaPrivateKey::New(kRsa2048Bit); + } + if (rsa_2048_key_data_.empty() && key_) { + rsa_2048_key_data_ = key_->Serialize(); + } + } break; + case kRsa3072Bit: { + if (!rsa_3072_key_data_.empty()) { + key_ = RsaPrivateKey::Load(rsa_3072_key_data_); + } + if (!key_) { + key_ = RsaPrivateKey::New(kRsa3072Bit); + } + if (rsa_3072_key_data_.empty() && key_) { + rsa_3072_key_data_ = key_->Serialize(); + } + } break; + case kRsaFieldUnknown: // Suppress compiler warnings + LOGE("RSA test was incorrectly instantiation"); + exit(EXIT_FAILURE); + break; + } + } + + void TearDown() override { key_.reset(); } + + protected: + std::unique_ptr key_; + static std::mutex rsa_key_mutex_; + static std::vector rsa_2048_key_data_; + static std::vector rsa_3072_key_data_; +}; + +std::mutex OEMCryptoRsaKeyTest::rsa_key_mutex_; +std::vector OEMCryptoRsaKeyTest::rsa_2048_key_data_; +std::vector OEMCryptoRsaKeyTest::rsa_3072_key_data_; + +// Basic verification of RSA private key generation. +TEST_P(OEMCryptoRsaKeyTest, KeyProperties) { + ASSERT_TRUE(key_); + const RsaFieldSize expected_field_size = GetParam(); + + EXPECT_EQ(key_->field_size(), expected_field_size); + EXPECT_NE(nullptr, key_->GetRsaKey()); +} + +// Checks that the private key serialization APIs are compatible +// and performing in a manner that is similar to other OEMCrypto methods +// that retrieve data. +TEST_P(OEMCryptoRsaKeyTest, SerializePrivateKey) { + ASSERT_TRUE(key_); + + constexpr size_t kInitialBufferSize = 10; // Definitely too small. + size_t buffer_size = kInitialBufferSize; + std::vector buffer(buffer_size); + + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + key_->Serialize(buffer.data(), &buffer_size)); + EXPECT_GT(buffer_size, kInitialBufferSize); + + buffer.resize(buffer_size); + EXPECT_EQ(OEMCrypto_SUCCESS, key_->Serialize(buffer.data(), &buffer_size)); + buffer.resize(buffer_size); + + const std::vector direct_key_data = key_->Serialize(); + EXPECT_FALSE(direct_key_data.empty()); + ASSERT_EQ(buffer.size(), direct_key_data.size()); + for (size_t i = 0; i < buffer.size(); i++) { + ASSERT_EQ(buffer[i], direct_key_data[i]) << "i = " << std::to_string(i); + } +} + +// Checks that a private key that is serialized can be deserialized and +// reload. Also checks that the serialization of a key produces the +// same data to ensure consistency. +TEST_P(OEMCryptoRsaKeyTest, SerializeAndReloadPrivateKey) { + ASSERT_TRUE(key_); + + const std::vector key_data = key_->Serialize(); + std::unique_ptr loaded_key = RsaPrivateKey::Load(key_data); + ASSERT_TRUE(loaded_key); + + EXPECT_EQ(key_->field_size(), loaded_key->field_size()); + + const std::vector loaded_key_data = loaded_key->Serialize(); + ASSERT_EQ(key_data.size(), loaded_key_data.size()); + for (size_t i = 0; i < key_data.size(); i++) { + ASSERT_EQ(key_data[i], loaded_key_data[i]) << "i = " << std::to_string(i); + } +} + +// Checks that a private key with explicitly indicated schemes include +// the scheme fields in the reserialized key. +TEST_P(OEMCryptoRsaKeyTest, SerializeAndReloadPrivateKeyWithAllowedSchemes) { + ASSERT_TRUE(key_); + + const std::vector raw_key_data = key_->Serialize(); + std::vector key_data = {'S', 'I', 'G', 'N', 0x00, 0x00, 0x00, 0x03}; + key_data.insert(key_data.end(), raw_key_data.begin(), raw_key_data.end()); + + std::unique_ptr explicit_key = RsaPrivateKey::Load(key_data); + ASSERT_TRUE(explicit_key); + EXPECT_EQ(key_->field_size(), explicit_key->field_size()); + EXPECT_EQ(explicit_key->allowed_schemes(), 0x03); + + const std::vector explicit_key_data = explicit_key->Serialize(); + ASSERT_EQ(key_data.size(), explicit_key_data.size()); + ASSERT_EQ(key_data, explicit_key_data); +} + +// Checks that a public key can be created from the private key. +TEST_P(OEMCryptoRsaKeyTest, DerivePublicKey) { + ASSERT_TRUE(key_); + + std::unique_ptr pub_key = key_->MakePublicKey(); + ASSERT_TRUE(pub_key); + + EXPECT_TRUE(key_->IsMatchingPublicKey(*pub_key)); +} + +// Checks that the public key serialization APIs are compatible +// and performing in a manner that is similar to other OEMCrypto methods +// that retrieve data. +TEST_P(OEMCryptoRsaKeyTest, SerializePublicKey) { + ASSERT_TRUE(key_); + + std::unique_ptr pub_key = key_->MakePublicKey(); + ASSERT_TRUE(pub_key); + + constexpr size_t kInitialBufferSize = 10; // Definitely too small. + size_t buffer_size = kInitialBufferSize; + std::vector buffer(buffer_size); + + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + pub_key->Serialize(buffer.data(), &buffer_size)); + EXPECT_GT(buffer_size, kInitialBufferSize); + + buffer.resize(buffer_size); + EXPECT_EQ(OEMCrypto_SUCCESS, pub_key->Serialize(buffer.data(), &buffer_size)); + buffer.resize(buffer_size); + + const std::vector direct_key_data = pub_key->Serialize(); + EXPECT_FALSE(direct_key_data.empty()); + ASSERT_EQ(buffer.size(), direct_key_data.size()); + for (size_t i = 0; i < buffer.size(); i++) { + ASSERT_EQ(buffer[i], direct_key_data[i]) << "i = " << std::to_string(i); + } +} + +// Checks that a public key that is serialized can be deserialized and +// reload. Also checks that the serialization of a key produces the +// same data to ensure consistency. +TEST_P(OEMCryptoRsaKeyTest, SerializeAndReloadPublicKey) { + ASSERT_TRUE(key_); + + std::unique_ptr pub_key = key_->MakePublicKey(); + ASSERT_TRUE(pub_key); + + const std::vector key_data = pub_key->Serialize(); + + std::unique_ptr loaded_key = RsaPublicKey::Load(key_data); + ASSERT_TRUE(loaded_key); + + EXPECT_EQ(pub_key->field_size(), loaded_key->field_size()); + EXPECT_EQ(pub_key->allowed_schemes(), loaded_key->allowed_schemes()); + + const std::vector loaded_key_data = loaded_key->Serialize(); + ASSERT_EQ(key_data.size(), loaded_key_data.size()); + for (size_t i = 0; i < key_data.size(); i++) { + ASSERT_EQ(key_data[i], loaded_key_data[i]) << "i = " << std::to_string(i); + } +} + +// Checks that the RSA signature generating API operates similar to +// existing signature generation functions. +TEST_P(OEMCryptoRsaKeyTest, GenerateSignature) { + ASSERT_TRUE(key_); + + const std::vector message = RandomData(kMessageSize); + ASSERT_FALSE(message.empty()) << "CdmRandom failed"; + + constexpr size_t kInitialBufferSize = 10; // Definitely too small. + size_t signature_size = kInitialBufferSize; + std::vector signature(signature_size); + EXPECT_EQ( + OEMCrypto_ERROR_SHORT_BUFFER, + key_->GenerateSignature(message.data(), message.size(), kRsaPssDefault, + signature.data(), &signature_size)); + EXPECT_GT(signature_size, kInitialBufferSize); + + signature.resize(signature_size); + EXPECT_EQ( + OEMCrypto_SUCCESS, + key_->GenerateSignature(message.data(), message.size(), kRsaPssDefault, + signature.data(), &signature_size)); + signature.resize(signature_size); + + EXPECT_LE(signature_size, key_->SignatureSize()); +} + +// Checks that RSA signatures can be verified by an RSA public key. +TEST_P(OEMCryptoRsaKeyTest, VerifySignature) { + ASSERT_TRUE(key_); + const std::vector message = RandomData(kMessageSize); + ASSERT_FALSE(message.empty()) << "CdmRandom failed"; + + const std::vector signature = key_->GenerateSignature(message); + + std::unique_ptr pub_key = key_->MakePublicKey(); + ASSERT_TRUE(pub_key); + + EXPECT_EQ(OEMCrypto_SUCCESS, pub_key->VerifySignature(message, signature)); + + // Check with different message. + const std::vector message_two = RandomData(kMessageSize); + EXPECT_EQ(OEMCrypto_ERROR_SIGNATURE_FAILURE, + pub_key->VerifySignature(message_two, signature)); + + // Check with bad signature. + const std::vector bad_signature = RandomData(signature.size()); + EXPECT_EQ(OEMCrypto_ERROR_SIGNATURE_FAILURE, + pub_key->VerifySignature(message, bad_signature)); +} + +// Checks that the special CAST receiver signature scheme works +// to the degree that it is possible to test. +TEST_P(OEMCryptoRsaKeyTest, GenerateAndVerifyRsaSignature) { + ASSERT_TRUE(key_); + // Key must be enabled for PKCS1 Block 1 padding scheme. + // To do so, the key is serialized and the padding scheme is + // added to the key data. + const std::vector key_data = key_->Serialize(); + ASSERT_FALSE(key_data.empty()); + std::vector pkcs_enabled_key_data = { + 'S', 'I', 'G', 'N', 0x00, 0x00, 0x00, kSign_PKCS1_Block1}; + pkcs_enabled_key_data.insert(pkcs_enabled_key_data.end(), key_data.begin(), + key_data.end()); + std::unique_ptr pkcs_enabled_key = + RsaPrivateKey::Load(pkcs_enabled_key_data); + ASSERT_TRUE(pkcs_enabled_key); + + // The actual cast message is a domain specific hash of the message, + // however, random data works for testing purposes. + const std::vector message = RandomData(kCastMessageSize); + + // Generate signature. + constexpr size_t kInitialBufferSize = 10; // Definitely too small. + size_t signature_size = kInitialBufferSize; + std::vector signature(signature_size); + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + pkcs_enabled_key->GenerateSignature(message.data(), message.size(), + kRsaPkcs1Cast, signature.data(), + &signature_size)); + EXPECT_GT(signature_size, kInitialBufferSize); + signature.resize(signature_size); + EXPECT_EQ(OEMCrypto_SUCCESS, + pkcs_enabled_key->GenerateSignature(message.data(), message.size(), + kRsaPkcs1Cast, signature.data(), + &signature_size)); + signature.resize(signature_size); + + EXPECT_LE(signature_size, pkcs_enabled_key->SignatureSize()); + + // Verify signature. + std::unique_ptr pub_key = pkcs_enabled_key->MakePublicKey(); + ASSERT_TRUE(pub_key); + + EXPECT_EQ(OEMCrypto_SUCCESS, + pub_key->VerifySignature(message, signature, kRsaPkcs1Cast)); +} + +// Verifies the session key exchange protocol used by the licensing +// server. +TEST_P(OEMCryptoRsaKeyTest, ShareSessionKey) { + constexpr size_t kSessionKeySize = 16; + ASSERT_TRUE(key_); + std::unique_ptr public_key = key_->MakePublicKey(); + ASSERT_TRUE(public_key); + + // Generate session key. + const std::vector session_key = RandomData(kSessionKeySize); + ASSERT_FALSE(session_key.empty()); + + // Server's perspective. + constexpr size_t kInitialBufferSize = 10; // Definitely too small. + size_t enc_session_key_size = kInitialBufferSize; + std::vector enc_session_key(enc_session_key_size); + OEMCryptoResult result = public_key->EncryptSessionKey( + session_key.data(), session_key.size(), enc_session_key.data(), + &enc_session_key_size); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, result); + enc_session_key.resize(enc_session_key_size); + result = public_key->EncryptSessionKey(session_key.data(), session_key.size(), + enc_session_key.data(), + &enc_session_key_size); + ASSERT_EQ(OEMCrypto_SUCCESS, result); + enc_session_key.resize(enc_session_key_size); + + // Client's perspective. + size_t received_session_key_size = kInitialBufferSize; + std::vector received_session_key(received_session_key_size); + result = key_->DecryptSessionKey( + enc_session_key.data(), enc_session_key.size(), + received_session_key.data(), &received_session_key_size); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, result); + received_session_key.resize(received_session_key_size); + result = key_->DecryptSessionKey( + enc_session_key.data(), enc_session_key.size(), + received_session_key.data(), &received_session_key_size); + ASSERT_EQ(OEMCrypto_SUCCESS, result); + received_session_key.resize(received_session_key_size); + + // Compare keys. + ASSERT_EQ(session_key.size(), received_session_key.size()); + ASSERT_EQ(session_key, received_session_key); +} + +// Verifies the encryption key exchange protocol used by the licensing +// server. +TEST_P(OEMCryptoRsaKeyTest, ShareEncryptionKey) { + constexpr size_t kEncryptionKeySize = 16; + ASSERT_TRUE(key_); + std::unique_ptr public_key = key_->MakePublicKey(); + ASSERT_TRUE(public_key); + + // Generate session key. + const std::vector encryption_key = RandomData(kEncryptionKeySize); + ASSERT_FALSE(encryption_key.empty()); + + // Server's perspective. + constexpr size_t kInitialBufferSize = 10; // Definitely too small. + size_t enc_encryption_key_size = kInitialBufferSize; + std::vector enc_encryption_key(enc_encryption_key_size); + OEMCryptoResult result = public_key->EncryptEncryptionKey( + encryption_key.data(), encryption_key.size(), enc_encryption_key.data(), + &enc_encryption_key_size); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, result); + enc_encryption_key.resize(enc_encryption_key_size); + result = public_key->EncryptEncryptionKey( + encryption_key.data(), encryption_key.size(), enc_encryption_key.data(), + &enc_encryption_key_size); + ASSERT_EQ(OEMCrypto_SUCCESS, result); + enc_encryption_key.resize(enc_encryption_key_size); + + // Client's perspective. + size_t received_encryption_key_size = kInitialBufferSize; + std::vector received_encryption_key(received_encryption_key_size); + result = key_->DecryptEncryptionKey( + enc_encryption_key.data(), enc_encryption_key.size(), + received_encryption_key.data(), &received_encryption_key_size); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, result); + received_encryption_key.resize(received_encryption_key_size); + result = key_->DecryptEncryptionKey( + enc_encryption_key.data(), enc_encryption_key.size(), + received_encryption_key.data(), &received_encryption_key_size); + ASSERT_EQ(OEMCrypto_SUCCESS, result); + received_encryption_key.resize(received_encryption_key_size); + + // Compare keys. + ASSERT_EQ(encryption_key.size(), received_encryption_key.size()); + ASSERT_EQ(encryption_key, received_encryption_key); +} + +INSTANTIATE_TEST_CASE_P(AllFieldSizes, OEMCryptoRsaKeyTest, + ::testing::Values(kRsa2048Bit, kRsa3072Bit)); + +} // namespace wvoec_ref