Change order of loading certificates from pk7 cert

-------------
Add libcurl to media_cas_packager_sdk. libcurl will later be used by a key fetcher to retrieve entitlement key from License Server using a HTTP request.

-------------
Add a function named parsehelper to parse DCSL from the key smith response.

-------------
Move wv_cas_key_fetcher to media_cas_packager_sdk so partners can use it request entitlement keys from License Server.

-------------
Add pkcs7 write method to x509_cert.cc

-------------
Update boringssl_repo to latest in master-with-bazel

-------------
Add a TsPacket class to media_cas_packager_sdk to allow the construction of a ECM TS packet in the SDK.

-------------
Move InsertEcm() from our internal CAS directory to the media_cas_packager_sdk, to be used to build a ECM TS packet by the SDK.

-------------
Add METADATA in common folder

-------------
Refactoring of certificate verification into DrmRootCertificate.

-------------
Extend the default duration of leaf certificates.

-------------
Fix moe_test

-------------
Add a new method to WvCasEcm to allow partner to create a TS packet carrying the generated ECM.

-------------
Change from SHA1 to SHA256 for Cast certificates

-------------
Update crypto mode enumeration to match WV ECM document

-------------
Fix the way we set the validity dates

-------------
Move exported_root/util/status to common/ to prepare for util::Status migration

Also added constructor/operator to copy from/to util::Status.

-------------
Add GenerateDCSLrequest function to certificate_util.h.

-------------
Fix build break

-------------
Allow 'table_id' (in the section header) be specified by caller of SDK method WvCasEcm::GenerateTsPacket().

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=224535399
This commit is contained in:
Fang Yu
2018-12-07 10:16:38 -08:00
parent fb96918196
commit 121d554c20
63 changed files with 4834 additions and 560 deletions

View File

@@ -1,4 +1,5 @@
workspace(name = "media_cas_packager_sdk")
load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository", "git_repository")
# CCTZ (Time-zone framework), needed by abseil.
git_repository(
@@ -21,7 +22,7 @@ git_repository(
git_repository(
name = "boringssl_repo",
commit = "27ae6cadd74fd054208730827a8de3fe9bc648f0", # 2017-12-07
commit = "14164f6fef47b7ebd97cdb0cea1624eabd6fe6b8", # 2018-11-26
remote = "https://github.com/google/boringssl.git",
)
@@ -39,11 +40,25 @@ git_repository(
new_git_repository(
name = "glog_repo",
build_file = "glog.BUILD",
build_file = "@//:glog.BUILD",
commit = "0472b91c5defdf90cff7292e3bf7bd86770a9a0a", # 2016-07-13
remote = "https://github.com/google/glog.git",
)
new_git_repository(
name = "curl_repo",
build_file = "@//:external_build_files/curl.BUILD",
commit = "9cf7b7e66089ee2150ff6e5709a3cca91841ec0b", # 2018-11-16
remote = "https://github.com/curl/curl.git",
)
new_git_repository(
name = "zlib_repo",
build_file = "@//:external_build_files/zlib.BUILD",
commit = "cacf7f1d4e3d44d871b605da3b647f07d718623f", # 2017-01-15
remote = "https://github.com/madler/zlib.git",
)
bind(
name = "protobuf",
actual = "@protobuf_repo//:protobuf",

View File

@@ -31,6 +31,120 @@ cc_library(
hdrs = ["certificate_type.h"],
)
cc_library(
name = "status",
srcs = ["status.cc"],
hdrs = ["status.h"],
deps = [
"//base",
"@abseil_repo//absl/base:core_headers",
"@abseil_repo//absl/strings",
"//util:error_space",
],
)
cc_test(
name = "status_test",
srcs = ["status_test.cc"],
deps = [
":status",
"//testing:gunit_main",
],
)
cc_library(
name = "client_cert",
srcs = ["client_cert.cc"],
hdrs = ["client_cert.h"],
deps = [
":crypto_util",
":drm_root_certificate",
":error_space",
":random_util",
":rsa_key",
":signing_key_util",
":wvm_token_handler",
"//base",
"//strings",
"@abseil_repo//absl/strings",
"@abseil_repo//absl/synchronization",
"@abseil_repo//absl/time",
"//util/gtl:map_util",
"//util:status",
"//protos/public:client_identification_proto",
"//protos/public:drm_certificate_proto",
"//protos/public:errors_proto",
"//protos/public:license_protocol_proto",
"//protos/public:signed_drm_certificate_proto",
],
)
cc_test(
name = "client_cert_test",
srcs = ["client_cert_test.cc"],
deps = [
":client_cert",
":drm_root_certificate",
":error_space",
":wvm_test_keys",
"//base",
"//strings",
"//testing:gunit_main",
"@abseil_repo//absl/strings",
"@abseil_repo//absl/synchronization",
"@abseil_repo//absl/time",
"//common:rsa_key",
"//common:rsa_test_keys",
"//protos/public:drm_certificate_proto",
"//protos/public:errors_proto",
"//protos/public:signed_drm_certificate_proto",
],
)
cc_library(
name = "device_status_list",
srcs = ["device_status_list.cc"],
hdrs = ["device_status_list.h"],
deps = [
":client_cert",
":crypto_util",
":drm_root_certificate",
":drm_service_certificate",
":error_space",
":random_util",
":rsa_key",
":signing_key_util",
"//base",
"@abseil_repo//absl/strings",
"@abseil_repo//absl/synchronization",
"//util/gtl:map_util",
"//util:status",
"//protos/public:client_identification_proto",
"//protos/public:device_certificate_status_proto",
"//protos/public:errors_proto",
"//protos/public:provisioned_device_info_proto",
],
)
cc_test(
name = "device_status_list_test",
timeout = "short",
srcs = ["device_status_list_test.cc"],
deps = [
":client_cert",
":device_status_list",
"//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_library(
name = "drm_root_certificate",
srcs = ["drm_root_certificate.cc"],
@@ -39,8 +153,11 @@ cc_library(
":certificate_type",
":error_space",
":rsa_key",
":sha_util",
"//base",
"@abseil_repo//absl/memory",
"@abseil_repo//absl/strings",
"@abseil_repo//absl/synchronization",
"//external:openssl",
"//util:status",
"//protos/public:drm_certificate_proto",
@@ -55,9 +172,12 @@ cc_test(
srcs = ["drm_root_certificate_test.cc"],
deps = [
":drm_root_certificate",
":error_space",
":rsa_key",
":rsa_test_keys",
":test_drm_certificates",
"//base",
"//external:protobuf",
"//testing:gunit_main",
"//protos/public:drm_certificate_proto",
"//protos/public:errors_proto",
@@ -65,22 +185,6 @@ cc_test(
],
)
cc_library(
name = "certificate_util",
srcs = ["certificate_util.cc"],
hdrs = ["certificate_util.h"],
deps = [
":certificate_type",
":drm_root_certificate",
":drm_service_certificate",
":verified_media_pipeline",
":vmp_checker",
"//base",
"//util:status",
"//license_server_sdk/internal:sdk",
],
)
cc_library(
name = "client_id_util",
srcs = ["client_id_util.cc"],
@@ -342,10 +446,10 @@ cc_test(
)
cc_library(
name = "test_certificates",
name = "test_drm_certificates",
testonly = 1,
srcs = ["test_certificates.cc"],
hdrs = ["test_certificates.h"],
srcs = ["test_drm_certificates.cc"],
hdrs = ["test_drm_certificates.h"],
deps = [
"//base",
"@abseil_repo//absl/strings",
@@ -454,11 +558,12 @@ cc_test(
srcs = ["drm_service_certificate_test.cc"],
deps = [
":aes_cbc_util",
":drm_root_certificate",
":drm_service_certificate",
":rsa_key",
":rsa_test_keys",
":rsa_util",
":test_certificates",
":test_drm_certificates",
"//base",
"//external:protobuf",
"//testing:gunit_main",
@@ -489,6 +594,7 @@ cc_library(
srcs = ["x509_cert.cc"],
hdrs = ["x509_cert.h"],
deps = [
":error_space",
":openssl_util",
":rsa_key",
"//base",

View File

@@ -1,55 +0,0 @@
////////////////////////////////////////////////////////////////////////////////
// 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 "common/certificate_util.h"
#include "common/certificate_type.h"
#include "common/drm_root_certificate.h"
#include "common/drm_service_certificate.h"
#include "common/verified_media_pipeline.h"
#include "common/vmp_checker.h"
#include "license_server_sdk/internal/client_cert.h"
#include "license_server_sdk/internal/device_status_list.h"
namespace widevine {
util::Status SetCertificateStatusList(
CertificateType cert_type, const std::string& signed_certificate_status_list,
uint32_t expiration_period_seconds, bool allow_unknown_devices) {
util::Status status =
VmpChecker::Instance()->SelectDrmCertificateType(cert_type);
if (!status.ok()) return status;
std::unique_ptr<DrmRootCertificate> root_cert;
status = DrmRootCertificate::CreateByType(cert_type, &root_cert);
if (!status.ok()) {
return status;
}
status = CertificateClientCert::SetDrmRootCertificatePublicKey(
root_cert->public_key());
if (!status.ok()) {
return status;
}
DeviceStatusList::Instance()->set_allow_unknown_devices(
allow_unknown_devices);
return DeviceStatusList::Instance()->UpdateStatusList(
root_cert->public_key(), signed_certificate_status_list,
expiration_period_seconds);
}
util::Status AddDrmServiceCertificate(
CertificateType cert_type, const std::string& service_certificate,
const std::string& service_private_key,
const std::string& service_private_key_passphrase) {
util::Status status =
VmpChecker::Instance()->SelectDrmCertificateType(cert_type);
if (!status.ok()) return status;
return DrmServiceCertificate::AddDrmServiceCertificate(
cert_type, service_certificate, service_private_key,
service_private_key_passphrase);
}
} // namespace widevine

View File

@@ -1,42 +0,0 @@
////////////////////////////////////////////////////////////////////////////////
// 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 COMMON_CERTIFICATE_UTIL_H_
#define COMMON_CERTIFICATE_UTIL_H_
#include <string>
#include "util/status.h"
#include "common/certificate_type.h"
namespace widevine {
// 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.
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|.
util::Status AddDrmServiceCertificate(
CertificateType cert_type, const std::string& service_certificate,
const std::string& service_private_key,
const std::string& service_private_key_passphrase);
} // namespace widevine
#endif // COMMON_CERTIFICATE_UTIL_H_

260
common/client_cert.cc Normal file
View File

@@ -0,0 +1,260 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2017 Google LLC.
//
// This software is licensed under the terms defined in the Widevine Master
// License Agreement. For a copy of this agreement, please contact
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#include "common/client_cert.h"
#include <memory>
#include <utility>
#include <vector>
#include "glog/logging.h"
#include "strings/serialize.h"
#include "absl/strings/escaping.h"
#include "absl/synchronization/mutex.h"
#include "util/gtl/map_util.h"
#include "util/status.h"
#include "common/crypto_util.h"
#include "common/drm_root_certificate.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"
// TODO(user): Get rid of this horror.
namespace widevine {
namespace {
const int kKeyboxSizeBytes = 72;
} // 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(const DrmRootCertificate* root_certificate,
ClientIdentification::TokenType token_type,
const std::string& token, ClientCert** client_cert) {
DCHECK(client_cert);
if (token_type == ClientIdentification::KEYBOX) {
*client_cert = nullptr;
if (token.size() < kKeyboxSizeBytes) {
return util::Status(error_space, INVALID_KEYBOX_TOKEN,
"keybox-token-is-too-short");
}
return ClientCert::CreateWithKeybox(token, client_cert);
} else if (token_type == ClientIdentification::DRM_DEVICE_CERTIFICATE) {
return CreateWithDrmCertificate(root_certificate, token, client_cert);
} else {
return util::Status(error_space, util::error::UNIMPLEMENTED,
"client-type-not-implemented");
}
}
util::Status ClientCert::CreateWithKeybox(const std::string& keybox_token,
ClientCert** client_cert) {
CHECK(client_cert);
*client_cert = nullptr;
std::unique_ptr<KeyboxClientCert> new_client_cert(new KeyboxClientCert);
util::Status status = new_client_cert->Initialize(keybox_token);
if (!status.ok()) {
return status;
}
*client_cert = new_client_cert.release();
return util::OkStatus();
}
util::Status ClientCert::CreateWithDrmCertificate(
const DrmRootCertificate* root_certificate, const std::string& drm_certificate,
ClientCert** client_cert) {
CHECK(client_cert);
*client_cert = nullptr;
std::unique_ptr<CertificateClientCert> new_client_cert(
new CertificateClientCert);
util::Status status =
new_client_cert->Initialize(root_certificate, drm_certificate);
if (!status.ok()) {
return status;
}
*client_cert = new_client_cert.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() {}
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);
}
util::Status KeyboxClientCert::Initialize(const std::string& keybox_bytes) {
if (keybox_bytes.size() < kKeyboxSizeBytes) {
return util::Status(error_space, INVALID_KEYBOX_TOKEN,
"keybox-token-is-too-short");
}
set_system_id(WvmTokenHandler::GetSystemId(keybox_bytes));
set_serial_number(WvmTokenHandler::GetEncryptedUniqueId(keybox_bytes));
bool insecure_keybox = false;
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;
return util::Status(error_space, new_code, status.error_message());
}
return util::OkStatus();
}
util::Status KeyboxClientCert::VerifySignature(
const std::string& message, const std::string& signature,
ProtocolVersion protocol_version) {
DCHECK(!signing_key().empty());
using crypto_util::VerifySignatureHmacSha256;
if (!VerifySignatureHmacSha256(
GetClientSigningKey(signing_key(), protocol_version), signature,
message)) {
return util::Status(error_space, INVALID_SIGNATURE, "invalid-keybox-mac");
}
return util::OkStatus();
}
CertificateClientCert::CertificateClientCert() {}
CertificateClientCert::~CertificateClientCert() {}
util::Status CertificateClientCert::Initialize(
const DrmRootCertificate* drm_root_certificate,
const std::string& serialized_certificate) {
CHECK(drm_root_certificate);
SignedDrmCertificate signed_device_cert;
DrmCertificate device_cert;
util::Status status = drm_root_certificate->VerifyCertificate(
serialized_certificate, &signed_device_cert, &device_cert);
if (!status.ok()) {
return status;
}
const SignedDrmCertificate& signer = signed_device_cert.signer();
DrmCertificate model_certificate;
if (!model_certificate.ParseFromString(signer.drm_certificate())) {
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
"drm-certificate-invalid-signer");
}
if (!model_certificate.has_serial_number()) {
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
"missing-signer-serial-number");
}
// Check to see if this model certificate is signed by a
// provisioner (entity using Widevine Provisioning Server SDK).
if (signer.has_signer()) {
DrmCertificate provisioner_certificate;
if (!provisioner_certificate.ParseFromString(
signer.signer().drm_certificate())) {
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
"model-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 (!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());
}
set_signer_serial_number(model_certificate.serial_number());
set_signer_creation_time_seconds(model_certificate.creation_time_seconds());
if (!model_certificate.has_system_id()) {
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
"model-certificate-missing-system-id");
}
set_system_id(model_certificate.system_id());
set_serial_number(device_cert.serial_number());
set_public_key(device_cert.public_key());
rsa_public_key_.reset(RsaPublicKey::Create(public_key()));
if (rsa_public_key_ == nullptr) {
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
"drm-certificate-public-key-failed");
}
// TODO(user): Move this somewhere else. It is license protocol.
set_key(Random16Bytes());
if (!rsa_public_key_->Encrypt(key(), &encrypted_session_key_)) {
return util::Status(error_space, ENCRYPT_ERROR,
"drm-certificate-failed-encrypt-session-key");
}
return util::OkStatus();
}
util::Status CertificateClientCert::VerifySignature(
const std::string& message, const std::string& signature,
ProtocolVersion protocol_version) {
CHECK(rsa_public_key_);
if (!rsa_public_key_->VerifySignature(message, signature)) {
return util::Status(error_space, INVALID_SIGNATURE, "");
}
return util::OkStatus();
}
} // namespace widevine

188
common/client_cert.h Normal file
View File

@@ -0,0 +1,188 @@
////////////////////////////////////////////////////////////////////////////////
// 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 COMMON_CLIENT_CERT_H__
#define COMMON_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 DrmRootCertificate;
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(
const DrmRootCertificate* root_certificate,
widevine::ClientIdentification::TokenType token_type,
const std::string& token, ClientCert** client_cert);
// Creates a Keybox based ClientCert.
static util::Status CreateWithKeybox(const std::string& keybox_token,
ClientCert** client_cert);
// Creates a Device Certificate based ClientCert.
static util::Status CreateWithDrmCertificate(
const DrmRootCertificate* root_certificate, 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 OK if signature
// is valid.
virtual util::Status 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 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_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_;
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);
util::Status Initialize(const std::string& keybox_bytes);
util::Status 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:
KeyboxClientCert();
friend class ClientCert;
friend class MockKeyboxClientCert;
std::string device_key_;
std::string encrypted_device_key_;
DISALLOW_COPY_AND_ASSIGN(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;
util::Status 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;
util::Status Initialize(const DrmRootCertificate* drm_root_certificate,
const std::string& serialized_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;
}
std::string session_key_;
std::string encrypted_session_key_;
std::unique_ptr<RsaPublicKey> rsa_public_key_;
private:
CertificateClientCert();
DISALLOW_COPY_AND_ASSIGN(CertificateClientCert);
};
} // namespace widevine
#endif // COMMON_CLIENT_CERT_H__

566
common/client_cert_test.cc Normal file
View File

@@ -0,0 +1,566 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2017 Google LLC.
//
// This software is licensed under the terms defined in the Widevine Master
// License Agreement. For a copy of this agreement, please contact
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#include "common/client_cert.h"
#include <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;
}
ASSERT_OK(
DrmRootCertificate::CreateByType(kCertificateTypeTesting, &root_cert_));
}
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_;
std::unique_ptr<DrmRootCertificate> root_cert_;
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(root_cert_.get(), ClientIdentification::KEYBOX,
expectation.token_, &client_cert_ptr);
} else {
status =
ClientCert::CreateWithKeybox(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) {
// Reset DRM certificate signature cache since some certificates get
// re-generated.
ASSERT_OK(
DrmRootCertificate::CreateByType(kCertificateTypeTesting, &root_cert_));
// Test validation of a valid request.
util::Status status;
ClientCert* client_cert_ptr = nullptr;
status = ClientCert::Create(root_cert_.get(),
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_1_3072_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));
// 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 model system ID.
dev_cert.reset(GenerateDrmCertificate(system_id, device_sn));
signer_cert.reset(GenerateIntermediateCertificate(system_id, signer_sn));
signer_cert->clear_system_id();
std::unique_ptr<SignedDrmCertificate> missing_model_sn(SignCertificate(
*dev_cert,
SignCertificate(*signer_cert, nullptr,
test_keys_.private_test_key_1_3072_bits()),
test_keys_.private_test_key_2_2048_bits()));
// 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,
"invalid-signed-drm-certificate")),
TestCertificateAndData(invalid_drm_cert->SerializeAsString(), "", 0,
util::Status(error_space, INVALID_DRM_CERTIFICATE,
"invalid-drm-certificate")),
TestCertificateAndData(bad_device_public_key->SerializeAsString(), "", 0,
util::Status(error_space, INVALID_DRM_CERTIFICATE,
"drm-certificate-public-key-failed")),
TestCertificateAndData(invalid_signer->SerializeAsString(), "", 0,
util::Status(error_space, INVALID_DRM_CERTIFICATE,
"invalid-signer-certificate")),
TestCertificateAndData(bad_signer_public_key->SerializeAsString(), "", 0,
util::Status(error_space, INVALID_DRM_CERTIFICATE,
"invalid-leaf-signer-public-key")),
TestCertificateAndData(bad_device_signature->SerializeAsString(), "", 0,
util::Status(error_space, INVALID_SIGNATURE,
"cache-miss-invalid-signature")),
TestCertificateAndData(
missing_model_sn->SerializeAsString(), "", 0,
util::Status(error_space, INVALID_DRM_CERTIFICATE,
"model-certificate-missing-system-id")),
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_SIGNATURE,
"cache-miss-invalid-signature")),
};
for (size_t i = 0; i < ABSL_ARRAYSIZE(kInvalidCertificate); ++i) {
TestBasicValidationDrmCertificate(kInvalidCertificate[i], false);
}
}
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::CreateWithKeybox(token, &client_cert_ptr);
ASSERT_EQ(MISSING_PRE_PROV_KEY, status.error_code());
}
TEST_F(ClientCertTest, ValidProvisionerDeviceCert) {
const uint32_t system_id = 5000;
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(root_cert_.get(),
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(root_cert_.get(),
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;
// TODO(user): Fix this test. It is failing for the right reasons, but the
// certificate chain is broken (intermediate signature does not match signer).
ASSERT_EQ("cache-miss-invalid-signature",
ClientCert::Create(root_cert_.get(),
ClientIdentification::DRM_DEVICE_CERTIFICATE,
serialized_cert, &client_cert_ptr)
.error_message());
EXPECT_FALSE(client_cert_ptr);
}
} // namespace widevine

View File

@@ -0,0 +1,366 @@
////////////////////////////////////////////////////////////////////////////////
// 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 "common/device_status_list.h"
#include <time.h>
#include <memory>
#include "glog/logging.h"
#include "absl/strings/escaping.h"
#include "absl/strings/numbers.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/client_cert.h"
#include "common/drm_service_certificate.h"
#include "common/error_space.h"
#include "common/rsa_key.h"
#include "protos/public/client_identification.pb.h"
#include "protos/public/errors.pb.h"
namespace widevine {
namespace {
const char kSignedListTerminator[] = "}";
const char kSignedList[] = "signedList\":";
const std::size_t kSignedListLen = strlen(kSignedList);
} // namespace
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;
}
util::Status DeviceStatusList::ExtractFromProvisioningServiceResponse(
const std::string& certificate_provisioning_service_response,
std::string* signed_certificate_status_list, std::string* certificate_status_list) {
util::Status status = util::OkStatus();
size_t signed_list_start =
certificate_provisioning_service_response.find(kSignedList);
if (signed_list_start != std::string::npos) {
size_t signed_list_end = certificate_provisioning_service_response.find(
kSignedListTerminator, signed_list_start);
if (signed_list_end == std::string::npos) {
return util::Status(
error_space, util::error::INVALID_ARGUMENT,
"Unable to parse the certificate_provisioning_service_response. "
"SignedList not terminated.");
}
std::string signed_list(
certificate_provisioning_service_response.begin() + signed_list_start +
kSignedListLen,
certificate_provisioning_service_response.begin() + signed_list_end);
// Strip off quotes.
signed_list.erase(std::remove(signed_list.begin(), signed_list.end(), '\"'),
signed_list.end());
// Strip off spaces.
signed_list.erase(std::remove(signed_list.begin(), signed_list.end(), ' '),
signed_list.end());
// Strip off newlines.
signed_list.erase(std::remove(signed_list.begin(), signed_list.end(), '\n'),
signed_list.end());
// Strip off carriage return (the control-M character)
signed_list.erase(std::remove(signed_list.begin(), signed_list.end(), '\r'),
signed_list.end());
if (!absl::WebSafeBase64Unescape(signed_list,
signed_certificate_status_list)) {
if (!absl::Base64Unescape(signed_list, signed_certificate_status_list)) {
return util::Status(error_space, util::error::INVALID_ARGUMENT,
"Base64 decode of signedlist failed.");
}
}
} else {
// certificate_provisioning_service_response is the signed list and not a
// JSON message.
if (!absl::WebSafeBase64Unescape(certificate_provisioning_service_response,
signed_certificate_status_list)) {
if (!absl::Base64Unescape(certificate_provisioning_service_response,
signed_certificate_status_list)) {
return util::Status(error_space, util::error::INVALID_ARGUMENT,
"Base64 decode of certList failed.");
}
}
}
SignedDeviceCertificateStatusList signed_status_list;
if (!signed_status_list.ParseFromString(*signed_certificate_status_list)) {
return util::Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
"signed-certificate-status-list-parse-error");
}
if (!signed_status_list.has_certificate_status_list()) {
return util::Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
"missing-status-list");
}
DeviceCertificateStatusList device_certificate_status_list;
if (!device_certificate_status_list.ParseFromString(
signed_status_list.certificate_status_list())) {
return util::Status(error_space, INVALID_CERTIFICATE_STATUS_LIST,
"certificate-status-list-parse-error");
}
*certificate_status_list = signed_status_list.certificate_status_list();
return util::OkStatus();
}
util::Status DeviceStatusList::GenerateSignedDeviceCertificateStatusListRequest(
const std::string& version,
std::string* signed_device_certificate_status_list_request) {
if (version.empty()) {
return util::Status(error_space, util::error::INVALID_ARGUMENT,
"SDK version is empty");
}
DCHECK(signed_device_certificate_status_list_request);
if (signed_device_certificate_status_list_request == nullptr) {
return util::Status(
error_space, util::error::INVALID_ARGUMENT,
"Signed_device_certificate_status_list_request is empty");
}
// Construct SignedDeviceCertificateStatusListRequest.
DeviceCertificateStatusListRequest request;
request.set_sdk_version(version);
request.set_sdk_time_seconds(DeviceStatusList::Instance()->GetCurrentTime());
std::string device_certificate_status_list_request;
request.SerializeToString(&device_certificate_status_list_request);
SignedDeviceCertificateStatusListRequest signed_request;
signed_request.set_device_certificate_status_list_request(
device_certificate_status_list_request);
const DrmServiceCertificate* sc =
DrmServiceCertificate::GetDefaultDrmServiceCertificate();
if (sc == nullptr) {
signed_device_certificate_status_list_request->clear();
return util::Status(error_space,
widevine::INVALID_SERVICE_CERTIFICATE,
"Drm service certificate is not loaded.");
}
const RsaPrivateKey* private_key = sc->private_key();
if (private_key == nullptr) {
return util::Status(error_space,
widevine::INVALID_SERVICE_CERTIFICATE,
"Private key in the service certificate is null.");
}
std::string signature;
private_key->GenerateSignature(device_certificate_status_list_request,
&signature);
signed_request.set_signature(signature);
signed_request.SerializeToString(
signed_device_certificate_status_list_request);
return util::OkStatus();
}
} // namespace widevine

