Files
android/libwvdrmengine/cdm/core/src/service_certificate.cpp
Rahul Frias ee5aff7706 Correct setting of service certificate.
[ Merge of http://go/wvgerrit/23380 ]

The service certificate was setup correctly if specified in mediadrm
properties. If instead the service certificate was later fetched from
the license service, it would not be marked as valid. This led to an
infinite loop of service certificate fetches and processing. This
prevented the license from being fetched and playback failures.

b/34638410

Test: Verified by new service certificate unittests + Hulu playback
using fugu.

Change-Id: I2a4f8754614fccdad3c80d3e13fba0b44d177d61
2017-01-27 02:44:38 -08:00

280 lines
10 KiB
C++

// Copyright 2017 Google Inc. All Rights Reserved.
#include "service_certificate.h"
#include "crypto_key.h"
#include "crypto_session.h"
#include "log.h"
#include "privacy_crypto.h"
#include "properties.h"
#include "wv_cdm_constants.h"
namespace {
// Service certificate for Google/Widevine Provisioning and License servers.
const unsigned char kServiceCertificateCAPublicKey[] = {
0x30, 0x82, 0x01, 0x8a, 0x02, 0x82, 0x01, 0x81, 0x00, 0xb4, 0xfe, 0x39,
0xc3, 0x65, 0x90, 0x03, 0xdb, 0x3c, 0x11, 0x97, 0x09, 0xe8, 0x68, 0xcd,
0xf2, 0xc3, 0x5e, 0x9b, 0xf2, 0xe7, 0x4d, 0x23, 0xb1, 0x10, 0xdb, 0x87,
0x65, 0xdf, 0xdc, 0xfb, 0x9f, 0x35, 0xa0, 0x57, 0x03, 0x53, 0x4c, 0xf6,
0x6d, 0x35, 0x7d, 0xa6, 0x78, 0xdb, 0xb3, 0x36, 0xd2, 0x3f, 0x9c, 0x40,
0xa9, 0x95, 0x26, 0x72, 0x7f, 0xb8, 0xbe, 0x66, 0xdf, 0xc5, 0x21, 0x98,
0x78, 0x15, 0x16, 0x68, 0x5d, 0x2f, 0x46, 0x0e, 0x43, 0xcb, 0x8a, 0x84,
0x39, 0xab, 0xfb, 0xb0, 0x35, 0x80, 0x22, 0xbe, 0x34, 0x23, 0x8b, 0xab,
0x53, 0x5b, 0x72, 0xec, 0x4b, 0xb5, 0x48, 0x69, 0x53, 0x3e, 0x47, 0x5f,
0xfd, 0x09, 0xfd, 0xa7, 0x76, 0x13, 0x8f, 0x0f, 0x92, 0xd6, 0x4c, 0xdf,
0xae, 0x76, 0xa9, 0xba, 0xd9, 0x22, 0x10, 0xa9, 0x9d, 0x71, 0x45, 0xd6,
0xd7, 0xe1, 0x19, 0x25, 0x85, 0x9c, 0x53, 0x9a, 0x97, 0xeb, 0x84, 0xd7,
0xcc, 0xa8, 0x88, 0x82, 0x20, 0x70, 0x26, 0x20, 0xfd, 0x7e, 0x40, 0x50,
0x27, 0xe2, 0x25, 0x93, 0x6f, 0xbc, 0x3e, 0x72, 0xa0, 0xfa, 0xc1, 0xbd,
0x29, 0xb4, 0x4d, 0x82, 0x5c, 0xc1, 0xb4, 0xcb, 0x9c, 0x72, 0x7e, 0xb0,
0xe9, 0x8a, 0x17, 0x3e, 0x19, 0x63, 0xfc, 0xfd, 0x82, 0x48, 0x2b, 0xb7,
0xb2, 0x33, 0xb9, 0x7d, 0xec, 0x4b, 0xba, 0x89, 0x1f, 0x27, 0xb8, 0x9b,
0x88, 0x48, 0x84, 0xaa, 0x18, 0x92, 0x0e, 0x65, 0xf5, 0xc8, 0x6c, 0x11,
0xff, 0x6b, 0x36, 0xe4, 0x74, 0x34, 0xca, 0x8c, 0x33, 0xb1, 0xf9, 0xb8,
0x8e, 0xb4, 0xe6, 0x12, 0xe0, 0x02, 0x98, 0x79, 0x52, 0x5e, 0x45, 0x33,
0xff, 0x11, 0xdc, 0xeb, 0xc3, 0x53, 0xba, 0x7c, 0x60, 0x1a, 0x11, 0x3d,
0x00, 0xfb, 0xd2, 0xb7, 0xaa, 0x30, 0xfa, 0x4f, 0x5e, 0x48, 0x77, 0x5b,
0x17, 0xdc, 0x75, 0xef, 0x6f, 0xd2, 0x19, 0x6d, 0xdc, 0xbe, 0x7f, 0xb0,
0x78, 0x8f, 0xdc, 0x82, 0x60, 0x4c, 0xbf, 0xe4, 0x29, 0x06, 0x5e, 0x69,
0x8c, 0x39, 0x13, 0xad, 0x14, 0x25, 0xed, 0x19, 0xb2, 0xf2, 0x9f, 0x01,
0x82, 0x0d, 0x56, 0x44, 0x88, 0xc8, 0x35, 0xec, 0x1f, 0x11, 0xb3, 0x24,
0xe0, 0x59, 0x0d, 0x37, 0xe4, 0x47, 0x3c, 0xea, 0x4b, 0x7f, 0x97, 0x31,
0x1c, 0x81, 0x7c, 0x94, 0x8a, 0x4c, 0x7d, 0x68, 0x15, 0x84, 0xff, 0xa5,
0x08, 0xfd, 0x18, 0xe7, 0xe7, 0x2b, 0xe4, 0x47, 0x27, 0x12, 0x11, 0xb8,
0x23, 0xec, 0x58, 0x93, 0x3c, 0xac, 0x12, 0xd2, 0x88, 0x6d, 0x41, 0x3d,
0xc5, 0xfe, 0x1c, 0xdc, 0xb9, 0xf8, 0xd4, 0x51, 0x3e, 0x07, 0xe5, 0x03,
0x6f, 0xa7, 0x12, 0xe8, 0x12, 0xf7, 0xb5, 0xce, 0xa6, 0x96, 0x55, 0x3f,
0x78, 0xb4, 0x64, 0x82, 0x50, 0xd2, 0x33, 0x5f, 0x91, 0x02, 0x03, 0x01,
0x00, 0x01};
} // namespace
namespace wvcdm {
// Protobuf generated classes.
using video_widevine::ClientIdentification;
using video_widevine::DrmDeviceCertificate;
using video_widevine::EncryptedClientIdentification;
using video_widevine::SignedDrmDeviceCertificate;
using video_widevine::SignedMessage;
ServiceCertificate::ServiceCertificate()
: crypto_session_(NULL),
valid_(false),
initialized_(false) {
certificate_.clear();
}
ServiceCertificate::~ServiceCertificate() {}
bool ServiceCertificate::Init(const CdmSessionId& session_id,
CryptoSession* session) {
if (session_id.empty()) {
LOGE("ServiceCertificate::Init: empty session id provided");
return false;
}
if (session == NULL) {
LOGE("ServiceCertificate::Init: crypto session not provided");
return false;
}
session_id_.assign(session_id);
crypto_session_ = session;
privacy_mode_enabled_ = Properties::UsePrivacyMode(session_id_);
SetupServiceCertificate();
initialized_ = true;
return true;
}
bool ServiceCertificate::IsRequired() {
return privacy_mode_enabled_;
}
bool ServiceCertificate::IsAvailable() {
return valid_;
}
CdmResponseType ServiceCertificate::VerifyAndSet(
const std::string& signed_service_certificate) {
CdmResponseType status;
bool has_provider_id;
status = VerifyAndExtractFromSignedCertificate(signed_service_certificate,
&certificate_,
&has_provider_id,
NULL);
if (status != NO_ERROR) {
LOGE("ServiceCertificate::VerifyAndSet: verify and extract failed with "
"status %d", status);
return status;
}
Properties::SetServiceCertificate(session_id_, signed_service_certificate);
valid_ = true;
return NO_ERROR;
}
bool ServiceCertificate::PrepareServiceCertificateRequest(
CdmKeyMessage* signed_request) {
if (!initialized_) {
LOGE("ServiceCertificate::PrepareServiceCertificateRequest: "
"not initialized");
return false;
}
if (!signed_request) {
LOGE("ServiceCertificate::PrepareServiceCertificateRequest: "
"no signed request provided");
return false;
}
SignedMessage signed_message;
signed_message.set_type(SignedMessage::SERVICE_CERTIFICATE_REQUEST);
signed_message.SerializeToString(signed_request);
return true;
}
CdmResponseType ServiceCertificate::VerifyAndExtractFromSignedCertificate(
const std::string& signed_certificate, std::string* certificate,
bool* /* has_provider_id */, std::string* /* provider_id */) {
SignedDrmDeviceCertificate signed_service_certificate;
if (!signed_service_certificate.ParseFromString(signed_certificate)) {
LOGE(
"ServiceCertificate::VerifyAndExtractFromSignedCertificate: "
"unable to parse signed service certificate");
return DEVICE_CERTIFICATE_ERROR_1;
}
RsaPublicKey root_ca_key;
std::string ca_public_key(
reinterpret_cast<const char*>(&kServiceCertificateCAPublicKey[0]),
sizeof(kServiceCertificateCAPublicKey));
if (!root_ca_key.Init(ca_public_key)) {
LOGE(
"ServiceCertificate::VerifyAndExtractFromSignedCertificate: public key "
"initialization failed");
return DEVICE_CERTIFICATE_ERROR_2;
}
if (!root_ca_key.VerifySignature(
signed_service_certificate.drm_certificate(),
signed_service_certificate.signature())) {
LOGE(
"ServiceCertificate::VerifyAndExtractFromSignedCertificate: service "
"certificate verification failed");
return DEVICE_CERTIFICATE_ERROR_3;
}
DrmDeviceCertificate service_certificate;
if (!service_certificate.ParseFromString(
signed_service_certificate.drm_certificate())) {
LOGE(
"ServiceCertificate::VerifyAndExtractFromSignedCertificate: unable to "
"parse retrieved service certificate");
return DEVICE_CERTIFICATE_ERROR_4;
}
if (service_certificate.type() !=
video_widevine::DrmDeviceCertificate_CertificateType_SERVICE) {
LOGE(
"ServiceCertificate::VerifyAndExtractFromSignedCertificate: "
"certificate not of type service, %d", service_certificate.type());
return INVALID_DEVICE_CERTIFICATE_TYPE;
}
#if 0 // TODO(gmorgan): service cert has no provider_id
*has_provider_id = service_certificate.has_provider_id();
if (*has_provider_id && provider_id != NULL) {
*provider_id = service_certificate.provider_id();
} else {
return DEVICE_CERTIFICATE_ERROR_5;
}
#endif
if (certificate != NULL &&
!signed_service_certificate.drm_certificate().empty()) {
*certificate = signed_service_certificate.drm_certificate();
}
return NO_ERROR;
}
CdmResponseType ServiceCertificate::EncryptClientId(
const ClientIdentification* clear_client_id,
EncryptedClientIdentification* encrypted_client_id) {
DrmDeviceCertificate service_certificate;
if (!service_certificate.ParseFromString(certificate_)) {
LOGE("ServiceCertificate::EncryptClientId: unable to parse retrieved "
"service certificate");
return PARSE_SERVICE_CERTIFICATE_ERROR;
}
if (service_certificate.type() !=
video_widevine::DrmDeviceCertificate_CertificateType_SERVICE) {
LOGE("ServiceCertificate::EncryptClientId: retrieved certificate not of "
"type service, %d", service_certificate.type());
return SERVICE_CERTIFICATE_TYPE_ERROR;
}
encrypted_client_id->set_provider_id(service_certificate.provider_id());
encrypted_client_id->set_service_certificate_serial_number(
service_certificate.serial_number());
std::string iv(KEY_IV_SIZE, 0); // TODO(gmorgan) randomize
std::string key(KEY_SIZE, 0);
if (!crypto_session_->GetRandom(key.size(),
reinterpret_cast<uint8_t*>(&key[0])))
return CLIENT_ID_GENERATE_RANDOM_ERROR;
if (!crypto_session_->GetRandom(iv.size(),
reinterpret_cast<uint8_t*>(&iv[0])))
return CLIENT_ID_GENERATE_RANDOM_ERROR;
std::string id, enc_id, enc_key;
clear_client_id->SerializeToString(&id);
AesCbcKey aes;
if (!aes.Init(key)) return CLIENT_ID_AES_INIT_ERROR;
if (!aes.Encrypt(id, &enc_id, &iv)) return CLIENT_ID_AES_ENCRYPT_ERROR;
RsaPublicKey rsa;
if (!rsa.Init(service_certificate.public_key()))
return CLIENT_ID_RSA_INIT_ERROR;
if (!rsa.Encrypt(key, &enc_key)) return CLIENT_ID_RSA_ENCRYPT_ERROR;
encrypted_client_id->set_encrypted_client_id_iv(iv);
encrypted_client_id->set_encrypted_privacy_key(enc_key);
encrypted_client_id->set_encrypted_client_id(enc_id);
return NO_ERROR;
}
bool ServiceCertificate::SetupServiceCertificate() {
std::string signed_certificate;
valid_ = false;
certificate_.clear();
if (!Properties::GetServiceCertificate(session_id_, &signed_certificate)) {
LOGV("ServiceCertificate::SetupServiceCertificate: no signed service "
"certificate set");
return false;
}
if (signed_certificate.empty()) {
LOGV("ServiceCertificate::SetupServiceCertificate: service certificate "
"empty");
return false;
}
std::string extracted_certificate;
std::string extracted_provider_id;
bool has_provider_id;
if (NO_ERROR != VerifyAndExtractFromSignedCertificate(
signed_certificate, &extracted_certificate,
&has_provider_id, &extracted_provider_id)) {
return false;
}
has_provider_id_ = has_provider_id;
if (has_provider_id_) {
provider_id_ = extracted_provider_id;
}
certificate_ = extracted_certificate;
valid_ = true;
return true;
}
} // namespace wvcdm