Add example binary for testing building the SDK after 'git clone' from our repo. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=227583629
263 lines
9.1 KiB
C++
263 lines
9.1 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
// Copyright 2017 Google LLC.
|
|
//
|
|
// This software is licensed under the terms defined in the Widevine Master
|
|
// License Agreement. For a copy of this agreement, please contact
|
|
// widevine-licensing@google.com.
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "common/client_cert.h"
|
|
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "glog/logging.h"
|
|
#include "strings/serialize.h"
|
|
#include "absl/strings/escaping.h"
|
|
#include "absl/synchronization/mutex.h"
|
|
#include "util/gtl/map_util.h"
|
|
#include "common/crypto_util.h"
|
|
#include "common/drm_root_certificate.h"
|
|
#include "common/error_space.h"
|
|
#include "common/random_util.h"
|
|
#include "common/sha_util.h"
|
|
#include "common/signing_key_util.h"
|
|
#include "common/status.h"
|
|
#include "common/wvm_token_handler.h"
|
|
#include "protos/public/drm_certificate.pb.h"
|
|
#include "protos/public/errors.pb.h"
|
|
#include "protos/public/signed_drm_certificate.pb.h"
|
|
|
|
namespace widevine {
|
|
namespace {
|
|
|
|
const int kKeyboxSizeBytes = 72;
|
|
|
|
} // namespace
|
|
|
|
// instead of ClientCert** to explicitly assigning ownership of the created
|
|
// object to the caller.
|
|
|
|
Status ClientCert::Create(const DrmRootCertificate* root_certificate,
|
|
ClientIdentification::TokenType token_type,
|
|
const std::string& token, ClientCert** client_cert) {
|
|
DCHECK(client_cert);
|
|
if (token_type == ClientIdentification::KEYBOX) {
|
|
*client_cert = nullptr;
|
|
if (token.size() < kKeyboxSizeBytes) {
|
|
return Status(error_space, INVALID_KEYBOX_TOKEN,
|
|
"keybox-token-is-too-short");
|
|
}
|
|
return ClientCert::CreateWithKeybox(token, client_cert);
|
|
} else if (token_type == ClientIdentification::DRM_DEVICE_CERTIFICATE) {
|
|
return CreateWithDrmCertificate(root_certificate, token, client_cert);
|
|
} else {
|
|
return Status(error_space, error::UNIMPLEMENTED,
|
|
"client-type-not-implemented");
|
|
}
|
|
}
|
|
|
|
Status ClientCert::CreateWithKeybox(const std::string& keybox_token,
|
|
ClientCert** client_cert) {
|
|
CHECK(client_cert);
|
|
*client_cert = nullptr;
|
|
|
|
std::unique_ptr<KeyboxClientCert> new_client_cert(new KeyboxClientCert);
|
|
Status status = new_client_cert->Initialize(keybox_token);
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
|
|
*client_cert = new_client_cert.release();
|
|
return OkStatus();
|
|
}
|
|
|
|
Status ClientCert::CreateWithDrmCertificate(
|
|
const DrmRootCertificate* root_certificate, const std::string& drm_certificate,
|
|
ClientCert** client_cert) {
|
|
CHECK(client_cert);
|
|
*client_cert = nullptr;
|
|
|
|
std::unique_ptr<CertificateClientCert> new_client_cert(
|
|
new CertificateClientCert);
|
|
Status status =
|
|
new_client_cert->Initialize(root_certificate, drm_certificate);
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
|
|
*client_cert = new_client_cert.release();
|
|
return OkStatus();
|
|
}
|
|
|
|
void ClientCert::CreateSignature(const std::string& message, std::string* signature) {
|
|
DCHECK(signature);
|
|
DCHECK(!signing_key().empty());
|
|
if (signature == nullptr) {
|
|
return;
|
|
}
|
|
using crypto_util::CreateSignatureHmacSha256;
|
|
*signature =
|
|
CreateSignatureHmacSha256(GetServerSigningKey(signing_key()), message);
|
|
}
|
|
|
|
void ClientCert::GenerateSigningKey(const std::string& message,
|
|
ProtocolVersion protocol_version) {
|
|
if (!signing_key_.empty()) return;
|
|
DCHECK(!key().empty());
|
|
using crypto_util::DeriveKey;
|
|
using crypto_util::kSigningKeyLabel;
|
|
set_signing_key(
|
|
DeriveKey(key(), kSigningKeyLabel,
|
|
protocol_version < VERSION_2_2 ? message : Sha512_Hash(message),
|
|
SigningKeyMaterialSizeBits(protocol_version)));
|
|
}
|
|
|
|
KeyboxClientCert::KeyboxClientCert() {}
|
|
|
|
KeyboxClientCert::~KeyboxClientCert() {}
|
|
|
|
void KeyboxClientCert::SetPreProvisioningKeys(
|
|
const std::multimap<uint32_t, std::string>& keymap) {
|
|
std::vector<WvmTokenHandler::PreprovKey> keyvector;
|
|
keyvector.reserve(keymap.size());
|
|
for (std::multimap<uint32_t, std::string>::const_iterator it = keymap.begin();
|
|
it != keymap.end(); ++it) {
|
|
std::string key = absl::HexStringToBytes(it->second);
|
|
DCHECK_EQ(key.size(), 16);
|
|
keyvector.push_back(WvmTokenHandler::PreprovKey(it->first, key));
|
|
}
|
|
WvmTokenHandler::SetPreprovKeys(keyvector);
|
|
}
|
|
|
|
bool KeyboxClientCert::IsSystemIdKnown(const uint32_t system_id) {
|
|
return WvmTokenHandler::IsSystemIdKnown(system_id);
|
|
}
|
|
|
|
uint32_t KeyboxClientCert::GetSystemId(const std::string& keybox_bytes) {
|
|
return WvmTokenHandler::GetSystemId(keybox_bytes);
|
|
}
|
|
|
|
Status KeyboxClientCert::Initialize(const std::string& keybox_bytes) {
|
|
if (keybox_bytes.size() < kKeyboxSizeBytes) {
|
|
return Status(error_space, INVALID_KEYBOX_TOKEN,
|
|
"keybox-token-is-too-short");
|
|
}
|
|
|
|
set_system_id(WvmTokenHandler::GetSystemId(keybox_bytes));
|
|
set_serial_number(WvmTokenHandler::GetEncryptedUniqueId(keybox_bytes));
|
|
bool insecure_keybox = false;
|
|
Status status = WvmTokenHandler::DecryptDeviceKey(keybox_bytes, &device_key_,
|
|
nullptr, &insecure_keybox);
|
|
if (!status.ok()) {
|
|
Errors new_code = status.error_code() == error::NOT_FOUND
|
|
? MISSING_PRE_PROV_KEY
|
|
: KEYBOX_DECRYPT_ERROR;
|
|
return Status(error_space, new_code, status.error_message());
|
|
}
|
|
return OkStatus();
|
|
}
|
|
|
|
Status KeyboxClientCert::VerifySignature(const std::string& message,
|
|
const std::string& signature,
|
|
ProtocolVersion protocol_version) {
|
|
DCHECK(!signing_key().empty());
|
|
using crypto_util::VerifySignatureHmacSha256;
|
|
if (!VerifySignatureHmacSha256(
|
|
GetClientSigningKey(signing_key(), protocol_version), signature,
|
|
message)) {
|
|
return Status(error_space, INVALID_SIGNATURE, "invalid-keybox-mac");
|
|
}
|
|
return OkStatus();
|
|
}
|
|
|
|
CertificateClientCert::CertificateClientCert() {}
|
|
|
|
CertificateClientCert::~CertificateClientCert() {}
|
|
|
|
Status CertificateClientCert::Initialize(
|
|
const DrmRootCertificate* drm_root_certificate,
|
|
const std::string& serialized_certificate) {
|
|
CHECK(drm_root_certificate);
|
|
|
|
SignedDrmCertificate signed_device_cert;
|
|
DrmCertificate device_cert;
|
|
Status status = drm_root_certificate->VerifyCertificate(
|
|
serialized_certificate, &signed_device_cert, &device_cert);
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
|
|
const SignedDrmCertificate& signer = signed_device_cert.signer();
|
|
DrmCertificate model_certificate;
|
|
if (!model_certificate.ParseFromString(signer.drm_certificate())) {
|
|
return Status(error_space, INVALID_DRM_CERTIFICATE,
|
|
"drm-certificate-invalid-signer");
|
|
}
|
|
if (!model_certificate.has_serial_number()) {
|
|
return Status(error_space, INVALID_DRM_CERTIFICATE,
|
|
"missing-signer-serial-number");
|
|
}
|
|
// Check to see if this model certificate is signed by a
|
|
// provisioner (entity using Widevine Provisioning Server SDK).
|
|
if (signer.has_signer()) {
|
|
DrmCertificate provisioner_certificate;
|
|
if (!provisioner_certificate.ParseFromString(
|
|
signer.signer().drm_certificate())) {
|
|
return Status(error_space, INVALID_DRM_CERTIFICATE,
|
|
"model-certificate-invalid-signer");
|
|
}
|
|
if (provisioner_certificate.type() == DrmCertificate::PROVISIONER) {
|
|
set_signed_by_provisioner(true);
|
|
} else {
|
|
return Status(error_space, INVALID_DRM_CERTIFICATE,
|
|
"expected-provisioning-provider-certificate-type");
|
|
}
|
|
if (!provisioner_certificate.has_provider_id() ||
|
|
provisioner_certificate.provider_id().empty()) {
|
|
return Status(error_space, INVALID_DRM_CERTIFICATE,
|
|
"missing-provisioning-service-id");
|
|
}
|
|
set_service_id(provisioner_certificate.provider_id());
|
|
}
|
|
set_signer_serial_number(model_certificate.serial_number());
|
|
set_signer_creation_time_seconds(model_certificate.creation_time_seconds());
|
|
if (!model_certificate.has_system_id()) {
|
|
return Status(error_space, INVALID_DRM_CERTIFICATE,
|
|
"model-certificate-missing-system-id");
|
|
}
|
|
set_system_id(model_certificate.system_id());
|
|
set_serial_number(device_cert.serial_number());
|
|
set_public_key(device_cert.public_key());
|
|
rsa_public_key_.reset(RsaPublicKey::Create(public_key()));
|
|
if (rsa_public_key_ == nullptr) {
|
|
return Status(error_space, INVALID_DRM_CERTIFICATE,
|
|
"drm-certificate-public-key-failed");
|
|
}
|
|
|
|
// TODO(user): Move this somewhere else. It is license protocol.
|
|
set_key(Random16Bytes());
|
|
if (!rsa_public_key_->Encrypt(key(), &encrypted_session_key_)) {
|
|
return Status(error_space, ENCRYPT_ERROR,
|
|
"drm-certificate-failed-encrypt-session-key");
|
|
}
|
|
|
|
return OkStatus();
|
|
}
|
|
|
|
Status CertificateClientCert::VerifySignature(
|
|
const std::string& message, const std::string& signature,
|
|
ProtocolVersion protocol_version) {
|
|
CHECK(rsa_public_key_);
|
|
|
|
if (!rsa_public_key_->VerifySignature(
|
|
protocol_version < VERSION_2_2 ? message : Sha512_Hash(message),
|
|
signature)) {
|
|
return Status(error_space, INVALID_SIGNATURE, "");
|
|
}
|
|
return OkStatus();
|
|
}
|
|
|
|
} // namespace widevine
|