[ 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
280 lines
10 KiB
C++
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
|