Exposing the Cast Signing Algorithm
1. Exposing the Cast Signing Algorithm in cdm core. 2. Update core Cast tests to use new core CDM Cast signing API. Bug: 279671867 Bug: 279672538 Change-Id: Ia73c4b5e6dd61edf790bca97a321881d310e7a99
This commit is contained in:
@@ -388,6 +388,13 @@ class CdmEngine {
|
|||||||
virtual void SetDefaultOtaKeyboxFallbackDurationRules();
|
virtual void SetDefaultOtaKeyboxFallbackDurationRules();
|
||||||
virtual void SetFastOtaKeyboxFallbackDurationRules();
|
virtual void SetFastOtaKeyboxFallbackDurationRules();
|
||||||
|
|
||||||
|
// A signing method specifically used by Cast.
|
||||||
|
// This method should not be used otherwise.
|
||||||
|
virtual CdmResponseType SignRSA(const std::string& wrapped_key,
|
||||||
|
const std::string& message,
|
||||||
|
std::string* signature,
|
||||||
|
RSA_Padding_Scheme padding_scheme);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class CdmEngineFactory;
|
friend class CdmEngineFactory;
|
||||||
|
|
||||||
|
|||||||
@@ -219,6 +219,13 @@ class CdmSession {
|
|||||||
|
|
||||||
virtual metrics::SessionMetrics* GetMetrics() { return metrics_.get(); }
|
virtual metrics::SessionMetrics* GetMetrics() { return metrics_.get(); }
|
||||||
|
|
||||||
|
virtual CdmResponseType LoadCastPrivateKey(
|
||||||
|
const CryptoWrappedKey& private_key);
|
||||||
|
|
||||||
|
virtual CdmResponseType GenerateRSASignature(const std::string& message,
|
||||||
|
std::string* signature,
|
||||||
|
RSA_Padding_Scheme scheme);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class CdmSessionTest;
|
friend class CdmSessionTest;
|
||||||
|
|
||||||
|
|||||||
@@ -347,6 +347,11 @@ class CryptoSession {
|
|||||||
virtual CdmResponseType LoadOtaProvisioning(bool use_test_key,
|
virtual CdmResponseType LoadOtaProvisioning(bool use_test_key,
|
||||||
const std::string& response);
|
const std::string& response);
|
||||||
|
|
||||||
|
// Cast specific generate signature method.
|
||||||
|
virtual CdmResponseType GenerateRsaSignature(const std::string& message,
|
||||||
|
std::string* signature,
|
||||||
|
RSA_Padding_Scheme scheme);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Creates an instance of CryptoSession with the given |crypto_metrics|.
|
// Creates an instance of CryptoSession with the given |crypto_metrics|.
|
||||||
// |crypto_metrics| is owned by the caller, must NOT be null, and must
|
// |crypto_metrics| is owned by the caller, must NOT be null, and must
|
||||||
@@ -385,8 +390,6 @@ class CryptoSession {
|
|||||||
// Note: This function will lock the global static field lock in write mode.
|
// Note: This function will lock the global static field lock in write mode.
|
||||||
bool SetUpUsageTable(RequestedSecurityLevel requested_security_level);
|
bool SetUpUsageTable(RequestedSecurityLevel requested_security_level);
|
||||||
|
|
||||||
CdmResponseType GenerateRsaSignature(const std::string& message,
|
|
||||||
std::string* signature);
|
|
||||||
size_t GetMaxSubsampleRegionSize();
|
size_t GetMaxSubsampleRegionSize();
|
||||||
|
|
||||||
bool SetDestinationBufferType();
|
bool SetDestinationBufferType();
|
||||||
|
|||||||
@@ -460,6 +460,7 @@ enum CdmResponseEnum : int32_t {
|
|||||||
STORE_ATSC_LICENSE_DEVICE_FILES_INIT_ERROR = 394,
|
STORE_ATSC_LICENSE_DEVICE_FILES_INIT_ERROR = 394,
|
||||||
STORE_ATSC_LICENSE_ERROR = 395,
|
STORE_ATSC_LICENSE_ERROR = 395,
|
||||||
SESSION_NOT_FOUND_GENERIC_CRYPTO = 396,
|
SESSION_NOT_FOUND_GENERIC_CRYPTO = 396,
|
||||||
|
SESSION_NOT_FOUND_24 = 397,
|
||||||
// Don't forget to add new values to
|
// Don't forget to add new values to
|
||||||
// * core/src/wv_cdm_types.cpp
|
// * core/src/wv_cdm_types.cpp
|
||||||
// * android/include/mapErrors-inl.h
|
// * android/include/mapErrors-inl.h
|
||||||
|
|||||||
@@ -2340,4 +2340,48 @@ void CdmEngine::SetFastOtaKeyboxFallbackDurationRules() {
|
|||||||
}
|
}
|
||||||
system_fallback_policy->SetFastBackoffDurationRules();
|
system_fallback_policy->SetFastBackoffDurationRules();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CdmResponseType CdmEngine::SignRSA(const std::string& wrapped_key,
|
||||||
|
const std::string& message,
|
||||||
|
std::string* signature,
|
||||||
|
RSA_Padding_Scheme padding_scheme) {
|
||||||
|
// Try to open cdm session.
|
||||||
|
CdmSessionId session_id;
|
||||||
|
auto sts = OpenSession("com.widevine", nullptr, nullptr, &session_id);
|
||||||
|
if (sts != NO_ERROR) {
|
||||||
|
LOGE("OpenSession failed, status: %d", static_cast<int>(sts));
|
||||||
|
return sts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the cdm session
|
||||||
|
std::shared_ptr<CdmSession> session;
|
||||||
|
if (!session_map_.FindSession(session_id, &session)) {
|
||||||
|
LOGE("Session not found: session_id = %s", IdToString(session_id));
|
||||||
|
return CdmResponseType(SESSION_NOT_FOUND_24);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load cast private key for signing
|
||||||
|
CryptoWrappedKey key(CryptoWrappedKey::kRsa, wrapped_key);
|
||||||
|
sts = session->LoadCastPrivateKey(key);
|
||||||
|
if (sts != NO_ERROR) {
|
||||||
|
LOGE("LoadCastPrivateKey failed, status: %d", static_cast<int>(sts));
|
||||||
|
return sts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Rsa signature for cast message
|
||||||
|
sts = session->GenerateRSASignature(message, signature, padding_scheme);
|
||||||
|
if (sts != NO_ERROR) {
|
||||||
|
LOGE("GenerateRSASignature failed, status: %d", static_cast<int>(sts));
|
||||||
|
return sts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to close cdm session.
|
||||||
|
sts = CloseSession(session_id);
|
||||||
|
if (sts != NO_ERROR) {
|
||||||
|
LOGE("CloseSession failed, status: %d", static_cast<int>(sts));
|
||||||
|
return sts;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sts;
|
||||||
|
}
|
||||||
} // namespace wvcdm
|
} // namespace wvcdm
|
||||||
|
|||||||
@@ -1302,6 +1302,18 @@ bool CdmSession::HasRootOfTrustBeenRenewed() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CdmResponseType CdmSession::LoadCastPrivateKey(
|
||||||
|
const CryptoWrappedKey& private_key) {
|
||||||
|
return crypto_session_->LoadCertificatePrivateKey(private_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
CdmResponseType CdmSession::GenerateRSASignature(const std::string& message,
|
||||||
|
std::string* signature,
|
||||||
|
RSA_Padding_Scheme scheme) {
|
||||||
|
return crypto_session_->GenerateRsaSignature(message, signature,
|
||||||
|
scheme);
|
||||||
|
}
|
||||||
|
|
||||||
// For testing only - takes ownership of pointers
|
// For testing only - takes ownership of pointers
|
||||||
|
|
||||||
void CdmSession::set_license_parser(CdmLicense* license_parser) {
|
void CdmSession::set_license_parser(CdmLicense* license_parser) {
|
||||||
|
|||||||
@@ -1635,7 +1635,8 @@ CdmResponseType CryptoSession::GenerateDerivedKeys(
|
|||||||
}
|
}
|
||||||
|
|
||||||
CdmResponseType CryptoSession::GenerateRsaSignature(const std::string& message,
|
CdmResponseType CryptoSession::GenerateRsaSignature(const std::string& message,
|
||||||
std::string* signature) {
|
std::string* signature,
|
||||||
|
RSA_Padding_Scheme scheme) {
|
||||||
LOGV("Generating RSA signature: id = %u", oec_session_id_);
|
LOGV("Generating RSA signature: id = %u", oec_session_id_);
|
||||||
RETURN_IF_NULL(signature, PARAMETER_NULL);
|
RETURN_IF_NULL(signature, PARAMETER_NULL);
|
||||||
|
|
||||||
@@ -1652,7 +1653,7 @@ CdmResponseType CryptoSession::GenerateRsaSignature(const std::string& message,
|
|||||||
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
||||||
message.size(),
|
message.size(),
|
||||||
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
|
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
|
||||||
&length, kSign_RSASSA_PSS),
|
&length, scheme),
|
||||||
metrics_, oemcrypto_generate_rsa_signature_, sts,
|
metrics_, oemcrypto_generate_rsa_signature_, sts,
|
||||||
metrics::Pow2Bucket(length));
|
metrics::Pow2Bucket(length));
|
||||||
});
|
});
|
||||||
@@ -3401,4 +3402,5 @@ CryptoSession* CryptoSessionFactory::MakeCryptoSession(
|
|||||||
metrics::CryptoMetrics* crypto_metrics) {
|
metrics::CryptoMetrics* crypto_metrics) {
|
||||||
return new CryptoSession(crypto_metrics);
|
return new CryptoSession(crypto_metrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace wvcdm
|
} // namespace wvcdm
|
||||||
|
|||||||
@@ -867,6 +867,8 @@ const char* CdmResponseEnumToString(CdmResponseEnum cdm_response_enum) {
|
|||||||
return "STORE_ATSC_LICENSE_ERROR";
|
return "STORE_ATSC_LICENSE_ERROR";
|
||||||
case SESSION_NOT_FOUND_GENERIC_CRYPTO:
|
case SESSION_NOT_FOUND_GENERIC_CRYPTO:
|
||||||
return "SESSION_NOT_FOUND_GENERIC_CRYPTO";
|
return "SESSION_NOT_FOUND_GENERIC_CRYPTO";
|
||||||
|
case SESSION_NOT_FOUND_24:
|
||||||
|
return "SESSION_NOT_FOUND_24";
|
||||||
}
|
}
|
||||||
return UnknownValueRep(cdm_response_enum);
|
return UnknownValueRep(cdm_response_enum);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,11 +114,13 @@ TEST_F(CorePIGTest, OfflineHWSecureRequired) {
|
|||||||
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
|
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CorePIGTest, CastReceiverProvisioning) {
|
TEST_F(CorePIGTest, CastReceiverProvisioningUsingCdm) {
|
||||||
auto digest = wvutil::a2b_hex( // digest info header
|
std::string digest_hex_str =
|
||||||
|
// digest info header
|
||||||
"3021300906052b0e03021a05000414"
|
"3021300906052b0e03021a05000414"
|
||||||
// sha1 of kMessage
|
// sha1 of kMessage
|
||||||
"d2662f893aaec72f3ca6decc2aa942f3949e8b21");
|
"d2662f893aaec72f3ca6decc2aa942f3949e8b21";
|
||||||
|
auto digest = wvutil::a2b_hex(digest_hex_str);
|
||||||
|
|
||||||
if (!wvoec::global_features.cast_receiver) {
|
if (!wvoec::global_features.cast_receiver) {
|
||||||
GTEST_SKIP() << "OEMCrypto does not support CAST Receiver functionality";
|
GTEST_SKIP() << "OEMCrypto does not support CAST Receiver functionality";
|
||||||
@@ -129,56 +131,30 @@ TEST_F(CorePIGTest, CastReceiverProvisioning) {
|
|||||||
config_.provisioning_service_certificate());
|
config_.provisioning_service_certificate());
|
||||||
provisioner.Provision(kCertificateX509, binary_provisioning_);
|
provisioner.Provision(kCertificateX509, binary_provisioning_);
|
||||||
|
|
||||||
// cdm_engine_.OpenSession here is to load test keybox
|
// cdm_engine_.SignRSA
|
||||||
// in order to successfully OEMCrypto_LoadDRMPrivateKey
|
std::string signature_str;
|
||||||
std::string session_id;
|
std::string digest_str(digest.begin(), digest.end());
|
||||||
CdmResponseType status = cdm_engine_.OpenSession(
|
ASSERT_EQ(NO_ERROR, cdm_engine_.SignRSA(provisioner.wrapped_key(), digest_str,
|
||||||
config_.key_system(), nullptr, nullptr, &session_id);
|
&signature_str, kSign_PKCS1_Block1));
|
||||||
ASSERT_EQ(NO_ERROR, status);
|
|
||||||
ASSERT_TRUE(cdm_engine_.IsOpenSession(session_id));
|
|
||||||
|
|
||||||
std::string wrapped_key_str = provisioner.wrapped_key();
|
// Verify the generated signature
|
||||||
std::vector<uint8_t> wrapped_key(wrapped_key_str.begin(),
|
std::vector<uint8_t> signature(signature_str.begin(), signature_str.end());
|
||||||
wrapped_key_str.end());
|
|
||||||
OEMCrypto_SESSION oemcrypto_session;
|
|
||||||
OEMCryptoResult sts =
|
|
||||||
OEMCrypto_OpenSession(&oemcrypto_session, kLevelDefault);
|
|
||||||
if (sts != OEMCrypto_SUCCESS) {
|
|
||||||
LOGE("Fail in OEMCrypto_OpenSession");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop test when OEMCrypto_LoadDRMPrivateKey fails.
|
|
||||||
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadDRMPrivateKey(
|
|
||||||
oemcrypto_session, OEMCrypto_RSA_Private_Key,
|
|
||||||
wrapped_key.data(), wrapped_key.size()));
|
|
||||||
|
|
||||||
// Generate signature for the digest
|
|
||||||
size_t signatureSize = 0;
|
|
||||||
ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER,
|
|
||||||
OEMCrypto_GenerateRSASignature(oemcrypto_session, digest.data(),
|
|
||||||
digest.size(), nullptr,
|
|
||||||
&signatureSize, kSign_PKCS1_Block1));
|
|
||||||
std::vector<uint8_t> signature;
|
|
||||||
signature.resize(signatureSize);
|
|
||||||
ASSERT_EQ(OEMCrypto_SUCCESS,
|
|
||||||
OEMCrypto_GenerateRSASignature(oemcrypto_session, digest.data(),
|
|
||||||
digest.size(), signature.data(),
|
|
||||||
&signatureSize, kSign_PKCS1_Block1));
|
|
||||||
LOGI("digest.size(): %zu, signature.size(): %zu", digest.size(),
|
LOGI("digest.size(): %zu, signature.size(): %zu", digest.size(),
|
||||||
signature.size());
|
signature.size());
|
||||||
|
|
||||||
// Verify the generated signature
|
|
||||||
std::string cert = provisioner.certificate();
|
std::string cert = provisioner.certificate();
|
||||||
const char* cert_str_ptr = cert.c_str();
|
const char* cert_str_ptr = cert.c_str();
|
||||||
LOGI("cert: %s", cert_str_ptr);
|
LOGI("cert: %s", cert_str_ptr);
|
||||||
|
|
||||||
// Extract the public key from the x509 cert chain
|
// Extract the public key from the x509 cert chain
|
||||||
BIO* bio = BIO_new(BIO_s_mem());
|
std::unique_ptr<BIO, void (*)(BIO*)> bio(BIO_new(BIO_s_mem()), BIO_free_all);
|
||||||
ASSERT_NE(bio, nullptr);
|
ASSERT_NE(bio, nullptr);
|
||||||
ASSERT_GT(BIO_puts(bio, cert_str_ptr), 0);
|
ASSERT_GT(BIO_puts(bio.get(), cert_str_ptr), 0);
|
||||||
X509* x509 = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
|
std::unique_ptr<X509, void (*)(X509*)> x509(
|
||||||
|
PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr), X509_free);
|
||||||
ASSERT_NE(x509, nullptr);
|
ASSERT_NE(x509, nullptr);
|
||||||
EVP_PKEY* pubkey = X509_get_pubkey(x509);
|
std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY*)> pubkey(
|
||||||
|
X509_get_pubkey(x509.get()), EVP_PKEY_free);
|
||||||
ASSERT_NE(pubkey, nullptr);
|
ASSERT_NE(pubkey, nullptr);
|
||||||
|
|
||||||
// remove digest info header for verification
|
// remove digest info header for verification
|
||||||
@@ -188,27 +164,20 @@ TEST_F(CorePIGTest, CastReceiverProvisioning) {
|
|||||||
// Modified from openssl example
|
// Modified from openssl example
|
||||||
// https://www.openssl.org/docs/man3.0/man3/EVP_PKEY_verify_init.html
|
// https://www.openssl.org/docs/man3.0/man3/EVP_PKEY_verify_init.html
|
||||||
// Set RSA padding as RSA_PKCS1_PADDING and digest algo to SHA1.
|
// Set RSA padding as RSA_PKCS1_PADDING and digest algo to SHA1.
|
||||||
EVP_PKEY_CTX* ctx;
|
|
||||||
unsigned char* md = digest.data();
|
unsigned char* md = digest.data();
|
||||||
unsigned char* sig = signature.data();
|
unsigned char* sig = signature.data();
|
||||||
size_t mdlen = digest.size();
|
size_t mdlen = digest.size();
|
||||||
size_t siglen = signature.size();
|
size_t siglen = signature.size();
|
||||||
|
|
||||||
ctx = EVP_PKEY_CTX_new(pubkey, nullptr /* no engine */);
|
std::unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX*)> ctx(
|
||||||
|
EVP_PKEY_CTX_new(pubkey.get(), nullptr /* no engine */), EVP_PKEY_CTX_free);
|
||||||
|
|
||||||
ASSERT_NE(ctx, nullptr);
|
ASSERT_NE(ctx, nullptr);
|
||||||
ASSERT_GT(EVP_PKEY_verify_init(ctx), 0);
|
ASSERT_GT(EVP_PKEY_verify_init(ctx.get()), 0);
|
||||||
ASSERT_GT(EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING), 0);
|
ASSERT_GT(EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_PADDING), 0);
|
||||||
ASSERT_GT(EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha1()), 0);
|
ASSERT_GT(EVP_PKEY_CTX_set_signature_md(ctx.get(), EVP_sha1()), 0);
|
||||||
|
|
||||||
/* Perform operation */
|
/* Perform operation */
|
||||||
EXPECT_EQ(1, EVP_PKEY_verify(ctx, sig, siglen, md, mdlen));
|
EXPECT_EQ(1, EVP_PKEY_verify(ctx.get(), sig, siglen, md, mdlen));
|
||||||
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
|
||||||
EVP_PKEY_free(pubkey);
|
|
||||||
BIO_free(bio);
|
|
||||||
X509_free(x509);
|
|
||||||
|
|
||||||
OEMCrypto_CloseSession(oemcrypto_session);
|
|
||||||
cdm_engine_.CloseSession(session_id);
|
|
||||||
}
|
}
|
||||||
} // namespace wvcdm
|
} // namespace wvcdm
|
||||||
|
|||||||
@@ -253,6 +253,7 @@ static inline WvStatus mapCdmResponseType(wvcdm::CdmResponseType res) {
|
|||||||
case wvcdm::SESSION_NOT_FOUND_14:
|
case wvcdm::SESSION_NOT_FOUND_14:
|
||||||
case wvcdm::SESSION_NOT_FOUND_15:
|
case wvcdm::SESSION_NOT_FOUND_15:
|
||||||
case wvcdm::SESSION_NOT_FOUND_16:
|
case wvcdm::SESSION_NOT_FOUND_16:
|
||||||
|
case wvcdm::SESSION_NOT_FOUND_24:
|
||||||
case wvcdm::SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE:
|
case wvcdm::SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE:
|
||||||
case wvcdm::STORAGE_PROHIBITED:
|
case wvcdm::STORAGE_PROHIBITED:
|
||||||
case wvcdm::STORE_LICENSE_ERROR_2:
|
case wvcdm::STORE_LICENSE_ERROR_2:
|
||||||
|
|||||||
Reference in New Issue
Block a user