123
common/device_status_list.h Normal file
View File

@@ -0,0 +1,123 @@
////////////////////////////////////////////////////////////////////////////////
// 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 COMMON_DEVICE_STATUS_LIST_H__
#define COMMON_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);
/**
* Parses signed device certificate status list and certificate status list
* from certificateProvisoningServer response.
*
* @param certificate_provisioning_service_response
* @param signed_certificate_status_list
* @param certificate_status_list
* @return WvPLStatus - Status::OK if success, else error.
*/
static util::Status ExtractFromProvisioningServiceResponse(
const std::string& certificate_provisioning_service_response,
std::string* signed_certificate_status_list, std::string* certificate_status_list);
/**
* Constructs signed device certificate status list request string.
*
* @param signed_device_certificate_status_list_request
* @param version
* @return util::Status - Status::OK if success, else error.
*/
static util::Status GenerateSignedDeviceCertificateStatusListRequest(
const std::string& version,
std::string* signed_device_certificate_status_list_request);
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 // COMMON_DEVICE_STATUS_LIST_H__

View 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 "common/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/client_cert.h"
#include "common/rsa_key.h"
#include "common/rsa_test_keys.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:
MockCertificateClientCert() {}
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:
MockKeyboxClientCert() {}
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

View File

@@ -6,23 +6,34 @@
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
// common_typos_disable. Successful / successfull.
#include "common/drm_root_certificate.h"
#include <memory>
#include "glog/logging.h"
#include "absl/memory/memory.h"
#include "absl/strings/escaping.h"
#include "openssl/sha.h"
#include "common/certificate_type.h"
#include "absl/synchronization/mutex.h"
#include "common/error_space.h"
#include "common/rsa_key.h"
#include "common/sha_util.h"
#include "protos/public/drm_certificate.pb.h"
#include "protos/public/errors.pb.h"
#include "protos/public/signed_drm_certificate.pb.h"
namespace widevine {
// From common::TestCertificates.
namespace {
const char kDevelopmentString[] = "dev"; // QA systems.
const char kProductionString[] = "prod"; // Production.
const char kTestingString[] = "test"; // Code development / unit tests.
const bool kUseCache = true;
// From common::TestDrmCertificates.
// TODO(user): common::test_certificates is a testonly target, consider
// how to use instead of dupliciating the test cert here.
static const unsigned char kTestRootCertificate[] = {
@@ -231,14 +242,145 @@ static const unsigned char kProdRootCertificate[] = {
0x1f, 0x17, 0x25, 0xce, 0x90, 0xb9, 0x6d, 0xcd, 0xc4, 0x46, 0xf5, 0xa3,
0x62, 0x13, 0x74, 0x02, 0xa7, 0x62, 0xa4, 0xfa, 0x55, 0xd9, 0xde, 0xcf,
0xa2, 0xe6, 0x80, 0x74, 0x55, 0x06, 0x49, 0xd5, 0x02, 0x0c};
} // namespace
util::Status DrmRootCertificate::Create(
const std::string& signed_drm_certificate,
std::unique_ptr<DrmRootCertificate>* cert) {
// Caches an individual signature for a certificate with a specific serial
// number (signer).
struct VerifiedCertSignature {
VerifiedCertSignature(const std::string& cert, const std::string& sig,
const std::string& signer_sn)
: signed_cert(cert), signature(sig), signer_serial(signer_sn) {}
std::string signed_cert;
std::string signature;
std::string signer_serial;
};
// Map of certificate serial number to its signature.
typedef std::map<std::string, VerifiedCertSignature> VerifiedCertSignatures;
class VerifiedCertSignatureCache {
public:
explicit VerifiedCertSignatureCache(const RsaKeyFactory* key_factory)
: key_factory_(key_factory) {}
// Checks cache, on miss, uses public key. If successful, adds to
// cache.
util::Status VerifySignature(const std::string& cert, const std::string& serial_number,
const std::string& signature,
const std::string& signer_public_key,
const std::string& signer_serial_number) {
{
VerifiedCertSignatures::iterator cached_signature;
absl::ReaderMutexLock read_lock(&signature_cache_mutex_);
cached_signature = signature_cache_.find(serial_number);
if (cached_signature != signature_cache_.end()) {
// TODO(user): Log which of the following three conditions occurs.
if ((cert != cached_signature->second.signed_cert) ||
(signature != cached_signature->second.signature) ||
(signer_serial_number != cached_signature->second.signer_serial)) {
// Cached signature mismatch.
return util::Status(error_space, INVALID_SIGNATURE,
"cached-signature-mismatch");
}
// Cached signature match.
return util::OkStatus();
}
}
// Cache miss. Verify signature.
std::unique_ptr<RsaPublicKey> signer_key(
key_factory_->CreateFromPkcs1PublicKey(signer_public_key));
if (!signer_key) {
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
"invalid-signer-public-key");
}
if (!signer_key->VerifySignature(cert, signature)) {
return util::Status(error_space, INVALID_SIGNATURE,
"cache-miss-invalid-signature");
}
// Add signature to cache.
absl::WriterMutexLock write_lock(&signature_cache_mutex_);
signature_cache_.emplace(
serial_number,
VerifiedCertSignature(cert, signature, signer_serial_number));
return util::OkStatus();
}
private:
VerifiedCertSignatures signature_cache_ GUARDED_BY(&signature_cache_mutex_);
absl::Mutex signature_cache_mutex_;
const RsaKeyFactory* key_factory_;
};
util::Status DrmRootCertificate::CreateByType(
CertificateType cert_type, std::unique_ptr<DrmRootCertificate>* cert) {
CHECK(cert);
return Create(cert_type, absl::make_unique<RsaKeyFactory>(), cert);
}
std::unique_ptr<DrmRootCertificate> DrmRootCertificate::CreateByType(
CertificateType cert_type, util::Status* status) {
CHECK(status);
std::unique_ptr<DrmRootCertificate> new_root_cert;
*status = CreateByType(cert_type, &new_root_cert);
return new_root_cert;
}
util::Status DrmRootCertificate::CreateByTypeString(
const std::string& cert_type_string, std::unique_ptr<DrmRootCertificate>* cert) {
CHECK(cert);
CertificateType cert_type;
if (cert_type_string == kDevelopmentString) {
cert_type = kCertificateTypeDevelopment;
} else if (cert_type_string == kProductionString) {
cert_type = kCertificateTypeProduction;
} else if (cert_type_string == kTestingString) {
cert_type = kCertificateTypeTesting;
} else {
return util::Status(
error_space, INVALID_PARAMETER,
absl::StrCat("invalid-certificate-type ", cert_type_string));
}
return CreateByType(cert_type, cert);
}
util::Status DrmRootCertificate::Create(
CertificateType cert_type, std::unique_ptr<RsaKeyFactory> key_factory,
std::unique_ptr<DrmRootCertificate>* cert) {
DCHECK(cert);
std::string serialized_certificate;
switch (cert_type) {
case kCertificateTypeProduction: {
serialized_certificate.assign(
kProdRootCertificate,
kProdRootCertificate + sizeof(kProdRootCertificate));
break;
}
case kCertificateTypeDevelopment: {
serialized_certificate.assign(
kDevRootCertificate,
kDevRootCertificate + sizeof(kDevRootCertificate));
break;
}
case kCertificateTypeTesting: {
serialized_certificate.assign(
kTestRootCertificate,
kTestRootCertificate + sizeof(kTestRootCertificate));
break;
}
default:
return util::Status(error_space, INVALID_PARAMETER,
"invalid-certificate-type");
}
SignedDrmCertificate signed_root_cert;
if (!signed_root_cert.ParseFromString(signed_drm_certificate)) {
if (!signed_root_cert.ParseFromString(serialized_certificate)) {
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
"signed-root-cert-deserialize-fail");
}
@@ -259,8 +401,9 @@ util::Status DrmRootCertificate::Create(
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
"missing-root-certificate-signature");
}
std::unique_ptr<RsaPublicKey> public_key(
RsaPublicKey::Create(root_cert.public_key()));
key_factory->CreateFromPkcs1PublicKey(root_cert.public_key()));
if (!public_key) {
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
"invalid-root-public-key");
@@ -270,51 +413,123 @@ util::Status DrmRootCertificate::Create(
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
"invalid-root-certificate-signature");
}
cert->reset(new DrmRootCertificate(root_cert.public_key()));
cert->reset(new DrmRootCertificate(
cert_type, serialized_certificate, root_cert.serial_number(),
root_cert.public_key(), std::move(key_factory)));
return util::OkStatus();
}
util::Status DrmRootCertificate::CreateByType(
CertificateType cert_type, std::unique_ptr<DrmRootCertificate>* cert) {
CHECK(cert);
return Create(GetDrmRootCertificate(cert_type), cert);
DrmRootCertificate::DrmRootCertificate(
CertificateType type, const std::string& serialized_certificate,
const std::string& serial_number, const std::string& public_key,
std::unique_ptr<RsaKeyFactory> key_factory)
: type_(type),
serialized_certificate_(serialized_certificate),
serial_number_(serial_number),
public_key_(public_key),
key_factory_(std::move(key_factory)),
signature_cache_(new VerifiedCertSignatureCache(key_factory_.get())) {}
DrmRootCertificate::~DrmRootCertificate() {}
std::string DrmRootCertificate::GetDigest() const {
return absl::BytesToHexString(Sha256_Hash(serialized_certificate_));
}
std::string DrmRootCertificate::GetDrmRootCertificate(CertificateType cert_type) {
std::string root_cert;
switch (cert_type) {
case kCertificateTypeProduction: {
root_cert.assign(kProdRootCertificate,
kProdRootCertificate + sizeof(kProdRootCertificate));
break;
util::Status DrmRootCertificate::VerifyCertificate(
const std::string& serialized_certificate,
SignedDrmCertificate* signed_certificate,
DrmCertificate* certificate) const {
std::unique_ptr<SignedDrmCertificate> local_signed_certificate;
if (!signed_certificate) {
local_signed_certificate = absl::make_unique<SignedDrmCertificate>();
signed_certificate = local_signed_certificate.get();
}
case kCertificateTypeDevelopment: {
root_cert.assign(kDevRootCertificate,
kDevRootCertificate + sizeof(kDevRootCertificate));
break;
}
case kCertificateTypeTesting: {
root_cert.assign(kTestRootCertificate,
kTestRootCertificate + sizeof(kTestRootCertificate));
break;
}
default:
// TODO(user): Consider returning util::Status indicating unsupported
// cert type.
break;
}
return root_cert;
if (!signed_certificate->ParseFromString(serialized_certificate)) {
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
"invalid-signed-drm-certificate");
}
std::string DrmRootCertificate::GetDigest(CertificateType cert_type) {
std::string cert(GetDrmRootCertificate(cert_type));
if (cert.empty()) {
return std::string();
std::unique_ptr<DrmCertificate> local_certificate;
if (!certificate) {
local_certificate = absl::make_unique<DrmCertificate>();
certificate = local_certificate.get();
}
std::string hash(SHA256_DIGEST_LENGTH, 0);
SHA256(reinterpret_cast<const unsigned char*>(cert.data()), cert.size(),
reinterpret_cast<unsigned char*>(&hash[0]));
return absl::BytesToHexString(hash);
if (signed_certificate->drm_certificate().empty() ||
!certificate->ParseFromString(signed_certificate->drm_certificate())) {
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
"invalid-drm-certificate");
}
if (certificate->serial_number().empty()) {
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
"missing-serial-number");
}
if (!certificate->has_creation_time_seconds()) {
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
"missing-creation-time");
}
if (certificate->public_key().empty()) {
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
"missing-public-key");
}
// Verify signature chain, but do not use cache for leaf certificates.
return VerifySignatures(*signed_certificate, certificate->serial_number(),
!kUseCache);
}
// Recursively verifies certificates with their signing certs or the root.
// use_cache should be false when initially called so that signatures do not
// cached leaf certificates not signed with the root certificate, such as for
// the case of device-unique device certificates.
// Signatures for root-signed certificates are always cached, even if they are
// leaf certificates. For example service, and provisioner certificates.
util::Status DrmRootCertificate::VerifySignatures(
const SignedDrmCertificate& signed_cert, const std::string& cert_serial_number,
bool use_cache) const {
if (!signed_cert.has_signer()) {
// Always use cache for root-signed certificates.
return signature_cache_->VerifySignature(
signed_cert.drm_certificate(), cert_serial_number,
signed_cert.signature(), public_key(), serial_number_);
}
DrmCertificate signer;
if (!signer.ParseFromString(signed_cert.signer().drm_certificate())) {
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
"invalid-signer-certificate");
}
// Verify the signer before verifying signed_cert.
util::Status status =
VerifySignatures(signed_cert.signer(), signer.serial_number(), kUseCache);
if (!status.ok()) {
return status;
}
if (use_cache) {
status = signature_cache_->VerifySignature(
signed_cert.drm_certificate(), cert_serial_number,
signed_cert.signature(), signer.public_key(), signer.serial_number());
if (!status.ok()) {
return status;
}
} else {
std::unique_ptr<RsaPublicKey> signer_public_key(
key_factory_->CreateFromPkcs1PublicKey(signer.public_key()));
if (!signer_public_key) {
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
"invalid-leaf-signer-public-key");
}
if (!signer_public_key->VerifySignature(signed_cert.drm_certificate(),
signed_cert.signature())) {
return util::Status(error_space, INVALID_SIGNATURE,
"cache-miss-invalid-signature");
}
}
return util::OkStatus();
}
} // namespace widevine

View File

