Export media_cas_proxy_sdk
This commit is contained in:
202
license_server_sdk/internal/BUILD
Normal file
202
license_server_sdk/internal/BUILD
Normal file
@@ -0,0 +1,202 @@
|
||||
################################################################################
|
||||
# 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.
|
||||
################################################################################
|
||||
|
||||
# This package is the Widevine SDK used by DRM license servers. It supports
|
||||
# Widevine modular DRM clients, e.g. Widevine CDM in Encryption Media Extension
|
||||
# in Chrome.
|
||||
|
||||
package(default_visibility = [":friends"])
|
||||
|
||||
# friends is a package_group which own the visibility for video widevine license_server_sdk lib
|
||||
package_group(
|
||||
name = "friends",
|
||||
packages = [
|
||||
"//common/...",
|
||||
"//license_server_sdk/...",
|
||||
"//media_cas_proxy_sdk/...",
|
||||
"//proxy_sdk/...",
|
||||
"//sdk/...",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
filegroup(
|
||||
name = "binary_release_files",
|
||||
srcs = [
|
||||
"client_cert.h",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "session_impl",
|
||||
srcs = [
|
||||
"session_impl.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"session_impl.h",
|
||||
],
|
||||
deps = [
|
||||
":sdk",
|
||||
"//base",
|
||||
"//strings",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@abseil_repo//absl/synchronization",
|
||||
"//util/endian",
|
||||
"//util/random:global_id",
|
||||
"//util:status",
|
||||
"//common:aes_cbc_util",
|
||||
"//common:certificate_type",
|
||||
"//common:certificate_util",
|
||||
"//common:crypto_util",
|
||||
"//common:drm_root_certificate",
|
||||
"//common:drm_service_certificate",
|
||||
"//common:error_space",
|
||||
"//common:random_util",
|
||||
"//common:remote_attestation_verifier",
|
||||
"//common:rsa_key",
|
||||
"//common:signing_key_util",
|
||||
"//common:verified_media_pipeline",
|
||||
"//common:vmp_checker",
|
||||
"//protos/public:client_identification_proto",
|
||||
"//protos/public:errors_proto",
|
||||
"//protos/public:license_protocol_proto",
|
||||
"//protos/public:license_server_sdk_proto",
|
||||
"//protos/public:provisioned_device_info_proto",
|
||||
"//protos/public:widevine_pssh_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "sdk",
|
||||
srcs = [
|
||||
"client_cert.cc",
|
||||
"device_status_list.cc",
|
||||
"key_control_block.cc",
|
||||
"parse_content_id.cc",
|
||||
"generate_error_response.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"client_cert.h",
|
||||
"device_status_list.h",
|
||||
"generate_error_response.h",
|
||||
"key_control_block.h",
|
||||
"parse_content_id.h",
|
||||
"session_usage_report.h",
|
||||
],
|
||||
deps = [
|
||||
"//base",
|
||||
"//strings",
|
||||
"@abseil_repo//absl/strings",
|
||||
"@abseil_repo//absl/synchronization",
|
||||
"//external:openssl",
|
||||
"//util/endian",
|
||||
"//util/gtl:map_util",
|
||||
"//util:status",
|
||||
"//common:crypto_util",
|
||||
"//common:drm_service_certificate",
|
||||
"//common:error_space",
|
||||
"//common:random_util",
|
||||
"//common:rsa_key",
|
||||
"//common:signing_key_util",
|
||||
"//common:wvm_token_handler",
|
||||
"//sdk/external/common/wvpl:wvpl_types",
|
||||
"//protos/public:client_identification_proto",
|
||||
"//protos/public:device_certificate_status_proto",
|
||||
"//protos/public:drm_certificate_proto",
|
||||
"//protos/public:errors_proto",
|
||||
"//protos/public:license_protocol_proto",
|
||||
"//protos/public:license_server_sdk_proto",
|
||||
"//protos/public:provisioned_device_info_proto",
|
||||
"//protos/public:signed_drm_certificate_proto",
|
||||
"//protos/public:widevine_pssh_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "session_impl_test",
|
||||
timeout = "short",
|
||||
srcs = ["session_impl_test.cc"],
|
||||
deps = [
|
||||
":sdk",
|
||||
":session_impl",
|
||||
"//base",
|
||||
"//external:protobuf",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//common:aes_cbc_util",
|
||||
"//common:crypto_util",
|
||||
"//common:drm_root_certificate",
|
||||
"//common:error_space",
|
||||
"//common:remote_attestation_verifier",
|
||||
"//common:rsa_key",
|
||||
"//common:rsa_test_keys",
|
||||
"//common:rsa_util",
|
||||
"//common:signing_key_util",
|
||||
"//common:test_certificates",
|
||||
"//common:test_utils",
|
||||
"//protos/public:client_identification_proto",
|
||||
"//protos/public:device_certificate_status_proto",
|
||||
"//protos/public:drm_certificate_proto",
|
||||
"//protos/public:errors_proto",
|
||||
"//protos/public:license_protocol_proto",
|
||||
"//protos/public:license_server_sdk_proto",
|
||||
"//protos/public:provisioned_device_info_proto",
|
||||
"//protos/public:remote_attestation_proto",
|
||||
"//protos/public:signed_drm_certificate_proto",
|
||||
"//protos/public:widevine_pssh_proto",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
cc_test(
|
||||
name = "key_control_block_test",
|
||||
timeout = "short",
|
||||
srcs = ["key_control_block_test.cc"],
|
||||
deps = [
|
||||
":sdk",
|
||||
"//base",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//protos/public:license_protocol_proto",
|
||||
"//protos/public:license_server_sdk_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "device_status_list_test",
|
||||
timeout = "short",
|
||||
srcs = ["device_status_list_test.cc"],
|
||||
deps = [
|
||||
":sdk",
|
||||
"//base",
|
||||
"//testing:gunit_main",
|
||||
"@abseil_repo//absl/strings",
|
||||
"//common:rsa_key",
|
||||
"//common:rsa_test_keys",
|
||||
"//protos/public:client_identification_proto",
|
||||
"//protos/public:errors_proto",
|
||||
"//protos/public:provisioned_device_info_proto",
|
||||
"//protos/public:signed_drm_certificate_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "parse_content_id_test",
|
||||
timeout = "short",
|
||||
srcs = ["parse_content_id_test.cc"],
|
||||
deps = [
|
||||
":sdk",
|
||||
"//base",
|
||||
"//testing:gunit_main",
|
||||
"//util/endian",
|
||||
"//common:error_space",
|
||||
"//protos/public:errors_proto",
|
||||
"//protos/public:license_server_sdk_proto",
|
||||
],
|
||||
)
|
||||
408
license_server_sdk/internal/client_cert.cc
Normal file
408
license_server_sdk/internal/client_cert.cc
Normal file
@@ -0,0 +1,408 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 "license_server_sdk/internal/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 "util/status.h"
|
||||
#include "common/crypto_util.h"
|
||||
#include "common/error_space.h"
|
||||
#include "common/random_util.h"
|
||||
#include "common/signing_key_util.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 kPreProvisioningKeySizeBytes = 16;
|
||||
const int kKeyboxSizeBytes = 72;
|
||||
|
||||
struct ValidatedSignerCertificate {
|
||||
std::string certificate;
|
||||
std::string signature;
|
||||
};
|
||||
|
||||
class ValidatedSignerCache {
|
||||
public:
|
||||
void SetRootPublicKey(std::unique_ptr<RsaPublicKey> new_root_key);
|
||||
bool Exist(const std::string& serial_number, const std::string& certificate,
|
||||
const std::string& signature);
|
||||
util::Status ValidateSigner(const std::string& serial_number,
|
||||
const std::string& certificate,
|
||||
const std::string& signature);
|
||||
|
||||
void ResetSignerCache();
|
||||
size_t SignerCacheSize();
|
||||
|
||||
static ValidatedSignerCache* GetSingleton();
|
||||
|
||||
private:
|
||||
absl::Mutex rsa_root_public_key_mutex_;
|
||||
std::unique_ptr<RsaPublicKey> rsa_root_public_key_
|
||||
GUARDED_BY(&rsa_root_public_key_mutex_);
|
||||
absl::Mutex validated_signer_cache_mutex_;
|
||||
std::map<std::string, ValidatedSignerCertificate> validated_signer_cache_
|
||||
GUARDED_BY(&validated_signer_cache_mutex_);
|
||||
};
|
||||
|
||||
void ValidatedSignerCache::SetRootPublicKey(
|
||||
std::unique_ptr<RsaPublicKey> new_root_key) {
|
||||
absl::WriterMutexLock lock(&rsa_root_public_key_mutex_);
|
||||
rsa_root_public_key_ = std::move(new_root_key);
|
||||
}
|
||||
|
||||
bool ValidatedSignerCache::Exist(const std::string& serial_number,
|
||||
const std::string& certificate,
|
||||
const std::string& signature) {
|
||||
absl::ReaderMutexLock lock(&validated_signer_cache_mutex_);
|
||||
ValidatedSignerCertificate* validated_signer =
|
||||
gtl::FindOrNull(validated_signer_cache_, serial_number);
|
||||
return (validated_signer != nullptr) &&
|
||||
(validated_signer->certificate == certificate) &&
|
||||
(validated_signer->signature == signature);
|
||||
}
|
||||
|
||||
util::Status ValidatedSignerCache::ValidateSigner(const std::string& serial_number,
|
||||
const std::string& certificate,
|
||||
const std::string& signature) {
|
||||
{
|
||||
absl::ReaderMutexLock key_lock(&rsa_root_public_key_mutex_);
|
||||
if (rsa_root_public_key_ == nullptr) {
|
||||
return util::Status(error_space, ROOT_CERTIFICATE_NOT_SET, "");
|
||||
}
|
||||
if (!rsa_root_public_key_->VerifySignature(certificate, signature)) {
|
||||
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"signer-certificate-verification-failed");
|
||||
}
|
||||
}
|
||||
absl::WriterMutexLock cache_lock(&validated_signer_cache_mutex_);
|
||||
validated_signer_cache_[serial_number].certificate = certificate;
|
||||
validated_signer_cache_[serial_number].signature = signature;
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
void ValidatedSignerCache::ResetSignerCache() {
|
||||
absl::WriterMutexLock lock(&validated_signer_cache_mutex_);
|
||||
validated_signer_cache_.clear();
|
||||
}
|
||||
|
||||
size_t ValidatedSignerCache::SignerCacheSize() {
|
||||
absl::ReaderMutexLock lock(&validated_signer_cache_mutex_);
|
||||
return validated_signer_cache_.size();
|
||||
}
|
||||
|
||||
ValidatedSignerCache* ValidatedSignerCache::GetSingleton() {
|
||||
static auto* const kInstance = new ValidatedSignerCache();
|
||||
return kInstance;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// TODO(user): change to util::StatusOr<std::unique_ptr<ClientCert>>
|
||||
// instead of ClientCert** to explicitly assigning ownership of the created
|
||||
// object to the caller.
|
||||
|
||||
util::Status ClientCert::Create(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 util::Status(error_space, INVALID_KEYBOX_TOKEN,
|
||||
"keybox-token-is-too-short");
|
||||
}
|
||||
return ClientCert::CreateWithToken(token, client_cert);
|
||||
} else if (token_type == ClientIdentification::DRM_DEVICE_CERTIFICATE) {
|
||||
return CreateCertificateClientCert(token, client_cert);
|
||||
} else {
|
||||
return util::Status(error_space, util::error::UNIMPLEMENTED,
|
||||
"client-type-not-implemented");
|
||||
}
|
||||
}
|
||||
|
||||
util::Status ClientCert::CreateWithToken(const std::string& keybox_token,
|
||||
ClientCert** client_cert) {
|
||||
*client_cert = nullptr;
|
||||
std::unique_ptr<ClientCert> ret(new KeyboxClientCert(keybox_token));
|
||||
if (ret->status() != util::OkStatus()) {
|
||||
return ret->status();
|
||||
}
|
||||
*client_cert = ret.release();
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status ClientCert::CreateCertificateClientCert(
|
||||
const std::string& drm_certificate, ClientCert** client_cert) {
|
||||
std::unique_ptr<ClientCert> ret(new CertificateClientCert(drm_certificate));
|
||||
if (ret->status() != util::OkStatus()) {
|
||||
return ret->status();
|
||||
}
|
||||
*client_cert = ret.release();
|
||||
return util::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, message,
|
||||
SigningKeyMaterialSize(protocol_version)));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
KeyboxClientCert::KeyboxClientCert(const std::string& keybox_bytes) {
|
||||
if (keybox_bytes.size() < kKeyboxSizeBytes) {
|
||||
set_status(util::Status(error_space, INVALID_KEYBOX_TOKEN,
|
||||
"keybox-token-is-too-short"));
|
||||
return;
|
||||
}
|
||||
|
||||
set_system_id(WvmTokenHandler::GetSystemId(keybox_bytes));
|
||||
set_serial_number(WvmTokenHandler::GetEncryptedUniqueId(keybox_bytes));
|
||||
bool insecure_keybox = false;
|
||||
util::Status status = WvmTokenHandler::DecryptDeviceKey(
|
||||
keybox_bytes, &device_key_, nullptr, &insecure_keybox);
|
||||
if (!status.ok()) {
|
||||
Errors new_code = status.error_code() == util::error::NOT_FOUND
|
||||
? MISSING_PRE_PROV_KEY
|
||||
: KEYBOX_DECRYPT_ERROR;
|
||||
set_status(util::Status(error_space, new_code, status.error_message()));
|
||||
}
|
||||
}
|
||||
|
||||
bool 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)) {
|
||||
set_status(util::Status(error_space, INVALID_SIGNATURE, ""));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
util::Status CertificateClientCert::SetDrmRootCertificatePublicKey(
|
||||
const std::string& root_public_key) {
|
||||
std::unique_ptr<RsaPublicKey> new_root_key(
|
||||
RsaPublicKey::Create(root_public_key));
|
||||
if (new_root_key == nullptr) {
|
||||
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"root-certificate-rsa-public-key-failed");
|
||||
}
|
||||
ValidatedSignerCache::GetSingleton()->SetRootPublicKey(
|
||||
std::move(new_root_key));
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
// Checks the device certificate using the following steps.
|
||||
// 1. Load the certificate bytes into a signed device certificate.
|
||||
// 2. Get the signer for the certificate.
|
||||
// 3. Verify the signature of the certificate using the signer.
|
||||
// 4. Load the root certificate.
|
||||
// 5. Verify the signature of the signer certificate.
|
||||
util::Status CertificateClientCert::ValidateCertificate(
|
||||
const SignedDrmCertificate& signed_drm_certificate) {
|
||||
// TODO(user): Cache valid certificates.
|
||||
// TODO(user): Find out why signed_drm_certificate.has_signer() always
|
||||
// returns false. Blindly assuming signer is there for now.
|
||||
const SignedDrmCertificate& signer = signed_drm_certificate.signer();
|
||||
DrmCertificate intermediate_certificate;
|
||||
if (!intermediate_certificate.ParseFromString(signer.drm_certificate())) {
|
||||
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"device-certificate-invalid-signer");
|
||||
}
|
||||
std::unique_ptr<RsaPublicKey> rsa_public_signer_key(
|
||||
RsaPublicKey::Create(intermediate_certificate.public_key()));
|
||||
if (!rsa_public_signer_key.get()) {
|
||||
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"signer-certificate-public-key-failed");
|
||||
}
|
||||
if (!rsa_public_signer_key->VerifySignature(
|
||||
signed_drm_certificate.drm_certificate(),
|
||||
signed_drm_certificate.signature())) {
|
||||
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"device-certificate-verification-failed");
|
||||
}
|
||||
if (!intermediate_certificate.has_serial_number()) {
|
||||
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"missing-signer-serial-number");
|
||||
}
|
||||
// Check to see if this intermediate device certificate is signed by a
|
||||
// provisioner (entity using Widevine Provisioning Server SDK).
|
||||
// TODO(user): refactor this code for clarity with the cert chaining.
|
||||
if (signer.has_signer()) {
|
||||
DrmCertificate provisioner_certificate;
|
||||
if (!provisioner_certificate.ParseFromString(
|
||||
signer.signer().drm_certificate())) {
|
||||
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"intermediate-certificate-invalid-signer");
|
||||
}
|
||||
if (provisioner_certificate.type() == DrmCertificate::PROVISIONER) {
|
||||
set_signed_by_provisioner(true);
|
||||
} else {
|
||||
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"expected-provisioning-provider-certificate-type");
|
||||
}
|
||||
if (!CheckSignerCache(provisioner_certificate.serial_number(),
|
||||
signer.signer().drm_certificate(),
|
||||
signer.signature())) {
|
||||
util::Status status = ValidateSigner(
|
||||
provisioner_certificate.serial_number(),
|
||||
signer.signer().drm_certificate(), signer.signer().signature());
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
if (!provisioner_certificate.has_provider_id() ||
|
||||
provisioner_certificate.provider_id().empty()) {
|
||||
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"missing-provisioning-service-id");
|
||||
}
|
||||
set_service_id(provisioner_certificate.provider_id());
|
||||
} else {
|
||||
if (!CheckSignerCache(intermediate_certificate.serial_number(),
|
||||
signer.drm_certificate(), signer.signature())) {
|
||||
util::Status status =
|
||||
ValidateSigner(intermediate_certificate.serial_number(),
|
||||
signer.drm_certificate(), signer.signature());
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
set_signer_serial_number(intermediate_certificate.serial_number());
|
||||
set_signer_creation_time_seconds(
|
||||
intermediate_certificate.creation_time_seconds());
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
CertificateClientCert::CertificateClientCert(
|
||||
const std::string& signed_drm_certificate_bytes) {
|
||||
SignedDrmCertificate signed_drm_certificate;
|
||||
if (!signed_drm_certificate.ParseFromString(signed_drm_certificate_bytes)) {
|
||||
set_status(util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"device-certificate-invalid-token"));
|
||||
return;
|
||||
}
|
||||
util::Status status = ValidateCertificate(signed_drm_certificate);
|
||||
if (!status.ok()) {
|
||||
set_status(status);
|
||||
return;
|
||||
}
|
||||
DrmCertificate drm_certificate;
|
||||
if (!drm_certificate.ParseFromString(
|
||||
signed_drm_certificate.drm_certificate())) {
|
||||
set_status(util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"device-certificate-invalid"));
|
||||
return;
|
||||
}
|
||||
if (!drm_certificate.has_system_id()) {
|
||||
set_status(util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"device-certificate-missing-system-id"));
|
||||
}
|
||||
set_system_id(drm_certificate.system_id());
|
||||
set_serial_number(drm_certificate.serial_number());
|
||||
set_public_key(drm_certificate.public_key());
|
||||
rsa_public_key_.reset(RsaPublicKey::Create(public_key()));
|
||||
if (rsa_public_key_ == nullptr) {
|
||||
set_status(util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"device-certificate-public-key-failed"));
|
||||
return;
|
||||
}
|
||||
set_key(Random16Bytes());
|
||||
if (!rsa_public_key_->Encrypt(key(), &encrypted_session_key_)) {
|
||||
set_status(util::Status(error_space, ENCRYPT_ERROR,
|
||||
"device-certificate-failed-encrypt-session-key"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CertificateClientCert::~CertificateClientCert() {}
|
||||
|
||||
bool CertificateClientCert::VerifySignature(const std::string& message,
|
||||
const std::string& signature,
|
||||
ProtocolVersion protocol_version) {
|
||||
if (!rsa_public_key_->VerifySignature(message, signature)) {
|
||||
set_status(util::Status(error_space, INVALID_SIGNATURE, ""));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CertificateClientCert::CheckSignerCache(const std::string& serial_number,
|
||||
const std::string& certificate,
|
||||
const std::string& signature) const {
|
||||
return ValidatedSignerCache::GetSingleton()->Exist(serial_number, certificate,
|
||||
signature);
|
||||
}
|
||||
|
||||
util::Status CertificateClientCert::ValidateSigner(const std::string& serial_number,
|
||||
const std::string& certificate,
|
||||
const std::string& signature) {
|
||||
return ValidatedSignerCache::GetSingleton()->ValidateSigner(
|
||||
serial_number, certificate, signature);
|
||||
}
|
||||
|
||||
void CertificateClientCert::ResetSignerCache() {
|
||||
ValidatedSignerCache::GetSingleton()->ResetSignerCache();
|
||||
}
|
||||
|
||||
size_t CertificateClientCert::SignerCacheSize() {
|
||||
return ValidatedSignerCache::GetSingleton()->SignerCacheSize();
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
216
license_server_sdk/internal/client_cert.h
Normal file
216
license_server_sdk/internal/client_cert.h
Normal file
@@ -0,0 +1,216 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef LICENSE_SERVER_SDK_INTERNAL_CLIENT_CERT_H__
|
||||
#define LICENSE_SERVER_SDK_INTERNAL_CLIENT_CERT_H__
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "util/status.h"
|
||||
#include "common/rsa_key.h"
|
||||
#include "protos/public/client_identification.pb.h"
|
||||
#include "protos/public/license_protocol.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
class SignedDrmCertificate;
|
||||
|
||||
// Handler class for LicenseRequests; validates requests and encrypts licenses.
|
||||
// TODO(user): Remove extra accessors after Keybox parsing is moved
|
||||
// to a separate class in KeyboxClientCert class.
|
||||
class ClientCert {
|
||||
public:
|
||||
virtual ~ClientCert() {}
|
||||
static util::Status Create(
|
||||
widevine::ClientIdentification::TokenType token_type,
|
||||
const std::string& token, ClientCert** client_cert);
|
||||
// Creates a Keybox based ClientCert.
|
||||
static util::Status CreateWithToken(const std::string& keybox_token,
|
||||
ClientCert** client_cert);
|
||||
// Creates a Device Certificate based ClientCert.
|
||||
static util::Status CreateCertificateClientCert(const std::string& drm_certificate,
|
||||
ClientCert** client_cert);
|
||||
// Creates a HMAC SHA256 signature based on the message and the key().
|
||||
// signature is owned by the caller and can not be NULL.
|
||||
virtual void CreateSignature(const std::string& message, std::string* signature);
|
||||
// Checks the passed in signature against a signature created used the
|
||||
// classes information and the passed in message. Returns true if signature
|
||||
// is valid.
|
||||
virtual bool VerifySignature(const std::string& message, const std::string& signature,
|
||||
ProtocolVersion protocol_version) = 0;
|
||||
// Creates a signing_key that is accessible using signing_key(). Signing_key
|
||||
// is constructed by doing a key derivation using the key() and message.
|
||||
virtual void GenerateSigningKey(const std::string& message,
|
||||
ProtocolVersion protocol_version);
|
||||
// Used to create signing keys. For Keybox token types this is the device key.
|
||||
// For Device Certificate token types this the session key.
|
||||
virtual const std::string& key() const = 0;
|
||||
virtual void set_key(const std::string& key) = 0;
|
||||
virtual const std::string& encrypted_key() const = 0;
|
||||
virtual uint32_t system_id() const { return system_id_; }
|
||||
virtual const std::string& signing_key() const { return signing_key_; }
|
||||
virtual const std::string& public_key() const { return public_key_; }
|
||||
virtual const std::string& serial_number() const { return serial_number_; }
|
||||
virtual void set_serial_number(const std::string& serial_number) {
|
||||
serial_number_ = serial_number;
|
||||
}
|
||||
virtual const std::string& signer_serial_number() const {
|
||||
return signer_serial_number_;
|
||||
}
|
||||
virtual uint32_t signer_creation_time_seconds() const {
|
||||
return signer_creation_time_seconds_;
|
||||
}
|
||||
virtual const util::Status& status() const { return status_; }
|
||||
virtual widevine::ClientIdentification::TokenType type() const = 0;
|
||||
virtual std::string service_id() const { return service_id_; }
|
||||
virtual bool signed_by_provisioner() const { return signed_by_provisioner_; }
|
||||
|
||||
protected:
|
||||
ClientCert() {}
|
||||
|
||||
virtual void set_system_id(uint32_t system_id) { system_id_ = system_id; }
|
||||
virtual void set_signing_key(const std::string& signing_key) {
|
||||
signing_key_ = signing_key;
|
||||
}
|
||||
virtual void set_status(const util::Status& status) { status_ = status; }
|
||||
virtual void set_service_id(const std::string& service_id) {
|
||||
service_id_ = service_id;
|
||||
}
|
||||
virtual void set_signed_by_provisioner(bool provisioner_signed_flag) {
|
||||
signed_by_provisioner_ = provisioner_signed_flag;
|
||||
}
|
||||
|
||||
std::string public_key_;
|
||||
std::string serial_number_;
|
||||
std::string signer_serial_number_;
|
||||
uint32_t signer_creation_time_seconds_ = 0;
|
||||
bool signed_by_provisioner_ = false;
|
||||
|
||||
private:
|
||||
uint32_t system_id_ = 0;
|
||||
std::string signing_key_;
|
||||
util::Status status_;
|
||||
std::string service_id_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ClientCert);
|
||||
};
|
||||
|
||||
// This class implements the crypto operations based on the Widevine keybox.
|
||||
// It will unpack token and perform all the crypto operations for securing
|
||||
// the key material in the license response.
|
||||
class KeyboxClientCert : public ClientCert {
|
||||
public:
|
||||
~KeyboxClientCert() override;
|
||||
|
||||
// Set the system-wide pre-provisioning keys; argument must be human-readable
|
||||
// hex digits.
|
||||
// Must be called before any other method of this class is called, unless
|
||||
// created by ClientCert::CreateWithPreProvisioningKey(...).
|
||||
static void SetPreProvisioningKeys(const std::multimap<uint32_t, std::string>& keys);
|
||||
static bool IsSystemIdKnown(const uint32_t system_id);
|
||||
static uint32_t GetSystemId(const std::string& keybox_bytes);
|
||||
|
||||
bool VerifySignature(const std::string& message, const std::string& signature,
|
||||
ProtocolVersion protocol_version) override;
|
||||
const std::string& key() const override { return device_key_; }
|
||||
void set_key(const std::string& key) override { device_key_ = key; }
|
||||
const std::string& encrypted_key() const override { return encrypted_device_key_; }
|
||||
widevine::ClientIdentification::TokenType type() const override {
|
||||
return widevine::ClientIdentification::KEYBOX;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ClientCert;
|
||||
friend class MockKeyboxClientCert;
|
||||
explicit KeyboxClientCert(const std::string& keybox_bytes);
|
||||
|
||||
std::string device_key_;
|
||||
std::string encrypted_device_key_;
|
||||
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(KeyboxClientCert);
|
||||
};
|
||||
// This class implements the device certificate operations based on RSA keys.
|
||||
// It will unpack token and perform all the crypto operations for securing
|
||||
// the key material in the license response.
|
||||
using widevine::RsaPublicKey;
|
||||
class CertificateClientCert : public ClientCert {
|
||||
public:
|
||||
~CertificateClientCert() override;
|
||||
// Sets the root certificate for certificate validation.
|
||||
static util::Status SetDrmRootCertificatePublicKey(
|
||||
const std::string& root_public_key);
|
||||
bool VerifySignature(const std::string& message, const std::string& signature,
|
||||
ProtocolVersion protocol_version) override;
|
||||
const std::string& key() const override { return session_key_; }
|
||||
void set_key(const std::string& key) override { session_key_ = key; }
|
||||
const std::string& encrypted_key() const override {
|
||||
return encrypted_session_key_;
|
||||
}
|
||||
widevine::ClientIdentification::TokenType type() const override {
|
||||
return widevine::ClientIdentification::DRM_DEVICE_CERTIFICATE;
|
||||
}
|
||||
|
||||
protected:
|
||||
friend class ClientCert;
|
||||
friend class MockCertificateClientCert;
|
||||
explicit CertificateClientCert(const std::string& signed_drm_certificate_bytes);
|
||||
util::Status ValidateCertificate(
|
||||
const SignedDrmCertificate& signed_drm_certificate);
|
||||
virtual void set_public_key(const std::string& public_key) {
|
||||
public_key_ = public_key;
|
||||
}
|
||||
virtual void set_signer_serial_number(const std::string& signer_serial_number) {
|
||||
signer_serial_number_ = signer_serial_number;
|
||||
}
|
||||
virtual void set_signer_creation_time_seconds(uint32_t creation_time_seconds) {
|
||||
signer_creation_time_seconds_ = creation_time_seconds;
|
||||
}
|
||||
// This method checks to see if a cached signature for the signer certificate
|
||||
// exists. The cache is populated by the ValidateSignature method, below.
|
||||
// - serial_number is the signer (intermediate) certificate serial number.
|
||||
// This method does a cached signature lookup using this value as the key.
|
||||
// - certificate is the serialized signer certificate. This method compares
|
||||
// this value to the value cached to determine whether there is a match.
|
||||
// - signature is the signature for the serialized signer certificate. This
|
||||
// method compares this value to the value cached to determine whether
|
||||
// there is a match.
|
||||
// Returns true if there exists a matching cached signature for the signer
|
||||
// certificate with the specified serial number, serialized
|
||||
// DrmCertificate, and serialized DrmCertificate signature.
|
||||
// Returns false otherwise.
|
||||
virtual bool CheckSignerCache(const std::string& serial_number,
|
||||
const std::string& certificate,
|
||||
const std::string& signature) const;
|
||||
// This method verifies the signature of a signer (intermediate) certificate,
|
||||
// caching it in the signer cache if verification succeeds.
|
||||
// - serial_number is the signer (intermediate) certificate serial number.
|
||||
// - certificate is the serialized signer certificate.
|
||||
// - signature is the signature for the serialized signer certificate, signed
|
||||
// with the root certificate private key.
|
||||
// Returns util::Status::OK and caches the validated signer information in
|
||||
// the signer cache if signature validation succeeds. Otherwise, returns
|
||||
virtual util::Status ValidateSigner(const std::string& serial_number,
|
||||
const std::string& certificate,
|
||||
const std::string& signature);
|
||||
|
||||
// The below two functions are only used for testing.
|
||||
static void ResetSignerCache();
|
||||
static size_t SignerCacheSize();
|
||||
|
||||
std::string session_key_;
|
||||
std::string encrypted_session_key_;
|
||||
std::unique_ptr<RsaPublicKey> rsa_public_key_;
|
||||
|
||||
private:
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(CertificateClientCert);
|
||||
};
|
||||
|
||||
} // namespace widevine
|
||||
#endif // LICENSE_SERVER_SDK_INTERNAL_CLIENT_CERT_H__
|
||||
607
license_server_sdk/internal/client_cert_test.cc
Normal file
607
license_server_sdk/internal/client_cert_test.cc
Normal file
@@ -0,0 +1,607 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 "license_server_sdk/internal/client_cert.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "common/drm_root_certificate.h"
|
||||
#include "common/error_space.h"
|
||||
#include "common/rsa_test_keys.h"
|
||||
#include "common/wvm_test_keys.h"
|
||||
#include "protos/public/drm_certificate.pb.h"
|
||||
#include "protos/public/errors.pb.h"
|
||||
#include "protos/public/signed_drm_certificate.pb.h"
|
||||
|
||||
// TODO(user): Change these tests to use on-the-fly generated intermediate
|
||||
// and device certificates based on RsaTestKeys.
|
||||
// TODO(user): Add testcase(s) VerifySignature, CreateSignature,
|
||||
// and GenerateSigningKey.
|
||||
|
||||
namespace widevine {
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::Return;
|
||||
|
||||
class ClientCertTest : public ::testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
if (!setup_preprov_keys_) {
|
||||
KeyboxClientCert::SetPreProvisioningKeys(
|
||||
wvm_test_keys::GetPreprovKeyMultimap());
|
||||
setup_preprov_keys_ = true;
|
||||
}
|
||||
CHECK_OK(CertificateClientCert::SetDrmRootCertificatePublicKey(
|
||||
test_keys_.public_test_key_1_3072_bits()));
|
||||
}
|
||||
|
||||
protected:
|
||||
// Simple container struct for test value and expected keys.
|
||||
class TestTokenAndKeys {
|
||||
public:
|
||||
const std::string token_;
|
||||
uint32_t expected_system_id_;
|
||||
const std::string expected_serial_number_;
|
||||
const std::string expected_device_key_;
|
||||
TestTokenAndKeys(const std::string& token, uint32_t expected_system_id,
|
||||
const std::string& expected_serial_number,
|
||||
const std::string& expected_device_key)
|
||||
: token_(token),
|
||||
expected_system_id_(expected_system_id),
|
||||
expected_serial_number_(expected_serial_number),
|
||||
expected_device_key_(expected_device_key) {}
|
||||
};
|
||||
|
||||
class TestCertificateAndData {
|
||||
public:
|
||||
const std::string certificate_;
|
||||
const std::string expected_serial_number_;
|
||||
uint32_t expected_system_id_;
|
||||
util::Status expected_status_;
|
||||
TestCertificateAndData(const std::string& certificate,
|
||||
const std::string& expected_serial_number,
|
||||
uint32_t expected_system_id,
|
||||
util::Status expected_status)
|
||||
: certificate_(certificate),
|
||||
expected_serial_number_(expected_serial_number),
|
||||
expected_system_id_(expected_system_id),
|
||||
expected_status_(std::move(expected_status)) {}
|
||||
};
|
||||
|
||||
void TestBasicValidation(const TestTokenAndKeys& expectation,
|
||||
const bool expect_success,
|
||||
const bool compare_device_key);
|
||||
void TestBasicValidationDrmCertificate(
|
||||
const TestCertificateAndData& expectation, const bool compare_data);
|
||||
|
||||
void GenerateSignature(const std::string& message, const std::string& private_key,
|
||||
std::string* signature);
|
||||
SignedDrmCertificate* SignCertificate(const DrmCertificate& certificate,
|
||||
SignedDrmCertificate* signer,
|
||||
const std::string& private_key);
|
||||
DrmCertificate* GenerateProvisionerCertificate(uint32_t system_id,
|
||||
const std::string& serial_number,
|
||||
const std::string& provider_id);
|
||||
SignedDrmCertificate* GenerateSignedProvisionerCertificate(
|
||||
uint32_t system_id, const std::string& serial_number, const std::string& service_id);
|
||||
DrmCertificate* GenerateIntermediateCertificate(uint32_t system_id,
|
||||
const std::string& serial_number);
|
||||
SignedDrmCertificate* GenerateSignedIntermediateCertificate(
|
||||
SignedDrmCertificate* signer, uint32_t system_id,
|
||||
const std::string& serial_number);
|
||||
DrmCertificate* GenerateDrmCertificate(uint32_t system_id,
|
||||
const std::string& serial_number);
|
||||
SignedDrmCertificate* GenerateSignedDrmCertificate(
|
||||
SignedDrmCertificate* signer, uint32_t system_id,
|
||||
const std::string& serial_number);
|
||||
|
||||
RsaTestKeys test_keys_;
|
||||
static bool setup_preprov_keys_;
|
||||
};
|
||||
bool ClientCertTest::setup_preprov_keys_(false);
|
||||
|
||||
void ClientCertTest::TestBasicValidation(const TestTokenAndKeys& expectation,
|
||||
const bool expect_success,
|
||||
const bool compare_device_key) {
|
||||
// Test validation of a valid request.
|
||||
util::Status status;
|
||||
ClientCert* client_cert_ptr = nullptr;
|
||||
|
||||
// Two ways to create a client cert object, test both.
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (i == 0) {
|
||||
status = ClientCert::Create(ClientIdentification::KEYBOX,
|
||||
expectation.token_, &client_cert_ptr);
|
||||
} else {
|
||||
status =
|
||||
ClientCert::CreateWithToken(expectation.token_, &client_cert_ptr);
|
||||
}
|
||||
std::unique_ptr<ClientCert> keybox_cert(client_cert_ptr);
|
||||
if (expect_success) {
|
||||
ASSERT_EQ(util::OkStatus(), status);
|
||||
ASSERT_TRUE(keybox_cert.get());
|
||||
EXPECT_EQ(expectation.expected_system_id_, keybox_cert->system_id());
|
||||
EXPECT_EQ(expectation.expected_serial_number_,
|
||||
keybox_cert->serial_number());
|
||||
if (compare_device_key) {
|
||||
EXPECT_EQ(expectation.expected_device_key_, keybox_cert->key());
|
||||
}
|
||||
} else {
|
||||
EXPECT_NE(util::OkStatus(), status);
|
||||
EXPECT_FALSE(keybox_cert);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClientCertTest::TestBasicValidationDrmCertificate(
|
||||
const TestCertificateAndData& expectation, const bool compare_data) {
|
||||
// Test validation of a valid request.
|
||||
util::Status status;
|
||||
ClientCert* client_cert_ptr = nullptr;
|
||||
status = ClientCert::Create(ClientIdentification::DRM_DEVICE_CERTIFICATE,
|
||||
expectation.certificate_, &client_cert_ptr);
|
||||
std::unique_ptr<ClientCert> drm_certificate_cert(client_cert_ptr);
|
||||
ASSERT_EQ(expectation.expected_status_, status);
|
||||
if (expectation.expected_status_.ok()) {
|
||||
ASSERT_TRUE(drm_certificate_cert.get());
|
||||
if (compare_data) {
|
||||
ASSERT_EQ(expectation.expected_serial_number_,
|
||||
drm_certificate_cert->signer_serial_number());
|
||||
ASSERT_EQ(expectation.expected_system_id_,
|
||||
drm_certificate_cert->system_id());
|
||||
}
|
||||
} else {
|
||||
ASSERT_FALSE(drm_certificate_cert.get());
|
||||
}
|
||||
}
|
||||
|
||||
void ClientCertTest::GenerateSignature(const std::string& message,
|
||||
const std::string& private_key,
|
||||
std::string* signature) {
|
||||
std::unique_ptr<RsaPrivateKey> rsa_private_key(
|
||||
RsaPrivateKey::Create(private_key));
|
||||
ASSERT_TRUE(rsa_private_key != nullptr);
|
||||
rsa_private_key->GenerateSignature(message, signature);
|
||||
}
|
||||
|
||||
// The caller relinquishes ownership of |signer|, which may also be nullptr.
|
||||
SignedDrmCertificate* ClientCertTest::SignCertificate(
|
||||
const DrmCertificate& certificate, SignedDrmCertificate* signer,
|
||||
const std::string& private_key) {
|
||||
std::unique_ptr<SignedDrmCertificate> signed_certificate(
|
||||
new SignedDrmCertificate);
|
||||
signed_certificate->set_drm_certificate(certificate.SerializeAsString());
|
||||
GenerateSignature(signed_certificate->drm_certificate(), private_key,
|
||||
signed_certificate->mutable_signature());
|
||||
if (signer != nullptr) {
|
||||
signed_certificate->set_allocated_signer(signer);
|
||||
}
|
||||
return signed_certificate.release();
|
||||
}
|
||||
|
||||
DrmCertificate* ClientCertTest::GenerateIntermediateCertificate(
|
||||
uint32_t system_id, const std::string& serial_number) {
|
||||
std::unique_ptr<DrmCertificate> intermediate_certificate(new DrmCertificate);
|
||||
intermediate_certificate->set_type(DrmCertificate::DEVICE_MODEL);
|
||||
intermediate_certificate->set_serial_number(serial_number);
|
||||
intermediate_certificate->set_public_key(
|
||||
test_keys_.public_test_key_2_2048_bits());
|
||||
intermediate_certificate->set_system_id(system_id);
|
||||
intermediate_certificate->set_creation_time_seconds(1234);
|
||||
return intermediate_certificate.release();
|
||||
}
|
||||
|
||||
SignedDrmCertificate* ClientCertTest::GenerateSignedIntermediateCertificate(
|
||||
SignedDrmCertificate* signer, uint32_t system_id,
|
||||
const std::string& serial_number) {
|
||||
std::unique_ptr<DrmCertificate> intermediate_certificate(
|
||||
GenerateIntermediateCertificate(system_id, serial_number));
|
||||
return SignCertificate(*intermediate_certificate, signer,
|
||||
test_keys_.private_test_key_1_3072_bits());
|
||||
}
|
||||
|
||||
DrmCertificate* ClientCertTest::GenerateDrmCertificate(
|
||||
uint32_t system_id, const std::string& serial_number) {
|
||||
std::unique_ptr<DrmCertificate> drm_certificate(new DrmCertificate);
|
||||
drm_certificate->set_type(DrmCertificate::DEVICE);
|
||||
drm_certificate->set_serial_number(serial_number);
|
||||
drm_certificate->set_system_id(system_id);
|
||||
drm_certificate->set_public_key(test_keys_.public_test_key_3_2048_bits());
|
||||
drm_certificate->set_creation_time_seconds(4321);
|
||||
return drm_certificate.release();
|
||||
}
|
||||
|
||||
SignedDrmCertificate* ClientCertTest::GenerateSignedDrmCertificate(
|
||||
SignedDrmCertificate* signer, uint32_t system_id,
|
||||
const std::string& serial_number) {
|
||||
std::unique_ptr<DrmCertificate> drm_certificate(
|
||||
GenerateDrmCertificate(system_id, serial_number));
|
||||
std::unique_ptr<SignedDrmCertificate> signed_drm_certificate(SignCertificate(
|
||||
*drm_certificate, signer, test_keys_.private_test_key_2_2048_bits()));
|
||||
return signed_drm_certificate.release();
|
||||
}
|
||||
|
||||
DrmCertificate* ClientCertTest::GenerateProvisionerCertificate(
|
||||
uint32_t system_id, const std::string& serial_number, const std::string& provider_id) {
|
||||
std::unique_ptr<DrmCertificate> provisioner_certificate(new DrmCertificate);
|
||||
provisioner_certificate->set_type(DrmCertificate::PROVISIONER);
|
||||
provisioner_certificate->set_serial_number(serial_number);
|
||||
// TODO(user): Need to generate 3072 bit test for provisioner certificates.
|
||||
provisioner_certificate->set_public_key(
|
||||
test_keys_.public_test_key_2_2048_bits());
|
||||
provisioner_certificate->set_system_id(system_id);
|
||||
provisioner_certificate->set_provider_id(provider_id);
|
||||
provisioner_certificate->set_creation_time_seconds(1234);
|
||||
return provisioner_certificate.release();
|
||||
}
|
||||
|
||||
SignedDrmCertificate* ClientCertTest::GenerateSignedProvisionerCertificate(
|
||||
uint32_t system_id, const std::string& serial_number, const std::string& service_id) {
|
||||
std::unique_ptr<DrmCertificate> provisioner_certificate(
|
||||
GenerateProvisionerCertificate(system_id, serial_number, service_id));
|
||||
return SignCertificate(*provisioner_certificate, nullptr,
|
||||
test_keys_.private_test_key_1_3072_bits());
|
||||
}
|
||||
|
||||
TEST_F(ClientCertTest, BasicValidation) {
|
||||
const TestTokenAndKeys kValidTokenAndExpectedKeys[] = {
|
||||
TestTokenAndKeys(
|
||||
absl::HexStringToBytes(
|
||||
"00000002000001128e1ebfe037828096ca6538b4f6f4bcb51c2b7191cf037e98"
|
||||
"beaa24924907e128f9ff49b54a165cd9c33e6547537eb4d29fb7e8df3c2c1cd9"
|
||||
"2517a12f4922953e"),
|
||||
274, absl::HexStringToBytes("8e1ebfe037828096ca6538b4f6f4bcb5"),
|
||||
absl::HexStringToBytes("4071197f1f8910d9bf10c6bc4c987638")),
|
||||
TestTokenAndKeys(
|
||||
absl::HexStringToBytes(
|
||||
"0000000200000112d906feebe1750c5886ff77c2dfa31bb40e002f3adbc0fa5b"
|
||||
"eb2486cf5f419549cdaa23230e5165ac2ffab56d53b692b7ba0c1857400c6add"
|
||||
"3af3ff3d5cb24985"),
|
||||
274, absl::HexStringToBytes("d906feebe1750c5886ff77c2dfa31bb4"),
|
||||
absl::HexStringToBytes("42cfb1765201042302a404d1e0fac8ed"))};
|
||||
|
||||
for (size_t i = 0; i < ABSL_ARRAYSIZE(kValidTokenAndExpectedKeys); ++i) {
|
||||
SCOPED_TRACE("Test data: " + absl::StrCat(i));
|
||||
TestBasicValidation(kValidTokenAndExpectedKeys[i], true, true);
|
||||
}
|
||||
|
||||
EXPECT_EQ(
|
||||
wvm_test_keys::kTestSystemId,
|
||||
KeyboxClientCert::GetSystemId(kValidTokenAndExpectedKeys[0].token_));
|
||||
}
|
||||
|
||||
TEST_F(ClientCertTest, BasicCertValidation) {
|
||||
const uint32_t system_id = 1234;
|
||||
const std::string serial_number("serial_number");
|
||||
std::unique_ptr<SignedDrmCertificate> signed_cert(
|
||||
GenerateSignedDrmCertificate(GenerateSignedIntermediateCertificate(
|
||||
nullptr, system_id, serial_number),
|
||||
system_id, serial_number + "-device"));
|
||||
const TestCertificateAndData kValidCertificateAndExpectedData(
|
||||
signed_cert->SerializeAsString(), serial_number, system_id,
|
||||
util::OkStatus());
|
||||
const bool compare_data = true;
|
||||
TestBasicValidationDrmCertificate(kValidCertificateAndExpectedData,
|
||||
compare_data);
|
||||
}
|
||||
|
||||
TEST_F(ClientCertTest, InvalidKeybox) {
|
||||
const TestTokenAndKeys kInvalidTokenAndExpectedKeys[] = {
|
||||
// This tests a malformed, but appropriately sized keybox.
|
||||
TestTokenAndKeys(
|
||||
absl::HexStringToBytes(
|
||||
"00000002000001129e1ebfe037828096ca6538b4f6f4bcb51c2b7191cf037e98"
|
||||
"beaa24924907e128f9ff49b54a165cd9c33e6547537eb4d29fb7e8df3c2c1cd9"
|
||||
"2517a12f4922953e"),
|
||||
0, absl::HexStringToBytes(""), absl::HexStringToBytes("")),
|
||||
// This has a length and system_id, but nothing else.
|
||||
TestTokenAndKeys(absl::HexStringToBytes("0000000200000112"), 0,
|
||||
absl::HexStringToBytes(""), absl::HexStringToBytes("")),
|
||||
// This has only a byte.
|
||||
TestTokenAndKeys(absl::HexStringToBytes(""), 0,
|
||||
absl::HexStringToBytes(""), absl::HexStringToBytes("")),
|
||||
// This has an emptry std::string for the keybox.
|
||||
TestTokenAndKeys(absl::HexStringToBytes(""), 0,
|
||||
absl::HexStringToBytes(""), absl::HexStringToBytes(""))};
|
||||
|
||||
for (size_t i = 0; i < ABSL_ARRAYSIZE(kInvalidTokenAndExpectedKeys); ++i) {
|
||||
SCOPED_TRACE("Test data: " + absl::StrCat(i));
|
||||
TestBasicValidation(kInvalidTokenAndExpectedKeys[i], false, false);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ClientCertTest, InvalidCertificate) {
|
||||
const uint32_t system_id(1234);
|
||||
const std::string device_sn("device-serial-number");
|
||||
const std::string signer_sn("signer-serial-number");
|
||||
std::unique_ptr<DrmCertificate> dev_cert;
|
||||
std::unique_ptr<DrmCertificate> signer_cert;
|
||||
std::unique_ptr<SignedDrmCertificate> signed_signer;
|
||||
|
||||
// Invalid serialized device certificate.
|
||||
std::unique_ptr<SignedDrmCertificate> invalid_drm_cert(
|
||||
new SignedDrmCertificate);
|
||||
invalid_drm_cert->set_drm_certificate("bad-serialized-cert");
|
||||
GenerateSignature(invalid_drm_cert->drm_certificate(),
|
||||
test_keys_.private_test_key_2_2048_bits(),
|
||||
invalid_drm_cert->mutable_signature());
|
||||
invalid_drm_cert->set_allocated_signer(
|
||||
GenerateSignedIntermediateCertificate(nullptr, system_id, signer_sn));
|
||||
// Missing system ID.
|
||||
dev_cert.reset(GenerateDrmCertificate(system_id, device_sn));
|
||||
dev_cert->clear_system_id();
|
||||
std::unique_ptr<SignedDrmCertificate> no_system_id(SignCertificate(
|
||||
*dev_cert.get(),
|
||||
GenerateSignedIntermediateCertificate(nullptr, system_id, signer_sn),
|
||||
test_keys_.private_test_key_2_2048_bits()));
|
||||
// Invalid device public key.
|
||||
dev_cert.reset(GenerateDrmCertificate(system_id, device_sn));
|
||||
dev_cert->set_public_key("bad-device-public-key");
|
||||
std::unique_ptr<SignedDrmCertificate> bad_device_public_key(SignCertificate(
|
||||
*dev_cert,
|
||||
GenerateSignedIntermediateCertificate(nullptr, system_id, signer_sn),
|
||||
test_keys_.private_test_key_2_2048_bits()));
|
||||
// Invalid serialized intermediate certificate.
|
||||
signed_signer.reset(
|
||||
GenerateSignedIntermediateCertificate(nullptr, system_id, signer_sn));
|
||||
signed_signer->set_drm_certificate("bad-serialized-cert");
|
||||
GenerateSignature(signed_signer->drm_certificate(),
|
||||
test_keys_.private_test_key_1_3072_bits(),
|
||||
signed_signer->mutable_signature());
|
||||
dev_cert.reset(GenerateDrmCertificate(system_id, device_sn));
|
||||
std::unique_ptr<SignedDrmCertificate> invalid_signer(
|
||||
SignCertificate(*dev_cert, signed_signer.release(),
|
||||
test_keys_.private_test_key_2_2048_bits()));
|
||||
// Invalid signer public key.
|
||||
dev_cert.reset(GenerateDrmCertificate(system_id, device_sn));
|
||||
signer_cert.reset(GenerateIntermediateCertificate(system_id, signer_sn));
|
||||
signer_cert->set_public_key("bad-signer-public-key");
|
||||
std::unique_ptr<SignedDrmCertificate> bad_signer_public_key(SignCertificate(
|
||||
*dev_cert,
|
||||
SignCertificate(*signer_cert, nullptr,
|
||||
test_keys_.private_test_key_1_3072_bits()),
|
||||
test_keys_.private_test_key_2_2048_bits()));
|
||||
// Invalid device certificate signature.
|
||||
std::unique_ptr<SignedDrmCertificate> bad_device_signature(
|
||||
GenerateSignedDrmCertificate(
|
||||
GenerateSignedIntermediateCertificate(nullptr, system_id, signer_sn),
|
||||
system_id, device_sn));
|
||||
bad_device_signature->set_signature("bad-signature");
|
||||
// Missing signer serial number.
|
||||
dev_cert.reset(GenerateDrmCertificate(system_id, device_sn));
|
||||
signer_cert.reset(GenerateIntermediateCertificate(system_id, signer_sn));
|
||||
signer_cert->clear_serial_number();
|
||||
std::unique_ptr<SignedDrmCertificate> missing_signer_sn(SignCertificate(
|
||||
*dev_cert,
|
||||
SignCertificate(*signer_cert, nullptr,
|
||||
test_keys_.private_test_key_1_3072_bits()),
|
||||
test_keys_.private_test_key_2_2048_bits()));
|
||||
// Invalid serialized intermediate certificate.
|
||||
dev_cert.reset(GenerateDrmCertificate(system_id, device_sn));
|
||||
signed_signer.reset(
|
||||
GenerateSignedIntermediateCertificate(nullptr, system_id, signer_sn));
|
||||
signed_signer->set_signature("bad-signature");
|
||||
std::unique_ptr<SignedDrmCertificate> bad_signer_signature(
|
||||
SignCertificate(*dev_cert, signed_signer.release(),
|
||||
test_keys_.private_test_key_2_2048_bits()));
|
||||
|
||||
const TestCertificateAndData kInvalidCertificate[] = {
|
||||
TestCertificateAndData("f", "", 0,
|
||||
util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"device-certificate-invalid-token")),
|
||||
TestCertificateAndData(invalid_drm_cert->SerializeAsString(), "", 0,
|
||||
util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"device-certificate-invalid")),
|
||||
TestCertificateAndData(
|
||||
no_system_id->SerializeAsString(), "", 0,
|
||||
util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"device-certificate-missing-system-id")),
|
||||
TestCertificateAndData(
|
||||
bad_device_public_key->SerializeAsString(), "", 0,
|
||||
util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"device-certificate-public-key-failed")),
|
||||
TestCertificateAndData(invalid_signer->SerializeAsString(), "", 0,
|
||||
util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"device-certificate-invalid-signer")),
|
||||
TestCertificateAndData(
|
||||
bad_signer_public_key->SerializeAsString(), "", 0,
|
||||
util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"signer-certificate-public-key-failed")),
|
||||
TestCertificateAndData(
|
||||
bad_device_signature->SerializeAsString(), "", 0,
|
||||
util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"device-certificate-verification-failed")),
|
||||
TestCertificateAndData(missing_signer_sn->SerializeAsString(), "", 0,
|
||||
util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"missing-signer-serial-number")),
|
||||
TestCertificateAndData(
|
||||
bad_signer_signature->SerializeAsString(), "", 0,
|
||||
util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"signer-certificate-verification-failed")),
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < ABSL_ARRAYSIZE(kInvalidCertificate); ++i) {
|
||||
TestBasicValidationDrmCertificate(kInvalidCertificate[i], false);
|
||||
}
|
||||
}
|
||||
|
||||
class MockCertificateClientCert : public CertificateClientCert {
|
||||
public:
|
||||
using CertificateClientCert::ResetSignerCache;
|
||||
using CertificateClientCert::SignerCacheSize;
|
||||
|
||||
explicit MockCertificateClientCert(const std::string& cert_bytes)
|
||||
: CertificateClientCert(cert_bytes) {}
|
||||
MOCK_METHOD3(ValidateSigner, util::Status(const std::string& serial_number,
|
||||
const std::string& certificate,
|
||||
const std::string& signature));
|
||||
util::Status CallValidateCertificate(
|
||||
const SignedDrmCertificate& signed_drm_certificate) {
|
||||
return ValidateCertificate(signed_drm_certificate);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ClientCertTest, SignerCache) {
|
||||
const uint32_t system_id = 1234;
|
||||
const std::string serial_number("serial-number");
|
||||
std::unique_ptr<SignedDrmCertificate> signed_cert(
|
||||
GenerateSignedDrmCertificate(GenerateSignedIntermediateCertificate(
|
||||
nullptr, system_id, serial_number),
|
||||
system_id, serial_number + "-device"));
|
||||
// TODO(user): Remove work from the ClientCert constructors to make it
|
||||
// more testable, and because it's just bad practice.
|
||||
MockCertificateClientCert::ResetSignerCache();
|
||||
MockCertificateClientCert client_cert(signed_cert->SerializeAsString());
|
||||
EXPECT_EQ(1, MockCertificateClientCert::SignerCacheSize());
|
||||
EXPECT_CALL(client_cert, ValidateSigner(_, _, _))
|
||||
.Times(0)
|
||||
.WillRepeatedly(Return(util::OkStatus()));
|
||||
EXPECT_EQ(util::OkStatus(),
|
||||
client_cert.CallValidateCertificate(*signed_cert));
|
||||
}
|
||||
|
||||
TEST_F(ClientCertTest, MissingPreProvKey) {
|
||||
// system ID in token is 0x01234567
|
||||
const std::string token(absl::HexStringToBytes(
|
||||
"00000002012345678e1ebfe037828096ca6538b4f6f4bcb51c2b7191cf037e98"
|
||||
"beaa24924907e128f9ff49b54a165cd9c33e6547537eb4d29fb7e8df3c2c1cd9"
|
||||
"2517a12f4922953e"));
|
||||
ClientCert* client_cert_ptr = nullptr;
|
||||
util::Status status = ClientCert::CreateWithToken(token, &client_cert_ptr);
|
||||
ASSERT_EQ(MISSING_PRE_PROV_KEY, status.error_code());
|
||||
}
|
||||
|
||||
TEST_F(ClientCertTest, ValidProvisionerDeviceCert) {
|
||||
const uint32_t system_id = 4890;
|
||||
const std::string service_id("widevine_test.com");
|
||||
const std::string device_serial_number("device-serial-number");
|
||||
const std::string intermediate_serial_number("intermediate-serial-number");
|
||||
const std::string provisioner_serial_number("provisioner-serial-number");
|
||||
|
||||
std::unique_ptr<SignedDrmCertificate> signed_provisioner_cert(
|
||||
GenerateSignedProvisionerCertificate(system_id, provisioner_serial_number,
|
||||
service_id));
|
||||
|
||||
std::unique_ptr<SignedDrmCertificate> signed_intermediate_cert(
|
||||
GenerateSignedIntermediateCertificate(signed_provisioner_cert.release(),
|
||||
system_id,
|
||||
intermediate_serial_number));
|
||||
|
||||
std::unique_ptr<SignedDrmCertificate> signed_device_cert(
|
||||
GenerateSignedDrmCertificate(signed_intermediate_cert.release(),
|
||||
system_id, device_serial_number));
|
||||
|
||||
std::string serialized_cert;
|
||||
signed_device_cert->SerializeToString(&serialized_cert);
|
||||
ClientCert* client_cert_ptr = nullptr;
|
||||
|
||||
EXPECT_OK(ClientCert::Create(ClientIdentification::DRM_DEVICE_CERTIFICATE,
|
||||
serialized_cert, &client_cert_ptr));
|
||||
ASSERT_TRUE(client_cert_ptr != nullptr);
|
||||
std::unique_ptr<ClientCert> drm_cert(client_cert_ptr);
|
||||
|
||||
EXPECT_EQ(service_id, drm_cert->service_id());
|
||||
EXPECT_EQ(device_serial_number, drm_cert->serial_number());
|
||||
EXPECT_EQ(intermediate_serial_number, drm_cert->signer_serial_number());
|
||||
EXPECT_EQ(system_id, drm_cert->system_id());
|
||||
}
|
||||
|
||||
TEST_F(ClientCertTest, InValidProvisionerDeviceCertEmptyServiceId) {
|
||||
const uint32_t system_id = 4890;
|
||||
const std::string service_id("");
|
||||
const std::string device_serial_number("device-serial-number");
|
||||
const std::string intermediate_serial_number("intermediate-serial-number");
|
||||
const std::string provisioner_serial_number("provisioner-serial-number");
|
||||
|
||||
std::unique_ptr<SignedDrmCertificate> signed_provisioner_cert(
|
||||
GenerateSignedProvisionerCertificate(system_id, provisioner_serial_number,
|
||||
service_id));
|
||||
|
||||
std::unique_ptr<SignedDrmCertificate> signed_intermediate_cert(
|
||||
GenerateSignedIntermediateCertificate(signed_provisioner_cert.release(),
|
||||
system_id,
|
||||
intermediate_serial_number));
|
||||
|
||||
std::unique_ptr<SignedDrmCertificate> signed_device_cert(
|
||||
GenerateSignedDrmCertificate(signed_intermediate_cert.release(),
|
||||
system_id, device_serial_number));
|
||||
|
||||
std::string serialized_cert;
|
||||
signed_device_cert->SerializeToString(&serialized_cert);
|
||||
ClientCert* client_cert_ptr = nullptr;
|
||||
|
||||
EXPECT_EQ("missing-provisioning-service-id",
|
||||
ClientCert::Create(ClientIdentification::DRM_DEVICE_CERTIFICATE,
|
||||
serialized_cert, &client_cert_ptr)
|
||||
.error_message());
|
||||
EXPECT_FALSE(client_cert_ptr);
|
||||
}
|
||||
|
||||
TEST_F(ClientCertTest, InValidProvisionerDeviceCertChain) {
|
||||
const uint32_t system_id = 4890;
|
||||
const uint32_t system_id2 = 4892;
|
||||
const std::string service_id("widevine_test.com");
|
||||
const std::string device_serial_number("device-serial-number");
|
||||
const std::string intermediate_serial_number("intermediate-serial-number");
|
||||
const std::string intermediate_serial_number2("intermediate-serial-number-2");
|
||||
|
||||
std::unique_ptr<SignedDrmCertificate> signed_intermediate_cert2(
|
||||
GenerateSignedIntermediateCertificate(nullptr, system_id2,
|
||||
intermediate_serial_number2));
|
||||
|
||||
// Instead of using a provisioner certificate to sign this intermediate
|
||||
// certificate, use another intermediate certificate. This is an invalid
|
||||
// chain and should generate an error when trying to create a client
|
||||
// certificate.
|
||||
std::unique_ptr<SignedDrmCertificate> signed_intermediate_cert(
|
||||
GenerateSignedIntermediateCertificate(signed_intermediate_cert2.release(),
|
||||
system_id,
|
||||
intermediate_serial_number));
|
||||
std::unique_ptr<SignedDrmCertificate> signed_device_cert(
|
||||
GenerateSignedDrmCertificate(signed_intermediate_cert.release(),
|
||||
system_id, device_serial_number));
|
||||
std::string serialized_cert;
|
||||
signed_device_cert->SerializeToString(&serialized_cert);
|
||||
ClientCert* client_cert_ptr = nullptr;
|
||||
|
||||
ASSERT_EQ("expected-provisioning-provider-certificate-type",
|
||||
ClientCert::Create(ClientIdentification::DRM_DEVICE_CERTIFICATE,
|
||||
serialized_cert, &client_cert_ptr)
|
||||
.error_message());
|
||||
EXPECT_FALSE(client_cert_ptr);
|
||||
|
||||
// Make a normal intermediate certificate.
|
||||
signed_intermediate_cert.reset(GenerateSignedIntermediateCertificate(
|
||||
nullptr, system_id, intermediate_serial_number));
|
||||
signed_device_cert.reset(GenerateSignedDrmCertificate(
|
||||
signed_intermediate_cert.release(), system_id, device_serial_number));
|
||||
|
||||
signed_device_cert->SerializeToString(&serialized_cert);
|
||||
EXPECT_OK(ClientCert::Create(ClientIdentification::DRM_DEVICE_CERTIFICATE,
|
||||
serialized_cert, &client_cert_ptr));
|
||||
|
||||
// The service Id should only get set if a provisioning cert exist.
|
||||
std::unique_ptr<ClientCert> drm_cert(client_cert_ptr);
|
||||
EXPECT_TRUE(drm_cert->service_id().empty());
|
||||
EXPECT_EQ(device_serial_number, drm_cert->serial_number());
|
||||
EXPECT_EQ(intermediate_serial_number, drm_cert->signer_serial_number());
|
||||
EXPECT_EQ(system_id, drm_cert->system_id());
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
241
license_server_sdk/internal/device_status_list.cc
Normal file
241
license_server_sdk/internal/device_status_list.cc
Normal file
@@ -0,0 +1,241 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Implements the DeviceStatusList class.
|
||||
|
||||
#include "license_server_sdk/internal/device_status_list.h"
|
||||
|
||||
#include <time.h>
|
||||
#include <memory>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "util/gtl/map_util.h"
|
||||
#include "common/error_space.h"
|
||||
#include "common/rsa_key.h"
|
||||
#include "license_server_sdk/internal/client_cert.h"
|
||||
#include "protos/public/client_identification.pb.h"
|
||||
#include "protos/public/errors.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
DeviceStatusList* DeviceStatusList::Instance() {
|
||||
// TODO(user): This is "ok" according to Google's Coding for Dummies, but
|
||||
// we should inject the status list into the sessions. This will require
|
||||
// exposing additional objects in the public interface.
|
||||
static DeviceStatusList* device_status_list(nullptr);
|
||||
if (!device_status_list) device_status_list = new DeviceStatusList;
|
||||
return device_status_list;
|
||||
}
|
||||
|
||||
DeviceStatusList::DeviceStatusList()
|
||||
: creation_time_seconds_(0),
|
||||
expiration_period_seconds_(0),
|
||||
allow_unknown_devices_(true),
|
||||
allow_test_only_devices_(false) {}
|
||||
|
||||
DeviceStatusList::~DeviceStatusList() {}
|
||||
|
||||
util::Status DeviceStatusList::UpdateStatusList(
|
||||
const std::string& root_certificate_public_key,
|
||||
const std::string& serialized_certificate_status_list,
|
||||
uint32_t expiration_period_seconds) {
|
||||
SignedDeviceCertificateStatusList signed_certificate_status_list;
|
||||
if (!signed_certificate_status_list.ParseFromString(
|
||||
serialized_certificate_status_list)) {
|
||||
return util::Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
|
||||
"signed-certificate-status-list-parse-error");
|
||||
}
|
||||
if (!signed_certificate_status_list.has_certificate_status_list()) {
|
||||
return util::Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
|
||||
"missing-status-list");
|
||||
}
|
||||
if (!signed_certificate_status_list.has_signature()) {
|
||||
return util::Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
|
||||
"missing-status-list-signature");
|
||||
}
|
||||
std::unique_ptr<RsaPublicKey> root_key(
|
||||
RsaPublicKey::Create(root_certificate_public_key));
|
||||
if (root_key == nullptr) {
|
||||
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"invalid-root-public-key");
|
||||
}
|
||||
if (!root_key->VerifySignature(
|
||||
signed_certificate_status_list.certificate_status_list(),
|
||||
signed_certificate_status_list.signature())) {
|
||||
return util::Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
|
||||
"invalid-status-list-signature");
|
||||
}
|
||||
DeviceCertificateStatusList certificate_status_list;
|
||||
if (!certificate_status_list.ParseFromString(
|
||||
signed_certificate_status_list.certificate_status_list())) {
|
||||
return util::Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
|
||||
"certificate-status-list-parse-error");
|
||||
}
|
||||
if (expiration_period_seconds &&
|
||||
(GetCurrentTime() > (certificate_status_list.creation_time_seconds() +
|
||||
expiration_period_seconds))) {
|
||||
return util::Status(error_space, EXPIRED_CERTIFICATE_STATUS_LIST,
|
||||
"certificate-status-list-expired");
|
||||
}
|
||||
|
||||
absl::WriterMutexLock lock(&status_map_lock_);
|
||||
device_status_map_.clear();
|
||||
for (int i = 0, n = certificate_status_list.certificate_status_size(); i < n;
|
||||
i++) {
|
||||
const DeviceCertificateStatus& cert_status =
|
||||
certificate_status_list.certificate_status(i);
|
||||
if (cert_status.has_device_info()) {
|
||||
const ProvisionedDeviceInfo& device_info = cert_status.device_info();
|
||||
if (device_info.has_system_id()) {
|
||||
device_status_map_[device_info.system_id()] = cert_status;
|
||||
} else {
|
||||
return util::Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
|
||||
"device-info-missing-system-id");
|
||||
}
|
||||
}
|
||||
}
|
||||
creation_time_seconds_ = certificate_status_list.creation_time_seconds();
|
||||
expiration_period_seconds_ = expiration_period_seconds;
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status DeviceStatusList::GetCertStatus(
|
||||
const ClientCert& client_cert, ProvisionedDeviceInfo* device_info) {
|
||||
CHECK(device_info);
|
||||
|
||||
// Keybox checks.
|
||||
if (client_cert.type() == ClientIdentification::KEYBOX) {
|
||||
if (!KeyboxClientCert::IsSystemIdKnown(client_cert.system_id())) {
|
||||
return util::Status(error_space, UNSUPPORTED_SYSTEM_ID,
|
||||
"keybox-unsupported-system-id");
|
||||
}
|
||||
// Get device information from certificate status list if available.
|
||||
if (!GetDeviceInfo(client_cert, device_info)) {
|
||||
device_info->Clear();
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
// DRM certificate checks.
|
||||
if (client_cert.type() != ClientIdentification::DRM_DEVICE_CERTIFICATE) {
|
||||
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"device-certificate-unsupported-token-type");
|
||||
}
|
||||
absl::ReaderMutexLock lock(&status_map_lock_);
|
||||
if (expiration_period_seconds_ &&
|
||||
(GetCurrentTime() >
|
||||
(creation_time_seconds_ + expiration_period_seconds_))) {
|
||||
return util::Status(error_space, EXPIRED_CERTIFICATE_STATUS_LIST,
|
||||
"certificate-status-list-expired");
|
||||
}
|
||||
DeviceCertificateStatus* device_cert_status =
|
||||
gtl::FindOrNull(device_status_map_, client_cert.system_id());
|
||||
if (device_cert_status) {
|
||||
*device_info = device_cert_status->device_info();
|
||||
if (device_cert_status->status() ==
|
||||
DeviceCertificateStatus::STATUS_REVOKED) {
|
||||
if (IsRevokedSystemIdAllowed(client_cert.system_id())) {
|
||||
LOG(WARNING) << "Allowing REVOKED device: "
|
||||
<< device_info->ShortDebugString();
|
||||
} else {
|
||||
return util::Status(error_space, DRM_DEVICE_CERTIFICATE_REVOKED,
|
||||
"device-certificate-revoked");
|
||||
}
|
||||
}
|
||||
if ((device_cert_status->status() ==
|
||||
DeviceCertificateStatus::STATUS_TEST_ONLY) &&
|
||||
!allow_test_only_devices_) {
|
||||
return util::Status(error_space, DEVELOPMENT_CERTIFICATE_NOT_ALLOWED,
|
||||
"test-only-drm-certificate-not-allowed");
|
||||
}
|
||||
if (!client_cert.signed_by_provisioner() &&
|
||||
(client_cert.signer_serial_number() !=
|
||||
device_cert_status->drm_serial_number())) {
|
||||
// Widevine-provisioned device, and the intermediate certificate serial
|
||||
// number does not match that in the status list. If the status list is
|
||||
// newer than the certificate, indicate an invalid certificate, so that
|
||||
// the device re-provisions. If, on the other hand, the certificate status
|
||||
// list is older than the certificate, the certificate is for all purposes
|
||||
// unknown.
|
||||
if (client_cert.signer_creation_time_seconds() < creation_time_seconds_) {
|
||||
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
|
||||
"intermediate-certificate-serial-number-mismatch");
|
||||
}
|
||||
return util::Status(error_space, DRM_DEVICE_CERTIFICATE_UNKNOWN,
|
||||
"device-certificate-status-unknown");
|
||||
}
|
||||
} else {
|
||||
if (!allow_unknown_devices_) {
|
||||
return util::Status(error_space, DRM_DEVICE_CERTIFICATE_UNKNOWN,
|
||||
"device-certificate-status-unknown");
|
||||
}
|
||||
device_info->Clear();
|
||||
}
|
||||
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
bool DeviceStatusList::GetDeviceInfo(const ClientCert& client_cert,
|
||||
ProvisionedDeviceInfo* device_info) {
|
||||
CHECK(device_info);
|
||||
absl::ReaderMutexLock lock(&status_map_lock_);
|
||||
DeviceCertificateStatus* device_cert_status =
|
||||
gtl::FindOrNull(device_status_map_, client_cert.system_id());
|
||||
if (device_cert_status) {
|
||||
*device_info = device_cert_status->device_info();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DeviceStatusList::IsSystemIdActive(uint32_t system_id) {
|
||||
absl::ReaderMutexLock lock(&status_map_lock_);
|
||||
DeviceCertificateStatus* device_cert_status =
|
||||
gtl::FindOrNull(device_status_map_, system_id);
|
||||
if (!device_cert_status) {
|
||||
return allow_unknown_devices_ ||
|
||||
KeyboxClientCert::IsSystemIdKnown(system_id);
|
||||
}
|
||||
if (device_cert_status->status() ==
|
||||
DeviceCertificateStatus::STATUS_TEST_ONLY) {
|
||||
return allow_test_only_devices_;
|
||||
}
|
||||
if (device_cert_status) {
|
||||
ProvisionedDeviceInfo device_info = device_cert_status->device_info();
|
||||
if (device_cert_status->status() ==
|
||||
DeviceCertificateStatus::STATUS_REVOKED) {
|
||||
if (IsRevokedSystemIdAllowed(system_id)) {
|
||||
LOG(WARNING) << "REVOKED system_id: " << system_id
|
||||
<< " is allowed to be active";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return device_cert_status->status() !=
|
||||
DeviceCertificateStatus::STATUS_REVOKED;
|
||||
}
|
||||
|
||||
uint32_t DeviceStatusList::GetCurrentTime() const { return time(nullptr); }
|
||||
|
||||
void DeviceStatusList::AllowRevokedDevices(const std::string& system_id_list) {
|
||||
for (absl::string_view sp : absl::StrSplit(system_id_list, ',')) {
|
||||
allowed_revoked_devices_.push_back(std::stoi(std::string(sp)));
|
||||
}
|
||||
std::sort(allowed_revoked_devices_.begin(), allowed_revoked_devices_.end());
|
||||
}
|
||||
|
||||
bool DeviceStatusList::IsRevokedSystemIdAllowed(uint32_t system_id) {
|
||||
auto it = std::binary_search(allowed_revoked_devices_.begin(),
|
||||
allowed_revoked_devices_.end(), system_id);
|
||||
return it;
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
99
license_server_sdk/internal/device_status_list.h
Normal file
99
license_server_sdk/internal/device_status_list.h
Normal file
@@ -0,0 +1,99 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// DeviceStatusList class header.
|
||||
|
||||
#ifndef LICENSE_SERVER_SDK_INTERNAL_DEVICE_STATUS_LIST_H__
|
||||
#define LICENSE_SERVER_SDK_INTERNAL_DEVICE_STATUS_LIST_H__
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "util/status.h"
|
||||
#include "protos/public/device_certificate_status.pb.h"
|
||||
#include "protos/public/provisioned_device_info.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
class ClientCert;
|
||||
// Manages the certificate status of devices. The list of
|
||||
// DeviceCertificateStatus is provided by the DRM server. Each license
|
||||
// request is checked to ensure the certificate in the request is valid and
|
||||
// not revoked. Also checks to see if the intermediate certificates were
|
||||
// updated where the system Id is the same, but the serial number changes.
|
||||
// This case should cause the clients to re-provision.
|
||||
class DeviceStatusList {
|
||||
public:
|
||||
// Returns a pointer to a singleton DeviceStatusList.
|
||||
static DeviceStatusList* Instance();
|
||||
|
||||
DeviceStatusList();
|
||||
virtual ~DeviceStatusList();
|
||||
// Takes |signed_certificate_status_list| and copies to an internal map of
|
||||
// device certifcate status list. The internal map is used to verify
|
||||
// a device was not revoked. Returns true is the list was successfully parsed.
|
||||
util::Status UpdateStatusList(const std::string& root_certificate_public_key,
|
||||
const std::string& signed_certificate_status_list,
|
||||
uint32_t expiration_period_seconds);
|
||||
void set_allow_unknown_devices(bool flag) { allow_unknown_devices_ = flag; }
|
||||
bool allow_unknown_devices() const { return allow_unknown_devices_; }
|
||||
void set_allow_test_only_devices(bool allow) {
|
||||
allow_test_only_devices_ = allow;
|
||||
}
|
||||
bool allow_test_only_devices() const { return allow_test_only_devices_; }
|
||||
|
||||
// Checks the device status list and returns either:
|
||||
// OK
|
||||
// UNSUPPORTED_SYSTEM_ID
|
||||
// INVALID_DRM_CERTIFICATE
|
||||
// DRM_DEVICE_CERTIFICATE_REVOKED
|
||||
// DRM_DEVICE_CERTIFICATE_UNKNOWN
|
||||
// If status is OK, a copy of the provisioned device info is copied
|
||||
// into |device_info|. Caller owns |device_info| and it must not be null.
|
||||
util::Status GetCertStatus(
|
||||
const ClientCert& client_cert,
|
||||
widevine::ProvisionedDeviceInfo* device_info);
|
||||
// Returns true if the pre-provisioning key or certificate for the specified
|
||||
// system ID are active (not disallowed or revoked).
|
||||
bool IsSystemIdActive(uint32_t system_id);
|
||||
|
||||
// Returns true if the system ID
|
||||
// Returns true is a ProvisionedDeviceInfo exist based on <client_cert>.
|
||||
// Caller owns <device_info> and it must not be null.
|
||||
bool GetDeviceInfo(const ClientCert& client_cert,
|
||||
widevine::ProvisionedDeviceInfo* device_info);
|
||||
// Returns the current POSIX time.
|
||||
virtual uint32_t GetCurrentTime() const;
|
||||
|
||||
// Enable delivery of licenses to revoked client devices. |system_id_list| is
|
||||
// a comma separated list of systems Ids to allow even if revoked.
|
||||
virtual void AllowRevokedDevices(const std::string& system_id_list);
|
||||
|
||||
private:
|
||||
// Returns true if the system ID is allowed to be revoked.
|
||||
// Caller owns |system_id|. They must not be null.
|
||||
bool IsRevokedSystemIdAllowed(uint32_t system_id);
|
||||
|
||||
absl::Mutex status_map_lock_;
|
||||
// Key is the system id for the device.
|
||||
std::map<uint32_t, widevine::DeviceCertificateStatus> device_status_map_;
|
||||
uint32_t creation_time_seconds_;
|
||||
uint32_t expiration_period_seconds_;
|
||||
bool allow_unknown_devices_;
|
||||
bool allow_test_only_devices_;
|
||||
// Contains the list of system_id values that are allowed to succeed even if
|
||||
// revoked.
|
||||
std::vector<uint32_t> allowed_revoked_devices_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DeviceStatusList);
|
||||
};
|
||||
|
||||
} // namespace widevine
|
||||
#endif // LICENSE_SERVER_SDK_INTERNAL_DEVICE_STATUS_LIST_H__
|
||||
377
license_server_sdk/internal/device_status_list_test.cc
Normal file
377
license_server_sdk/internal/device_status_list_test.cc
Normal file
@@ -0,0 +1,377 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 "license_server_sdk/internal/device_status_list.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "testing/gmock.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "common/rsa_key.h"
|
||||
#include "common/rsa_test_keys.h"
|
||||
#include "license_server_sdk/internal/client_cert.h"
|
||||
#include "protos/public/client_identification.pb.h"
|
||||
#include "protos/public/errors.pb.h"
|
||||
#include "protos/public/provisioned_device_info.pb.h"
|
||||
#include "protos/public/signed_drm_certificate.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::Return;
|
||||
using ::testing::ReturnRef;
|
||||
using ::testing::ReturnRefOfCopy;
|
||||
|
||||
const uint32_t kValidCertSystemId = 100;
|
||||
const uint32_t kRevokedCertSystemId = 101;
|
||||
const uint32_t kValidPpkSystemId = 102;
|
||||
const uint32_t kTestOnlyCertSystemId = 103;
|
||||
const uint32_t kRevokedAllowedDeviceCertSystemId = 104;
|
||||
const uint32_t kUnknownSystemId = 666;
|
||||
const char kValidSerialNumber[] = "valid-serial-number";
|
||||
const char kRevokedSerialNumber[] = "revoked-serial-number";
|
||||
const char kRevokedAllowDeviceSerialNumber[] =
|
||||
"revoked-allow-device-serial-number";
|
||||
const char kTestOnlySerialNumber[] = "test_only-serial-number";
|
||||
const char kMismatchSerialNumber[] = "mismatch-serial-number";
|
||||
const char kDeviceModel[] = "device-model-x";
|
||||
const char kTestPreprovKey[] = "00112233445566778899aabbccddeeff";
|
||||
const uint32_t kStatusListCreationTime = 17798001;
|
||||
const uint32_t kDefaultExpirePeriod = 0;
|
||||
|
||||
class MockCertificateClientCert : public CertificateClientCert {
|
||||
public:
|
||||
explicit MockCertificateClientCert() : CertificateClientCert("token-bytes") {}
|
||||
MOCK_CONST_METHOD0(system_id, uint32_t());
|
||||
MOCK_CONST_METHOD0(signer_serial_number, std::string &());
|
||||
MOCK_CONST_METHOD0(signer_creation_time_seconds, uint32_t());
|
||||
MOCK_CONST_METHOD0(type, ClientIdentification::TokenType());
|
||||
MOCK_CONST_METHOD0(signed_by_provisioner, bool());
|
||||
};
|
||||
|
||||
class MockKeyboxClientCert : public KeyboxClientCert {
|
||||
public:
|
||||
explicit MockKeyboxClientCert() : KeyboxClientCert("token-bytes") {}
|
||||
MOCK_CONST_METHOD0(system_id, uint32_t());
|
||||
MOCK_CONST_METHOD0(type, ClientIdentification::TokenType());
|
||||
};
|
||||
|
||||
class DeviceStatusListTest : public ::testing::Test {
|
||||
public:
|
||||
~DeviceStatusListTest() override {}
|
||||
|
||||
void SetUp() override {
|
||||
DeviceCertificateStatus *cert_status;
|
||||
|
||||
// Device cert with status RELEASED.
|
||||
cert_status = cert_status_list_.add_certificate_status();
|
||||
cert_status->mutable_device_info()->set_system_id(kValidCertSystemId);
|
||||
cert_status->set_drm_serial_number(kValidSerialNumber);
|
||||
cert_status->mutable_device_info()->set_model(kDeviceModel);
|
||||
cert_status->set_status(DeviceCertificateStatus::STATUS_RELEASED);
|
||||
|
||||
// Device cert with status REVOKED.
|
||||
cert_status = cert_status_list_.add_certificate_status();
|
||||
cert_status->mutable_device_info()->set_system_id(kRevokedCertSystemId);
|
||||
cert_status->set_drm_serial_number(kRevokedSerialNumber);
|
||||
cert_status->set_status(DeviceCertificateStatus::STATUS_REVOKED);
|
||||
|
||||
// Device cert with status REVOKED ALLOWED DEVICE.
|
||||
cert_status = cert_status_list_.add_certificate_status();
|
||||
cert_status->mutable_device_info()->set_system_id(
|
||||
kRevokedAllowedDeviceCertSystemId);
|
||||
cert_status->set_drm_serial_number(kRevokedAllowDeviceSerialNumber);
|
||||
cert_status->set_status(DeviceCertificateStatus::STATUS_REVOKED);
|
||||
device_status_list_.AllowRevokedDevices(
|
||||
absl::StrCat(kRevokedAllowedDeviceCertSystemId));
|
||||
|
||||
// Device cert with status TEST_ONLY.
|
||||
cert_status = cert_status_list_.add_certificate_status();
|
||||
cert_status->mutable_device_info()->set_system_id(kTestOnlyCertSystemId);
|
||||
cert_status->set_drm_serial_number(kTestOnlySerialNumber);
|
||||
cert_status->set_status(DeviceCertificateStatus::STATUS_TEST_ONLY);
|
||||
|
||||
cert_status_list_.set_creation_time_seconds(kStatusListCreationTime);
|
||||
cert_status_list_.SerializeToString(
|
||||
signed_cert_status_list_.mutable_certificate_status_list());
|
||||
std::unique_ptr<RsaPrivateKey> root_key(
|
||||
RsaPrivateKey::Create(test_keys_.private_test_key_1_3072_bits()));
|
||||
ASSERT_TRUE(root_key);
|
||||
|
||||
ASSERT_TRUE(root_key->GenerateSignature(
|
||||
signed_cert_status_list_.certificate_status_list(),
|
||||
signed_cert_status_list_.mutable_signature()));
|
||||
ASSERT_TRUE(
|
||||
signed_cert_status_list_.SerializeToString(&serialized_status_list_));
|
||||
|
||||
ASSERT_EQ(util::OkStatus(),
|
||||
device_status_list_.UpdateStatusList(
|
||||
test_keys_.public_test_key_1_3072_bits(),
|
||||
serialized_status_list_, kDefaultExpirePeriod));
|
||||
}
|
||||
|
||||
DeviceStatusList device_status_list_;
|
||||
RsaTestKeys test_keys_;
|
||||
DeviceCertificateStatusList cert_status_list_;
|
||||
SignedDeviceCertificateStatusList signed_cert_status_list_;
|
||||
std::string serialized_status_list_;
|
||||
};
|
||||
|
||||
// Returns the number of DevcieCertificateStatus messages in the list.
|
||||
|
||||
TEST_F(DeviceStatusListTest, CheckForValidAndRevokedCert) {
|
||||
// Test case where the Certificate status is set to Valid.
|
||||
ProvisionedDeviceInfo device_info;
|
||||
MockCertificateClientCert valid_client_cert;
|
||||
std::string valid_drm_serial_number(kValidSerialNumber);
|
||||
EXPECT_CALL(valid_client_cert, type())
|
||||
.WillRepeatedly(Return(ClientIdentification::DRM_DEVICE_CERTIFICATE));
|
||||
EXPECT_CALL(valid_client_cert, system_id())
|
||||
.WillRepeatedly(Return(kValidCertSystemId));
|
||||
EXPECT_CALL(valid_client_cert, signer_serial_number())
|
||||
.WillRepeatedly(ReturnRef(valid_drm_serial_number));
|
||||
EXPECT_EQ(util::OkStatus(),
|
||||
device_status_list_.GetCertStatus(valid_client_cert, &device_info));
|
||||
EXPECT_TRUE(device_info.has_model());
|
||||
EXPECT_EQ(kDeviceModel, device_info.model());
|
||||
|
||||
// Test case where the Certificate status is Revoked.
|
||||
MockCertificateClientCert revoked_client_cert;
|
||||
std::string revoked_drm_serial_number(kRevokedSerialNumber);
|
||||
EXPECT_CALL(revoked_client_cert, type())
|
||||
.WillRepeatedly(Return(ClientIdentification::DRM_DEVICE_CERTIFICATE));
|
||||
EXPECT_CALL(revoked_client_cert, system_id())
|
||||
.WillRepeatedly(Return(kRevokedCertSystemId));
|
||||
EXPECT_CALL(revoked_client_cert, signer_serial_number())
|
||||
.WillRepeatedly(ReturnRef(revoked_drm_serial_number));
|
||||
EXPECT_EQ(DRM_DEVICE_CERTIFICATE_REVOKED,
|
||||
device_status_list_.GetCertStatus(revoked_client_cert, &device_info)
|
||||
.error_code());
|
||||
|
||||
// Test case where the revoked cert is allowed.
|
||||
device_status_list_.AllowRevokedDevices(absl::StrCat(kRevokedCertSystemId));
|
||||
EXPECT_OK(
|
||||
device_status_list_.GetCertStatus(revoked_client_cert, &device_info));
|
||||
}
|
||||
|
||||
TEST_F(DeviceStatusListTest, TestOnlyCertAllowed) {
|
||||
ProvisionedDeviceInfo device_info;
|
||||
MockCertificateClientCert test_only_client_cert;
|
||||
std::string test_only_drm_serial_number(kTestOnlySerialNumber);
|
||||
EXPECT_CALL(test_only_client_cert, type())
|
||||
.WillRepeatedly(Return(ClientIdentification::DRM_DEVICE_CERTIFICATE));
|
||||
EXPECT_CALL(test_only_client_cert, system_id())
|
||||
.WillRepeatedly(Return(kTestOnlyCertSystemId));
|
||||
EXPECT_CALL(test_only_client_cert, signer_serial_number())
|
||||
.WillRepeatedly(ReturnRef(test_only_drm_serial_number));
|
||||
EXPECT_EQ(
|
||||
DEVELOPMENT_CERTIFICATE_NOT_ALLOWED,
|
||||
device_status_list_.GetCertStatus(test_only_client_cert, &device_info)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(DeviceStatusListTest, TestOnlyCertNotAllowed) {
|
||||
ProvisionedDeviceInfo device_info;
|
||||
MockCertificateClientCert test_only_client_cert;
|
||||
std::string test_only_drm_serial_number(kTestOnlySerialNumber);
|
||||
device_status_list_.set_allow_test_only_devices(true);
|
||||
EXPECT_CALL(test_only_client_cert, type())
|
||||
.WillRepeatedly(Return(ClientIdentification::DRM_DEVICE_CERTIFICATE));
|
||||
EXPECT_CALL(test_only_client_cert, system_id())
|
||||
.WillRepeatedly(Return(kTestOnlyCertSystemId));
|
||||
EXPECT_CALL(test_only_client_cert, signer_serial_number())
|
||||
.WillRepeatedly(ReturnRef(test_only_drm_serial_number));
|
||||
EXPECT_EQ(util::OkStatus(), device_status_list_.GetCertStatus(
|
||||
test_only_client_cert, &device_info));
|
||||
}
|
||||
|
||||
TEST_F(DeviceStatusListTest, ValidAndUnknownKeybox) {
|
||||
std::multimap<uint32_t, std::string> preprov_keys;
|
||||
preprov_keys.insert(std::make_pair(kValidCertSystemId, kTestPreprovKey));
|
||||
KeyboxClientCert::SetPreProvisioningKeys(preprov_keys);
|
||||
|
||||
// Test case where the Certificate status is set to Valid.
|
||||
ProvisionedDeviceInfo device_info;
|
||||
MockKeyboxClientCert valid_client_keybox;
|
||||
std::string valid_drm_serial_number(kValidSerialNumber);
|
||||
EXPECT_CALL(valid_client_keybox, type())
|
||||
.WillRepeatedly(Return(ClientIdentification::KEYBOX));
|
||||
EXPECT_CALL(valid_client_keybox, system_id())
|
||||
.WillRepeatedly(Return(kValidCertSystemId));
|
||||
EXPECT_EQ(util::OkStatus(), device_status_list_.GetCertStatus(
|
||||
valid_client_keybox, &device_info));
|
||||
EXPECT_TRUE(device_info.has_model());
|
||||
EXPECT_EQ(kDeviceModel, device_info.model());
|
||||
|
||||
MockKeyboxClientCert unknown_client_keybox;
|
||||
EXPECT_CALL(unknown_client_keybox, type())
|
||||
.WillRepeatedly(Return(ClientIdentification::KEYBOX));
|
||||
EXPECT_CALL(unknown_client_keybox, system_id())
|
||||
.WillRepeatedly(Return(kUnknownSystemId));
|
||||
EXPECT_EQ(
|
||||
UNSUPPORTED_SYSTEM_ID,
|
||||
device_status_list_.GetCertStatus(unknown_client_keybox, &device_info)
|
||||
.error_code());
|
||||
EXPECT_TRUE(device_info.has_model());
|
||||
EXPECT_EQ(kDeviceModel, device_info.model());
|
||||
}
|
||||
|
||||
TEST_F(DeviceStatusListTest, SignerSerialNumberMismatch) {
|
||||
device_status_list_.set_allow_unknown_devices(true);
|
||||
|
||||
// Test case where the signer certificate is older than the current status
|
||||
// list.
|
||||
MockCertificateClientCert older_client_cert;
|
||||
ProvisionedDeviceInfo device_info;
|
||||
std::string mismatch_drm_serial_number(kMismatchSerialNumber);
|
||||
EXPECT_CALL(older_client_cert, type())
|
||||
.WillRepeatedly(Return(ClientIdentification::DRM_DEVICE_CERTIFICATE));
|
||||
EXPECT_CALL(older_client_cert, system_id())
|
||||
.WillRepeatedly(Return(kValidCertSystemId));
|
||||
EXPECT_CALL(older_client_cert, signer_serial_number())
|
||||
.WillRepeatedly(ReturnRef(mismatch_drm_serial_number));
|
||||
EXPECT_CALL(older_client_cert, signer_creation_time_seconds())
|
||||
.WillRepeatedly(Return(kStatusListCreationTime - 1));
|
||||
EXPECT_EQ(INVALID_DRM_CERTIFICATE,
|
||||
device_status_list_.GetCertStatus(older_client_cert, &device_info)
|
||||
.error_code());
|
||||
|
||||
// We allow this case only for certs signed by a provisioner cert.
|
||||
EXPECT_CALL(older_client_cert, signed_by_provisioner())
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_EQ(util::OkStatus(),
|
||||
device_status_list_.GetCertStatus(older_client_cert, &device_info));
|
||||
EXPECT_TRUE(device_info.has_system_id());
|
||||
EXPECT_EQ(kValidCertSystemId, device_info.system_id());
|
||||
|
||||
// Test case where the signer certificate is newer than the current status
|
||||
// list, and unknown devices are allowed.
|
||||
MockCertificateClientCert newer_client_cert1;
|
||||
EXPECT_CALL(newer_client_cert1, type())
|
||||
.WillRepeatedly(Return(ClientIdentification::DRM_DEVICE_CERTIFICATE));
|
||||
EXPECT_CALL(newer_client_cert1, system_id())
|
||||
.WillRepeatedly(Return(kValidCertSystemId));
|
||||
EXPECT_CALL(newer_client_cert1, signer_serial_number())
|
||||
.WillRepeatedly(ReturnRef(mismatch_drm_serial_number));
|
||||
EXPECT_CALL(newer_client_cert1, signer_creation_time_seconds())
|
||||
.WillRepeatedly(Return(kStatusListCreationTime));
|
||||
EXPECT_EQ(DRM_DEVICE_CERTIFICATE_UNKNOWN,
|
||||
device_status_list_.GetCertStatus(newer_client_cert1, &device_info)
|
||||
.error_code());
|
||||
|
||||
// Test case where the signer certificate is newer than the current status
|
||||
// list, and unknown devices are not allowed.
|
||||
device_status_list_.set_allow_unknown_devices(false);
|
||||
MockCertificateClientCert newer_client_cert2;
|
||||
EXPECT_CALL(newer_client_cert2, type())
|
||||
.WillRepeatedly(Return(ClientIdentification::DRM_DEVICE_CERTIFICATE));
|
||||
EXPECT_CALL(newer_client_cert2, system_id())
|
||||
.WillRepeatedly(Return(kValidCertSystemId));
|
||||
EXPECT_CALL(newer_client_cert2, signer_serial_number())
|
||||
.WillRepeatedly(ReturnRef(mismatch_drm_serial_number));
|
||||
EXPECT_CALL(newer_client_cert2, signer_creation_time_seconds())
|
||||
.WillRepeatedly(Return(kStatusListCreationTime + 1));
|
||||
EXPECT_EQ(DRM_DEVICE_CERTIFICATE_UNKNOWN,
|
||||
device_status_list_.GetCertStatus(newer_client_cert2, &device_info)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(DeviceStatusListTest, InvalidStatusList) {
|
||||
EXPECT_EQ(INVALID_CERTIFICATE_STATUS_LIST,
|
||||
device_status_list_
|
||||
.UpdateStatusList(test_keys_.public_test_key_2_2048_bits(),
|
||||
serialized_status_list_, 0)
|
||||
.error_code());
|
||||
|
||||
++(*signed_cert_status_list_.mutable_certificate_status_list())[4];
|
||||
ASSERT_TRUE(
|
||||
signed_cert_status_list_.SerializeToString(&serialized_status_list_));
|
||||
EXPECT_EQ(INVALID_CERTIFICATE_STATUS_LIST,
|
||||
device_status_list_
|
||||
.UpdateStatusList(test_keys_.public_test_key_1_3072_bits(),
|
||||
serialized_status_list_, 0)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
class MockDeviceStatusList : public DeviceStatusList {
|
||||
public:
|
||||
MOCK_CONST_METHOD0(GetCurrentTime, uint32_t());
|
||||
};
|
||||
|
||||
TEST_F(DeviceStatusListTest, ExpiredStatusListOnSet) {
|
||||
MockDeviceStatusList mock_device_status_list;
|
||||
EXPECT_CALL(mock_device_status_list, GetCurrentTime())
|
||||
.Times(2)
|
||||
.WillOnce(Return(kStatusListCreationTime + 100))
|
||||
.WillOnce(Return(kStatusListCreationTime + 101));
|
||||
EXPECT_EQ(util::OkStatus(), mock_device_status_list.UpdateStatusList(
|
||||
test_keys_.public_test_key_1_3072_bits(),
|
||||
serialized_status_list_, 100));
|
||||
EXPECT_EQ(EXPIRED_CERTIFICATE_STATUS_LIST,
|
||||
mock_device_status_list
|
||||
.UpdateStatusList(test_keys_.public_test_key_1_3072_bits(),
|
||||
serialized_status_list_, 100)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(DeviceStatusListTest, ExpiredStatusListOnCertCheck) {
|
||||
MockDeviceStatusList mock_device_status_list;
|
||||
EXPECT_CALL(mock_device_status_list, GetCurrentTime())
|
||||
.Times(3)
|
||||
.WillOnce(Return(kStatusListCreationTime + 100))
|
||||
.WillOnce(Return(kStatusListCreationTime + 100))
|
||||
.WillOnce(Return(kStatusListCreationTime + 101));
|
||||
EXPECT_EQ(util::OkStatus(), mock_device_status_list.UpdateStatusList(
|
||||
test_keys_.public_test_key_1_3072_bits(),
|
||||
serialized_status_list_, 100));
|
||||
|
||||
ProvisionedDeviceInfo device_info;
|
||||
MockCertificateClientCert valid_client_cert;
|
||||
std::string valid_drm_serial_number(kValidSerialNumber);
|
||||
EXPECT_CALL(valid_client_cert, type())
|
||||
.WillRepeatedly(Return(ClientIdentification::DRM_DEVICE_CERTIFICATE));
|
||||
EXPECT_CALL(valid_client_cert, system_id())
|
||||
.WillRepeatedly(Return(kValidCertSystemId));
|
||||
EXPECT_CALL(valid_client_cert, signer_serial_number())
|
||||
.WillRepeatedly(ReturnRef(valid_drm_serial_number));
|
||||
EXPECT_CALL(valid_client_cert, signer_creation_time_seconds())
|
||||
.WillRepeatedly(Return(kStatusListCreationTime - 1));
|
||||
EXPECT_EQ(util::OkStatus(), mock_device_status_list.GetCertStatus(
|
||||
valid_client_cert, &device_info));
|
||||
|
||||
EXPECT_EQ(
|
||||
EXPIRED_CERTIFICATE_STATUS_LIST,
|
||||
mock_device_status_list.GetCertStatus(valid_client_cert, &device_info)
|
||||
.error_code());
|
||||
}
|
||||
|
||||
TEST_F(DeviceStatusListTest, IsSystemIdActive) {
|
||||
std::multimap<uint32_t, std::string> preprov_keys;
|
||||
preprov_keys.insert(
|
||||
std::make_pair(kValidPpkSystemId, "00112233445566778899aabbccddeeff"));
|
||||
KeyboxClientCert::SetPreProvisioningKeys(preprov_keys);
|
||||
device_status_list_.set_allow_unknown_devices(false);
|
||||
EXPECT_TRUE(device_status_list_.IsSystemIdActive(kValidCertSystemId));
|
||||
EXPECT_TRUE(device_status_list_.IsSystemIdActive(kValidPpkSystemId));
|
||||
EXPECT_FALSE(device_status_list_.IsSystemIdActive(kRevokedCertSystemId));
|
||||
EXPECT_FALSE(device_status_list_.IsSystemIdActive(kUnknownSystemId));
|
||||
device_status_list_.set_allow_unknown_devices(true);
|
||||
EXPECT_TRUE(device_status_list_.IsSystemIdActive(kValidCertSystemId));
|
||||
EXPECT_TRUE(device_status_list_.IsSystemIdActive(kValidPpkSystemId));
|
||||
EXPECT_FALSE(device_status_list_.IsSystemIdActive(kRevokedCertSystemId));
|
||||
EXPECT_TRUE(device_status_list_.IsSystemIdActive(kUnknownSystemId));
|
||||
EXPECT_TRUE(
|
||||
device_status_list_.IsSystemIdActive(kRevokedAllowedDeviceCertSystemId));
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
90
license_server_sdk/internal/generate_error_response.cc
Normal file
90
license_server_sdk/internal/generate_error_response.cc
Normal file
@@ -0,0 +1,90 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 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 "license_server_sdk/internal/generate_error_response.h"
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "common/drm_service_certificate.h"
|
||||
#include "common/error_space.h"
|
||||
#include "sdk/external/common/wvpl/wvpl_types.h"
|
||||
#include "protos/public/errors.pb.h"
|
||||
#include "protos/public/license_protocol.pb.h"
|
||||
|
||||
namespace util = widevine::util;
|
||||
using widevine::DRM_DEVICE_CERTIFICATE_REVOKED;
|
||||
using widevine::DrmServiceCertificate;
|
||||
using widevine::EXPIRED_CERTIFICATE_STATUS_LIST;
|
||||
using widevine::INVALID_DRM_CERTIFICATE;
|
||||
using widevine::LicenseError;
|
||||
using widevine::SERVICE_CERTIFICATE_REQUEST_MESSAGE;
|
||||
using widevine::SignedMessage;
|
||||
|
||||
namespace widevine {
|
||||
bool GenerateErrorResponse(const util::Status& create_session_status,
|
||||
std::string* license_response) {
|
||||
DCHECK(license_response);
|
||||
|
||||
LicenseError error_proto;
|
||||
if (create_session_status.error_space() == error_space) {
|
||||
switch (create_session_status.error_code()) {
|
||||
case INVALID_DRM_CERTIFICATE:
|
||||
error_proto.set_error_code(
|
||||
LicenseError::INVALID_DRM_DEVICE_CERTIFICATE);
|
||||
break;
|
||||
case DRM_DEVICE_CERTIFICATE_REVOKED:
|
||||
// TODO(user): Do we want to rename this error (INACTIVE) or
|
||||
// generate new ones?
|
||||
error_proto.set_error_code(
|
||||
LicenseError::REVOKED_DRM_DEVICE_CERTIFICATE);
|
||||
break;
|
||||
case EXPIRED_CERTIFICATE_STATUS_LIST:
|
||||
error_proto.set_error_code(LicenseError::SERVICE_UNAVAILABLE);
|
||||
break;
|
||||
case SERVICE_CERTIFICATE_REQUEST_MESSAGE: {
|
||||
SignedMessage signed_message;
|
||||
signed_message.set_type(SignedMessage::SERVICE_CERTIFICATE);
|
||||
signed_message.set_msg(
|
||||
DrmServiceCertificate::GetDefaultDrmServiceCertificateOrDie()
|
||||
->certificate());
|
||||
if (!signed_message.SerializeToString(license_response)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((create_session_status.error_space() ==
|
||||
util::Status::canonical_space()) &&
|
||||
(create_session_status.error_code() == util::error::UNAVAILABLE)) {
|
||||
error_proto.set_error_code(LicenseError::SERVICE_UNAVAILABLE);
|
||||
}
|
||||
if (!error_proto.has_error_code()) {
|
||||
return false;
|
||||
}
|
||||
SignedMessage signed_message;
|
||||
signed_message.set_type(SignedMessage::ERROR_RESPONSE);
|
||||
if (!error_proto.SerializeToString(signed_message.mutable_msg())) {
|
||||
return false;
|
||||
}
|
||||
if (!signed_message.SerializeToString(license_response)) {
|
||||
return false;
|
||||
}
|
||||
switch (error_proto.error_code()) {
|
||||
case LicenseError::INVALID_DRM_DEVICE_CERTIFICATE:
|
||||
case LicenseError::REVOKED_DRM_DEVICE_CERTIFICATE:
|
||||
case LicenseError::SERVICE_UNAVAILABLE:
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace widevine
|
||||
27
license_server_sdk/internal/generate_error_response.h
Normal file
27
license_server_sdk/internal/generate_error_response.h
Normal file
@@ -0,0 +1,27 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2018 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.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef LICENSE_SERVER_SDK_INTERNAL_GENERATE_ERROR_RESPONSE_H_
|
||||
#define LICENSE_SERVER_SDK_INTERNAL_GENERATE_ERROR_RESPONSE_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "util/status.h"
|
||||
|
||||
namespace widevine {
|
||||
// Generates a SignedMessage containing a message generated in response to
|
||||
// an error condition. |status| is a previous error status returned by the
|
||||
// Session or util::Status(util::error::UNAVAILABLE, ...) to indicate that the
|
||||
// backend is unavailable, |signed_message| points to a std::string to contain the
|
||||
// serialized SignedMessage, and may not be NULL. This method returns true if
|
||||
// there is an error license to be sent to the client, or false otherwise.
|
||||
// Example usage in the Session::Create comments above.
|
||||
bool GenerateErrorResponse(const util::Status& status,
|
||||
std::string* license_response);
|
||||
} // namespace widevine
|
||||
#endif // LICENSE_SERVER_SDK_INTERNAL_GENERATE_ERROR_RESPONSE_H_
|
||||
224
license_server_sdk/internal/key_control_block.cc
Normal file
224
license_server_sdk/internal/key_control_block.cc
Normal file
@@ -0,0 +1,224 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Handles population of the Key Control Block (KCB) as defined here:
|
||||
// https://docs.google.com/document/d/1pHSJ2IKL0axmQz2gmDZ7olxPWb_ZcULaJrYwDZAeS7k/edit#heading=h.pmnr7d8jqxea
|
||||
|
||||
#include "license_server_sdk/internal/key_control_block.h"
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "glog/logging.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "protos/public/client_identification.pb.h"
|
||||
#include "protos/public/license_server_sdk.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
namespace key_control_block {
|
||||
|
||||
void DevicesCanHandleOEMCryptoVersionInKCB(const std::string& system_ids) {
|
||||
for (absl::string_view sp : absl::StrSplit(system_ids, ',')) {
|
||||
devices_can_handle_oemcrypto_version_.push_back(std::stoi(std::string(sp)));
|
||||
}
|
||||
std::sort(devices_can_handle_oemcrypto_version_.begin(),
|
||||
devices_can_handle_oemcrypto_version_.end());
|
||||
}
|
||||
|
||||
bool Generate(
|
||||
const License::KeyContainer& key_container, uint32_t duration, uint32_t nonce,
|
||||
const ClientIdentification::ClientCapabilities& client_capabilities,
|
||||
bool has_provider_session_token,
|
||||
const SessionInit* session_init,
|
||||
LicenseType license_type,
|
||||
uint32_t system_id, std::string* key_control_block) {
|
||||
DCHECK(key_control_block);
|
||||
|
||||
bool disable_oem_crypto_api_version_reflection = false;
|
||||
std::string override_oem_crypto_api_version;
|
||||
if (session_init != nullptr) {
|
||||
disable_oem_crypto_api_version_reflection =
|
||||
session_init->disable_oem_crypto_api_version_reflection();
|
||||
override_oem_crypto_api_version =
|
||||
session_init->override_oem_crypto_api_version();
|
||||
}
|
||||
uint32_t control_bits = 0;
|
||||
// Indicates the device must be at OEMCrypto v9 or greater.
|
||||
bool min_v9_block = false;
|
||||
// Some devices (such as LSI implementation) will fail if we return kc10 in
|
||||
// the KCB. Until OEMCrypto version 13 is rolled-out, we will whitelist
|
||||
// devices that can support kc10, kc11, kc12, ...
|
||||
bool device_can_support_kcxx = false;
|
||||
auto it = std::binary_search(devices_can_handle_oemcrypto_version_.begin(),
|
||||
devices_can_handle_oemcrypto_version_.end(),
|
||||
system_id);
|
||||
// Set |device_can_support_kcxx| if either
|
||||
// (a) Device System Id is whitelisted.
|
||||
// (b) The device client capabilities has an OEM Crypto API version >=13
|
||||
// AND
|
||||
// |disable_oem_crypto_api_version_reflection| has been enabled.
|
||||
if ((it || client_capabilities.oem_crypto_api_version() >= 13) &&
|
||||
!disable_oem_crypto_api_version_reflection) {
|
||||
device_can_support_kcxx = true;
|
||||
}
|
||||
switch (key_container.type()) {
|
||||
case License::KeyContainer::CONTENT:
|
||||
case License::KeyContainer::ENTITLEMENT: {
|
||||
if (key_container.has_required_protection() &&
|
||||
key_container.required_protection().has_cgms_flags()) {
|
||||
control_bits |= kKeyControlFlagsObserveCgms; // Observe CGMS
|
||||
if (key_container.required_protection().cgms_flags() ==
|
||||
License::KeyContainer::OutputProtection::COPY_ONCE) {
|
||||
control_bits |= kKeyControlFlagsCgmsCopyOnce;
|
||||
} else if (key_container.required_protection().cgms_flags() ==
|
||||
License::KeyContainer::OutputProtection::COPY_NEVER) {
|
||||
control_bits |= kKeyControlFlagsCgmsCopyNever;
|
||||
}
|
||||
}
|
||||
if (key_container.has_required_protection() &&
|
||||
key_container.required_protection().has_hdcp() &&
|
||||
key_container.required_protection().hdcp() !=
|
||||
License::KeyContainer::OutputProtection::HDCP_NONE) {
|
||||
control_bits |= kKeyControlFlagsObserveHdcp; // Observe HDCP
|
||||
control_bits |= kKeyControlFlagsHdcpRequired;
|
||||
switch (key_container.required_protection().hdcp()) {
|
||||
case License::KeyContainer::OutputProtection::HDCP_V1:
|
||||
control_bits |= kKeyControlFlagsHdcp_V1_0;
|
||||
break;
|
||||
case License::KeyContainer::OutputProtection::HDCP_V2:
|
||||
control_bits |= kKeyControlFlagsHdcp_V2_0;
|
||||
break;
|
||||
case License::KeyContainer::OutputProtection::HDCP_V2_1:
|
||||
control_bits |= kKeyControlFlagsHdcp_V2_1;
|
||||
break;
|
||||
case License::KeyContainer::OutputProtection::HDCP_V2_2:
|
||||
control_bits |= kKeyControlFlagsHdcp_V2_2;
|
||||
break;
|
||||
case License::KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT:
|
||||
control_bits |= kKeyControlFlagsLocalDisplayOnly;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (key_container.has_required_protection() &&
|
||||
key_container.required_protection().has_hdcp_srm_rule() &&
|
||||
key_container.required_protection().hdcp_srm_rule() ==
|
||||
License::KeyContainer::OutputProtection::CURRENT_SRM) {
|
||||
// Observe SRM_Version_Required
|
||||
control_bits |= kKeyControlFlagsSrmVersionRequired;
|
||||
}
|
||||
if (key_container.anti_rollback_usage_table()) {
|
||||
if (client_capabilities.has_oem_crypto_api_version() &&
|
||||
client_capabilities.oem_crypto_api_version() < 10) {
|
||||
// Attempt to send a key requiring anti rollback to a client that is
|
||||
// earlier than version 10 is not allowed.
|
||||
return false;
|
||||
}
|
||||
control_bits |= kKeyControlFlagsAntiRollbackUsageTableRequired;
|
||||
}
|
||||
if (key_container.has_level()) {
|
||||
control_bits |= kKeyControlFlagsObserveDataPath; // Observe DataPath
|
||||
if (key_container.level() == License::KeyContainer::HW_SECURE_ALL) {
|
||||
control_bits |= kKeyControlFlagsDataPathSecure;
|
||||
}
|
||||
}
|
||||
if (key_container.has_required_protection() &&
|
||||
key_container.required_protection().disable_analog_output()) {
|
||||
control_bits |= kKeyControlFlagsDisableAnalogOutput;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case License::KeyContainer::OPERATOR_SESSION: {
|
||||
if (key_container.has_operator_session_key_permissions()) {
|
||||
if (key_container.operator_session_key_permissions().allow_encrypt()) {
|
||||
control_bits |= kKeyControlFlagsAllowEncrypt;
|
||||
}
|
||||
if (key_container.operator_session_key_permissions().allow_decrypt()) {
|
||||
control_bits |= kKeyControlFlagsAllowDecrypt;
|
||||
}
|
||||
if (key_container.operator_session_key_permissions().allow_sign()) {
|
||||
control_bits |= kKeyControlFlagsAllowSign;
|
||||
}
|
||||
if (key_container.operator_session_key_permissions()
|
||||
.allow_signature_verify()) {
|
||||
control_bits |= kKeyControlFlagsAllowVerify;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case License::KeyContainer::KEY_CONTROL:
|
||||
// No control bits for this type of key container used for license
|
||||
// renewals.
|
||||
break;
|
||||
default:
|
||||
// Should not generate key control blocks for any key types other than
|
||||
// the above.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (license_type == STREAMING) {
|
||||
control_bits |= kKeyControlFlagsNonceEnable;
|
||||
}
|
||||
if (client_capabilities.session_token()) {
|
||||
if (has_provider_session_token) {
|
||||
if (license_type == STREAMING) {
|
||||
control_bits |= kKeyControlFlagsReplayControl1;
|
||||
} else {
|
||||
control_bits |= kKeyControlFlagsReplayControl2;
|
||||
}
|
||||
}
|
||||
min_v9_block = true;
|
||||
}
|
||||
if (client_capabilities.has_oem_crypto_api_version() &&
|
||||
client_capabilities.oem_crypto_api_version() >= 9) {
|
||||
min_v9_block = true;
|
||||
}
|
||||
|
||||
const size_t kcb_buffer_len = 4;
|
||||
uint32_t kcb_buffer[kcb_buffer_len];
|
||||
char* kcb_char_ptr = reinterpret_cast<char*>(kcb_buffer);
|
||||
kcb_char_ptr[0] = 'k';
|
||||
kcb_char_ptr[1] = 'c';
|
||||
kcb_char_ptr[2] = 't';
|
||||
kcb_char_ptr[3] = 'l';
|
||||
if (!override_oem_crypto_api_version.empty() && device_can_support_kcxx) {
|
||||
memcpy(kcb_char_ptr, override_oem_crypto_api_version.data(),
|
||||
(override_oem_crypto_api_version.size() > kcb_buffer_len
|
||||
? kcb_buffer_len : override_oem_crypto_api_version.size()));
|
||||
} else if (min_v9_block) {
|
||||
// Limited by only 2 characters in KCB, hence cannot support 3 digits
|
||||
// like 100.
|
||||
const int maxApiVersionSupported = 99;
|
||||
if (client_capabilities.oem_crypto_api_version() <= 9 ||
|
||||
!device_can_support_kcxx) {
|
||||
kcb_char_ptr[2] = '0';
|
||||
kcb_char_ptr[3] = '9';
|
||||
} else if (client_capabilities.oem_crypto_api_version() <=
|
||||
maxApiVersionSupported) {
|
||||
kcb_char_ptr[2] = '0' + client_capabilities.oem_crypto_api_version() / 10;
|
||||
kcb_char_ptr[3] = '0' + client_capabilities.oem_crypto_api_version() % 10;
|
||||
} else {
|
||||
// TODO(user): Update KCB to handle 3 digit version.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
kcb_buffer[1] = htonl(duration);
|
||||
kcb_buffer[2] = htonl(nonce);
|
||||
kcb_buffer[3] = htonl(control_bits);
|
||||
key_control_block->assign(reinterpret_cast<const char*>(&kcb_buffer[0]),
|
||||
sizeof(kcb_buffer));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace key_control_block
|
||||
} // namespace widevine
|
||||
71
license_server_sdk/internal/key_control_block.h
Normal file
71
license_server_sdk/internal/key_control_block.h
Normal file
@@ -0,0 +1,71 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef LICENSE_SERVER_SDK_INTERNAL_KEY_CONTROL_BLOCK_H__
|
||||
#define LICENSE_SERVER_SDK_INTERNAL_KEY_CONTROL_BLOCK_H__
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "protos/public/client_identification.pb.h"
|
||||
#include "protos/public/license_protocol.pb.h"
|
||||
#include "protos/public/license_server_sdk.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace key_control_block {
|
||||
|
||||
// This class implements the Key Control Block (KCB) embedded in the license.
|
||||
// Documemtation on the KCB is located here:
|
||||
// 1pHSJ2IKL0axmQz2gmDZ7olxPWb_ZcULaJrYwDZAeS7k/edit#
|
||||
const uint32_t kKeyControlFlagsCgmsCopyOnce = 0x00000002;
|
||||
const uint32_t kKeyControlFlagsCgmsCopyNever = 0x00000003;
|
||||
const uint32_t kKeyControlFlagsHdcpRequired = 0x00000004;
|
||||
const uint32_t kKeyControlFlagsNonceEnable = 0x00000008;
|
||||
const uint32_t kKeyControlFlagsDataPathSecure = 0x00000010;
|
||||
const uint32_t kKeyControlFlagsAllowVerify = 0x00000020;
|
||||
const uint32_t kKeyControlFlagsAllowSign = 0x00000040;
|
||||
const uint32_t kKeyControlFlagsAllowDecrypt = 0x00000080;
|
||||
const uint32_t kKeyControlFlagsAllowEncrypt = 0x00000100;
|
||||
const uint32_t kKeyControlFlagsAntiRollbackUsageTableRequired = 0x10000000;
|
||||
const uint32_t kKeyControlFlagsObserveCgms = 0x20000000;
|
||||
const uint32_t kKeyControlFlagsObserveHdcp = 0x40000000;
|
||||
const uint32_t kKeyControlFlagsObserveDataPath = 0x80000000;
|
||||
const uint32_t kKeyControlFlagsReplayControlMask = 0x00006000;
|
||||
const uint32_t kKeyControlFlagsReplayControl1 = 0x00002000;
|
||||
const uint32_t kKeyControlFlagsReplayControl2 = 0x00004000;
|
||||
const uint32_t kKeyControlFlagsHdcpMask = 0x00001e00;
|
||||
const uint32_t kKeyControlFlagsHdcp_V1_0 = 0x00000200;
|
||||
const uint32_t kKeyControlFlagsHdcp_V2_0 = 0x00000400;
|
||||
const uint32_t kKeyControlFlagsHdcp_V2_1 = 0x00000600;
|
||||
const uint32_t kKeyControlFlagsHdcp_V2_2 = 0x00000800;
|
||||
const uint32_t kKeyControlFlagsLocalDisplayOnly = 0x00001E00;
|
||||
const uint32_t kKeyControlFlagsDisableAnalogOutput = 0x00200000;
|
||||
const uint32_t kKeyControlFlagsSrmVersionRequired = 0x00400000;
|
||||
|
||||
// Generate the key control block structuure. |key_control_block| must not be
|
||||
// null and is used to hold the key control data. Returns true if successful,
|
||||
// false otherwise.
|
||||
bool Generate(
|
||||
const License::KeyContainer& key_container, uint32_t duration, uint32_t nonce,
|
||||
const ClientIdentification::ClientCapabilities& client_capabilities,
|
||||
bool has_provider_session_token,
|
||||
const SessionInit* session_init, LicenseType license_type,
|
||||
uint32_t system_id, std::string* key_control_block);
|
||||
|
||||
// Specify a comma separated list of system Ids that can support having
|
||||
// OEMCrypto version, as specified in the license request, refected back in
|
||||
// the Key Control Block. Otherwise, only 'kctl' or 'kc09' is returned in KCB.
|
||||
void DevicesCanHandleOEMCryptoVersionInKCB(const std::string& system_ids);
|
||||
|
||||
// Contains the list of system_id values that can support reflecting OEMCrypto
|
||||
// version in KCB.
|
||||
static std::vector<uint32_t> devices_can_handle_oemcrypto_version_;
|
||||
|
||||
} // namespace key_control_block
|
||||
} // namespace widevine
|
||||
#endif // LICENSE_SERVER_SDK_INTERNAL_KEY_CONTROL_BLOCK_H__
|
||||
802
license_server_sdk/internal/key_control_block_test.cc
Normal file
802
license_server_sdk/internal/key_control_block_test.cc
Normal file
@@ -0,0 +1,802 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 "license_server_sdk/internal/key_control_block.h"
|
||||
|
||||
#include "base/googleinit.h"
|
||||
#include "testing/gunit.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "protos/public/license_protocol.pb.h"
|
||||
#include "protos/public/license_server_sdk.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace key_control_block {
|
||||
|
||||
const char* kKCBAntiRollbackFalse = "6b633039000000000000000000002008";
|
||||
const char* kKCBAntiRollbackTrue = "6b633039000000000000000010002008";
|
||||
|
||||
TEST(KeyControlBlockTest, test_cmgs_bits) {
|
||||
uint32_t duration = 0;
|
||||
uint32_t nonce = 0;
|
||||
bool has_provider_session_token = true;
|
||||
uint32_t system_id = 0;
|
||||
ClientIdentification::ClientCapabilities client_capabilities;
|
||||
SessionInit session_init;
|
||||
client_capabilities.set_session_token(true);
|
||||
License::KeyContainer key_container;
|
||||
key_container.set_type(License::KeyContainer::CONTENT);
|
||||
key_container.mutable_required_protection()->set_cgms_flags(
|
||||
License::KeyContainer::OutputProtection::COPY_FREE);
|
||||
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000020002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.mutable_required_protection()->set_cgms_flags(
|
||||
License::KeyContainer::OutputProtection::COPY_ONCE);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b63303900000000000000002000200a",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.mutable_required_protection()->set_cgms_flags(
|
||||
License::KeyContainer::OutputProtection::COPY_NEVER);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b63303900000000000000002000200b",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
}
|
||||
|
||||
TEST(KeyControlBlockTest, test_hdcp_bits_v0) {
|
||||
uint32_t duration = 0;
|
||||
uint32_t nonce = 0;
|
||||
bool has_provider_session_token = true;
|
||||
uint32_t system_id = 0;
|
||||
ClientIdentification::ClientCapabilities client_capabilities;
|
||||
client_capabilities.set_session_token(false);
|
||||
SessionInit session_init;
|
||||
License::KeyContainer key_container;
|
||||
key_container.set_type(License::KeyContainer::CONTENT);
|
||||
key_container.mutable_required_protection()->set_hdcp(
|
||||
License::KeyContainer::OutputProtection::HDCP_NONE);
|
||||
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b63746c000000000000000000000008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.mutable_required_protection()->set_hdcp(
|
||||
License::KeyContainer::OutputProtection::HDCP_V1);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b63746c00000000000000004000020c",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
}
|
||||
|
||||
TEST(KeyControlBlockTest, test_hdcp_bits_v9) {
|
||||
uint32_t duration = 0;
|
||||
uint32_t nonce = 0;
|
||||
bool has_provider_session_token = true;
|
||||
uint32_t system_id = 0;
|
||||
SessionInit session_init;
|
||||
ClientIdentification::ClientCapabilities client_capabilities;
|
||||
client_capabilities.set_session_token(true);
|
||||
License::KeyContainer key_container;
|
||||
key_container.set_type(License::KeyContainer::CONTENT);
|
||||
|
||||
key_container.mutable_required_protection()->set_hdcp(
|
||||
License::KeyContainer::OutputProtection::HDCP_NONE);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000000002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.mutable_required_protection()->set_hdcp(
|
||||
License::KeyContainer::OutputProtection::HDCP_V1);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b63303900000000000000004000220c",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.mutable_required_protection()->set_hdcp(
|
||||
License::KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000040003e0c",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
}
|
||||
|
||||
TEST(KeyControlBlockTest, test_hdcp_versioning) {
|
||||
uint32_t duration = 0;
|
||||
uint32_t nonce = 0;
|
||||
bool has_provider_session_token = true;
|
||||
uint32_t system_id = 0;
|
||||
SessionInit session_init;
|
||||
ClientIdentification::ClientCapabilities client_capabilities;
|
||||
client_capabilities.set_session_token(true);
|
||||
client_capabilities.set_oem_crypto_api_version(0);
|
||||
License::KeyContainer key_container;
|
||||
key_container.set_type(License::KeyContainer::CONTENT);
|
||||
|
||||
key_container.mutable_required_protection()->set_hdcp(
|
||||
License::KeyContainer::OutputProtection::HDCP_V2);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b63303900000000000000004000240c",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.mutable_required_protection()->set_hdcp(
|
||||
License::KeyContainer::OutputProtection::HDCP_V2_1);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b63303900000000000000004000260c",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.mutable_required_protection()->set_hdcp(
|
||||
License::KeyContainer::OutputProtection::HDCP_V2_2);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b63303900000000000000004000280c",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
}
|
||||
|
||||
TEST(KeyControlBlockTest, test_srm_version_required_bits) {
|
||||
uint32_t duration = 0;
|
||||
uint32_t nonce = 0;
|
||||
bool has_provider_session_token = true;
|
||||
uint32_t system_id = 0;
|
||||
SessionInit session_init;
|
||||
ClientIdentification::ClientCapabilities client_capabilities;
|
||||
client_capabilities.set_session_token(true);
|
||||
License::KeyContainer key_container;
|
||||
key_container.set_type(License::KeyContainer::CONTENT);
|
||||
|
||||
key_container.mutable_requested_protection()->set_hdcp_srm_rule(
|
||||
License::KeyContainer::OutputProtection::CURRENT_SRM);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000000002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.mutable_required_protection()->set_hdcp_srm_rule(
|
||||
License::KeyContainer::OutputProtection::HDCP_SRM_RULE_NONE);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000000002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.mutable_required_protection()->set_hdcp_srm_rule(
|
||||
License::KeyContainer::OutputProtection::CURRENT_SRM);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000000402008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
}
|
||||
|
||||
TEST(KeyControlBlockTest, test_analog_output) {
|
||||
uint32_t duration = 0;
|
||||
uint32_t nonce = 0;
|
||||
bool has_provider_session_token = true;
|
||||
uint32_t system_id = 0;
|
||||
SessionInit session_init;
|
||||
ClientIdentification::ClientCapabilities client_capabilities;
|
||||
client_capabilities.set_session_token(true);
|
||||
License::KeyContainer key_container;
|
||||
key_container.set_type(License::KeyContainer::CONTENT);
|
||||
|
||||
// Requested is ignored.
|
||||
key_container.mutable_requested_protection()->set_disable_analog_output(
|
||||
false);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000000002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.mutable_requested_protection()->set_disable_analog_output(true);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000000002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.mutable_required_protection()->set_disable_analog_output(false);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000000002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.mutable_required_protection()->set_disable_analog_output(true);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000000202008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
}
|
||||
|
||||
TEST(KeyControlBlockTest, test_data_path_bits) {
|
||||
uint32_t duration = 0;
|
||||
uint32_t nonce = 0;
|
||||
bool has_provider_session_token = true;
|
||||
uint32_t system_id = 0;
|
||||
SessionInit session_init;
|
||||
ClientIdentification::ClientCapabilities client_capabilities;
|
||||
client_capabilities.set_session_token(true);
|
||||
License::KeyContainer key_container;
|
||||
key_container.set_type(License::KeyContainer::CONTENT);
|
||||
|
||||
key_container.set_level(License::KeyContainer::SW_SECURE_CRYPTO);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000080002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.set_level(License::KeyContainer::SW_SECURE_DECODE);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000080002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.set_level(License::KeyContainer::HW_SECURE_CRYPTO);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000080002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.set_level(License::KeyContainer::HW_SECURE_DECODE);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000080002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.set_level(License::KeyContainer::HW_SECURE_ALL);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000080002018",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
}
|
||||
|
||||
// Test updating the duration for all keys associated with this license.
|
||||
// This is the case for license renewals.
|
||||
TEST(KeyControlBlockTest, test_renewal) {
|
||||
uint32_t duration = 0;
|
||||
uint32_t nonce = 0x12345678;
|
||||
bool has_provider_session_token = true;
|
||||
uint32_t system_id = 0;
|
||||
SessionInit session_init;
|
||||
ClientIdentification::ClientCapabilities client_capabilities;
|
||||
client_capabilities.set_session_token(true);
|
||||
License::KeyContainer key_container;
|
||||
key_container.set_type(License::KeyContainer::KEY_CONTROL);
|
||||
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000001234567800002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
duration = 0x6789;
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000067891234567800002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
}
|
||||
|
||||
// Test for operator session key control blocks.
|
||||
TEST(KeyControlBlockTest, test_operator_session_key) {
|
||||
uint32_t duration = 0;
|
||||
uint32_t nonce = 0x12345678;
|
||||
bool has_provider_session_token = true;
|
||||
uint32_t system_id = 0;
|
||||
SessionInit session_init;
|
||||
ClientIdentification::ClientCapabilities client_capabilities;
|
||||
client_capabilities.set_session_token(true);
|
||||
License::KeyContainer key_container;
|
||||
key_container.set_type(License::KeyContainer::OPERATOR_SESSION);
|
||||
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000001234567800002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.mutable_operator_session_key_permissions()->set_allow_encrypt(
|
||||
true);
|
||||
duration = 0x87654321;
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039876543211234567800002108",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.mutable_operator_session_key_permissions()->set_allow_decrypt(
|
||||
true);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039876543211234567800002188",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.mutable_operator_session_key_permissions()->set_allow_sign(
|
||||
true);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b6330398765432112345678000021c8",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
key_container.mutable_operator_session_key_permissions()
|
||||
->set_allow_signature_verify(true);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b6330398765432112345678000021e8",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
}
|
||||
|
||||
TEST(KeyControlBlockTest, test_signing_key) {
|
||||
uint32_t duration = 0;
|
||||
uint32_t nonce = 0x12345678;
|
||||
bool has_provider_session_token = true;
|
||||
uint32_t system_id = 0;
|
||||
SessionInit session_init;
|
||||
ClientIdentification::ClientCapabilities client_capabilities;
|
||||
client_capabilities.set_session_token(true);
|
||||
License::KeyContainer key_container;
|
||||
key_container.set_type(License::KeyContainer::SIGNING);
|
||||
|
||||
EXPECT_FALSE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
}
|
||||
|
||||
TEST(KeyControlBlockTest, test_has_provider_session_token) {
|
||||
uint32_t duration = 0;
|
||||
uint32_t nonce = 0x12345678;
|
||||
bool has_provider_session_token = true;
|
||||
uint32_t system_id = 0;
|
||||
SessionInit session_init;
|
||||
ClientIdentification::ClientCapabilities client_capabilities;
|
||||
client_capabilities.set_session_token(true);
|
||||
License::KeyContainer key_container;
|
||||
key_container.set_type(License::KeyContainer::KEY_CONTROL);
|
||||
|
||||
EXPECT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000001234567800002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
EXPECT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, OFFLINE, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000001234567800004000",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
has_provider_session_token = false;
|
||||
EXPECT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000001234567800000008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
EXPECT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, OFFLINE, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000001234567800000000",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
}
|
||||
|
||||
TEST(KeyControlBlockTest, test_client_supports_session_token) {
|
||||
uint32_t duration = 0;
|
||||
uint32_t nonce = 0x12345678;
|
||||
bool has_provider_session_token = true;
|
||||
uint32_t system_id = 0;
|
||||
SessionInit session_init;
|
||||
ClientIdentification::ClientCapabilities client_capabilities;
|
||||
client_capabilities.set_session_token(true);
|
||||
License::KeyContainer key_container;
|
||||
key_container.set_type(License::KeyContainer::KEY_CONTROL);
|
||||
|
||||
EXPECT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, OFFLINE, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000001234567800004000",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
client_capabilities.set_session_token(false);
|
||||
EXPECT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, OFFLINE, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b63746c000000001234567800000000",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
EXPECT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b63746c000000001234567800000008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
client_capabilities.set_session_token(false);
|
||||
EXPECT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b63746c000000001234567800000008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
}
|
||||
|
||||
TEST(KeyControlBlockTest, test_replay_control) {
|
||||
uint32_t duration = 0;
|
||||
uint32_t nonce = 0;
|
||||
bool has_provider_session_token = true;
|
||||
uint32_t system_id = 0;
|
||||
SessionInit session_init;
|
||||
ClientIdentification::ClientCapabilities client_capabilities;
|
||||
client_capabilities.set_session_token(true);
|
||||
client_capabilities.set_oem_crypto_api_version(9);
|
||||
License::KeyContainer key_container;
|
||||
key_container.set_type(License::KeyContainer::CONTENT);
|
||||
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000000002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, OFFLINE, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000000004000",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
}
|
||||
|
||||
TEST(KeyControlBlockTest, test_anti_rollback_bit) {
|
||||
uint32_t duration = 0;
|
||||
uint32_t nonce = 0;
|
||||
bool has_provider_session_token = true;
|
||||
uint32_t system_id = 0;
|
||||
SessionInit session_init;
|
||||
ClientIdentification::ClientCapabilities client_capabilities;
|
||||
client_capabilities.set_session_token(true);
|
||||
client_capabilities.set_oem_crypto_api_version(9);
|
||||
License::KeyContainer key_container;
|
||||
key_container.set_type(License::KeyContainer::CONTENT);
|
||||
|
||||
// anti_rollback bit in KCB is false because client does not support anti
|
||||
// rollback.
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
kKCBAntiRollbackFalse,
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
// anti_rollback bit in KCB is false because oem api version < 10.
|
||||
client_capabilities.set_anti_rollback_usage_table(true);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
kKCBAntiRollbackFalse,
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
// anti_rollback bit in KCB is false because provider did not specify it as
|
||||
// required in the key_container.
|
||||
client_capabilities.set_oem_crypto_api_version(10);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
kKCBAntiRollbackFalse,
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
// anti_rollback bit in KCB is true.
|
||||
key_container.set_anti_rollback_usage_table(true);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
kKCBAntiRollbackTrue,
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
// KCB call should fail due to invalid usage. The provider is attempting to
|
||||
// require anti rollback on clients earlier than version 10.
|
||||
client_capabilities.set_oem_crypto_api_version(9);
|
||||
ASSERT_FALSE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
}
|
||||
|
||||
TEST(KeyControlBlockTest, test_OEMCrypto_Version) {
|
||||
uint32_t duration = 0;
|
||||
uint32_t nonce = 0;
|
||||
bool has_provider_session_token = true;
|
||||
// This device (system_id) can only support kctl, kc09 in the KCB.
|
||||
const uint32_t default_system_id = 1000;
|
||||
// This device (system_id) can support kcXX in the KCB, where XX is the
|
||||
// OEMVersion.
|
||||
const uint32_t oem_version_in_kcb_system_id = 1001;
|
||||
const std::string system_id_can_handle_oem_version("2000,1001,3000");
|
||||
SessionInit session_init;
|
||||
ClientIdentification::ClientCapabilities client_capabilities;
|
||||
client_capabilities.set_session_token(true);
|
||||
License::KeyContainer key_container;
|
||||
key_container.set_type(License::KeyContainer::CONTENT);
|
||||
|
||||
key_control_block::DevicesCanHandleOEMCryptoVersionInKCB(
|
||||
system_id_can_handle_oem_version);
|
||||
|
||||
client_capabilities.set_oem_crypto_api_version(8);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, default_system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
// Should be 'kc09' because the client_capabilities has session token.
|
||||
// k c 0 9
|
||||
// "6b 63 30 39 000000000000000000002008",
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000000002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
client_capabilities.set_oem_crypto_api_version(9);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, default_system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
// Should be 'kc09' because the client_capabilities has session token.
|
||||
// k c 0 9
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000000002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
client_capabilities.set_oem_crypto_api_version(10);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, default_system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
// Should be 'kc09' because the client_capabilities has session token.
|
||||
// k c 0 9
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000000002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
client_capabilities.set_oem_crypto_api_version(10);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING,
|
||||
oem_version_in_kcb_system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
// Should return 'kc10' because this client can handle the version in KCB.
|
||||
// k c 1 0
|
||||
EXPECT_EQ(
|
||||
"6b633130000000000000000000002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
client_capabilities.set_oem_crypto_api_version(11);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, default_system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
// k c 0 9
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000000002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
client_capabilities.set_oem_crypto_api_version(11);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING,
|
||||
oem_version_in_kcb_system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
// k c 1 1
|
||||
EXPECT_EQ(
|
||||
"6b633131000000000000000000002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
// Cannot support version > 99, Generate() should fail.
|
||||
client_capabilities.set_oem_crypto_api_version(999);
|
||||
ASSERT_FALSE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING,
|
||||
oem_version_in_kcb_system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
|
||||
// Return the latest OEM version starting from V13. Make sure V12 still
|
||||
// returns kc09.
|
||||
client_capabilities.set_oem_crypto_api_version(12);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, default_system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
// k c 0 9
|
||||
EXPECT_EQ(
|
||||
"6b633039000000000000000000002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
// Return the latest OEM version starting from V13. Make sure V13 returns
|
||||
// kc13.
|
||||
client_capabilities.set_oem_crypto_api_version(13);
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, default_system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
// k c 1 3
|
||||
EXPECT_EQ(
|
||||
"6b633133000000000000000000002008",
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()));
|
||||
|
||||
for (int i = 0 ; i < 2; i++) {
|
||||
// For v13 OEM version or later, if
|
||||
// 'disable_oem_crypto_api_version_reflection' is set to true, return kc09
|
||||
// (b/38270837).
|
||||
session_init.set_disable_oem_crypto_api_version_reflection(true);
|
||||
client_capabilities.set_oem_crypto_api_version(13);
|
||||
if (i == 1) {
|
||||
// overriding the api version should not change expectation since
|
||||
// disable_oem_crypto_api_version_reflection is true.
|
||||
session_init.set_override_oem_crypto_api_version("kc13");
|
||||
}
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, default_system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
// k c 0 9
|
||||
EXPECT_EQ("6b633039000000000000000000002008",
|
||||
absl::BytesToHexString(
|
||||
key_container.key_control().key_control_block()));
|
||||
}
|
||||
|
||||
client_capabilities.set_oem_crypto_api_version(13);
|
||||
session_init.clear_override_oem_crypto_api_version();
|
||||
std::string expected_kcb;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (i == 0) {
|
||||
session_init.set_disable_oem_crypto_api_version_reflection(true);
|
||||
// Refection is diabled, expect kc09;
|
||||
expected_kcb = "6b633039000000000000000000002008"; // kc09;
|
||||
} else if (i == 1) {
|
||||
session_init.set_disable_oem_crypto_api_version_reflection(false);
|
||||
session_init.set_override_oem_crypto_api_version("kc12");
|
||||
expected_kcb = "6b633132000000000000000000002008"; // kc12
|
||||
} else if (i == 2) {
|
||||
session_init.set_override_oem_crypto_api_version("kc13");
|
||||
expected_kcb = "6b633133000000000000000000002008"; // kc13
|
||||
} else if (i == 3) {
|
||||
session_init.set_override_oem_crypto_api_version("kc14");
|
||||
expected_kcb = "6b633134000000000000000000002008"; // kc14
|
||||
} else if (i == 4) {
|
||||
// override value is larger that supported (length of 4)
|
||||
session_init.set_override_oem_crypto_api_version("123456789");
|
||||
expected_kcb = "31323334000000000000000000002008"; // 1234
|
||||
} else if (i == 5) {
|
||||
// override value is smaller than expected (length of 4)
|
||||
session_init.set_override_oem_crypto_api_version("12");
|
||||
expected_kcb = "3132746c000000000000000000002008"; // 12tl
|
||||
}
|
||||
// Return the OEM version specified in session_init.
|
||||
ASSERT_TRUE(key_control_block::Generate(
|
||||
key_container, duration, nonce, client_capabilities,
|
||||
has_provider_session_token, &session_init, STREAMING, default_system_id,
|
||||
key_container.mutable_key_control()->mutable_key_control_block()));
|
||||
EXPECT_EQ(
|
||||
expected_kcb,
|
||||
absl::BytesToHexString(key_container.key_control().key_control_block()))
|
||||
<< ">> session_init test case: " << i;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace key_control_block
|
||||
} // namespace widevine
|
||||
242
license_server_sdk/internal/parse_content_id.cc
Normal file
242
license_server_sdk/internal/parse_content_id.cc
Normal file
@@ -0,0 +1,242 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 "license_server_sdk/internal/parse_content_id.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
|
||||
#include <cstdint>
|
||||
#include "glog/logging.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "util/endian/endian.h"
|
||||
#include "common/error_space.h"
|
||||
#include "protos/public/errors.pb.h"
|
||||
#include "protos/public/license_server_sdk.pb.h"
|
||||
#include "protos/public/widevine_pssh.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
namespace {
|
||||
|
||||
const uint8_t kWidevineSystemId[] = {0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6,
|
||||
0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc,
|
||||
0xd5, 0x1d, 0x21, 0xed};
|
||||
|
||||
void AddKeyIdIfNotFound(const std::string& key_id,
|
||||
ContentInfo::ContentInfoEntry* entry) {
|
||||
for (int idx = 0; idx < entry->key_ids_size(); ++idx)
|
||||
if (entry->key_ids(idx) == key_id) return;
|
||||
|
||||
entry->add_key_ids(key_id);
|
||||
}
|
||||
|
||||
util::Status AddWidevinePsshInfo(
|
||||
const std::string& pssh_data,
|
||||
ContentInfo::ContentInfoEntry* content_info_entry) {
|
||||
if (pssh_data.empty()) {
|
||||
return util::Status(error_space, INVALID_WIDEVINE_PSSH_DATA,
|
||||
"widevine-pssh-data-is-empty");
|
||||
}
|
||||
|
||||
if (!content_info_entry->mutable_pssh()
|
||||
->mutable_widevine_data()
|
||||
->ParseFromString(pssh_data)) {
|
||||
return util::Status(error_space, INVALID_WIDEVINE_PSSH_DATA,
|
||||
"invalid-widevine-pssh-data");
|
||||
}
|
||||
content_info_entry->mutable_pssh()->set_system_id(
|
||||
std::string(kWidevineSystemId, kWidevineSystemId + sizeof(kWidevineSystemId)));
|
||||
const widevine::WidevinePsshData& wv_pssh =
|
||||
content_info_entry->pssh().widevine_data();
|
||||
for (int idx = 0; idx < wv_pssh.key_ids_size(); ++idx)
|
||||
AddKeyIdIfNotFound(wv_pssh.key_ids(idx), content_info_entry);
|
||||
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status ParseCencId(
|
||||
const LicenseRequest::ContentIdentification& content_id,
|
||||
ContentInfo* content_info) {
|
||||
content_info->set_init_data_type(
|
||||
LicenseRequest::ContentIdentification::InitData::CENC);
|
||||
for (int idx = 0; idx < content_id.cenc_id_deprecated().pssh_size(); ++idx) {
|
||||
util::Status status =
|
||||
AddWidevinePsshInfo(content_id.cenc_id_deprecated().pssh(idx),
|
||||
content_info->add_content_info_entry());
|
||||
if (!status.ok()) return status;
|
||||
}
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status AddWebmKeyId(const std::string& key_id, ContentInfo* content_info) {
|
||||
content_info->set_init_data_type(
|
||||
LicenseRequest::ContentIdentification::InitData::WEBM);
|
||||
content_info->add_content_info_entry()->add_key_ids(key_id);
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status ParseIsoBmffBoxes(const std::string& boxes, ContentInfo* content_info) {
|
||||
const uint32_t kPsshType = 0x70737368;
|
||||
const size_t kPsshSystemIdSize = 16;
|
||||
const size_t kKeyIdSize = 16;
|
||||
const size_t kMinPsshSize = kPsshSystemIdSize + 8;
|
||||
|
||||
if (boxes.empty()) {
|
||||
return util::Status(error_space, INVALID_CENC_INIT_DATA,
|
||||
"init-data-is-empty");
|
||||
}
|
||||
|
||||
const char* r_ptr = boxes.data();
|
||||
const char* end_ptr = r_ptr + boxes.size();
|
||||
|
||||
while (r_ptr < end_ptr) {
|
||||
ContentInfo::ContentInfoEntry content_info_entry;
|
||||
|
||||
if (r_ptr + 8 > end_ptr)
|
||||
return util::Status(error_space, INVALID_CENC_INIT_DATA,
|
||||
"init-data-too-short");
|
||||
|
||||
const char* box_start = r_ptr;
|
||||
uint64_t box_size = BigEndian::Load32(r_ptr);
|
||||
r_ptr += 4;
|
||||
uint32_t box_type = BigEndian::Load32(r_ptr);
|
||||
r_ptr += 4;
|
||||
|
||||
if (box_size == 1) {
|
||||
if (r_ptr + 8 > end_ptr) {
|
||||
return util::Status(error_space, INVALID_CENC_INIT_DATA,
|
||||
"init-data-too-short");
|
||||
}
|
||||
box_size = BigEndian::Load64(r_ptr);
|
||||
r_ptr += 8;
|
||||
}
|
||||
|
||||
const char* box_end = box_start + box_size;
|
||||
if (box_end > end_ptr) {
|
||||
return util::Status(error_space, INVALID_CENC_INIT_DATA,
|
||||
"init-data-too-short");
|
||||
}
|
||||
if (box_end < r_ptr) {
|
||||
return util::Status(error_space, INVALID_CENC_INIT_DATA,
|
||||
"invalid-box-size");
|
||||
}
|
||||
|
||||
if (box_type != kPsshType) {
|
||||
r_ptr = box_end;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r_ptr + kMinPsshSize > box_end)
|
||||
return util::Status(error_space, INVALID_PSSH, "pssh-contents-too-short");
|
||||
|
||||
const uint32_t version_and_flags = BigEndian::Load32(r_ptr);
|
||||
r_ptr += 4;
|
||||
|
||||
const uint8_t version = static_cast<uint8_t>(version_and_flags >> 24);
|
||||
if (version > 1) {
|
||||
return util::Status(error_space, UNSUPPORTED_PSSH_VERSION,
|
||||
absl::StrCat("unsupported-pssh-version ", version));
|
||||
}
|
||||
|
||||
content_info_entry.mutable_pssh()->set_system_id(
|
||||
std::string(r_ptr, r_ptr + kPsshSystemIdSize));
|
||||
bool is_widevine_pssh =
|
||||
!memcmp(r_ptr, kWidevineSystemId, kPsshSystemIdSize);
|
||||
r_ptr += kPsshSystemIdSize;
|
||||
|
||||
if (version == 1) {
|
||||
if (r_ptr + 4 > box_end) {
|
||||
return util::Status(error_space, INVALID_PSSH,
|
||||
"pssh-contents-too-short");
|
||||
}
|
||||
|
||||
const uint32_t num_key_ids = BigEndian::Load32(r_ptr);
|
||||
r_ptr += 4;
|
||||
|
||||
if (r_ptr + (num_key_ids * kKeyIdSize) > box_end) {
|
||||
return util::Status(error_space, INVALID_PSSH,
|
||||
"pssh-contents-too-short");
|
||||
}
|
||||
|
||||
for (uint32_t idx = 0; idx < num_key_ids; ++idx) {
|
||||
AddKeyIdIfNotFound(std::string(r_ptr, r_ptr + kKeyIdSize),
|
||||
&content_info_entry);
|
||||
r_ptr += kKeyIdSize;
|
||||
}
|
||||
}
|
||||
|
||||
if (r_ptr + 4 > box_end)
|
||||
return util::Status(error_space, INVALID_PSSH, "pssh-contents-too-short");
|
||||
|
||||
uint32_t data_size = BigEndian::Load32(r_ptr);
|
||||
r_ptr += 4;
|
||||
if (r_ptr + data_size > box_end)
|
||||
return util::Status(error_space, INVALID_PSSH, "pssh-contents-too-short");
|
||||
if (r_ptr + data_size < box_end)
|
||||
return util::Status(error_space, INVALID_PSSH, "pssh-contents-too-long");
|
||||
|
||||
if (is_widevine_pssh) {
|
||||
util::Status status = AddWidevinePsshInfo(
|
||||
std::string(r_ptr, r_ptr + data_size), &content_info_entry);
|
||||
if (!status.ok()) return status;
|
||||
} else {
|
||||
content_info_entry.mutable_pssh()->set_raw_data(
|
||||
std::string(r_ptr, r_ptr + data_size));
|
||||
}
|
||||
r_ptr += data_size;
|
||||
|
||||
*content_info->add_content_info_entry() = content_info_entry;
|
||||
}
|
||||
|
||||
return util::OkStatus();
|
||||
}
|
||||
|
||||
util::Status ParseInitData(
|
||||
const LicenseRequest::ContentIdentification& content_id,
|
||||
ContentInfo* content_info) {
|
||||
if (!content_id.init_data().has_init_data())
|
||||
return util::Status(error_space, MISSING_INIT_DATA, "missing-init-data");
|
||||
|
||||
if (content_id.init_data().init_data_type() ==
|
||||
LicenseRequest::ContentIdentification::InitData::CENC) {
|
||||
return ParseIsoBmffBoxes(content_id.init_data().init_data(), content_info);
|
||||
}
|
||||
if (content_id.init_data().init_data_type() ==
|
||||
LicenseRequest::ContentIdentification::InitData::WEBM) {
|
||||
return AddWebmKeyId(content_id.init_data().init_data(), content_info);
|
||||
}
|
||||
return util::Status(error_space, UNKNOWN_INIT_DATA_TYPE,
|
||||
"unknown-init-data-type");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
util::Status ParseContentId(
|
||||
const LicenseRequest::ContentIdentification& content_id,
|
||||
ContentInfo* content_info) {
|
||||
DCHECK(content_info);
|
||||
|
||||
content_info->Clear();
|
||||
switch (content_id.content_id_variant_case()) {
|
||||
case LicenseRequest_ContentIdentification::kCencIdDeprecated:
|
||||
return ParseCencId(content_id, content_info);
|
||||
case LicenseRequest_ContentIdentification::kWebmIdDeprecated:
|
||||
return AddWebmKeyId(content_id.webm_id_deprecated().header(),
|
||||
content_info);
|
||||
case LicenseRequest_ContentIdentification::kInitData:
|
||||
return ParseInitData(content_id, content_info);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return util::Status(error_space, INVALID_CONTENT_ID_TYPE,
|
||||
"invalid-content-id-type");
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
29
license_server_sdk/internal/parse_content_id.h
Normal file
29
license_server_sdk/internal/parse_content_id.h
Normal file
@@ -0,0 +1,29 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef LICENSE_SERVER_SDK_INTERNAL_PARSE_CONTENT_ID_H__
|
||||
#define LICENSE_SERVER_SDK_INTERNAL_PARSE_CONTENT_ID_H__
|
||||
|
||||
#include "util/status.h"
|
||||
#include "protos/public/license_protocol.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
class ContentInfo;
|
||||
|
||||
// Parse the ContentIdentification message passed into |content_id| and populate
|
||||
// the ContentInfo message passed into |content_info|. This function deep parses
|
||||
// PSSH boxes and the Widevine PSSH Data. |content_info| may not be NULL and the
|
||||
// caller retains ownership.
|
||||
util::Status ParseContentId(
|
||||
const LicenseRequest::ContentIdentification& content_id,
|
||||
ContentInfo* content_info);
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
#endif // LICENSE_SERVER_SDK_INTERNAL_PARSE_CONTENT_ID_H__
|
||||
302
license_server_sdk/internal/parse_content_id_test.cc
Normal file
302
license_server_sdk/internal/parse_content_id_test.cc
Normal file
@@ -0,0 +1,302 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 "license_server_sdk/internal/parse_content_id.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string>
|
||||
|
||||
#include <cstdint>
|
||||
#include "testing/gunit.h"
|
||||
#include "util/endian/endian.h"
|
||||
#include "common/error_space.h"
|
||||
#include "protos/public/errors.pb.h"
|
||||
#include "protos/public/license_server_sdk.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
namespace {
|
||||
|
||||
const char kExpectedKey1[] = "0123456789abcdef";
|
||||
|
||||
const uint8_t kWvSystemId[] = {0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce,
|
||||
0xa3, 0xc8, 0x27, 0xdc, 0xd5, 0x1d, 0x21, 0xed};
|
||||
|
||||
const uint8_t kWvPsshV0[] = {
|
||||
0x00, 0x00, 0x00, 0x34, // Size of this box = 52.
|
||||
0x70, 0x73, 0x73, 0x68, // Type 'pssh'.
|
||||
0x00, 0x00, 0x00, 0x00, // Version and flags should be 0.
|
||||
0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce, 0xa3, 0xc8,
|
||||
0x27, 0xdc, 0xd5, 0x1d, 0x21, 0xed, // Widevine System ID.
|
||||
0x00, 0x00, 0x00, 0x14, // Size of data = 20.
|
||||
0x08, 0x01, 0x12, 0x10, 0x72, 0x65, 0x5d, 0x53, 0xc9, 0x09,
|
||||
0x57, 0xbf, 0xb0, 0x7b, 0xef, 0xd3, 0xdd, 0x54, 0xa2, 0xbd // Data.
|
||||
};
|
||||
const size_t kWvPsshV0DataOffset = 32;
|
||||
const uint8_t kWvPsshV0KeyId[] = {0x72, 0x65, 0x5d, 0x53, 0xc9, 0x09, 0x57, 0xbf,
|
||||
0xb0, 0x7b, 0xef, 0xd3, 0xdd, 0x54, 0xa2, 0xbd};
|
||||
|
||||
const uint8_t kInvalidWvPsshV0[] = {
|
||||
0x00, 0x00, 0x00, 0x24, // Size of this box = 36.
|
||||
0x70, 0x73, 0x73, 0x68, // Type 'pssh'.
|
||||
0x00, 0x00, 0x00, 0x00, // Version and flags should be 0.
|
||||
0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce,
|
||||
0xa3, 0xc8, 0x27, 0xdc, 0xd5, 0x1d, 0x21, 0xed, // Widevine System ID.
|
||||
0x00, 0x00, 0x00, 0x04, // Size of data = 4.
|
||||
0x08, 0x01, 0x12, 0x10};
|
||||
|
||||
const uint8_t kWvPsshV1[] = {
|
||||
0x00, 0x00, 0x00, 0x58, // Size of this box = 88.
|
||||
0x70, 0x73, 0x73, 0x68, // Type 0x70737368 == 'pssh'.
|
||||
0x01, 0x00, 0x00, 0x00, // PSSH Version 1.
|
||||
0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce, //
|
||||
0xa3, 0xc8, 0x27, 0xdc, 0xd5, 0x1d, 0x21, 0xed, // Widevine System ID.
|
||||
0x00, 0x00, 0x00, 0x02, // 2 Key IDs.
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // KeyID 1.
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, //
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // KeyID 2.
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, //
|
||||
0x00, 0x00, 0x00, 0x14, // Size of data = 20.
|
||||
0x08, 0x01, 0x12, 0x10, 0x72, 0x65, 0x5d, 0x53, 0xc9, 0x09, 0x57, // Data.
|
||||
0xbf, 0xb0, 0x7b, 0xef, 0xd3, 0xdd, 0x54, 0xa2, 0xbd};
|
||||
const size_t kWvPsshV1BoxSizeOffset = 0;
|
||||
const size_t kWvPsshV1BoxVersionOffset = 8;
|
||||
const size_t kWvPsshV1NumKeyIdsOffset = 28;
|
||||
const size_t kWvPsshV1DataSizeOffset = 64;
|
||||
const size_t kWvPsshV1DataOffset = 68;
|
||||
|
||||
const uint8_t kNonWvPsshV1[] = {
|
||||
0x00, 0x00, 0x00, 0x48, // Size of this box = 72.
|
||||
0x70, 0x73, 0x73, 0x68, // Type 0x70737368 == 'pssh'.
|
||||
0x01, 0x00, 0x00, 0x00, // PSSH Version 1.
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // System ID.
|
||||
0x00, 0x00, 0x00, 0x02, // 2 Key IDs.
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // KeyID 1.
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // KeyID 2.
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x04, // Size of data = 4.
|
||||
0x00, 0x01, 0x1a, 0x10 // Data.
|
||||
};
|
||||
|
||||
const uint8_t kNonPsshBox[] = {
|
||||
0x00, 0x00, 0x00, 0x08, // Size of this box = 8.
|
||||
0x70, 0x73, 0x73, 0x74, // Type 0x70737368 == 'psst'.
|
||||
};
|
||||
|
||||
void MakeDeprecatedWebmContentId(
|
||||
const std::string& key_id, LicenseRequest::ContentIdentification* content_id) {
|
||||
content_id->Clear();
|
||||
content_id->mutable_webm_id_deprecated()->set_header(key_id);
|
||||
}
|
||||
|
||||
void MakeInitDataWebmContentId(
|
||||
const std::string& key_id, LicenseRequest::ContentIdentification* content_id) {
|
||||
content_id->Clear();
|
||||
content_id->mutable_init_data()->set_init_data_type(
|
||||
LicenseRequest::ContentIdentification::InitData::WEBM);
|
||||
content_id->mutable_init_data()->set_init_data(key_id);
|
||||
}
|
||||
|
||||
void VerifyWebmContentId(
|
||||
const LicenseRequest::ContentIdentification& content_id) {
|
||||
ContentInfo content_info;
|
||||
ASSERT_EQ(util::OkStatus(), ParseContentId(content_id, &content_info));
|
||||
ASSERT_EQ(LicenseRequest::ContentIdentification::InitData::WEBM,
|
||||
content_info.init_data_type());
|
||||
ASSERT_EQ(1, content_info.content_info_entry_size());
|
||||
ASSERT_EQ(1, content_info.content_info_entry(0).key_ids_size());
|
||||
ASSERT_EQ(kExpectedKey1, content_info.content_info_entry(0).key_ids(0));
|
||||
ASSERT_FALSE(content_info.content_info_entry(0).has_pssh());
|
||||
}
|
||||
|
||||
void MakeDeprecatedCencContentId(
|
||||
const std::string& pssh_data,
|
||||
LicenseRequest::ContentIdentification* content_id) {
|
||||
content_id->Clear();
|
||||
*content_id->mutable_cenc_id_deprecated()->add_pssh() = pssh_data;
|
||||
}
|
||||
|
||||
void MakeInitDataCencContentId(
|
||||
const std::string& pssh, LicenseRequest::ContentIdentification* content_id) {
|
||||
content_id->Clear();
|
||||
content_id->mutable_init_data()->set_init_data_type(
|
||||
LicenseRequest::ContentIdentification::InitData::CENC);
|
||||
content_id->mutable_init_data()->set_init_data(pssh);
|
||||
}
|
||||
|
||||
void MakeExistingLicenseContentId(
|
||||
LicenseRequest::ContentIdentification* content_id) {
|
||||
content_id->Clear();
|
||||
content_id->mutable_existing_license();
|
||||
}
|
||||
|
||||
void VerifyCencContentId(
|
||||
const LicenseRequest::ContentIdentification& content_id) {
|
||||
ContentInfo content_info;
|
||||
ASSERT_EQ(util::OkStatus(), ParseContentId(content_id, &content_info));
|
||||
ASSERT_EQ(LicenseRequest::ContentIdentification::InitData::CENC,
|
||||
content_info.init_data_type());
|
||||
ASSERT_EQ(1, content_info.content_info_entry_size());
|
||||
ASSERT_EQ(1, content_info.content_info_entry(0).key_ids_size());
|
||||
ASSERT_TRUE(content_info.content_info_entry(0).has_pssh());
|
||||
ASSERT_EQ(std::string(kWvSystemId, kWvSystemId + sizeof(kWvSystemId)),
|
||||
content_info.content_info_entry(0).pssh().system_id());
|
||||
ASSERT_TRUE(content_info.content_info_entry(0).pssh().has_widevine_data());
|
||||
ASSERT_FALSE(content_info.content_info_entry(0).pssh().has_raw_data());
|
||||
}
|
||||
|
||||
void MakeModifiedV1WvCencContentId(
|
||||
size_t modification_offset, uint32_t modification_value,
|
||||
LicenseRequest::ContentIdentification* content_id) {
|
||||
std::string box_data(kWvPsshV1, kWvPsshV1 + sizeof(kWvPsshV1));
|
||||
BigEndian::Store32(&box_data[modification_offset], modification_value);
|
||||
MakeInitDataCencContentId(box_data, content_id);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(ParseContentIdTest, DeprecatedWebmContentId) {
|
||||
LicenseRequest::ContentIdentification content_id;
|
||||
MakeDeprecatedWebmContentId(kExpectedKey1, &content_id);
|
||||
ASSERT_NO_FATAL_FAILURE(VerifyWebmContentId(content_id));
|
||||
}
|
||||
|
||||
TEST(ParseContentIdTest, InitDataWebmContentId) {
|
||||
LicenseRequest::ContentIdentification content_id;
|
||||
MakeInitDataWebmContentId(kExpectedKey1, &content_id);
|
||||
ASSERT_NO_FATAL_FAILURE(VerifyWebmContentId(content_id));
|
||||
}
|
||||
|
||||
TEST(ParseContentIdTest, DeprecatedCencContentId) {
|
||||
LicenseRequest::ContentIdentification content_id;
|
||||
MakeDeprecatedCencContentId(
|
||||
std::string(kWvPsshV0 + kWvPsshV0DataOffset, kWvPsshV0 + sizeof(kWvPsshV0)),
|
||||
&content_id);
|
||||
ASSERT_NO_FATAL_FAILURE(VerifyCencContentId(content_id));
|
||||
}
|
||||
|
||||
TEST(ParseContentIdTest, InitDataCencContentId) {
|
||||
LicenseRequest::ContentIdentification content_id;
|
||||
MakeInitDataCencContentId(std::string(kWvPsshV0, kWvPsshV0 + sizeof(kWvPsshV0)),
|
||||
&content_id);
|
||||
ASSERT_NO_FATAL_FAILURE(VerifyCencContentId(content_id));
|
||||
}
|
||||
|
||||
TEST(ParseContentIdTest, PsshV1) {
|
||||
LicenseRequest::ContentIdentification content_id;
|
||||
MakeInitDataCencContentId(std::string(kWvPsshV1, kWvPsshV1 + sizeof(kWvPsshV1)),
|
||||
&content_id);
|
||||
ContentInfo content_info;
|
||||
EXPECT_EQ(util::OkStatus(), ParseContentId(content_id, &content_info));
|
||||
EXPECT_EQ(LicenseRequest::ContentIdentification::InitData::CENC,
|
||||
content_info.init_data_type());
|
||||
ASSERT_EQ(1, content_info.content_info_entry_size());
|
||||
ASSERT_EQ(3, content_info.content_info_entry(0).key_ids_size());
|
||||
EXPECT_TRUE(content_info.content_info_entry(0).has_pssh());
|
||||
EXPECT_EQ(std::string(kWvSystemId, kWvSystemId + sizeof(kWvSystemId)),
|
||||
content_info.content_info_entry(0).pssh().system_id());
|
||||
EXPECT_TRUE(content_info.content_info_entry(0).pssh().has_widevine_data());
|
||||
EXPECT_FALSE(content_info.content_info_entry(0).pssh().has_raw_data());
|
||||
}
|
||||
|
||||
TEST(ParseContentIdTest, ExistingLicense) {
|
||||
LicenseRequest::ContentIdentification content_id;
|
||||
ContentInfo content_info;
|
||||
MakeExistingLicenseContentId(&content_id);
|
||||
EXPECT_EQ(util::Status(error_space, INVALID_CONTENT_ID_TYPE,
|
||||
"invalid-content-id-type"),
|
||||
ParseContentId(content_id, &content_info));
|
||||
}
|
||||
|
||||
TEST(ParseContentIdTest, MultipleBoxes) {
|
||||
LicenseRequest::ContentIdentification content_id;
|
||||
MakeInitDataCencContentId(
|
||||
std::string(kNonPsshBox, kNonPsshBox + sizeof(kNonPsshBox)) +
|
||||
std::string(kWvPsshV0, kWvPsshV0 + sizeof(kWvPsshV0)) +
|
||||
std::string(kNonPsshBox, kNonPsshBox + sizeof(kNonPsshBox)) +
|
||||
std::string(kWvPsshV1, kWvPsshV1 + sizeof(kWvPsshV1)) +
|
||||
std::string(kNonPsshBox, kNonPsshBox + sizeof(kNonPsshBox)) +
|
||||
std::string(kNonWvPsshV1, kNonWvPsshV1 + sizeof(kNonWvPsshV1)) +
|
||||
std::string(kNonPsshBox, kNonPsshBox + sizeof(kNonPsshBox)),
|
||||
&content_id);
|
||||
ContentInfo content_info;
|
||||
EXPECT_EQ(util::OkStatus(), ParseContentId(content_id, &content_info));
|
||||
EXPECT_EQ(LicenseRequest::ContentIdentification::InitData::CENC,
|
||||
content_info.init_data_type());
|
||||
EXPECT_EQ(3, content_info.content_info_entry_size());
|
||||
}
|
||||
|
||||
TEST(ParseContentIdTest, BoxSizeTooLarge) {
|
||||
LicenseRequest::ContentIdentification content_id;
|
||||
MakeModifiedV1WvCencContentId(kWvPsshV1BoxSizeOffset, 89, &content_id);
|
||||
ContentInfo content_info;
|
||||
EXPECT_EQ("Errors::INVALID_CENC_INIT_DATA: init-data-too-short",
|
||||
ParseContentId(content_id, &content_info).ToString());
|
||||
}
|
||||
|
||||
TEST(ParseContentIdTest, BoxSizeTooSmall) {
|
||||
LicenseRequest::ContentIdentification content_id;
|
||||
MakeModifiedV1WvCencContentId(kWvPsshV1BoxSizeOffset, 87, &content_id);
|
||||
ContentInfo content_info;
|
||||
EXPECT_EQ("Errors::INVALID_PSSH: pssh-contents-too-short",
|
||||
ParseContentId(content_id, &content_info).ToString());
|
||||
}
|
||||
|
||||
TEST(ParseContentIdTest, UnsupportedPsshVersion) {
|
||||
LicenseRequest::ContentIdentification content_id;
|
||||
MakeModifiedV1WvCencContentId(kWvPsshV1BoxVersionOffset, 0x02000000,
|
||||
&content_id);
|
||||
ContentInfo content_info;
|
||||
EXPECT_EQ("Errors::UNSUPPORTED_PSSH_VERSION: unsupported-pssh-version 2",
|
||||
ParseContentId(content_id, &content_info).ToString());
|
||||
}
|
||||
|
||||
TEST(ParseContentIdTest, TooManyKeyIds) {
|
||||
LicenseRequest::ContentIdentification content_id;
|
||||
MakeModifiedV1WvCencContentId(kWvPsshV1NumKeyIdsOffset, 3, &content_id);
|
||||
ContentInfo content_info;
|
||||
EXPECT_EQ("Errors::INVALID_PSSH: pssh-contents-too-short",
|
||||
ParseContentId(content_id, &content_info).ToString());
|
||||
}
|
||||
|
||||
TEST(ParseContentIdTest, TooFewKeyIds) {
|
||||
LicenseRequest::ContentIdentification content_id;
|
||||
MakeModifiedV1WvCencContentId(kWvPsshV1NumKeyIdsOffset, 1, &content_id);
|
||||
ContentInfo content_info;
|
||||
EXPECT_EQ("Errors::INVALID_PSSH: pssh-contents-too-long",
|
||||
ParseContentId(content_id, &content_info).ToString());
|
||||
}
|
||||
|
||||
TEST(ParseContentIdTest, DataSizeTooSmall) {
|
||||
LicenseRequest::ContentIdentification content_id;
|
||||
MakeModifiedV1WvCencContentId(kWvPsshV1DataSizeOffset, 19, &content_id);
|
||||
ContentInfo content_info;
|
||||
EXPECT_EQ("Errors::INVALID_PSSH: pssh-contents-too-long",
|
||||
ParseContentId(content_id, &content_info).ToString());
|
||||
}
|
||||
|
||||
TEST(ParseContentIdTest, DataSizeTooLarge) {
|
||||
LicenseRequest::ContentIdentification content_id;
|
||||
MakeModifiedV1WvCencContentId(kWvPsshV1DataSizeOffset, 21, &content_id);
|
||||
ContentInfo content_info;
|
||||
EXPECT_EQ("Errors::INVALID_PSSH: pssh-contents-too-short",
|
||||
ParseContentId(content_id, &content_info).ToString());
|
||||
}
|
||||
|
||||
TEST(ParseContentIdTest, InvalidWidevinePsshData) {
|
||||
LicenseRequest::ContentIdentification content_id;
|
||||
MakeInitDataCencContentId(
|
||||
std::string(kInvalidWvPsshV0, kInvalidWvPsshV0 + sizeof(kInvalidWvPsshV0)),
|
||||
&content_id);
|
||||
ContentInfo content_info;
|
||||
EXPECT_EQ("Errors::INVALID_WIDEVINE_PSSH_DATA: invalid-widevine-pssh-data",
|
||||
ParseContentId(content_id, &content_info).ToString());
|
||||
}
|
||||
} // namespace widevine
|
||||
1151
license_server_sdk/internal/session_impl.cc
Normal file
1151
license_server_sdk/internal/session_impl.cc
Normal file
File diff suppressed because it is too large
Load Diff
377
license_server_sdk/internal/session_impl.h
Normal file
377
license_server_sdk/internal/session_impl.h
Normal file
@@ -0,0 +1,377 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef LICENSE_SERVER_SDK_INTERNAL_SESSION_IMPL_H_
|
||||
#define LICENSE_SERVER_SDK_INTERNAL_SESSION_IMPL_H_
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <cstdint>
|
||||
#include "base/macros.h"
|
||||
#include "util/status.h"
|
||||
#include "common/certificate_type.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "protos/public/client_identification.pb.h"
|
||||
#include "protos/public/license_protocol.pb.h"
|
||||
#include "protos/public/license_server_sdk.pb.h"
|
||||
#include "protos/public/provisioned_device_info.pb.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
class ClientCert;
|
||||
class ClientIdentification;
|
||||
class ContentInfo;
|
||||
class SessionInit;
|
||||
class SessionState;
|
||||
class SessionTest;
|
||||
class SessionUsage;
|
||||
|
||||
const uint32_t kMajorVersion = 4;
|
||||
const uint32_t kMinorVersion = 5;
|
||||
const uint32_t kRelease = 1;
|
||||
|
||||
const uint32_t kMasterSigningKeySizeBytes = 16;
|
||||
const uint32_t kSigningKeySizeBytes = 64;
|
||||
const uint32_t kProviderSessionTokenSizeBytes = 255;
|
||||
|
||||
// Helper function to determine which provider client token to use.
|
||||
std::string GetProviderClientToken(const SessionInit& session_init,
|
||||
const ClientIdentification& client_id);
|
||||
|
||||
// A Session represents a lifetime for the request/response between the DRM
|
||||
// client and the DRM server. During this lifetime, referred to as a DRM
|
||||
// session, some security secrets are established. These secrets are used
|
||||
// to wrap a license from the DRM server. A DRM session is used for each
|
||||
// license request. Example usage:
|
||||
// Session::SetPreProvisioningKeys(<hex formatted std::string>);
|
||||
// Session::SetCertificateStatusList(
|
||||
// widevine::sdk::kCertificateTypeProduction,
|
||||
// cert_status_list,
|
||||
// expiration_period_seconds,
|
||||
// /* allow unknown device */ false);
|
||||
// Session::AddDrmServiceCertificate(
|
||||
// widevine::sdk::kCertificateTypeProduction,
|
||||
// service_certificate,
|
||||
// service_private_key,
|
||||
// service_private_key_passphrase);
|
||||
// std::string signed_license_request;
|
||||
// // assign signed_license_request to incoming license request.
|
||||
// Session* session = NULL;
|
||||
// util::Status status = Session::Create(signed_license_request, &session);
|
||||
// if (!status.ok()) {
|
||||
// std::string error_license;
|
||||
// if (Session::GenerateErrorResponse(status, &error_license)) {
|
||||
// // Send error_license to the client.
|
||||
// } else {
|
||||
// // Handle error
|
||||
// }
|
||||
// return ...
|
||||
// }
|
||||
// License::Policy policies;
|
||||
// License::KeyContainer key_container;
|
||||
// // ... evaluate request().content_id and request().type to determine
|
||||
// // ... what License::Policy and License::KeyContainer objects
|
||||
// // ... are needed to fulfill this client request.
|
||||
// SessionInit init; // Required if |policies| contains |can_renew| true.
|
||||
// SessionState cache; // Required if caching state.
|
||||
// std::string signed_license;
|
||||
// Status status = session->GenerateSignedLicense(&policies,
|
||||
// &key_container,
|
||||
// &init,
|
||||
// &cache,
|
||||
// &signed_license);
|
||||
// // For renewal, either SessionState may be cached and provided to the call
|
||||
// // to GenerateSignedLicense. Or if it is not possible to cache the
|
||||
// // SessionState, then SessionInit must be provided with either
|
||||
// // |signing_key| or |master_signing_key|, in order to allow the sdk to
|
||||
// // validate the signature of the renewal request, and to sign the
|
||||
// // license issued.
|
||||
|
||||
class SessionImpl {
|
||||
public:
|
||||
// Set pre-provisioning keys system-wide. Map key is system_id, value.
|
||||
// Value should be human-readable hex digits suitable for passing to
|
||||
// absl::HexStringToBytes().
|
||||
// Must be called before any other calls to this class. Calls are
|
||||
// thread-safe, so the keys can be updated at any time.
|
||||
static void SetPreProvisioningKeys(const std::map<uint32_t, std::string>& keys);
|
||||
static void SetPreProvisioningKeys(const std::multimap<uint32_t, std::string>& keys);
|
||||
|
||||
// Set the certificate status list system-wide. |cert_type| specifies
|
||||
// whether to use development or production root certificates.
|
||||
// |expiration_period| is the number of seconds until the
|
||||
// certificate_status_list expires after its creation time
|
||||
// (creation_time_seconds). If |allow_unknown_devices| is false, an error is
|
||||
// returned if the device does not appear in the certificate_status_list.
|
||||
static util::Status SetCertificateStatusList(
|
||||
CertificateType cert_type, const std::string& certificate_status_list,
|
||||
uint32_t expiration_period_seconds, bool allow_unknown_devices);
|
||||
|
||||
// Add a service certificate system-wide. |cert_type| indicates the type of
|
||||
// root certificate used to sign the service certificate;
|
||||
// |service_certificate| is a Google-generated certificate used to
|
||||
// authenticate the service provider for purposes of device privacy;
|
||||
// |service_private_key| is the encrypted PKCS#8 private RSA key corresponding
|
||||
// to the service certificate; and |service_private_key_passphrase| is the
|
||||
// password required to decrypt |service_private_key|.
|
||||
static util::Status AddDrmServiceCertificate(
|
||||
CertificateType cert_type, const std::string& service_certificate,
|
||||
const std::string& service_private_key,
|
||||
const std::string& service_private_key_passphrase);
|
||||
|
||||
// Enable delivery of licenses to client devices. This includes devices with
|
||||
// TEST_ONLY status, and development platform verification certificates.
|
||||
// Defaults to false.
|
||||
static void AllowDevelopmentClients(bool enable);
|
||||
|
||||
// Enable delivery of licenses to revoked client devices. |system_id_list| is
|
||||
// a comma separated list of systems Ids to allow even if the device is in the
|
||||
// revoked state.
|
||||
static void AllowRevokedDevices(const std::string& system_id_list);
|
||||
|
||||
// Creates a Session object.
|
||||
// |signed_license_request| is the serialized SignedMessage received from the
|
||||
// client. |session| points to a Session*, which must be initialized to NULL
|
||||
// on entry, but |session| itself may not be NULL. The new Session object will
|
||||
// be owned by the caller. This method returns util::Status::OK if successful,
|
||||
// or an appropriate error status, in which case
|
||||
// Session::GenerateErrorResponse should be invoked.
|
||||
// Example usage:
|
||||
// Session* session = NULL;
|
||||
// util::Status status = Session::Create(request_from_client, &session);
|
||||
// if (!status.ok()) {
|
||||
// std::string error_license;
|
||||
// if (Session::GenerateErrorResponse(status, &error_license)) {
|
||||
// // Send error_license to the client.
|
||||
// } else {
|
||||
// // Handle error
|
||||
// }
|
||||
// return ...
|
||||
// }
|
||||
// // Create license, invoke GenerateSignedLicense, etc.
|
||||
static util::Status Create(const std::string& signed_license_request,
|
||||
SessionImpl** session);
|
||||
|
||||
// Variation of Session::Create which also fills in the parsed LicenseRequest,
|
||||
// for use in logging or debugging.
|
||||
static util::Status Create(const std::string& signed_license_request,
|
||||
SessionImpl** session,
|
||||
LicenseRequest* parsed_request_out);
|
||||
|
||||
// Same as Create(), but caller can specify the ClientIdentification
|
||||
// message and/or PlatformVerificationStatus. If ClientIdentification is
|
||||
// specified, this variation of Create() will use the specified |client_id|
|
||||
// instead of what is specified in |signed_license_request|. If
|
||||
// PlatformVerificationStatus is specified, this method will use the specified
|
||||
// |platform_verification_status| instead of attempting to determine it by
|
||||
// calling SessionImpl::VerifyPlatform(...).
|
||||
// Background for this function is to support cases where the client
|
||||
// identification is encrypted with the provider's service certificate in
|
||||
// which case we won't be able to decrypt OR when the provider determines
|
||||
// platform verification. The provider will specify the
|
||||
// clear client identification in |client_id| and the platform verification
|
||||
// in |platform_verification_status|.
|
||||
static util::Status CreateForProxy(
|
||||
const std::string& signed_license_request,
|
||||
const PlatformVerificationStatus platform_verification_status,
|
||||
const ClientIdentification* client_id, SessionImpl** session,
|
||||
LicenseRequest* parsed_request_out);
|
||||
|
||||
// Generates a SignedMessage containing a message generated in response to
|
||||
// an error condition. |status| is a previous error status returned by the
|
||||
// Session or util::Status(util::error::UNAVAILABLE, ...) to indicate that the
|
||||
// backend is unavailable, |signed_message| points to a std::string to contain the
|
||||
// serialized SignedMessage, and may not be NULL. This method returns true if
|
||||
// there is an error license to be sent to the client, or false otherwise.
|
||||
// Example usage in the Session::Create comments above.
|
||||
static bool GenerateErrorResponse(const util::Status& status,
|
||||
std::string* signed_message_bytes);
|
||||
|
||||
// DeriveKey uses the NIST 800-108 KDF recommendation, using AES-CMAC PRF.
|
||||
// NIST 800-108:
|
||||
// http://csrc.nist.gov/publications/nistpubs/800-108/sp800-108.pdf
|
||||
// AES-CMAC:
|
||||
// http://tools.ietf.org/html/rfc4493
|
||||
static std::string DeriveKey(const std::string& key, const std::string& label,
|
||||
const std::string& context, const uint32_t size_bits);
|
||||
|
||||
// Returns a std::string containing the hex-encoded SHA-256 digest of the root
|
||||
// certificate specified by |cert_type|. Used for purposes of root certificate
|
||||
// verification.
|
||||
static std::string GetRootCertificateDigest(CertificateType cert_type);
|
||||
|
||||
// Returns a std::string containing the Widevine License Server SDK version in the
|
||||
// form <major_version>.<minor_version>.<release> <build date> <build time> .
|
||||
static std::string GetSdkVersionString();
|
||||
|
||||
// Returns true if service certificate is loaded.
|
||||
static bool is_service_certificate_loaded() {
|
||||
return is_service_certificate_loaded_;
|
||||
}
|
||||
|
||||
static const char* kEncryptionKeyLabel; // NOLINT
|
||||
static const uint32_t kEncryptionKeySizeBits;
|
||||
static const char* kSigningKeyLabel; // NOLINT
|
||||
static const uint32_t kSigningKeySizeBits;
|
||||
|
||||
virtual ~SessionImpl();
|
||||
virtual const LicenseRequest& request() const { return *license_request_; }
|
||||
virtual const std::string& GetSessionId();
|
||||
|
||||
// Returns true if a provisioned device info exists. Caller
|
||||
// owns |provisioned_device_info| and it must not be null.
|
||||
virtual bool GetProvisionedDeviceInfo(
|
||||
widevine::ProvisionedDeviceInfo* device_info);
|
||||
|
||||
// Returns true if client info exists, otherwise returns false. Populate the
|
||||
// specified |client_info| structure.
|
||||
virtual bool GetClientInfo(ClientIdentification* client_info) const;
|
||||
|
||||
// Accessor for request_id field which may be encoded in one of multiple
|
||||
// places in the liciense request protcol buffer. Use this method instead
|
||||
// of accessing directly. |request_id| is a pointer to a std::string to contain
|
||||
// the request ID upon successful return.
|
||||
virtual util::Status GetRequestId(std::string* request_id) const;
|
||||
|
||||
// Accessor for license_type field which may be encoded in one of multiple
|
||||
// places in the license request protcol buffer. Use this method instead
|
||||
// of accessing directly. |license_type| is a pointer to a value to contain
|
||||
// the license type upon successful return.
|
||||
virtual util::Status GetLicenseType(LicenseType* license_type) const;
|
||||
|
||||
// Method used to get ContentIdentification in a consistent message regardless
|
||||
// of the type or version of initialization data contained in the content_id
|
||||
// field of the license request. Use this method instead of accessing the
|
||||
// fields of ContentIdentification directly. |content_info| is a pointer to a
|
||||
// message to contain the parsed values from content_id upon successful
|
||||
// return.
|
||||
virtual util::Status GetContentInfo(ContentInfo* content_info) const;
|
||||
|
||||
// Returns the serial number of certificate associated with this device and
|
||||
// content provider.
|
||||
virtual std::string GetDrmDeviceId() const;
|
||||
|
||||
// Copies the session usage table from license request to |usage_report|.
|
||||
// Returns true if session usage exist in the license request, otherwise
|
||||
// returns false.
|
||||
bool GetSessionUsage(SessionUsage* usage_report) const;
|
||||
|
||||
// Generates a serialized signed License response, emptying |policy| and
|
||||
// |key_container|, encrypting the keys therein. |session_init| and
|
||||
// |session_state| are returned to be cached and provided in subsequent
|
||||
// calls to the function. If no additional PolicyItem or KeyContainer objects
|
||||
// are necessary to fulfill the request (such as the case with license
|
||||
// renewal), |policy| and/or |key_container| may be NULL.
|
||||
// The response is expected to be sent to the Widevine CDM.
|
||||
virtual util::Status GenerateSignedLicense(
|
||||
/*IN*/ const License::Policy* policy,
|
||||
/*IN*/ const std::list<License::KeyContainer>* key_container,
|
||||
/*IN*/ const SessionInit* session_init,
|
||||
/*INOUT*/ SessionState* session_state,
|
||||
/*OUT*/ std::string* signed_message_bytes);
|
||||
|
||||
// Verify the required |output_protection| can be satisified based on the
|
||||
// |device_capabilities| specified by the client.
|
||||
virtual util::Status VerifyDeviceCapabilities(
|
||||
const ClientIdentification::ClientCapabilities& device_capabilities,
|
||||
const License::KeyContainer::OutputProtection& output_protection) const;
|
||||
|
||||
|
||||
virtual PlatformVerificationStatus GetPlatformVerificationStatus() const;
|
||||
|
||||
// Returns the service id of the provider that owns the device certificate.
|
||||
virtual std::string GetDrmDeviceServiceId() const;
|
||||
|
||||
// Returns true if the license request contained a key control nonce, else
|
||||
// false.
|
||||
virtual bool HasKeyControlNonce() const { return has_key_control_nonce_; }
|
||||
|
||||
protected:
|
||||
friend class Session;
|
||||
PlatformVerificationStatus platform_verification_status_ =
|
||||
PLATFORM_NO_VERIFICATION;
|
||||
|
||||
// For testing only. This allows unit tests to define a mock Session class.
|
||||
SessionImpl();
|
||||
|
||||
private:
|
||||
friend class SessionTest;
|
||||
|
||||
// Takes ownership of |message|, |request|, |client_cert| and |device_info|.
|
||||
SessionImpl(SignedMessage* message, LicenseRequest* request, bool has_nonce,
|
||||
uint32_t nonce, widevine::ProvisionedDeviceInfo* device_info);
|
||||
|
||||
// Called by the SessionImpl::Create factory to initialize a new session.
|
||||
util::Status Init();
|
||||
util::Status GenerateNewLicenseInfo(/*IN*/ const SessionInit* session_init,
|
||||
/*OUT*/ LicenseIdentification* new_id,
|
||||
/*OUT*/ std::string* renewal_signing_key,
|
||||
/*OUT*/ std::string* signing_key);
|
||||
util::Status GeneratePriorLicenseInfo(
|
||||
/*IN*/ const SessionInit* session_init,
|
||||
/*INOUT*/ SessionState* session_state,
|
||||
/*OUT*/ LicenseIdentification* new_id,
|
||||
/*OUT*/ std::string* signing_key);
|
||||
void SetSessionKey(const std::string& session_key);
|
||||
|
||||
// Verifies remote attestation in the license request and sets
|
||||
// |platform_verification_status_|.
|
||||
virtual util::Status VerifyRemoteAttestation();
|
||||
// Return true if the provider session token should get included in the
|
||||
// license response.
|
||||
static bool ShouldSetProviderSessionToken(
|
||||
bool pst_provided, bool client_supports_pst,
|
||||
ProvisionedDeviceInfo::WvSecurityLevel level, uint32_t oem_crypto_version);
|
||||
|
||||
// Verifies the platform by platform-specific means such as Remote Attestion
|
||||
// or host code verification. Only meaningful for Widevine Level 2-3, as
|
||||
// Level 1 devices are verified by default.
|
||||
virtual util::Status VerifyPlatform();
|
||||
|
||||
// Extracts the nonce from |request| and populates |key_control_nonce|. Sets
|
||||
// |key_control_nonce| to true if the nonce is found.
|
||||
static util::Status LoadKeyControlNonce(const LicenseRequest& request,
|
||||
bool* has_key_control_nonce,
|
||||
uint32_t* key_control_nonce);
|
||||
|
||||
// Validates required fields are set in the license |request|.
|
||||
static util::Status CheckLicenseRequestFields(const LicenseRequest& request);
|
||||
|
||||
// De-serialize the license request from the |signed_license_request|
|
||||
// into |license_request| and |signed_message|.
|
||||
static util::Status ParseLicenseRequestFromString(
|
||||
const std::string& signed_license_request, SignedMessage* signed_message,
|
||||
LicenseRequest* license_request);
|
||||
|
||||
// Validates the Provider Session Token from |pst_src|.
|
||||
static util::Status CheckProviderSessionToken(const std::string& pst_src);
|
||||
|
||||
std::unique_ptr<SignedMessage> signed_message_;
|
||||
std::unique_ptr<LicenseRequest> license_request_;
|
||||
// Client cert object used for all crypto operations.
|
||||
std::unique_ptr<ClientCert> client_cert_;
|
||||
std::unique_ptr<ClientIdentification> client_identification_;
|
||||
std::string session_key_;
|
||||
std::string session_id_;
|
||||
absl::Mutex session_id_lock_;
|
||||
bool has_key_control_nonce_ = false;
|
||||
static bool is_service_certificate_loaded_;
|
||||
uint32_t key_control_nonce_;
|
||||
std::unique_ptr<ProvisionedDeviceInfo> provisioned_device_info_;
|
||||
std::string remote_attestation_cert_serial_number_;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(SessionImpl);
|
||||
};
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
#endif // LICENSE_SERVER_SDK_INTERNAL_SESSION_IMPL_H_
|
||||
2888
license_server_sdk/internal/session_impl_test.cc
Normal file
2888
license_server_sdk/internal/session_impl_test.cc
Normal file
File diff suppressed because it is too large
Load Diff
53
license_server_sdk/internal/session_usage_report.h
Normal file
53
license_server_sdk/internal/session_usage_report.h
Normal file
@@ -0,0 +1,53 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef LICENSE_SERVER_SDK_INTERNAL_SESSION_USAGE_REPORT_H_
|
||||
#define LICENSE_SERVER_SDK_INTERNAL_SESSION_USAGE_REPORT_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace widevine {
|
||||
|
||||
const uint32_t kMaxProviderSessionTokenSizeBytes = 128;
|
||||
|
||||
enum SessionUsageStatus {
|
||||
SessionUsageStatus_Unused = 0,
|
||||
// License still active on the device, has not been released yet.
|
||||
SessionUsageStatus_Active = 1,
|
||||
// Deprecated in OEMCrypto V13, replaced with Inactive_Used and
|
||||
// Inactive_Unused. All Inactive status values indicate the license was
|
||||
// relased.
|
||||
SessionUsageStatus_Inactive_Deprecated = 2,
|
||||
// Keys released after use.
|
||||
SessionUsageStatus_Inactive_Used = 3,
|
||||
// Keys released before use.
|
||||
SessionUsageStatus_Inactive_Unused = 4,
|
||||
};
|
||||
|
||||
// Data sent in the license release request from the client to indicate the
|
||||
// license usage.
|
||||
struct InternalSessionUsageReport {
|
||||
// HMAC SHA1 of the rest of the report.
|
||||
uint8_t signature[20];
|
||||
// Current status of status report: 0=unused, 1=active, 2=inactive.
|
||||
uint8_t status;
|
||||
// The clock security level are: 0=insecure clock, 1=secure timer,
|
||||
// 2=secure clock.
|
||||
uint8_t clock_security_level;
|
||||
uint8_t pst_length;
|
||||
// Make int64_t's word aligned.
|
||||
uint8_t padding;
|
||||
int64_t seconds_since_license_received;
|
||||
int64_t seconds_since_first_decrypt;
|
||||
int64_t seconds_since_last_decrypt;
|
||||
uint8_t pst[kMaxProviderSessionTokenSizeBytes];
|
||||
} ABSL_ATTRIBUTE_PACKED;
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
#endif // LICENSE_SERVER_SDK_INTERNAL_SESSION_USAGE_REPORT_H_
|
||||
Reference in New Issue
Block a user