From 3db90f54c177f5968fc91c9a2479791a13a9af23 Mon Sep 17 00:00:00 2001 From: Jeff Tinker Date: Mon, 10 Mar 2014 12:41:14 -0700 Subject: [PATCH] Support CAST V2 authentication bug: 12702350 Squashed commit of these CLs from the widevine cdm repo: Cast V2 cdm support https://widevine-internal-review.googlesource.com/#/c/9190/ Add CASTv2 Support to DrmPlugin https://widevine-internal-review.googlesource.com/#/c/9228/ Test for CastV2 authentication APIs https://widevine-internal-review.googlesource.com/9550 Change-Id: I6d66bc1bbd653db5542c68687b30b441dd20617f --- libwvdrmengine/cdm/core/include/cdm_engine.h | 6 +- .../core/include/certificate_provisioning.h | 9 +- .../cdm/core/include/wv_cdm_types.h | 5 + libwvdrmengine/cdm/core/src/cdm_engine.cpp | 21 +- .../cdm/core/src/certificate_provisioning.cpp | 32 ++- .../cdm/core/src/license_protocol.proto | 23 ++ .../cdm/core/test/cdm_engine_test.cpp | 9 +- .../include/wv_content_decryption_module.h | 9 +- .../cdm/src/wv_content_decryption_module.cpp | 14 +- .../cdm/test/request_license_test.cpp | 80 ++++++- libwvdrmengine/include/WVErrors.h | 4 +- libwvdrmengine/mediadrm/include/WVDrmPlugin.h | 16 +- .../include/WVGenericCryptoInterface.h | 27 +++ libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp | 120 +++++++++- .../mediadrm/test/WVDrmPlugin_test.cpp | 38 +++- libwvdrmengine/test/castv2/Android.mk | 14 ++ .../test/castv2/AndroidManifest.xml | 22 ++ libwvdrmengine/test/castv2/default.properties | 2 + .../test/castv2/res/drawable-hdpi/icon.png | Bin 0 -> 4147 bytes .../test/castv2/res/drawable-ldpi/icon.png | Bin 0 -> 1723 bytes .../test/castv2/res/drawable-mdpi/icon.png | Bin 0 -> 2574 bytes .../test/castv2/res/layout/main.xml | 6 + .../test/castv2/res/values/strings.xml | 4 + .../android/castv2/test/CastSignAPITest.java | 206 ++++++++++++++++++ .../castv2/test/CertificateRequester.java | 124 +++++++++++ .../castv2/test/ProvisionRequester.java | 117 ++++++++++ 26 files changed, 864 insertions(+), 44 deletions(-) create mode 100644 libwvdrmengine/test/castv2/Android.mk create mode 100644 libwvdrmengine/test/castv2/AndroidManifest.xml create mode 100644 libwvdrmengine/test/castv2/default.properties create mode 100644 libwvdrmengine/test/castv2/res/drawable-hdpi/icon.png create mode 100644 libwvdrmengine/test/castv2/res/drawable-ldpi/icon.png create mode 100644 libwvdrmengine/test/castv2/res/drawable-mdpi/icon.png create mode 100644 libwvdrmengine/test/castv2/res/layout/main.xml create mode 100644 libwvdrmengine/test/castv2/res/values/strings.xml create mode 100644 libwvdrmengine/test/castv2/src/com/android/castv2/test/CastSignAPITest.java create mode 100644 libwvdrmengine/test/castv2/src/com/android/castv2/test/CertificateRequester.java create mode 100644 libwvdrmengine/test/castv2/src/com/android/castv2/test/ProvisionRequester.java diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index 03f8d244..dc5b7825 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -80,11 +80,15 @@ class CdmEngine : public TimerHandler { // Provisioning related methods virtual CdmResponseType GetProvisioningRequest( + CdmCertificateType cert_type, + const std::string& cert_authority, CdmProvisioningRequest* request, std::string* default_url); virtual CdmResponseType HandleProvisioningResponse( - CdmProvisioningResponse& response); + CdmProvisioningResponse& response, + std::string* cert, + std::string* wrapped_key); // Secure stop related methods virtual CdmResponseType GetSecureStops(CdmSecureStops* secure_stops); diff --git a/libwvdrmengine/cdm/core/include/certificate_provisioning.h b/libwvdrmengine/cdm/core/include/certificate_provisioning.h index 9e80a197..0ccd9b29 100644 --- a/libwvdrmengine/cdm/core/include/certificate_provisioning.h +++ b/libwvdrmengine/cdm/core/include/certificate_provisioning.h @@ -13,14 +13,18 @@ class CdmSession; class CertificateProvisioning { public: - CertificateProvisioning() {}; + CertificateProvisioning() : cert_type_(kCertificateWidevine) {}; ~CertificateProvisioning() {}; // Provisioning related methods CdmResponseType GetProvisioningRequest(SecurityLevel requested_security_level, + CdmCertificateType cert_type, + const std::string& cert_authority, CdmProvisioningRequest* request, std::string* default_url); - CdmResponseType HandleProvisioningResponse(CdmProvisioningResponse& response); + CdmResponseType HandleProvisioningResponse(CdmProvisioningResponse& response, + std::string* cert, + std::string* wrapped_key); private: void ComposeJsonRequestAsQueryString(const std::string& message, @@ -30,6 +34,7 @@ class CertificateProvisioning { const std::string& end_substr, std::string* result); CryptoSession crypto_session_; + CdmCertificateType cert_type_; CORE_DISALLOW_COPY_AND_ASSIGN(CertificateProvisioning); }; diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_types.h b/libwvdrmengine/cdm/core/include/wv_cdm_types.h index ad418081..0b59fed9 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_types.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_types.h @@ -64,6 +64,11 @@ enum CdmSecurityLevel { kSecurityLevelUnknown }; +enum CdmCertificateType { + kCertificateWidevine, + kCertificateX509, +}; + struct CdmDecryptionParameters { bool is_encrypted; bool is_secure; diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index de70bb89..dd61c4ab 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -446,6 +446,8 @@ CdmResponseType CdmEngine::QueryKeyControlInfo( * Returns NO_ERROR for success and UNKNOWN_ERROR if fails. */ CdmResponseType CdmEngine::GetProvisioningRequest( + CdmCertificateType cert_type, + const std::string& cert_authority, CdmProvisioningRequest* request, std::string* default_url) { if (!request || !default_url) { @@ -454,6 +456,8 @@ CdmResponseType CdmEngine::GetProvisioningRequest( } return cert_provisioning_.GetProvisioningRequest( cert_provisioning_requested_security_level_, + cert_type, + cert_authority, request, default_url); } @@ -466,12 +470,25 @@ CdmResponseType CdmEngine::GetProvisioningRequest( * Returns NO_ERROR for success and UNKNOWN_ERROR if fails. */ CdmResponseType CdmEngine::HandleProvisioningResponse( - CdmProvisioningResponse& response) { + CdmProvisioningResponse& response, + std::string* cert, + std::string* wrapped_key) { if (response.empty()) { LOGE("CdmEngine::HandleProvisioningResponse: Empty provisioning response."); return UNKNOWN_ERROR; } - return cert_provisioning_.HandleProvisioningResponse(response); + if (NULL == cert) { + LOGE("CdmEngine::HandleProvisioningResponse: invalid certificate " + "destination"); + return UNKNOWN_ERROR; + } + if (NULL == wrapped_key) { + LOGE("CdmEngine::HandleProvisioningResponse: invalid wrapped key " + "destination"); + return UNKNOWN_ERROR; + } + return cert_provisioning_.HandleProvisioningResponse(response, cert, + wrapped_key); } CdmResponseType CdmEngine::GetSecureStops( diff --git a/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp b/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp index 36fee47b..68a4e058 100644 --- a/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp +++ b/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp @@ -6,6 +6,7 @@ #include "license_protocol.pb.h" #include "log.h" #include "string_conversions.h" +#include "wv_cdm_constants.h" namespace { const std::string kDefaultProvisioningServerUrl = @@ -17,6 +18,7 @@ const std::string kDefaultProvisioningServerUrl = namespace wvcdm { // Protobuf generated classes. using video_widevine_server::sdk::ClientIdentification; +using video_widevine_server::sdk::ProvisioningOptions; using video_widevine_server::sdk::ProvisioningRequest; using video_widevine_server::sdk::ProvisioningResponse; using video_widevine_server::sdk::SignedProvisioningMessage; @@ -54,6 +56,8 @@ void CertificateProvisioning::ComposeJsonRequestAsQueryString( */ CdmResponseType CertificateProvisioning::GetProvisioningRequest( SecurityLevel requested_security_level, + CdmCertificateType cert_type, + const std::string& cert_authority, CdmProvisioningRequest* request, std::string* default_url) { default_url->assign(kDefaultProvisioningServerUrl); @@ -86,6 +90,24 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest( std::string the_nonce(reinterpret_cast(&nonce), sizeof(nonce)); provisioning_request.set_nonce(the_nonce); + ProvisioningOptions* options = provisioning_request.mutable_options(); + switch (cert_type) { + case kCertificateWidevine: + options->set_certificate_type( + video_widevine_server::sdk::ProvisioningOptions_CertificateType_RSA_WIDEVINE); + break; + case kCertificateX509: + options->set_certificate_type( + video_widevine_server::sdk::ProvisioningOptions_CertificateType_X509); + break; + default: + LOGE("GetProvisioningRequest: unknown certificate type %ld", cert_type); + return UNKNOWN_ERROR; + } + + cert_type_ = cert_type; + options->set_certificate_authority(cert_authority); + std::string serialized_message; provisioning_request.SerializeToString(&serialized_message); @@ -155,7 +177,9 @@ bool CertificateProvisioning::ParseJsonResponse( * Returns NO_ERROR for success and UNKNOWN_ERROR if fails. */ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( - CdmProvisioningResponse& response) { + CdmProvisioningResponse& response, + std::string* cert, + std::string* wrapped_key) { // Extracts signed response from JSON string, decodes base64 signed response const std::string kMessageStart = "\"signedResponse\": \""; @@ -211,6 +235,12 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( crypto_session_.Close(); + if (cert_type_ == kCertificateX509) { + *cert = provisioning_response.device_certificate(); + *wrapped_key = wrapped_rsa_key; + return NO_ERROR; + } + const std::string& device_certificate = provisioning_response.device_certificate(); diff --git a/libwvdrmengine/cdm/core/src/license_protocol.proto b/libwvdrmengine/cdm/core/src/license_protocol.proto index 9f0d4157..d874f15d 100644 --- a/libwvdrmengine/cdm/core/src/license_protocol.proto +++ b/libwvdrmengine/cdm/core/src/license_protocol.proto @@ -292,15 +292,38 @@ message SessionState { // Public protocol buffer definitions for Widevine Device Certificate // Provisioning protocol. +// PROPOSED message for customizing provisioning request. +// This could support requesting specificy types of certificates. +// E.g. Cast X.509 certs. +message ProvisioningOptions { + // PROPOSED enum identifying the certificate type. + enum CertificateType { + RSA_WIDEVINE = 0; // Default. The original certificate type. + X509 = 1; // X.509 certificate. + } + + optional CertificateType certificate_type = 1; + + // OPEN QUESTION: How does the client specify the cert root authority? + // Should this be the cert authority's domain? E.g. foo.com? + optional string certificate_authority = 2; +} + // Provisioning request sent by client devices to provisioning service. message ProvisioningRequest { // Device root of trust and other client identification. Required. optional ClientIdentification client_id = 1; // Nonce value used to prevent replay attacks. Required. optional bytes nonce = 2; + // Options for type of certificate to generate. Optional. + optional ProvisioningOptions options = 3; } // Provisioning response sent by the provisioning server to client devices. +// +// PROPOSAL: The contents of this message vary depending upon the value of +// CertificateType in options. TODO(blueeyes): Determine the right way to +// transfer X.509 certs. message ProvisioningResponse { // AES-128 encrypted device private RSA key. PKCS#1 ASN.1 DER-encoded. // Required. diff --git a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp index 63c250bd..286c2658 100644 --- a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp +++ b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp @@ -188,9 +188,14 @@ TEST(WvCdmProvisioningTest, ProvisioningTest) { CdmEngine cdm_engine; CdmProvisioningRequest prov_request; std::string provisioning_server_url; + CdmCertificateType cert_type = kCertificateWidevine; + std::string cert_authority; + std::string cert, wrapped_key; - cdm_engine.GetProvisioningRequest(&prov_request, &provisioning_server_url); - cdm_engine.HandleProvisioningResponse(kValidJsonProvisioningResponse); + cdm_engine.GetProvisioningRequest(cert_type, cert_authority, + &prov_request, &provisioning_server_url); + cdm_engine.HandleProvisioningResponse(kValidJsonProvisioningResponse, + &cert, &wrapped_key); } TEST_F(WvCdmEngineTest, BaseIsoBmffMessageTest) { diff --git a/libwvdrmengine/cdm/include/wv_content_decryption_module.h b/libwvdrmengine/cdm/include/wv_content_decryption_module.h index df8a558f..9c85b2b2 100644 --- a/libwvdrmengine/cdm/include/wv_content_decryption_module.h +++ b/libwvdrmengine/cdm/include/wv_content_decryption_module.h @@ -64,10 +64,15 @@ class WvContentDecryptionModule { // Provisioning related methods virtual CdmResponseType GetProvisioningRequest( - CdmProvisioningRequest* request, std::string* default_url); + CdmCertificateType cert_type, + const std::string& cert_authority, + CdmProvisioningRequest* request, + std::string* default_url); virtual CdmResponseType HandleProvisioningResponse( - CdmProvisioningResponse& response); + CdmProvisioningResponse& response, + std::string* cert, + std::string* wrapped_key); // Secure stop related methods virtual CdmResponseType GetSecureStops(CdmSecureStops* secure_stops); diff --git a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp index 50afa2d8..8ed59bb2 100644 --- a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp +++ b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp @@ -102,13 +102,19 @@ CdmResponseType WvContentDecryptionModule::QueryKeyControlInfo( } CdmResponseType WvContentDecryptionModule::GetProvisioningRequest( - CdmProvisioningRequest* request, std::string* default_url) { - return cdm_engine_->GetProvisioningRequest(request, default_url); + CdmCertificateType cert_type, + const std::string& cert_authority, + CdmProvisioningRequest* request, + std::string* default_url) { + return cdm_engine_->GetProvisioningRequest(cert_type, cert_authority, + request, default_url); } CdmResponseType WvContentDecryptionModule::HandleProvisioningResponse( - CdmProvisioningResponse& response) { - return cdm_engine_->HandleProvisioningResponse(response); + CdmProvisioningResponse& response, + std::string* cert, + std::string* wrapped_key) { + return cdm_engine_->HandleProvisioningResponse(response, cert, wrapped_key); } CdmResponseType WvContentDecryptionModule::GetSecureStops( diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index 4e160dd1..905fa27f 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -423,40 +423,82 @@ class WvCdmSessionSharingTest TEST_F(WvCdmRequestLicenseTest, ProvisioningTest) { decryptor_.OpenSession(g_key_system, NULL, &session_id_); std::string provisioning_server_url; + CdmCertificateType cert_type = kCertificateWidevine; + std::string cert_authority, cert, wrapped_key; EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.GetProvisioningRequest( + cert_type, cert_authority, &key_msg_, &provisioning_server_url)); EXPECT_EQ(provisioning_server_url, g_config->provisioning_server_url()); std::string response = GetCertRequestResponse(g_config->provisioning_test_server_url(), 200); EXPECT_NE(0, static_cast(response.size())); - EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.HandleProvisioningResponse(response)); + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_.HandleProvisioningResponse(response, &cert, + &wrapped_key)); + EXPECT_EQ(0, static_cast(cert.size())); + EXPECT_EQ(0, static_cast(wrapped_key.size())); decryptor_.CloseSession(session_id_); } TEST_F(WvCdmRequestLicenseTest, ProvisioningRetryTest) { decryptor_.OpenSession(g_key_system, NULL, &session_id_); std::string provisioning_server_url; + CdmCertificateType cert_type = kCertificateWidevine; + std::string cert_authority, cert, wrapped_key; EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.GetProvisioningRequest( + cert_type, cert_authority, &key_msg_, &provisioning_server_url)); EXPECT_EQ(provisioning_server_url, g_config->provisioning_server_url()); EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.GetProvisioningRequest( + cert_type, cert_authority, &key_msg_, &provisioning_server_url)); EXPECT_EQ(provisioning_server_url, g_config->provisioning_server_url()); std::string response = GetCertRequestResponse(g_config->provisioning_test_server_url(), 200); EXPECT_NE(0, static_cast(response.size())); - EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.HandleProvisioningResponse(response)); + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_.HandleProvisioningResponse(response, &cert, + &wrapped_key)); + EXPECT_EQ(0, static_cast(cert.size())); + EXPECT_EQ(0, static_cast(wrapped_key.size())); response = GetCertRequestResponse(g_config->provisioning_test_server_url(), 200); EXPECT_NE(0, static_cast(response.size())); EXPECT_EQ(wvcdm::UNKNOWN_ERROR, - decryptor_.HandleProvisioningResponse(response)); + decryptor_.HandleProvisioningResponse(response, &cert, + &wrapped_key)); + EXPECT_EQ(0, static_cast(cert.size())); + EXPECT_EQ(0, static_cast(wrapped_key.size())); + decryptor_.CloseSession(session_id_); +} + +TEST_F(WvCdmRequestLicenseTest, DISABLED_X509ProvisioningTest) { + decryptor_.OpenSession(g_key_system, NULL, &session_id_); + std::string provisioning_server_url; + CdmCertificateType cert_type = kCertificateX509; + // TODO(rfrias): insert appropriate CA here + std::string cert_authority = "cast.google.com"; + std::string cert, wrapped_key; + + EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.GetProvisioningRequest( + cert_type, cert_authority, + &key_msg_, &provisioning_server_url)); + EXPECT_EQ(provisioning_server_url, g_config->provisioning_server_url()); + + std::string response = + GetCertRequestResponse(g_config->provisioning_test_server_url(), 200); + EXPECT_NE(0, static_cast(response.size())); + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_.HandleProvisioningResponse(response, &cert, + &wrapped_key)); + EXPECT_NE(0, static_cast(cert.size())); + EXPECT_NE(0, static_cast(wrapped_key.size())); decryptor_.CloseSession(session_id_); } @@ -479,14 +521,19 @@ TEST_F(WvCdmRequestLicenseTest, PropertySetTest) { if (NEED_PROVISIONING == sts) { std::string provisioning_server_url; + CdmCertificateType cert_type = kCertificateWidevine; + std::string cert_authority, cert, wrapped_key; EXPECT_EQ( NO_ERROR, - decryptor_.GetProvisioningRequest(&key_msg_, &provisioning_server_url)); + decryptor_.GetProvisioningRequest(cert_type, cert_authority, + &key_msg_, &provisioning_server_url)); EXPECT_EQ(provisioning_server_url, g_config->provisioning_server_url()); std::string response = GetCertRequestResponse(g_config->provisioning_test_server_url(), 200); EXPECT_NE(0, static_cast(response.size())); - EXPECT_EQ(NO_ERROR, decryptor_.HandleProvisioningResponse(response)); + EXPECT_EQ(NO_ERROR, + decryptor_.HandleProvisioningResponse(response, &cert, + &wrapped_key)); EXPECT_EQ(NO_ERROR, decryptor_.OpenSession(g_key_system, &property_set_L3, &session_id_L3)); } else { @@ -523,14 +570,19 @@ TEST_F(WvCdmRequestLicenseTest, ForceL3Test) { EXPECT_EQ(NEED_PROVISIONING, decryptor_.OpenSession(g_key_system, &property_set, &session_id_)); std::string provisioning_server_url; + CdmCertificateType cert_type = kCertificateWidevine; + std::string cert_authority, cert, wrapped_key; EXPECT_EQ(NO_ERROR, - decryptor_.GetProvisioningRequest(&key_msg_, + decryptor_.GetProvisioningRequest(cert_type, cert_authority, + &key_msg_, &provisioning_server_url)); EXPECT_EQ(provisioning_server_url, g_config->provisioning_server_url()); std::string response = GetCertRequestResponse(g_config->provisioning_test_server_url(), 200); EXPECT_NE(0, static_cast(response.size())); - EXPECT_EQ(NO_ERROR, decryptor_.HandleProvisioningResponse(response)); + EXPECT_EQ(NO_ERROR, + decryptor_.HandleProvisioningResponse(response, &cert, + &wrapped_key)); EXPECT_EQ(NO_ERROR, decryptor_.OpenSession(g_key_system, &property_set, &session_id_)); @@ -912,13 +964,18 @@ TEST_F(WvCdmRequestLicenseTest, SecurityLevelPathBackwardCompatibility) { decryptor_.OpenSession(g_key_system, NULL, &session_id_); std::string provisioning_server_url; + CdmCertificateType cert_type = kCertificateWidevine; + std::string cert_authority, cert, wrapped_key; EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.GetProvisioningRequest( + cert_type, cert_authority, &key_msg_, &provisioning_server_url)); EXPECT_EQ(provisioning_server_url, g_config->provisioning_server_url()); std::string response = GetCertRequestResponse(g_config->provisioning_test_server_url(), 200); EXPECT_NE(0, static_cast(response.size())); - EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.HandleProvisioningResponse(response)); + EXPECT_EQ(wvcdm::NO_ERROR, + decryptor_.HandleProvisioningResponse(response, &cert, + &wrapped_key)); decryptor_.CloseSession(session_id_); decryptor_.OpenSession(g_key_system, NULL, &session_id_); @@ -969,13 +1026,16 @@ TEST_F(WvCdmRequestLicenseTest, SecurityLevelPathBackwardCompatibility) { app_parameters, &key_msg_, &server_url)); EXPECT_EQ(NO_ERROR, - decryptor_.GetProvisioningRequest(&key_msg_, + decryptor_.GetProvisioningRequest(cert_type, cert_authority, + &key_msg_, &provisioning_server_url)); EXPECT_EQ(provisioning_server_url, g_config->provisioning_server_url()); response = GetCertRequestResponse(g_config->provisioning_test_server_url(), 200); EXPECT_NE(0, static_cast(response.size())); - EXPECT_EQ(NO_ERROR, decryptor_.HandleProvisioningResponse(response)); + EXPECT_EQ(NO_ERROR, + decryptor_.HandleProvisioningResponse(response, &cert, + &wrapped_key)); EXPECT_EQ(NO_ERROR, decryptor_.OpenSession(g_key_system, &property_set, &session_id_)); diff --git a/libwvdrmengine/include/WVErrors.h b/libwvdrmengine/include/WVErrors.h index fad78f6c..def9a485 100644 --- a/libwvdrmengine/include/WVErrors.h +++ b/libwvdrmengine/include/WVErrors.h @@ -18,7 +18,9 @@ enum { kErrorUnsupportedCrypto = ERROR_DRM_VENDOR_MIN + 2, kErrorExpectedUnencrypted = ERROR_DRM_VENDOR_MIN + 3, kErrorSessionIsOpen = ERROR_DRM_VENDOR_MIN + 4, - kErrorWVDrmMaxErrorUsed = ERROR_DRM_VENDOR_MIN + 4, + kErrorTooManySessions = ERROR_DRM_VENDOR_MIN + 5, + kErrorInvalidKey = ERROR_DRM_VENDOR_MIN + 6, + kErrorWVDrmMaxErrorUsed = ERROR_DRM_VENDOR_MIN + 6, // Used by crypto test mode kErrorTestMode = ERROR_DRM_VENDOR_MAX, diff --git a/libwvdrmengine/mediadrm/include/WVDrmPlugin.h b/libwvdrmengine/mediadrm/include/WVDrmPlugin.h index e33350fc..69be54ac 100644 --- a/libwvdrmengine/mediadrm/include/WVDrmPlugin.h +++ b/libwvdrmengine/mediadrm/include/WVDrmPlugin.h @@ -72,10 +72,14 @@ class WVDrmPlugin : public android::DrmPlugin, const Vector& sessionId, KeyedVector& infoMap) const; - virtual status_t getProvisionRequest(Vector& request, + virtual status_t getProvisionRequest(const String8& cert_type, + const String8& cert_authority, + Vector& request, String8& defaultUrl); - virtual status_t provideProvisionResponse(const Vector& response); + virtual status_t provideProvisionResponse(const Vector& response, + Vector& certificate, + Vector& wrapped_key); virtual status_t getSecureStops(List >& secureStops); @@ -120,6 +124,12 @@ class WVDrmPlugin : public android::DrmPlugin, const Vector& signature, bool& match); + virtual status_t signRSA(const Vector& sessionId, + const String8& algorithm, + const Vector& message, + const Vector& wrappedKey, + Vector& signature); + virtual void onEvent(const CdmSessionId& cdmSessionId, CdmEventType cdmEventType); @@ -224,6 +234,8 @@ class WVDrmPlugin : public android::DrmPlugin, status_t mapAndNotifyOfOEMCryptoResult(const Vector& sessionId, OEMCryptoResult res); + + status_t mapOEMCryptoResult(OEMCryptoResult res); }; } // namespace wvdrm diff --git a/libwvdrmengine/mediadrm/include/WVGenericCryptoInterface.h b/libwvdrmengine/mediadrm/include/WVGenericCryptoInterface.h index cb6ebb8a..b21dae39 100644 --- a/libwvdrmengine/mediadrm/include/WVGenericCryptoInterface.h +++ b/libwvdrmengine/mediadrm/include/WVGenericCryptoInterface.h @@ -57,6 +57,33 @@ class WVGenericCryptoInterface { algorithm, signature, signature_length); } + virtual OEMCryptoResult openSession(OEMCrypto_SESSION *session) { + return OEMCrypto_OpenSession(session); + } + + virtual OEMCryptoResult closeSession(OEMCrypto_SESSION session) { + return OEMCrypto_CloseSession(session); + } + + virtual OEMCryptoResult loadDeviceRSAKey(OEMCrypto_SESSION session, + const uint8_t* wrapped_rsa_key, + size_t wrapped_rsa_key_length) { + return OEMCrypto_LoadDeviceRSAKey(session, wrapped_rsa_key, + wrapped_rsa_key_length); + } + + virtual OEMCryptoResult generateRSASignature( + OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + uint8_t* signature, + size_t* signature_length, + RSA_Padding_Scheme padding_scheme) { + return OEMCrypto_GenerateRSASignature(session, message, message_length, + signature, signature_length, + padding_scheme); +} + private: DISALLOW_EVIL_CONSTRUCTORS(WVGenericCryptoInterface); }; diff --git a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp index f9fe05c0..ca188362 100644 --- a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp @@ -320,12 +320,23 @@ status_t WVDrmPlugin::queryKeyStatus( return mapCdmResponseType(res); } -status_t WVDrmPlugin::getProvisionRequest(Vector& request, +status_t WVDrmPlugin::getProvisionRequest(const String8& cert_type, + const String8& cert_authority, + Vector& request, String8& defaultUrl) { CdmProvisioningRequest cdmProvisionRequest; string cdmDefaultUrl; - CdmResponseType res = mCDM->GetProvisioningRequest(&cdmProvisionRequest, + CdmCertificateType cdmCertType = kCertificateWidevine; + if (cert_type == "X.509") { + cdmCertType = kCertificateX509; + } + + string cdmCertAuthority = cert_authority.string(); + + CdmResponseType res = mCDM->GetProvisioningRequest(cdmCertType, + cdmCertAuthority, + &cdmProvisionRequest, &cdmDefaultUrl); if (isCdmResponseTypeSuccess(res)) { @@ -342,9 +353,27 @@ status_t WVDrmPlugin::getProvisionRequest(Vector& request, } status_t WVDrmPlugin::provideProvisionResponse( - const Vector& response) { + const Vector& response, + Vector& certificate, + Vector& wrapped_key) { CdmProvisioningResponse cdmResponse(response.begin(), response.end()); - CdmResponseType res = mCDM->HandleProvisioningResponse(cdmResponse); + string cdmCertificate; + string cdmWrappedKey; + CdmResponseType res = mCDM->HandleProvisioningResponse(cdmResponse, + &cdmCertificate, + &cdmWrappedKey); + if (isCdmResponseTypeSuccess(res)) { + certificate.clear(); + certificate.appendArray( + reinterpret_cast(cdmCertificate.data()), + cdmCertificate.size()); + + wrapped_key.clear(); + wrapped_key.appendArray( + reinterpret_cast(cdmWrappedKey.data()), + cdmWrappedKey.size()); + } + return mapCdmResponseType(res); } @@ -688,7 +717,7 @@ status_t WVDrmPlugin::sign(const Vector& sessionId, res = mCrypto->sign(cryptoSession.oecSessionId(), message.array(), message.size(), cryptoSession.macAlgorithm(), - signature.editArray(), &signatureSize); + NULL, &signatureSize); if (res != OEMCrypto_ERROR_SHORT_BUFFER) { ALOGE("OEMCrypto_Generic_Sign failed with %u when requesting signature " @@ -755,6 +784,68 @@ status_t WVDrmPlugin::verify(const Vector& sessionId, } } +status_t WVDrmPlugin::signRSA(const Vector& sessionId, + const String8& algorithm, + const Vector& message, + const Vector& wrappedKey, + Vector& signature) { + CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end()); + + if (!mCryptoSessions.count(cdmSessionId)) { + return android::ERROR_DRM_SESSION_NOT_OPENED; + } + + const CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId]; + + RSA_Padding_Scheme padding_scheme; + if (algorithm == "RSASSA-PSS-SHA1") { + padding_scheme = kSign_RSASSA_PSS; + } else if (algorithm == "PKCS1-BlockType1") { + padding_scheme = kSign_PKCS1_Block1; + } else { + ALOGE("Unknown RSA Algorithm %s", algorithm.string()); + return android::ERROR_DRM_CANNOT_HANDLE; + } + + OEMCryptoResult res = mCrypto->loadDeviceRSAKey(cryptoSession.oecSessionId(), + wrappedKey.array(), + wrappedKey.size()); + if (res != OEMCrypto_SUCCESS) { + ALOGE("OEMCrypto_LoadDeviceRSAKey failed with %u", res); + return mapOEMCryptoResult(res); + } + + size_t signatureSize = 0; + + res = mCrypto->generateRSASignature(cryptoSession.oecSessionId(), + message.array(), message.size(), + NULL, &signatureSize, padding_scheme); + + if (res != OEMCrypto_ERROR_SHORT_BUFFER) { + ALOGE("OEMCrypto_GenerateRSASignature failed with %u when requesting " + "signature size", res); + if (res != OEMCrypto_SUCCESS) { + return mapOEMCryptoResult(res); + } else { + return android::ERROR_DRM_UNKNOWN; + } + } + + signature.resize(signatureSize); + + res = mCrypto->generateRSASignature(cryptoSession.oecSessionId(), + message.array(), message.size(), + signature.editArray(), &signatureSize, + padding_scheme); + + if (res != OEMCrypto_SUCCESS) { + ALOGE("OEMCrypto_GenerateRSASignature failed with %u", res); + return mapOEMCryptoResult(res); + } + + return android::OK; +} + void WVDrmPlugin::onEvent(const CdmSessionId& cdmSessionId, CdmEventType cdmEventType) { Vector sessionId; @@ -791,8 +882,13 @@ status_t WVDrmPlugin::mapAndNotifyOfCdmResponseType( status_t WVDrmPlugin::mapAndNotifyOfOEMCryptoResult( const Vector& sessionId, OEMCryptoResult res) { - // Note that we only cover those errors that OEMCryptoCENC.h states may be - // returned by the generic crypto methods. + if (res == OEMCrypto_ERROR_NO_DEVICE_KEY) { + sendEvent(kDrmPluginEventProvisionRequired, 0, &sessionId, NULL); + } + return mapOEMCryptoResult(res); +} + +status_t WVDrmPlugin::mapOEMCryptoResult(OEMCryptoResult res) { switch (res) { case OEMCrypto_SUCCESS: return android::OK; @@ -801,11 +897,19 @@ status_t WVDrmPlugin::mapAndNotifyOfOEMCryptoResult( case OEMCrypto_ERROR_SHORT_BUFFER: return kErrorIncorrectBufferSize; case OEMCrypto_ERROR_NO_DEVICE_KEY: - sendEvent(kDrmPluginEventProvisionRequired, 0, &sessionId, NULL); return android::ERROR_DRM_NOT_PROVISIONED; case OEMCrypto_ERROR_INVALID_SESSION: return android::ERROR_DRM_SESSION_NOT_OPENED; + case OEMCrypto_ERROR_TOO_MANY_SESSIONS: + return kErrorTooManySessions; + case OEMCrypto_ERROR_INVALID_RSA_KEY: + return kErrorInvalidKey; + case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES: + return android::ERROR_DRM_RESOURCE_BUSY; + case OEMCrypto_ERROR_NOT_IMPLEMENTED: + return android::ERROR_DRM_CANNOT_HANDLE; case OEMCrypto_ERROR_UNKNOWN_FAILURE: + case OEMCrypto_ERROR_OPEN_SESSION_FAILED: return android::ERROR_DRM_UNKNOWN; default: return android::UNKNOWN_ERROR; diff --git a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp index 6b649cf5..35d97361 100644 --- a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp +++ b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp @@ -56,11 +56,13 @@ class MockCDM : public WvContentDecryptionModule { MOCK_METHOD2(QueryKeyControlInfo, CdmResponseType(const CdmSessionId&, CdmQueryMap*)); - MOCK_METHOD2(GetProvisioningRequest, CdmResponseType(CdmProvisioningRequest*, + MOCK_METHOD4(GetProvisioningRequest, CdmResponseType(CdmCertificateType, + const std::string&, + CdmProvisioningRequest*, std::string*)); - MOCK_METHOD1(HandleProvisioningResponse, - CdmResponseType(CdmProvisioningResponse&)); + MOCK_METHOD3(HandleProvisioningResponse, + CdmResponseType(CdmProvisioningResponse&, std::string*, std::string*)); MOCK_METHOD1(GetSecureStops, CdmResponseType(CdmSecureStops*)); @@ -93,6 +95,18 @@ class MockCrypto : public WVGenericCryptoInterface { MOCK_METHOD6(verify, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*, size_t, OEMCrypto_Algorithm, const uint8_t*, size_t)); + + MOCK_METHOD1(openSession, OEMCryptoResult(OEMCrypto_SESSION*)); + + MOCK_METHOD1(closeSession, OEMCryptoResult(OEMCrypto_SESSION)); + + MOCK_METHOD3(loadDeviceRSAKey, OEMCryptoResult(OEMCrypto_SESSION, + const uint8_t*, size_t)); + + MOCK_METHOD6(generateRSASignature, OEMCryptoResult(OEMCrypto_SESSION, + const uint8_t*, size_t, + uint8_t*, size_t*, + RSA_Padding_Scheme)); }; class MockDrmPluginListener : public DrmPluginListener { @@ -567,15 +581,17 @@ TEST_F(WVDrmPluginTest, GetsProvisioningRequests) { static const char* kDefaultUrl = "http://google.com/"; - EXPECT_CALL(cdm, GetProvisioningRequest(_, _)) - .WillOnce(DoAll(SetArgPointee<0>(cdmRequest), - SetArgPointee<1>(kDefaultUrl), + EXPECT_CALL(cdm, GetProvisioningRequest(kCertificateWidevine, IsEmpty(), + _, _)) + .WillOnce(DoAll(SetArgPointee<2>(cdmRequest), + SetArgPointee<3>(kDefaultUrl), Return(wvcdm::NO_ERROR))); Vector request; String8 defaultUrl; - status_t res = plugin.getProvisionRequest(request, defaultUrl); + status_t res = plugin.getProvisionRequest(String8(""), String8(""), request, + defaultUrl); ASSERT_EQ(OK, res); EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize)); @@ -597,10 +613,14 @@ TEST_F(WVDrmPluginTest, HandlesProvisioningResponses) { response.appendArray(responseRaw, kResponseSize); EXPECT_CALL(cdm, HandleProvisioningResponse(ElementsAreArray(responseRaw, - kResponseSize))) + kResponseSize), + _, _)) .Times(1); - status_t res = plugin.provideProvisionResponse(response); + Vector cert; + Vector key; + + status_t res = plugin.provideProvisionResponse(response, cert, key); ASSERT_EQ(OK, res); } diff --git a/libwvdrmengine/test/castv2/Android.mk b/libwvdrmengine/test/castv2/Android.mk new file mode 100644 index 00000000..4f9cf909 --- /dev/null +++ b/libwvdrmengine/test/castv2/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_STATIC_JAVA_LIBRARIES := com.android.mediadrm.signer + +LOCAL_PACKAGE_NAME := CastSignAPITest + +include $(BUILD_PACKAGE) + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/libwvdrmengine/test/castv2/AndroidManifest.xml b/libwvdrmengine/test/castv2/AndroidManifest.xml new file mode 100644 index 00000000..479293a1 --- /dev/null +++ b/libwvdrmengine/test/castv2/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + diff --git a/libwvdrmengine/test/castv2/default.properties b/libwvdrmengine/test/castv2/default.properties new file mode 100644 index 00000000..8d2c89a7 --- /dev/null +++ b/libwvdrmengine/test/castv2/default.properties @@ -0,0 +1,2 @@ +# Project target. +target=android-IceCreamSandwich diff --git a/libwvdrmengine/test/castv2/res/drawable-hdpi/icon.png b/libwvdrmengine/test/castv2/res/drawable-hdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8074c4c571b8cd19e27f4ee5545df367420686d7 GIT binary patch literal 4147 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 literal 0 HcmV?d00001 diff --git a/libwvdrmengine/test/castv2/res/drawable-ldpi/icon.png b/libwvdrmengine/test/castv2/res/drawable-ldpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1095584ec21f71cd0afc9e0993aa2209671b590c GIT binary patch literal 1723 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 + + diff --git a/libwvdrmengine/test/castv2/res/values/strings.xml b/libwvdrmengine/test/castv2/res/values/strings.xml new file mode 100644 index 00000000..098fced1 --- /dev/null +++ b/libwvdrmengine/test/castv2/res/values/strings.xml @@ -0,0 +1,4 @@ + + + CastSignAPI Test + diff --git a/libwvdrmengine/test/castv2/src/com/android/castv2/test/CastSignAPITest.java b/libwvdrmengine/test/castv2/src/com/android/castv2/test/CastSignAPITest.java new file mode 100644 index 00000000..917df249 --- /dev/null +++ b/libwvdrmengine/test/castv2/src/com/android/castv2/test/CastSignAPITest.java @@ -0,0 +1,206 @@ +package com.android.castv2.test; + +import android.app.Activity; +import android.os.Bundle; +import android.os.Looper; +import android.view.View; +import android.view.SurfaceView; +import android.view.SurfaceHolder; +import android.view.Surface; +import android.view.Display; +import android.content.Context; +import android.graphics.Point; +import android.media.MediaDrm; +import android.widget.TextView; +import android.media.MediaDrm.CryptoSession; +import android.media.MediaDrmException; +import android.media.NotProvisionedException; +import android.media.MediaCrypto; +import android.media.MediaCodec; +import android.media.MediaCryptoException; +import android.media.MediaCodec.CryptoException; +import android.media.MediaCodecList; +import android.media.MediaCodec.CryptoInfo; +import android.media.MediaCodecInfo; +import android.media.MediaFormat; +import android.util.Log; +import android.util.TypedValue; +import android.util.AttributeSet; +import java.util.UUID; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.ListIterator; +import java.util.Iterator; +import java.util.HashMap; +import java.util.Random; +import java.nio.ByteBuffer; +import java.lang.Exception; +import java.lang.InterruptedException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.lang.Math; +import java.security.cert.CertificateFactory; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.InvalidKeyException; +import com.android.mediadrm.signer.MediaDrmSigner; +import java.io.InputStream; +import java.io.ByteArrayInputStream; +import dalvik.system.DexClassLoader; + +public class CastSignAPITest extends Activity { + private final String TAG = "CastSignAPITest"; + + static final UUID kWidevineScheme = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL); + + private boolean mTestFailed; + private TextView mTextView; + private String mDisplayText; + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + mTextView = new TextView(this); + + mDisplayText = new String("Cast Signing API Test\n\n"); + Display display = getWindowManager().getDefaultDisplay(); + Point size = new Point(); + display.getSize(size); + int width = size.x; + int height = size.y; + final int max_lines = 20; + int textSize = Math.min(width, height) / max_lines; + mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); + setContentView(mTextView); + Log.i(TAG, "width=" + width + " height=" + height + " size=" + textSize); + + new Thread() { + @Override + public void run() { + mTestFailed = false; + + testCastSign(); + + if (mTestFailed) { + displayText("\nTEST FAILED!"); + } else { + displayText("\nTEST PASSED!"); + } + } + }.start(); + } + + // draw text on the surface view + private void displayText(String text) { + Log.i(TAG, text); + mDisplayText += text + "\n"; + + runOnUiThread(new Runnable() { + public void run() { + mTextView.setText(mDisplayText); + } + }); + } + + private byte[] openSession(MediaDrm drm) { + byte[] sessionId = null; + boolean retryOpen; + do { + try { + retryOpen = false; + sessionId = drm.openSession(); + } catch (NotProvisionedException e) { + Log.i(TAG, "Missing certificate, provisioning"); + ProvisionRequester provisionRequester = new ProvisionRequester(); + provisionRequester.doTransact(drm); + retryOpen = true; + } + } while (retryOpen); + return sessionId; + } + + private void testCastSign() { + final byte[] kMessage = hex2ba("ee07514066c23c770a665719abf051e0" + + "f75a399578305eb2547ca67ecd2356ca" + + "7bc0f5dc1854533eb98ee0fae1107ad2" + + "a8707c671be10fcd4853990897b71378" + + "70e39957a1536b39132e438ba681810d" + + "ebc3f9fb38907a1066b09c63af9016a7" + + "57425b29ff7fdf53859ebdc1659bdc49" + + "f40a5cb5f597b0b8a836b424ad9c68a9"); + + final byte[] kDigest = hex2ba(// digest info header + "3021300906052b0e03021a05000414" + + // sha1 of kMessage + "d2662f893aaec72f3ca6decc2aa942f3949e8b21"); + + MediaDrm drm; + + try { + drm = new MediaDrm(kWidevineScheme); + } catch (MediaDrmException e) { + Log.e(TAG, "Failed to create MediaDrm: ", e); + mTestFailed = true; + return; + } + + CertificateRequester certificateRequester = new CertificateRequester(); + MediaDrmSigner.Certificate signerCert = certificateRequester.doTransact(drm); + if (signerCert == null) { + displayText("Failed to get certificate from server!"); + mTestFailed = true; + } else { + byte[] sessionId = openSession(drm); + + byte[] signature = MediaDrmSigner.signRSA(this, drm, sessionId, "PKCS1-BlockType1", + signerCert.getWrappedPrivateKey(), + kDigest); + + try { + byte[] certBuf = signerCert.getContent(); + InputStream certStream = new ByteArrayInputStream(certBuf); + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + Certificate javaCert = certFactory.generateCertificate(certStream); + PublicKey pubkey = javaCert.getPublicKey(); + Signature instance = Signature.getInstance("SHA1WithRSA"); + instance.initVerify(pubkey); + instance.update(kMessage); + if (instance.verify(signature)) { + Log.d(TAG, "Signatures match, test passed!"); + } else { + Log.e(TAG, "Signature did not verify, test failed!"); + mTestFailed = true; + } + } catch (CertificateException e) { + Log.e(TAG, "Failed to parse certificate", e); + mTestFailed = true; + } catch (NoSuchAlgorithmException e) { + mTestFailed = true; + Log.e(TAG, "Failed to get signature instance", e); + } catch (InvalidKeyException e) { + mTestFailed = true; + Log.e(TAG, "Invalid public key", e); + } catch (SignatureException e) { + mTestFailed = true; + Log.e(TAG, "Failed to update", e); + } + + drm.closeSession(sessionId); + } + } + + private static byte[] hex2ba(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i+1), 16)); + } + return data; + } +} diff --git a/libwvdrmengine/test/castv2/src/com/android/castv2/test/CertificateRequester.java b/libwvdrmengine/test/castv2/src/com/android/castv2/test/CertificateRequester.java new file mode 100644 index 00000000..bceae2ef --- /dev/null +++ b/libwvdrmengine/test/castv2/src/com/android/castv2/test/CertificateRequester.java @@ -0,0 +1,124 @@ +package com.android.castv2.test; + +import android.media.MediaDrm; +import com.android.mediadrm.signer.MediaDrmSigner; +import android.media.DeniedByServerException; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.HttpClient; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.HttpResponse; +import org.apache.http.util.EntityUtils; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.io.InputStream; +import java.io.ByteArrayInputStream; +import java.util.Arrays; +import android.os.AsyncTask; +import android.util.Log; + + +public class CertificateRequester { + private final String TAG = "CertificateRequester"; + + public CertificateRequester() { + } + + public MediaDrmSigner.Certificate doTransact(MediaDrm drm) { + MediaDrmSigner.Certificate result = null; + MediaDrmSigner.CertificateRequest certificateRequest = + MediaDrmSigner.getCertificateRequest(drm, MediaDrmSigner.CERTIFICATE_TYPE_X509, + "test-certificate"); + + PostRequestTask postTask = new PostRequestTask(certificateRequest.getData()); + Log.i(TAG, "Requesting certificate from server '" + certificateRequest.getDefaultUrl() + "'"); + postTask.execute(certificateRequest.getDefaultUrl()); + + // wait for post task to complete + byte[] responseBody; + long startTime = System.currentTimeMillis(); + do { + responseBody = postTask.getResponseBody(); + if (responseBody == null) { + sleep(100); + } else { + break; + } + } while (System.currentTimeMillis() - startTime < 5000); + + if (responseBody == null) { + Log.e(TAG, "No response from certificate server!"); + } else { + try { + result = MediaDrmSigner.provideCertificateResponse(drm, responseBody); + } catch (DeniedByServerException e) { + Log.e(TAG, "Server denied certificate request"); + } + } + return result; + } + + private class PostRequestTask extends AsyncTask { + private final String TAG = "PostRequestTask"; + + private byte[] mCertificateRequest; + private byte[] mResponseBody; + + public PostRequestTask(byte[] certificateRequest) { + mCertificateRequest = certificateRequest; + } + + protected Void doInBackground(String... urls) { + mResponseBody = postRequest(urls[0], mCertificateRequest); + if (mResponseBody != null) { + Log.d(TAG, "response length=" + mResponseBody.length); + } + return null; + } + + public byte[] getResponseBody() { + return mResponseBody; + } + + private byte[] postRequest(String url, byte[] certificateRequest) { + HttpClient httpclient = new DefaultHttpClient(); + HttpPost httppost = new HttpPost(url + "?signedRequest=" + new String(certificateRequest)); + + Log.d(TAG, "PostRequest:" + httppost.getRequestLine()); + + try { + // Add data + httppost.setHeader("Accept", "*/*"); + httppost.setHeader("User-Agent", "Widevine CDM v1.0"); + httppost.setHeader("Content-Type", "application/json"); + + // Execute HTTP Post Request + HttpResponse response = httpclient.execute(httppost); + + byte[] responseBody; + int responseCode = response.getStatusLine().getStatusCode(); + if (responseCode == 200) { + responseBody = EntityUtils.toByteArray(response.getEntity()); + } else { + Log.d(TAG, "Server returned HTTP error code " + responseCode); + return null; + } + return responseBody; + + } catch (ClientProtocolException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + } + + private void sleep(int msec) { + try { + Thread.sleep(msec); + } catch (InterruptedException e) { + } + } +} + diff --git a/libwvdrmengine/test/castv2/src/com/android/castv2/test/ProvisionRequester.java b/libwvdrmengine/test/castv2/src/com/android/castv2/test/ProvisionRequester.java new file mode 100644 index 00000000..1d28c5fc --- /dev/null +++ b/libwvdrmengine/test/castv2/src/com/android/castv2/test/ProvisionRequester.java @@ -0,0 +1,117 @@ +package com.android.castv2.test; + +import android.media.MediaDrm; +import android.media.DeniedByServerException; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.HttpClient; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.HttpResponse; +import org.apache.http.util.EntityUtils; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import android.os.AsyncTask; +import android.util.Log; + +public class ProvisionRequester { + private final String TAG = "ProvisionRequester"; + + public ProvisionRequester() { + } + + public void doTransact(MediaDrm drm) { + MediaDrm.ProvisionRequest drmRequest; + drmRequest = drm.getProvisionRequest(); + + PostRequestTask postTask = new PostRequestTask(drmRequest.getData()); + Log.i(TAG, "Attempting to provision from server '" + drmRequest.getDefaultUrl() + "'"); + postTask.execute(drmRequest.getDefaultUrl()); + + // wait for post task to complete + byte[] responseBody; + long startTime = System.currentTimeMillis(); + do { + responseBody = postTask.getResponseBody(); + if (responseBody == null) { + sleep(100); + } else { + break; + } + } while (System.currentTimeMillis() - startTime < 5000); + + if (responseBody == null) { + Log.e(TAG, "No response from provisioning server!"); + } else { + try { + drm.provideProvisionResponse(responseBody); + } catch (DeniedByServerException e) { + Log.e(TAG, "Server denied provisioning request"); + } + } + } + + private class PostRequestTask extends AsyncTask { + private final String TAG = "PostRequestTask"; + + private byte[] mDrmRequest; + private byte[] mResponseBody; + + public PostRequestTask(byte[] drmRequest) { + mDrmRequest = drmRequest; + } + + protected Void doInBackground(String... urls) { + mResponseBody = postRequest(urls[0], mDrmRequest); + if (mResponseBody != null) { + Log.d(TAG, "response length=" + mResponseBody.length); + } + return null; + } + + public byte[] getResponseBody() { + return mResponseBody; + } + + private byte[] postRequest(String url, byte[] drmRequest) { + HttpClient httpclient = new DefaultHttpClient(); + HttpPost httppost = new HttpPost(url + "?signedRequest=" + new String(drmRequest)); + + Log.d(TAG, "PostRequest:" + httppost.getRequestLine()); + + try { + // Add data + httppost.setHeader("Accept", "*/*"); + httppost.setHeader("User-Agent", "Widevine CDM v1.0"); + httppost.setHeader("Content-Type", "application/json"); + + // Execute HTTP Post Request + HttpResponse response = httpclient.execute(httppost); + + byte[] responseBody; + int responseCode = response.getStatusLine().getStatusCode(); + if (responseCode == 200) { + responseBody = EntityUtils.toByteArray(response.getEntity()); + } else { + Log.d(TAG, "Server returned HTTP error code " + responseCode); + return null; + } + return responseBody; + + } catch (ClientProtocolException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + } + + private void sleep(int msec) { + try { + Thread.sleep(msec); + } catch (InterruptedException e) { + } + } +} +