commit 131a00206c0867693a5e596fd8e61c3d9ed5b342 Author: Kongqun Yang Date: Mon Nov 14 10:47:48 2016 -0800 Release provisioning sdk b3889b8 Change-Id: Ib423c4bada1ec2c47eff174b0d99dc9c8813a5ed diff --git a/example/example_data/certificate_list b/example/example_data/certificate_list new file mode 100644 index 0000000..cf9d5da Binary files /dev/null and b/example/example_data/certificate_list differ diff --git a/example/example_data/intermediate.encrypted.private b/example/example_data/intermediate.encrypted.private new file mode 100644 index 0000000..91f3edf 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..c187cb9 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..4f75666 Binary files /dev/null and b/example/example_data/message differ diff --git a/example/example_data/provider.cert b/example/example_data/provider.cert new file mode 100644 index 0000000..178d9b3 Binary files /dev/null and b/example/example_data/provider.cert differ diff --git a/example/example_data/provider.encrypted.private b/example/example_data/provider.encrypted.private new file mode 100644 index 0000000..056470f Binary files /dev/null and b/example/example_data/provider.encrypted.private differ diff --git a/example/example_data/provider.passphrase b/example/example_data/provider.passphrase new file mode 100644 index 0000000..79ea782 --- /dev/null +++ b/example/example_data/provider.passphrase @@ -0,0 +1 @@ +provider_passphrase \ No newline at end of file diff --git a/example/example_data/provisioning_message_generator b/example/example_data/provisioning_message_generator new file mode 100755 index 0000000..d0c227d Binary files /dev/null and b/example/example_data/provisioning_message_generator differ diff --git a/example/example_data/service.cert b/example/example_data/service.cert new file mode 100644 index 0000000..ff6ee16 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..5b17231 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..c9c0d0d Binary files /dev/null and b/example/example_data/service.public differ diff --git a/example/example_data/user.private b/example/example_data/user.private new file mode 100644 index 0000000..b7b5f33 Binary files /dev/null and b/example/example_data/user.private differ diff --git a/example/example_data/user.public b/example/example_data/user.public new file mode 100644 index 0000000..4ec3886 Binary files /dev/null and b/example/example_data/user.public differ diff --git a/example/provisioning_example.cc b/example/provisioning_example.cc new file mode 100644 index 0000000..907d0a6 --- /dev/null +++ b/example/provisioning_example.cc @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include + +#include "provisioning_sdk/public/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::OK; +using widevine::ProvisioningEngine; +using widevine::ProvisioningSession; +using widevine::kCertDevelopment; + +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( + kCertDevelopment, GetContents("example_data/service.cert"), + GetContents("example_data/service.encrypted.private"), + GetContents("example_data/service.passphrase"), + GetContents("example_data/provider.cert"), + GetContents("example_data/provider.encrypted.private"), + GetContents("example_data/provider.passphrase")) != 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/user.public"), + GetContents("example_data/user.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/libprovisioning_sdk.so b/libprovisioning_sdk.so new file mode 100755 index 0000000..8ad3e95 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..dae596b --- /dev/null +++ b/protos/public/certificate_provisioning.proto @@ -0,0 +1,88 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// Author: tinskip@google.com (Thomas Inskip) +// +// Description: +// Public protocol buffer definitions for Widevine Device Certificate +// Provisioning protocol. + +syntax = "proto2"; + +package widevine; +option java_package = "com.google.video.widevine.protos"; + +import "protos/public/client_identification.proto"; + +// 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. + } + + 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; +} + +// Provisioning request sent by client devices to provisioning service. +message ProvisioningRequest { + 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 origin_id { + // Stable identifier, unique for each device + application (or origin). + // Required if doing per-origin provisioning. + bytes stable_id = 4; + // Stable content provider ID. + bytes provider_id = 6; + } +} + +// 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 { + // 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 + // SignedDrmDeviceCertificate. 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; +} + +// Serialized ProvisioningRequest or ProvisioningResponse signed with +// The message authentication key. +message SignedProvisioningMessage { + enum ProtocolVersion { + VERSION_2 = 2; // Keybox factory-provisioned devices. + VERSION_3 = 3; // OEM certificate factory-provisioned devices. + } + + // Serialized ProvisioningRequest or ProvisioningResponse. Required. + optional bytes message = 1; + // HMAC-SHA256 (Keybox) or RSASSA-PSS (OEM) signature of message. Required. + optional bytes signature = 2; + // Version number of provisioning protocol. + optional ProtocolVersion protocol_version = 3 [default = VERSION_2]; +} diff --git a/protos/public/client_identification.proto b/protos/public/client_identification.proto new file mode 100644 index 0000000..01680b2 --- /dev/null +++ b/protos/public/client_identification.proto @@ -0,0 +1,82 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// Author: tinskip@google.com (Thomas Inskip) +// +// 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_NO_DIGITAL_OUTPUT = 0xff; + } + + 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]; + } + + // 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; +} + +// EncryptedClientIdentification message used to hold ClientIdentification +// messages encrypted for privacy purposes. +message EncryptedClientIdentification { + // Service ID for which the ClientIdentifcation is encrypted (owner of service + // certificate). + optional string service_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/provisioned_device_info.proto b/protos/public/provisioned_device_info.proto new file mode 100644 index 0000000..480530c --- /dev/null +++ b/protos/public/provisioned_device_info.proto @@ -0,0 +1,39 @@ +// Description: +// Provisioned device info format definitions. + +syntax = "proto2"; + +package widevine; + +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 system ID for the device. Mandatory. + optional uint32 system_id = 1; + // Name of system-on-a-chip. Optional. + optional string soc = 2; + // Name of manufacturer. Optional. + optional string manufacturer = 3; + // Manufacturer's model name. Matches "brand" in device metadata. Optional. + optional string model = 4; + // Type of device (Phone, Tablet, TV, etc). + optional string device_type = 5; + // 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]; +} diff --git a/protos/public/signed_device_certificate.proto b/protos/public/signed_device_certificate.proto new file mode 100644 index 0000000..db808ff --- /dev/null +++ b/protos/public/signed_device_certificate.proto @@ -0,0 +1,19 @@ +// Signed device certificate definition. + +syntax = "proto2"; + +package widevine; + +option java_outer_classname = "SignedDeviceCertificateProtos"; +option java_package = "com.google.video.widevine.protos"; + +// DrmDeviceCertificate signed by a higher (CA) DRM certificate. +message SignedDrmDeviceCertificate { + // Serialized certificate. Required. + optional bytes drm_certificate = 1; + // Signature of certificate. Signed with root or intermediate + // certificate specified below. Required. + optional bytes signature = 2; + // SignedDrmDeviceCertificate used to sign this certificate. + optional SignedDrmDeviceCertificate signer = 3; +} diff --git a/provisioning_sdk/public/certificate_type.h b/provisioning_sdk/public/certificate_type.h new file mode 100644 index 0000000..4f1c92f --- /dev/null +++ b/provisioning_sdk/public/certificate_type.h @@ -0,0 +1,14 @@ +#ifndef PROVISIONING_SDK_PUBLIC_CERTIFICATE_TYPE_H_ +#define PROVISIONING_SDK_PUBLIC_CERTIFICATE_TYPE_H_ + +namespace widevine { + +enum CertificateType { + kCertTesting = 0, + kCertDevelopment, + kCertProduction, +}; + +} // namespace widevine + +#endif // PROVISIONING_SDK_PUBLIC_CERTIFICATE_TYPE_H_ diff --git a/provisioning_sdk/public/provisioning_engine.h b/provisioning_sdk/public/provisioning_engine.h new file mode 100644 index 0000000..6ba5049 --- /dev/null +++ b/provisioning_sdk/public/provisioning_engine.h @@ -0,0 +1,142 @@ +// Copyright 2016 Google Inc. All rights reserved. + +#ifndef PROVISIONING_SDK_PUBLIC_PROVISIONING_ENGINE_H_ +#define PROVISIONING_SDK_PUBLIC_PROVISIONING_ENGINE_H_ + +#include +#include +#include + +#include "provisioning_sdk/public/certificate_type.h" +#include "provisioning_sdk/public/provisioning_status.h" + +namespace widevine { + +class ProvisioningEngineImpl; +class ProvisioningSession; + +// 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(); + ~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. + // * 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); + + // 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. + 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. + 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 provisioning exchange between a client device + // and the provisioning server. + // * |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. + ProvisioningStatus NewProvisioningSession( + const std::string& device_public_key, + const std::string& device_private_key, + std::unique_ptr* new_session) 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. + // * |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: +#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..d8f85d8 --- /dev/null +++ b/provisioning_sdk/public/provisioning_session.h @@ -0,0 +1,50 @@ +// Copyright 2016 Google Inc. All rights reserved. + +#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: + ~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, and the ProvisioningSession can be deleted. + // Returns OK if successful, or an appropriate error status code otherwise. + 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. + const ProvisionedDeviceInfo* GetDeviceInfo() const; + + 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..857a99a --- /dev/null +++ b/provisioning_sdk/public/provisioning_status.h @@ -0,0 +1,41 @@ +// Copyright 2016 Google Inc. All rights reserved. + +#ifndef PROVISIONING_SDK_PUBLIC_PROVISIONING_STATUS_H_ +#define PROVISIONING_SDK_PUBLIC_PROVISIONING_STATUS_H_ + +namespace widevine { + +enum ProvisioningStatus { + OK = 0, + INVALID_CERTIFICATE_TYPE = 1, + PROVISIONING_ENGINE_UNINITIALIZED = 2, + INVALID_SERVICE_DRM_CERTIFICATE = 3, + // Invalid service private key or private key passphrase. + INVALID_SERVICE_PRIVATE_KEY = 4, + INVALID_PROVISIONING_DRM_CERTIFICATE = 5, + // Invalid provisioning private key or private key passphrase. + INVALID_PROVISIONING_PRIVATE_KEY = 6, + INVALID_INTERMEDIATE_DRM_CERTIFICATE = 7, + // Invalid intermediate private key or private key passphrase. + INVALID_INTERMEDIATE_PRIVATE_KEY = 8, + INVALID_STATUS_LIST = 9, + STATUS_LIST_EXPIRED = 10, + UNKNOWN_SYSTEM_ID = 11, + INVALID_DEVICE_PUBLIC_KEY = 12, + INVALID_DEVICE_PRIVATE_KEY = 13, + INVALID_REQUEST_MESSAGE = 14, + INVALID_MAC = 15, + MISSING_DRM_INTERMEDIATE_CERT = 16, + DRM_DEVICE_CERTIFICATE_NOT_SET = 17, + DEVICE_REVOKED = 18, + INVALID_SERIAL_NUMBER = 19, + INTERNAL_ERROR = 20, + NUM_PROVISIONING_STATUS, +}; + +// Returns the message std::string for the given ProvisioningStatus. +const char* GetProvisioningStatusMessage(ProvisioningStatus status); + +} // namespace widevine + +#endif // PROVISIONING_SDK_PUBLIC_PROVISIONING_STATUS_H_ diff --git a/provisioning_sdk/public/python/base.i b/provisioning_sdk/public/python/base.i new file mode 100644 index 0000000..c2a03d3 --- /dev/null +++ b/provisioning_sdk/public/python/base.i @@ -0,0 +1,19 @@ +%include "std_string.i" +%include "typemaps.i" + +%define %ignoreall %ignore ""; %enddef +%define %unignore %rename("%s") %enddef +%define %unignoreall %rename("%s") ""; %enddef + +%define COPY_TYPEMAPS(oldtype, newtype) +typedef oldtype newtype; +%apply oldtype * OUTPUT { newtype * OUTPUT }; +%apply oldtype & OUTPUT { newtype & OUTPUT }; +%apply oldtype * INPUT { newtype * INPUT }; +%apply oldtype & INPUT { newtype & INPUT }; +%apply oldtype * INOUT { newtype * INOUT }; +%apply oldtype & INOUT { newtype & INOUT }; +%enddef + +COPY_TYPEMAPS(int, int32); +COPY_TYPEMAPS(unsigned int, uint32); diff --git a/provisioning_sdk/public/python/certificate_type.i b/provisioning_sdk/public/python/certificate_type.i new file mode 100644 index 0000000..1f70ec4 --- /dev/null +++ b/provisioning_sdk/public/python/certificate_type.i @@ -0,0 +1,20 @@ +// Swig file to generate a Python library for: +// provisioning_sdk/public/certificate_type.h + +%module pywrapcertificate_type + +%include "base.i" + +%{ +#include "provisioning_sdk/public/certificate_type.h" +%} + +%ignoreall + +%unignore widevine; +%unignore widevine::CertificateType; +%unignore widevine::kCertDevelopment; + +%include "provisioning_sdk/public/certificate_type.h" + +%unignoreall 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..e7df25e --- /dev/null +++ b/provisioning_sdk/public/python/drm_intermediate_certificate_test.py @@ -0,0 +1,46 @@ +import unittest + +import pywrapprovisioning_engine +import pywrapprovisioning_status +import test_data_utility + + +class AddDrmIntermediateTest(unittest.TestCase): + + def setUp(self): + self._engine = pywrapprovisioning_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(pywrapprovisioning_status.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(pywrapprovisioning_status.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(pywrapprovisioning_status.UNKNOWN_SYSTEM_ID, add_ca_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..a5d7454 --- /dev/null +++ b/provisioning_sdk/public/python/init_engine_test.py @@ -0,0 +1,142 @@ +import unittest + +import pywrapcertificate_type +import pywrapprovisioning_engine +import pywrapprovisioning_status +import test_data_utility + + +class InitEngineTest(unittest.TestCase): + + def setUp(self): + self._engine = pywrapprovisioning_engine.ProvisioningEngine() + + def testInitEngineSucceed(self): + test_data_utility.InitProvisionEngineWithTestData( + self._engine, verify_success=True) + + def testSetCertificateStatusListWithoutInit(self): + status = self._engine.SetCertificateStatusList('CERTIFICATE_STATUS_LIST', + 3600) + self.assertEqual( + pywrapprovisioning_status.PROVISIONING_ENGINE_UNINITIALIZED, status) + + def testGenerateDrmIntermediateCertificateWithoutInit(self): + status, _ = self._engine.GenerateDrmIntermediateCertificate( + 100, 'INTERMEDIATE_PUBLIC_KEY') + self.assertEqual( + pywrapprovisioning_status.PROVISIONING_ENGINE_UNINITIALIZED, status) + + def testAddDrmIntermediateCertificateWithoutInit(self): + status = self._engine.AddDrmIntermediateCertificate( + 'INTERMEDIATE_CERTIFICATE', 'INTERMEDIATE_PRIVATE_KEY', + 'INTERMEDIATE_PRIVATE_KEY_PASSPHRASE') + self.assertEqual( + pywrapprovisioning_status.PROVISIONING_ENGINE_UNINITIALIZED, status) + + def testGenerateDeviceDrmCertificateWithoutInit(self): + status, _ = self._engine.GenerateDeviceDrmCertificate( + 100, 'DEVICE_PUBLIC_KEY', 'DEVICE_SERIAL_NUMBER') + self.assertEqual( + pywrapprovisioning_status.PROVISIONING_ENGINE_UNINITIALIZED, status) + + def testNewProvisioningSessionWithoutInit(self): + status, session = self._engine.NewProvisioningSession('DEVICE_PUBLIC_KEY', + 'DEVICE_PRIVATE_KEY') + self.assertEqual( + pywrapprovisioning_status.PROVISIONING_ENGINE_UNINITIALIZED, status) + self.assertIsNone(session) + + def testInitEngineInvalidServiceDrmCert(self): + status = self._engine.Initialize( + pywrapcertificate_type.kCertDevelopment, 'INVALID_CERT', + test_data_utility.SERVICE_PRIVATE_KEY, + test_data_utility.SERVICE_PRIVATE_KEY_PASS, + test_data_utility.PROVISIONING_DRM_CERT, + test_data_utility.PROVISIONING_PRIVATE_KEY, + test_data_utility.PROVISIONING_PRIVATE_KEY_PASS) + self.assertEqual(pywrapprovisioning_status.INVALID_SERVICE_DRM_CERTIFICATE, + status) + + def testInitEngineInvalidServicePrivateKey(self): + status = self._engine.Initialize( + pywrapcertificate_type.kCertDevelopment, + test_data_utility.SERVICE_DRM_CERT, 'INVALID_KEY', + test_data_utility.SERVICE_PRIVATE_KEY_PASS, + test_data_utility.PROVISIONING_DRM_CERT, + test_data_utility.PROVISIONING_PRIVATE_KEY, + test_data_utility.PROVISIONING_PRIVATE_KEY_PASS) + self.assertEqual(pywrapprovisioning_status.INVALID_SERVICE_PRIVATE_KEY, + status) + + def testInitEngineWrongServicePrivateKey(self): + status = self._engine.Initialize( + pywrapcertificate_type.kCertDevelopment, + test_data_utility.SERVICE_DRM_CERT, + test_data_utility.PROVISIONING_PRIVATE_KEY, + test_data_utility.SERVICE_PRIVATE_KEY_PASS, + test_data_utility.PROVISIONING_DRM_CERT, + test_data_utility.PROVISIONING_PRIVATE_KEY, + test_data_utility.PROVISIONING_PRIVATE_KEY_PASS) + self.assertEqual(pywrapprovisioning_status.INVALID_SERVICE_PRIVATE_KEY, + status) + + def testInitEngineInvalidServicePrivateKeyPassphrase(self): + status = self._engine.Initialize( + pywrapcertificate_type.kCertDevelopment, + test_data_utility.SERVICE_DRM_CERT, + test_data_utility.SERVICE_PRIVATE_KEY, 'INVALID_PASSPHRASE', + test_data_utility.PROVISIONING_DRM_CERT, + test_data_utility.PROVISIONING_PRIVATE_KEY, + test_data_utility.PROVISIONING_PRIVATE_KEY_PASS) + self.assertEqual(pywrapprovisioning_status.INVALID_SERVICE_PRIVATE_KEY, + status) + + def testInitEngineInvalidDrmCert(self): + status = self._engine.Initialize( + pywrapcertificate_type.kCertDevelopment, + test_data_utility.SERVICE_DRM_CERT, + test_data_utility.SERVICE_PRIVATE_KEY, + test_data_utility.SERVICE_PRIVATE_KEY_PASS, 'INVALID_CERT', + test_data_utility.PROVISIONING_PRIVATE_KEY, + test_data_utility.PROVISIONING_PRIVATE_KEY_PASS) + self.assertEqual( + pywrapprovisioning_status.INVALID_PROVISIONING_DRM_CERTIFICATE, status) + + def testInitEngineInvalidDrmPrivateKey(self): + status = self._engine.Initialize( + pywrapcertificate_type.kCertDevelopment, + test_data_utility.SERVICE_DRM_CERT, + test_data_utility.SERVICE_PRIVATE_KEY, + test_data_utility.SERVICE_PRIVATE_KEY_PASS, + test_data_utility.PROVISIONING_DRM_CERT, 'INVALID_KEY', + test_data_utility.PROVISIONING_PRIVATE_KEY_PASS) + self.assertEqual(pywrapprovisioning_status.INVALID_PROVISIONING_PRIVATE_KEY, + status) + + def testInitEngineWrongDrmPrivateKey(self): + status = self._engine.Initialize( + pywrapcertificate_type.kCertDevelopment, + test_data_utility.SERVICE_DRM_CERT, + test_data_utility.SERVICE_PRIVATE_KEY, + test_data_utility.SERVICE_PRIVATE_KEY_PASS, + test_data_utility.PROVISIONING_DRM_CERT, + test_data_utility.SERVICE_PRIVATE_KEY, + test_data_utility.PROVISIONING_PRIVATE_KEY_PASS) + self.assertEqual(pywrapprovisioning_status.INVALID_PROVISIONING_PRIVATE_KEY, + status) + + def testInitEngineInvalidDrmPrivateKeyPassphrase(self): + status = self._engine.Initialize(pywrapcertificate_type.kCertDevelopment, + test_data_utility.SERVICE_DRM_CERT, + test_data_utility.SERVICE_PRIVATE_KEY, + test_data_utility.SERVICE_PRIVATE_KEY_PASS, + test_data_utility.PROVISIONING_DRM_CERT, + test_data_utility.PROVISIONING_PRIVATE_KEY, + 'INVALID_PASSPHRASE') + self.assertEqual(pywrapprovisioning_status.INVALID_PROVISIONING_PRIVATE_KEY, + 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..f8eab05 --- /dev/null +++ b/provisioning_sdk/public/python/new_session_test.py @@ -0,0 +1,107 @@ +import unittest + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.serialization import load_der_public_key + +import pywrapprovisioning_engine +import pywrapprovisioning_status +import test_data_utility +from protos.public import certificate_provisioning_pb2 +from protos.public import signed_device_certificate_pb2 + + +class NewSessionTest(unittest.TestCase): + + def setUp(self): + self._engine = pywrapprovisioning_engine.ProvisioningEngine() + test_data_utility.InitProvisionEngineWithTestData( + self._engine, verify_success=True) + test_data_utility.SetCertificateStatusListWithTestData( + self._engine, 0, verify_success=True) + + 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(test_data_utility.MESSAGE) + test_data_utility.AssertSuccess(status, 'Failed to create session.') + + signed_request = test_data_utility.ConvertToSignedProvisioningMessage( + test_data_utility.MESSAGE) + + unsigned_request = certificate_provisioning_pb2.ProvisioningRequest() + unsigned_request.ParseFromString(signed_request.message) + + signed_response = test_data_utility.ConvertToSignedProvisioningMessage( + raw_response) + + self._VerifyMessageSignature(test_data_utility.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(pywrapprovisioning_status.INVALID_REQUEST_MESSAGE, status) + + def testNewSessionWithoutIntermediateCert(self): + (_, new_session) = test_data_utility.NewProvisioningSessionWithTestData( + self._engine, verify_success=True) + (status, _, _) = new_session.ProcessMessage(test_data_utility.MESSAGE) + self.assertEqual(pywrapprovisioning_status.MISSING_DRM_INTERMEDIATE_CERT, + status) + + def testNewSessionInvalidDevicePublicKey(self): + test_data_utility.AddDrmIntermediateCertificateWithTestData( + self._engine, 2001, verify_success=True) + (session_status, _) = self._engine.NewProvisioningSession( + 'INVALID_PUBLIC_KEY', test_data_utility.DEVICE_PRIVATE_KEY) + self.assertEqual(pywrapprovisioning_status.INVALID_DEVICE_PUBLIC_KEY, + session_status) + + def testNewSessionInvalidDevicePrivateKey(self): + test_data_utility.AddDrmIntermediateCertificateWithTestData( + self._engine, 2001, verify_success=True) + (session_status, _) = self._engine.NewProvisioningSession( + test_data_utility.DEVICE_PUBLIC_KEY, 'INVALID_PRIVATE_KEY') + self.assertEqual(pywrapprovisioning_status.INVALID_DEVICE_PRIVATE_KEY, + session_status) + + def _VerifyMessageSignature(self, public_key, signed_response): + self._VerifySignature(public_key, signed_response.signature, + signed_response.message) + + def _VerifyCertSignature(self, public_key, signed_cert): + self._VerifySignature(public_key, signed_cert.signature, + signed_cert.drm_certificate) + + def _VerifyProvisioningResponse(self, request, response): + self.assertEqual(request.nonce, response.nonce) + + signed_cert = signed_device_certificate_pb2.SignedDrmDeviceCertificate() + signed_cert.ParseFromString(response.device_certificate) + + self._VerifyCertSignature(test_data_utility.CA_PUBLIC_KEY, signed_cert) + + def _VerifySignature(self, public_key, signature, data): + key = load_der_public_key(public_key, backend=default_backend()) + key.verify(signature, data, + padding.PSS( + padding.MGF1(test_data_utility.HASH_ALGORITHM), + test_data_utility.SALT_LEN), + test_data_utility.HASH_ALGORITHM) + + +if __name__ == '__main__': + unittest.main() diff --git a/provisioning_sdk/public/python/provisioning_engine.i b/provisioning_sdk/public/python/provisioning_engine.i new file mode 100644 index 0000000..6b0bfdc --- /dev/null +++ b/provisioning_sdk/public/python/provisioning_engine.i @@ -0,0 +1,41 @@ +// Swig file to generate a Python library for: +// provisioning_sdk/public/provisioning_engine.h + +%module pywrapprovisioning_engine + +%include "base.i" +%include "unique_ptr.i" +%import(module="pywrapprovisioning_session") "provisioning_sdk/public/python/provisioning_session.i" + +UNIQUE_PTR_ARGOUT(widevine::ProvisioningSession, new_session); + +%apply int { CertificateType, ProvisioningStatus }; +%apply int32 { int32_t }; +%apply uint32 { uint32_t }; + +%apply std::string* OUTPUT { std::string* certificate }; + +%{ +#include "provisioning_sdk/public/provisioning_engine.h" +#include "provisioning_sdk/public/provisioning_session.h" +using namespace widevine; +%} + +%ignoreall + +%unignore widevine; +%unignore widevine::ProvisioningSession; + +%unignore widevine::ProvisioningEngine; +%unignore widevine::ProvisioningEngine::ProvisioningEngine; +%unignore widevine::ProvisioningEngine::~ProvisioningEngine; +%unignore widevine::ProvisioningEngine::SetCertificateStatusList; +%unignore widevine::ProvisioningEngine::Initialize; +%unignore widevine::ProvisioningEngine::GenerateDrmIntermediateCertificate; +%unignore widevine::ProvisioningEngine::AddDrmIntermediateCertificate; +%unignore widevine::ProvisioningEngine::NewProvisioningSession; +%unignore widevine::ProvisioningEngine::GenerateDeviceDrmCertificate; + +%include "provisioning_sdk/public/provisioning_engine.h" + +%unignoreall diff --git a/provisioning_sdk/public/python/provisioning_session.i b/provisioning_sdk/public/python/provisioning_session.i new file mode 100644 index 0000000..06d40ce --- /dev/null +++ b/provisioning_sdk/public/python/provisioning_session.i @@ -0,0 +1,29 @@ +// Swig file to generate a Python library for: +// provisioning_sdk/public/provisioning_session.h + +%module pywrapprovisioning_session + +%include "base.i" + +%apply bool* OUTPUT { bool* done }; + +%apply int { ProvisioningStatus }; + +%apply std::string* OUTPUT { std::string* response }; + +%{ +#include "provisioning_sdk/public/provisioning_session.h" +using namespace widevine; +%} + +%ignoreall + +%unignore widevine; +%unignore widevine::ProvisioningSession; +%unignore widevine::ProvisioningSession::~ProvisioningSession; +%unignore widevine::ProvisioningSession::ProcessMessage; + + +%include "provisioning_sdk/public/provisioning_session.h" + +%unignoreall diff --git a/provisioning_sdk/public/python/provisioning_status.i b/provisioning_sdk/public/python/provisioning_status.i new file mode 100644 index 0000000..e110d28 --- /dev/null +++ b/provisioning_sdk/public/python/provisioning_status.i @@ -0,0 +1,34 @@ +// Swig file to generate a Python library for: +// provisioning_sdk/public/provisioning_status.h + +%module pywrapprovisioning_status + +%include "base.i" + +%{ +#include "provisioning_sdk/public/provisioning_status.h" +%} + +%ignoreall + +%unignore widevine; +%unignore widevine::ProvisioningStatus; +%unignore widevine::OK; +%unignore widevine::PROVISIONING_ENGINE_UNINITIALIZED; +%unignore widevine::INVALID_SERVICE_DRM_CERTIFICATE; +%unignore widevine::INVALID_SERVICE_PRIVATE_KEY; +%unignore widevine::INVALID_PROVISIONING_DRM_CERTIFICATE; +%unignore widevine::INVALID_PROVISIONING_PRIVATE_KEY; +%unignore widevine::INVALID_STATUS_LIST; +%unignore widevine::STATUS_LIST_EXPIRED; +%unignore widevine::UNKNOWN_SYSTEM_ID; +%unignore widevine::INVALID_DEVICE_PUBLIC_KEY; +%unignore widevine::INVALID_DEVICE_PRIVATE_KEY; +%unignore widevine::INVALID_REQUEST_MESSAGE; +%unignore widevine::MISSING_DRM_INTERMEDIATE_CERT; +%unignore widevine::DEVICE_REVOKED; +%unignore widevine::GetProvisioningStatusMessage; + +%include "provisioning_sdk/public/provisioning_status.h" + +%unignoreall 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..3a4e048 --- /dev/null +++ b/provisioning_sdk/public/python/set_certificate_status_list_test.py @@ -0,0 +1,27 @@ +import unittest + +import pywrapprovisioning_engine +import pywrapprovisioning_status +import test_data_utility + + +class SetCertificateStatusListTest(unittest.TestCase): + + def setUp(self): + self._engine = pywrapprovisioning_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(pywrapprovisioning_status.INVALID_STATUS_LIST, + set_cert_status_list) + + +if __name__ == '__main__': + unittest.main() diff --git a/provisioning_sdk/public/python/setup.py b/provisioning_sdk/public/python/setup.py new file mode 100644 index 0000000..84b2ed9 --- /dev/null +++ b/provisioning_sdk/public/python/setup.py @@ -0,0 +1,55 @@ +"""setup script to build Python wrappers using swig configurations.""" + +import os + +from distutils.core import Extension +from distutils.core import setup + +OUT_DIRNAME = 'test_genfiles' + + +def GetSdkRootDir(): + """Obtains folder containing |OUT_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, OUT_DIRNAME)): + current_dir = os.path.dirname(current_dir) + return current_dir + + +SDK_ROOT_DIR = GetSdkRootDir() + +SWIG_CONFIG_FILE = os.path.join(SDK_ROOT_DIR, OUT_DIRNAME, '%s.i') +SWIG_CONFIG_MODULE_PATH = OUT_DIRNAME + '.%s' + +SDK_LIBRARY_DIR = os.path.join(SDK_ROOT_DIR, 'bazel-bin', 'provisioning_sdk', + 'public') + + +def ProvisioningSwigExtension(extension_name): + return Extension( + name=SWIG_CONFIG_MODULE_PATH % ('_pywrap' + extension_name), + sources=[SWIG_CONFIG_FILE % extension_name], + include_dirs=[SDK_ROOT_DIR], + swig_opts=['-c++'], + library_dirs=[SDK_ROOT_DIR, SDK_LIBRARY_DIR], + runtime_library_dirs=[SDK_ROOT_DIR, SDK_LIBRARY_DIR], + libraries=['provisioning_sdk'], + extra_compile_args=['-std=c++11']) + + +if __name__ == '__main__': + os.chdir(SDK_ROOT_DIR) + setup( + name='provisioning_sdk', + ext_modules=[ + ProvisioningSwigExtension('certificate_type'), + ProvisioningSwigExtension('provisioning_status'), + ProvisioningSwigExtension('provisioning_session'), + ProvisioningSwigExtension('provisioning_engine') + ], + py_modules=[ + SWIG_CONFIG_MODULE_PATH % 'pywrapcertificate_type', + SWIG_CONFIG_MODULE_PATH % 'pywarpprovisioning_status', + SWIG_CONFIG_MODULE_PATH % 'pywrapprovisioning_session', + SWIG_CONFIG_MODULE_PATH % 'pywrapprovisioning_engine' + ]) 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..89f9124 --- /dev/null +++ b/provisioning_sdk/public/python/test_data_utility.py @@ -0,0 +1,146 @@ +"""Utility class for Provisioning SDK testing.""" + +import os + +from cryptography.hazmat.primitives import hashes + +import pywrapcertificate_type +import pywrapprovisioning_status +from protos.public import certificate_provisioning_pb2 + +HASH_ALGORITHM = hashes.SHA1() +SALT_LEN = 20 + +TEST_DATA_FOLDER = os.path.join('example', 'example_data') + + +def GetTestData(filename): + current_dir = os.path.realpath(os.path.dirname(__file__)) + while not os.path.isdir(os.path.join(current_dir, TEST_DATA_FOLDER)): + current_dir = os.path.dirname(current_dir) + filename = os.path.join(current_dir, TEST_DATA_FOLDER, filename) + with open(filename, 'rb') as data_file: + data = data_file.read() + return data + + +SERVICE_DRM_CERT = GetTestData('service.cert') +SERVICE_PUBLIC_KEY = GetTestData('service.public') +SERVICE_PRIVATE_KEY = GetTestData('service.encrypted.private') +SERVICE_PRIVATE_KEY_PASS = GetTestData('service.passphrase') +PROVISIONING_DRM_CERT = GetTestData('provider.cert') +PROVISIONING_PRIVATE_KEY = GetTestData('provider.encrypted.private') +PROVISIONING_PRIVATE_KEY_PASS = GetTestData('provider.passphrase') +CA_PUBLIC_KEY = GetTestData('intermediate.public') +DEVICE_PUBLIC_KEY = GetTestData('user.public') +DEVICE_PRIVATE_KEY = GetTestData('user.private') +MESSAGE = GetTestData('message') + + +def InitProvisionEngineWithTestData(engine, verify_success=False): + """Initialize the provisioning engine with sample credentials. + + Args: + engine: a pywrapprovisioning_engine.ProvisioningEngine instance + verify_success: whether to verify that resulting status code equals OK + + Returns: + OK on success, or an appropriate error status code otherwise. + """ + status = engine.Initialize(pywrapcertificate_type.kCertDevelopment, + SERVICE_DRM_CERT, SERVICE_PRIVATE_KEY, + SERVICE_PRIVATE_KEY_PASS, PROVISIONING_DRM_CERT, + PROVISIONING_PRIVATE_KEY, + PROVISIONING_PRIVATE_KEY_PASS) + if verify_success: + AssertSuccess(status, 'Failed to initialize.') + return status + + +def SetCertificateStatusListWithTestData(engine, + expiration_period_seconds, + verify_success=False): + """Set the certificate status list with sample certificate status list. + + Args: + engine: a pywrapprovisioning_engine.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 + + Returns: + OK on success, or an appropriate error status code otherwise. + """ + certificate_status_list = GetTestData('certificate_list') + + status = engine.SetCertificateStatusList(certificate_status_list, + expiration_period_seconds) + + if verify_success: + AssertSuccess(status, 'Failed to set certificate status list.') + + return status + + +def AddDrmIntermediateCertificateWithTestData(engine, + system_id, + verify_success=False): + """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 pywrapprovisioning_engine.ProvisioningEngine instance + system_id: Widevine system ID for the type of device + verify_success: whether to verify that resulting status code equals OK + + Returns: + OK on success, or an appropriate error status code otherwise. + """ + ca_private_key = GetTestData('intermediate.encrypted.private') + ca_private_key_passphrase = GetTestData('intermediate.passphrase') + + gen_status, ca_certificate = engine.GenerateDrmIntermediateCertificate( + system_id, CA_PUBLIC_KEY) + AssertSuccess(gen_status, 'Failed to generate intermediate certificate.') + + add_ca_status = engine.AddDrmIntermediateCertificate( + ca_certificate, ca_private_key, ca_private_key_passphrase) + + if verify_success: + AssertSuccess(add_ca_status, 'Failed to add intermediate certificate.') + + return add_ca_status + + +def NewProvisioningSessionWithTestData(engine, verify_success=False): + """Create a provisioning session with sample device public and private keys. + + Args: + engine: a pywrapprovisioning_engine.ProvisioningEngine instance + verify_success: whether to verify that resulting status code equals OK + + Returns: + status: OK on success, or an appropriate error status code otherwise. + new_session: A new provisioning_session. + """ + status, new_session = engine.NewProvisioningSession(DEVICE_PUBLIC_KEY, + DEVICE_PRIVATE_KEY) + + if verify_success: + AssertSuccess(status, 'Failed to create session.') + + return (status, new_session) + + +def AssertSuccess(status, message=None): + """Assert status equals OK.""" + assert pywrapprovisioning_status.OK == status, message + + +def ConvertToSignedProvisioningMessage(serialized_message): + signed_message = certificate_provisioning_pb2.SignedProvisioningMessage() + signed_message.ParseFromString(serialized_message) + return signed_message diff --git a/provisioning_sdk/public/python/unique_ptr.i b/provisioning_sdk/public/python/unique_ptr.i new file mode 100644 index 0000000..624ea1a --- /dev/null +++ b/provisioning_sdk/public/python/unique_ptr.i @@ -0,0 +1,43 @@ +namespace std { + template class unique_ptr {}; +} + +%define _UNIQUE_PTR_TEMPLATE(type) +template <> class std::unique_ptr {}; +%enddef + +%define UNIQUE_PTR(type) +_UNIQUE_PTR_TEMPLATE(type); + +%typemap(out) std::unique_ptr %{ + $result = SWIG_NewPointerObj( + SWIG_as_voidptr($1.release()), $descriptor(type*), SWIG_POINTER_OWN); +%} +%enddef + +%define UNIQUE_PTR_WITH_ERROR(type, err_str) +_UNIQUE_PTR_TEMPLATE(type); + +%typemap(out) std::unique_ptr %{ + if ($1) { + $result = SWIG_NewPointerObj( + SWIG_as_voidptr($1.release()), $descriptor(type*), SWIG_POINTER_OWN); + } else { + SWIG_exception(SWIG_ValueError, err_str); + } +%} +%enddef + +%define UNIQUE_PTR_ARGOUT(type, arg_name) +_UNIQUE_PTR_TEMPLATE(type) + +%typemap(in, numinputs=0) std::unique_ptr* arg_name + (std::unique_ptr temp) %{ + $1 = &temp; +%} + +%typemap(argout) std::unique_ptr* arg_name %{ + %append_output(SWIG_NewPointerObj(SWIG_as_voidptr($1->release()), + $descriptor(type*), SWIG_POINTER_OWN)); +%} +%enddef diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..6cf2211 --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# +# 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 +# - swig: http://www.i.org/download.html + +set -e + +hash protoc 2>/dev/null || { echo >&2 "protobuf is required but not installed. Aborting."; exit 1; } + +cd "$(dirname "$0")" + +rm -rf test_genfiles +mkdir test_genfiles + +protoc -I="$(pwd)" --python_out="$(pwd)/test_genfiles" "$(pwd)/protos/public/client_identification.proto" +protoc -I="$(pwd)" --python_out="$(pwd)/test_genfiles" "$(pwd)/protos/public/provisioned_device_info.proto" +protoc -I="$(pwd)" --python_out="$(pwd)/test_genfiles" "$(pwd)/protos/public/certificate_provisioning.proto" +protoc -I="$(pwd)" --python_out="$(pwd)/test_genfiles" "$(pwd)/protos/public/signed_device_certificate.proto" + +cp provisioning_sdk/public/python/* test_genfiles/ +cd test_genfiles +python setup.py build_ext --inplace + +shopt -s globstar +for d in "protos"/**/; do + touch -- "$d/__init__.py"; +done; + +python init_engine_test.py +python set_certificate_status_list_test.py +python drm_intermediate_certificate_test.py +python new_session_test.py