diff --git a/tools/published_devices_client/WORKSPACE b/tools/published_devices_client/WORKSPACE new file mode 100644 index 0000000..c09fe7a --- /dev/null +++ b/tools/published_devices_client/WORKSPACE @@ -0,0 +1,369 @@ +# JTS Framework WORKSPACE. + +workspace(name = "jts") +load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository", "git_repository") +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +# In Bazel 2.0, Maven rules provided via new rules_jvm_external. +RULES_JVM_EXTERNAL_TAG = "3.1" +RULES_JVM_EXTERNAL_SHA = "e246373de2353f3d34d35814947aa8b7d0dd1a58c2f7a6c41cfeaff3007c2d14" +http_archive( + name = "rules_jvm_external", + strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, + sha256 = RULES_JVM_EXTERNAL_SHA, + url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG, +) +load("@rules_jvm_external//:defs.bzl", "maven_install") + +# Google Guice. +maven_install( + name = "google_guice", + artifacts = ["com.google.inject:guice:4.2.0"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# Appache core framework. +maven_install( + name = "apache_httpcore", + artifacts = ["org.apache.httpcomponents:httpcore:4.4.10"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# Google APIs client library +maven_install( + name = "google_api", + artifacts = ["com.google.api-client:google-api-client:1.30.2"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# Google HTTP Client +maven_install( + name = "google_http_client", + artifacts = ["com.google.http-client:google-http-client:1.30.2"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# Google HTTP Client Jackson2 Extensions +maven_install( + name = "jackson2", + artifacts = ["com.google.http-client:google-http-client-jackson2:1.31.0"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +maven_install( + name = "jackson_core", + artifacts = ["com.fasterxml.jackson.core:jackson-core:2.10.0.pr2"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# Google Oauth2 API +maven_install( + name = "google_oauth2", + artifacts = ["com.google.oauth-client:google-oauth-client:1.30.1"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# Google Java common framework. +maven_install( + name = "google_guava", + artifacts = ["com.google.guava:guava:25.1-jre"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# GRPC +git_repository( + name = "io_grpc_grpc_java", + remote = "https://github.com/grpc/grpc-java.git", + tag = "v1.23.0", +) + +# GRPC Core +maven_install( + name = "grpc_core", + artifacts = ["io.grpc:grpc-core:1.23.0"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# GRPC Netty +maven_install( + name = "grpc_netty", + artifacts = ["io.grpc:grpc-netty:1.23.0"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +maven_install( + name = "grpc_netty_all", + artifacts = ["io.netty:netty-all:4.1.41.Final"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# GRPC Stub +maven_install( + name = "grpc_stub", + artifacts = ["io.grpc:grpc-stub:1.23.0"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# GRPC Contenxt +maven_install( + name = "grpc_context", + artifacts = ["io.grpc:grpc-context:1.18.0"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# GSON +maven_install( + name = "gson", + artifacts = ["com.google.code.gson:gson:2.8.6"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# Netty TcNative (for GRPC) +maven_install( + name = "netty_tcnative", + artifacts = ["io.netty:netty-tcnative:2.0.25.Final"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# Netty BoringSSL +maven_install( + name = "netty_boringssl", + artifacts = ["io.netty:netty-tcnative-boringssl-static:2.0.25.Final"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# Open Census (for GRPC) +maven_install( + name = "open_census_api", + artifacts = ["io.opencensus:opencensus-api:0.24.0"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +maven_install( + name = "open_census", + artifacts = ["io.opencensus:opencensus-contrib-http-jetty-client:0.19.0"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +maven_install( + name = "open_census_util", + artifacts = ["io.opencensus:opencensus-contrib-http-util:0.19.0"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +maven_install( + name = "open_census_grpc_metrics", + artifacts = ["io.opencensus:opencensus-contrib-grpc-metrics:0.24.0"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# Perfmark (GRPC) +maven_install( + name = "io_perfmark_api", + artifacts = ["io.perfmark:perfmark-api:0.17.0"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# Google Protobuf Protos +PROTOBUF_BUILD_FILE = """ +package(default_visibility = ["//visibility:public"]) + +PROTO_FILES = [ + "google/protobuf/descriptor.proto", +] + +filegroup( + name = "protobuf_files", + srcs = PROTO_FILES, + visibility = ["//visibility:public"], +) + +proto_library( + name = "protobuf_protos", + srcs = [":protobuf_files"], + visibility = ["//visibility:public"], + +) +""" + +git_repository( + name = "com_github_googleapis_googleapis", + remote = "https://github.com/googleapis/googleapis.git", + branch = "master", +) +load("@com_github_googleapis_googleapis//:repository_rules.bzl", "switched_rules_by_language") +switched_rules_by_language( + name = "com_google_googleapis_imports", + java = True) + +http_archive( + name = "common_protos", + build_file_content = PROTOBUF_BUILD_FILE, + strip_prefix = "protobuf-3.9.1/src", + urls = [ + "https://github.com/protocolbuffers/protobuf/releases/download/v3.9.1/protobuf-all-3.9.1.tar.gz", + ], +) + +# Java commandline framework. +maven_install( + name = "jcommander", + artifacts = ["com.beust:jcommander:1.72"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +#Javax Inject (for Guice). +maven_install( + name = "javax_inject", + artifacts = ["javax.inject:javax.inject:1"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# JSON Java. +maven_install( + name = "json", + artifacts = ["org.json:json:20090211"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# JSR305 Annotations. +maven_install( + name = "jsr305_annotations", + artifacts = ["com.google.code.findbugs:jsr305:3.0.2"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# AOP (for Guice). +maven_install( + name = "aopalliance", + artifacts = ["aopalliance:aopalliance:1.0"], + repositories = [ + "https://jcenter.bintray.com", + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) + +# Protocol Buffer compiler. +git_repository( + name = "com_google_protobuf", + remote = "https://github.com/google/protobuf.git", + tag = "v3.9.1", +) + +bind( + name = "protobuf", + actual = "@com_google_protobuf//:protobuf", +) + +bind( + name = "protobuf_java", + actual = "@com_google_protobuf//:protobuf_java", +) + +# Loads necessary bazel rules for later consumption. +load("@io_grpc_grpc_java//:repositories.bzl", "grpc_java_repositories") +grpc_java_repositories() + +load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") +protobuf_deps() diff --git a/tools/published_devices_client/google/chrome/widevine/contentpartners/v1beta1/BUILD b/tools/published_devices_client/google/chrome/widevine/contentpartners/v1beta1/BUILD new file mode 100644 index 0000000..4175520 --- /dev/null +++ b/tools/published_devices_client/google/chrome/widevine/contentpartners/v1beta1/BUILD @@ -0,0 +1,40 @@ +# Copyright 2019 Google LLC. All rights reserved. + +# Published Devices protocol buffer definitions for the +# Widevine Published Devices One Platform API service. + +package(default_visibility = ["//visibility:public"]) + +load("@io_grpc_grpc_java//:java_grpc_library.bzl", "java_grpc_library") + +proto_library( + name = "published_devices_proto", + srcs = ["published_devices.proto"], + deps = [ + ":device_security_profiles_proto", + "@com_github_googleapis_googleapis//google/api:annotations_proto", + "@com_github_googleapis_googleapis//google/api:field_behavior_proto", + ], +) + +proto_library( + name = "device_security_profiles_proto", + srcs = ["device_security_profiles.proto"], + deps = [ + "@com_github_googleapis_googleapis//google/api:field_behavior_proto", + ], +) + +java_proto_library( + name = "published_devices_java_proto", + deps = [":published_devices_proto"], + +) + +java_grpc_library( + name = "published_devices_grpc", + srcs = [":published_devices_proto"], + deps = [ + ":published_devices_java_proto", + ], +) diff --git a/tools/published_devices_client/google/chrome/widevine/contentpartners/v1beta1/device_security_profiles.proto b/tools/published_devices_client/google/chrome/widevine/contentpartners/v1beta1/device_security_profiles.proto new file mode 100644 index 0000000..83abdc2 --- /dev/null +++ b/tools/published_devices_client/google/chrome/widevine/contentpartners/v1beta1/device_security_profiles.proto @@ -0,0 +1,94 @@ +// This file contains the proto definitions of DeviceSecurityProfile needed for +// the PublishedDevicesService. + +syntax = "proto3"; + +package google.chrome.widevine.contentpartners.v1beta1; + +import "google/api/field_behavior.proto"; + +option java_multiple_files = true; +option java_outer_classname = "DeviceSecurityProfilesProto"; +option java_package = "com.google.chrome.widevine.contentpartners.v1beta1"; + +// The DeviceSecurityProfileCriteria is produced by the client (a Widevine +// SDK). It is serialized and embedded in the +// ListDeviceSecurityProfilesRequest. +message DeviceSecurityProfileCriteria { + // Content provider who wants to get the DSPs. Required. + string content_provider = 1 [(google.api.field_behavior) = REQUIRED]; + // Content owners who own the DSPs. If this field is empty, return all + // DSPs that the content provider is included. Otherwise, a list of DSPs + // owned by the listed content_owners would be filtered for the requested + // provider. Optional field. + repeated string content_owners = 2 [(google.api.field_behavior) = OPTIONAL]; +} + +// A request sent to Widevine Published Server to retrieve +// the DeviceSecurityProfiles. +// (-- api-linter: core::0132::request-unknown-fields=disabled +// api-linter: core::0132::request-parent-required=disabled +// api-linter: core::0158::request-page-token-field=disabled +// api-linter: core::0158::request-page-size-field=disabled +// aip.dev/not-precedent: This is a non-standard, non-precedent API that +// cannot use pagination. It has no parent, page_size and page_token. +// Pagination is not appropriate. --) +message ListDeviceSecurityProfilesRequest { + // DeviceSecurityProfileCriteria for List request. Required. + DeviceSecurityProfileCriteria device_security_profile_criteria = 1 + [(google.api.field_behavior) = REQUIRED]; +} + +// A signed message which contains a serialized DeviceSecurityProfileList and +// the signature. +// (--GOOGLE_INTERNAL +// This message is copied from +// video/widevine/protos/public/device_security_profile_list.proto +// --) +message SignedDeviceSecurityProfiles { + // Serialized DeviceSecurityProfileList. Required. + // A device security profile list contains device security profiles + // defined by the content owner. Each DSP aggregates the device's + // capabilities, such as security profile level, minimum output requirements, + // minimum security requirements for this profile. The information is intended + // to be shared publicly. + bytes device_security_profiles = 1 [(google.api.field_behavior) = REQUIRED]; + // Signature of device_security_profiles. Signed with root + // certificate private key using RSASSA-PSS. Required. + bytes signature = 2 [(google.api.field_behavior) = REQUIRED]; + // Optional field that indicates the hash algorithm used in signature scheme. + HashAlgorithm hash_algorithm = 3 [(google.api.field_behavior) = OPTIONAL]; +} + +// A response returned from Widevine Published Server which contains a signed +// DeviceSecurityProfileList message. +// (-- api-linter: core::0132::response-unknown-fields=disabled +// api-linter: core::0158::response-repeated-first-field=disabled +// api-linter: core::0158::response-next-page-token-field=disabled +// aip.dev/not-precedent: This is a non-standard, non-precedent API that +// cannot use pagination. We serialize and sign the list data in a signed +// message, which is used in the response. The first field is not repeated +// and response has no next_page_token field. The +// SignedDeviceSecurityProfiles is intended to be consumed as a single blob. +// Pagination is not appropriate. --) +message ListDeviceSecurityProfilesResponse { + // A signed message which contains a serialized DeviceSecurityProfileList and + // the signature. + SignedDeviceSecurityProfiles signed_device_security_profiles = 1 + [(google.api.field_behavior) = REQUIRED]; +} + +// A representation of a hash algorithm used in signature. +// (--GOOGLE_INTERNAL +// This enum is copied from +// video/widevine/protos/public/hash_algorithm.proto +// --) +enum HashAlgorithm { + // Unspecified hash algorithm: SHA_256 shall be used for ECC based algorithms + // and SHA_1 shall be used otherwise. + HASH_ALGORITHM_UNSPECIFIED = 0; + // Secure Hash Algorithm 1 (SHA-1). + HASH_ALGORITHM_SHA_1 = 1; + // Secure Hash Algorithm 2 256 bits (SHA-256). + HASH_ALGORITHM_SHA_256 = 2; +} diff --git a/tools/published_devices_client/google/chrome/widevine/contentpartners/v1beta1/published_devices.proto b/tools/published_devices_client/google/chrome/widevine/contentpartners/v1beta1/published_devices.proto new file mode 100755 index 0000000..6483e6d --- /dev/null +++ b/tools/published_devices_client/google/chrome/widevine/contentpartners/v1beta1/published_devices.proto @@ -0,0 +1,100 @@ +// This file contains the definitions needed for the PublishedDevicesService. +// The PublishedDevicesService allows clients to retrieve a signed and +// verifiable list of devices and device security profiles that support the +// Widevine Digital Rights Management (DRM) solution. +// +// (--GOOGLE_INTERNAL +// This proto is intended to support a feature of the "Widevine API" where +// external content providers will retrieve device certificate status list +// as well as device secruity profiles to achieve better and more secure +// playback experience. +// +// For more info about the service, please see design doc +// https://docs.google.com/document/d/1ZClc0HJuFlijDrdZxcR_DmNOi6Hqo7bjjEtewLnafas/edit?usp=sharing +// --) + +syntax = "proto3"; + +package google.chrome.widevine.contentpartners.v1beta1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/chrome/widevine/contentpartners/v1beta1/device_security_profiles.proto"; + +option csharp_namespace = "Google.Chrome.Widevine.ContentPartners.V1Beta1"; +option java_multiple_files = true; +option java_outer_classname = "PublishedDevicesProtos"; +option java_package = "com.google.chrome.widevine.contentpartners.v1beta1"; + +// GCWPD is an acronym for Google Chrome Widevine Published Devices. +option objc_class_prefix = "GCWPD"; + + +// This service produces a list of Widevine supported devices +// signed by a certificate that can be verified by the client. +service PublishedDevicesService { + // Return the PublishedDevices containing the list of devices and a signature. + // The request is used to authenticate the client. + rpc GetPublishedDevices(PublishedDevicesRequest) returns (PublishedDevices) { + + option (google.api.http) = { + post: "/v1beta1/publishedDevices:getSignedBatch" + body: "*" + }; + } + + // Return the ListDeviceSecurityProfilesResponse containing a list of + // device security profiles. + rpc ListDeviceSecurityProfiles(ListDeviceSecurityProfilesRequest) + returns (ListDeviceSecurityProfilesResponse) { + + option (google.api.http) = { + get: "/v1beta1/deviceSecurityProfiles:listDeviceSecurityProfiles" + }; + } +} + +// The SdkClientInformation is produced by the client (a Widevine SDK). It is +// serialized and embedded in the PublishedDevicesRequest. It is used to +// declare attributes of the client's SDK including the Widevine-issued service +// certificate that is used by the client. +message SdkClientInformation { + // The version of sdk. Required. + string sdk_version = 1; + // POSIX time, in seconds, when this request was created. Required. + uint64 sdk_time_seconds = 2; + // The serialized service certificate used to sign the request. Required. + bytes service_certificate = 3; +} + +// A signed request sent to Widevine Provisioning Server (keysmith) to retrieve +// the PublishedDevices. +message PublishedDevicesRequest { + // A serialized SdkClientInformation proto. Required. + bytes sdk_client_information = 1; + + // This is the SHA256 digest, PKCS#7 padded signature of the + // sdk_client_information field. This signature uses the + // RSA private key corresponding to the server certificate. Required. + bytes signature = 2; + + // Optional field that indicates the hash algorithm used in signature scheme. + HashAlgorithm hash_algorithm = 3 [(google.api.field_behavior) = OPTIONAL]; +} + +// Contains a serialized DeviceCertificateStatusList and the signature. +message PublishedDevices { + // Serialized DeviceCertificateStatusList. Required. + // A device certificate status list contains information about various + // device series information such as the make, model and status (RELEASED, + // IN_TESTING, REVOKED, etc).The information is intended to be shared + // publicly. + bytes published_devices = 1; + + // Signature of device_certificate_status_list_request. Signed with root + // certificate private key using RSASSA-PSS. Required. + bytes signature = 2; + + // Optional field that indicates the hash algorithm used in signature scheme. + HashAlgorithm hash_algorithm = 3 [(google.api.field_behavior) = OPTIONAL]; +} diff --git a/tools/published_devices_client/httpclient/BUILD b/tools/published_devices_client/httpclient/BUILD new file mode 100644 index 0000000..0051ae5 --- /dev/null +++ b/tools/published_devices_client/httpclient/BUILD @@ -0,0 +1,21 @@ +# Copyright 2019 Google LLC. All rights reserved. +# Desciption: +# JTS http clients. + +package( + default_visibility = ["//visibility:public"], +) + +java_library( + name = "httpclient", + srcs = glob(["*.java"]), + deps = [ + "@google_guice//:com_google_inject_guice", + "@json//:org_json_json", + "@google_api//:com_google_api_client_google_api_client", + "@google_http_client//:com_google_http_client_google_http_client", + "@google_oauth2//:com_google_oauth_client_google_oauth_client", + "@jackson2//:com_google_http_client_google_http_client_jackson2", + "@jackson_core//:com_fasterxml_jackson_core_jackson_core", + ], +) diff --git a/tools/published_devices_client/httpclient/Credentials.java b/tools/published_devices_client/httpclient/Credentials.java new file mode 100644 index 0000000..b11004e --- /dev/null +++ b/tools/published_devices_client/httpclient/Credentials.java @@ -0,0 +1,56 @@ +// Copyright 2019 Google LLC. All rights reserved. +package com.google.video.widevine.jts.httpclient; + +import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * JTS Cloud Credentials. + * + * Provides Oauth2 authentication, and access/refresh tokens. + */ +public class Credentials { + + private static final List SCOPES = Arrays.asList( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/widevine/frontend"); + private static final int MIN_ACCESS_TOKEN_REFRESH_SEC = 1200; + private static final Logger logger = Logger.getLogger(Credentials.class.getName()); + + private String credentialsJsonFile = null; + private GoogleCredential googleCredential = null; + private String accessToken = null; + + /** + * Credentials Constructor. + * + * @param credentialsJsonFile The full path to a GCP service account json credentials file. + */ + public Credentials(String credentialsJsonFile) throws IOException { + this.credentialsJsonFile = credentialsJsonFile; + activateGoogleCredentials(); + } + + private void activateGoogleCredentials() throws IOException { + googleCredential = GoogleCredential + .fromStream(new FileInputStream(credentialsJsonFile)) + .createScoped(SCOPES); + googleCredential.refreshToken(); + } + + /** Provides an Oauth2 Access Token that can be used to make GCP service calls.*/ + public String getAccessToken() throws IOException { + if (accessToken == null + || googleCredential.getExpiresInSeconds() <= MIN_ACCESS_TOKEN_REFRESH_SEC) { + googleCredential.refreshToken(); + accessToken = googleCredential.getAccessToken(); + logger.log(Level.INFO, "Getting new access token."); + } + return accessToken; + } +} diff --git a/tools/published_devices_client/interfaces/BUILD b/tools/published_devices_client/interfaces/BUILD new file mode 100644 index 0000000..4735f66 --- /dev/null +++ b/tools/published_devices_client/interfaces/BUILD @@ -0,0 +1,17 @@ +# Copyright 2018 Google LLC. All rights reserved. +# Desciption: +# JTS interfaces. + +package( + default_visibility = ["//visibility:public"], +) + +java_library( + name = "interfaces", + srcs = glob(["*.java"]), + deps = [ + "//google/chrome/widevine/contentpartners/v1beta1:published_devices_java_proto", + "@com_google_protobuf//:protobuf_java", + "@apache_httpcore//:org_apache_httpcomponents_httpcore", + ], +) diff --git a/tools/published_devices_client/interfaces/DeviceCertificate.java b/tools/published_devices_client/interfaces/DeviceCertificate.java new file mode 100644 index 0000000..ef5ac88 --- /dev/null +++ b/tools/published_devices_client/interfaces/DeviceCertificate.java @@ -0,0 +1,18 @@ +// Copyright 2019 Google LLC. All rights reserved. +package com.google.video.widevine.jts.interfaces; + +import com.google.chrome.widevine.contentpartners.v1beta1.PublishedDevices; + +/** + * DeviceCertificate defines APIs for getting PublishedDevices and Published Devices list. + */ +public interface DeviceCertificate { + + /** + * Get the latest {@code PublishedDevices} containing Published Devices list data. + * + * @return {@code PublishedDevices} containing Published Devices list data. + */ + public PublishedDevices getPublishedDevices() throws Exception; + +} diff --git a/tools/published_devices_client/providers/BUILD b/tools/published_devices_client/providers/BUILD new file mode 100644 index 0000000..89293e4 --- /dev/null +++ b/tools/published_devices_client/providers/BUILD @@ -0,0 +1,37 @@ +# Copyright 2018 Google LLC. All rights reserved. +# Desciption: +# JTS interfaces. + +package( + default_visibility = ["//visibility:public"], +) + +java_library( + name = "providers", + srcs = glob(["*.java"]), + runtime_deps = [ + "@grpc_context//:io_grpc_grpc_context", + "@grpc_netty_all//:io_netty_netty_all", + "@io_perfmark_api//:io_perfmark_perfmark_api", + "@netty_boringssl//:io_netty_netty_tcnative_boringssl_static", + "@open_census//:io_opencensus_opencensus_contrib_http_jetty_client", + "@open_census_api//:io_opencensus_opencensus_api", + "@open_census_grpc_metrics//:io_opencensus_opencensus_contrib_grpc_metrics", + "@open_census_util//:io_opencensus_opencensus_contrib_http_util", + ], + deps = [ + ":libwidevine_license_wvpl_sdk_lib.jar", + "//google/chrome/widevine/contentpartners/v1beta1:published_devices_grpc", + "//google/chrome/widevine/contentpartners/v1beta1:published_devices_java_proto", + "//httpclient", + "//interfaces", + "@google_guice//:com_google_inject_guice", + "@json//:org_json_json", + "@com_google_protobuf//:protobuf_java", + "@google_guava//:com_google_guava_guava", + "@grpc_core//:io_grpc_grpc_core", + "@grpc_netty//:io_grpc_grpc_netty", + "@grpc_stub//:io_grpc_grpc_stub", + "@io_grpc_grpc_java//api", + ], +) diff --git a/tools/published_devices_client/providers/PublishedDevicesProvider.java b/tools/published_devices_client/providers/PublishedDevicesProvider.java new file mode 100644 index 0000000..ff5733d --- /dev/null +++ b/tools/published_devices_client/providers/PublishedDevicesProvider.java @@ -0,0 +1,91 @@ +// Copyright 2020 Google LLC. All rights reserved. + +package com.google.video.widevine.jts.providers; + +import com.google.chrome.widevine.contentpartners.v1beta1.PublishedDevices; +import com.google.chrome.widevine.contentpartners.v1beta1.PublishedDevicesRequest; +import com.google.chrome.widevine.contentpartners.v1beta1.PublishedDevicesServiceGrpc; +import com.google.inject.Inject; +import com.google.protobuf.TextFormat; +import com.google.video.widevine.jts.httpclient.Credentials; +import com.google.video.widevine.jts.interfaces.DeviceCertificate; +import com.google.video.widevine.sdk.wvpl.WvPLBaseEnvironment; +import com.google.video.widevine.sdk.wvpl.WvPLStatusException; +import io.grpc.ManagedChannel; +import io.grpc.Metadata; +import io.grpc.netty.NettyChannelBuilder; +import io.grpc.stub.MetadataUtils; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Provides the latest {@code PublishedDevices} data from the Widevine Published Devices Service. + * + * This implementation uses the Widevine Published Devices API, and support a gRPC method for + * retrieving PublishedDevices with an embedded Published Devices list. + */ +public class PublishedDevicesProvider implements DeviceCertificate { + private static final Logger logger = Logger.getLogger(PublishedDevicesProvider.class.getName()); + private WvPLBaseEnvironment environment = null; + private String apiServicePath = null; + private Credentials credentials = null; + + /** + * PublishedDevicesProvider constructor. + * + * @param environment A WvPLBaseEnvironment object initialized with a Service Certificate. + * @param serviceAccountPath Path to a GCP Service Account json file, used in OAUTH. + * @param apiServicePath Path to a Widevine Published Devices API service. + * @throws IOException upon failure creating OAUTH credentials. + */ + @Inject + public PublishedDevicesProvider( + WvPLBaseEnvironment environment, String serviceAccountPath, String apiServicePath) + throws IOException { + this.environment = environment; + this.apiServicePath = apiServicePath; + credentials = new Credentials(serviceAccountPath); + } + + /** + * Get the latest {@code PublishedDevices} from the Widevine Published Devices Service. + * + * @return The latest {@code PublishedDevices} data. + * @throws InterruptedException upon RPC failure. + * @throws WvPLStatusException upon WvPLBaseEnvironment errors. + */ + @Override + public PublishedDevices getPublishedDevices() throws InterruptedException, WvPLStatusException{ + ManagedChannel channel = null; + PublishedDevices devicesResponse = null; + + logger.log(Level.INFO, "Getting PublishedDevices..."); + try { + channel = createRpcChannel(apiServicePath); + Metadata metadata = new Metadata(); + String token = "Bearer " + credentials.getAccessToken(); + metadata.put(Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER), token); + PublishedDevicesServiceGrpc.PublishedDevicesServiceBlockingStub blockingStub = + PublishedDevicesServiceGrpc.newBlockingStub(channel) + .withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata)); + devicesResponse = blockingStub.getPublishedDevices(PublishedDevicesRequest.parseFrom( + environment.generateDeviceStatusListRequest())); + logger.log(Level.INFO, "GRPC Call to PublishedDevicesService.GetSignedList returned:\n %s" + + TextFormat.printer().printToString(devicesResponse)); + } catch (IOException e) { + logger.log(Level.INFO, "IOException encountered trying to retrieve the signed list: " + e); + } finally { + if (channel != null) { + channel.shutdown(); + channel.awaitTermination(1, TimeUnit.SECONDS); + } + } + return devicesResponse; + } + + private static ManagedChannel createRpcChannel(String host) { + return NettyChannelBuilder.forTarget(host).build(); + } +} diff --git a/tools/published_devices_client/published_devices_client_deploy.jar b/tools/published_devices_client/published_devices_client_deploy.jar deleted file mode 100644 index 77911c6..0000000 Binary files a/tools/published_devices_client/published_devices_client_deploy.jar and /dev/null differ diff --git a/tools/published_devices_client/tools/BUILD b/tools/published_devices_client/tools/BUILD new file mode 100644 index 0000000..fe227bf --- /dev/null +++ b/tools/published_devices_client/tools/BUILD @@ -0,0 +1,39 @@ +# Copyright 2019 Google LLC. All rights reserved. +# Desciption: +# JTS tools. + +package(default_visibility = ["//visibility:public"]) + +java_library( + name = "published_devices_cli_lib", + srcs = [ + "PublishedDevicesCli.java", + ], + runtime_deps = [ + "@grpc_context//:io_grpc_grpc_context", + "@grpc_netty_all//:io_netty_netty_all", + "@io_perfmark_api//:io_perfmark_perfmark_api", + "@netty_boringssl//:io_netty_netty_tcnative_boringssl_static", + "@netty_tcnative//:io_netty_netty_tcnative", + "@open_census//:io_opencensus_opencensus_contrib_http_jetty_client", + "@open_census_api//:io_opencensus_opencensus_api", + "@open_census_grpc_metrics//:io_opencensus_opencensus_contrib_grpc_metrics", + "@open_census_util//:io_opencensus_opencensus_contrib_http_util", + ], + deps = [ + "//google/chrome/widevine/contentpartners/v1beta1:published_devices_grpc", + "//google/chrome/widevine/contentpartners/v1beta1:published_devices_java_proto", + "//providers", + "//providers:libwidevine_license_wvpl_sdk_lib.jar", + "@jcommander//:com_beust_jcommander", + "@google_guava//:com_google_guava_guava", + ], +) + +java_binary( + name = "published_devices_cli", + main_class = "com.google.video.widevine.jts.tools.PublishedDevicesCli", + runtime_deps = [ + ":published_devices_cli_lib", + ], +) diff --git a/tools/published_devices_client/tools/PublishedDevicesCli.java b/tools/published_devices_client/tools/PublishedDevicesCli.java new file mode 100644 index 0000000..4971b62 --- /dev/null +++ b/tools/published_devices_client/tools/PublishedDevicesCli.java @@ -0,0 +1,140 @@ +// Copyright 2020 Google LLC. All rights reserved. + +package com.google.video.widevine.jts.tools; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.beust.jcommander.JCommander; +import com.beust.jcommander.Parameter; +import com.google.chrome.widevine.contentpartners.v1beta1.PublishedDevices; +import com.google.common.io.BaseEncoding; +import com.google.video.widevine.jts.providers.PublishedDevicesProvider; +import com.google.video.widevine.sdk.wvpl.WvPLEnvironment; +import com.google.video.widevine.sdk.wvpl.WvPLStatus; +import com.google.video.widevine.sdk.wvpl.WvPLStatusException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; + +/** + * Published Devices Command Line Interface. + * + * Provides a command line interface to get the latest {@code PublishedDevices} data from the + * Widevine Published Devices Service. + * + * Default Widevine Service API path: widevine.googleapis.com. + * + * To build: + * Bazel build java/com/google/video/widevine/jts/tools:published_devices_client_deploy.jar + * + * To run: + * java -Djava.library.path=./path/to/license/sdk.so \ + * -jar /path/to/published_devices_client_deploy.jar \ + * -service_cert_path /path/to/cert_file.der \ + * -service_private_key_path /path/to/private_key.der \ + * -service_private_key_passphrase theprivatekeypassphrase + * -service_account_path /path/to/service-account.json \ + */ +public final class PublishedDevicesCli { + private WvPLEnvironment environment = null; + private PublishedDevicesProvider publishedDevices = null; + private String serviceCertPath = null; + private String privateKeyPath = null; + private String privateKeyPassphrase = null; + + /** Command Line Flags. */ + static class Flags{ + private Flags() {} + + @Parameter(names = "-service_cert_path", description = "Path to service certificate") + private String serviceCertPath = ""; + + @Parameter(names = "-service_private_key_path", + description = "Path to private key file needed to decrypt service certificate") + private String servicePrivateKeyPath = ""; + + @Parameter(names = "-service_private_key_passphrase", + description = "Passphrase needed to decrypt the private key") + private String servicePrivateKeyPassphrase = ""; + + @Parameter(names = "-service_account_path", + description = "Path to a GCP Service Account json file") + private String serviceAccountPath = ""; + + @Parameter(names = "-api_service_path", + description = "Optional. Path to a Widevine API service.") + private String apiServicePath = "widevine.googleapis.com"; + + @Parameter(names = "-s", + description = "Save PublishedDevices file path.") + private String saveFilePath = null; + } + + public PublishedDevicesCli(String serviceCertPath, String privateKeyPath, + String privateKeyPassphrase, String serviceAccountPath, String apiServicePath) + throws Exception { + this.serviceCertPath = serviceCertPath; + this.privateKeyPath = privateKeyPath; + this.privateKeyPassphrase = privateKeyPassphrase; + initializeWvplEnvironment(); + publishedDevices = new PublishedDevicesProvider( + environment, serviceAccountPath, apiServicePath); + } + + /** + * Get the latest {@code PublishedDevices} from the Widevine Published Devices Service. + * + * @return The latest PublishedDevices data. + * @throws InterruptedException upon RPC failure. + * @throws WvPLStatusException upon WvPLBaseEnvironment errors. + */ + public PublishedDevices getPublishedDevices() throws InterruptedException, WvPLStatusException { + return publishedDevices.getPublishedDevices(); + } + + /** + * Saves PublishedDevices proto data to a file. + * + * @param saveFilePath The path to the save file. + * @param publishedDevices A PublishedDevices proto. + */ + public static void savePublishedDevicesDataToFile(String saveFilePath, + PublishedDevices publishedDevices) throws IOException { + Files.write( + Paths.get(saveFilePath), + BaseEncoding.base64Url().encode(publishedDevices.toByteArray()).getBytes(UTF_8)); + } + + public static void main(String[] args) throws Exception { + Flags flags = new Flags(); + new JCommander(flags, args); + PublishedDevicesCli devices = new PublishedDevicesCli( + flags.serviceCertPath, + flags.servicePrivateKeyPath, + flags.servicePrivateKeyPassphrase, + flags.serviceAccountPath, + flags.apiServicePath); + PublishedDevices publishedDevices = devices.getPublishedDevices(); + if (flags.saveFilePath != null) { + savePublishedDevicesDataToFile(flags.saveFilePath, publishedDevices); + } + } + + private void initializeWvplEnvironment() throws Exception { + environment = new WvPLEnvironment(new HashMap<>()); + // Set service certificate. + WvPLStatus status = + environment.setServiceCertificate( + loadDataFromFile(serviceCertPath), + loadDataFromFile(privateKeyPath), + privateKeyPassphrase.getBytes(UTF_8)); + if (!status.getStatusCode().equals(WvPLStatus.StatusCode.OK)) { + throw new Exception("Set server certificate status: " + status); + } + } + + private static byte[] loadDataFromFile(String filePath) throws IOException { + return Files.readAllBytes(Paths.get(filePath)); + } +}