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:
Jeff Tinker
2014-03-10 12:41:14 -07:00
parent f111bea1b1
commit 3db90f54c1
26 changed files with 864 additions and 44 deletions

View File

@@ -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);

View File

@@ -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);
};

View File

@@ -64,6 +64,11 @@ enum CdmSecurityLevel {
kSecurityLevelUnknown
};
enum CdmCertificateType {
kCertificateWidevine,
kCertificateX509,
};
struct CdmDecryptionParameters {
bool is_encrypted;
bool is_secure;

View File

@@ -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(

View File

@@ -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();

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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(

View File

@@ -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_));

View File

@@ -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,

View File

@@ -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

View File

@@ -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);
};

View File

@@ -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;

View File

@@ -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);
}

View 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))

View 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>

View File

@@ -0,0 +1,2 @@
# Project target.
target=android-IceCreamSandwich

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View 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>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">CastSignAPI Test</string>
</resources>

View File

@@ -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;
}
}

View File

@@ -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) {
}
}
}

View File

@@ -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) {
}
}
}