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
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -64,6 +64,11 @@ enum CdmSecurityLevel {
|
||||
kSecurityLevelUnknown
|
||||
};
|
||||
|
||||
enum CdmCertificateType {
|
||||
kCertificateWidevine,
|
||||
kCertificateX509,
|
||||
};
|
||||
|
||||
struct CdmDecryptionParameters {
|
||||
bool is_encrypted;
|
||||
bool is_secure;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<char*>(&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();
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<int>(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<int>(cert.size()));
|
||||
EXPECT_EQ(0, static_cast<int>(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<int>(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<int>(cert.size()));
|
||||
EXPECT_EQ(0, static_cast<int>(wrapped_key.size()));
|
||||
|
||||
response =
|
||||
GetCertRequestResponse(g_config->provisioning_test_server_url(), 200);
|
||||
EXPECT_NE(0, static_cast<int>(response.size()));
|
||||
EXPECT_EQ(wvcdm::UNKNOWN_ERROR,
|
||||
decryptor_.HandleProvisioningResponse(response));
|
||||
decryptor_.HandleProvisioningResponse(response, &cert,
|
||||
&wrapped_key));
|
||||
EXPECT_EQ(0, static_cast<int>(cert.size()));
|
||||
EXPECT_EQ(0, static_cast<int>(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<int>(response.size()));
|
||||
EXPECT_EQ(wvcdm::NO_ERROR,
|
||||
decryptor_.HandleProvisioningResponse(response, &cert,
|
||||
&wrapped_key));
|
||||
EXPECT_NE(0, static_cast<int>(cert.size()));
|
||||
EXPECT_NE(0, static_cast<int>(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<int>(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<int>(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<int>(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<int>(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_));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -72,10 +72,14 @@ class WVDrmPlugin : public android::DrmPlugin,
|
||||
const Vector<uint8_t>& sessionId,
|
||||
KeyedVector<String8, String8>& infoMap) const;
|
||||
|
||||
virtual status_t getProvisionRequest(Vector<uint8_t>& request,
|
||||
virtual status_t getProvisionRequest(const String8& cert_type,
|
||||
const String8& cert_authority,
|
||||
Vector<uint8_t>& request,
|
||||
String8& defaultUrl);
|
||||
|
||||
virtual status_t provideProvisionResponse(const Vector<uint8_t>& response);
|
||||
virtual status_t provideProvisionResponse(const Vector<uint8_t>& response,
|
||||
Vector<uint8_t>& certificate,
|
||||
Vector<uint8_t>& wrapped_key);
|
||||
|
||||
virtual status_t getSecureStops(List<Vector<uint8_t> >& secureStops);
|
||||
|
||||
@@ -120,6 +124,12 @@ class WVDrmPlugin : public android::DrmPlugin,
|
||||
const Vector<uint8_t>& signature,
|
||||
bool& match);
|
||||
|
||||
virtual status_t signRSA(const Vector<uint8_t>& sessionId,
|
||||
const String8& algorithm,
|
||||
const Vector<uint8_t>& message,
|
||||
const Vector<uint8_t>& wrappedKey,
|
||||
Vector<uint8_t>& signature);
|
||||
|
||||
virtual void onEvent(const CdmSessionId& cdmSessionId,
|
||||
CdmEventType cdmEventType);
|
||||
|
||||
@@ -224,6 +234,8 @@ class WVDrmPlugin : public android::DrmPlugin,
|
||||
|
||||
status_t mapAndNotifyOfOEMCryptoResult(const Vector<uint8_t>& sessionId,
|
||||
OEMCryptoResult res);
|
||||
|
||||
status_t mapOEMCryptoResult(OEMCryptoResult res);
|
||||
};
|
||||
|
||||
} // namespace wvdrm
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -320,12 +320,23 @@ status_t WVDrmPlugin::queryKeyStatus(
|
||||
return mapCdmResponseType(res);
|
||||
}
|
||||
|
||||
status_t WVDrmPlugin::getProvisionRequest(Vector<uint8_t>& request,
|
||||
status_t WVDrmPlugin::getProvisionRequest(const String8& cert_type,
|
||||
const String8& cert_authority,
|
||||
Vector<uint8_t>& 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<uint8_t>& request,
|
||||
}
|
||||
|
||||
status_t WVDrmPlugin::provideProvisionResponse(
|
||||
const Vector<uint8_t>& response) {
|
||||
const Vector<uint8_t>& response,
|
||||
Vector<uint8_t>& certificate,
|
||||
Vector<uint8_t>& 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<const uint8_t*>(cdmCertificate.data()),
|
||||
cdmCertificate.size());
|
||||
|
||||
wrapped_key.clear();
|
||||
wrapped_key.appendArray(
|
||||
reinterpret_cast<const uint8_t*>(cdmWrappedKey.data()),
|
||||
cdmWrappedKey.size());
|
||||
}
|
||||
|
||||
return mapCdmResponseType(res);
|
||||
}
|
||||
|
||||
@@ -688,7 +717,7 @@ status_t WVDrmPlugin::sign(const Vector<uint8_t>& 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<uint8_t>& sessionId,
|
||||
}
|
||||
}
|
||||
|
||||
status_t WVDrmPlugin::signRSA(const Vector<uint8_t>& sessionId,
|
||||
const String8& algorithm,
|
||||
const Vector<uint8_t>& message,
|
||||
const Vector<uint8_t>& wrappedKey,
|
||||
Vector<uint8_t>& 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<uint8_t> sessionId;
|
||||
@@ -791,8 +882,13 @@ status_t WVDrmPlugin::mapAndNotifyOfCdmResponseType(
|
||||
status_t WVDrmPlugin::mapAndNotifyOfOEMCryptoResult(
|
||||
const Vector<uint8_t>& 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;
|
||||
|
||||
@@ -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<uint8_t> 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<uint8_t> cert;
|
||||
Vector<uint8_t> key;
|
||||
|
||||
status_t res = plugin.provideProvisionResponse(response, cert, key);
|
||||
|
||||
ASSERT_EQ(OK, res);
|
||||
}
|
||||
|
||||
14
libwvdrmengine/test/castv2/Android.mk
Normal file
14
libwvdrmengine/test/castv2/Android.mk
Normal file
@@ -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))
|
||||
22
libwvdrmengine/test/castv2/AndroidManifest.xml
Normal file
22
libwvdrmengine/test/castv2/AndroidManifest.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.castv2.test"
|
||||
>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-library android:name="com.android.mediadrm.signer"
|
||||
android:required="true" />
|
||||
|
||||
<uses-sdk android:minSdkVersion="18"></uses-sdk>
|
||||
<application android:icon="@drawable/icon" android:label="@string/app_name" android:theme="@android:style/Theme.Holo.NoActionBar">
|
||||
<activity android:name="CastSignAPITest"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="landscape">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
2
libwvdrmengine/test/castv2/default.properties
Normal file
2
libwvdrmengine/test/castv2/default.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
# Project target.
|
||||
target=android-IceCreamSandwich
|
||||
BIN
libwvdrmengine/test/castv2/res/drawable-hdpi/icon.png
Normal file
BIN
libwvdrmengine/test/castv2/res/drawable-hdpi/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
BIN
libwvdrmengine/test/castv2/res/drawable-ldpi/icon.png
Normal file
BIN
libwvdrmengine/test/castv2/res/drawable-ldpi/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
BIN
libwvdrmengine/test/castv2/res/drawable-mdpi/icon.png
Normal file
BIN
libwvdrmengine/test/castv2/res/drawable-mdpi/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
6
libwvdrmengine/test/castv2/res/layout/main.xml
Normal file
6
libwvdrmengine/test/castv2/res/layout/main.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical">
|
||||
</LinearLayout>
|
||||
4
libwvdrmengine/test/castv2/res/values/strings.xml
Normal file
4
libwvdrmengine/test/castv2/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">CastSignAPI Test</string>
|
||||
</resources>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<String, Void, Void> {
|
||||
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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, Void, Void> {
|
||||
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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user