diff --git a/WORKSPACE b/WORKSPACE index 3ed01b1..1d5e2a1 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -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", diff --git a/common/BUILD b/common/BUILD index 28b097e..13346a0 100644 --- a/common/BUILD +++ b/common/BUILD @@ -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", diff --git a/common/certificate_util.cc b/common/certificate_util.cc deleted file mode 100644 index 356ff69..0000000 --- a/common/certificate_util.cc +++ /dev/null @@ -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 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 diff --git a/common/certificate_util.h b/common/certificate_util.h deleted file mode 100644 index b6fe1d0..0000000 --- a/common/certificate_util.h +++ /dev/null @@ -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 - -#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_ diff --git a/common/client_cert.cc b/common/client_cert.cc new file mode 100644 index 0000000..da0d33e --- /dev/null +++ b/common/client_cert.cc @@ -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 +#include +#include + +#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> +// 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 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 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& keymap) { + std::vector keyvector; + keyvector.reserve(keymap.size()); + for (std::multimap::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 diff --git a/common/client_cert.h b/common/client_cert.h new file mode 100644 index 0000000..11812af --- /dev/null +++ b/common/client_cert.h @@ -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 +#include +#include + +#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& 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 rsa_public_key_; + + private: + CertificateClientCert(); + + DISALLOW_COPY_AND_ASSIGN(CertificateClientCert); +}; + +} // namespace widevine +#endif // COMMON_CLIENT_CERT_H__ diff --git a/common/client_cert_test.cc b/common/client_cert_test.cc new file mode 100644 index 0000000..4321f30 --- /dev/null +++ b/common/client_cert_test.cc @@ -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 +#include +#include +#include + +#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 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 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 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 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 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 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 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 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 drm_certificate( + GenerateDrmCertificate(system_id, serial_number)); + std::unique_ptr 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 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 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 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 dev_cert; + std::unique_ptr signer_cert; + std::unique_ptr signed_signer; + + // Invalid serialized device certificate. + std::unique_ptr 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 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 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 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 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 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 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 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 signed_provisioner_cert( + GenerateSignedProvisionerCertificate(system_id, provisioner_serial_number, + service_id)); + + std::unique_ptr signed_intermediate_cert( + GenerateSignedIntermediateCertificate(signed_provisioner_cert.release(), + system_id, + intermediate_serial_number)); + + std::unique_ptr 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 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 signed_provisioner_cert( + GenerateSignedProvisionerCertificate(system_id, provisioner_serial_number, + service_id)); + + std::unique_ptr signed_intermediate_cert( + GenerateSignedIntermediateCertificate(signed_provisioner_cert.release(), + system_id, + intermediate_serial_number)); + + std::unique_ptr 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 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 signed_intermediate_cert( + GenerateSignedIntermediateCertificate(signed_intermediate_cert2.release(), + system_id, + intermediate_serial_number)); + std::unique_ptr 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 diff --git a/common/device_status_list.cc b/common/device_status_list.cc new file mode 100644 index 0000000..187b0f0 --- /dev/null +++ b/common/device_status_list.cc @@ -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 +#include + +#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 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 diff --git a/common/device_status_list.h b/common/device_status_list.h new file mode 100644 index 0000000..d46767f --- /dev/null +++ b/common/device_status_list.h @@ -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 +#include + +#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 . + // Caller owns 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 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 allowed_revoked_devices_; + + DISALLOW_COPY_AND_ASSIGN(DeviceStatusList); +}; + +} // namespace widevine +#endif // COMMON_DEVICE_STATUS_LIST_H__ diff --git a/common/device_status_list_test.cc b/common/device_status_list_test.cc new file mode 100644 index 0000000..b76ce55 --- /dev/null +++ b/common/device_status_list_test.cc @@ -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 +#include +#include +#include + +#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 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 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 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 diff --git a/common/drm_root_certificate.cc b/common/drm_root_certificate.cc index dc42f66..9e52ad9 100644 --- a/common/drm_root_certificate.cc +++ b/common/drm_root_certificate.cc @@ -6,23 +6,34 @@ // widevine-licensing@google.com. //////////////////////////////////////////////////////////////////////////////// +// common_typos_disable. Successful / successfull. + #include "common/drm_root_certificate.h" #include #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* 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 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 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* cert) { CHECK(cert); + return Create(cert_type, absl::make_unique(), cert); +} + +std::unique_ptr DrmRootCertificate::CreateByType( + CertificateType cert_type, util::Status* status) { + CHECK(status); + + std::unique_ptr 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* 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 key_factory, + std::unique_ptr* 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 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* 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 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; - } - 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; +util::Status DrmRootCertificate::VerifyCertificate( + const std::string& serialized_certificate, + SignedDrmCertificate* signed_certificate, + DrmCertificate* certificate) const { + std::unique_ptr local_signed_certificate; + if (!signed_certificate) { + local_signed_certificate = absl::make_unique(); + signed_certificate = local_signed_certificate.get(); } - return root_cert; + if (!signed_certificate->ParseFromString(serialized_certificate)) { + return util::Status(error_space, INVALID_DRM_CERTIFICATE, + "invalid-signed-drm-certificate"); + } + + std::unique_ptr local_certificate; + if (!certificate) { + local_certificate = absl::make_unique(); + certificate = local_certificate.get(); + } + 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); } -std::string DrmRootCertificate::GetDigest(CertificateType cert_type) { - std::string cert(GetDrmRootCertificate(cert_type)); - if (cert.empty()) { - return std::string(); +// 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_); } - std::string hash(SHA256_DIGEST_LENGTH, 0); - SHA256(reinterpret_cast(cert.data()), cert.size(), - reinterpret_cast(&hash[0])); - return absl::BytesToHexString(hash); + + 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 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 diff --git a/common/drm_root_certificate.h b/common/drm_root_certificate.h index fdea567..a18ae44 100644 --- a/common/drm_root_certificate.h +++ b/common/drm_root_certificate.h @@ -13,6 +13,8 @@ #ifndef COMMON_DRM_ROOT_CERTIFICATE_H_ #define COMMON_DRM_ROOT_CERTIFICATE_H_ +// common_typos_disable. Successful / successfull. + #include #include @@ -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 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* 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 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 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* 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 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 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 key_factory, std::unique_ptr* 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 key_factory_; + mutable std::unique_ptr signature_cache_; + DISALLOW_IMPLICIT_CONSTRUCTORS(DrmRootCertificate); }; diff --git a/common/drm_root_certificate_test.cc b/common/drm_root_certificate_test.cc index f5029d9..5b192ce 100644 --- a/common/drm_root_certificate_test.cc +++ b/common/drm_root_certificate_test.cc @@ -9,92 +9,256 @@ // Description: // Unit tests for drm_root_certificate.cc +#include "common/drm_root_certificate.h" + #include +#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 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 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 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 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* 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> private_keys_; + SignedDrmCertificate signed_drm_certificate_; + DrmCertificate drm_certificates_[3]; + std::unique_ptr root_cert_; }; -TEST_F(DrmRootCertificateTest, DrmRootCertificateCreation) { - RsaTestKeys test_keys; - std::unique_ptr 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 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 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 diff --git a/common/drm_service_certificate.cc b/common/drm_service_certificate.cc index 75c0047..466ee31 100644 --- a/common/drm_service_certificate.cc +++ b/common/drm_service_certificate.cc @@ -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 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 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 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 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) { diff --git a/common/drm_service_certificate.h b/common/drm_service_certificate.h index 88a2161..69a3036 100644 --- a/common/drm_service_certificate.h +++ b/common/drm_service_certificate.h @@ -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 diff --git a/common/drm_service_certificate_test.cc b/common/drm_service_certificate_test.cc index fbd02bb..b2b8628 100644 --- a/common/drm_service_certificate_test.cc +++ b/common/drm_service_certificate_test.cc @@ -6,6 +6,8 @@ // widevine-licensing@google.com. //////////////////////////////////////////////////////////////////////////////// +#include "common/drm_service_certificate.h" + #include #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 root_private_key_; + std::unique_ptr 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) { diff --git a/common/mock_rsa_key.h b/common/mock_rsa_key.h index dd8fa9a..e221785 100644 --- a/common/mock_rsa_key.h +++ b/common/mock_rsa_key.h @@ -54,14 +54,14 @@ class MockRsaKeyFactory : public RsaKeyFactory { MockRsaKeyFactory() {} ~MockRsaKeyFactory() override {} - MOCK_METHOD1(CreateFromPkcs1PrivateKey, - std::unique_ptr(const std::string& private_key)); - MOCK_METHOD2( + MOCK_CONST_METHOD1(CreateFromPkcs1PrivateKey, + std::unique_ptr(const std::string& private_key)); + MOCK_CONST_METHOD2( CreateFromPkcs8PrivateKey, std::unique_ptr(const std::string& private_key, const std::string& private_key_passphrase)); - MOCK_METHOD1(CreateFromPkcs1PublicKey, - std::unique_ptr(const std::string& public_key)); + MOCK_CONST_METHOD1(CreateFromPkcs1PublicKey, + std::unique_ptr(const std::string& public_key)); private: MockRsaKeyFactory(const MockRsaKeyFactory&) = delete; diff --git a/common/openssl_util.h b/common/openssl_util.h index b715c8b..824aa45 100644 --- a/common/openssl_util.h +++ b/common/openssl_util.h @@ -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; using ScopedBIO = ScopedOpenSSLType; +using ScopedPKCS7 = ScopedOpenSSLType; using ScopedPKEY = ScopedOpenSSLType; using ScopedRSA = ScopedOpenSSLType; using ScopedX509 = ScopedOpenSSLType; @@ -59,6 +61,7 @@ using ScopedX509StoreCtx = ScopedOpenSSLType; using ScopedX509Req = ScopedOpenSSLType; using ScopedAsn1UtcTime = ScopedOpenSSLType; +using ScopedAsn1Time = ScopedOpenSSLType; using ScopedAsn1Utc8String = ScopedOpenSSLType; using ScopedAsn1Integer = ScopedOpenSSLType; diff --git a/common/remote_attestation_verifier.cc b/common/remote_attestation_verifier.cc index b8cb252..185369a 100644 --- a/common/remote_attestation_verifier.cc +++ b/common/remote_attestation_verifier.cc @@ -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(); diff --git a/common/remote_attestation_verifier.h b/common/remote_attestation_verifier.h index 4ea7d2c..34be518 100644 --- a/common/remote_attestation_verifier.h +++ b/common/remote_attestation_verifier.h @@ -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. diff --git a/common/rsa_key.cc b/common/rsa_key.cc index 3f17744..6a14527 100644 --- a/common/rsa_key.cc +++ b/common/rsa_key.cc @@ -286,12 +286,12 @@ RsaKeyFactory::RsaKeyFactory() {} RsaKeyFactory::~RsaKeyFactory() {} std::unique_ptr RsaKeyFactory::CreateFromPkcs1PrivateKey( - const std::string& private_key) { + const std::string& private_key) const { return std::unique_ptr(RsaPrivateKey::Create(private_key)); } std::unique_ptr 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 RsaKeyFactory::CreateFromPkcs8PrivateKey( } std::unique_ptr RsaKeyFactory::CreateFromPkcs1PublicKey( - const std::string& public_key) { + const std::string& public_key) const { return std::unique_ptr(RsaPublicKey::Create(public_key)); } diff --git a/common/rsa_key.h b/common/rsa_key.h index 30913a8..cb15ff5 100644 --- a/common/rsa_key.h +++ b/common/rsa_key.h @@ -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 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 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 CreateFromPkcs1PublicKey( - const std::string& public_key); + const std::string& public_key) const; private: DISALLOW_COPY_AND_ASSIGN(RsaKeyFactory); diff --git a/common/status.cc b/common/status.cc new file mode 100644 index 0000000..51c5ada --- /dev/null +++ b/common/status.cc @@ -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 + +#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 { + 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 diff --git a/util/status.h b/common/status.h similarity index 65% rename from util/status.h rename to common/status.h index 5e3b15b..c716834 100644 --- a/util/status.h +++ b/common/status.h @@ -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 @@ -54,66 +54,57 @@ enum StatusCode { } // namespace error -class GenericErrorSpace : public ErrorSpaceImpl { - 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_ diff --git a/util/status_test.cc b/common/status_test.cc similarity index 88% rename from util/status_test.cc rename to common/status_test.cc index a91259a..7c1b6ac 100644 --- a/util/status_test.cc +++ b/common/status_test.cc @@ -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 diff --git a/common/test_certificates.cc b/common/test_drm_certificates.cc similarity index 99% rename from common/test_certificates.cc rename to common/test_drm_certificates.cc index b9f53d8..69faa48 100644 --- a/common/test_certificates.cc +++ b/common/test_drm_certificates.cc @@ -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)), diff --git a/common/test_certificates.h b/common/test_drm_certificates.h similarity index 84% rename from common/test_certificates.h rename to common/test_drm_certificates.h index 598b492..666d693 100644 --- a/common/test_certificates.h +++ b/common/test_drm_certificates.h @@ -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 #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_ diff --git a/common/vmp_checker.cc b/common/vmp_checker.cc index cbba839..e4354b8 100644 --- a/common/vmp_checker.cc +++ b/common/vmp_checker.cc @@ -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 ca_cert(new X509Cert); util::Status status = ca_cert->LoadDer( cert_type == kCertificateTypeProduction diff --git a/common/vmp_checker.h b/common/vmp_checker.h index e5269ad..b16107c 100644 --- a/common/vmp_checker.h +++ b/common/vmp_checker.h @@ -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); diff --git a/common/vmp_checker_test.cc b/common/vmp_checker_test.cc index 521b5a6..005dc86 100644 --- a/common/vmp_checker_test.cc +++ b/common/vmp_checker_test.cc @@ -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( diff --git a/common/x509_cert.cc b/common/x509_cert.cc index ddd40e6..aa95ff4 100644 --- a/common/x509_cert.cc +++ b/common/x509_cert.cc @@ -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(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(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); diff --git a/common/x509_cert.h b/common/x509_cert.h index db08235..e303006 100644 --- a/common/x509_cert.h +++ b/common/x509_cert.h @@ -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); diff --git a/common/x509_cert_test.cc b/common/x509_cert_test.cc index 0cc8ccb..85cdee2 100644 --- a/common/x509_cert_test.cc +++ b/common/x509_cert_test.cc @@ -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(¬_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(¬_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 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 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 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 cert1(new X509Cert); ASSERT_EQ(util::OkStatus(), cert1->LoadPem(kTestPemCert)); diff --git a/curl.BUILD b/curl.BUILD new file mode 100644 index 0000000..3c912a3 --- /dev/null +++ b/curl.BUILD @@ -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 ", + "# 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", + ]), +) diff --git a/example/BUILD b/example/BUILD index 4c6cb44..b3938aa 100644 --- a/example/BUILD +++ b/example/BUILD @@ -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", + ], +) diff --git a/example/wv_cas_key_fetcher_example.cc b/example/wv_cas_key_fetcher_example.cc new file mode 100644 index 0000000..aee22e3 --- /dev/null +++ b/example/wv_cas_key_fetcher_example.cc @@ -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 +#include + +#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; +} diff --git a/media_cas_packager_sdk/internal/BUILD b/media_cas_packager_sdk/internal/BUILD index 8f13434..5f64e1a 100644 --- a/media_cas_packager_sdk/internal/BUILD +++ b/media_cas_packager_sdk/internal/BUILD @@ -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", ], ) diff --git a/media_cas_packager_sdk/internal/ecm.cc b/media_cas_packager_sdk/internal/ecm.cc index ac13752..1a1d629 100644 --- a/media_cas_packager_sdk/internal/ecm.cc +++ b/media_cas_packager_sdk/internal/ecm.cc @@ -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; - } - + 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& keys) { generation()); std::bitset decrypt_mode( static_cast(crypto_mode())); - if (decrypt_mode.to_string() == "00") { - LOG(FATAL) << "Invalid decrypt mode \"00\""; - } std::bitset rotation_enabled( RotationFieldValue(paired_keys_required())); std::bitset wrapped_key_iv_size( diff --git a/media_cas_packager_sdk/internal/mpeg2ts.h b/media_cas_packager_sdk/internal/mpeg2ts.h new file mode 100644 index 0000000..ca7fa7c --- /dev/null +++ b/media_cas_packager_sdk/internal/mpeg2ts.h @@ -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 + +#include +#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_ diff --git a/media_cas_packager_sdk/internal/ts_packet.cc b/media_cas_packager_sdk/internal/ts_packet.cc new file mode 100644 index 0000000..95e0ae9 --- /dev/null +++ b/media_cas_packager_sdk/internal/ts_packet.cc @@ -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 + +#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 diff --git a/media_cas_packager_sdk/internal/ts_packet.h b/media_cas_packager_sdk/internal/ts_packet.h new file mode 100644 index 0000000..9afb234 --- /dev/null +++ b/media_cas_packager_sdk/internal/ts_packet.h @@ -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 +#include + +#include +#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_ diff --git a/media_cas_packager_sdk/internal/ts_packet_test.cc b/media_cas_packager_sdk/internal/ts_packet_test.cc new file mode 100644 index 0000000..68e1037 --- /dev/null +++ b/media_cas_packager_sdk/internal/ts_packet_test.cc @@ -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 + +#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 diff --git a/media_cas_packager_sdk/internal/util.cc b/media_cas_packager_sdk/internal/util.cc index 31e7b00..95c292b 100644 --- a/media_cas_packager_sdk/internal/util.cc +++ b/media_cas_packager_sdk/internal/util.cc @@ -9,12 +9,23 @@ #include "media_cas_packager_sdk/internal/util.h" #include +#include #include #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 diff --git a/media_cas_packager_sdk/internal/util.h b/media_cas_packager_sdk/internal/util.h index 3f8cfc8..0b66276 100644 --- a/media_cas_packager_sdk/internal/util.h +++ b/media_cas_packager_sdk/internal/util.h @@ -9,7 +9,12 @@ #ifndef MEDIA_CAS_PACKAGER_SDK_INTERNAL_UTIL_H_ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_UTIL_H_ +#include +#include + #include +#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 diff --git a/media_cas_packager_sdk/internal/util_test.cc b/media_cas_packager_sdk/internal/util_test.cc new file mode 100644 index 0000000..48a2f7f --- /dev/null +++ b/media_cas_packager_sdk/internal/util_test.cc @@ -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 + +#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 diff --git a/media_cas_packager_sdk/public/BUILD b/media_cas_packager_sdk/public/BUILD index ab2c395..00099c2 100644 --- a/media_cas_packager_sdk/public/BUILD +++ b/media_cas_packager_sdk/public/BUILD @@ -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", ], ) diff --git a/media_cas_packager_sdk/public/wv_cas_ecm.cc b/media_cas_packager_sdk/public/wv_cas_ecm.cc index 7f9a8f5..e243a74 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm.cc +++ b/media_cas_packager_sdk/public/wv_cas_ecm.cc @@ -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 diff --git a/media_cas_packager_sdk/public/wv_cas_ecm.h b/media_cas_packager_sdk/public/wv_cas_ecm.h index 14bce9d..d85623c 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm.h +++ b/media_cas_packager_sdk/public/wv_cas_ecm.h @@ -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 +#include #include #include #include @@ -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_; diff --git a/media_cas_packager_sdk/public/wv_cas_ecm_test.cc b/media_cas_packager_sdk/public/wv_cas_ecm_test.cc index bc31973..083e0ee 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_ecm_test.cc @@ -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', @@ -26,9 +28,9 @@ const char kEvenContentIv8Bytes[] = "even_iv."; // 8 bytes const char kEvenContentIv16Bytes[] = "even_iv........."; // 16 bytes const char kEvenEntitlementKeyId[] = "even_ent_key_id."; // 16 bytes const char kEvenEntitlementKey[] = - "even_entitlement_key............"; // 32 bytes -const char kOddKey[] = "odd_key........."; // 16 bytes -const char kCsaOddKey[] = "odd_key."; // 8 bytes + "even_entitlement_key............"; // 32 bytes +const char kOddKey[] = "odd_key........."; // 16 bytes +const char kCsaOddKey[] = "odd_key."; // 8 bytes const char kCsaOddKeyWithNul[] = {'\x00', '\x02', '\x00', '\x00', '\x00', '\x00', '\x02', '\x00'}; const char kOddContentIv8Bytes[] = "odd_iv.."; // 8 bytes @@ -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 diff --git a/media_cas_packager_sdk/public/wv_cas_key_fetcher.cc b/media_cas_packager_sdk/public/wv_cas_key_fetcher.cc new file mode 100644 index 0000000..a1e5056 --- /dev/null +++ b/media_cas_packager_sdk/public/wv_cas_key_fetcher.cc @@ -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 +#include +#include + +#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(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 diff --git a/media_cas_packager_sdk/public/wv_cas_key_fetcher.h b/media_cas_packager_sdk/public/wv_cas_key_fetcher.h new file mode 100644 index 0000000..91de195 --- /dev/null +++ b/media_cas_packager_sdk/public/wv_cas_key_fetcher.h @@ -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 + +#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_ diff --git a/media_cas_packager_sdk/public/wv_cas_key_fetcher_test.cc b/media_cas_packager_sdk/public/wv_cas_key_fetcher_test.cc new file mode 100644 index 0000000..dd3491f --- /dev/null +++ b/media_cas_packager_sdk/public/wv_cas_key_fetcher_test.cc @@ -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 diff --git a/media_cas_packager_sdk/public/wv_cas_types.cc b/media_cas_packager_sdk/public/wv_cas_types.cc index d2bad18..11c12e0 100644 --- a/media_cas_packager_sdk/public/wv_cas_types.cc +++ b/media_cas_packager_sdk/public/wv_cas_types.cc @@ -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(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(i); + return true; + } } - if (str == "kDvbCsa2") { - return CryptoMode::kDvbCsa2; - } - return CryptoMode::kCryptoModeUnspecified; + LOG(ERROR) << "Invalid crypto mode: " << str; + return false; } } // namespace cas diff --git a/media_cas_packager_sdk/public/wv_cas_types.h b/media_cas_packager_sdk/public/wv_cas_types.h index 64024e2..3acd30e 100644 --- a/media_cas_packager_sdk/public/wv_cas_types.h +++ b/media_cas_packager_sdk/public/wv_cas_types.h @@ -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 diff --git a/media_cas_packager_sdk/public/wv_cas_types_test.cc b/media_cas_packager_sdk/public/wv_cas_types_test.cc index dfe14cf..6068e82 100644 --- a/media_cas_packager_sdk/public/wv_cas_types_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_types_test.cc @@ -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(-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 diff --git a/testing/BUILD b/testing/BUILD index bad30ce..9fd8c49 100644 --- a/testing/BUILD +++ b/testing/BUILD @@ -18,7 +18,6 @@ cc_library( ], deps = [ "//external:gtest", - "//util:status", ], ) diff --git a/util/BUILD b/util/BUILD index 714b6ef..d40db8b 100644 --- a/util/BUILD +++ b/util/BUILD @@ -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", ], ) diff --git a/util/error_space.h b/util/error_space.h index 68c093a..2e66a39 100644 --- a/util/error_space.h +++ b/util/error_space.h @@ -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); } }; diff --git a/util/error_space_test.cc b/util/error_space_test.cc index a32d339..47f547f 100644 --- a/util/error_space_test.cc +++ b/util/error_space_test.cc @@ -16,14 +16,16 @@ namespace { class Space1 : public util::ErrorSpaceImpl { 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 diff --git a/util/proto_status.h b/util/proto_status.h index 2205db4..1878bc2 100644 --- a/util/proto_status.h +++ b/util/proto_status.h @@ -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 -class ProtoEnumErrorSpace : public ErrorSpaceImpl> { +class ProtoEnumErrorSpace + : public util::ErrorSpaceImpl> { public: - static std::string SpaceName() { + static std::string space_name() { return google::protobuf::GetEnumDescriptor()->full_name(); } - static std::string CodeToString(int code) { + static std::string code_to_string(int code) { const google::protobuf::EnumValueDescriptor* v = google::protobuf::GetEnumDescriptor()->FindValueByNumber(code); if (v) return v->name(); return std::to_string(code); } + }; } // namespace util diff --git a/util/proto_status_test.cc b/util/proto_status_test.cc index ea84e04..052caf2 100644 --- a/util/proto_status_test.cc +++ b/util/proto_status_test.cc @@ -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::Get(), PROVIDER_ID_MISMATCH, "provider_id_mismatch"); Status status2(ProtoEnumErrorSpace::Get(), PROVIDER_ID_MISMATCH, - "this is a provider_id_mismatch error"); + "provider_id_mismatch"); EXPECT_EQ(status1, status2); } +TEST(StatusTest, ErrorMessageMismatch) { + Status status1(ProtoEnumErrorSpace::Get(), PROVIDER_ID_MISMATCH, + "provider_id_mismatch"); + Status status2(ProtoEnumErrorSpace::Get(), PROVIDER_ID_MISMATCH, + "this is a provider_id_mismatch error"); + EXPECT_NE(status1, status2); +} + TEST(StatusTest, NotTheSameStatus) { Status status1(ProtoEnumErrorSpace::Get(), PROVIDER_ID_MISMATCH, "provider_id_mismatch"); diff --git a/util/status.cc b/util/status.cc deleted file mode 100644 index 2936549..0000000 --- a/util/status.cc +++ /dev/null @@ -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 - -#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 diff --git a/zlib.BUILD b/zlib.BUILD new file mode 100644 index 0000000..64834f5 --- /dev/null +++ b/zlib.BUILD @@ -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 = ["."], +)