@@ -13,6 +13,8 @@
#ifndef COMMON_DRM_ROOT_CERTIFICATE_H_
#define COMMON_DRM_ROOT_CERTIFICATE_H_
// common_typos_disable. Successful / successfull.
#include <memory>
#include <string>
@@ -23,41 +25,82 @@
namespace widevine {
class DrmCertificate;
class RsaKeyFactory;
class RsaPublicKey;
class SignedDrmCertificate;
class VerifiedCertSignatureCache;
// Root certificate and certificate chain verifier with internal caching.
// This object is thread-safe.
class DrmRootCertificate {
public:
virtual ~DrmRootCertificate() {}
virtual ~DrmRootCertificate();
// Creates a DrmRootCertificate object given a certificate type.
// |cert| may not be nullptr, and it points to a
// std::unique_ptr<DrmRootCertificate> which will be used to return a newly
// created DrmRootCertificate* if successful. The caller assumes ownership of
// the new DrmRootCertificate. This method returns util::Status::OK on
// success, or appropriate error status otherwise.
// created const DrmRootCertificate* if successful. The caller assumes
// ownership of the new DrmRootCertificate. This method returns
// util::Status::OK on success, or appropriate error status otherwise.
static util::Status CreateByType(CertificateType cert_type,
std::unique_ptr<DrmRootCertificate>* cert);
// Returns the hex-encoded SHA-256 digest for the specified root certificate.
static std::string GetDigest(CertificateType cert_type);
// Given |cert_type|, the appropiate root certificate is returned as
// a serialized SignedDrmCertificates.
static std::string GetDrmRootCertificate(CertificateType cert_type);
// Variant on the method above to make CLIF happy until b/110539622 is fixed.
static std::unique_ptr<DrmRootCertificate> CreateByType(
CertificateType cert_type, util::Status* status);
// Creates a DrmRootCertificate object given a certificate type std::string, which
// must be one of "prod", "qa", or "test".
// |cert| may not be nullptr, and it points to a
// std::unique_ptr<DrmRootCertificate> which will be used to return a newly
// created const DrmRootCertificate* if successful. The caller assumes
// ownership of the new DrmRootCertificate. This method returns
// util::Status::OK on success, or appropriate error status otherwise.
static util::Status CreateByTypeString(
const std::string& cert_type_string,
std::unique_ptr<DrmRootCertificate>* cert);
// |certificate| will contgain the DRM certificate upon successful return.
// May be null.
// Returns util::Status::OK if successful, or an appropriate error code
// otherwise.
virtual util::Status VerifyCertificate(
const std::string& serialized_certificate,
SignedDrmCertificate* signed_certificate,
DrmCertificate* certificate) const;
// Returns the hex-encoded SHA-256 digest for this certificate.
virtual std::string GetDigest() const;
const CertificateType type() const { return type_; }
const std::string& public_key() const { return public_key_; }
// Verifies a DRM certificate.
protected:
DrmRootCertificate(CertificateType cert_type,
const std::string& serialized_certificate,
const std::string& serial_number, const std::string& public_key,
std::unique_ptr<RsaKeyFactory> key_factory);
private:
friend class DrmRootCertificateTest;
// Creates a DrmRootCertificate object given a serialized
// SignedDrmCertificate. |cert| may not be nullptr, and it points to a
// std::unique_ptr<DrmRootCertificate> which will be used to return a newly
// created DrmRootCertificate* if successful. The caller assumes ownership of
// the new DrmRootCertificate. This method returns util::Status::OK on
// success, or appropriate error status otherwise.
// TODO(user): Consider moving to private.
static util::Status Create(const std::string& signed_drm_certificate,
static util::Status Create(CertificateType cert_type,
std::unique_ptr<RsaKeyFactory> key_factory,
std::unique_ptr<DrmRootCertificate>* cert);
explicit DrmRootCertificate(const std::string& public_key)
: public_key_(public_key) {}
util::Status VerifySignatures(const SignedDrmCertificate& signed_cert,
const std::string& cert_serial_number,
bool use_cache) const;
CertificateType type_;
std::string serialized_certificate_;
std::string serial_number_;
std::string public_key_;
std::unique_ptr<RsaKeyFactory> key_factory_;
mutable std::unique_ptr<VerifiedCertSignatureCache> signature_cache_;
DISALLOW_IMPLICIT_CONSTRUCTORS(DrmRootCertificate);
};

View File

@@ -9,92 +9,256 @@
// Description:
// Unit tests for drm_root_certificate.cc
#include "common/drm_root_certificate.h"
#include <memory>
#include "google/protobuf/util/message_differencer.h"
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "common/drm_root_certificate.h"
#include "common/error_space.h"
#include "common/rsa_key.h"
#include "common/rsa_test_keys.h"
#include "common/test_drm_certificates.h"
#include "protos/public/drm_certificate.pb.h"
#include "protos/public/errors.pb.h"
#include "protos/public/signed_drm_certificate.pb.h"
using google::protobuf::util::MessageDifferencer;
namespace widevine {
TEST(DrmRootCertificateCreateTest, TestCertificate) {
const std::string kTestCertificateHash(
"49f917b1bdfed78002a58e799a58e940"
"1fffaaed9d8d80752782b066757e2c8c");
std::unique_ptr<DrmRootCertificate> root_cert;
ASSERT_EQ(util::OkStatus(), DrmRootCertificate::CreateByType(
kCertificateTypeTesting, &root_cert));
ASSERT_TRUE(root_cert != nullptr);
EXPECT_EQ(kTestCertificateHash, root_cert->GetDigest());
}
TEST(DrmRootCertificateCreateTest, DevCertificate) {
const std::string kDevelopmentCertificateHash(
"0e25ee95476a770f30b98ac5ef778b3f"
"137b66c29385b84f547a361b4724b17d");
std::unique_ptr<DrmRootCertificate> root_cert;
ASSERT_EQ(util::OkStatus(), DrmRootCertificate::CreateByType(
kCertificateTypeDevelopment, &root_cert));
ASSERT_TRUE(root_cert != nullptr);
EXPECT_EQ(kDevelopmentCertificateHash, root_cert->GetDigest());
}
TEST(DrmRootCertificateCreateTest, ProdCertificate) {
const std::string kProductionCertificateHash(
"d62fdabc9286648a81f7d3bedaf2f5a5"
"27bbad39bc38da034ba98a21569adb9b");
std::unique_ptr<DrmRootCertificate> root_cert;
ASSERT_EQ(util::OkStatus(), DrmRootCertificate::CreateByType(
kCertificateTypeProduction, &root_cert));
ASSERT_TRUE(root_cert != nullptr);
EXPECT_EQ(kProductionCertificateHash, root_cert->GetDigest());
}
TEST(DrmRootCertificateTestCertificatesTest, Success) {
TestDrmCertificates test_certs;
std::unique_ptr<DrmRootCertificate> root_cert;
ASSERT_TRUE(
DrmRootCertificate::CreateByType(kCertificateTypeTesting, &root_cert)
.ok());
EXPECT_TRUE(root_cert
->VerifyCertificate(test_certs.test_root_certificate(),
nullptr, nullptr)
.ok());
EXPECT_TRUE(
root_cert
->VerifyCertificate(test_certs.test_intermediate_certificate(),
nullptr, nullptr)
.ok());
EXPECT_TRUE(root_cert
->VerifyCertificate(test_certs.test_user_device_certificate(),
nullptr, nullptr)
.ok());
EXPECT_TRUE(root_cert
->VerifyCertificate(test_certs.test_service_certificate(),
nullptr, nullptr)
.ok());
}
class DrmRootCertificateTest : public testing::Test {
protected:
DrmRootCertificateTest() {}
util::Status DrmRootCertificateCreate(
const std::string& signed_drm_certificate,
std::unique_ptr<DrmRootCertificate>* cert) {
return DrmRootCertificate::Create(signed_drm_certificate, cert);
DrmRootCertificateTest() {
private_keys_.emplace_back(
RsaPrivateKey::Create(test_keys_.private_test_key_1_3072_bits()));
private_keys_.emplace_back(
RsaPrivateKey::Create(test_keys_.private_test_key_2_2048_bits()));
private_keys_.emplace_back(
RsaPrivateKey::Create(test_keys_.private_test_key_3_2048_bits()));
}
void SetUp() override {
drm_certificates_[0].set_serial_number("level 0");
drm_certificates_[0].set_creation_time_seconds(0);
drm_certificates_[0].set_public_key(
test_keys_.public_test_key_1_3072_bits());
drm_certificates_[1].set_serial_number("level 1");
drm_certificates_[1].set_creation_time_seconds(1);
drm_certificates_[1].set_public_key(
test_keys_.public_test_key_2_2048_bits());
drm_certificates_[2].set_serial_number("level 2");
drm_certificates_[2].set_creation_time_seconds(2);
drm_certificates_[2].set_public_key(
test_keys_.public_test_key_3_2048_bits());
ASSERT_EQ(util::OkStatus(), DrmRootCertificate::CreateByType(
kCertificateTypeTesting, &root_cert_));
}
void GenerateSignedDrmCertificate() {
SignedDrmCertificate* current_sc(&signed_drm_certificate_);
ASSERT_TRUE(drm_certificates_[2].SerializeToString(
current_sc->mutable_drm_certificate()));
ASSERT_TRUE(private_keys_[1]->GenerateSignature(
current_sc->drm_certificate(), current_sc->mutable_signature()));
current_sc = current_sc->mutable_signer();
ASSERT_TRUE(drm_certificates_[1].SerializeToString(
current_sc->mutable_drm_certificate()));
ASSERT_TRUE(private_keys_[0]->GenerateSignature(
current_sc->drm_certificate(), current_sc->mutable_signature()));
current_sc = current_sc->mutable_signer();
ASSERT_TRUE(drm_certificates_[0].SerializeToString(
current_sc->mutable_drm_certificate()));
ASSERT_TRUE(private_keys_[0]->GenerateSignature(
current_sc->drm_certificate(), current_sc->mutable_signature()));
}
RsaTestKeys test_keys_;
std::vector<std::unique_ptr<RsaPrivateKey>> private_keys_;
SignedDrmCertificate signed_drm_certificate_;
DrmCertificate drm_certificates_[3];
std::unique_ptr<DrmRootCertificate> root_cert_;
};
TEST_F(DrmRootCertificateTest, DrmRootCertificateCreation) {
RsaTestKeys test_keys;
std::unique_ptr<DrmRootCertificate> root_cert;
// First, invalid serialized cert. Should fail.
EXPECT_EQ(INVALID_DRM_CERTIFICATE,
DrmRootCertificateCreate("bad_cert", &root_cert).error_code());
SignedDrmCertificate signed_cert;
std::string serialized;
// Serialized empty cert. Should fail.
ASSERT_TRUE(signed_cert.SerializeToString(&serialized));
EXPECT_NE(util::OkStatus(),
DrmRootCertificateCreate(serialized, &root_cert));
// Add public key. Should still fail.
DrmCertificate drm_cert;
drm_cert.set_public_key(test_keys.public_test_key_1_3072_bits());
ASSERT_TRUE(
drm_cert.SerializeToString(signed_cert.mutable_drm_certificate()));
ASSERT_TRUE(signed_cert.SerializeToString(&serialized));
EXPECT_EQ(INVALID_DRM_CERTIFICATE,
DrmRootCertificateCreate(serialized, &root_cert).error_code());
// Now self-sign the cert. Should succeed.
std::unique_ptr<RsaPrivateKey> private_key(
RsaPrivateKey::Create(test_keys.private_test_key_1_3072_bits()));
ASSERT_TRUE(private_key.get());
ASSERT_TRUE(private_key->GenerateSignature(signed_cert.drm_certificate(),
signed_cert.mutable_signature()));
ASSERT_TRUE(signed_cert.SerializeToString(&serialized));
EXPECT_EQ(util::OkStatus(),
DrmRootCertificateCreate(serialized, &root_cert));
ASSERT_TRUE(root_cert);
// Verify the public key.
EXPECT_EQ(test_keys.public_test_key_1_3072_bits(), root_cert->public_key());
TEST_F(DrmRootCertificateTest, SuccessNoOutput) {
GenerateSignedDrmCertificate();
ASSERT_EQ(util::OkStatus(),
root_cert_->VerifyCertificate(
signed_drm_certificate_.SerializeAsString(), nullptr, nullptr));
}
TEST_F(DrmRootCertificateTest, DrmRootCertificateCreationByType) {
std::unique_ptr<DrmRootCertificate> root_cert;
EXPECT_EQ(util::OkStatus(), DrmRootCertificate::CreateByType(
kCertificateTypeTesting, &root_cert));
ASSERT_TRUE(root_cert != nullptr);
EXPECT_EQ(util::OkStatus(), DrmRootCertificate::CreateByType(
kCertificateTypeDevelopment, &root_cert));
ASSERT_TRUE(root_cert != nullptr);
EXPECT_EQ(util::OkStatus(), DrmRootCertificate::CreateByType(
kCertificateTypeProduction, &root_cert));
ASSERT_TRUE(root_cert != nullptr);
TEST_F(DrmRootCertificateTest, SuccessWithOutput) {
GenerateSignedDrmCertificate();
SignedDrmCertificate out_signed_cert;
DrmCertificate out_cert;
ASSERT_EQ(util::OkStatus(), root_cert_->VerifyCertificate(
signed_drm_certificate_.SerializeAsString(),
&out_signed_cert, &out_cert));
EXPECT_TRUE(
MessageDifferencer::Equals(out_signed_cert, signed_drm_certificate_));
EXPECT_TRUE(MessageDifferencer::Equals(out_cert, drm_certificates_[2]));
}
TEST_F(DrmRootCertificateTest, DrmRootCertificateDigest) {
const std::string test_cert_hash(
"49f917b1bdfed78002a58e799a58e940"
"1fffaaed9d8d80752782b066757e2c8c");
const std::string dev_cert_hash(
"0e25ee95476a770f30b98ac5ef778b3f"
"137b66c29385b84f547a361b4724b17d");
const std::string prod_cert_hash(
"d62fdabc9286648a81f7d3bedaf2f5a5"
"27bbad39bc38da034ba98a21569adb9b");
EXPECT_EQ(test_cert_hash,
DrmRootCertificate::GetDigest(kCertificateTypeTesting));
EXPECT_EQ(dev_cert_hash,
DrmRootCertificate::GetDigest(kCertificateTypeDevelopment));
EXPECT_EQ(prod_cert_hash,
DrmRootCertificate::GetDigest(kCertificateTypeProduction));
TEST_F(DrmRootCertificateTest, InvalidSignedDrmCertificate) {
EXPECT_EQ(util::Status(error_space, INVALID_DRM_CERTIFICATE,
"invalid-signed-drm-certificate"),
root_cert_->VerifyCertificate("pure garbage", nullptr, nullptr));
}
TEST_F(DrmRootCertificateTest, InvalidSignerCertificate) {
GenerateSignedDrmCertificate();
signed_drm_certificate_.mutable_signer()->set_drm_certificate("more garbage");
EXPECT_EQ(util::Status(error_space, INVALID_DRM_CERTIFICATE,
"invalid-signer-certificate"),
root_cert_->VerifyCertificate(
signed_drm_certificate_.SerializeAsString(), nullptr, nullptr));
}
TEST_F(DrmRootCertificateTest, MissingDrmCertificate) {
GenerateSignedDrmCertificate();
signed_drm_certificate_.clear_drm_certificate();
EXPECT_EQ(util::Status(error_space, INVALID_DRM_CERTIFICATE,
"invalid-drm-certificate"),
root_cert_->VerifyCertificate(
signed_drm_certificate_.SerializeAsString(), nullptr, nullptr));
}
TEST_F(DrmRootCertificateTest, InvalidDrmCertificate) {
GenerateSignedDrmCertificate();
signed_drm_certificate_.set_drm_certificate("junk");
EXPECT_EQ(util::Status(error_space, INVALID_DRM_CERTIFICATE,
"invalid-drm-certificate"),
root_cert_->VerifyCertificate(
signed_drm_certificate_.SerializeAsString(), nullptr, nullptr));
}
TEST_F(DrmRootCertificateTest, InvalidPublicKey) {
drm_certificates_[0].set_public_key("rubbish");
GenerateSignedDrmCertificate();
EXPECT_EQ(util::Status(error_space, INVALID_DRM_CERTIFICATE,
"invalid-signer-public-key"),
root_cert_->VerifyCertificate(
signed_drm_certificate_.SerializeAsString(), nullptr, nullptr));
}
TEST_F(DrmRootCertificateTest, MissingPublicKey) {
drm_certificates_[2].clear_public_key();
GenerateSignedDrmCertificate();
EXPECT_EQ(
util::Status(error_space, INVALID_DRM_CERTIFICATE, "missing-public-key"),
root_cert_->VerifyCertificate(signed_drm_certificate_.SerializeAsString(),
nullptr, nullptr));
}
TEST_F(DrmRootCertificateTest, MissingCreationTime) {
drm_certificates_[2].clear_creation_time_seconds();
GenerateSignedDrmCertificate();
EXPECT_EQ(util::Status(error_space, INVALID_DRM_CERTIFICATE,
"missing-creation-time"),
root_cert_->VerifyCertificate(
signed_drm_certificate_.SerializeAsString(), nullptr, nullptr));
}
TEST_F(DrmRootCertificateTest, MissingSerialNumber) {
drm_certificates_[2].set_serial_number("");
GenerateSignedDrmCertificate();
EXPECT_EQ(util::Status(error_space, INVALID_DRM_CERTIFICATE,
"missing-serial-number"),
root_cert_->VerifyCertificate(
signed_drm_certificate_.SerializeAsString(), nullptr, nullptr));
}
TEST_F(DrmRootCertificateTest, InvalidSignatureWithNoCache) {
GenerateSignedDrmCertificate();
signed_drm_certificate_.mutable_signer()->set_signature(
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
EXPECT_EQ(util::Status(error_space, INVALID_SIGNATURE,
"cache-miss-invalid-signature"),
root_cert_->VerifyCertificate(
signed_drm_certificate_.SerializeAsString(), nullptr, nullptr));
}
TEST_F(DrmRootCertificateTest, InvalidSignatureWithCache) {
GenerateSignedDrmCertificate();
// Verify and cache.
ASSERT_EQ(util::OkStatus(),
root_cert_->VerifyCertificate(
signed_drm_certificate_.SerializeAsString(), nullptr, nullptr));
// Verify success using cache.
ASSERT_EQ(util::OkStatus(),
root_cert_->VerifyCertificate(
signed_drm_certificate_.SerializeAsString(), nullptr, nullptr));
// Verify failure using cache.
signed_drm_certificate_.mutable_signer()->set_signature(
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
EXPECT_EQ(
util::Status(error_space, INVALID_SIGNATURE, "cached-signature-mismatch"),
root_cert_->VerifyCertificate(signed_drm_certificate_.SerializeAsString(),
nullptr, nullptr));
}
} // namespace widevine

View File

@@ -108,49 +108,24 @@ DrmServiceCertificateMap* DrmServiceCertificateMap::GetInstance() {
} // namespace
util::Status DrmServiceCertificate::AddDrmServiceCertificate(
const std::string& root_public_key, const std::string& service_certificate,
const DrmRootCertificate* root_cert, const std::string& service_certificate,
const std::string& service_private_key,
const std::string& service_private_key_passphrase) {
std::unique_ptr<RsaPublicKey> root_key(RsaPublicKey::Create(root_public_key));
if (root_key == nullptr) {
return util::Status(error_space, INVALID_DRM_CERTIFICATE,
"root-certificate-rsa-public-key-failed");
}
SignedDrmCertificate signed_cert;
if (!signed_cert.ParseFromString(service_certificate)) {
return util::Status(error_space, INVALID_SERVICE_CERTIFICATE,
"signed-certificate-parse-failed");
}
if (!root_key->VerifySignature(signed_cert.drm_certificate(),
signed_cert.signature())) {
return util::Status(error_space, INVALID_SERVICE_CERTIFICATE,
"certificate-signature-verification-failed");
}
DrmCertificate drm_cert;
if (!drm_cert.ParseFromString(signed_cert.drm_certificate())) {
return util::Status(error_space, INVALID_SERVICE_CERTIFICATE,
"certificate-parse-failed");
util::Status status =
root_cert->VerifyCertificate(service_certificate, nullptr, &drm_cert);
if (!status.ok()) {
return status;
}
if (drm_cert.type() != DrmCertificate::SERVICE) {
return util::Status(error_space, INVALID_SERVICE_CERTIFICATE,
"not-service-certificate");
}
if (drm_cert.serial_number().empty()) {
return util::Status(error_space, INVALID_SERVICE_CERTIFICATE,
"missing-certificate-serial-number");
}
if (drm_cert.provider_id().empty()) {
return util::Status(error_space, INVALID_SERVICE_CERTIFICATE,
"missing-certificate-service-id");
}
if (!drm_cert.has_creation_time_seconds()) {
return util::Status(error_space, INVALID_SERVICE_CERTIFICATE,
"missing-certificate-creation-time");
}
if (drm_cert.public_key().empty()) {
return util::Status(error_space, INVALID_SERVICE_CERTIFICATE,
"missing-certificate-public-key");
}
std::unique_ptr<RsaPublicKey> public_key(
RsaPublicKey::Create(drm_cert.public_key()));
if (!public_key) {
@@ -178,21 +153,6 @@ util::Status DrmServiceCertificate::AddDrmServiceCertificate(
return util::OkStatus();
}
util::Status DrmServiceCertificate::AddDrmServiceCertificate(
CertificateType root_cert_type, const std::string& service_certificate,
const std::string& service_private_key,
const std::string& service_private_key_passphrase) {
std::unique_ptr<DrmRootCertificate> root_cert;
util::Status status =
DrmRootCertificate::CreateByType(root_cert_type, &root_cert);
if (!status.ok()) {
return status;
}
return AddDrmServiceCertificate(root_cert->public_key(), service_certificate,
service_private_key,
service_private_key_passphrase);
}
const DrmServiceCertificate*
DrmServiceCertificate::GetDefaultDrmServiceCertificate() {
return DrmServiceCertificateMap::GetInstance()->GetDefaultCert();
@@ -212,30 +172,15 @@ const DrmServiceCertificate* DrmServiceCertificate::GetDrmServiceCertificate(
}
util::Status DrmServiceCertificate::SetDefaultDrmServiceCertificate(
const std::string& root_public_key, const std::string& service_certificate,
const DrmRootCertificate* root_drm_cert, const std::string& service_certificate,
const std::string& service_private_key,
const std::string& service_private_key_passphrase) {
DrmServiceCertificateMap::GetInstance()->ClearDefaultDrmServiceCertificate();
return AddDrmServiceCertificate(root_public_key, service_certificate,
return AddDrmServiceCertificate(root_drm_cert, service_certificate,
service_private_key,
service_private_key_passphrase);
}
util::Status DrmServiceCertificate::SetDefaultDrmServiceCertificate(
CertificateType root_cert_type, const std::string& service_certificate,
const std::string& service_private_key,
const std::string& service_private_key_passphrase) {
std::unique_ptr<DrmRootCertificate> root_cert;
util::Status status =
DrmRootCertificate::CreateByType(root_cert_type, &root_cert);
if (!status.ok()) {
return status;
}
return SetDefaultDrmServiceCertificate(
root_cert->public_key(), service_certificate, service_private_key,
service_private_key_passphrase);
}
util::Status DrmServiceCertificate::DecryptClientIdentification(
const EncryptedClientIdentification& encrypted_client_id,
ClientIdentification* client_id) {

View File

@@ -29,13 +29,16 @@ class RequestInspectorTest;
namespace widevine {
class ClientIdentification;
class DrmRootCertificate;
class EncryptedClientIdentification;
// TODO(user): Add a DrmCertificateList class to provide the static method
// functionality.
class DrmServiceCertificate {
public:
// Create a new DrmServiceCertificate object and add it to the list of valid
// service certificates. |root_cert_type| indicates which root public key to
// use to verify |service_certificate|, |service_certificate| is a
// service certificates. |drm_root_cert| is the root certificate for the type
// of certifiate being added. |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,
@@ -46,16 +49,16 @@ class DrmServiceCertificate {
// used as the default service certificate.
// This method is thread-safe.
static util::Status AddDrmServiceCertificate(
CertificateType root_cert_type, const std::string& service_certificate,
const std::string& service_private_key,
const DrmRootCertificate* root_drm_cert,
const std::string& service_certificate, const std::string& service_private_key,
const std::string& service_private_key_passphrase);
// Same as AddDrmServiceCertificate(), but will clear the default service
// certificate if it's set. This will result in this service certificate
// being set as the default service certificate.
static util::Status SetDefaultDrmServiceCertificate(
CertificateType root_cert_type, const std::string& service_certificate,
const std::string& service_private_key,
const DrmRootCertificate* root_drm_cert,
const std::string& service_certificate, const std::string& service_private_key,
const std::string& service_private_key_passphrase);
// Returns the default service certificate. Will return null if no default

View File

@@ -6,6 +6,8 @@
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#include "common/drm_service_certificate.h"
#include <memory>
#include "glog/logging.h"
@@ -14,11 +16,11 @@
#include "testing/gunit.h"
#include "absl/strings/escaping.h"
#include "common/aes_cbc_util.h"
#include "common/drm_service_certificate.h"
#include "common/drm_root_certificate.h"
#include "common/rsa_key.h"
#include "common/rsa_test_keys.h"
#include "common/rsa_util.h"
#include "common/test_certificates.h"
#include "common/test_drm_certificates.h"
#include "protos/public/client_identification.pb.h"
#include "protos/public/drm_certificate.pb.h"
#include "protos/public/errors.pb.h" // IWYU pragma: keep
@@ -38,7 +40,9 @@ class DrmServiceCertificateTest : public ::testing::Test {
iv_(absl::HexStringToBytes(kIv)),
root_private_key_(
RsaPrivateKey::Create(test_keys_.private_test_key_1_3072_bits())) {
CHECK(root_private_key_ != nullptr);
EXPECT_TRUE(root_private_key_);
EXPECT_OK(
DrmRootCertificate::CreateByType(kCertificateTypeTesting, &root_cert_));
client_id_.set_type(ClientIdentification::DRM_DEVICE_CERTIFICATE);
client_id_.set_token(test_certs_.test_user_device_certificate());
}
@@ -78,8 +82,7 @@ class DrmServiceCertificateTest : public ::testing::Test {
return util::Status(util::error::INTERNAL, "");
}
return DrmServiceCertificate::SetDefaultDrmServiceCertificate(
test_keys_.public_test_key_1_3072_bits(), signed_cert,
encrypted_private_key, kPassphrase);
root_cert_.get(), signed_cert, encrypted_private_key, kPassphrase);
}
util::Status AddDrmServiceCertificate(const std::string& serial_number,
@@ -95,8 +98,7 @@ class DrmServiceCertificateTest : public ::testing::Test {
return util::Status(util::error::INTERNAL, "");
}
return DrmServiceCertificate::AddDrmServiceCertificate(
test_keys_.public_test_key_1_3072_bits(), signed_cert,
encrypted_private_key, kPassphrase);
root_cert_.get(), signed_cert, encrypted_private_key, kPassphrase);
}
void EncryptClientIdentification(
@@ -118,10 +120,11 @@ class DrmServiceCertificateTest : public ::testing::Test {
}
RsaTestKeys test_keys_;
TestCertificates test_certs_;
TestDrmCertificates test_certs_;
std::string privacy_key_;
std::string iv_;
std::unique_ptr<RsaPrivateKey> root_private_key_;
std::unique_ptr<DrmRootCertificate> root_cert_;
ClientIdentification client_id_;
};
@@ -201,6 +204,7 @@ TEST_F(DrmServiceCertificateTest, MultipleCertsPerService) {
std::string serial_number1("serial_number1");
std::string serial_number2("serial_number2");
std::string serial_number3("serial_number3");
std::string serial_number4("serial_number4");
std::string provider_id("someservice.com");
uint32_t creation_time_seconds(1234);
@@ -249,13 +253,13 @@ TEST_F(DrmServiceCertificateTest, MultipleCertsPerService) {
ASSERT_TRUE(drm_cert.ParseFromString(signed_cert.drm_certificate()));
EXPECT_EQ(serial_number1, drm_cert.serial_number());
EXPECT_OK(SetDefaultDrmServiceCertificate(serial_number2, provider_id,
EXPECT_OK(SetDefaultDrmServiceCertificate(serial_number4, provider_id,
creation_time_seconds));
default_cert = DrmServiceCertificate::GetDefaultDrmServiceCertificate();
ASSERT_TRUE(default_cert);
ASSERT_TRUE(signed_cert.ParseFromString(default_cert->certificate()));
ASSERT_TRUE(drm_cert.ParseFromString(signed_cert.drm_certificate()));
EXPECT_EQ(serial_number2, drm_cert.serial_number());
EXPECT_EQ(serial_number4, drm_cert.serial_number());
}
TEST_F(DrmServiceCertificateTest, DrmServiceCertificateNotFound) {

View File

@@ -54,13 +54,13 @@ class MockRsaKeyFactory : public RsaKeyFactory {
MockRsaKeyFactory() {}
~MockRsaKeyFactory() override {}
MOCK_METHOD1(CreateFromPkcs1PrivateKey,
MOCK_CONST_METHOD1(CreateFromPkcs1PrivateKey,
std::unique_ptr<RsaPrivateKey>(const std::string& private_key));
MOCK_METHOD2(
MOCK_CONST_METHOD2(
CreateFromPkcs8PrivateKey,
std::unique_ptr<RsaPrivateKey>(const std::string& private_key,
const std::string& private_key_passphrase));
MOCK_METHOD1(CreateFromPkcs1PublicKey,
MOCK_CONST_METHOD1(CreateFromPkcs1PublicKey,
std::unique_ptr<RsaPublicKey>(const std::string& public_key));
private:

View File

@@ -14,6 +14,7 @@
#include "openssl/bio.h"
#include "openssl/evp.h"
#include "openssl/pkcs7.h"
#include "openssl/rsa.h"
#include "openssl/x509v3.h"
@@ -46,6 +47,7 @@ using ScopedOpenSSLStackOnly =
using ScopedBIGNUM = ScopedOpenSSLType<BIGNUM, BN_free>;
using ScopedBIO = ScopedOpenSSLType<BIO, BIO_vfree>;
using ScopedPKCS7 = ScopedOpenSSLType<PKCS7, PKCS7_free>;
using ScopedPKEY = ScopedOpenSSLType<EVP_PKEY, EVP_PKEY_free>;
using ScopedRSA = ScopedOpenSSLType<RSA, RSA_free>;
using ScopedX509 = ScopedOpenSSLType<X509, X509_free>;
@@ -59,6 +61,7 @@ using ScopedX509StoreCtx =
ScopedOpenSSLType<X509_STORE_CTX, X509_STORE_CTX_free>;
using ScopedX509Req = ScopedOpenSSLType<X509_REQ, X509_REQ_free>;
using ScopedAsn1UtcTime = ScopedOpenSSLType<ASN1_UTCTIME, ASN1_UTCTIME_free>;
using ScopedAsn1Time = ScopedOpenSSLType<ASN1_TIME, ASN1_TIME_free>;
using ScopedAsn1Utc8String =
ScopedOpenSSLType<ASN1_UTF8STRING, ASN1_UTF8STRING_free>;
using ScopedAsn1Integer = ScopedOpenSSLType<ASN1_INTEGER, ASN1_INTEGER_free>;

View File

@@ -107,7 +107,7 @@ RemoteAttestationVerifier& RemoteAttestationVerifier::get() {
return instance;
}
void RemoteAttestationVerifier::EnableTestCertificates(bool enable) {
void RemoteAttestationVerifier::EnableTestDrmCertificates(bool enable) {
absl::WriterMutexLock lock(&ca_mutex_);
enable_test_certificates_ = enable;
ca_.reset();

View File

@@ -40,7 +40,7 @@ class RemoteAttestationVerifier {
// Call to use the test (non-production) remote attestation root certificate.
// This method is thread-safe.
void EnableTestCertificates(bool enable);
void EnableTestDrmCertificates(bool enable);
// Call to verify a RemoteAttestation challenge response, used in licensing
// protocol.

View File

@@ -286,12 +286,12 @@ RsaKeyFactory::RsaKeyFactory() {}
RsaKeyFactory::~RsaKeyFactory() {}
std::unique_ptr<RsaPrivateKey> RsaKeyFactory::CreateFromPkcs1PrivateKey(
const std::string& private_key) {
const std::string& private_key) const {
return std::unique_ptr<RsaPrivateKey>(RsaPrivateKey::Create(private_key));
}
std::unique_ptr<RsaPrivateKey> RsaKeyFactory::CreateFromPkcs8PrivateKey(
const std::string& private_key, const std::string& private_key_passphrase) {
const std::string& private_key, const std::string& private_key_passphrase) const {
std::string pkcs1_key;
const bool result =
private_key_passphrase.empty()
@@ -306,7 +306,7 @@ std::unique_ptr<RsaPrivateKey> RsaKeyFactory::CreateFromPkcs8PrivateKey(
}
std::unique_ptr<RsaPublicKey> RsaKeyFactory::CreateFromPkcs1PublicKey(
const std::string& public_key) {
const std::string& public_key) const {
return std::unique_ptr<RsaPublicKey>(RsaPublicKey::Create(public_key));
}

View File

@@ -60,9 +60,12 @@ class RsaPrivateKey {
// Returns the RSA key size (modulus) in bytes.
virtual uint32_t KeySize() const;
private:
friend class RsaPublicKey;
friend class X509CertificateBuilder; // TODO(user): Get rid of this.
const RSA* key() const { return key_; }
private:
RSA* key_;
// SWIG appears to think this declaration is a syntax error. Excluding it for
@@ -110,9 +113,12 @@ class RsaPublicKey {
// Returns the RSA key size (modulus) in bytes.
virtual uint32_t KeySize() const;
private:
friend class RsaPrivateKey;
friend class X509CertificateBuilder; // TODO(user): Get rid of this.
const RSA* key() const { return key_; }
private:
RSA* key_;
// SWIG appears to think this declaration is a syntax error. Excluding it for
@@ -130,16 +136,16 @@ class RsaKeyFactory {
// Create an RsaPrivateKey object using a DER encoded PKCS#1 RSAPrivateKey.
virtual std::unique_ptr<RsaPrivateKey> CreateFromPkcs1PrivateKey(
const std::string& private_key);
const std::string& private_key) const;
// Create a PKCS#1 RsaPrivateKey object using an PKCS#8 PrivateKeyInfo or
// EncryptedPrivateKeyInfo (if |private_key_passprhase| is not empty).
virtual std::unique_ptr<RsaPrivateKey> CreateFromPkcs8PrivateKey(
const std::string& private_key, const std::string& private_key_passphrase);
const std::string& private_key, const std::string& private_key_passphrase) const;
// Create an RsaPublicKey object using a DER encoded PKCS#1 RSAPublicKey.
virtual std::unique_ptr<RsaPublicKey> CreateFromPkcs1PublicKey(
const std::string& public_key);
const std::string& public_key) const;
private:
DISALLOW_COPY_AND_ASSIGN(RsaKeyFactory);

76
common/status.cc Normal file
View File

@@ -0,0 +1,76 @@
////////////////////////////////////////////////////////////////////////////////
// Copyright 2017 Google LLC.
//
// This software is licensed under the terms defined in the Widevine Master
// License Agreement. For a copy of this agreement, please contact
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#include "common/status.h"
#include <string>
#include "absl/base/macros.h"
#include "absl/strings/str_cat.h"
#include "util/status.h"
namespace widevine {
namespace util {
namespace {
const char* kGenericErrorStatusMessage[] = {"OK",
"unknown_error",
"unknown_error",
"invalid_argument",
"unknown_error",
"not_found",
"already_exists",
"permission_denied",
"unknown_error",
"unknown_error",
"unknown_error",
"unknown_error",
"unimplemented",
"internal",
"unavailable"};
} // namespace
class GenericErrorSpace : public util::ErrorSpaceImpl<GenericErrorSpace> {
public:
static std::string space_name();
static std::string code_to_string(int code);
};
std::string GenericErrorSpace::space_name() { return "generic"; }
std::string GenericErrorSpace::code_to_string(int code) {
static_assert(
ABSL_ARRAYSIZE(kGenericErrorStatusMessage) == error::NUM_ERRORS,
"mismatching generic error status message and generic error status.");
if (code >= 0 && code < error::NUM_ERRORS)
return kGenericErrorStatusMessage[code];
return std::to_string(code);
}
const util::ErrorSpace* Status::canonical_space() {
return GenericErrorSpace::Get();
}
std::string Status::ToString() const {
if (status_code_ == error::OK) return "OK";
return absl::StrCat("Errors::", error_space_->String(status_code_), ": ",
error_message_);
}
std::ostream& operator<<(std::ostream& os, const Status& x) {
os << x.ToString();
return os;
}
} // namespace util
} // namespace widevine

View File

@@ -6,8 +6,8 @@
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#ifndef UTIL_STATUS_H_
#define UTIL_STATUS_H_
#ifndef COMMON_STATUS_H_
#define COMMON_STATUS_H_
#include <string>
@@ -54,66 +54,57 @@ enum StatusCode {
} // namespace error
class GenericErrorSpace : public ErrorSpaceImpl<GenericErrorSpace> {
public:
static std::string SpaceName();
static std::string CodeToString(int code);
};
class Status {
public:
Status() : status_code_(error::OK) {}
~Status() {}
Status() = default;
~Status() = default;
explicit Status(error::StatusCode c) : status_code_(c) {}
Status(error::StatusCode c, const std::string& error_message)
: status_code_(c), error_message_(error_message) {}
Status(const ErrorSpace* e, error::StatusCode c,
const std::string& error_message) {
SetError(e, c, error_message);
}
Status(const ErrorSpace* e, int error, const std::string& error_message) {
SetError(e, error, error_message);
}
void SetError(const ErrorSpace* e, int c, const std::string& error_message) {
error_space_ = e;
status_code_ = c;
error_message_ = error_message;
}
Status(const util::ErrorSpace* e, error::StatusCode c,
const std::string& error_message)
: error_space_(e), status_code_(c), error_message_(error_message) {}
Status(const util::ErrorSpace* e, int error, const std::string& error_message)
: error_space_(e), status_code_(error), error_message_(error_message) {}
bool ok() const { return status_code_ == error::OK; }
const ErrorSpace* error_space() const { return error_space_; }
static const ErrorSpace* canonical_space() {
return GenericErrorSpace::Get();
}
const util::ErrorSpace* error_space() const { return error_space_; }
static const util::ErrorSpace* canonical_space();
std::string ToString() const;
std::string error_message() const { return error_message_; }
int error_code() const { return status_code_; }
private:
const ErrorSpace* error_space_ = GenericErrorSpace::Get();
int status_code_;
const util::ErrorSpace* error_space_ = canonical_space();
int status_code_ = error::OK;
std::string error_message_;
}; // class Status
};
inline Status OkStatus() { return Status(); }
// Here error_message_ is ignored during comparison.
inline bool operator==(const Status& s1, const Status& s2) {
return s1.error_space() == s2.error_space() &&
s1.error_code() == s2.error_code();
s1.error_code() == s2.error_code() &&
s1.error_message() == s2.error_message();
}
inline bool operator!=(const Status& s1, const Status& s2) {
return s1.error_space() != s2.error_space() ||
s1.error_code() != s2.error_code();
return !(s1 == s2);
}
// Prints a human-readable representation of 'x' to 'os'.
std::ostream& operator<<(std::ostream& os, const Status& x);
#define CHECK_OK(expression) CHECK_EQ(util::error::OK, expression.error_code())
#define CHECK_OK(expression) CHECK(expression.ok()) << expression.ToString()
} // namespace util
} // namespace widevine
#endif // UTIL_STATUS_H_
#endif // COMMON_STATUS_H_

View File

@@ -6,8 +6,7 @@
// widevine-licensing@google.com.
////////////////////////////////////////////////////////////////////////////////
#include "util/status.h"
#include "common/status.h"
#include "testing/gunit.h"
namespace widevine {
@@ -27,13 +26,13 @@ TEST(StatusTest, OK_Status2) {
TEST(StatusTest, ALREADY_EXISTS_Status) {
Status status(error::ALREADY_EXISTS, "it is already exist");
EXPECT_EQ("Errors::ALREADY_EXISTS: it is already exist", status.ToString());
EXPECT_EQ("Errors::already_exists: it is already exist", status.ToString());
}
// test case for status in boundary cases.
TEST(StatusTest, UNAVAILABLE_Status) {
Status status(error::UNAVAILABLE, "unavailable");
EXPECT_EQ("Errors::UNAVAILABLE: unavailable", status.ToString());
EXPECT_EQ("Errors::unavailable: unavailable", status.ToString());
}
TEST(StatusTest, NoNameCode) {
@@ -43,7 +42,7 @@ TEST(StatusTest, NoNameCode) {
TEST(StatusTest, EQUAL_OPERATOR) {
Status status1(error::ALREADY_EXISTS, "already exists 1");
Status status2(error::ALREADY_EXISTS, "already exists 2");
Status status2(error::ALREADY_EXISTS, "already exists 1");
EXPECT_EQ(status1, status2);
}
@@ -59,5 +58,6 @@ TEST(StatusTest, NOT_EQUAL_OPERATOR_NONE_MSG) {
EXPECT_NE(status1, status2);
}
} // namespace util
} // namespace widevine

View File

@@ -8,7 +8,7 @@
//
#include "common/test_certificates.h"
#include "common/test_drm_certificates.h"
namespace widevine {
@@ -311,7 +311,7 @@ const unsigned char kTestDrmServiceCertificate[] = {
0x34, 0xba, 0xf5, 0xec, 0xaf, 0x26, 0xfb, 0x64, 0xc4, 0x38, 0x7e, 0xdb,
0x51, 0x28, 0x49, 0xa7, 0x12, 0x88, 0xa5, 0x6d, 0xa2, 0xfa};
TestCertificates::TestCertificates()
TestDrmCertificates::TestDrmCertificates()
: test_root_certificate_(
kTestRootCertificate,
kTestRootCertificate + sizeof(kTestRootCertificate)),

View File

@@ -10,18 +10,18 @@
// Class contains certificates that can be used for testing. Provides methods
// to retrieve a test root certificate, a test intermediate certificate and a
// test user device certificate.
#ifndef COMMON_TEST_CERTIFICATES_H_
#define COMMON_TEST_CERTIFICATES_H_
#ifndef COMMON_TEST_DRM_CERTIFICATES_H_
#define COMMON_TEST_DRM_CERTIFICATES_H_
#include <string>
#include "base/macros.h"
namespace widevine {
class TestCertificates {
class TestDrmCertificates {
public:
TestCertificates();
virtual ~TestCertificates() {}
TestDrmCertificates();
virtual ~TestDrmCertificates() {}
// returns a test root certificate
const std::string& test_root_certificate() const { return test_root_certificate_; }
@@ -47,8 +47,8 @@ class TestCertificates {
const std::string test_user_device_certificate_;
const std::string test_service_certificate_;
DISALLOW_COPY_AND_ASSIGN(TestCertificates);
DISALLOW_COPY_AND_ASSIGN(TestDrmCertificates);
};
} // namespace widevine
#endif // COMMON_TEST_CERTIFICATES_H_
#endif // COMMON_TEST_DRM_CERTIFICATES_H_

View File

@@ -248,7 +248,7 @@ VmpChecker::VmpChecker() : allow_development_vmp_(false) {}
VmpChecker::~VmpChecker() {}
util::Status VmpChecker::SelectDrmCertificateType(CertificateType cert_type) {
util::Status VmpChecker::SelectCertificateType(CertificateType cert_type) {
std::unique_ptr<X509Cert> ca_cert(new X509Cert);
util::Status status = ca_cert->LoadDer(
cert_type == kCertificateTypeProduction

View File

@@ -35,7 +35,7 @@ class VmpChecker {
static VmpChecker* Instance();
// Select the type of root to use. Not thread-safe.
virtual util::Status SelectDrmCertificateType(CertificateType root_type);
virtual util::Status SelectCertificateType(CertificateType cert_type);
// Verify VMP data and return appropriate result.
virtual util::Status VerifyVmpData(const std::string& vmp_data, Result* result);

View File

@@ -154,8 +154,8 @@ const char kSameAsPrevious[] = "";
class VmpCheckerTest : public ::testing::Test {
public:
void SetUp() override {
ASSERT_OK(VmpChecker::Instance()->SelectDrmCertificateType(
kCertificateTypeTesting));
ASSERT_OK(
VmpChecker::Instance()->SelectCertificateType(kCertificateTypeTesting));
vmp_data_.Clear();
VmpChecker::Instance()->set_allow_development_vmp(true);
signing_key_.reset(

View File

@@ -184,6 +184,24 @@ std::string X509Cert::GetSerialNumber() const {
return result;
}
bool X509Cert::GetNotBeforeSeconds(int64_t* valid_start_seconds) const {
if (openssl_cert_ == nullptr) {
return false;
}
return Asn1TimeToEpochSeconds(X509_get0_notBefore(openssl_cert_),
valid_start_seconds)
.ok();
}
bool X509Cert::GetNotAfterSeconds(int64_t* valid_end_seconds) const {
if (openssl_cert_ == nullptr) {
return false;
}
return Asn1TimeToEpochSeconds(X509_get0_notAfter(openssl_cert_),
valid_end_seconds)
.ok();
}
bool X509Cert::IsCaCertificate() const {
return X509_check_ca(openssl_cert_) != 0;
}
@@ -204,6 +222,40 @@ bool X509Cert::GetV3BooleanExtension(const std::string& oid, bool* value) const
return true;
}
util::Status X509Cert::Asn1TimeToEpochSeconds(const ASN1_TIME* asn1_time,
int64_t* epoch_seconds) const {
if (asn1_time == nullptr) {
// This code is exported to shared source. The exported code does not yet
// support MakeStatus.
// NOLINTNEXTLINE
return util::Status(util::error::INVALID_ARGUMENT,
"asn1_time cannot be null.");
}
if (epoch_seconds == nullptr) {
// NOLINTNEXTLINE
return util::Status(util::error::INVALID_ARGUMENT,
"epoch_seconds cannot be null.");
}
ScopedAsn1Time epoch_time(ASN1_TIME_new());
if (!ASN1_TIME_set(epoch_time.get(), 0)) {
// NOLINTNEXTLINE
return util::Status(util::error::INTERNAL, "Failed to set epoch time.");
}
int day = 0;
int seconds = 0;
if (!ASN1_TIME_diff(&day, &seconds, epoch_time.get(), asn1_time)) {
// NOLINTNEXTLINE
return util::Status(util::error::INTERNAL,
"Failed to convert asn1 time to epoch time.");
}
*epoch_seconds = 24L * 3600L * day + seconds;
return util::OkStatus();
}
X509CertChain::~X509CertChain() { Reset(); }
void X509CertChain::Reset() {
@@ -248,12 +300,48 @@ util::Status X509CertChain::LoadPkcs7(const std::string& pk7_cert_chain) {
}
while (sk_X509_num(cert_stack.get()) > 0) {
cert_chain_.push_back(new X509Cert(sk_X509_pop(cert_stack.get())));
cert_chain_.insert(cert_chain_.begin(),
new X509Cert(sk_X509_pop(cert_stack.get())));
}
return util::OkStatus();
}
std::string X509CertChain::GetPkcs7() {
std::string pkcs7_cert;
ScopedX509Stack cert_stack(sk_X509_new_null());
for (X509Cert* cert : cert_chain_) {
// X509 stack takes ownership of certificates. Copy certificates to retain
// |cert_chain_|.
X509Cert cert_copy;
if (!cert_copy.LoadPem(cert->GetPem()).ok()) {
LOG(WARNING) << "Certificate chain serialization failed";
return "";
}
X509* openssl_cert_copy = const_cast<X509*>(cert_copy.openssl_cert());
cert_copy.openssl_cert_ = nullptr;
sk_X509_push(cert_stack.get(), openssl_cert_copy);
}
ScopedPKCS7 pkcs7(
PKCS7_sign(nullptr, nullptr, cert_stack.get(), nullptr, PKCS7_DETACHED));
if (!pkcs7) {
LOG(WARNING) << "Could not convert certificate chain to PKCS7";
return "";
}
ScopedBIO bio(BIO_new(BIO_s_mem()));
if (bio.get() == nullptr || !i2d_PKCS7_bio(bio.get(), pkcs7.get())) {
LOG(WARNING) << "Failed writing PKCS7 to bio";
return "";
}
int cert_size = BIO_pending(bio.get());
pkcs7_cert.resize(cert_size);
if (BIO_read(bio.get(), &pkcs7_cert[0], cert_size) != cert_size) {
LOG(WARNING) << "BIO_read failure";
return "";
}
return pkcs7_cert;
}
X509Cert* X509CertChain::GetCert(size_t cert_index) const {
if (cert_index >= cert_chain_.size()) {
return NULL;
@@ -325,6 +413,25 @@ util::Status X509CA::VerifyCertChain(const X509CertChain& cert_chain) {
return OpenSslX509Verify(leaf_cert->openssl_cert(), intermediates.get());
}
util::Status X509CA::VerifyCertWithChain(const X509Cert& cert,
const X509CertChain& cert_chain) {
ScopedX509StackOnly intermediates(sk_X509_new_null());
if (!intermediates) {
// MakeStatus is now preferred. But we don't support it in the exported
// version, yet. So, ignore lint here.
// NOLINTNEXTLINE
return util::Status(
util::Status::canonical_space(), util::error::INTERNAL,
"Failed to allocate X.509 intermediate certificate stack");
}
for (size_t idx = 0; idx < cert_chain.GetNumCerts(); ++idx) {
sk_X509_push(intermediates.get(),
const_cast<X509*>(cert_chain.GetCert(idx)->openssl_cert()));
}
return OpenSslX509Verify(cert.openssl_cert(), intermediates.get());
}
util::Status X509CA::OpenSslX509Verify(const X509* cert,
STACK_OF(X509) * intermediates) {
DCHECK(cert);

View File

@@ -70,6 +70,16 @@ class X509Cert {
// if an error occurs.
std::string GetSerialNumber() const;
// Gets the start of the validity period for the certificate in seconds
// since the epoch. |valid_start_seconds| must not be null. Returns true on
// success, false otherwise.
bool GetNotBeforeSeconds(int64_t* valid_start_seconds) const;
// Gets the end of the validity period for the certificate in seconds
// since the epoch. |valid_end_seconds| must not be null. Returns true on
// success, false otherwise.
bool GetNotAfterSeconds(int64_t* valid_end_seconds) const;
// Returns true if the certificate is a CA (root or intermediate) certificate.
bool IsCaCertificate() const;
@@ -81,6 +91,8 @@ class X509Cert {
private:
explicit X509Cert(X509* openssl_cert);
util::Status Asn1TimeToEpochSeconds(const ASN1_TIME* asn1_time,
int64_t* epoch_seconds) const;
X509* openssl_cert_;
std::string subject_name_;
@@ -107,6 +119,10 @@ class X509CertChain {
// container.
util::Status LoadPkcs7(const std::string& pk7_cert_chain);
// Writes the |cert_chain_| to a DER-encoded PKCS#7 X.509 cryptographic
// message. The final message does not include signed data.
std::string GetPkcs7();
// Returns the number of certificates in the chain.
size_t GetNumCerts() const { return cert_chain_.size(); }
@@ -138,6 +154,12 @@ class X509CA {
// used when constructing X509CA. This method is thread-safe.
util::Status VerifyCertChain(const X509CertChain& cert_chain);
// Does X.509 PKI validation of |cert| using the |cert_chain|
// certificates. This method allows |cert| to be an ICA. This method is
// thread-safe.
util::Status VerifyCertWithChain(const X509Cert& cert,
const X509CertChain& cert_chain);
private:
util::Status InitializeStore();
util::Status OpenSslX509Verify(const X509* cert, STACK_OF(X509) * stack);

View File

@@ -52,6 +52,32 @@ const char kTestRootCaDerCert[] =
"d22de9a13c5092c92c297021c51a2a0a5250cf26c271ff262f25a7738ae4"
"c270d87191c13aefdd177b";
const char kTestRootCaPemCert[] =
"-----BEGIN CERTIFICATE-----\n"
"MIIEAzCCAuugAwIBAgIJAKJPlK965oMfMA0GCSqGSIb3DQEBBQUAMIGXMQswCQYD\n"
"VQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjERMA8GA1UEBwwIS2lya2xhbmQx\n"
"EzARBgNVBAoMCkdvb2dsZSBJbmMxETAPBgNVBAsMCFdpZGV2aW5lMRUwEwYDVQQD\n"
"DAxUZXN0IFJvb3QgQ0ExITAfBgkqhkiG9w0BCQEWEnRpbnNraXBAZ29vZ2xlLmNv\n"
"bTAeFw0xMzA4MTYwMDU3MTBaFw0zMzA4MTUwMDU3MTBaMIGXMQswCQYDVQQGEwJV\n"
"UzETMBEGA1UECAwKV2FzaGluZ3RvbjERMA8GA1UEBwwIS2lya2xhbmQxEzARBgNV\n"
"BAoMCkdvb2dsZSBJbmMxETAPBgNVBAsMCFdpZGV2aW5lMRUwEwYDVQQDDAxUZXN0\n"
"IFJvb3QgQ0ExITAfBgkqhkiG9w0BCQEWEnRpbnNraXBAZ29vZ2xlLmNvbTCCASIw\n"
"DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbu5inZn3c2LbVUXtHW37NhbHQs\n"
"YX1f1I8vv8s/LsQKCAvQTVUc5RlHGou07Fwsdb+KLSyvP4XZDp45OR372q5oBRMZ\n"
"DacbGyrkgpoVxEvBsZsXE0hEuUxvBtkhYzMjZXTz8RsNEMPGIUEOQmMMV86ekBBX\n"
"7aXDwiA+4q2AWg2TUvqR2kWm9IdbRSTBk8Qv2QSKECBOWyyCA0Arp2Dn4bQSbD4q\n"
"tCWPK/KM0xcN6Mc4pqH0z8wGSfqV8UFP2dCd1PURvAqb86WESjNNngpLlSXSeJvm\n"
"q6/i0MwgedzwMP+pvorj/iyrTr36SU1IqoxjJk0x4iCKnCj3PgEDzhZGg78CAwEA\n"
"AaNQME4wHQYDVR0OBBYEFE0w/xgaxPENqZ5qEsAeAqzK34QKMB8GA1UdIwQYMBaA\n"
"FE0w/xgaxPENqZ5qEsAeAqzK34QKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF\n"
"BQADggEBAHeem5jT7AZvKYYpA6AOnJglnZh8BLnmoubDOB7lnsHdDX3uedphLk36\n"
"o0ZciRaZPtet67JzQN4gyhAQZ/g0KyEk7A1dtTEne0ZTw7xysqja6uEg5TSOGjOP\n"
"bmjnEpQ2Am54Ak8E12axMiUuwVJALc7CgXQ0aqC6mX1/GvFA/wJb7IQfgDm6ENfM\n"
"CYzyRVT4y7KqMYdSBcZ98vBTDYeE+vY8T5ReYto3TK1hVeauRPWXvP9FZuoqrEJY\n"
"5K6BVpwO3dHfaSlTK0U4vSBLL/WEfLRqxzg8lv6C0i3poTxQksksKXAhxRoqClJQ\n"
"zybCcf8mLyWnc4rkwnDYcZHBOu/dF3s=\n"
"-----END CERTIFICATE-----\n";
const char kTestPemCert[] =
"-----BEGIN CERTIFICATE-----\n"
"MIIDwzCCAqsCAQIwDQYJKoZIhvcNAQEFBQAwgZ8xCzAJBgNVBAYTAlVTMRMwEQYD\n"
@@ -81,6 +107,9 @@ const char kTestPemCertSubjectField_CN[] =
"stable id/emailAddress=tinskip@google.com";
const char kTestPemCertSerialNumber[] = "\002";
const int64_t kTestPemCertNotBeforeSeconds = 1376689440;
const int64_t kTestPemCertNotAfterSeconds = 2007755040;
const char kTestPemCertChain[] =
"-----BEGIN CERTIFICATE-----\n"
"MIIDwzCCAqsCAQIwDQYJKoZIhvcNAQEFBQAwgZ8xCzAJBgNVBAYTAlVTMRMwEQYD\n"
@@ -130,6 +159,32 @@ const char kTestPemCertChain[] =
"6kIkGZCFP/ws7ctk+fQyjjttncIdL2k=\n"
"-----END CERTIFICATE-----\n";
const char kTestPemIca[] =
"-----BEGIN CERTIFICATE-----\n"
"MIIEAzCCAuugAwIBAgIBATANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UEBhMCVVMx\n"
"EzARBgNVBAgMCldhc2hpbmd0b24xETAPBgNVBAcMCEtpcmtsYW5kMRMwEQYDVQQK\n"
"DApHb29nbGUgSW5jMREwDwYDVQQLDAhXaWRldmluZTEVMBMGA1UEAwwMVGVzdCBS\n"
"b290IENBMSEwHwYJKoZIhvcNAQkBFhJ0aW5za2lwQGdvb2dsZS5jb20wHhcNMTMw\n"
"ODE2MjE0MTQ2WhcNMzMwODE1MjE0MTQ2WjCBnzELMAkGA1UEBhMCVVMxEzARBgNV\n"
"BAgMCldhc2hpbmd0b24xETAPBgNVBAcMCEtpcmtsYW5kMRMwEQYDVQQKDApHb29n\n"
"bGUgSW5jMREwDwYDVQQLDAhXaWRldmluZTEdMBsGA1UEAwwUVGVzdCBJbnRlcm1l\n"
"ZGlhdGUgQ0ExITAfBgkqhkiG9w0BCQEWEnRpbnNraXBAZ29vZ2xlLmNvbTCCASIw\n"
"DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANooBi6x3I9Incs6ytlPjBu7yEy5\n"
"f6BLf5NREE5nQm74Rt7PAA7YVDtxHP+pi1uyxsL3fUrx904s4tdXNRK85/2zn7+o\n"
"oZPYb8fH6dgl7ocmYeyC0jSmg7++ZiaS6OsjPSUTE2aEbAe6Q+ZhYsAbdkL7Z2dN\n"
"UJR9akhLEqlqfX4q5bWA0M3P/2/fqNYMS0w010Nwpd+KydbceT0rHQTmTGVsqCCL\n"
"gmaP9a8aQRMSP0dn5IOcc/K1Qnnfw1gxnjGF4aBP7KbCMxNBrbgBOwiTxgEMIcKZ\n"
"9IGszAcpftKX5ra3XePzFWCcnwilppaaE/2XWXkcAehc8d3xtkdAYZyVIBUCAwEA\n"
"AaNQME4wHQYDVR0OBBYEFDm35gzM6ll13HhZUbW5uDw7BieTMB8GA1UdIwQYMBaA\n"
"FE0w/xgaxPENqZ5qEsAeAqzK34QKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF\n"
"BQADggEBALj+/Z8ygfWVNncV0N9UsAcwlGUe5ME+VoXUF/0SOmdrc8LtPc2Dkc8b\n"
"xiQN1wHxE/OFsbsOdobPzwOBh67KyYyVWtxzzsLO0MHGxsbOmwa1AersoP4x8xoC\n"
"HaBU90cviYqz5k6rZyBIlFIrM5lqG1JB3U0kTceG/1sqwRAAu94BYqMW1iWyr9Mq\n"
"ASRCVBOrksWda4pZkCLp62vk7ItOcs2PrHf6UWbANTDH+8Q+pIw2wuJ5lf/imqKO\n"
"qrYCJmAi6VBa2jyHqXVPMk6lL1Rmdk4UgOsRvsbmKzb2vYeWIwhsXY5Spo3WVTLv\n"
"6kIkGZCFP/ws7ctk+fQyjjttncIdL2k=\n"
"-----END CERTIFICATE-----\n";
const char kTestPk7CertChain[] =
"308207fb06092a864886f70d010702a08207ec308207e80201013100300b"
"06092a864886f70d010701a08207ce308203c3308202ab020102300d0609"
@@ -293,6 +348,7 @@ const char kTestDevCodeSigningCert[] =
"5MXS+h+FxQ6QUar2q1zHc/0Gr1hLzA6HYBmI0/AF8LsHs799XjrMKHkSBN6UQkC1\n"
"hRk=\n"
"-----END CERTIFICATE-----\n";
const char kDevCertFlagOid[] = "1.3.6.1.4.1.11129.4.1.2";
const bool kTestDevCodeSigningCertFlagValue = true;
@@ -340,6 +396,22 @@ TEST(X509CertTest, GetSerialNumber) {
EXPECT_EQ(kTestPemCertSerialNumber, test_cert.GetSerialNumber());
}
TEST(X509CertTest, GetNotBeforeSeconds) {
X509Cert test_cert;
ASSERT_EQ(util::OkStatus(), test_cert.LoadPem(kTestPemCert));
int64_t not_before_seconds = 0;
ASSERT_TRUE(test_cert.GetNotBeforeSeconds(&not_before_seconds));
EXPECT_EQ(kTestPemCertNotBeforeSeconds, not_before_seconds);
}
TEST(X509CertTest, GetNotAfterSeconds) {
X509Cert test_cert;
ASSERT_EQ(util::OkStatus(), test_cert.LoadPem(kTestPemCert));
int64_t not_after_seconds = 0;
ASSERT_TRUE(test_cert.GetNotAfterSeconds(&not_after_seconds));
EXPECT_EQ(kTestPemCertNotAfterSeconds, not_after_seconds);
}
TEST(X509CertTest, CertChain) {
X509CertChain test_chain;
ASSERT_EQ(util::OkStatus(), test_chain.LoadPem(kTestPemCertChain));
@@ -388,6 +460,62 @@ TEST(X509CertTest, ChainVerificationPkcs7) {
EXPECT_EQ(util::OkStatus(), ca.VerifyCertChain(test_chain));
}
TEST(X509CertTest, VerifyCertWithChainIca) {
std::unique_ptr<X509Cert> ca_cert(new X509Cert);
ASSERT_EQ(util::OkStatus(), ca_cert->LoadPem(kTestRootCaPemCert));
X509CA ca(ca_cert.release());
// Verify the ICA with the root succeeds.
X509CertChain test_chain;
ASSERT_EQ(util::OkStatus(), test_chain.LoadPem(kTestRootCaPemCert));
ASSERT_EQ(1, test_chain.GetNumCerts());
X509Cert ica_cert;
ASSERT_EQ(util::OkStatus(), ica_cert.LoadPem(kTestPemIca));
EXPECT_EQ(util::OkStatus(), ca.VerifyCertWithChain(ica_cert, test_chain));
}
TEST(X509CertTest, VerifyCertWithChainLeaf) {
std::unique_ptr<X509Cert> ca_cert(new X509Cert);
ASSERT_EQ(util::OkStatus(), ca_cert->LoadPem(kTestRootCaPemCert));
X509CA ca(ca_cert.release());
// Verify the leaf with the root and ICA succeeds.
X509CertChain test_chain;
ASSERT_EQ(util::OkStatus(), test_chain.LoadPem(kTestPemIca));
ASSERT_EQ(1, test_chain.GetNumCerts());
X509Cert leaf_cert;
ASSERT_EQ(util::OkStatus(), leaf_cert.LoadPem(kTestPemCert));
EXPECT_EQ(util::OkStatus(), ca.VerifyCertWithChain(leaf_cert, test_chain));
}
TEST(X509CertTest, VerifyCertWithChainLeafMissincIca) {
std::unique_ptr<X509Cert> ca_cert(new X509Cert);
ASSERT_EQ(util::OkStatus(), ca_cert->LoadPem(kTestRootCaPemCert));
X509CA ca(ca_cert.release());
// Verify the leaf with only the root fails (ICA missing).
X509CertChain test_chain;
ASSERT_EQ(util::OkStatus(), test_chain.LoadPem(kTestRootCaPemCert));
ASSERT_EQ(1, test_chain.GetNumCerts());
X509Cert leaf_cert;
ASSERT_EQ(util::OkStatus(), leaf_cert.LoadPem(kTestPemCert));
EXPECT_NE(util::OkStatus(), ca.VerifyCertWithChain(leaf_cert, test_chain));
}
TEST(X509CertTest, GetPkcs7) {
X509CertChain test_chain;
ASSERT_EQ(util::OkStatus(), test_chain.LoadPem(kTestPemCertChain));
std::string pkcs7_certificate = test_chain.GetPkcs7();
ASSERT_NE(pkcs7_certificate.size(), 0);
X509CertChain new_test_chain;
ASSERT_EQ(util::OkStatus(), new_test_chain.LoadPkcs7(pkcs7_certificate));
ASSERT_EQ(test_chain.GetNumCerts(), new_test_chain.GetNumCerts());
for (int i = 0; i < test_chain.GetNumCerts(); i++) {
ASSERT_EQ(test_chain.GetCert(i)->GetPem(),
new_test_chain.GetCert(i)->GetPem());
}
}
TEST(X509CertTest, BooleanExtension) {
std::unique_ptr<X509Cert> cert1(new X509Cert);
ASSERT_EQ(util::OkStatus(), cert1->LoadPem(kTestPemCert));

584
curl.BUILD Normal file
View File

@@ -0,0 +1,584 @@
################################################################################
# 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.
################################################################################
# Build file for curl.
exports_files(["COPYING"])
cc_library(
name = "curl",
srcs = [
"include/curl_config.h", # generated by genrule below
"lib/amigaos.c",
"lib/amigaos.h",
"lib/arpa_telnet.h",
"lib/asyn-ares.c",
"lib/asyn-thread.c",
"lib/asyn.h",
"lib/base64.c",
"lib/config-amigaos.h",
"lib/config-dos.h",
"lib/config-mac.h",
"lib/config-os400.h",
"lib/config-riscos.h",
"lib/config-symbian.h",
"lib/config-tpf.h",
"lib/config-vxworks.h",
"lib/config-win32.h",
"lib/config-win32ce.h",
"lib/conncache.c",
"lib/conncache.h",
"lib/connect.c",
"lib/connect.h",
"lib/content_encoding.c",
"lib/content_encoding.h",
"lib/cookie.c",
"lib/cookie.h",
"lib/curl_addrinfo.c",
"lib/curl_addrinfo.h",
"lib/curl_base64.h",
"lib/curl_ctype.c",
"lib/curl_ctype.h",
"lib/curl_des.c",
"lib/curl_des.h",
"lib/curl_endian.c",
"lib/curl_endian.h",
"lib/curl_fnmatch.c",
"lib/curl_fnmatch.h",
"lib/curl_gethostname.c",
"lib/curl_gethostname.h",
"lib/curl_gssapi.c",
"lib/curl_gssapi.h",
"lib/curl_hmac.h",
"lib/curl_ldap.h",
"lib/curl_md4.h",
"lib/curl_md5.h",
"lib/curl_memory.h",
"lib/curl_memrchr.c",
"lib/curl_memrchr.h",
"lib/curl_multibyte.c",
"lib/curl_multibyte.h",
"lib/curl_ntlm_core.c",
"lib/curl_ntlm_core.h",
"lib/curl_ntlm_wb.c",
"lib/curl_ntlm_wb.h",
"lib/curl_path.c",
"lib/curl_path.h",
"lib/curl_printf.h",
"lib/curl_range.c",
"lib/curl_range.h",
"lib/curl_rtmp.c",
"lib/curl_rtmp.h",
"lib/curl_sasl.c",
"lib/curl_sasl.h",
"lib/curl_sec.h",
"lib/curl_setup.h",
"lib/curl_setup_once.h",
"lib/curl_sha256.h",
"lib/curl_sspi.c",
"lib/curl_sspi.h",
"lib/curl_threads.c",
"lib/curl_threads.h",
"lib/curlx.h",
"lib/dict.c",
"lib/dict.h",
"lib/doh.c",
"lib/doh.h",
"lib/dotdot.c",
"lib/dotdot.h",
"lib/easy.c",
"lib/easyif.h",
"lib/escape.c",
"lib/escape.h",
"lib/file.c",
"lib/file.h",
"lib/fileinfo.c",
"lib/fileinfo.h",
"lib/formdata.c",
"lib/formdata.h",
"lib/ftp.c",
"lib/ftp.h",
"lib/ftplistparser.c",
"lib/ftplistparser.h",
"lib/getenv.c",
"lib/getinfo.c",
"lib/getinfo.h",
"lib/gopher.c",
"lib/gopher.h",
"lib/hash.c",
"lib/hash.h",
"lib/hmac.c",
"lib/hostasyn.c",
"lib/hostcheck.c",
"lib/hostcheck.h",
"lib/hostip.c",
"lib/hostip.h",
"lib/hostip4.c",
"lib/hostip6.c",
"lib/hostsyn.c",
"lib/http.c",
"lib/http.h",
"lib/http2.c",
"lib/http2.h",
"lib/http_chunks.c",
"lib/http_chunks.h",
"lib/http_digest.c",
"lib/http_digest.h",
"lib/http_negotiate.c",
"lib/http_negotiate.h",
"lib/http_ntlm.c",
"lib/http_ntlm.h",
"lib/http_proxy.c",
"lib/http_proxy.h",
"lib/idn_win32.c",
"lib/if2ip.c",
"lib/if2ip.h",
"lib/imap.c",
"lib/imap.h",
"lib/inet_ntop.c",
"lib/inet_ntop.h",
"lib/inet_pton.c",
"lib/inet_pton.h",
"lib/krb5.c",
"lib/ldap.c",
"lib/llist.c",
"lib/llist.h",
"lib/md4.c",
"lib/md5.c",
"lib/memdebug.c",
"lib/memdebug.h",
"lib/mime.c",
"lib/mime.h",
"lib/mprintf.c",
"lib/multi.c",
"lib/multihandle.h",
"lib/multiif.h",
"lib/netrc.c",
"lib/netrc.h",
"lib/non-ascii.c",
"lib/non-ascii.h",
"lib/nonblock.c",
"lib/nonblock.h",
"lib/nwlib.c",
"lib/nwos.c",
"lib/openldap.c",
"lib/parsedate.c",
"lib/parsedate.h",
"lib/pingpong.c",
"lib/pingpong.h",
"lib/pipeline.c",
"lib/pipeline.h",
"lib/pop3.c",
"lib/pop3.h",
"lib/progress.c",
"lib/progress.h",
"lib/psl.c",
"lib/psl.h",
"lib/rand.c",
"lib/rand.h",
"lib/rtsp.c",
"lib/rtsp.h",
"lib/security.c",
"lib/select.c",
"lib/select.h",
"lib/sendf.c",
"lib/sendf.h",
"lib/setopt.c",
"lib/setopt.h",
"lib/setup-os400.h",
"lib/setup-vms.h",
"lib/sha256.c",
"lib/share.c",
"lib/share.h",
"lib/sigpipe.h",
"lib/slist.c",
"lib/slist.h",
"lib/smb.c",
"lib/smb.h",
"lib/smtp.c",
"lib/smtp.h",
"lib/sockaddr.h",
"lib/socks.c",
"lib/socks.h",
"lib/socks_gssapi.c",
"lib/socks_sspi.c",
"lib/speedcheck.c",
"lib/speedcheck.h",
"lib/splay.c",
"lib/splay.h",
"lib/ssh-libssh.c",
"lib/ssh.c",
"lib/ssh.h",
"lib/strcase.c",
"lib/strcase.h",
"lib/strdup.c",
"lib/strdup.h",
"lib/strerror.c",
"lib/strerror.h",
"lib/strtok.c",
"lib/strtok.h",
"lib/strtoofft.c",
"lib/strtoofft.h",
"lib/system_win32.c",
"lib/system_win32.h",
"lib/telnet.c",
"lib/telnet.h",
"lib/tftp.c",
"lib/tftp.h",
"lib/timeval.c",
"lib/timeval.h",
"lib/transfer.c",
"lib/transfer.h",
"lib/url.c",
"lib/url.h",
"lib/urlapi-int.h",
"lib/urlapi.c",
"lib/urldata.h",
"lib/vauth/cleartext.c",
"lib/vauth/cram.c",
"lib/vauth/digest.c",
"lib/vauth/digest.h",
"lib/vauth/digest_sspi.c",
"lib/vauth/krb5_gssapi.c",
"lib/vauth/krb5_sspi.c",
"lib/vauth/ntlm.c",
"lib/vauth/ntlm.h",
"lib/vauth/ntlm_sspi.c",
"lib/vauth/oauth2.c",
"lib/vauth/spnego_gssapi.c",
"lib/vauth/spnego_sspi.c",
"lib/vauth/vauth.c",
"lib/vauth/vauth.h",
"lib/version.c",
"lib/vtls/cyassl.c",
"lib/vtls/cyassl.h",
"lib/vtls/darwinssl.c",
"lib/vtls/darwinssl.h",
"lib/vtls/gskit.c",
"lib/vtls/gskit.h",
"lib/vtls/gtls.c",
"lib/vtls/gtls.h",
"lib/vtls/mbedtls.c",
"lib/vtls/mbedtls.h",
"lib/vtls/mesalink.c",
"lib/vtls/mesalink.h",
"lib/vtls/nss.c",
"lib/vtls/nssg.h",
"lib/vtls/openssl.c",
"lib/vtls/openssl.h",
"lib/vtls/polarssl.c",
"lib/vtls/polarssl.h",
"lib/vtls/polarssl_threadlock.c",
"lib/vtls/polarssl_threadlock.h",
"lib/vtls/schannel.c",
"lib/vtls/schannel.h",
"lib/vtls/schannel_verify.c",
"lib/vtls/vtls.c",
"lib/vtls/vtls.h",
"lib/warnless.c",
"lib/warnless.h",
"lib/wildcard.c",
"lib/wildcard.h",
"lib/x509asn1.c",
"lib/x509asn1.h",
],
hdrs = [
"include/curl/curl.h",
"include/curl/curlver.h",
"include/curl/easy.h",
"include/curl/mprintf.h",
"include/curl/multi.h",
"include/curl/stdcheaders.h",
"include/curl/system.h",
"include/curl/typecheck-gcc.h",
"include/curl/urlapi.h",
],
copts = [
"-Iexternal/curl/lib",
"-D_GNU_SOURCE",
"-DHAVE_CONFIG_H",
"-DCURL_DISABLE_FTP",
"-DCURL_DISABLE_NTLM", # turning it off in configure is not enough
"-DHAVE_LIBZ",
"-DHAVE_ZLIB_H",
"-Wno-std::string-plus-int",
"-DCURL_MAX_WRITE_SIZE=65536",
# dealing with conflicting types when building
"-DBUILDING_LIBCURL",
],
includes = [
"include",
# lib/curl_setup.h is included by many .c files under lib/
"lib"
],
linkopts = [
"-lrt",
],
visibility = ["//visibility:public"],
deps = [
"@zlib_repo//:zlib",
"@boringssl_repo//:ssl",
],
)
genrule(
name = "configure",
outs = ["include/curl_config.h"],
cmd = "\n".join([
"cat <<'EOF' >$@",
"#ifndef EXTERNAL_CURL_INCLUDE_CURL_CONFIG_H_",
"#define EXTERNAL_CURL_INCLUDE_CURL_CONFIG_H_",
"",
"#if !defined(_WIN32) && !defined(__APPLE__)",
"# include <openssl/opensslv.h>",
"# if defined(OPENSSL_IS_BORINGSSL)",
"# define HAVE_BORINGSSL 1",
"# endif",
"#endif",
"",
"#if defined(_WIN32)",
"# include \"lib/config-win32.h\"",
"# define BUILDING_LIBCURL 1",
"# define CURL_DISABLE_CRYPTO_AUTH 1",
"# define CURL_DISABLE_IMAP 1",
"# define CURL_DISABLE_LDAP 1",
"# define CURL_DISABLE_LDAPS 1",
"# define CURL_DISABLE_POP3 1",
"# define CURL_PULL_WS2TCPIP_H 1",
"# define HTTP_ONLY 1",
"#elif defined(__APPLE__)",
"# define HAVE_FSETXATTR_6 1",
"# define HAVE_SETMODE 1",
"# define HAVE_SYS_FILIO_H 1",
"# define HAVE_SYS_SOCKIO_H 1",
"# define OS \"x86_64-apple-darwin15.5.0\"",
"# define USE_DARWINSSL 1",
"#else",
"# define CURL_CA_BUNDLE \"/etc/ssl/certs/ca-certificates.crt\"",
"# define GETSERVBYPORT_R_ARGS 6",
"# define GETSERVBYPORT_R_BUFSIZE 4096",
"# define HAVE_BORINGSSL 1",
"# define HAVE_CLOCK_GETTIME_MONOTONIC 1",
"# define HAVE_CRYPTO_CLEANUP_ALL_EX_DATA 1",
"# define HAVE_FSETXATTR_5 1",
"# define HAVE_GETHOSTBYADDR_R 1",
"# define HAVE_GETHOSTBYADDR_R_8 1",
"# define HAVE_GETHOSTBYNAME_R 1",
"# define HAVE_GETHOSTBYNAME_R_6 1",
"# define HAVE_GETSERVBYPORT_R 1",
"# define HAVE_LIBSSL 1",
"# define HAVE_MALLOC_H 1",
"# define HAVE_MSG_NOSIGNAL 1",
"# define HAVE_OPENSSL_CRYPTO_H 1",
"# define HAVE_OPENSSL_ERR_H 1",
"# define HAVE_OPENSSL_PEM_H 1",
"# define HAVE_OPENSSL_PKCS12_H 1",
"# define HAVE_OPENSSL_RSA_H 1",
"# define HAVE_OPENSSL_SSL_H 1",
"# define HAVE_OPENSSL_X509_H 1",
"# define HAVE_RAND_EGD 1",
"# define HAVE_RAND_STATUS 1",
"# define HAVE_SSL_GET_SHUTDOWN 1",
"# define HAVE_STROPTS_H 1",
"# define HAVE_TERMIOS_H 1",
"# define OS \"x86_64-pc-linux-gnu\"",
"# define RANDOM_FILE \"/dev/urandom\"",
"# define USE_OPENSSL 1",
"#endif",
"",
"#if !defined(_WIN32)",
"# define CURL_DISABLE_DICT 1",
"# define CURL_DISABLE_FILE 1",
"# define CURL_DISABLE_GOPHER 1",
"# define CURL_DISABLE_IMAP 1",
"# define CURL_DISABLE_LDAP 1",
"# define CURL_DISABLE_LDAPS 1",
"# define CURL_DISABLE_POP3 1",
"# define CURL_DISABLE_SMTP 1",
"# define CURL_DISABLE_TELNET 1",
"# define CURL_DISABLE_TFTP 1",
"# define CURL_EXTERN_SYMBOL __attribute__ ((__visibility__ (\"default\")))",
"# define ENABLE_IPV6 1",
"# define GETHOSTNAME_TYPE_ARG2 size_t",
"# define GETNAMEINFO_QUAL_ARG1 const",
"# define GETNAMEINFO_TYPE_ARG1 struct sockaddr *",
"# define GETNAMEINFO_TYPE_ARG2 socklen_t",
"# define GETNAMEINFO_TYPE_ARG46 socklen_t",
"# define GETNAMEINFO_TYPE_ARG7 int",
"# define HAVE_ALARM 1",
"# define HAVE_ALLOCA_H 1",
"# define HAVE_ARPA_INET_H 1",
"# define HAVE_ARPA_TFTP_H 1",
"# define HAVE_ASSERT_H 1",
"# define HAVE_BASENAME 1",
"# define HAVE_BOOL_T 1",
"# define HAVE_CONNECT 1",
"# define HAVE_DLFCN_H 1",
"# define HAVE_ERRNO_H 1",
"# define HAVE_FCNTL 1",
"# define HAVE_FCNTL_H 1",
"# define HAVE_FCNTL_O_NONBLOCK 1",
"# define HAVE_FDOPEN 1",
"# define HAVE_FORK 1",
"# define HAVE_FREEADDRINFO 1",
"# define HAVE_FREEIFADDRS 1",
"# if !defined(__ANDROID__)",
"# define HAVE_FSETXATTR 1",
"# endif",
"# define HAVE_FTRUNCATE 1",
"# define HAVE_GAI_STRERROR 1",
"# define HAVE_GETADDRINFO 1",
"# define HAVE_GETADDRINFO_THREADSAFE 1",
"# define HAVE_GETEUID 1",
"# define HAVE_GETHOSTBYADDR 1",
"# define HAVE_GETHOSTBYNAME 1",
"# define HAVE_GETHOSTNAME 1",
"# if !defined(__ANDROID__)",
"# define HAVE_GETIFADDRS 1",
"# endif",
"# define HAVE_GETNAMEINFO 1",
"# define HAVE_GETPPID 1",
"# define HAVE_GETPROTOBYNAME 1",
"# define HAVE_GETPWUID 1",
"# if !defined(__ANDROID__)",
"# define HAVE_GETPWUID_R 1",
"# endif",
"# define HAVE_GETRLIMIT 1",
"# define HAVE_GETTIMEOFDAY 1",
"# define HAVE_GMTIME_R 1",
"# if !defined(__ANDROID__)",
"# define HAVE_IFADDRS_H 1",
"# endif",
"# define HAVE_IF_NAMETOINDEX 1",
"# define HAVE_INET_ADDR 1",
"# define HAVE_INET_NTOP 1",
"# define HAVE_INET_PTON 1",
"# define HAVE_INTTYPES_H 1",
"# define HAVE_IOCTL 1",
"# define HAVE_IOCTL_FIONBIO 1",
"# define HAVE_IOCTL_SIOCGIFADDR 1",
"# define HAVE_LIBGEN_H 1",
"# define HAVE_LIBZ 1",
"# define HAVE_LIMITS_H 1",
"# define HAVE_LL 1",
"# define HAVE_LOCALE_H 1",
"# define HAVE_LOCALTIME_R 1",
"# define HAVE_LONGLONG 1",
"# define HAVE_MEMORY_H 1",
"# define HAVE_NETDB_H 1",
"# define HAVE_NETINET_IN_H 1",
"# define HAVE_NETINET_TCP_H 1",
"# define HAVE_NET_IF_H 1",
"# define HAVE_PERROR 1",
"# define HAVE_PIPE 1",
"# define HAVE_POLL 1",
"# define HAVE_POLL_FINE 1",
"# define HAVE_POLL_H 1",
"# define HAVE_POSIX_STRERROR_R 1",
"# define HAVE_PWD_H 1",
"# define HAVE_RECV 1",
"# define HAVE_SELECT 1",
"# define HAVE_SEND 1",
"# define HAVE_SETJMP_H 1",
"# define HAVE_SETLOCALE 1",
"# define HAVE_SETRLIMIT 1",
"# define HAVE_SETSOCKOPT 1",
"# define HAVE_SGTTY_H 1",
"# define HAVE_SIGACTION 1",
"# define HAVE_SIGINTERRUPT 1",
"# define HAVE_SIGNAL 1",
"# define HAVE_SIGNAL_H 1",
"# define HAVE_SIGSETJMP 1",
"# define HAVE_SIG_ATOMIC_T 1",
"# define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1",
"# define HAVE_SOCKET 1",
"# define HAVE_SOCKETPAIR 1",
"# define HAVE_STDBOOL_H 1",
"# define HAVE_STDINT_H 1",
"# define HAVE_STDIO_H 1",
"# define HAVE_STDLIB_H 1",
"# define HAVE_STRCASECMP 1",
"# define HAVE_STRDUP 1",
"# define HAVE_STRERROR_R 1",
"# define HAVE_STRINGS_H 1",
"# define HAVE_STRING_H 1",
"# define HAVE_STRNCASECMP 1",
"# define HAVE_STRSTR 1",
"# define HAVE_STRTOK_R 1",
"# define HAVE_STRTOLL 1",
"# define HAVE_STRUCT_SOCKADDR_STORAGE 1",
"# define HAVE_STRUCT_TIMEVAL 1",
"# define HAVE_SYS_IOCTL_H 1",
"# define HAVE_SYS_PARAM_H 1",
"# define HAVE_SYS_POLL_H 1",
"# define HAVE_SYS_RESOURCE_H 1",
"# define HAVE_SYS_SELECT_H 1",
"# define HAVE_SYS_SOCKET_H 1",
"# define HAVE_SYS_STAT_H 1",
"# define HAVE_SYS_TIME_H 1",
"# define HAVE_SYS_TYPES_H 1",
"# define HAVE_SYS_UIO_H 1",
"# define HAVE_SYS_UN_H 1",
"# define HAVE_SYS_WAIT_H 1",
"# define HAVE_SYS_XATTR_H 1",
"# define HAVE_TIME_H 1",
"# define HAVE_UNAME 1",
"# define HAVE_UNISTD_H 1",
"# define HAVE_UTIME 1",
"# define HAVE_UTIME_H 1",
"# define HAVE_VARIADIC_MACROS_C99 1",
"# define HAVE_VARIADIC_MACROS_GCC 1",
"# define HAVE_WRITABLE_ARGV 1",
"# define HAVE_WRITEV 1",
"# define HAVE_ZLIB_H 1",
"# define LT_OBJDIR \".libs/\"",
"# define PACKAGE \"curl\"",
"# define PACKAGE_BUGREPORT \"a suitable curl mailing list: https://curl.haxx.se/mail/\"",
"# define PACKAGE_NAME \"curl\"",
"# define PACKAGE_STRING \"curl -\"",
"# define PACKAGE_TARNAME \"curl\"",
"# define PACKAGE_URL \"\"",
"# define PACKAGE_VERSION \"-\"",
"# define RECV_TYPE_ARG1 int",
"# define RECV_TYPE_ARG2 void *",
"# define RECV_TYPE_ARG3 size_t",
"# define RECV_TYPE_ARG4 int",
"# define RECV_TYPE_RETV ssize_t",
"# define RETSIGTYPE void",
"# define SELECT_QUAL_ARG5",
"# define SELECT_TYPE_ARG1 int",
"# define SELECT_TYPE_ARG234 fd_set *",
"# define SELECT_TYPE_ARG5 struct timeval *",
"# define SELECT_TYPE_RETV int",
"# define SEND_QUAL_ARG2 const",
"# define SEND_TYPE_ARG1 int",
"# define SEND_TYPE_ARG2 void *",
"# define SEND_TYPE_ARG3 size_t",
"# define SEND_TYPE_ARG4 int",
"# define SEND_TYPE_RETV ssize_t",
"# define SIZEOF_INT 4",
"# define SIZEOF_LONG 8",
"# define SIZEOF_OFF_T 8",
"# define SIZEOF_SHORT 2",
"# define SIZEOF_SIZE_T 8",
"# define SIZEOF_TIME_T 8",
"# define SIZEOF_VOIDP 8",
"# define SIZEOF_CURL_OFF_T 8",
"# define STDC_HEADERS 1",
"# define STRERROR_R_TYPE_ARG3 size_t",
"# define TIME_WITH_SYS_TIME 1",
"# define VERSION \"-\"",
"# ifndef _DARWIN_USE_64_BIT_INODE",
"# define _DARWIN_USE_64_BIT_INODE 1",
"# endif",
"#endif",
"",
"#endif // EXTERNAL_CURL_INCLUDE_CURL_CONFIG_H_",
"EOF",
]),
)

View File

@@ -51,3 +51,14 @@ cc_binary(
"//media_cas_packager_sdk/public:wv_cas_types",
],
)
cc_binary(
name = "wv_cas_key_fetcher_example",
srcs = ["wv_cas_key_fetcher_example.cc"],
deps = [
"//base",
"//util:status",
"//media_cas_packager_sdk/public:wv_cas_key_fetcher",
"//protos/public:media_cas_encryption_proto",
],
)

View File

@@ -0,0 +1,70 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <iostream>
#include <string>
#include "gflags/gflags.h"
#include "glog/logging.h"
#include "util/status.h"
#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
#include "protos/public/media_cas_encryption.pb.h"
DEFINE_string(content_id, "21140844", "Content ID");
DEFINE_bool(key_rotation, true, "Whether key rotation is enabled");
DEFINE_string(track_type, "SD", "Provider name");
namespace util = widevine::util;
int main(int argc, char **argv) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
CHECK(!FLAGS_content_id.empty() && !FLAGS_track_type.empty())
<< "Flags 'content_id' and 'track_type' are required";
// Required flags in key fetcher.
CHECK(!FLAGS_license_server.empty() && !FLAGS_signing_provider.empty() &&
!FLAGS_signing_key.empty() && !FLAGS_signing_iv.empty())
<< "Flags 'license_server', 'signing_provider', 'signing_key' "
"and 'signing_iv' are required";
std::string request_str;
widevine::CasEncryptionRequest request;
request.set_provider(FLAGS_signing_provider);
request.set_content_id(FLAGS_content_id);
request.set_key_rotation(FLAGS_key_rotation);
// Only 1 track in this example.
request.add_track_types(FLAGS_track_type);
LOG(INFO) << "Request: " << request.ShortDebugString();
if (!request.SerializeToString(&request_str)) {
LOG(ERROR) << "Failed to serialize request";
return -1;
}
std::string signed_response_str;
widevine::cas::WvCasKeyFetcher key_fetcher;
util::Status status =
key_fetcher.RequestEntitlementKey(request_str, &signed_response_str);
if (!status.ok()) {
LOG(ERROR) << "Failed to request entitlement key";
return -1;
}
widevine::SignedCasEncryptionResponse signed_response;
if (!signed_response.ParseFromString(signed_response_str)) {
LOG(ERROR) << "Failed to deserialize signed response";
return -1;
}
LOG(INFO) << "Signed response: " << signed_response.ShortDebugString();
widevine::CasEncryptionResponse response;
if (!response.ParseFromString(signed_response.response())) {
LOG(ERROR) << "Failed to deserialize response";
return -1;
}
LOG(INFO) << "Response: " << response.ShortDebugString();
return 0;
}

View File

@@ -120,6 +120,17 @@ cc_library(
deps = ["//util:status"],
)
cc_library(
name = "mpeg2ts",
hdrs = [
"mpeg2ts.h",
],
deps = [
"//base",
"@abseil_repo//absl/base:core_headers",
],
)
cc_library(
name = "simulcrypt",
srcs = ["simulcrypt.cc"],
@@ -149,11 +160,60 @@ cc_test(
)
cc_library(
name = "util",
srcs = ["util.cc"],
hdrs = ["util.h"],
name = "ts_packet",
srcs = [
"ts_packet.cc",
],
hdrs = [
"mpeg2ts.h",
"ts_packet.h",
],
deps = [
":mpeg2ts",
"//base",
"@abseil_repo//absl/base:core_headers",
"@abseil_repo//absl/strings",
"//util:status",
"//common:string_util",
],
)
cc_test(
name = "ts_packet_test",
size = "small",
srcs = ["ts_packet_test.cc"],
deps = [
":mpeg2ts",
":ts_packet",
"//base",
"//testing:gunit_main",
],
)
cc_library(
name = "util",
srcs = ["util.cc"],
hdrs = [
"mpeg2ts.h",
"util.h",
],
deps = [
":mpeg2ts",
":ts_packet",
"//base",
"@abseil_repo//absl/base:core_headers",
"//util:status",
],
)
cc_test(
name = "util_test",
size = "small",
srcs = [
"util_test.cc",
],
deps = [
":util",
"//testing:gunit_main",
],
)

View File

@@ -140,12 +140,7 @@ util::Status CasEcm::Initialize(const std::string& content_id,
"Parameter content_iv_size must be kIvSize8 or kIvSize16."};
}
if (ecm_init_parameters.crypto_mode == CryptoMode::kCryptoModeUnspecified) {
return {util::error::INVALID_ARGUMENT, "Invalid crypto mode."};
} else {
crypto_mode_ = ecm_init_parameters.crypto_mode;
}
content_id_ = content_id;
content_provider_ = content_provider;
paired_keys_required_ = ecm_init_parameters.key_rotation_enabled;
@@ -414,9 +409,6 @@ std::string CasEcm::SerializeEcm(const std::vector<EntitledKeyInfo*>& keys) {
generation());
std::bitset<kNumBitsDecryptModeField> decrypt_mode(
static_cast<int>(crypto_mode()));
if (decrypt_mode.to_string() == "00") {
LOG(FATAL) << "Invalid decrypt mode \"00\"";
}
std::bitset<kNumBitsRotationEnabledField> rotation_enabled(
RotationFieldValue(paired_keys_required()));
std::bitset<kNumBitsWrappedKeyIvSizeField> wrapped_key_iv_size(

View File

@@ -0,0 +1,40 @@
////////////////////////////////////////////////////////////////////////////////
// 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 MEDIA_CAS_PACKAGER_SDK_INTERNAL_MPEG2TS_H_
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_MPEG2TS_H_
#include <cstddef>
#include <cstdint>
#include "base/macros.h"
namespace widevine {
namespace cas {
// MPEG2 TS Program ID (13 bits).
typedef uint16_t ProgramId;
// MPEG2 TS Continuity Counter (4 bits).
typedef uint8_t ContinuityCounter;
constexpr ProgramId kInvalidPid = 0x2000;
constexpr size_t kTsPacketSize = 188;
constexpr size_t kTsPacketHeaderSize = 4;
constexpr size_t kMaxTsPayloadSize = kTsPacketSize - kTsPacketHeaderSize;
constexpr uint8_t kTsPacketSyncByte = 0x47;
constexpr uint8_t kTsPacketTableId80 = 0x80;
constexpr uint8_t kTsPacketTableId81 = 0x81;
} // namespace cas
} // namespace widevine
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_MPEG2TS_H_

View File

@@ -0,0 +1,77 @@
////////////////////////////////////////////////////////////////////////////////
// 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 "media_cas_packager_sdk/internal/ts_packet.h"
#include <bitset>
#include "glog/logging.h"
#include "absl/strings/str_cat.h"
#include "util/status.h"
#include "common/string_util.h"
namespace widevine {
namespace cas {
util::Status TsPacket::Write(std::string* output) const {
DCHECK(output);
output->resize(kTsPacketSize);
// TsPacket header.
std::bitset<8> sync_byte(kTsPacketSyncByte);
std::bitset<1> transport_error_indication(transport_error_indication_);
std::bitset<1> payload_unit_start_indicator(payload_unit_start_indicator_);
std::bitset<1> transport_priority(transport_priority_);
std::bitset<13> pid(pid_);
std::bitset<2> transport_scrambling_control(transport_scrambling_control_);
std::bitset<2> adaptation_field_control(adaptation_field_control_);
std::bitset<4> continuity_counter(continuity_counter_);
if (adaptation_field_control_ & kAdaptationFieldOnly) {
return util::Status(util::error::INTERNAL,
"TsPacket does NOT handle adaptation field yet");
}
// Converts header bitset to string.
std::string header_bitset = absl::StrCat(
sync_byte.to_string(), transport_error_indication.to_string(),
payload_unit_start_indicator.to_string(), transport_priority.to_string(),
pid.to_string(), transport_scrambling_control.to_string(),
adaptation_field_control.to_string(), continuity_counter.to_string());
if (header_bitset.size() != 4 * 8) {
return util::Status(util::error::INTERNAL,
absl::StrCat("TS packet header bitset incorret size: ",
header_bitset.size()));
}
std::string serialized_header;
util::Status status = string_util::BitsetStringToBinaryString(
header_bitset, &serialized_header);
if (!status.ok()) {
return util::Status(util::error::INTERNAL,
"Failed to convert TS packet header bitset to std::string");
}
// Write TsPacket payload.
if (payload_.size() != CalculatePayloadSize()) {
return util::Status(
util::error::INVALID_ARGUMENT,
absl::StrCat("Incorrect payload size: ", payload_.size()));
}
// Return header + payload as a TS packet.
*output = serialized_header + payload_;
return util::OkStatus();
}
int32_t TsPacket::CalculatePayloadSize() const {
int32_t adaptation_length = 0;
return kMaxTsPayloadSize - adaptation_length;
}
} // namespace cas
} // namespace widevine

View File

@@ -0,0 +1,117 @@
////////////////////////////////////////////////////////////////////////////////
// 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.
////////////////////////////////////////////////////////////////////////////////
//
// TS Packet is the basic unit of data in a transport stream, and a transport
// stream is merely a sequence of packets, without any global header. Each
// packet starts with a sync byte and a header, that may be followed with
// optional additional headers; the rest of the packet consists of payload.
// All header fields are read as big-endian. Packets are 188 bytes in length,
// but the communication medium may add additional information:
// Forward error correction is added by ISDB & DVB (16 bytes) and ATSC
// (20 bytes),[4] while the M2TS format prefixes packets with a 4-byte
// copyright and timestamp tag.
//
// TsPacket class is used to read data from the input stream or serialize data
// into the output stream. In contrast to other classes/interfaces in
// video/file, TsPacket does not use bidirectional BinaryIO interface, because
// BinaryIO is 10-15% slower than BitReader/BitWriter, and for entity like
// TsPacket this difference is tangible.
//
// TsPacket is a simple class. It does not know how to automatically update
// AdaptationField::length and AdaptationFieldExtension::length. In order
// to initialize TsPacket correctly, user has to set all its fields including
// length. Length calculation helpers are provided, though:
// * AdaptationFieldExtension::CalculateLength()
// * AdaptationField::CalculateLength()
// * TsPacket::PayloadSize()
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_TS_PACKET_H_
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_TS_PACKET_H_
#include <memory>
#include <string>
#include <cstdint>
#include "base/macros.h"
#include "absl/strings/string_view.h"
#include "util/status.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
namespace widevine {
namespace cas {
// NOTE(user): This class does not handle "adaptation field" or
// "adaptation field extension" yet.
// NOTE(user): This class does not support reading a TS packet yet.
class TsPacket {
public:
enum AdaptationFieldControl {
kAFCReserved = 0x00,
kPayloadOnly = 0x01,
kAdaptationFieldOnly = 0x02,
kAdaptationFieldAndPayload = 0x03
};
TsPacket() = default;
TsPacket(const TsPacket&) = delete;
TsPacket& operator=(const TsPacket&) = delete;
virtual ~TsPacket() = default;
// Writes the packet into the provided output. Returns kOk on success.
virtual util::Status Write(std::string* output) const;
// Returns the size of payload data for the current TS packet configuration.
int32_t CalculatePayloadSize() const;
// Getters.
bool transport_error_indication() const {
return transport_error_indication_;
}
bool payload_unit_start_indicator() const {
return payload_unit_start_indicator_;
}
bool transport_priority() const { return transport_priority_; }
uint32_t pid() const { return pid_; }
uint32_t transport_scrambling_control() const {
return transport_scrambling_control_;
}
uint32_t adaptation_field_control() const { return adaptation_field_control_; }
uint32_t continuity_counter() const { return continuity_counter_; }
const std::string& payload() const { return payload_; }
// Setters.
void set_transport_error_indication(bool v) {
transport_error_indication_ = v;
}
void set_payload_unit_start_indicator(bool v) {
payload_unit_start_indicator_ = v;
}
void set_transport_priority(bool v) { transport_priority_ = v; }
void set_pid(uint32_t v) { pid_ = v; }
void set_transport_scrambling_control(uint32_t v) {
transport_scrambling_control_ = v;
}
void set_adaptation_field_control(uint32_t v) { adaptation_field_control_ = v; }
void set_continuity_counter(uint32_t v) { continuity_counter_ = v; }
void set_payload(const std::string& p) { payload_ = p; }
private:
bool transport_error_indication_ = false;
bool payload_unit_start_indicator_ = false;
bool transport_priority_ = false;
uint32_t pid_ = kInvalidPid;
uint32_t transport_scrambling_control_ = 0;
uint32_t adaptation_field_control_ = 0;
uint32_t continuity_counter_ = 0;
std::string payload_;
};
} // namespace cas
} // namespace widevine
#endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_TS_PACKET_H_

View File

@@ -0,0 +1,84 @@
////////////////////////////////////////////////////////////////////////////////
// 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 "media_cas_packager_sdk/internal/ts_packet.h"
#include <string>
#include "base/macros.h"
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
namespace widevine {
namespace cas {
class TsPacketTest : public ::testing::Test {
public:
TsPacketTest() = default;
protected:
void AddStuffing(std::string* bytes, int payload_size) {
*bytes += std::string(kTsPacketSize - bytes->size() - payload_size, 0xFF);
}
void ExpectWriteMatches(TsPacket* packet, const std::string& expected) {
std::string bytes;
ASSERT_OK(packet->Write(&bytes));
EXPECT_EQ(expected, bytes);
}
private:
DISALLOW_COPY_AND_ASSIGN(TsPacketTest);
};
TEST_F(TsPacketTest, Packet1) {
TsPacket ts;
ts.set_transport_error_indication(true);
ts.set_payload_unit_start_indicator(false);
ts.set_transport_priority(true);
ts.set_pid(0x123);
ts.set_transport_scrambling_control(3);
ts.set_adaptation_field_control(TsPacket::kPayloadOnly);
ts.set_continuity_counter(0x7);
const std::string payload(ts.CalculatePayloadSize(), 0xFF);
ts.set_payload(payload);
std::string bytes = std::string("\x47\xA1\x23\xD7", 4) + payload;
ExpectWriteMatches(&ts, bytes);
EXPECT_EQ(kMaxTsPayloadSize, ts.CalculatePayloadSize());
}
TEST_F(TsPacketTest, Packet2) {
TsPacket ts;
ts.set_transport_error_indication(false);
ts.set_payload_unit_start_indicator(true);
ts.set_transport_priority(false);
ts.set_pid(0x345);
ts.set_transport_scrambling_control(1);
ts.set_adaptation_field_control(TsPacket::kPayloadOnly);
ts.set_continuity_counter(0x4);
const std::string payload(ts.CalculatePayloadSize(), 0xFF);
ts.set_payload(payload);
std::string bytes = std::string("\x47\x43\x45\x54", 4) + payload;
ExpectWriteMatches(&ts, bytes);
EXPECT_EQ(184, ts.CalculatePayloadSize());
}
TEST_F(TsPacketTest, BlankPacket184BytePayload) {
TsPacket ts;
ts.set_pid(0x0);
ts.set_adaptation_field_control(TsPacket::kPayloadOnly);
const std::string payload(ts.CalculatePayloadSize(), 0xFF);
ts.set_payload(payload);
std::string bytes = std::string("\x47\x00\x00\x10", 4) + payload;
ExpectWriteMatches(&ts, bytes);
EXPECT_EQ(184, ts.CalculatePayloadSize());
}
} // namespace cas
} // namespace widevine

View File

@@ -9,12 +9,23 @@
#include "media_cas_packager_sdk/internal/util.h"
#include <netinet/in.h>
#include <stddef.h>
#include <string.h>
#include "glog/logging.h"
#include "media_cas_packager_sdk/internal/ts_packet.h"
namespace widevine {
namespace cas {
namespace {
constexpr size_t kSectionHeaderSize = 4;
constexpr size_t kTsFillValue = 0xFF;
ContinuityCounter Increment(ContinuityCounter continuity_counter) {
return ++continuity_counter & 0xf;
}
} // namespace
void BigEndianToHost16(uint16_t* destination, const void* source) {
DCHECK(destination);
@@ -24,5 +35,56 @@ void BigEndianToHost16(uint16_t* destination, const void* source) {
*destination = ntohs(big_endian_number);
}
util::Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid,
uint8_t table_id, ContinuityCounter* cc,
uint8_t* buffer, ssize_t* bytes_modified) {
DCHECK(cc);
DCHECK(buffer);
DCHECK(bytes_modified);
DCHECK_EQ(*bytes_modified % kTsPacketSize, 0);
// Create a TS payload of 184 bytes (Max TS size - ts header length).
CHECK_LE(ecm.size(), kMaxTsPayloadSize);
std::string payload(kMaxTsPayloadSize, kTsFillValue);
// Reference https://en.wikipedia.org/wiki/Program-specific_information
static constexpr uint8_t kPointerField = '\x00';
// Here are the 8 bits covers multiple fields from
// "section syntax indicator" to the first two bits of "section length".
// We always set the first two bits of "section length" to 0 because
// the data follows (i.e. the ECM) cannot be more than 180 bytes, so
// the remaining 8 bits in "section length" is sufficient to store its
// length.
static constexpr uint8_t kSyntaxtIndicatorToLength = '\x70';
uint8_t ecm_length = ecm.size();
// Section header.
memcpy(&payload.at(0), &kPointerField, 1);
memcpy(&payload.at(1), &table_id, 1);
memcpy(&payload.at(2), &kSyntaxtIndicatorToLength, 1);
memcpy(&payload.at(3), &ecm_length, 1);
// ECM follows the header.
memcpy(&payload.at(kSectionHeaderSize), ecm.data(), ecm.size());
// Wrap the data with a TS header.
TsPacket ecm_packet;
ecm_packet.set_payload_unit_start_indicator(true);
ecm_packet.set_pid(pid);
ecm_packet.set_payload(payload);
ecm_packet.set_adaptation_field_control(0x1);
ecm_packet.set_continuity_counter(*cc);
*cc = Increment(*cc);
// And write the packet.
std::string ecm_ts_packet;
util::Status status = ecm_packet.Write(&ecm_ts_packet);
if (!status.ok()) {
return status;
}
memcpy(buffer + *bytes_modified, ecm_ts_packet.data(), ecm_ts_packet.size());
*bytes_modified += ecm_ts_packet.size();
return util::OkStatus();
}
} // namespace cas
} // namespace widevine

View File

@@ -9,7 +9,12 @@
#ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_UTIL_H_
#define MEDIA_CAS_PACKAGER_SDK_INTERNAL_UTIL_H_
#include <sys/types.h>
#include <string>
#include <cstdint>
#include "util/status.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
namespace widevine {
namespace cas {
@@ -19,6 +24,28 @@ namespace cas {
// the result in |destination|.
void BigEndianToHost16(uint16_t* destination, const void* source);
// Packages an ECM as a TS packet and inserts it into a buffer.
// Args:
// - |ecm| is the serialized ECM.
// - |pid| is the PID used for ECMs in the TS header.
// - |table_id| is the table ID byte put in the section header, it should be
// either 0x80 or 0x81. Changing table ID from 0x80 or 0x81 or
// 0x81 to 0x80 is used to singal to the client that the key contained
// in the ECM has changed. In other words, if you are building an ECM
// with a new key that was not in any previous ECM, you should flip the
// table ID so the client knows this is an important ECM it should process.
// - |cc| is the continuity counter to use in the header, which will also be
// incremented by this function.
// - |buffer| is the output buffer. Must be big enough to hold an additional
// 188 bytes.
// - |bytes_modified| the number of bytes which have already been modified in
// the |buffer| and is used as an offset.
// |bytes_modified| will be incremented by 188 if insertion of ECM into
// |buffer| is successful.
util::Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid,
uint8_t table_id, ContinuityCounter* cc,
uint8_t* buffer, ssize_t* bytes_modified);
} // namespace cas
} // namespace widevine

View File

@@ -0,0 +1,77 @@
////////////////////////////////////////////////////////////////////////////////
// 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 "media_cas_packager_sdk/internal/util.h"
#include <string.h>
#include "testing/gmock.h"
#include "testing/gunit.h"
namespace {
// ECM payload data taken from a CETS encrypted file at Google Fiber
// default_key_id = 0000000000000002, random_iv = 0x30EB, 0xDAF4, 0xC66D, 0x57DC
constexpr char kEcmPayload[] = {'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4',
'\xC6', '\x6D', '\x57', '\xDC'};
// ECM packet for Video PID (TSheader + payload) taken from a CETS encrypted
// file, payload_unit_start = 1, adaptation_field_control = 1, cc = 0
constexpr char kExpectedEcmPacket[] = {
// TS header.
'\x47', '\x5F', '\xFD', '\x10',
// Section header.
'\x00', '\x80', '\x70', '\x1C',
// ECM.
'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4', '\xC6', '\x6D', '\x57',
'\xDC',
// Padding.
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF'};
} // namespace
namespace widevine {
namespace cas {
TEST(InsertEcmAsTsPacketTest, BasicHappyPath) {
// declare variables used by InsertEcmAsTsPacket()
ContinuityCounter ecm_cc_ = 0;
ProgramId video_ecm_pid_ = 0x1FFD;
uint8_t buffer[188]; // output ts packet size
ssize_t output_bytes_modified = 0;
// convert kEcmPayload[] into a std::string to be accepted by the method
std::string ecm_data(kEcmPayload);
EXPECT_OK(InsertEcmAsTsPacket(ecm_data, video_ecm_pid_, kTsPacketTableId80,
&ecm_cc_, buffer, &output_bytes_modified));
EXPECT_EQ(0, memcmp(kExpectedEcmPacket, buffer, sizeof(buffer)));
EXPECT_EQ(1, ecm_cc_);
EXPECT_EQ(188, output_bytes_modified);
}
} // namespace cas
} // namespace widevine

View File

@@ -43,6 +43,7 @@ cc_library(
"//util:status",
"//media_cas_packager_sdk/internal:ecm",
"//media_cas_packager_sdk/internal:ecm_generator",
"//media_cas_packager_sdk/internal:key_fetcher",
"//protos/public:media_cas_encryption_proto",
"//protos/public:media_cas_proto",
],
@@ -93,15 +94,17 @@ cc_library(
deps = [
":wv_cas_types",
"//base",
"@abseil_repo//absl/base:core_headers",
"@abseil_repo//absl/memory",
"@abseil_repo//absl/strings",
"@abseil_repo//absl/base:core_headers", # buildcleaner: keep
"@abseil_repo//absl/memory", # buildcleaner: keep
"@abseil_repo//absl/strings", # buildcleaner: keep
"//util:status",
"//common:crypto_util",
"//example:constants",
"//media_cas_packager_sdk/internal:ecm",
"//media_cas_packager_sdk/internal:ecm_generator",
"//media_cas_packager_sdk/internal:fixed_key_fetcher",
"//media_cas_packager_sdk/internal:mpeg2ts",
"//media_cas_packager_sdk/internal:util",
],
)
@@ -114,6 +117,44 @@ cc_test(
":wv_cas_types",
"//testing:gunit_main",
"@abseil_repo//absl/strings",
"//media_cas_packager_sdk/internal:mpeg2ts",
],
)
cc_library(
name = "wv_cas_key_fetcher",
srcs = [
"wv_cas_key_fetcher.cc",
],
hdrs = [
"wv_cas_key_fetcher.h",
],
deps = [
"//base",
"@abseil_repo//absl/base:core_headers",
"@abseil_repo//absl/strings",
"//third_party/curl",
"//util:status",
"//common:signature_util",
"//media_cas_packager_sdk/internal:key_fetcher",
"//protos/public:media_cas_encryption_proto",
],
)
cc_test(
name = "wv_cas_key_fetcher_test",
size = "small",
srcs = [
"wv_cas_key_fetcher_test.cc",
],
deps = [
":wv_cas_key_fetcher",
"//base",
"//external:protobuf",
"//testing:gunit_main",
"@abseil_repo//absl/strings",
"//util:status",
"//protos/public:media_cas_encryption_proto",
],
)

View File

@@ -21,6 +21,8 @@
#include "media_cas_packager_sdk/internal/ecm.h"
#include "media_cas_packager_sdk/internal/ecm_generator.h"
#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
#include "media_cas_packager_sdk/internal/util.h"
#include "media_cas_packager_sdk/public/wv_cas_types.h"
namespace widevine {
@@ -312,5 +314,21 @@ WvCasStatus WvCasEcm::GenerateSingleKeyEcm(
return OK;
}
WvCasStatus WvCasEcm::GenerateTsPacket(const std::string& ecm, uint16_t pid,
uint8_t table_id,
uint8_t* continuity_counter,
uint8_t* packet) {
ssize_t bytes_modified = 0;
util::Status status = InsertEcmAsTsPacket(
ecm, pid, table_id, continuity_counter, packet, &bytes_modified);
if (!status.ok() || bytes_modified != kTsPacketSize) {
memset(packet, 0, kTsPacketSize);
LOG(ERROR) << "Failed to generate TS packet: " << status;
return INTERNAL;
}
return OK;
}
} // namespace cas
} // namespace widevine

View File

@@ -9,7 +9,7 @@
#ifndef MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_ECM_H_
#include <stddef.h>
#include <cstddef>
#include <list>
#include <map>
#include <string>
@@ -111,6 +111,29 @@ class WvCasEcm {
const char* const even_entitlement_key_id,
const char* const even_entitlement_key, std::string* ecm) const;
// Generate a TS packet with the given |ecm| as payload.
//
// Args (all pointer parameters must be not nullptr):
// - |ecm| serialized ECM, e.g., generated by GenerateEcm() method above
// - |pid| program ID for the ECM stream
// - |table_id| is the table ID byte put in the section header, it should be
// either 0x80 or 0x81. Changing table ID from 0x80 or 0x81 or
// 0x81 to 0x80 is used to singal to the client that the key contained
// in the ECM has changed. In other words, if you are building an ECM
// with a new key that was not in any previous ECM, you should flip the
// table ID so the client knows this is an important ECM it should process.
// - |continuity_counter| continuity_counter for the ECM packet,
// it will be incremented, only last 4 bits are used
// - |packet| a buffer of size at least 188 bytes to be used to return
// the generated TS packet
//
// Returns:
// - A status indicating whether there was any error during processing
virtual WvCasStatus GenerateTsPacket(const std::string& ecm, uint16_t pid,
uint8_t table_id,
uint8_t* continuity_counter,
uint8_t* packet);
private:
bool initialized_ = false;
int content_iv_size_;

View File

@@ -11,6 +11,7 @@
#include "testing/gunit.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "media_cas_packager_sdk/internal/mpeg2ts.h"
#include "media_cas_packager_sdk/public/wv_cas_types.h"
using ::testing::Test;
@@ -18,6 +19,7 @@ using ::testing::Test;
namespace widevine {
namespace cas {
namespace {
const char kEvenKey[] = "even_key........"; // 16 bytes
const char kCsaEvenKey[] = "even_key"; // 8 bytes
const char kCsaEvenKeyWithNul[] = {'\x01', '\x00', '\x00', '\x00',
@@ -37,6 +39,46 @@ const char kOddEntitlementKeyId[] = "odd_ent_key_id.."; // 16 bytes
const char kOddEntitlementKey[] =
"odd_entitlement_key............."; // 32 bytes
// ECM payload data taken from a CETS encrypted file at Google Fiber
// default_key_id = 0000000000000002, random_iv = 0x30EB, 0xDAF4, 0xC66D, 0x57DC
constexpr char kEcmPayload[] = {'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4',
'\xC6', '\x6D', '\x57', '\xDC'};
// ECM packet for Video PID (TSheader + payload) taken from a CETS encrypted
// file, payload_unit_start = 1, adaptation_field_control = 1, cc = 0
constexpr char kExpectedEcmPacket[] = {
// TS header.
'\x47', '\x5F', '\xFD', '\x10',
// Section header.
'\x00', '\x80', '\x70', '\x1C',
// ECM.
'\x5F', '\x08', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30',
'\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x30', '\x32',
'\x41', '\x70', '\x30', '\xEB', '\xDA', '\xF4', '\xC6', '\x6D', '\x57',
'\xDC',
// Padding.
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF',
'\xFF', '\xFF', '\xFF'};
} // namespace
class WvCasEcmTest : public Test {
protected:
WvCasEcmTest() {}
@@ -193,7 +235,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_16BytesContentIv_Cbc_Success) {
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40105c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
"4ad40101c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
"6e5f69762e2e2e2e2e2e2e2e2e6f64645f656e745f6b65795f69642e2e34cd74b6b99888"
"9aad0e71b44bdd8c0e03e31ea68ab80a6ee79f59f0936bc6aa64fe976b6a4a5db2dc7e3e"
@@ -216,7 +258,7 @@ TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_16BytesContentIv_Cbc_Success) {
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40104c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
"4ad40100c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952"
"a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665"
"6e5f69762e2e2e2e2e2e2e2e2e",
absl::BytesToHexString(actual_ecm));
@@ -242,7 +284,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_8BytesContentIv_Csa_Success) {
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40107806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
"4ad40105806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
"e6b5f391e74f120cfb876efba02d7f506f0ef185b3398096111dafd86ff7d395a2657665"
"6e5f69762e6f64645f656e745f6b65795f69642e2e7f655fe61e99e89e03ac23df98cc02"
"1cf21dfe9637c72c3480727ab18332d4ee219e81b8f34c9df2704b0595501832736f6464"
@@ -265,7 +307,7 @@ TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_8BytesContentIv_Csa_Success) {
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40106806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
"4ad40104806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10"
"e6b5f391e74f120cfb876efba02d7f506f0ef185b3398096111dafd86ff7d395a2657665"
"6e5f69762e",
absl::BytesToHexString(actual_ecm));
@@ -291,7 +333,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_8BytesContentIv_Csa_NulCharInKey_Success) {
/* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40107806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
"4ad40105806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
"d0b085eb9a85a57dbdb799158e829996988524bf3b3cfe01b28d4474f85ec2991d657665"
"6e5f69762e6f64645f656e745f6b65795f69642e2e874aab870ffba640875a4521d3cd57"
"02f26d0f9c7e9c69d7059c9ad42b091ec1f151aaa190536f4f330edebe84fe5a786f6464"
@@ -315,11 +357,36 @@ TEST_F(WvCasEcmTest,
/* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm));
EXPECT_EQ(
"4ad40106806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
"4ad40104806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf"
"d0b085eb9a85a57dbdb799158e829996988524bf3b3cfe01b28d4474f85ec2991d657665"
"6e5f69762e",
absl::BytesToHexString(actual_ecm));
}
TEST_F(WvCasEcmTest, GenerateTsPacket_TableId80) {
std::string ecm(kEcmPayload);
ProgramId pid = 0x1FFD;
ContinuityCounter cc = 0;
uint8_t packet[188];
EXPECT_EQ(OK, wv_cas_ecm_.GenerateTsPacket(ecm, pid, kTsPacketTableId80, &cc,
packet));
EXPECT_EQ(0, memcmp(kExpectedEcmPacket, packet, sizeof(packet)));
EXPECT_EQ(1, cc);
}
TEST_F(WvCasEcmTest, GenerateTsPacket_TableId81) {
std::string ecm(kEcmPayload);
ProgramId pid = 0x1FFD;
ContinuityCounter cc = 0;
uint8_t packet[188];
EXPECT_EQ(OK, wv_cas_ecm_.GenerateTsPacket(ecm, pid, kTsPacketTableId81, &cc,
packet));
char expected_ecm[188];
memcpy(expected_ecm, kExpectedEcmPacket, 188);
expected_ecm[5] = '\x81';
EXPECT_EQ(0, memcmp(expected_ecm, packet, sizeof(packet)));
EXPECT_EQ(1, cc);
}
} // namespace cas
} // namespace widevine

View File

@@ -0,0 +1,160 @@
////////////////////////////////////////////////////////////////////////////////
// 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 "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
#include <stddef.h>
#include <string.h>
#include <string>
#include "gflags/gflags.h"
#include "glog/logging.h"
#include "net/proto2/util/public/json_util.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "curl/curl.h"
#include "curl/easy.h"
#include "util/status.h"
#include "common/signature_util.h"
#include "protos/public/media_cas_encryption.pb.h"
using google::protobuf::util::JsonPrintOptions;
using google::protobuf::util::JsonStringToMessage;
using google::protobuf::util::MessageToJsonString;
DEFINE_string(
license_server, "",
"HTTP URL to the license server for making CAS encryption request");
DEFINE_string(signing_provider, "",
"Name of the provider signing the CAS encryption request");
DEFINE_string(signing_key, "",
"AES key (in hex) for signing the CAS encryption request");
DEFINE_string(signing_iv, "",
"AES iv (in hex) for signing the CAS encryption request");
namespace widevine {
namespace cas {
util::Status WvCasKeyFetcher::RequestEntitlementKey(
const std::string& request_string, std::string* signed_response_string) {
if (FLAGS_signing_provider.empty() || FLAGS_signing_key.empty() ||
FLAGS_signing_iv.empty()) {
return util::Status(
util::error::INVALID_ARGUMENT,
"Flag 'signing_provider', 'signing_key' or 'signing_iv' is empty");
}
// Processes request.
CasEncryptionRequest request;
request.ParseFromString(request_string);
std::string request_json;
JsonPrintOptions print_options;
// Set this option so that the json output is
// {"content_id":"MjExNDA4NDQ=", ...
// instead of
// {"contentId":"MjExNDA4NDQ=", ...
print_options.preserve_proto_field_names = true;
// NOTE: MessageToJsonString will automatically converts 'bytes' type fields
// to base64. For example content ID '21140844' becomes 'MjExNDA4NDQ='.
if (!MessageToJsonString(request, &request_json, print_options).ok()) {
return util::Status(util::error::INTERNAL,
"Failed to convert request message to json.");
}
LOG(INFO) << "Json CasEncryptionRequest: " << request_json;
// Creates signed request.
SignedCasEncryptionRequest signed_request;
signed_request.set_request(request_json);
std::string signature;
if (!signature_util::GenerateAesSignature(
request_json, absl::HexStringToBytes(FLAGS_signing_key),
absl::HexStringToBytes(FLAGS_signing_iv), &signature)
.ok()) {
return util::Status(util::error::INTERNAL, "Failed to sign the request.");
}
signed_request.set_signature(signature);
signed_request.set_signer(FLAGS_signing_provider);
std::string signed_request_json;
// NOTE: MessageToJsonString will automatically converts the 'request' and
// 'signature' fields in SignedCasEncryptionRequest to base64, because they
// are of type 'bytes'.
if (!MessageToJsonString(signed_request, &signed_request_json).ok()) {
return util::Status(util::error::INTERNAL,
"Failed to convert signed request message to json.");
}
LOG(INFO) << "Json SignedCasEncryptionRequest: " << signed_request_json;
// Makes HTTP request against License Server.
std::string http_response_json;
util::Status status =
MakeHttpRequest(signed_request_json, &http_response_json);
if (!status.ok()) {
return status;
}
LOG(INFO) << "Json HTTP response: " << http_response_json;
HttpResponse http_response;
if (!JsonStringToMessage(http_response_json, &http_response).ok()) {
return util::Status(util::error::INTERNAL,
"Failed to convert http response json to message.");
}
// Processes signed response.
// TODO(b/114741232): Seems we are getting CasEncryptionResponse back instead
// of SignedCasEncryptionResponse.
LOG(INFO) << "Json CasEncryptionResponse: " << http_response.response();
CasEncryptionResponse response;
if (!JsonStringToMessage(http_response.response(), &response).ok()) {
return util::Status(util::error::INTERNAL,
"Failed to convert response json to message.");
}
SignedCasEncryptionResponse signed_response;
signed_response.set_response(response.SerializeAsString());
signed_response.SerializeToString(signed_response_string);
return util::OkStatus();
}
size_t AppendToString(void* ptr, size_t size, size_t count, std::string* output) {
const absl::string_view data(static_cast<char*>(ptr), size * count);
absl::StrAppend(output, data);
return data.size();
}
util::Status WvCasKeyFetcher::MakeHttpRequest(
const std::string& signed_request_json, std::string* http_response_json) const {
CHECK(http_response_json);
if (FLAGS_license_server.empty()) {
return util::Status(util::error::INVALID_ARGUMENT,
"Flag 'license_server' is empty");
}
CURL* curl;
CURLcode curl_code;
curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, FLAGS_license_server.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, signed_request_json.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEDATA, http_response_json);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &AppendToString);
// If we don't provide POSTFIELDSIZE, libcurl will strlen() by itself.
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,
(int64_t)strlen(signed_request_json.c_str()));
curl_code = curl_easy_perform(curl);
if (curl_code != CURLE_OK) {
return util::Status(util::error::INTERNAL,
"curl_easy_perform() failed: " +
std::string(curl_easy_strerror(curl_code)));
}
curl_easy_cleanup(curl);
} else {
return util::Status(util::error::INTERNAL, "curl_easy_init() failed");
}
return util::OkStatus();
}
} // namespace cas
} // namespace widevine

View File

@@ -0,0 +1,58 @@
////////////////////////////////////////////////////////////////////////////////
// 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 MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_KEY_FETCHER_H_
#define MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_KEY_FETCHER_H_
#include <string>
#include "base/commandlineflags_declare.h"
#include "util/status.h"
#include "media_cas_packager_sdk/internal/key_fetcher.h"
DECLARE_string(license_server);
DECLARE_string(signing_provider);
DECLARE_string(signing_key);
DECLARE_string(signing_iv);
namespace widevine {
namespace cas {
// WV CAS KeyFetcher. Performs the communication with the Widevine License
// Server to get entitlement keys on behalf of a WvCasEcm object.
class WvCasKeyFetcher : public KeyFetcher {
public:
WvCasKeyFetcher() = default;
WvCasKeyFetcher(const WvCasKeyFetcher&) = delete;
WvCasKeyFetcher& operator=(const WvCasKeyFetcher&) = delete;
virtual ~WvCasKeyFetcher() = default;
// Get entitlement keys from the license server. Send a
// SignedCasEncryptionRequest message to the license server, receive a
// SignedCasEncryptionResponse and return it to the caller.
// Args:
// |request_string| a serialized CasEncryptionRequest message, produced
// by WvCasEcm::Initialize().
// |signed_response_string| a serialized SignedCasEncryptionResponse
// message. It should be passed into
// widevine::cas::Ecm::ProcessCasEncryptionResponse().
virtual util::Status RequestEntitlementKey(const std::string& request_string,
std::string* signed_response_string);
protected:
// Makes a HTTP request to License Server for entitlement key(s).
// Returns the HTTP response in Json format in |http_response_json|.
// Protected visibility to support unit testing.
virtual util::Status MakeHttpRequest(const std::string& signed_request_json,
std::string* http_response_json) const;
};
} // namespace cas
} // namespace widevine
#endif // MEDIA_CAS_PACKAGER_SDK_PUBLIC_WV_CAS_KEY_FETCHER_H_

View File

@@ -0,0 +1,109 @@
////////////////////////////////////////////////////////////////////////////////
// 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 "media_cas_packager_sdk/public/wv_cas_key_fetcher.h"
#include "base/commandlineflags_declare.h"
#include "glog/logging.h"
#include "google/protobuf/text_format.h"
#include "testing/gmock.h"
#include "testing/gunit.h"
#include "absl/strings/escaping.h"
#include "util/status.h"
#include "protos/public/media_cas_encryption.pb.h"
using testing::_;
using testing::DoAll;
using testing::Return;
using testing::SetArgumentPointee;
namespace {
const char kSigningProvider[] = "widevine_test";
const char kSingingKey[] =
"1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9";
const char kSingingIv[] = "d58ce954203b7c9a9a9d467f59839249";
const char kCasEncryptionRequest[] =
"content_id: \"21140844\" "
"provider: \"widevine\" "
"track_types: \"SD\" "
"key_rotation: false";
const char kSignedCasEncryptionRequest[] =
"{\"request\":"
"\"eyJjb250ZW50X2lkIjoiTWpFeE5EQTRORFE9IiwicHJvdmlkZXIiOiJ3aWRldmluZSIsIn"
"RyYWNrX3R5cGVzIjpbIlNEIl0sImtleV9yb3RhdGlvbiI6ZmFsc2V9\",\"signature\":"
"\"JyTnKEy1w98HRP1lL78+OIEiqtIXyfCN8iudvNXIWhw=\",\"signer\":\"widevine_"
"test\"}";
const char kHttpResponse[] =
"{\"response\":"
"\"eyJzdGF0dXMiOiJPSyIsImNvbnRlbnRfaWQiOiJNakV4TkRBNE5EUT0iLCJlbnRpdGxlbWVu"
"dF9rZXlzIjpbeyJrZXlfaWQiOiJNUGFndXhNb1hNNkUxUzhEOUF3RkNBPT0iLCJrZXkiOiJoZ1"
"JycmdqeUg4NjQycjY3VHd0OHJ1cU5MUGNMRmtKcWRVSUROdm5GZDBNPSIsInRyYWNrX3R5cGUi"
"OiJTRCIsImtleV9zbG90IjoiU0lOR0xFIn1dfQ==\"}";
} // namespace
namespace widevine {
namespace cas {
class MockWvCasKeyFetcher : public WvCasKeyFetcher {
public:
MockWvCasKeyFetcher() : WvCasKeyFetcher() {}
~MockWvCasKeyFetcher() override {}
MOCK_CONST_METHOD2(MakeHttpRequest,
util::Status(const std::string& signed_request_json,
std::string* http_response_json));
};
class WvCasKeyFetcherTest : public ::testing::Test {
public:
WvCasKeyFetcherTest() {}
void SetUp() override {
FLAGS_signing_provider = kSigningProvider;
FLAGS_signing_key = kSingingKey;
FLAGS_signing_iv = kSingingIv;
CHECK(
google::protobuf::TextFormat::ParseFromString(kCasEncryptionRequest, &request_));
response_.set_status(CasEncryptionResponse::OK);
response_.set_content_id("21140844");
CasEncryptionResponse::KeyInfo* entitlement_keys =
response_.add_entitlement_keys();
CHECK(absl::Base64Unescape("MPaguxMoXM6E1S8D9AwFCA==",
entitlement_keys->mutable_key_id()));
CHECK(absl::Base64Unescape("hgRrrgjyH8642r67Twt8ruqNLPcLFkJqdUIDNvnFd0M=",
entitlement_keys->mutable_key()));
entitlement_keys->set_track_type("SD");
entitlement_keys->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE);
}
protected:
MockWvCasKeyFetcher mock_key_fetcher_;
CasEncryptionRequest request_;
CasEncryptionResponse response_;
};
TEST_F(WvCasKeyFetcherTest, TestRequestEntitlementKey) {
EXPECT_CALL(mock_key_fetcher_,
MakeHttpRequest(kSignedCasEncryptionRequest, _))
.WillOnce(DoAll(SetArgumentPointee<1>(std::string(kHttpResponse)),
Return(util::OkStatus())));
std::string actual_signed_response;
EXPECT_OK(mock_key_fetcher_.RequestEntitlementKey(
request_.SerializeAsString(), &actual_signed_response));
SignedCasEncryptionResponse expected_signed_response;
expected_signed_response.set_response(response_.SerializeAsString());
EXPECT_EQ(expected_signed_response.SerializeAsString(),
actual_signed_response);
}
} // namespace cas
} // namespace widevine

View File

@@ -8,6 +8,7 @@
#include "media_cas_packager_sdk/public/wv_cas_types.h"
#include "glog/logging.h"
#include "base/macros.h"
namespace widevine {
@@ -37,34 +38,38 @@ std::string GetWvCasStatusMessage(WvCasStatus status) {
return kWvCasStatusMessage[status];
}
std::string CryptoModeToString(CryptoMode mode) {
switch (mode) {
case CryptoMode::kAesCtr: {
return "kAesCtr";
}
case CryptoMode::kAesCbc: {
return "kAesCbc";
}
case CryptoMode::kDvbCsa2: {
return "kDvbCsa2";
}
default: {
return "kCryptoModeUnspecified";
// Numeric value of crypto mode is the index into strings array.
static const char* kCrypoModeStrings[] = {
"AesCbc",
"AesCtr",
"DvbCsa2",
};
bool CryptoModeToString(CryptoMode mode, std::string* str) {
if (str == nullptr) {
return false;
}
int mode_idx = static_cast<int>(mode);
if (mode_idx >= 0 && mode_idx < arraysize(kCrypoModeStrings)) {
*str = kCrypoModeStrings[mode_idx];
return true;
}
LOG(ERROR) << "Invalid crypto mode: " << mode_idx;
return false;
}
CryptoMode StringToCryptoMode(std::string str) {
if (str == "kAesCtr") {
return CryptoMode::kAesCtr;
bool StringToCryptoMode(const std::string& str, CryptoMode* mode) {
if (mode == nullptr) {
return false;
}
if (str == "kAesCbc") {
return CryptoMode::kAesCbc;
for (int i = 0; i < arraysize(kCrypoModeStrings); ++i) {
if (str.compare(kCrypoModeStrings[i]) == 0) {
*mode = static_cast<CryptoMode>(i);
return true;
}
if (str == "kDvbCsa2") {
return CryptoMode::kDvbCsa2;
}
return CryptoMode::kCryptoModeUnspecified;
LOG(ERROR) << "Invalid crypto mode: " << str;
return false;
}
} // namespace cas

View File

@@ -50,17 +50,19 @@ enum WvCasStatus {
// Returns the message std::string for the given WvCasStatus.
std::string GetWvCasStatusMessage(WvCasStatus status);
// Crypto mode for encryption / decryption.
// Crypto mode for encryption / decryption. ENUM value should be consistent with
// https://docs.google.com/document/d/1A5vflf8tbKyUheV-xsvfxFqB6YyNLNdsGXYx8ZnhjfY/edit#heading=h.ej4ts3lifoio
enum class CryptoMode : int {
kCryptoModeUnspecified = 0,
kAesCbc = 0,
kAesCtr = 1,
kAesCbc = 2,
kDvbCsa2 = 3
kDvbCsa2 = 2,
};
std::string CryptoModeToString(CryptoMode mode);
// Returns false if mode is not a valid CryptoMode.
bool CryptoModeToString(CryptoMode mode, std::string* str);
CryptoMode StringToCryptoMode(std::string str);
// Returns false if str is not a valid CryptoMode.
bool StringToCryptoMode(const std::string& str, CryptoMode* mode);
} // namespace cas
} // namespace widevine

View File

@@ -25,19 +25,25 @@ TEST(WvCasTypesTest, GetWvCasStatusMessage) {
}
TEST(WvCasTypesTest, CryptoModeToString) {
EXPECT_EQ("kAesCtr", CryptoModeToString(CryptoMode::kAesCtr));
EXPECT_EQ("kAesCbc", CryptoModeToString(CryptoMode::kAesCbc));
EXPECT_EQ("kDvbCsa2", CryptoModeToString(CryptoMode::kDvbCsa2));
EXPECT_EQ("kCryptoModeUnspecified",
CryptoModeToString(CryptoMode::kCryptoModeUnspecified));
std::string crypto_mode;
ASSERT_TRUE(CryptoModeToString(CryptoMode::kAesCtr, &crypto_mode));
EXPECT_EQ("AesCtr", crypto_mode);
ASSERT_TRUE(CryptoModeToString(CryptoMode::kAesCbc, &crypto_mode));
EXPECT_EQ("AesCbc", crypto_mode);
ASSERT_TRUE(CryptoModeToString(CryptoMode::kDvbCsa2, &crypto_mode));
EXPECT_EQ("DvbCsa2", crypto_mode);
EXPECT_FALSE(CryptoModeToString(static_cast<CryptoMode>(-1), &crypto_mode));
}
TEST(WvCasTypesTest, StringToCryptoMode) {
EXPECT_EQ(CryptoMode::kAesCtr, StringToCryptoMode("kAesCtr"));
EXPECT_EQ(CryptoMode::kAesCbc, StringToCryptoMode("kAesCbc"));
EXPECT_EQ(CryptoMode::kDvbCsa2, StringToCryptoMode("kDvbCsa2"));
EXPECT_EQ(CryptoMode::kCryptoModeUnspecified,
StringToCryptoMode("wrong crypto mode"));
CryptoMode crypto_mode;
ASSERT_TRUE(StringToCryptoMode("AesCtr", &crypto_mode));
EXPECT_EQ(CryptoMode::kAesCtr, crypto_mode);
ASSERT_TRUE(StringToCryptoMode("AesCbc", &crypto_mode));
EXPECT_EQ(CryptoMode::kAesCbc, crypto_mode);
ASSERT_TRUE(StringToCryptoMode("DvbCsa2", &crypto_mode));
EXPECT_EQ(CryptoMode::kDvbCsa2, crypto_mode);
EXPECT_FALSE(StringToCryptoMode("invalid crypto mode", &crypto_mode));
}
} // namespace cas

View File

@@ -18,7 +18,6 @@ cc_library(
],
deps = [
"//external:gtest",
"//util:status",
],
)

View File

@@ -15,22 +15,6 @@ filegroup(
name = "binary_release_files",
srcs = [
"error_space.h",
"status.h",
],
)
cc_library(
name = "status",
srcs = [
"status.cc",
],
hdrs = [
"status.h",
],
deps = [
":error_space",
"//base",
"@abseil_repo//absl/strings",
],
)
@@ -43,17 +27,8 @@ cc_library(
name = "proto_status",
hdrs = ["proto_status.h"],
deps = [
":status",
"//external:protobuf",
],
)
cc_test(
name = "status_test",
srcs = ["status_test.cc"],
deps = [
":status",
"//testing:gunit_main",
"//util:error_space",
],
)

View File

@@ -49,11 +49,11 @@ class ErrorSpaceImpl : public ErrorSpace {
// pointer to stateless static methods, so that clients of ErrorSpaceImpl are
// safe to have constexpr global instances.
static std::string SpaceNameImpl(const ErrorSpace* /*space*/) {
return T::SpaceName();
return T::space_name();
}
static std::string CodeToStringImpl(const ErrorSpace* /*space*/, int code) {
return T::CodeToString(code);
return T::code_to_string(code);
}
};

View File

@@ -16,14 +16,16 @@ namespace {
class Space1 : public util::ErrorSpaceImpl<Space1> {
public:
static std::string SpaceName() { return "Space1"; }
static std::string CodeToString(int code) { return "Test" + std::to_string(code); }
static std::string space_name() { return "Space1"; }
static std::string code_to_string(int code) {
return "Test" + std::to_string(code);
}
};
TEST(ErrorSpaceTest, Basic) {
const ErrorSpace* space1 = Space1::Get();
EXPECT_EQ("Space1", space1->SpaceName());
EXPECT_EQ(Space1::CodeToString(23), space1->String(23));
EXPECT_EQ(Space1::code_to_string(23), space1->String(23));
}
} // namespace

View File

@@ -11,24 +11,26 @@
#include "google/protobuf/descriptor.h"
#include "google/protobuf/generated_enum_reflection.h"
#include "util/status.h"
#include "util/error_space.h"
namespace widevine {
namespace util {
template <typename T>
class ProtoEnumErrorSpace : public ErrorSpaceImpl<ProtoEnumErrorSpace<T>> {
class ProtoEnumErrorSpace
: public util::ErrorSpaceImpl<ProtoEnumErrorSpace<T>> {
public:
static std::string SpaceName() {
static std::string space_name() {
return google::protobuf::GetEnumDescriptor<T>()->full_name();
}
static std::string CodeToString(int code) {
static std::string code_to_string(int code) {
const google::protobuf::EnumValueDescriptor* v =
google::protobuf::GetEnumDescriptor<T>()->FindValueByNumber(code);
if (v) return v->name();
return std::to_string(code);
}
};
} // namespace util

View File

@@ -9,6 +9,7 @@
#include "util/proto_status.h"
#include "testing/gunit.h"
#include "common/status.h"
#include "protos/public/errors.pb.h"
namespace widevine {
@@ -31,10 +32,18 @@ TEST(StatusTest, Same) {
Status status1(ProtoEnumErrorSpace<Errors>::Get(), PROVIDER_ID_MISMATCH,
"provider_id_mismatch");
Status status2(ProtoEnumErrorSpace<Errors>::Get(), PROVIDER_ID_MISMATCH,
"this is a provider_id_mismatch error");
"provider_id_mismatch");
EXPECT_EQ(status1, status2);
}
TEST(StatusTest, ErrorMessageMismatch) {
Status status1(ProtoEnumErrorSpace<Errors>::Get(), PROVIDER_ID_MISMATCH,
"provider_id_mismatch");
Status status2(ProtoEnumErrorSpace<Errors>::Get(), PROVIDER_ID_MISMATCH,
"this is a provider_id_mismatch error");
EXPECT_NE(status1, status2);
}
TEST(StatusTest, NotTheSameStatus) {
Status status1(ProtoEnumErrorSpace<Errors>::Get(), PROVIDER_ID_MISMATCH,
"provider_id_mismatch");

View File

@@ -1,62 +0,0 @@
////////////////////////////////////////////////////////////////////////////////
// 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 <string>
#include "base/macros.h"
#include "absl/strings/str_cat.h"
#include "util/status.h"
namespace widevine {
namespace util {
namespace {
const char* kLicenseServerStatusMessage[] = {"OK",
"UNKNOWN_ERROR",
"UNKNOWN_ERROR",
"INVALID_ARGUMENT",
"UNKNOWN_ERROR",
"NOT_FOUND",
"ALREADY_EXISTS",
"PERMISSION_DENIED",
"UNKNOWN_ERROR",
"UNKNOWN_ERROR",
"UNKNOWN_ERROR",
"UNKNOWN_ERROR",
"UNIMPLEMENTED",
"INTERNAL",
"UNAVAILABLE"};
} // namespace
std::string GenericErrorSpace::SpaceName() { return "generic"; }
std::string GenericErrorSpace::CodeToString(int code) {
static_assert(
arraysize(kLicenseServerStatusMessage) == error::NUM_ERRORS,
"mismatching license_server_sdk status message and license_server_sdk "
"status.");
if (code >= 0 && code < error::NUM_ERRORS)
return kLicenseServerStatusMessage[code];
return std::to_string(code);
}
std::string Status::ToString() const {
if (status_code_ == error::OK) return "OK";
return absl::StrCat("Errors::", error_space_->String(status_code_), ": ",
error_message_);
}
std::ostream& operator<<(std::ostream& os, const Status& x) {
os << x.ToString();
return os;
}
} // namespace util
} // namespace widevine

48
zlib.BUILD Normal file
View File

@@ -0,0 +1,48 @@
################################################################################
# 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.
################################################################################
# Build file for zlib.
package(default_visibility = ["//visibility:public"])
cc_library(
name = "zlib",
srcs = [
"adler32.c",
"compress.c",
"crc32.c",
"crc32.h",
"deflate.c",
"deflate.h",
"gzclose.c",
"gzguts.h",
"gzlib.c",
"gzread.c",
"gzwrite.c",
"infback.c",
"inffast.c",
"inffast.h",
"inffixed.h",
"inflate.c",
"inflate.h",
"inftrees.c",
"inftrees.h",
"trees.c",
"trees.h",
"uncompr.c",
"zconf.h",
"zutil.c",
"zutil.h",
],
hdrs = ["zlib.h"],
copts = [
"-Wno-shift-negative-value",
"-Wno-implicit-function-declaration",
],
includes = ["."],
)