diff --git a/common/certificate_type.h b/common/certificate_type.h new file mode 100644 index 0000000..b63a639 --- /dev/null +++ b/common/certificate_type.h @@ -0,0 +1,22 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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_CERTIFICATE_TYPE_H_ +#define COMMON_CERTIFICATE_TYPE_H_ + +namespace widevine { + +enum CertificateType { + kCertificateTypeTesting, + kCertificateTypeDevelopment, + kCertificateTypeProduction, +}; + +} // namespace widevine + +#endif // COMMON_CERTIFICATE_TYPE_H_ diff --git a/common/python/certificate_type.clif b/common/python/certificate_type.clif new file mode 100644 index 0000000..fc009e9 --- /dev/null +++ b/common/python/certificate_type.clif @@ -0,0 +1,11 @@ +################################################################################ +# 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. +################################################################################ + +from "common/certificate_type.h": + namespace `widevine`: + enum CertificateType diff --git a/example/example_data/certificate_list b/example/example_data/certificate_list new file mode 100644 index 0000000..985216d Binary files /dev/null and b/example/example_data/certificate_list differ diff --git a/example/example_data/device.private b/example/example_data/device.private new file mode 100644 index 0000000..6de1b83 Binary files /dev/null and b/example/example_data/device.private differ diff --git a/example/example_data/device.public b/example/example_data/device.public new file mode 100644 index 0000000..0b18ee6 Binary files /dev/null and b/example/example_data/device.public differ diff --git a/example/example_data/intermediate.cert b/example/example_data/intermediate.cert new file mode 100644 index 0000000..cec454e Binary files /dev/null and b/example/example_data/intermediate.cert differ diff --git a/example/example_data/intermediate.encrypted.private b/example/example_data/intermediate.encrypted.private new file mode 100644 index 0000000..316a1e3 Binary files /dev/null and b/example/example_data/intermediate.encrypted.private differ diff --git a/example/example_data/intermediate.passphrase b/example/example_data/intermediate.passphrase new file mode 100644 index 0000000..0a79238 --- /dev/null +++ b/example/example_data/intermediate.passphrase @@ -0,0 +1 @@ +intermediate_passphrase \ No newline at end of file diff --git a/example/example_data/intermediate.public b/example/example_data/intermediate.public new file mode 100644 index 0000000..52cd231 Binary files /dev/null and b/example/example_data/intermediate.public differ diff --git a/example/example_data/message b/example/example_data/message new file mode 100644 index 0000000..a9378c8 Binary files /dev/null and b/example/example_data/message differ diff --git a/example/example_data/provisioner.cert b/example/example_data/provisioner.cert new file mode 100644 index 0000000..aac9844 Binary files /dev/null and b/example/example_data/provisioner.cert differ diff --git a/example/example_data/provisioner.encrypted.private b/example/example_data/provisioner.encrypted.private new file mode 100644 index 0000000..221b139 Binary files /dev/null and b/example/example_data/provisioner.encrypted.private differ diff --git a/example/example_data/provisioner.passphrase b/example/example_data/provisioner.passphrase new file mode 100644 index 0000000..79ea782 --- /dev/null +++ b/example/example_data/provisioner.passphrase @@ -0,0 +1 @@ +provider_passphrase \ No newline at end of file diff --git a/example/example_data/provisioner.public b/example/example_data/provisioner.public new file mode 100644 index 0000000..8ef5475 Binary files /dev/null and b/example/example_data/provisioner.public differ diff --git a/example/example_data/provisioner.spoid_secret b/example/example_data/provisioner.spoid_secret new file mode 100644 index 0000000..7f3fe95 --- /dev/null +++ b/example/example_data/provisioner.spoid_secret @@ -0,0 +1,4 @@ +Twas bryllyg, and ye slythy toves +Did gyre and gymble in ye wabe: +All mimsy were ye borogoves; +And ye mome raths outgrabe. diff --git a/example/example_data/root.private b/example/example_data/root.private new file mode 100644 index 0000000..03b99d9 Binary files /dev/null and b/example/example_data/root.private differ diff --git a/example/example_data/service.cert b/example/example_data/service.cert new file mode 100644 index 0000000..916a1b1 Binary files /dev/null and b/example/example_data/service.cert differ diff --git a/example/example_data/service.encrypted.private b/example/example_data/service.encrypted.private new file mode 100644 index 0000000..23dccdd Binary files /dev/null and b/example/example_data/service.encrypted.private differ diff --git a/example/example_data/service.passphrase b/example/example_data/service.passphrase new file mode 100644 index 0000000..0cadfae --- /dev/null +++ b/example/example_data/service.passphrase @@ -0,0 +1 @@ +service_passphrase \ No newline at end of file diff --git a/example/example_data/service.public b/example/example_data/service.public new file mode 100644 index 0000000..435af4a Binary files /dev/null and b/example/example_data/service.public differ diff --git a/example/provisioning_example.cc b/example/provisioning_example.cc new file mode 100644 index 0000000..cd14e58 --- /dev/null +++ b/example/provisioning_example.cc @@ -0,0 +1,113 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2016 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 +#include +#include + +#include "common/certificate_type.h" +#include "provisioning_sdk/public/provisioning_engine.h" +#include "provisioning_sdk/public/provisioning_session.h" +#include "provisioning_sdk/public/provisioning_status.h" + +using widevine::kCertificateTypeTesting; +using widevine::OK; +using widevine::ProvisioningEngine; +using widevine::ProvisioningSession; + +std::string GetContents(const std::string& file_name) { + if (file_name.empty()) { + std::cout << "File name is empty." << std::endl; + return ""; + } + FILE* file = fopen(file_name.c_str(), "r"); + if (!file) { + std::cout << "Unable to open file " << file_name << std::endl; + return ""; + } + std::string contents; + const size_t kReadSize = 0x1000; + char buffer[kReadSize]; + while (true) { + size_t size_read = fread(buffer, sizeof(char), kReadSize, file); + if (size_read == 0) break; + contents.append(buffer, size_read); + } + if (!feof(file)) std::cout << "Failed to read all file contents."; + fclose(file); + return contents; +} + +int main(int argc, char** argv) { + ProvisioningEngine engine; + + // Call Initialize to setup the engine. + if (engine.Initialize( + kCertificateTypeTesting, GetContents("example_data/service.cert"), + GetContents("example_data/service.encrypted.private"), + GetContents("example_data/service.passphrase"), + GetContents("example_data/provisioner.cert"), + GetContents("example_data/provisioner.encrypted.private"), + GetContents("example_data/provisioner.passphrase"), + GetContents("example_data/provisioner.spoid_secret")) != OK) { + std::cout << "Failed to initialize." << std::endl; + return 1; + } + + // Certificate status list should be updated periodically. In this example, + // we'll just set it once. Note that in practice, the expiration should not be + // 10 years long. + if (engine.SetCertificateStatusList( + GetContents("example_data/certificate_list"), + 10 * 365 * 24 * 3600 /* 10 years */) != OK) { + std::cout << "Failed to set certificate status list." << std::endl; + return 1; + } + + // Before being able to process provisioning request for a specific type of + // device, we need to generate the intermediate certificate and add it to the + // engine first. This only needs to be done once for every new type of device. + const int kSystemId = 2001; + std::string certificate; + if (engine.GenerateDrmIntermediateCertificate( + kSystemId, GetContents("example_data/intermediate.public"), + &certificate) != OK) { + std::cout << "Failed to generate intermediate certificate." << std::endl; + return 1; + } + if (engine.AddDrmIntermediateCertificate( + certificate, + GetContents("example_data/intermediate.encrypted.private"), + GetContents("example_data/intermediate.passphrase")) != OK) { + std::cout << "Failed to add intermediate certificate." << std::endl; + return 1; + } + + // In order to process provisioning request, we need to create a session. The + // public/private key pairs should be unique - they cannot be reused if the + // message is processed successfully; if ProcessMessage fails, they can be + // reused on another session. + std::unique_ptr session; + if (engine.NewProvisioningSession( + GetContents("example_data/device.public"), + GetContents("example_data/device.private"), &session) != OK) { + std::cout << "Failed to create session." << std::endl; + return 1; + } + std::string response; + bool done; + if (session->ProcessMessage(GetContents("example_data/message"), &response, + &done) != OK) { + std::cout << "Failed to process message." << std::endl; + return 1; + } + std::cout << "Message processed successfully."; + return 0; +} diff --git a/example/provisioning_message_generator b/example/provisioning_message_generator new file mode 100755 index 0000000..89d10f2 Binary files /dev/null and b/example/provisioning_message_generator differ diff --git a/libprovisioning_sdk.so b/libprovisioning_sdk.so new file mode 100755 index 0000000..13f8288 Binary files /dev/null and b/libprovisioning_sdk.so differ diff --git a/protos/public/certificate_provisioning.proto b/protos/public/certificate_provisioning.proto new file mode 100644 index 0000000..20332e0 --- /dev/null +++ b/protos/public/certificate_provisioning.proto @@ -0,0 +1,188 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2016 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. +//////////////////////////////////////////////////////////////////////////////// + +// +// Description: +// Public protocol buffer definitions for Widevine Device Certificate +// Provisioning protocol. + +syntax = "proto2"; + +package widevine; + +import "protos/public/client_identification.proto"; +import "protos/public/hash_algorithm.proto"; +import "protos/public/remote_attestation.proto"; + +option java_package = "com.google.video.widevine.protos"; + + +// ProvisioningOptions specifies the type of certificate to specify and +// in the case of X509 certificates, the certificate authority to use. +message ProvisioningOptions { + enum CertificateType { + WIDEVINE_DRM = 0; // Default. The original certificate type. + X509 = 1; // X.509 certificate. + WIDEVINE_KEYBOX = 2; + } + + optional CertificateType certificate_type = 1 [default = WIDEVINE_DRM]; + + // Contains the application-specific name used to identify the certificate + // authority for signing the generated certificate. This is required iff the + // certificate type is X509. + optional string certificate_authority = 2; + // System ID for OTA keybox provisioning. Requires device secure boot. + optional uint32 system_id = 3; +} + +// Provisioning request sent by client devices to provisioning service. +message ProvisioningRequest { + message EncryptedSessionKeys { + message SessionKeys { + // 16 bytes encryption key generated by client, used by the server to: + // (1) AES-128-CBC decrypt encrypted_client_id in + // EncryptedClientIdentification which is in RemoteAttestation + // (2) AES-128-CBC encrypt device_key to be returned in + // ProvisioningResponse. + optional bytes encryption_key = 1; + // 32 bytes mac key generated by client, used by server to sign + // the ProvisioningResponse. + optional bytes mac_key = 2; + } + // Serial number of certificate which was used to encrypt the session keys. + // Required. + optional bytes certificate_serial_number = 1; + // Serialized, encrypted session keys. Required. + optional bytes encrypted_session_keys = 2; + } + oneof clear_or_encrypted_client_id { + // Device root of trust and other client identification. Required. + ClientIdentification client_id = 1; + EncryptedClientIdentification encrypted_client_id = 5; + } + // Nonce value used to prevent replay attacks. Required. + optional bytes nonce = 2; + // Options for type of certificate to generate. Optional. + optional ProvisioningOptions options = 3; + oneof spoid_param { + // Stable identifier, unique for each device + application (or origin). + // To be deprecated. + bytes stable_id = 4; + // Service provider ID from the service certificate's provider_id field. + // Preferred parameter. + bytes provider_id = 6; + // Client-generated stable per-origin identifier to be copied directly + // to the client certificate serial number. + bytes spoid = 7; + } + // SessionKeys encrypted using a service cert public key. + // Required for keybox provisioning. + optional EncryptedSessionKeys encrypted_session_keys = 8; +} + +// Provisioning response sent by the provisioning server to client devices. +// This message is used for both regular Widevine DRM certificates and for +// application-specific X.509 certificates. +message ProvisioningResponse { + message OtaKeybox { + // Iv used along with SessionKeys.encryption_key for encrypting device key. + optional bytes device_key_encryption_iv = 1; + // Device key component of the keybox, encrypted using the + // SessionKeys.encryption_key in the request and |device_key_encryption_iv| + // above. + optional bytes encrypted_device_key = 2; + // Device CA token component of the keybox. + optional bytes device_ca_token = 3; + } + // AES-128 encrypted device private RSA key. PKCS#1 ASN.1 DER-encoded. + // Required. For X.509 certificates, the private RSA key may also include + // a prefix as specified by private_key_prefix in the X509CertificateMetadata + // proto message. + optional bytes device_rsa_key = 1; + // Initialization vector used to encrypt device_rsa_key. Required. + optional bytes device_rsa_key_iv = 2; + // For Widevine DRM certificates, this contains the serialized + // SignedDrmCertificate. For X.509 certificates, this contains the PEM + // encoded X.509 certificate. Required. + optional bytes device_certificate = 3; + // Nonce value matching nonce in ProvisioningRequest. Required. + optional bytes nonce = 4; + // Key used to wrap device_rsa_key when DRM provisioning an OEM factory + // provisioned device. Encrypted with the device OEM public key using + // RSA-OAEP. + optional bytes wrapping_key = 5; + // Only populated in OTA keybox provisioning response. + optional OtaKeybox ota_keybox = 6; +} + +// Protocol-specific context data used to hold the state of the server in +// stateful provisioning protocols. For more information, please refer to +// mE_ZP4WmSX-JNldg +message ProvisioningContext { + // Serialized ProvisioningContextKeyData. Required. + optional bytes key_data = 1; + // Protocol-dependent context data, encrypted with key and IV in key_data. + // Required. + optional bytes context_data = 2; +} + +message SignedProvisioningContext { + // ProvisioningContext in bytes. + optional bytes provisioning_context = 1; + // RSASSA-PSS signature of provisioning_context. Signed with service private + // key. + optional bytes signature = 2; + // Optional field that indicates the hash algorithm used in signature scheme. + optional HashAlgorithmProto hash_algorithm = 3; +} + +// Cryptographic tokens to be used for ProvisioningContext. +message ProvisioningContextKeyData { + // Encryption key, usually 32 bytes used for AES-256-CBC. Required. + optional bytes encryption_key = 1; + // Encryption IV, 16 bytes. Required. + optional bytes encryption_iv = 2; +} + +// Serialized ProvisioningRequest or ProvisioningResponse signed with +// The message authentication key. +message SignedProvisioningMessage { + enum ProtocolVersion { + SERVICE_CERTIFICATE_REQUEST = 1; // Service certificate request. + PROVISIONING_20 = 2; // Keybox factory-provisioned devices. + PROVISIONING_30 = 3; // OEM certificate factory-provisioned devices. + } + + // Serialized protobuf message for the corresponding protocol and stage of + // the provisioning exchange. ProvisioningRequest or ProvisioningResponse + // in the case of Provisioning 2.0, 3.0 and ARCPP_PROVISIONING. Required. + optional bytes message = 1; + // HMAC-SHA256 (Keybox) or RSASSA-PSS (OEM) signature of message. Required + // for provisioning 2.0 and 3.0. For ARCPP_PROVISIONING, only used in + // response. + optional bytes signature = 2; + // Version number of provisioning protocol. + optional ProtocolVersion protocol_version = 3 [default = PROVISIONING_20]; + // Protocol-specific context / state information for multiple-exchange, + // stateful provisioing protocols. Optional. + optional SignedProvisioningContext signed_provisioning_context = 4; + // Remote attestation data to authenticate that the ChromeOS client device + // is operating in verified mode. Remote attestation challenge data is + // |message| field above. Required for ARCPP_PROVISIONING request. + // It contains signature of |message|. + optional RemoteAttestation remote_attestation = 5; + // The core message is the simple serialization of fields used by OEMCrypto. + // This field was introduced in OEMCrypto API v16. The core message format is + // documented in the "Widevine Core Message Serialization", found internally + // at + // https://docs.google.com/document/d/1M5f0OA8zrIFufpZiny_M9WkvJkCUs9DpRpeDmk9QKKY/edit + optional bytes oemcrypto_core_message = 6; + // Optional field that indicates the hash algorithm used in signature scheme. + optional HashAlgorithmProto hash_algorithm = 7; +} diff --git a/protos/public/client_identification.proto b/protos/public/client_identification.proto new file mode 100644 index 0000000..0454f8f --- /dev/null +++ b/protos/public/client_identification.proto @@ -0,0 +1,131 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2016 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. +//////////////////////////////////////////////////////////////////////////////// + +// +// Description: +// ClientIdentification messages used by provisioning and license protocols. + +syntax = "proto2"; + +package widevine; + +option java_package = "com.google.video.widevine.protos"; +option java_outer_classname = "ClientIdentificationProtos"; + + +// ClientIdentification message used to authenticate the client device. +message ClientIdentification { + enum TokenType { + KEYBOX = 0; + DRM_DEVICE_CERTIFICATE = 1; + REMOTE_ATTESTATION_CERTIFICATE = 2; + OEM_DEVICE_CERTIFICATE = 3; + } + + message NameValue { + optional string name = 1; + optional string value = 2; + } + + // Capabilities which not all clients may support. Used for the license + // exchange protocol only. + message ClientCapabilities { + enum HdcpVersion { + HDCP_NONE = 0; + HDCP_V1 = 1; + HDCP_V2 = 2; + HDCP_V2_1 = 3; + HDCP_V2_2 = 4; + HDCP_V2_3 = 5; + HDCP_NO_DIGITAL_OUTPUT = 0xff; + } + + enum CertificateKeyType { + RSA_2048 = 0; + RSA_3072 = 1; + ECC_SECP256R1 = 2; + ECC_SECP384R1 = 3; + ECC_SECP521R1 = 4; + } + + enum AnalogOutputCapabilities { + ANALOG_OUTPUT_UNKNOWN = 0; + ANALOG_OUTPUT_NONE = 1; + ANALOG_OUTPUT_SUPPORTED = 2; + ANALOG_OUTPUT_SUPPORTS_CGMS_A = 3; + } + + optional bool client_token = 1 [default = false]; + optional bool session_token = 2 [default = false]; + optional bool video_resolution_constraints = 3 [default = false]; + optional HdcpVersion max_hdcp_version = 4 [default = HDCP_NONE]; + optional uint32 oem_crypto_api_version = 5; + // Client has hardware support for protecting the usage table, such as + // storing the generation number in secure memory. For Details, see: + // https://docs.google.com/document/d/1Mm8oB51SYAgry62mEuh_2OEkabikBiS61kN7HsDnh9Y/edit#heading=h.xgjl2srtytjt + optional bool anti_rollback_usage_table = 6 [default = false]; + // The client shall report |srm_version| if available. + optional uint32 srm_version = 7; + // A device may have SRM data, and report a version, but may not be capable + // of updating SRM data. + optional bool can_update_srm = 8 [default = false]; + repeated CertificateKeyType supported_certificate_key_type = 9; + optional AnalogOutputCapabilities analog_output_capabilities = 10 + [default = ANALOG_OUTPUT_UNKNOWN]; + optional bool can_disable_analog_output = 11 [default = false]; + // Clients can indicate a performance level supported by OEMCrypto. + // This will allow applications and providers to choose an appropriate + // quality of content to serve. Currently defined tiers are + // 1 (low), 2 (medium) and 3 (high). Any other value indicate that + // the resource rating is unavailable or reporting erroneous values + // for that device. For details see, + // https://docs.google.com/document/d/1wodSYK-Unj3AgTSXqujWuBCAFC00qF85G1AhfLtqdko + optional uint32 resource_rating_tier = 12 [default = 0]; + } + + message ClientCredentials { + optional TokenType type = 1 [default = KEYBOX]; + optional bytes token = 2; + } + + // Type of factory-provisioned device root of trust. Optional. + optional TokenType type = 1 [default = KEYBOX]; + // Factory-provisioned device root of trust. Required. + optional bytes token = 2; + // Optional client information name/value pairs. + repeated NameValue client_info = 3; + // Client token generated by the content provider. Optional. + optional bytes provider_client_token = 4; + // Number of licenses received by the client to which the token above belongs. + // Only present if client_token is specified. + optional uint32 license_counter = 5; + // List of non-baseline client capabilities. + optional ClientCapabilities client_capabilities = 6; + // Serialized VmpData message. Optional. + optional bytes vmp_data = 7; + // Optional field that may contain additional provisioning credentials. + optional ClientCredentials device_credentials = 8; +} + +// EncryptedClientIdentification message used to hold ClientIdentification +// messages encrypted for privacy purposes. +message EncryptedClientIdentification { + // Provider ID for which the ClientIdentifcation is encrypted (owner of + // service certificate). + optional string provider_id = 1; + // Serial number for the service certificate for which ClientIdentification is + // encrypted. + optional bytes service_certificate_serial_number = 2; + // Serialized ClientIdentification message, encrypted with the privacy key + // using AES-128-CBC with PKCS#5 padding. + optional bytes encrypted_client_id = 3; + // Initialization vector needed to decrypt encrypted_client_id. + optional bytes encrypted_client_id_iv = 4; + // AES-128 privacy key, encrypted with the service public key using RSA-OAEP. + optional bytes encrypted_privacy_key = 5; +} diff --git a/protos/public/device_common.proto b/protos/public/device_common.proto new file mode 100644 index 0000000..f3c5676 --- /dev/null +++ b/protos/public/device_common.proto @@ -0,0 +1,172 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2020 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// This file contains device-related definitions that are common to both the +// legacy device management service and the new devices service. Eventually, +// we may merge the contents of this file into other files. + +syntax = "proto3"; + +package widevine; + +option java_package = "com.google.video.widevine.protos"; + + +// Allows additional make/models to be associated with a system_id. +message DeviceModel { + // ModelStatus is used to specify how confident we are that this + // make/model/year combination is allowed by the device manufacturer. + // VERIFIED indicates that the manufacturer confirmed that it is correct. + // UNVERIFIED means that we have sufficient data to believe it is correct, + // but the manufacturer has not confirmed. + // UNKNOWN indicates that we do not have sufficient information to indicate + // whether or not the device is allowed by the manufacturer. + // REJECTED indicates that the manufacturer explicitly disallowed the use + // of the make/model/year combination. + enum ModelStatus { + MODEL_STATUS_UNSPECIFIED = 0; + MODEL_STATUS_VERIFIED = 1; + MODEL_STATUS_UNVERIFIED = 2; + MODEL_STATUS_UNKNOWN = 4; + MODEL_STATUS_REJECTED = 3; + } + // Represents the device manufacturer. Typically, this will be Philips, LG, + // Sharp, etc. + string manufacturer = 1; + // Model of the device. + string model_name = 2; + // The expected release year of the make/model combination. Optional. + uint32 model_year = 3; + // The model status of this make and model. + ModelStatus status = 4; +} + +// DeviceState defines the current state of the device. It is used in +// licensing to determine if a (classic or MDRM/CENC) license should be +// issued. The status affects if and how a device record is shown in +// keysmith's CertificateStatusList. +// +// States: +// DEVICE_STATE_UNKNOWN: This should not be used. +// It only indicates that a state has not been set. +// IN_TESTING: The first valid state of a device record. A newly created +// device should be in this state until the device is considered +// "released". In this state a device should only be supported on test +// services (e.g. UAT license service). +// PRE_RELEASE: The state of a device when it's ready to be used with +// production services. In this state a device can receive production +// classic and MDRM/CENC licenses. The device will also be listed in +// keysmith's certificate status list. The device data will be +// available for sharing with internal partners only. +// RELEASED: Indicates that the device is available on the store shelves. +// The device data will be available for sharing with external partners. +// DELETED: Indicates that the device was manually disabled and should +// not be used for any test or production services. The device should +// not appear in the device certificate status list. Customers will +// not be able to see or utilize this state when managing their devices. +// TEST_ONLY: Indicates that this device was never intended for production +// but can be used for test purposes. The device will be listed in the +// certificate status list as a test device. +// REVOKED: Indicates that the device was revoked. No test or production +// service should honor requests (classic nor MDRM/CENC) from one of +// these devices. The device serial number and its REVOKED status will +// appear in keysmith's certificate status list. +// +// Devices in the above states have the following behaviors in widevince +// services: +// +// Licensing | Certificate | Cert | Cert | Test | +// State Prod | UAT | Provisioning | Listed | status | device | redact +// -- -- -- -- -- -- -- -- +// IN_TESTING No Yes Yes Yes VALID true yes +// TEST_ONLY No Yes Yes Yes VALID true no +// PRE_RELEASE Yes Yes Yes Yes VALID false yes +// RELEASED Yes Yes Yes Yes VALID false no +// REVOKED No No No Yes REVOKED false no +// DELETED No No No No n/a n/a n/a +enum DeviceState { + DEVICE_STATE_UNKNOWN = 0; + IN_TESTING = 1; + RELEASED = 2; + DELETED = 3; + TEST_ONLY = 4; + REVOKED = 5; + PRE_RELEASE = 6; +} + +// Specifies the device type, or form factor of a device. +enum DeviceType { + DEVICE_TYPE_UNSPECIFIED = 0; + DEVICE_TYPE_PHONE = 1; + DEVICE_TYPE_TV = 2; + DEVICE_TYPE_TABLET = 3; + DEVICE_TYPE_GAMING_CONSOLE = 4; + DEVICE_TYPE_SET_TOP_BOX = 5; + DEVICE_TYPE_VIDEO_DONGLE = 6; + DEVICE_TYPE_PC = 7; + DEVICE_TYPE_AUTO = 8; + DEVICE_TYPE_WEARABLE = 9; + DEVICE_TYPE_CONNECTED_AUDIO_DEVICE = 10; + DEVICE_TYPE_SMART_DISPLAY = 11; + // Legacy identifier for records that were created for SoC integration. + DEVICE_TYPE_SOC = 12; +} + +// Specifies the platform and OS of the device. +enum Platform { + PLATFORM_UNSPECIFIED = 0; + PLATFORM_CHROMECAST = 1; + PLATFORM_FUCHSIA = 2; + PLATFORM_IOS = 3; + PLATFORM_IPAD_OS = 4; + PLATFORM_TV_OS = 5; + PLATFORM_ANDROID = 6; + PLATFORM_WINDOWS = 7; + PLATFORM_CHROME_OS = 8; + PLATFORM_MAC_OS = 9; + PLATFORM_LINUX = 10; + PLATFORM_WEB_OS = 11; + PLATFORM_TIZEN = 12; + PLATFORM_FIRE_OS = 13; + PLATFORM_ROKU = 14; + PLATFORM_PLAYSTATION = 15; + PLATFORM_XBOX = 16; + PLATFORM_KAIOS = 17; + PLATFORM_RDK = 18; + PLATFORM_OTHER = 19; +} + +// This is used for tri-state answers. Yes-TEE, Yes-REE, No. +// This has to be in device_common to avoid import conflicts between security +// profiles and device security profiles. +enum OsOptionalSupport { + OS_OPTIONAL_SUPPORT_UNSPECIFIED = 0; + YES_TEE = 1; + YES_REE = 2; + NO_SUPPORT = 3; +} + +// Version of High-bandwidth Digital Content Protection (HDCP). +// This has to be in device_common to avoid import conflicts between security +// profiles and device security profiles. +enum HdcpVersion { + HDCP_VERSION_UNSPECIFIED = 0; + HDCP_V1 = 1; + HDCP_V2 = 2; + HDCP_V2_1 = 3; + HDCP_V2_2 = 4; + HDCP_V2_3 = 5; +} + +// Widevine device security level. +enum DeviceSecurityLevel { + SECURITY_LEVEL_UNSPECIFIED = 0; + LEVEL_1 = 1; + LEVEL_2 = 2; + LEVEL_3 = 3; +} diff --git a/protos/public/external_license.proto b/protos/public/external_license.proto new file mode 100644 index 0000000..6fe74b3 --- /dev/null +++ b/protos/public/external_license.proto @@ -0,0 +1,81 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 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. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Definitions of the protocol buffer messages used in the Widevine license +// exchange protocol to support DRM systems native to a device other than +// Widevine. + +syntax = "proto2"; + +package widevine; + +import "protos/public/client_identification.proto"; +import "protos/public/license_protocol.proto"; + +option java_package = "com.google.video.widevine.externallicense"; +option java_multiple_files = true; + + +enum ExternalLicenseType { + EXTERNAL_LICENSE_TYPE_UNDEFINED = 0; + PLAYREADY_LICENSE_NEW = 1; + PLAYREADY_LICENSE_RENEWAL = 2; + PLAYREADY_LICENSE_RELEASE = 3; +} + +message EncryptedLicenseRequest { + // Provider ID for which the license request is encrypted (owner of + // service certificate). + optional string provider_id = 1; + // Serial number for the service certificate for which license_request is + // encrypted. + optional bytes service_certificate_serial_number = 2; + // Serialized license request message, encrypted with the privacy key + // using AES-128-CBC with PKCS#5 padding. + optional bytes encrypted_license_request = 3; + // Initialization vector needed to decrypt encrypted_license_request. + optional bytes encrypted_license_request_iv = 4; + // AES-128 privacy key, encrypted with the service public key using RSA-OAEP. + optional bytes encrypted_privacy_key = 5; +} + +message ExternalLicenseRequest { + optional ExternalLicenseType request_type = 1; + // The license request. + oneof clear_or_encrypted_request { + // License request from the client. + bytes request = 2; + // Encrypted request from the client. + EncryptedLicenseRequest encrypted_request = 7; + } + oneof clear_or_encrypted_client_id { + // Information about the device. + ClientIdentification client_id = 3; + // Encrypted ClientIdentification message, used for privacy purposes. + EncryptedClientIdentification encrypted_client_id = 4; + } + // Information about the content, including the PSSH data. + optional LicenseRequest.ContentIdentification content_id = 5; + // Time of the request in seconds (UTC) as set by the client. + optional int64 request_time = 6; +} + +message ExternalLicense { + optional ExternalLicenseType license_type = 1; + // The license representing the license type. + optional bytes license = 2; + // Widevine specific policy for renewals and expiry. + optional License.Policy policy = 3; + // Time of the request in seconds (UTC) as set in + // LicenseRequest.request_time. If this time is not set in the request, + // the local time at the license service is used in this field. + optional int64 license_start_time = 4; + // List of key Identifiers associated with this license. + repeated bytes key_id = 5; +} diff --git a/protos/public/hash_algorithm.proto b/protos/public/hash_algorithm.proto new file mode 100644 index 0000000..bcb0751 --- /dev/null +++ b/protos/public/hash_algorithm.proto @@ -0,0 +1,20 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2020 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. +//////////////////////////////////////////////////////////////////////////////// + +syntax = "proto3"; + +package widevine; + +// LINT.IfChange +enum HashAlgorithmProto { + // Unspecified hash algorithm: SHA_256 shall be used for ECC based algorithms + // and SHA_1 shall be used otherwise. + HASH_ALGORITHM_UNSPECIFIED = 0; + HASH_ALGORITHM_SHA_1 = 1; + HASH_ALGORITHM_SHA_256 = 2; +} diff --git a/protos/public/license_protocol.proto b/protos/public/license_protocol.proto new file mode 100644 index 0000000..388adaf --- /dev/null +++ b/protos/public/license_protocol.proto @@ -0,0 +1,522 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2016 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. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Definitions of the protocol buffer messages used in the Widevine license +// exchange protocol, described in Widevine license exchange protocol document + +syntax = "proto2"; + +package widevine; + +import "protos/public/client_identification.proto"; +import "protos/public/hash_algorithm.proto"; +import "protos/public/remote_attestation.proto"; + +option java_package = "com.google.video.widevine.protos"; + + +// option optimize_for = LITE_RUNTIME; +enum LicenseType { + STREAMING = 1; + OFFLINE = 2; + // License type decision is left to provider. + AUTOMATIC = 3; +} + +enum PlatformVerificationStatus { + // The platform is not verified. + PLATFORM_UNVERIFIED = 0; + // Tampering detected on the platform. + PLATFORM_TAMPERED = 1; + // The platform has been verified by means of software. + PLATFORM_SOFTWARE_VERIFIED = 2; + // The platform has been verified by means of hardware (e.g. secure boot). + PLATFORM_HARDWARE_VERIFIED = 3; + // Platform verification was not performed. + PLATFORM_NO_VERIFICATION = 4; + // Platform and secure storage capability have been verified by means of + // software. + PLATFORM_SECURE_STORAGE_SOFTWARE_VERIFIED = 5; +} + +// LicenseIdentification is propagated from LicenseRequest to License, +// incrementing version with each iteration. +message LicenseIdentification { + optional bytes request_id = 1; + optional bytes session_id = 2; + optional bytes purchase_id = 3; + optional LicenseType type = 4; + optional int32 version = 5; + optional bytes provider_session_token = 6; +} + +// This message is used to indicate the license cateogry spec for a license as +// a part of initial license issuance. +// LINT.IfChange +message LicenseCategorySpec { + // Possible license categories. + enum LicenseCategory { + // By default, License is used for single content. + SINGLE_CONTENT_LICENSE_DEFAULT = 0; + // License is used for multiple contents (could be a combination of + // single contents and groups of contents). + MULTI_CONTENT_LICENSE = 1; + // License is used for contents logically grouped. + GROUP_LICENSE = 2; + } + // Optional. License category indicates if license is used for single + // content, multiple contents (could be a combination of + // single contents and groups of contents) or a group of contents. + optional LicenseCategory license_category = 1; + // Optional. Content or group ID covered by the license. + oneof content_or_group_id { + // Content_id would be present if it is a license for single content. + bytes content_id = 2; + // Group_id would be present if the license is a multi_content_license or + // group_license. Group Id could be the name of a group of contents, + // defined by licensor. + bytes group_id = 3; + } +} + +message License { + // LINT.IfChange + message Policy { + // Indicates that playback of the content is allowed. + optional bool can_play = 1 [default = false]; + + // Indicates that the license may be persisted to non-volatile + // storage for offline use. + optional bool can_persist = 2 [default = false]; + + // Indicates that renewal of this license is allowed. + optional bool can_renew = 3 [default = false]; + + // For the |*duration*| fields, playback must halt when + // license_start_time (seconds since the epoch (UTC)) + + // license_duration_seconds is exceeded. A value of 0 + // indicates that there is no limit to the duration. + + // Indicates the rental window. + optional int64 rental_duration_seconds = 4 [default = 0]; + + // Indicates the viewing window, once playback has begun. + optional int64 playback_duration_seconds = 5 [default = 0]; + + // Indicates the time window for this specific license. + optional int64 license_duration_seconds = 6 [default = 0]; + + // The |renewal*| fields only apply if |can_renew| is true. + + // The window of time, in which playback is allowed to continue while + // renewal is attempted, yet unsuccessful due to backend problems with + // the license server. + optional int64 renewal_recovery_duration_seconds = 7 [default = 0]; + + // All renewal requests for this license shall be directed to the + // specified URL. + optional string renewal_server_url = 8; + + // How many seconds after license_start_time, before renewal is first + // attempted. + optional int64 renewal_delay_seconds = 9 [default = 0]; + + // Specifies the delay in seconds between subsequent license + // renewal requests, in case of failure. + optional int64 renewal_retry_interval_seconds = 10 [default = 0]; + + // Indicates that the license shall be sent for renewal when usage is + // started. + optional bool renew_with_usage = 11 [default = false]; + + // Indicates to client that license renewal and release requests ought to + // include ClientIdentification (client_id). + optional bool always_include_client_id = 12 [default = false]; + + // Duration of grace period before playback_duration_seconds (short window) + // goes into effect. Optional. + optional int64 play_start_grace_period_seconds = 13 [default = 0]; + + // Enables "soft enforcement" of playback_duration_seconds, letting the user + // finish playback even if short window expires. Optional. + optional bool soft_enforce_playback_duration = 14 [default = false]; + + // Enables "soft enforcement" of rental_duration_seconds. Initial playback + // must always start before rental duration expires. In order to allow + // subsequent playbacks to start after the rental duration expires, + // soft_enforce_playback_duration must be true. Otherwise, subsequent + // playbacks will not be allowed once rental duration expires. Optional. + optional bool soft_enforce_rental_duration = 15 [default = true]; + } + + message KeyContainer { + enum KeyType { + SIGNING = 1; // No more than one signing key may appear. + CONTENT = 2; // Content key. + KEY_CONTROL = 3; // Key control block for license renewals. No key. + OPERATOR_SESSION = 4; // wrapped keys for auxiliary crypto operations. + ENTITLEMENT = 5; // Entitlement keys. + OEM_CONTENT = 6; // Partner-specific content key. + } + + // The SecurityLevel enumeration allows the server to communicate the level + // of robustness required by the client, in order to use the key. + enum SecurityLevel { + // Software-based whitebox crypto is required. + SW_SECURE_CRYPTO = 1; + + // Software crypto and an obfuscated decoder is required. + SW_SECURE_DECODE = 2; + + // The key material and crypto operations must be performed within a + // hardware backed trusted execution environment. + HW_SECURE_CRYPTO = 3; + + // The crypto and decoding of content must be performed within a hardware + // backed trusted execution environment. + HW_SECURE_DECODE = 4; + + // The crypto, decoding and all handling of the media (compressed and + // uncompressed) must be handled within a hardware backed trusted + // execution environment. + HW_SECURE_ALL = 5; + } + + message KeyControl { + // If present, the key control must be communicated to the secure + // environment prior to any usage. This message is automatically generated + // by the Widevine License Server SDK. + optional bytes key_control_block = 1; + optional bytes iv = 2; + } + + message OutputProtection { + // Indicates whether HDCP is required on digital outputs, and which + // version should be used. + enum HDCP { + HDCP_NONE = 0; + HDCP_V1 = 1; + HDCP_V2 = 2; + HDCP_V2_1 = 3; + HDCP_V2_2 = 4; + HDCP_V2_3 = 5; + HDCP_NO_DIGITAL_OUTPUT = 0xff; + } + optional HDCP hdcp = 1 [default = HDCP_NONE]; + + // Indicate the CGMS setting to be inserted on analog output. + enum CGMS { + CGMS_NONE = 42; + COPY_FREE = 0; + COPY_ONCE = 2; + COPY_NEVER = 3; + } + optional CGMS cgms_flags = 2 [default = CGMS_NONE]; + + enum HdcpSrmRule { + HDCP_SRM_RULE_NONE = 0; + // In 'required_protection', this means most current SRM is required. + // Update the SRM on the device. If update cannot happen, + // do not allow the key. + // In 'requested_protection', this means most current SRM is requested. + // Update the SRM on the device. If update cannot happen, + // allow use of the key anyway. + CURRENT_SRM = 1; + } + optional HdcpSrmRule hdcp_srm_rule = 3 [default = HDCP_SRM_RULE_NONE]; + // Optional requirement to indicate analog output is not allowed. + optional bool disable_analog_output = 4 [default = false]; + // Optional requirement to indicate digital output is not allowed. + optional bool disable_digital_output = 5 [default = false]; + // Optional. If set, it indicates digital video recording (DVR) is + // allowed. + optional bool allow_record = 6 [default = false]; + } + + message VideoResolutionConstraint { + // Minimum and maximum video resolutions in the range (height x width). + optional uint32 min_resolution_pixels = 1; + optional uint32 max_resolution_pixels = 2; + // Optional output protection requirements for this range. If not + // specified, the OutputProtection in the KeyContainer applies. + optional OutputProtection required_protection = 3; + } + + message OperatorSessionKeyPermissions { + // Permissions/key usage flags for operator service keys + // (type = OPERATOR_SESSION). + optional bool allow_encrypt = 1 [default = false]; + optional bool allow_decrypt = 2 [default = false]; + optional bool allow_sign = 3 [default = false]; + optional bool allow_signature_verify = 4 [default = false]; + } + + // KeyCategorySpec message is used to identify if current key is generated + // for a single content or a group of contents. Currently it is only used in + // CAS request. + message KeyCategorySpec { + // Represents what kind of content a key is used for. + enum KeyCategory { + // By default, key is created for single content. + SINGLE_CONTENT_KEY_DEFAULT = 0; + // Key is created for a group of contents. + GROUP_KEY = 1; + } + // Indicate if the current key is created for single content or for group + // use. + optional KeyCategory key_category = 1; + // Id for key category. If it is a key for single content, this id + // represents the content_id. Otherwise, it represents a group_id. + oneof content_or_group_id { + bytes content_id = 2; + bytes group_id = 3; + } + } + + optional bytes id = 1; + optional bytes iv = 2; + optional bytes key = 3; + optional KeyType type = 4; + optional SecurityLevel level = 5 [default = SW_SECURE_CRYPTO]; + optional OutputProtection required_protection = 6; + // NOTE: Use of requested_protection is not recommended as it is only + // supported on a small number of platforms. + optional OutputProtection requested_protection = 7; + optional KeyControl key_control = 8; + optional OperatorSessionKeyPermissions operator_session_key_permissions = 9; + // Optional video resolution constraints. If the video resolution of the + // content being decrypted/decoded falls within one of the specified ranges, + // the optional required_protections may be applied. Otherwise an error will + // be reported. + // NOTE: Use of this feature is not recommended, as it is only supported on + // a small number of platforms. + repeated VideoResolutionConstraint video_resolution_constraints = 10; + // Optional flag to indicate the key must only be used if the client + // supports anti rollback of the user table. Content provider can query the + // client capabilities to determine if the client support this feature. + optional bool anti_rollback_usage_table = 11 [default = false]; + // Optional not limited to commonly known track types such as SD, HD. + // It can be some provider defined label to identify the track. + optional string track_label = 12; + // Optional. It is used to identify if current key is generated for a + // single content or a group of contents. Currently it is only used in CAS + // request. + optional KeyCategorySpec key_category_spec = 13; + } + + optional LicenseIdentification id = 1; + optional Policy policy = 2; + repeated KeyContainer key = 3; + // Time of the request in seconds (UTC) as set in + // LicenseRequest.request_time. If this time is not set in the request, + // the local time at the license service is used in this field. + optional int64 license_start_time = 4; + // TODO(b/65054419): Deprecate remote_attestation_verified in favor of + // platform_verification_status, below. + optional bool remote_attestation_verified = 5 [default = false]; + // Client token generated by the content provider. Optional. + optional bytes provider_client_token = 6; + // 4cc code specifying the CENC protection scheme as defined in the CENC 3.0 + // specification. Propagated from Widevine PSSH box. Optional. + optional uint32 protection_scheme = 7; + // 8 byte verification field "HDCPDATA" followed by unsigned 32 bit minimum + // HDCP SRM version (whether the version is for HDCP1 SRM or HDCP2 SRM + // depends on client max_hdcp_version). + optional bytes srm_requirement = 8; + // If present this contains a signed SRM file (either HDCP1 SRM or HDCP2 SRM + // depending on client max_hdcp_version) that should be installed on the + // client device. + optional bytes srm_update = 9; + // Indicates the status of any type of platform verification performed by the + // server. + optional PlatformVerificationStatus platform_verification_status = 10 + [default = PLATFORM_NO_VERIFICATION]; + // IDs of the groups for which keys are delivered in this license, if any. + repeated bytes group_ids = 11; + // Optional. LicenseCategorySpec is used to indicate the license cateogry for + // a license. It could be used as a part of initial license issuance or shown + // as a part of license in license response. + optional LicenseCategorySpec license_category_spec = 12; +} + +enum ProtocolVersion { + VERSION_2_0 = 20; + VERSION_2_1 = 21; + VERSION_2_2 = 22; +} + +message LicenseRequest { + message ContentIdentification { + message WidevinePsshData { + repeated bytes pssh_data = 1; + optional LicenseType license_type = 2; + optional bytes request_id = 3; // Opaque, client-specified. + } + + message WebmKeyId { + optional bytes header = 1; + optional LicenseType license_type = 2; + optional bytes request_id = 3; // Opaque, client-specified. + } + + message ExistingLicense { + optional LicenseIdentification license_id = 1; + optional int64 seconds_since_started = 2; + optional int64 seconds_since_last_played = 3; + optional bytes session_usage_table_entry = 4; + } + + message InitData { + enum InitDataType { + CENC = 1; + WEBM = 2; + } + + optional InitDataType init_data_type = 1 [default = CENC]; + optional bytes init_data = 2; + optional LicenseType license_type = 3; + optional bytes request_id = 4; + } + + oneof content_id_variant { + // Exactly one of these must be present. + WidevinePsshData widevine_pssh_data = 1; + WebmKeyId webm_key_id = 2; + ExistingLicense existing_license = 3; + InitData init_data = 4; + } + } + + enum RequestType { + NEW = 1; + RENEWAL = 2; + RELEASE = 3; + } + + // The client_id provides information authenticating the calling device. It + // contains the Widevine keybox token that was installed on the device at the + // factory. This field or encrypted_client_id below is required for a valid + // license request, but both should never be present in the same request. + optional ClientIdentification client_id = 1; + optional ContentIdentification content_id = 2; + optional RequestType type = 3; + // Time of the request in seconds (UTC) as set by the client. + optional int64 request_time = 4; + // Old-style decimal-encoded string key control nonce. + optional bytes key_control_nonce_deprecated = 5; + optional ProtocolVersion protocol_version = 6 [default = VERSION_2_0]; + // New-style uint32 key control nonce, please use instead of + // key_control_nonce_deprecated. + optional uint32 key_control_nonce = 7; + // Encrypted ClientIdentification message, used for privacy purposes. + optional EncryptedClientIdentification encrypted_client_id = 8; +} + +message LicenseError { + enum Error { + // The device credentials are invalid. The device must re-provision. + INVALID_DRM_DEVICE_CERTIFICATE = 1; + // The device credentials have been revoked. Re-provisioning is not + // possible. + REVOKED_DRM_DEVICE_CERTIFICATE = 2; + // The service is currently unavailable due to the backend being down + // or similar circumstances. + SERVICE_UNAVAILABLE = 3; + } + optional Error error_code = 1; +} + +message MetricData { + enum MetricType { + // The time spent in the 'stage', specified in microseconds. + LATENCY = 1; + // The UNIX epoch timestamp at which the 'stage' was first accessed in + // microseconds. + TIMESTAMP = 2; + } + + message TypeValue { + optional MetricType type = 1; + // The value associated with 'type'. For example if type == LATENCY, the + // value would be the time in microseconds spent in this 'stage'. + optional int64 value = 2 [default = 0]; + } + + // 'stage' that is currently processing the SignedMessage. Required. + optional string stage_name = 1; + // metric and associated value. + repeated TypeValue metric_data = 2; +} + +message VersionInfo { + // License SDK version reported by the Widevine License SDK. This field + // is populated automatically by the SDK. + optional string license_sdk_version = 1; + // Version of the service hosting the license SDK. This field is optional. + // It may be provided by the hosting service. + optional string license_service_version = 2; +} + +message SignedMessage { + enum MessageType { + LICENSE_REQUEST = 1; + LICENSE = 2; + ERROR_RESPONSE = 3; + SERVICE_CERTIFICATE_REQUEST = 4; + SERVICE_CERTIFICATE = 5; + SUB_LICENSE = 6; + CAS_LICENSE_REQUEST = 7; + CAS_LICENSE = 8; + EXTERNAL_LICENSE_REQUEST = 9; + EXTERNAL_LICENSE = 10; + } + + enum SessionKeyType { + UNDEFINED = 0; + WRAPPED_AES_KEY = 1; + EPHEMERAL_ECC_PUBLIC_KEY = 2; + } + + optional MessageType type = 1; + optional bytes msg = 2; + // Required field that contains the signature of the bytes of msg. + // For license requests, the signing algorithm is determined by the + // certificate contained in the request. + // For license responses, the signing algorithm is HMAC with signing key based + // on |session_key|. + optional bytes signature = 3; + // If populated, the contents of this field will be signaled by the + // |session_key_type| type. If the |session_key_type| is WRAPPED_AES_KEY the + // key is the bytes of an encrypted AES key. If the |session_key_type| is + // EPHERMERAL_ECC_PUBLIC_KEY the field contains the bytes of an RFC5208 ASN1 + // serialized ECC public key. + optional bytes session_key = 4; + // Remote attestation data which will be present in the initial license + // request for ChromeOS client devices operating in verified mode. Remote + // attestation challenge data is |msg| field above. Optional. + optional RemoteAttestation remote_attestation = 5; + + repeated MetricData metric_data = 6; + // Version information from the SDK and license service. This information is + // provided in the license response. + optional VersionInfo service_version_info = 7; + // Optional field that contains the algorithm type used to generate the + // session_key and signature in a LICENSE message. + optional SessionKeyType session_key_type = 8 [default = WRAPPED_AES_KEY]; + // The core message is the simple serialization of fields used by OEMCrypto. + // This field was introduced in OEMCrypto API v16. + optional bytes oemcrypto_core_message = 9; + // Optional field that indicates the hash algorithm used in signature scheme. + optional HashAlgorithmProto hash_algorithm = 10; + // If true it indicates that a LICENSE message session key was based on a key + // provided in a secondary encryption certificate. The secondary encryption + // certificate was provided by the client in a previous LICENSE_REQUEST + // message. + optional bool using_dual_certificate = 11; +} diff --git a/protos/public/provisioned_device_info.proto b/protos/public/provisioned_device_info.proto new file mode 100644 index 0000000..d73d68b --- /dev/null +++ b/protos/public/provisioned_device_info.proto @@ -0,0 +1,79 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2016 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. +//////////////////////////////////////////////////////////////////////////////// + +// Description: +// Provisioned device info format definitions. + +syntax = "proto2"; + +package widevine; + +import "protos/public/device_common.proto"; + +option java_package = "com.google.video.widevine.protos"; +option java_outer_classname = "ProvisionedDeviceInfoProto"; + + +// Contains device model information for a provisioned device. +message ProvisionedDeviceInfo { + enum WvSecurityLevel { + // Defined in Widevine Security Integration Guide for DASH on Android: + // http://doc/1Zum-fcJeoIw6KG1kDP_KepIE5h9gAZg0PaMtemBvk9c/edit#heading=h.1t3h5sf + LEVEL_UNSPECIFIED = 0; + LEVEL_1 = 1; + LEVEL_2 = 2; + LEVEL_3 = 3; + } + // Widevine initial provisioning / bootstrapping method. DRM certificates are + // required for retrieving licenses, so if a DRM certificate is not initially + // provisioned, then the provisioned credentials will be used to provision + // a DRM certificate via the Widevine Provisioning Service. + enum ProvisioningMethod { + // Don't use this. + PROVISIONING_METHOD_UNSPECIFIED = 0; + // Factory-provisioned device-unique keybox. + FACTORY_KEYBOX = 1; + // Factory-provisioned device-unique OEM certificate. + FACTORY_OEM_DEVICE_CERTIFICATE = 2; + // Factory-provisioned model-group OEM certificate. + FACTORY_OEM_GROUP_CERTIFICATE = 3; + // Factory-provisioned model-group DRM certificate (Level-3 "baked in"). + FACTORY_DRM_GROUP_CERTIFICATE = 4; + // OTA-provisioned keybox (Level-1 ARC++). + OTA_KEYBOX = 5; + // OTA-provisioned device-unique OEM certificate. + OTA_OEM_DEVICE_CERTIFICATE = 6; + // OTA-provisioned model-group OEM certificate. + OTA_OEM_GROUP_CERTIFICATE = 7; + // OTA-provisioned device-unique DRM certificate (Bedrock). + OTA_DRM_DEVICE_CERTIFICATE = 8; + } + + // Widevine system ID for the device. Mandatory. + optional uint32 system_id = 1; + // Name of system-on-a-chip. Optional. + optional string soc = 2; + // First registered manufacturer. Optional. + optional string manufacturer = 3; + // First registered manufacturer's model name. Matches "brand" in device + // metadata. Optional. + optional string model = 4; + // First registered type of device (Phone, Tablet, TV, etc). + optional string device_type = 5; + // First registered device model year. Optional. + optional uint32 model_year = 6; + // Widevine-defined security level. Optional. + optional WvSecurityLevel security_level = 7 [default = LEVEL_UNSPECIFIED]; + // True if the certificate corresponds to a test (non production) device. + // Optional. + optional bool test_device = 8 [default = false]; + // Indicates the type of device root of trust which was factory provisioned. + optional ProvisioningMethod provisioning_method = 9; + // A list of ModelInfo using the same system_id. + repeated DeviceModel model_info = 10; +} diff --git a/protos/public/remote_attestation.proto b/protos/public/remote_attestation.proto new file mode 100644 index 0000000..6a487ac --- /dev/null +++ b/protos/public/remote_attestation.proto @@ -0,0 +1,31 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Remote attestation is used by ChromeOS device to authenticate itself +// to Widevine services for both licensing and keybox provisioning. + +syntax = "proto2"; + +package widevine; + +import "protos/public/client_identification.proto"; + +option java_package = "com.google.video.widevine.protos"; + + +message RemoteAttestation { + // Encrypted ClientIdentification message containing the device remote + // attestation certificate. Required. + optional EncryptedClientIdentification certificate = 1; + // Bytes of salt which were added to the remote attestation challenge prior to + // signing it. Required. + optional bytes salt = 2; + // Signed remote attestation challenge + salt. Required. + optional bytes signature = 3; +} diff --git a/protos/public/signed_drm_certificate.proto b/protos/public/signed_drm_certificate.proto new file mode 100644 index 0000000..6022fbf --- /dev/null +++ b/protos/public/signed_drm_certificate.proto @@ -0,0 +1,32 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2016 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. +//////////////////////////////////////////////////////////////////////////////// + +// Signed device certificate definition. + +syntax = "proto2"; + +package widevine; + +import "protos/public/hash_algorithm.proto"; + +option java_outer_classname = "SignedDrmCertificateProtos"; +option java_package = "com.google.video.widevine.protos"; + + +// DrmCertificate signed by a higher (CA) DRM certificate. +message SignedDrmCertificate { + // Serialized certificate. Required. + optional bytes drm_certificate = 1; + // Signature of certificate. Signed with root or intermediate + // certificate specified below. Required. + optional bytes signature = 2; + // SignedDrmCertificate used to sign this certificate. + optional SignedDrmCertificate signer = 3; + // Optional field that indicates the hash algorithm used in signature scheme. + optional HashAlgorithmProto hash_algorithm = 4; +} diff --git a/provisioning_sdk-bin.tar b/provisioning_sdk-bin.tar deleted file mode 100644 index b11fa28..0000000 Binary files a/provisioning_sdk-bin.tar and /dev/null differ diff --git a/provisioning_sdk/public/provisioning_engine.h b/provisioning_sdk/public/provisioning_engine.h new file mode 100644 index 0000000..6f1444c --- /dev/null +++ b/provisioning_sdk/public/provisioning_engine.h @@ -0,0 +1,182 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2016 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 PROVISIONING_SDK_PUBLIC_PROVISIONING_ENGINE_H_ +#define PROVISIONING_SDK_PUBLIC_PROVISIONING_ENGINE_H_ + +#include +#include +#include +#include +#include + +#include "common/certificate_type.h" +#include "provisioning_sdk/public/provisioning_status.h" + +namespace widevine { + +class ProvisioningEngineImpl; +class ProvisioningSession; +class ProvisioningSessionImpl; + +// Session factory function used to implement third-party provisioning +// protocols. +// * |engine| is the ProvisioningEngineImpl which invokes the function. +// * |new_session| will point, on successful return, to the newly created +// ProvisioningSessionImpl. +// * Returns OK if successful, or an appropriate error status code otherwise. +typedef std::function* new_session)> + SessionFactory; + +// Class which is used to implement a Widevine DRM device provisioning engine. +// There should be only one instance of ProvisioningEngine. The engine should +// be "Initialized" before being used. ProvisioningEngine::Initialize is the +// only method that is not thread-safe. After initializing the engine, it can +// be safely used in different threads. +class ProvisioningEngine { + public: + ProvisioningEngine(); + virtual ~ProvisioningEngine(); + + // Initializes the provisioning engine with required credentials. + // * |certificate_type| indicates which type of certificate chains will be + // used for device provisioning via this engine. + // * |service_drm_certificate| is a Google-generated certificate used to + // authenticate the service provider for purposes of user privacy. + // * |service_private_key| is the encrypted PKCS#8 private RSA key + // corresponding to the service certificate. + // * |service_private_key_passphrase| is the password required to decrypt + // |service_private_key|, if any. + // * |provisioning_drm_certificate| is a Google-generated certificate used to + // sign intermediate DRM certificates. + // * |provisioning_private_key| is the encrypted PKCS#8 private RSA key + // corresponding to the provisioning certificate. + // * |provisioning_private_key_passphrase| is the password required to + // decrypt |provisioning_private_key|, if any. + // * |secret_spoid_sauce| is a stable secret used as a factor in the + // derivation of Stable Per-Origin IDentifiers. + // * Returns OK on success, or an appropriate error status code otherwise. + ProvisioningStatus Initialize( + CertificateType certificate_type, + const std::string& service_drm_certificate, + const std::string& service_private_key, + const std::string& service_private_key_passphrase, + const std::string& provisioning_drm_certificate, + const std::string& provisioning_private_key, + const std::string& provisioning_private_key_passphrase, + const std::string& secret_spoid_sauce); + + // Third-party protocol registration method. + // * |protocol| is the provisioning protocol, as defined in the + // SignedProvisioningMessage message. + // * |session_factory| is the function which instantiates the appropriate + // ProvisioningSessionImpl object for the specified protocol. + void RegisterProtocol(int protocol, + SessionFactory session_factory); + + // Set the certificate status list for this engine. + // * |certificate_status_list| is a certificate status list generated by the + // Widevine Provisioning Service. + // * |expiration_period| is the number of seconds until the + // |certificate_status_list| expires after its creation time + // (creation_time_seconds). Zero means it will never expire. + // * Returns OK on success, or an appropriate error status code otherwise. + virtual ProvisioningStatus SetCertificateStatusList( + const std::string& certificate_status_list, + uint32_t expiration_period_seconds); + + // Generate an intermediate DRM certificate. + // * |system_id| is the Widevine system ID for the type of device. + // * |public_key| is a DER-encoded PKCS#1.5 RSAPublicKey message which will + // be embedded in the generated certificate. + // * |certificate| will contain the new intermediate certificate, upon + // successful return. + // * Returns OK on success, or an appropriate error status code otherwise. + // NOTE: The generated certificate and associated private key should be stored + // securely to be reused. They should also be propagated to all + // engines, including this one, by invoking + // |AddIntermediatedrmcertificate| on all active ProvisioningEngine(s). + ProvisioningStatus GenerateDrmIntermediateCertificate( + uint32_t system_id, const std::string& public_key, + std::string* certificate) const; + + // Add an intermediate DRM certificate to the provisioning engine. This is + // usually done once for each supported device type. + // * |intermediate_cert| is the intermediate DRM certificate to be added. + // * |cert_private_key| is a PKCS#8 private key corresponding to + // |intermediate_cert|. + // * |cert_private_key_passphrase| is the passphrase for cert_private_key, + // if any. + // * Returns OK on success, or an appropriate error status code otherwise. + virtual ProvisioningStatus AddDrmIntermediateCertificate( + const std::string& intermediate_cert, const std::string& cert_private_key, + const std::string& cert_private_key_passphrase); + + // Create a session to handle a certificate provisioning exchange between + // a client device and the provisioning server. + // * |protocol| is the protocol version for the session. If not sure, it + // probably ought to be |PROVISIONING_30|. + // * |device_public_key| is a DER-encoded PKCS#1.5 RSAPublicKey message which + // will used to create the DRM certificate to be provisioned onto the + // device. + // * |device_private_key| is a DER-encoded PKCS#8 PrivateKeyInfo message + // which contains the private key matching |device_public_key|. + // * |new_session| will point, on successful return, to the newly created + // ProvisioningSession. + // * Returns OK if successful, or an appropriate error status code otherwise. + // The key pairs can be re-used if the created session failed to process the + // message. + // NOTE: All ProvisioningSession objects must be deleted before the + // ProvisioningEngine which created them. + virtual ProvisioningStatus NewProvisioningSession( + const std::string& device_public_key, + const std::string& device_private_key, + std::unique_ptr* new_session) const; + + // This is the same as NewProvisioningSession above, but with outputs reversed + // To get around CLIF bug https://github.com/google/clif/issues/30. + std::unique_ptr NewProvisioningSession( + const std::string& device_public_key, + const std::string& device_private_key, ProvisioningStatus* status) const; + + // Generate a new device DRM certificate to be provisioned by means other than + // the Widevine provisioning protocol. + // NOTE: This API should only be used to provision devices which were + // manufactured without Widevine DRM support. It is meant to be used as + // an exception, and not the norm. Most devices should be provisioned + // by means of a ProvisioningSession. + // * |system_id| is the Widevine system ID for the type of device being + // provisioned. + // * |public_key| is a DER-encoded PKCS#1.5 RSAPublicKey message which will + // be embedded in the generated certificate. + // * |serial_number| is a binary std::string to be used as the generated DRM + // certificate serial number. + // * |certificate| will contain, upon successful return the generated + // certificate. + // * Returns OK on success, or an appropriate error status code otherwise. + ProvisioningStatus GenerateDeviceDrmCertificate( + uint32_t system_id, const std::string& public_key, + const std::string& serial_number, std::string* certificate) const; + + private: + std::map + protocol_registry_; + +#ifndef SWIGPYTHON + ProvisioningEngine(const ProvisioningEngine&) = delete; + ProvisioningEngine& operator=(const ProvisioningEngine&) = delete; +#endif + + std::unique_ptr impl_; +}; + +} // namespace widevine + +#endif // PROVISIONING_SDK_PUBLIC_PROVISIONING_ENGINE_H_ diff --git a/provisioning_sdk/public/provisioning_session.h b/provisioning_sdk/public/provisioning_session.h new file mode 100644 index 0000000..1495925 --- /dev/null +++ b/provisioning_sdk/public/provisioning_session.h @@ -0,0 +1,59 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2016 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 PROVISIONING_SDK_PUBLIC_PROVISIONING_SESSION_H_ +#define PROVISIONING_SDK_PUBLIC_PROVISIONING_SESSION_H_ + +#include +#include + +#include "provisioning_sdk/public/provisioning_status.h" + +namespace widevine { + +class ProvisionedDeviceInfo; +class ProvisioningSessionImpl; + +// Class which is used to implement the provisioning session state machine. +class ProvisioningSession { + public: + virtual ~ProvisioningSession(); + + // Process a message from the client device. + // * |message| is the message received from the client device. + // * |response| will contain, upon successful return, a message to be sent + // back to the client device as a response to |message|. + // * |done| will indicate, upon successful return, whether the provisioning + // exchange is complete. + // Returns OK if successful, or an appropriate error status code otherwise. + virtual ProvisioningStatus ProcessMessage(const std::string& message, + std::string* response, bool* done); + + // * Returns a ProvisioneddeviceInfo message containing information about the + // type of device being provisioned. May return nullptr. + virtual const ProvisionedDeviceInfo* GetDeviceInfo() const; + + protected: + ProvisioningSession(); // To enable mocking. + + private: +#ifndef SWIGPYTHON + friend class ProvisioningEngine; + + ProvisioningSession(const ProvisioningSession&) = delete; + ProvisioningSession& operator=(const ProvisioningSession&) = delete; +#endif + + explicit ProvisioningSession(std::unique_ptr impl); + + std::unique_ptr impl_; +}; + +} // namespace widevine + +#endif // PROVISIONING_SDK_PUBLIC_PROVISIONING_SESSION_H_ diff --git a/provisioning_sdk/public/provisioning_status.h b/provisioning_sdk/public/provisioning_status.h new file mode 100644 index 0000000..342868e --- /dev/null +++ b/provisioning_sdk/public/provisioning_status.h @@ -0,0 +1,58 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2016 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 PROVISIONING_SDK_PUBLIC_PROVISIONING_STATUS_H_ +#define PROVISIONING_SDK_PUBLIC_PROVISIONING_STATUS_H_ + +#include + +namespace widevine { + +enum ProvisioningStatus { + OK = 0, + INVALID_CERTIFICATE_TYPE = 1, + PROVISIONING_ENGINE_UNINITIALIZED = 2, + INVALID_SERVICE_DRM_CERTIFICATE = 3, + INVALID_PROVISIONER_DRM_CERTIFICATE = 5, + // Invalid provisioner private key or private key passphrase. + INVALID_PROVISIONER_PRIVATE_KEY = 6, + INVALID_INTERMEDIATE_DRM_CERTIFICATE = 7, + INVALID_INTERMEDIATE_PUBLIC_KEY = 8, + // Invalid intermediate private key or private key passphrase. + INVALID_INTERMEDIATE_PRIVATE_KEY = 9, + INVALID_STATUS_LIST = 10, + STATUS_LIST_EXPIRED = 11, + UNKNOWN_SYSTEM_ID = 12, + INVALID_DRM_DEVICE_PUBLIC_KEY = 13, + INVALID_DRM_DEVICE_PRIVATE_KEY = 14, + INVALID_REQUEST_MESSAGE = 15, + INVALID_MAC = 16, + MISSING_DEVICE_MODEL_CERT = 17, + DRM_DEVICE_CERTIFICATE_NOT_SET = 18, + DEVICE_REVOKED = 19, + INVALID_SERIAL_NUMBER = 20, + INTERNAL_ERROR = 21, + INVALID_SPOID_SAUCE = 22, + INVALID_PROTOCOL = 23, + INVALID_CONTEXT_KEY_DATA = 24, + INVALID_CONTEXT = 25, + PROTOCOL_ERROR = 26, + INVALID_KEYBOX_DEVICE_KEY = 27, + INVALID_SESSION_KEYS = 28, + INVALID_PREPROV_KEY = 29, + MISSING_PREPROV_KEY = 30, + REMOTE_ATTESTATION_VERIFICATION_FAILURE = 31, + NUM_PROVISIONING_STATUS, +}; + +// Returns the message std::string for the given ProvisioningStatus. +std::string GetProvisioningStatusMessage(ProvisioningStatus status); + +} // namespace widevine + +#endif // PROVISIONING_SDK_PUBLIC_PROVISIONING_STATUS_H_ diff --git a/provisioning_sdk/public/python/certificate_type_setup.py b/provisioning_sdk/public/python/certificate_type_setup.py new file mode 100644 index 0000000..5fdc991 --- /dev/null +++ b/provisioning_sdk/public/python/certificate_type_setup.py @@ -0,0 +1,36 @@ +################################################################################ +# 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. +################################################################################ +"""Installation file for the certificate_type module.""" + +import setup_common as common +import setuptools + +if __name__ == '__main__': + setuptools.setup( + name='certificate_type', + ext_modules=[ + setuptools.Extension( + name='certificate_type', + sources=[ + '%s/certificate_type.cc' % common.WVCOMMON_SRC_DIR, + '%s/initcertificate_type.cc' % common.WVCOMMON_SRC_DIR, + '%s/clif/python/runtime.cc' % common.CLIF_PREFIX, + '%s/clif/python/slots.cc' % common.CLIF_PREFIX, + '%s/clif/python/types.cc' % common.CLIF_PREFIX, + ], + include_dirs=[ + common.SDK_ROOT_DIR, common.GEN_DIR, common.CLIF_PREFIX, '/' + ], + extra_compile_args=['-std=c++11'], + library_dirs=[common.SDK_LIBRARY_DIR], + libraries=['provisioning_sdk'], + runtime_library_dirs=[common.SDK_LIBRARY_DIR], + install_requires=['enum34;python_version<"3.4"'], + ), + ], + ) diff --git a/provisioning_sdk/public/python/crypto_utility.py b/provisioning_sdk/public/python/crypto_utility.py new file mode 100644 index 0000000..232dcdb --- /dev/null +++ b/provisioning_sdk/public/python/crypto_utility.py @@ -0,0 +1,27 @@ +################################################################################ +# Copyright 2016 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. +################################################################################ +"""Utility functions for cryptography.""" + +import logging + +from cryptography.hazmat import backends +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import padding + + +def VerifySignature(public_key, signature, data): + hash_algorithm = hashes.SHA1() + salt_len = 20 + + logging.info('Verying signature.') + key = serialization.load_der_public_key( + public_key, backend=backends.default_backend()) + key.verify(signature, data, + padding.PSS(padding.MGF1(hash_algorithm), salt_len), + hash_algorithm) diff --git a/provisioning_sdk/public/python/drm_intermediate_certificate_test.py b/provisioning_sdk/public/python/drm_intermediate_certificate_test.py new file mode 100644 index 0000000..940b69c --- /dev/null +++ b/provisioning_sdk/public/python/drm_intermediate_certificate_test.py @@ -0,0 +1,54 @@ +################################################################################ +# Copyright 2016 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. +################################################################################ + +import unittest + +import test_data_utility +from provisioning_engine import ProvisioningEngine +from provisioning_status import ProvisioningStatus + + +class AddDrmIntermediateTest(unittest.TestCase): + + def setUp(self): + self._engine = ProvisioningEngine() + test_data_utility.InitProvisionEngineWithTestData( + self._engine, verify_success=True) + + def testGenerateDrmIntermediateCertificateWithValidExpirationPeriod(self): + test_data_utility.SetCertificateStatusListWithTestData( + self._engine, 0, verify_success=True) + + test_data_utility.AddDrmIntermediateCertificateWithTestData( + self._engine, 2001, verify_success=True) + + def testSetCertificateStatusListInvalid(self): + set_cert_status_list = self._engine.SetCertificateStatusList( + 'INVALID_STATUS_LIST', 0) + self.assertEqual(ProvisioningStatus.INVALID_STATUS_LIST, + set_cert_status_list) + + def testAddDrmIntermediateCertificateWithoutCertificateStatusList(self): + # Users should not be able to add DRM certificate without having + # certificate status list. + status = test_data_utility.AddDrmIntermediateCertificateWithTestData( + self._engine, 2001) + self.assertEqual(ProvisioningStatus.STATUS_LIST_EXPIRED, status) + + def testAddDrmIntermediateCertificateSystemIdInvalid(self): + test_data_utility.SetCertificateStatusListWithTestData( + self._engine, 0, verify_success=True) + + # system_id 9999 is not in the sample certificate status list + add_ca_status = test_data_utility.AddDrmIntermediateCertificateWithTestData( + self._engine, 9999) + self.assertEqual(ProvisioningStatus.UNKNOWN_SYSTEM_ID, add_ca_status) + + +if __name__ == '__main__': + unittest.main() diff --git a/provisioning_sdk/public/python/engine_generate_certificate_test.py b/provisioning_sdk/public/python/engine_generate_certificate_test.py new file mode 100644 index 0000000..e0038c6 --- /dev/null +++ b/provisioning_sdk/public/python/engine_generate_certificate_test.py @@ -0,0 +1,67 @@ +################################################################################ +# Copyright 2016 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. +################################################################################ + +import unittest + +from certificate_type import CertificateType +import crypto_utility +import test_data_provider +import test_data_utility +from provisioning_engine import ProvisioningEngine +from provisioning_status import ProvisioningStatus +from protos.public import signed_drm_certificate_pb2 + + +class EngineGenerateCertificateTest(unittest.TestCase): + + def setUp(self): + self._engine = ProvisioningEngine() + test_data_utility.InitProvisionEngineWithTestData( + self._engine, verify_success=True) + test_data_utility.SetCertificateStatusListWithTestData( + self._engine, 0, verify_success=True) + test_data_utility.AddDrmIntermediateCertificateWithTestData( + self._engine, 2001, verify_success=True) + self._data_provider = test_data_provider.TestDataProvider( + CertificateType.kCertificateTypeTesting) + + def testSuccess(self): + status, signed_cert_string = self._engine.GenerateDeviceDrmCertificate( + 2001, self._data_provider.device_public_key, 'DEVICE_SERIAL_NUMBER') + self.assertEqual(ProvisioningStatus.OK, status) + + signed_cert = signed_drm_certificate_pb2.SignedDrmCertificate() + signed_cert.ParseFromString(signed_cert_string) + crypto_utility.VerifySignature(self._data_provider.ca_public_key, + signed_cert.signature, + signed_cert.drm_certificate) + + def testEmptySerialNumber(self): + status, _ = self._engine.GenerateDeviceDrmCertificate( + 2001, self._data_provider.device_public_key, '') + self.assertEqual(ProvisioningStatus.INVALID_SERIAL_NUMBER, status) + + def testEmptyPublicKey(self): + status, _ = self._engine.GenerateDeviceDrmCertificate( + 2001, '', 'DEVICE_SERIAL_NUMBER') + self.assertEqual(ProvisioningStatus.INVALID_DRM_DEVICE_PUBLIC_KEY, status) + + def testInvalidPublicKey(self): + status, _ = self._engine.GenerateDeviceDrmCertificate( + 2001, 'PUBLIC_KEY_MUST_BE_IN_DER_ENCODED_PKCS1_FORMAT', + 'DEVICE_SERIAL_NUMBER') + self.assertEqual(ProvisioningStatus.INVALID_DRM_DEVICE_PUBLIC_KEY, status) + + def testMissingIntermediateCertificate(self): + status, _ = self._engine.GenerateDeviceDrmCertificate( + 2002, self._data_provider.device_public_key, 'DEVICE_SERIAL_NUMBER') + self.assertEqual(ProvisioningStatus.UNKNOWN_SYSTEM_ID, status) + + +if __name__ == '__main__': + unittest.main() diff --git a/provisioning_sdk/public/python/init_engine_test.py b/provisioning_sdk/public/python/init_engine_test.py new file mode 100644 index 0000000..664ef47 --- /dev/null +++ b/provisioning_sdk/public/python/init_engine_test.py @@ -0,0 +1,167 @@ +################################################################################ +# Copyright 2016 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. +################################################################################ + +import unittest + +from certificate_type import CertificateType +import test_data_provider +import test_data_utility +from provisioning_engine import ProvisioningEngine +from provisioning_status import ProvisioningStatus + + +class InitEngineTest(unittest.TestCase): + + def setUp(self): + self._engine = ProvisioningEngine() + self._data_provider = test_data_provider.TestDataProvider( + CertificateType.kCertificateTypeTesting) + + def testInitEngineSucceed(self): + status = test_data_utility.InitProvisionEngineWithTestData( + self._engine, verify_success=True) + self.assertEqual(ProvisioningStatus.OK, status) + + def testSetCertificateStatusListWithoutInit(self): + status = self._engine.SetCertificateStatusList('CERTIFICATE_STATUS_LIST', + 3600) + self.assertEqual(ProvisioningStatus.PROVISIONING_ENGINE_UNINITIALIZED, + status) + + def testGenerateDrmIntermediateCertificateWithoutInit(self): + status, _ = self._engine.GenerateDrmIntermediateCertificate( + 100, 'INTERMEDIATE_PUBLIC_KEY') + self.assertEqual(ProvisioningStatus.PROVISIONING_ENGINE_UNINITIALIZED, + status) + + def testAddDrmIntermediateCertificateWithoutInit(self): + status = self._engine.AddDrmIntermediateCertificate( + 'INTERMEDIATE_CERTIFICATE', 'INTERMEDIATE_PRIVATE_KEY', + 'INTERMEDIATE_PRIVATE_KEY_PASSPHRASE') + self.assertEqual(ProvisioningStatus.PROVISIONING_ENGINE_UNINITIALIZED, + status) + + def testGenerateDeviceDrmCertificateWithoutInit(self): + status, _ = self._engine.GenerateDeviceDrmCertificate( + 100, 'DEVICE_PUBLIC_KEY', 'DEVICE_SERIAL_NUMBER') + self.assertEqual(ProvisioningStatus.PROVISIONING_ENGINE_UNINITIALIZED, + status) + + def testNewProvisioningSessionWithoutInit(self): + session, status = self._engine.NewProvisioningSession( + 'DEVICE_PUBLIC_KEY', + 'DEVICE_PRIVATE_KEY') + self.assertEqual(ProvisioningStatus.PROVISIONING_ENGINE_UNINITIALIZED, + status) + self.assertIsNone(session) + + def testInitEngineInvalidServiceDrmCert(self): + status = self._engine.Initialize( + CertificateType.kCertificateTypeTesting, 'INVALID_CERT', + self._data_provider.service_private_key, + self._data_provider.service_private_key_passphrase, + self._data_provider.provisioner_drm_cert, + self._data_provider.provisioner_private_key, + self._data_provider.provisioner_private_key_passphrase, + self._data_provider.provisioner_spoid_secret) + self.assertEqual(ProvisioningStatus.INVALID_SERVICE_DRM_CERTIFICATE, status) + + def testInitEngineInvalidServicePrivateKey(self): + status = self._engine.Initialize( + CertificateType.kCertificateTypeTesting, + self._data_provider.service_drm_cert, 'INVALID_KEY', + self._data_provider.service_private_key_passphrase, + self._data_provider.provisioner_drm_cert, + self._data_provider.provisioner_private_key, + self._data_provider.provisioner_private_key_passphrase, + self._data_provider.provisioner_spoid_secret) + self.assertEqual(ProvisioningStatus.INVALID_SERVICE_DRM_CERTIFICATE, status) + + def testInitEngineWrongServicePrivateKey(self): + status = self._engine.Initialize( + CertificateType.kCertificateTypeTesting, + self._data_provider.service_drm_cert, + self._data_provider.provisioner_private_key, + self._data_provider.service_private_key_passphrase, + self._data_provider.provisioner_drm_cert, + self._data_provider.provisioner_private_key, + self._data_provider.provisioner_private_key_passphrase, + self._data_provider.provisioner_spoid_secret) + self.assertEqual(ProvisioningStatus.INVALID_SERVICE_DRM_CERTIFICATE, status) + + def testInitEngineInvalidServicePrivateKeyPassphrase(self): + status = self._engine.Initialize( + CertificateType.kCertificateTypeTesting, + self._data_provider.service_drm_cert, + self._data_provider.service_private_key, 'INVALID_PASSPHRASE', + self._data_provider.provisioner_drm_cert, + self._data_provider.provisioner_private_key, + self._data_provider.provisioner_private_key_passphrase, + self._data_provider.provisioner_spoid_secret) + self.assertEqual(ProvisioningStatus.INVALID_SERVICE_DRM_CERTIFICATE, status) + + def testInitEngineInvalidDrmCert(self): + status = self._engine.Initialize( + CertificateType.kCertificateTypeTesting, + self._data_provider.service_drm_cert, + self._data_provider.service_private_key, + self._data_provider.service_private_key_passphrase, 'INVALID_CERT', + self._data_provider.provisioner_private_key, + self._data_provider.provisioner_private_key_passphrase, + self._data_provider.provisioner_spoid_secret) + self.assertEqual(ProvisioningStatus.INVALID_PROVISIONER_DRM_CERTIFICATE, + status) + + def testInitEngineInvalidDrmPrivateKey(self): + status = self._engine.Initialize( + CertificateType.kCertificateTypeTesting, + self._data_provider.service_drm_cert, + self._data_provider.service_private_key, + self._data_provider.service_private_key_passphrase, + self._data_provider.provisioner_drm_cert, 'INVALID_KEY', + self._data_provider.provisioner_private_key_passphrase, + self._data_provider.provisioner_spoid_secret) + self.assertEqual(ProvisioningStatus.INVALID_PROVISIONER_PRIVATE_KEY, status) + + def testInitEngineWrongDrmPrivateKey(self): + status = self._engine.Initialize( + CertificateType.kCertificateTypeTesting, + self._data_provider.service_drm_cert, + self._data_provider.service_private_key, + self._data_provider.service_private_key_passphrase, + self._data_provider.provisioner_drm_cert, + self._data_provider.service_private_key, + self._data_provider.provisioner_private_key_passphrase, + self._data_provider.provisioner_spoid_secret) + self.assertEqual(ProvisioningStatus.INVALID_PROVISIONER_PRIVATE_KEY, status) + + def testInitEngineInvalidDrmPrivateKeyPassphrase(self): + status = self._engine.Initialize( + CertificateType.kCertificateTypeTesting, + self._data_provider.service_drm_cert, + self._data_provider.service_private_key, + self._data_provider.service_private_key_passphrase, + self._data_provider.provisioner_drm_cert, + self._data_provider.provisioner_private_key_passphrase, + 'INVALID_PASSPHRASE', self._data_provider.provisioner_spoid_secret) + self.assertEqual(ProvisioningStatus.INVALID_PROVISIONER_PRIVATE_KEY, status) + + def testInitEngineInvalidSpoidSecret(self): + status = self._engine.Initialize( + CertificateType.kCertificateTypeTesting, + self._data_provider.service_drm_cert, + self._data_provider.service_private_key, + self._data_provider.service_private_key_passphrase, + self._data_provider.provisioner_drm_cert, + self._data_provider.provisioner_private_key, + self._data_provider.provisioner_private_key_passphrase, '') + self.assertEqual(ProvisioningStatus.INVALID_SPOID_SAUCE, status) + + +if __name__ == '__main__': + unittest.main() diff --git a/provisioning_sdk/public/python/new_session_test.py b/provisioning_sdk/public/python/new_session_test.py new file mode 100644 index 0000000..62fde7c --- /dev/null +++ b/provisioning_sdk/public/python/new_session_test.py @@ -0,0 +1,110 @@ +################################################################################ +# Copyright 2016 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. +################################################################################ + +import unittest + +from certificate_type import CertificateType +import crypto_utility +import test_data_provider +import test_data_utility +from provisioning_engine import ProvisioningEngine +from provisioning_status import ProvisioningStatus +from protos.public import certificate_provisioning_pb2 +from protos.public import signed_drm_certificate_pb2 + + +class NewSessionTest(unittest.TestCase): + + def setUp(self): + self._engine = ProvisioningEngine() + test_data_utility.InitProvisionEngineWithTestData( + self._engine, verify_success=True) + test_data_utility.SetCertificateStatusListWithTestData( + self._engine, 0, verify_success=True) + self._data_provider = test_data_provider.TestDataProvider( + CertificateType.kCertificateTypeTesting) + + def testNewSessionSuccess(self): + test_data_utility.AddDrmIntermediateCertificateWithTestData( + self._engine, 2001, verify_success=True) + + (new_session, _) = test_data_utility.NewProvisioningSessionWithTestData( + self._engine, verify_success=True) + (status, raw_response, _) = new_session.ProcessMessage( + self._data_provider.message) + assert ProvisioningStatus.OK == status + + signed_request = test_data_utility.ConvertToSignedProvisioningMessage( + self._data_provider.message) + + unsigned_request = certificate_provisioning_pb2.ProvisioningRequest() + unsigned_request.ParseFromString(signed_request.message) + + signed_response = test_data_utility.ConvertToSignedProvisioningMessage( + raw_response) + + self._VerifyMessageSignature(self._data_provider.service_public_key, + signed_response) + + unsigned_response = certificate_provisioning_pb2.ProvisioningResponse() + unsigned_response.ParseFromString(signed_response.message) + + self._VerifyProvisioningResponse(unsigned_request, unsigned_response) + + def testProcessInvalidMessage(self): + test_data_utility.AddDrmIntermediateCertificateWithTestData( + self._engine, 2001, verify_success=True) + (new_session, _) = test_data_utility.NewProvisioningSessionWithTestData( + self._engine) + (status, _, _) = new_session.ProcessMessage('INVALID_MESSAGE') + self.assertEqual(ProvisioningStatus.INVALID_REQUEST_MESSAGE, status) + + def testNewSessionWithoutIntermediateCert(self): + (new_session, _) = test_data_utility.NewProvisioningSessionWithTestData( + self._engine, verify_success=True) + (status, _, _) = new_session.ProcessMessage(self._data_provider.message) + self.assertEqual(ProvisioningStatus.MISSING_DEVICE_MODEL_CERT, + status) + + def testNewSessionInvalidDevicePublicKey(self): + test_data_utility.AddDrmIntermediateCertificateWithTestData( + self._engine, 2001, verify_success=True) + (_, session_status) = self._engine.NewProvisioningSession( + 'INVALID_PUBLIC_KEY', + self._data_provider.device_private_key) + self.assertEqual(ProvisioningStatus.INVALID_DRM_DEVICE_PUBLIC_KEY, + session_status) + + def testNewSessionInvalidDevicePrivateKey(self): + test_data_utility.AddDrmIntermediateCertificateWithTestData( + self._engine, 2001, verify_success=True) + (_, session_status) = self._engine.NewProvisioningSession( + self._data_provider.device_public_key, + 'INVALID_PRIVATE_KEY') + self.assertEqual(ProvisioningStatus.INVALID_DRM_DEVICE_PRIVATE_KEY, + session_status) + + def _VerifyMessageSignature(self, public_key, signed_response): + crypto_utility.VerifySignature(public_key, signed_response.signature, + signed_response.message) + + def _VerifyCertSignature(self, public_key, signed_cert): + crypto_utility.VerifySignature(public_key, signed_cert.signature, + signed_cert.drm_certificate) + + def _VerifyProvisioningResponse(self, request, response): + self.assertEqual(request.nonce, response.nonce) + + signed_cert = signed_drm_certificate_pb2.SignedDrmCertificate() + signed_cert.ParseFromString(response.device_certificate) + + self._VerifyCertSignature(self._data_provider.ca_public_key, signed_cert) + + +if __name__ == '__main__': + unittest.main() diff --git a/provisioning_sdk/public/python/provisioning_engine.clif b/provisioning_sdk/public/python/provisioning_engine.clif new file mode 100644 index 0000000..23d0f58 --- /dev/null +++ b/provisioning_sdk/public/python/provisioning_engine.clif @@ -0,0 +1,44 @@ +################################################################################ +# 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. +################################################################################ + +from "common/python/certificate_type.h" import * +from "provisioning_sdk/public/python/provisioning_status.h" import * +from "provisioning_sdk/public/python/provisioning_session.h" import * + +from "provisioning_sdk/public/provisioning_engine.h": + namespace `widevine`: + class ProvisioningEngine: + def Initialize(self, + certificate_type: CertificateType, + service_certificate: bytes, + service_private_key: bytes, + service_private_key_passhprase: bytes, + provisioning_drm_certificate: bytes, + provisioning_private_key: bytes, + provisioning_private_key_passhprase: bytes, + secret_spoid_sauce: bytes) -> ProvisioningStatus + def SetCertificateStatusList(self, + certificate_status_list: bytes, + expiration_period_seconds: int) -> ProvisioningStatus + def GenerateDrmIntermediateCertificate(self, + system_id: int, + public_key: bytes) -> (status: ProvisioningStatus, + certificate: bytes) + def AddDrmIntermediateCertificate(self, + intermediate_cert: bytes, + cert_private_key: bytes, + cert_private_key_passhprase: bytes) -> ProvisioningStatus + def NewProvisioningSession(self, + device_public_key: bytes, + device_private_key: bytes) -> (new_session: ProvisioningSession, + status: ProvisioningStatus) + def GenerateDeviceDrmCertificate(self, + system_id: int, + public_key: bytes, + serial_number: bytes) -> (status: ProvisioningStatus, + certificate: bytes) diff --git a/provisioning_sdk/public/python/provisioning_engine_setup.py b/provisioning_sdk/public/python/provisioning_engine_setup.py new file mode 100644 index 0000000..e2b377c --- /dev/null +++ b/provisioning_sdk/public/python/provisioning_engine_setup.py @@ -0,0 +1,45 @@ +################################################################################ +# 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. +################################################################################ +"""Installation file for the provisioning_engine module.""" + +import certificate_type +import provisioning_session +import provisioning_status +import setup_common as common +import setuptools + +if __name__ == '__main__': + setuptools.setup( + name='provisioning_engine', + ext_modules=[ + setuptools.Extension( + name='provisioning_engine', + sources=[ + '%s/provisioning_engine.cc' % common.SDK_SRC_DIR, + '%s/initprovisioning_engine.cc' % common.SDK_SRC_DIR, + '%s/clif/python/pyproto.cc' % common.CLIF_PREFIX, + '%s/clif/python/runtime.cc' % common.CLIF_PREFIX, + '%s/clif/python/slots.cc' % common.CLIF_PREFIX, + '%s/clif/python/types.cc' % common.CLIF_PREFIX, + ], + include_dirs=[ + common.SDK_ROOT_DIR, common.GEN_DIR, common.CLIF_PREFIX, '/' + ], + extra_compile_args=['-std=c++11'], + library_dirs=[common.SDK_LIBRARY_DIR], + libraries=['provisioning_sdk', 'protobuf'], + runtime_library_dirs=[common.SDK_LIBRARY_DIR], + install_requires=['enum34;python_version<"3.4"'], + extra_objects=[ + certificate_type.__file__, + provisioning_status.__file__, + provisioning_session.__file__, + ], + ), + ], + ) diff --git a/provisioning_sdk/public/python/provisioning_session.clif b/provisioning_sdk/public/python/provisioning_session.clif new file mode 100644 index 0000000..aca0ce6 --- /dev/null +++ b/provisioning_sdk/public/python/provisioning_session.clif @@ -0,0 +1,16 @@ +################################################################################ +# 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. +################################################################################ + +from "provisioning_sdk/public/python/provisioning_status.h" import * + +from "provisioning_sdk/public/provisioning_session.h": + namespace `widevine`: + class ProvisioningSession: + def ProcessMessage(self, message: bytes) -> (status: ProvisioningStatus, + response: bytes, + done: bool) diff --git a/provisioning_sdk/public/python/provisioning_session_setup.py b/provisioning_sdk/public/python/provisioning_session_setup.py new file mode 100644 index 0000000..4809994 --- /dev/null +++ b/provisioning_sdk/public/python/provisioning_session_setup.py @@ -0,0 +1,38 @@ +################################################################################ +# 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. +################################################################################ +"""Installation file for the provisioning_session module.""" + +import provisioning_status +import setup_common as common +import setuptools + +if __name__ == '__main__': + setuptools.setup( + name='provisioning_session', + ext_modules=[ + setuptools.Extension( + name='provisioning_session', + sources=[ + '%s/provisioning_session.cc' % common.SDK_SRC_DIR, + '%s/initprovisioning_session.cc' % common.SDK_SRC_DIR, + '%s/clif/python/runtime.cc' % common.CLIF_PREFIX, + '%s/clif/python/slots.cc' % common.CLIF_PREFIX, + '%s/clif/python/types.cc' % common.CLIF_PREFIX, + ], + include_dirs=[ + common.SDK_ROOT_DIR, common.GEN_DIR, common.CLIF_PREFIX, '/' + ], + extra_compile_args=['-std=c++11'], + library_dirs=[common.SDK_LIBRARY_DIR], + libraries=['provisioning_sdk'], + runtime_library_dirs=[common.SDK_LIBRARY_DIR], + install_requires=['enum34;python_version<"3.4"'], + extra_objects=[provisioning_status.__file__], + ), + ], + ) diff --git a/provisioning_sdk/public/python/provisioning_status.clif b/provisioning_sdk/public/python/provisioning_status.clif new file mode 100644 index 0000000..f7b69f2 --- /dev/null +++ b/provisioning_sdk/public/python/provisioning_status.clif @@ -0,0 +1,11 @@ +################################################################################ +# 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. +################################################################################ + +from "provisioning_sdk/public/provisioning_status.h": + namespace `widevine`: + enum ProvisioningStatus diff --git a/provisioning_sdk/public/python/provisioning_status_setup.py b/provisioning_sdk/public/python/provisioning_status_setup.py new file mode 100644 index 0000000..f0c401f --- /dev/null +++ b/provisioning_sdk/public/python/provisioning_status_setup.py @@ -0,0 +1,36 @@ +################################################################################ +# 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. +################################################################################ +"""Installation file for the provisioning_status module.""" + +import setup_common as common +import setuptools + +if __name__ == '__main__': + setuptools.setup( + name='provisioning_status', + ext_modules=[ + setuptools.Extension( + name='provisioning_status', + sources=[ + '%s/provisioning_status.cc' % common.SDK_SRC_DIR, + '%s/initprovisioning_status.cc' % common.SDK_SRC_DIR, + '%s/clif/python/runtime.cc' % common.CLIF_PREFIX, + '%s/clif/python/slots.cc' % common.CLIF_PREFIX, + '%s/clif/python/types.cc' % common.CLIF_PREFIX, + ], + include_dirs=[ + common.SDK_ROOT_DIR, common.GEN_DIR, common.CLIF_PREFIX, '/' + ], + extra_compile_args=['-std=c++11'], + library_dirs=[common.SDK_LIBRARY_DIR], + libraries=['provisioning_sdk'], + runtime_library_dirs=[common.SDK_LIBRARY_DIR], + install_requires=['enum34;python_version<"3.4"'], + ), + ], + ) diff --git a/provisioning_sdk/public/python/set_certificate_status_list_test.py b/provisioning_sdk/public/python/set_certificate_status_list_test.py new file mode 100644 index 0000000..0bb8a23 --- /dev/null +++ b/provisioning_sdk/public/python/set_certificate_status_list_test.py @@ -0,0 +1,35 @@ +################################################################################ +# Copyright 2016 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. +################################################################################ + +import unittest + +import test_data_utility +from provisioning_engine import ProvisioningEngine +from provisioning_status import ProvisioningStatus + + +class SetCertificateStatusListTest(unittest.TestCase): + + def setUp(self): + self._engine = ProvisioningEngine() + test_data_utility.InitProvisionEngineWithTestData( + self._engine, verify_success=True) + + def testSetCertificateStatusListSuccess(self): + test_data_utility.SetCertificateStatusListWithTestData( + self._engine, 0, verify_success=True) + + def testSetCertificateStatusListInvalid(self): + set_cert_status_list = self._engine.SetCertificateStatusList( + 'INVALID_STATUS_LIST', 0) + self.assertEqual(ProvisioningStatus.INVALID_STATUS_LIST, + set_cert_status_list) + + +if __name__ == '__main__': + unittest.main() diff --git a/provisioning_sdk/public/python/setup_common.py b/provisioning_sdk/public/python/setup_common.py new file mode 100644 index 0000000..a9b08e7 --- /dev/null +++ b/provisioning_sdk/public/python/setup_common.py @@ -0,0 +1,38 @@ +################################################################################ +# 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. +################################################################################ +"""Common definitions for Provisioning SDK python setup files.""" + +import os + +GEN_DIRNAME = 'test_genfiles' + + +def _GetSdkRootDir(): + """Obtains folder containing |GEN_DIRNAME| that is considered as root dir.""" + current_dir = os.path.realpath(os.path.dirname(__file__)) + while not os.path.isdir(os.path.join(current_dir, GEN_DIRNAME)): + current_dir = os.path.dirname(current_dir) + + os.chdir(current_dir) + return current_dir + + +SDK_ROOT_DIR = _GetSdkRootDir() +GEN_DIR = '%s/%s' % (SDK_ROOT_DIR, GEN_DIRNAME) + +SDK_LIBRARY_DIR = os.path.join(SDK_ROOT_DIR, 'bazel-bin', 'provisioning_sdk', + 'public') +if not os.path.exists(SDK_LIBRARY_DIR): + SDK_LIBRARY_DIR = SDK_ROOT_DIR + +CLIF_PREFIX = os.environ['CLIF_PREFIX'] +BUILD_DIR = os.environ['PYEXT_BUILD_DIR'] + +WVCOMMON_SRC_DIR = '%s/common/python' % GEN_DIR +WVPROTO_SRC_DIR = '%s/protos/public' % GEN_DIR +SDK_SRC_DIR = '%s/provisioning_sdk/public/python' % GEN_DIR diff --git a/provisioning_sdk/public/python/test_data_provider.py b/provisioning_sdk/public/python/test_data_provider.py new file mode 100644 index 0000000..4710c25 --- /dev/null +++ b/provisioning_sdk/public/python/test_data_provider.py @@ -0,0 +1,107 @@ +# Lint as: python2, python3 +################################################################################ +# 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. +################################################################################ +"""Class that provides test data for Provisioning SDK testing.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +from certificate_type import CertificateType + +_TEST_CERT_DATA_FOLDER = os.path.join('example', 'example_data') +_DEV_CERT_DATA_FOLDER = os.path.join('example', 'dev_cert_example_data') + + +class TestDataProvider(object): + """For for Test Data.""" + + def __init__(self, cert_type): + """Initializes the TestData for Provisioning SDK tests.""" + assert (cert_type in (CertificateType.kCertificateTypeDevelopment, + CertificateType.kCertificateTypeTesting)) + self._cert_type = cert_type + + def _GetTestData(self, filename): + """Helps read test data files such as certs and keys for SDK testing.""" + current_dir = os.path.realpath(os.path.dirname(__file__)) + if self._cert_type == CertificateType.kCertificateTypeDevelopment: + subfolder_path = _DEV_CERT_DATA_FOLDER + elif self._cert_type == CertificateType.kCertificateTypeTesting: + subfolder_path = _TEST_CERT_DATA_FOLDER + while not os.path.isdir(os.path.join(current_dir, subfolder_path)): + current_dir = os.path.dirname(current_dir) + filename = os.path.join(current_dir, subfolder_path, filename) + try: + with open(filename, 'rb') as data_file: + data = data_file.read() + return data + except IOError: + print('TestDataProvider: Failed to read \'%s\'' % filename) + return None + + @property + def service_drm_cert(self): + return self._GetTestData('service.cert') + + @property + def service_public_key(self): + return self._GetTestData('service.public') + + @property + def service_private_key(self): + return self._GetTestData('service.encrypted.private') + + @property + def service_private_key_passphrase(self): + return self._GetTestData('service.passphrase') + + @property + def provisioner_drm_cert(self): + return self._GetTestData('provisioner.cert') + + @property + def provisioner_private_key(self): + return self._GetTestData('provisioner.encrypted.private') + + @property + def provisioner_private_key_passphrase(self): + return self._GetTestData('provisioner.passphrase') + + @property + def provisioner_spoid_secret(self): + return self._GetTestData('provisioner.spoid_secret') + + @property + def ca_public_key(self): + return self._GetTestData('intermediate.public') + + @property + def ca_private_key(self): + return self._GetTestData('intermediate.encrypted.private') + + @property + def ca_private_key_passphrase(self): + return self._GetTestData('intermediate.passphrase') + + @property + def device_public_key(self): + return self._GetTestData('device.public') + + @property + def device_private_key(self): + return self._GetTestData('device.private') + + @property + def message(self): + return self._GetTestData('message') + + @property + def certificate_list(self): + return self._GetTestData('certificate_list') diff --git a/provisioning_sdk/public/python/test_data_utility.py b/provisioning_sdk/public/python/test_data_utility.py new file mode 100644 index 0000000..a6884d2 --- /dev/null +++ b/provisioning_sdk/public/python/test_data_utility.py @@ -0,0 +1,184 @@ +################################################################################ +# Copyright 2016 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. +################################################################################ +"""Utility class for Provisioning SDK testing.""" + +import logging + +from certificate_type import CertificateType +import test_data_provider +from provisioning_status import ProvisioningStatus +from protos.public import certificate_provisioning_pb2 + +logging.basicConfig(level=logging.DEBUG) + + +def InitProvisionEngineWithTestData( + engine, + verify_success=False, + cert_type=CertificateType.kCertificateTypeTesting): + """Initialize the provisioning engine with sample credentials. + + Args: + engine: a ProvisioningEngine instance + verify_success: whether to verify that resulting status code equals OK + cert_type: The type of certificate to use for initializing SDK - + {kCertificateTypeTesting/kCertificateTypeDevelopment} + + Returns: + OK on success, or an appropriate error status code otherwise. + """ + data_provider = test_data_provider.TestDataProvider(cert_type) + logging.info('Adding service certificate.') + + logging.info('Initializing provisioning engine with test data.') + status = engine.Initialize(cert_type, data_provider.service_drm_cert, + data_provider.service_private_key, + data_provider.service_private_key_passphrase, + data_provider.provisioner_drm_cert, + data_provider.provisioner_private_key, + data_provider.provisioner_private_key_passphrase, + data_provider.provisioner_spoid_secret) + + if verify_success: + assert ProvisioningStatus.OK == status + + return status + + +def SetCertificateStatusListWithTestData( + engine, + expiration_period_seconds, + verify_success=False, + cert_type=CertificateType.kCertificateTypeTesting): + """Set the certificate status list with sample certificate status list. + + Args: + engine: a ProvisioningEngine instance + expiration_period_seconds: number of seconds until certificate_status_list + expires after its creation time + verify_success: whether to verify that resulting status code equals OK + cert_type: The type of certificate to use for initializing SDK - + {kCertificateTypeTesting/kCertificateTypeDevelopment} + + Returns: + OK on success, or an appropriate error status code otherwise. + """ + logging.info('Setting certificate status list with test data.') + data_provider = test_data_provider.TestDataProvider(cert_type) + certificate_status_list = data_provider.certificate_list + + status = engine.SetCertificateStatusList(certificate_status_list, + expiration_period_seconds) + + if verify_success: + assert ProvisioningStatus.OK == status + + return status + + +def AddDrmIntermediateCertificateWithTestData( + engine, + system_id, + verify_success=False, + cert_type=CertificateType.kCertificateTypeTesting): + """Generate an intermediate DRM cert and add it to provisioning engine. + + The intermediate DRM certificate is generated with sample public key and + is added to the provisioning engine with sample certificate private key and + passphrase. + + Args: + engine: a ProvisioningEngine instance + system_id: Widevine system ID for the type of device + verify_success: whether to verify that resulting status code equals OK + cert_type: The type of certificate to use for initializing SDK - + {kCertificateTypeTesting/kCertificateTypeDevelopment} + + Returns: + OK on success, or an appropriate error status code otherwise. + """ + logging.info('Generating DRM intermediate certificate for system_id <%d>.', + system_id) + data_provider = test_data_provider.TestDataProvider(cert_type) + gen_status, ca_certificate = engine.GenerateDrmIntermediateCertificate( + system_id, data_provider.ca_public_key) + assert ProvisioningStatus.OK == gen_status + + logging.info('Adding DRM intermediate certificate.') + add_ca_status = engine.AddDrmIntermediateCertificate( + ca_certificate, data_provider.ca_private_key, + data_provider.ca_private_key_passphrase) + + if verify_success: + assert ProvisioningStatus.OK == add_ca_status + + return add_ca_status + + +def GenerateDeviceDrmCertificate( + engine, + system_id, + serial_number, + verify_success=False, + cert_type=CertificateType.kCertificateTypeTesting): + """Generate a device DRM certificate. + + Args: + engine: a ProvisioningEngine instance + system_id: Widevine system ID for the type of device + serial_number: The serial number for the device DRM certificate. + verify_success: whether to verify that resulting status code equals OK + cert_type: The type of certificate to use for initializing SDK - + {kCertificateTypeTesting/kCertificateTypeDevelopment} + + Returns: + OK on success, or an appropriate error status code otherwise. + """ + logging.info( + 'Generating Device cert for system_id <%d> and serial_number <%s>.', + system_id, serial_number) + data_provider = test_data_provider.TestDataProvider(cert_type) + gen_status, ca_certificate = engine.GenerateDeviceDrmCertificate( + system_id, data_provider.device_public_key, serial_number) + if verify_success: + assert ProvisioningStatus.OK == gen_status + return ca_certificate + + +def NewProvisioningSessionWithTestData( + engine, + verify_success=False, + cert_type=CertificateType.kCertificateTypeTesting): + """Create a provisioning session with sample device public and private keys. + + Args: + engine: a ProvisioningEngine instance + verify_success: whether to verify that resulting status code equals OK + cert_type: The type of certificate to use for initializing SDK - + {kCertificateTypeTesting/kCertificateTypeDevelopment} + + Returns: + new_session: A new provisioning_session. + status: OK on success, or an appropriate error status code otherwise. + """ + logging.info('Starting a new provisioning session with' + 'sample device public and private keys.') + data_provider = test_data_provider.TestDataProvider(cert_type) + new_session, status = engine.NewProvisioningSession( + data_provider.device_public_key, + data_provider.device_private_key) + if verify_success: + assert (ProvisioningStatus.OK == status), 'status = %r' % status + + return new_session, status + + +def ConvertToSignedProvisioningMessage(serialized_message): + signed_message = certificate_provisioning_pb2.SignedProvisioningMessage() + signed_message.ParseFromString(serialized_message) + return signed_message diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..5989b43 --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,116 @@ +#!/bin/bash +################################################################################ +# Copyright 2016 Google LLC. +# +# This software is licensed under the terms defined in the Widevine Master +# License Agreement. For a copy of this agreement, please contact +# widevine-licensing@google.com. +################################################################################ +# +# This script generates a directory that stores the intermediate artifacts +# needed for testing. +# +# Prerequirements (if running the script directly): +# - Python 2.7 or later +# - pip: https://pip.pypa.io/en/latest/installing/ +# - Python cryptography package: https://cryptography.io/en/latest/installation/ +# - Protocol compiler: https://github.com/google/protobuf#protocol-compiler-installation +# On Ubuntu: sudo apt-get install protobuf-compiler +# - Protobuf Python runtime (version 3.0 or later): sudo pip install protobuf +# - CLIF: https://github.com/google/clif +# Environment Variables: +# - TOOLS_PATH: colon-delimited paths to additional tools such as protoc. +# - CLIF_PREFIX: prefix path to CLIF installation. + +set -ex +cd "$(dirname "$0")" + +if [[ -v TOOLS_PATH ]]; then + export PATH=$PATH:$TOOLS_PATH +fi + +if ! which protoc 2>/dev/null; then + echo >&2 "Protobuf is required but not found (did you set TOOLS_PATH?). Aborting." + exit 1 +fi + +if ! [[ -v CLIF_PREFIX ]]; then + echo >&2 "CLIF_PREFIX not set. Defaulting to '$HOME/opt'." + export CLIF_PREFIX=$HOME/opt +fi + +PYCLIF="$CLIF_PREFIX/clif/bin/pyclif" +PYCLIF_PROTO="$CLIF_PREFIX/clif/bin/pyclif_proto" +CLIF_INC="-I$CLIF_PREFIX" +if ! [[ -x $PYCLIF ]]; then + echo >&2 "CLIF is required but not found." + exit 1 +fi + +# Get the path of the native python C++ header files. +PYTHON_INC="-I$(printf "from distutils.sysconfig import get_python_inc\nprint(get_python_inc())\n" | python)" + +SRC_DIR="$(pwd)" +GEN_DIR="$(pwd)/test_genfiles" +PROTO_GEN_DIR="$GEN_DIR/protos/public" +COMMON_GEN_DIR="$GEN_DIR/common/python" +SDK_PYTHON_GEN_DIR="$GEN_DIR/provisioning_sdk/public/python" +export PYEXT_BUILD_DIR="$(pwd)/python_ext_build" +PYEXT_INSTALL_DIR="$(pwd)/python_env" + +rm -rf "$GEN_DIR" +rm -rf "$PYEXT_BUILD_DIR" +rm -rf "$PYEXT_INSTALL_DIR" +mkdir -p "$PROTO_GEN_DIR" +mkdir -p "$COMMON_GEN_DIR" +mkdir -p "$SDK_PYTHON_GEN_DIR" +mkdir -p "$PYEXT_BUILD_DIR" + +# Generate C++ outputs. +# The protos are needed to compile the public header provisioning_engine.h. +protoc -I="$SRC_DIR" --cpp_out="$GEN_DIR" "$SRC_DIR/protos/public/client_identification.proto" +protoc -I="$SRC_DIR" --cpp_out="$GEN_DIR" "$SRC_DIR/protos/public/remote_attestation.proto" +protoc -I="$SRC_DIR" --cpp_out="$GEN_DIR" "$SRC_DIR/protos/public/certificate_provisioning.proto" + +# Generate py outputs. +protoc -I="$SRC_DIR" --python_out="$GEN_DIR" "$SRC_DIR/protos/public/client_identification.proto" +protoc -I="$SRC_DIR" --python_out="$GEN_DIR" "$SRC_DIR/protos/public/hash_algorithm.proto" +protoc -I="$SRC_DIR" --python_out="$GEN_DIR" "$SRC_DIR/protos/public/remote_attestation.proto" +protoc -I="$SRC_DIR" --python_out="$GEN_DIR" "$SRC_DIR/protos/public/provisioned_device_info.proto" +protoc -I="$SRC_DIR" --python_out="$GEN_DIR" "$SRC_DIR/protos/public/certificate_provisioning.proto" +protoc -I="$SRC_DIR" --python_out="$GEN_DIR" "$SRC_DIR/protos/public/signed_drm_certificate.proto" + +# Generate CLIF Python wrappers. +"$PYCLIF" --modname certificate_type --ccdeps_out="$COMMON_GEN_DIR/certificate_type.cc" --ccinit_out="$COMMON_GEN_DIR/initcertificate_type.cc" --header_out="$COMMON_GEN_DIR/certificate_type.h" --cc_flags="-std=c++11 -I$SRC_DIR $PYTHON_INC $CLIF_INC" "$SRC_DIR/common/python/certificate_type.clif" + +"$PYCLIF" --modname provisioning_status --ccdeps_out="$SDK_PYTHON_GEN_DIR/provisioning_status.cc" --ccinit_out="$SDK_PYTHON_GEN_DIR/initprovisioning_status.cc" --header_out="$SDK_PYTHON_GEN_DIR/provisioning_status.h" --cc_flags="-std=c++11 -I$SRC_DIR $PYTHON_INC $CLIF_INC" "$SRC_DIR/provisioning_sdk/public/python/provisioning_status.clif" + +"$PYCLIF" --modname provisioning_session --ccdeps_out="$SDK_PYTHON_GEN_DIR/provisioning_session.cc" --ccinit_out="$SDK_PYTHON_GEN_DIR/initprovisioning_session.cc" --header_out="$SDK_PYTHON_GEN_DIR/provisioning_session.h" --cc_flags="-std=c++11 -I$SRC_DIR -I$GEN_DIR $PYTHON_INC $CLIF_INC" --include_paths="$GEN_DIR" "$SRC_DIR/provisioning_sdk/public/python/provisioning_session.clif" + +"$PYCLIF" --modname provisioning_engine --ccdeps_out="$SDK_PYTHON_GEN_DIR/provisioning_engine.cc" --ccinit_out="$SDK_PYTHON_GEN_DIR/initprovisioning_engine.cc" --header_out="$SDK_PYTHON_GEN_DIR/provisioning_engine.h" --cc_flags="-std=c++11 -I$SRC_DIR -I$GEN_DIR $PYTHON_INC $CLIF_INC" --include_paths="$GEN_DIR" "$SRC_DIR/provisioning_sdk/public/python/provisioning_engine.clif" + +# Create virtual Python environment, build extensions, and install onto virtual +# environment. +cp -a provisioning_sdk/public/python/* test_genfiles/ +cd test_genfiles +rm -rf "$PYEXT_INSTALL_DIR" +virtualenv "$PYEXT_INSTALL_DIR" +"$PYEXT_INSTALL_DIR/bin/python" "$PYEXT_INSTALL_DIR/bin/pip" install enum34 +"$PYEXT_INSTALL_DIR/bin/python" "$PYEXT_INSTALL_DIR/bin/pip" install protobuf +"$PYEXT_INSTALL_DIR/bin/python" "$PYEXT_INSTALL_DIR/bin/pip" install cryptography + +"$PYEXT_INSTALL_DIR/bin/python" certificate_type_setup.py build --build-base="$PYEXT_BUILD_DIR" install +"$PYEXT_INSTALL_DIR/bin/python" provisioning_status_setup.py build --build-base="$PYEXT_BUILD_DIR" install +"$PYEXT_INSTALL_DIR/bin/python" provisioning_session_setup.py build --build-base="$PYEXT_BUILD_DIR" install +"$PYEXT_INSTALL_DIR/bin/python" provisioning_engine_setup.py build --build-base="$PYEXT_BUILD_DIR" install + +shopt -s globstar +for d in "protos"/**/; do + touch -- "$d/__init__.py"; +done + +"$PYEXT_INSTALL_DIR/bin/python" init_engine_test.py +"$PYEXT_INSTALL_DIR/bin/python" set_certificate_status_list_test.py +"$PYEXT_INSTALL_DIR/bin/python" drm_intermediate_certificate_test.py +"$PYEXT_INSTALL_DIR/bin/python" engine_generate_certificate_test.py +"$PYEXT_INSTALL_DIR/bin/python" new_session_test.py