From 5c42bf9b7ff158dc514d157e5636419a2ff257e0 Mon Sep 17 00:00:00 2001 From: Lu Chen Date: Mon, 27 Jan 2020 16:05:15 -0800 Subject: [PATCH] Replace hardcoded parameters --- WORKSPACE | 29 +- common/BUILD | 514 ++++++++++-- common/aes_cbc_util.cc | 8 +- common/aes_cbc_util.h | 8 +- common/aes_cbc_util_test.cc | 36 +- common/certificate_client_cert.cc | 268 ++++++ common/certificate_client_cert.h | 96 +++ common/client_cert.cc | 245 +----- common/client_cert.h | 184 +---- common/client_cert_test.cc | 364 ++++++--- common/client_id_util.cc | 33 +- common/client_id_util.h | 7 +- common/content_id_util.cc | 64 ++ common/content_id_util.h | 30 + common/content_id_util_test.cc | 81 ++ common/core_message_util.cc | 51 ++ common/core_message_util.h | 33 + common/crypto_util.cc | 56 +- common/crypto_util.h | 13 +- common/crypto_util_test.cc | 146 +++- common/device_info_util.cc | 41 + common/device_info_util.h | 29 + common/device_status_list.cc | 293 +++++-- common/device_status_list.h | 164 +++- common/device_status_list_test.cc | 450 ++++++++-- common/drm_root_certificate.cc | 111 ++- common/drm_root_certificate.h | 27 +- common/drm_root_certificate_test.cc | 231 +++++- common/drm_service_certificate.cc | 50 +- common/drm_service_certificate.h | 32 +- common/drm_service_certificate_test.cc | 67 +- common/ec_key.cc | 280 +++++++ common/ec_key.h | 144 ++++ common/ec_key_source.h | 43 + common/ec_key_test.cc | 274 +++++++ common/ec_test_keys.cc | 223 +++++ common/ec_test_keys.h | 91 +++ common/ec_util.cc | 198 +++++ common/ec_util.h | 87 ++ common/ec_util_test.cc | 202 +++++ common/ecb_util.cc | 12 +- common/ecb_util.h | 12 +- common/ecies_crypto.cc | 239 ++++++ common/ecies_crypto.h | 82 ++ common/ecies_crypto_test.cc | 258 ++++++ common/fake_ec_key_source.cc | 78 ++ common/fake_ec_key_source.h | 53 ++ common/file_util_test.cc | 3 +- common/keybox_client_cert.cc | 56 ++ common/keybox_client_cert.h | 66 ++ common/local_ec_key_source.cc | 50 ++ common/local_ec_key_source.h | 42 + common/local_ec_key_source_test.cc | 64 ++ common/mock_rsa_key.h | 22 +- common/openssl_util.h | 3 + common/output_protection_util.cc | 54 ++ common/output_protection_util.h | 31 + common/output_protection_util_test.cc | 155 ++++ common/private_key_util.h | 84 ++ common/remote_attestation_verifier.cc | 10 +- common/remote_attestation_verifier.h | 10 +- common/rot_id_generator.cc | 107 +++ common/rot_id_generator.h | 91 +++ common/rot_id_generator_test.cc | 258 ++++++ common/rot_id_util.cc | 51 ++ common/rot_id_util.h | 45 + common/rot_id_util_test.cc | 66 ++ common/rsa_key.cc | 12 +- common/rsa_key.h | 19 +- common/rsa_key_test.cc | 13 + common/rsa_util.cc | 93 +-- common/rsa_util.h | 6 +- common/security_profile_list.cc | 185 +++++ common/security_profile_list.h | 96 +++ common/security_profile_list_test.cc | 157 ++++ common/sha_util.h | 3 +- common/sha_util_test.cc | 6 +- common/signature_util.cc | 6 +- common/signature_util.h | 6 +- common/signer_public_key.cc | 69 ++ common/signer_public_key.h | 40 + common/signer_public_key_test.cc | 78 ++ common/signing_key_util.cc | 2 +- common/signing_key_util.h | 3 +- common/signing_key_util_test.cc | 3 +- common/status.h | 9 +- common/string_util.cc | 3 +- common/string_util.h | 3 +- common/test_drm_certificates.cc | 766 +++++++++++++----- common/test_drm_certificates.h | 57 +- common/vmp_checker.cc | 8 +- common/vmp_checker_test.cc | 3 +- common/wvm_token_handler.cc | 8 +- common/wvm_token_handler.h | 18 +- common/x509_cert.cc | 3 +- common/x509_cert.h | 22 +- example/BUILD | 2 +- example/test_ecmg_messages.h | 209 ++++- media_cas_packager_sdk/internal/BUILD | 22 +- media_cas_packager_sdk/internal/ecm.cc | 147 ++-- media_cas_packager_sdk/internal/ecm.h | 76 +- .../internal/ecm_generator.cc | 49 +- .../internal/ecm_generator.h | 28 +- .../internal/ecm_generator_test.cc | 50 +- media_cas_packager_sdk/internal/ecm_test.cc | 206 +++-- .../internal/ecmg_client_handler.cc | 696 ++++++++++++---- .../internal/ecmg_client_handler.h | 80 +- .../internal/ecmg_client_handler_test.cc | 437 +++++++++- .../internal/ecmg_constants.h | 13 +- media_cas_packager_sdk/internal/emmg.cc | 4 +- media_cas_packager_sdk/internal/emmg_test.cc | 7 +- .../internal/fixed_key_fetcher.cc | 13 +- .../internal/fixed_key_fetcher.h | 7 +- media_cas_packager_sdk/internal/key_fetcher.h | 5 +- .../internal/simulcrypt_util.cc | 2 +- .../internal/simulcrypt_util.h | 2 +- media_cas_packager_sdk/internal/util.cc | 4 +- media_cas_packager_sdk/internal/util.h | 7 +- media_cas_packager_sdk/public/BUILD | 28 +- .../public/wv_cas_ca_descriptor.cc | 10 +- .../public/wv_cas_ca_descriptor.h | 9 +- .../public/wv_cas_ca_descriptor_test.cc | 43 +- media_cas_packager_sdk/public/wv_cas_ecm.cc | 65 +- media_cas_packager_sdk/public/wv_cas_ecm.h | 4 +- .../public/wv_cas_ecm_test.cc | 20 +- .../public/wv_cas_key_fetcher.cc | 13 +- .../public/wv_cas_key_fetcher.h | 7 +- .../public/wv_cas_key_fetcher_test.cc | 5 +- media_cas_packager_sdk/public/wv_cas_types.cc | 39 +- media_cas_packager_sdk/public/wv_cas_types.h | 16 +- .../public/wv_cas_types_test.cc | 31 + media_cas_packager_sdk/public/wv_ecmg.cc | 66 +- protos/public/BUILD | 18 +- protos/public/media_cas_encryption.proto | 6 +- 134 files changed, 9510 insertions(+), 1938 deletions(-) create mode 100644 common/certificate_client_cert.cc create mode 100644 common/certificate_client_cert.h create mode 100644 common/content_id_util.cc create mode 100644 common/content_id_util.h create mode 100644 common/content_id_util_test.cc create mode 100644 common/core_message_util.cc create mode 100644 common/core_message_util.h create mode 100644 common/device_info_util.cc create mode 100644 common/device_info_util.h create mode 100644 common/ec_key.cc create mode 100644 common/ec_key.h create mode 100644 common/ec_key_source.h create mode 100644 common/ec_key_test.cc create mode 100644 common/ec_test_keys.cc create mode 100644 common/ec_test_keys.h create mode 100644 common/ec_util.cc create mode 100644 common/ec_util.h create mode 100644 common/ec_util_test.cc create mode 100644 common/ecies_crypto.cc create mode 100644 common/ecies_crypto.h create mode 100644 common/ecies_crypto_test.cc create mode 100644 common/fake_ec_key_source.cc create mode 100644 common/fake_ec_key_source.h create mode 100644 common/keybox_client_cert.cc create mode 100644 common/keybox_client_cert.h create mode 100644 common/local_ec_key_source.cc create mode 100644 common/local_ec_key_source.h create mode 100644 common/local_ec_key_source_test.cc create mode 100644 common/output_protection_util.cc create mode 100644 common/output_protection_util.h create mode 100644 common/output_protection_util_test.cc create mode 100644 common/private_key_util.h create mode 100644 common/rot_id_generator.cc create mode 100644 common/rot_id_generator.h create mode 100644 common/rot_id_generator_test.cc create mode 100644 common/rot_id_util.cc create mode 100644 common/rot_id_util.h create mode 100644 common/rot_id_util_test.cc create mode 100644 common/security_profile_list.cc create mode 100644 common/security_profile_list.h create mode 100644 common/security_profile_list_test.cc create mode 100644 common/signer_public_key.cc create mode 100644 common/signer_public_key.h create mode 100644 common/signer_public_key_test.cc diff --git a/WORKSPACE b/WORKSPACE index b370163..0541e7c 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1,6 +1,6 @@ workspace(name = "media_cas_packager_sdk") - 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") # CCTZ (Time-zone framework), needed by abseil. git_repository( @@ -11,14 +11,33 @@ git_repository( git_repository( name = "abseil_repo", - commit = "475d64f2de7403a01b1b36c487328ed41d29c20c", #2018-04-10 + commit = "aa844899c937bde5d2b24f276b59997e5b668bde", #2019-08-08 remote = "https://github.com/abseil/abseil-cpp.git", ) +# Name com_google_protobuf instead of protobuf_repo because Bazel's proto rules +# implicitly depend on @com_google_protobuf. See +# https://bazel.build/blog/2017/02/27/protocol-buffers.html. git_repository( - name = "protobuf_repo", + name = "com_google_protobuf", remote = "https://github.com/google/protobuf.git", - tag = "v3.6.1.3", + tag = "v3.8.0", +) + +# Bazel custom build rule support. +git_repository( + name = "bazel_skylib", + remote = "https://github.com/bazelbuild/bazel-skylib.git", + tag = "0.8.0", +) + +# Protobuf library support. Not included in the recent protobuf release. +http_archive( + name = "zlib", + build_file = "@com_google_protobuf//:third_party/zlib.BUILD", + sha256 = "c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1", + strip_prefix = "zlib-1.2.11", + urls = ["https://zlib.net/zlib-1.2.11.tar.gz"], ) git_repository( @@ -62,7 +81,7 @@ new_git_repository( bind( name = "protobuf", - actual = "@protobuf_repo//:protobuf", + actual = "@com_google_protobuf//:protobuf", ) bind( diff --git a/common/BUILD b/common/BUILD index a048b23..fadf427 100644 --- a/common/BUILD +++ b/common/BUILD @@ -20,6 +20,35 @@ filegroup( ], ) +cc_library( + name = "content_id_util", + srcs = ["content_id_util.cc"], + hdrs = ["content_id_util.h"], + deps = [ + ":error_space", + ":status", + "//license_server_sdk/internal:sdk", + "//protos/public:errors_cc_proto", + "//protos/public:external_license_cc_proto", + "//protos/public:license_protocol_cc_proto", + "//protos/public:license_server_sdk_cc_proto", + "//protos/public:widevine_pssh_cc_proto", + ], +) + +cc_test( + name = "content_id_util_test", + srcs = ["content_id_util_test.cc"], + deps = [ + ":content_id_util", + "//testing:gunit_main", + "//protos/public:errors_cc_proto", + "//protos/public:external_license_cc_proto", + "//protos/public:license_protocol_cc_proto", + "//protos/public:widevine_pssh_cc_proto", + ], +) + cc_library( name = "widevine_system_id", srcs = ["widevine_system_id.cc"], @@ -32,12 +61,37 @@ cc_library( hdrs = ["certificate_type.h"], ) +cc_library( + name = "security_profile_list", + srcs = ["security_profile_list.cc"], + hdrs = ["security_profile_list.h"], + deps = [ + ":client_id_util", + "@abseil_repo//absl/synchronization", + "//protos/public:client_identification_cc_proto", + "//protos/public:provisioned_device_info_cc_proto", + "//protos/public:security_profile_cc_proto", + ], +) + +cc_test( + name = "security_profile_list_test", + timeout = "short", + srcs = ["security_profile_list_test.cc"], + deps = [ + ":security_profile_list", + "//base", + "//external:protobuf", + "//testing:gunit_main", + "//protos/public:security_profile_cc_proto", + ], +) + cc_library( name = "status", srcs = ["status.cc"], hdrs = ["status.h"], deps = [ - "//base", "@abseil_repo//absl/base:core_headers", "@abseil_repo//absl/strings", "//util:error_space", @@ -55,12 +109,23 @@ cc_test( cc_library( name = "client_cert", - srcs = ["client_cert.cc"], - hdrs = ["client_cert.h"], + srcs = [ + "certificate_client_cert.cc", + "certificate_client_cert.h", + "client_cert.cc", + "keybox_client_cert.cc", + ], + hdrs = [ + "client_cert.h", + "keybox_client_cert.h", + ], deps = [ ":crypto_util", ":drm_root_certificate", + ":ec_key", + ":ec_util", ":error_space", + ":openssl_util", ":random_util", ":rsa_key", ":sha_util", @@ -68,16 +133,13 @@ cc_library( ":status", ":wvm_token_handler", "//base", - "//strings", + "@abseil_repo//absl/memory", "@abseil_repo//absl/strings", - "@abseil_repo//absl/synchronization", - "@abseil_repo//absl/time", - "//util/gtl:map_util", - "//protos/public:client_identification_proto", - "//protos/public:drm_certificate_proto", - "//protos/public:errors_proto", - "//protos/public:license_protocol_proto", - "//protos/public:signed_drm_certificate_proto", + "//protos/public:client_identification_cc_proto", + "//protos/public:drm_certificate_cc_proto", + "//protos/public:errors_cc_proto", + "//protos/public:license_protocol_cc_proto", + "//protos/public:signed_drm_certificate_cc_proto", ], ) @@ -86,22 +148,19 @@ cc_test( srcs = ["client_cert_test.cc"], deps = [ ":client_cert", - ":drm_root_certificate", + ":ec_test_keys", ":error_space", + ":rsa_key", + ":rsa_test_keys", ":sha_util", + ":status", ":test_drm_certificates", ":wvm_test_keys", - "//base", - "//strings", "//testing:gunit_main", "@abseil_repo//absl/strings", - "@abseil_repo//absl/synchronization", - "@abseil_repo//absl/time", - "//common:rsa_key", - "//common:rsa_test_keys", - "//protos/public:drm_certificate_proto", - "//protos/public:errors_proto", - "//protos/public:signed_drm_certificate_proto", + "//protos/public:drm_certificate_cc_proto", + "//protos/public:errors_cc_proto", + "//protos/public:signed_drm_certificate_cc_proto", ], ) @@ -111,22 +170,29 @@ cc_library( hdrs = ["device_status_list.h"], deps = [ ":client_cert", - ":crypto_util", - ":drm_root_certificate", ":drm_service_certificate", ":error_space", - ":random_util", ":rsa_key", - ":signing_key_util", ":status", "//base", "@abseil_repo//absl/strings", "@abseil_repo//absl/synchronization", "//util/gtl:map_util", - "//protos/public:client_identification_proto", - "//protos/public:device_certificate_status_proto", - "//protos/public:errors_proto", - "//protos/public:provisioned_device_info_proto", + "//protos/public:client_identification_cc_proto", + "//protos/public:device_certificate_status_cc_proto", + "//protos/public:errors_cc_proto", + "//protos/public:provisioned_device_info_cc_proto", + "//protos/public:signed_device_info_cc_proto", + ], +) + +cc_library( + name = "device_info_util", + srcs = ["device_info_util.cc"], + hdrs = ["device_info_util.h"], + deps = [ + "@abseil_repo//absl/strings", + "//protos/public:provisioned_device_info_cc_proto", ], ) @@ -137,15 +203,19 @@ cc_test( deps = [ ":client_cert", ":device_status_list", + ":rsa_key", + ":rsa_test_keys", + ":status", "//base", + "//external:protobuf", "//testing:gunit_main", "@abseil_repo//absl/strings", - "//common:rsa_key", - "//common:rsa_test_keys", - "//protos/public:client_identification_proto", - "//protos/public:errors_proto", - "//protos/public:provisioned_device_info_proto", - "//protos/public:signed_drm_certificate_proto", + "//protos/public:client_identification_cc_proto", + "//protos/public:device_certificate_status_cc_proto", + "//protos/public:errors_cc_proto", + "//protos/public:provisioned_device_info_cc_proto", + "//protos/public:signed_device_info_cc_proto", + "//protos/public:signed_drm_certificate_cc_proto", ], ) @@ -155,18 +225,19 @@ cc_library( hdrs = ["drm_root_certificate.h"], deps = [ ":certificate_type", + ":ec_key", ":error_space", ":rsa_key", ":sha_util", + ":signer_public_key", ":status", "//base", "@abseil_repo//absl/memory", "@abseil_repo//absl/strings", "@abseil_repo//absl/synchronization", - "//external:openssl", - "//protos/public:drm_certificate_proto", - "//protos/public:errors_proto", - "//protos/public:signed_drm_certificate_proto", + "//protos/public:drm_certificate_cc_proto", + "//protos/public:errors_cc_proto", + "//protos/public:signed_drm_certificate_cc_proto", ], ) @@ -176,6 +247,8 @@ cc_test( srcs = ["drm_root_certificate_test.cc"], deps = [ ":drm_root_certificate", + ":ec_key", + ":ec_test_keys", ":error_space", ":rsa_key", ":rsa_test_keys", @@ -183,9 +256,10 @@ cc_test( "//base", "//external:protobuf", "//testing:gunit_main", - "//protos/public:drm_certificate_proto", - "//protos/public:errors_proto", - "//protos/public:signed_drm_certificate_proto", + "@abseil_repo//absl/memory", + "//protos/public:drm_certificate_cc_proto", + "//protos/public:errors_cc_proto", + "//protos/public:signed_drm_certificate_cc_proto", ], ) @@ -195,13 +269,25 @@ cc_library( hdrs = ["client_id_util.h"], deps = [ ":aes_cbc_util", + ":client_cert", ":drm_service_certificate", ":error_space", ":status", "//base", "@abseil_repo//absl/strings", - "//protos/public:client_identification_proto", - "//protos/public:errors_proto", + "//protos/public:client_identification_cc_proto", + "//protos/public:drm_certificate_cc_proto", + "//protos/public:errors_cc_proto", + "//protos/public:signed_drm_certificate_cc_proto", + ], +) + +cc_library( + name = "private_key_util", + hdrs = ["private_key_util.h"], + deps = [ + "//base", + "//external:openssl", ], ) @@ -210,6 +296,7 @@ cc_library( srcs = ["rsa_util.cc"], hdrs = ["rsa_util.h"], deps = [ + ":private_key_util", "//base", "//external:openssl", ], @@ -269,9 +356,6 @@ cc_library( testonly = 1, srcs = ["rsa_test_keys.cc"], hdrs = ["rsa_test_keys.h"], - deps = [ - "//base", - ], ) cc_library( @@ -284,6 +368,168 @@ cc_library( ], ) +cc_library( + name = "ec_util", + srcs = ["ec_util.cc"], + hdrs = [ + "ec_key.h", + "ec_util.h", + ], + deps = [ + ":openssl_util", + ":private_key_util", + "//base", + "@abseil_repo//absl/memory", + "//external:openssl", + ], +) + +cc_test( + name = "ec_util_test", + size = "medium", + timeout = "short", + srcs = ["ec_util_test.cc"], + deps = [ + ":ec_test_keys", + ":ec_util", + ":openssl_util", + "//base", + "//testing:gunit", + "//testing:gunit_main", + "@abseil_repo//absl/strings", + ], +) + +cc_library( + name = "ec_key", + srcs = ["ec_key.cc"], + hdrs = ["ec_key.h"], + deps = [ + ":aes_cbc_util", + ":ec_util", + ":openssl_util", + ":sha_util", + "//base", + "@abseil_repo//absl/memory", + "//external:openssl", + ], +) + +cc_test( + name = "ec_key_test", + size = "medium", + timeout = "short", + srcs = ["ec_key_test.cc"], + deps = [ + ":ec_key", + ":ec_test_keys", + ":ec_util", + ":random_util", + "//testing:gunit", + "//testing:gunit_main", + "//external:openssl", + ], +) + +cc_library( + name = "ec_key_source", + hdrs = ["ec_key_source.h"], + deps = [ + ":ec_key", + "//base", + ], +) + +cc_library( + name = "local_ec_key_source", + srcs = ["local_ec_key_source.cc"], + hdrs = [ + "local_ec_key_source.h", + ], + deps = [ + ":ec_key", + ":ec_key_source", + ":ec_util", + "//base", + "//external:openssl", + ], +) + +cc_test( + name = "local_ec_key_source_test", + size = "medium", + timeout = "short", + srcs = ["local_ec_key_source_test.cc"], + deps = [ + ":ec_key", + ":ec_test_keys", + ":ec_util", + ":local_ec_key_source", + ":random_util", + "//testing:gunit", + "//testing:gunit_main", + "//external:openssl", + ], +) + +cc_library( + name = "fake_ec_key_source", + testonly = 1, + srcs = ["fake_ec_key_source.cc"], + hdrs = ["fake_ec_key_source.h"], + deps = [ + ":ec_key", + ":ec_key_source", + ":ec_test_keys", + "//base", + "//external:openssl", + ], +) + +cc_library( + name = "ecies_crypto", + srcs = ["ecies_crypto.cc"], + hdrs = ["ecies_crypto.h"], + deps = [ + ":aes_cbc_util", + ":crypto_util", + ":ec_key", + ":ec_key_source", + ":ec_util", + ":openssl_util", + ":status", + "//base", + "@abseil_repo//absl/memory", + "@abseil_repo//absl/strings", + "//external:openssl", + ], +) + +cc_test( + name = "ecies_crypto_test", + size = "medium", + timeout = "short", + srcs = ["ecies_crypto_test.cc"], + deps = [ + ":ec_key", + ":ec_key_source", + ":ec_test_keys", + ":ec_util", + ":ecies_crypto", + ":fake_ec_key_source", + "//testing:gunit", + "//testing:gunit_main", + "@abseil_repo//absl/strings", + ], +) + +cc_library( + name = "ec_test_keys", + testonly = 1, + srcs = ["ec_test_keys.cc"], + hdrs = ["ec_test_keys.h"], +) + cc_library( name = "aes_cbc_util", srcs = ["aes_cbc_util.cc"], @@ -313,7 +559,6 @@ cc_library( "//base", "@abseil_repo//absl/strings", "//external:openssl", - "//util/endian", ], ) @@ -326,6 +571,7 @@ cc_test( "//testing:gunit", "//testing:gunit_main", "@abseil_repo//absl/strings", + "//external:openssl", ], ) @@ -420,7 +666,6 @@ cc_library( ":rsa_key", ":sha_util", ":status", - "//base", ], ) @@ -431,7 +676,7 @@ cc_library( deps = [ ":crypto_util", "//base", - "//protos/public:license_protocol_proto", + "//protos/public:license_protocol_cc_proto", ], ) @@ -445,7 +690,7 @@ cc_test( "//testing:gunit", "//testing:gunit_main", "@abseil_repo//absl/strings", - "//protos/public:license_protocol_proto", + "//protos/public:license_protocol_cc_proto", ], ) @@ -454,10 +699,6 @@ cc_library( testonly = 1, srcs = ["test_drm_certificates.cc"], hdrs = ["test_drm_certificates.h"], - deps = [ - "//base", - "@abseil_repo//absl/strings", - ], ) cc_library( @@ -509,7 +750,7 @@ cc_library( deps = [ "//util:error_space", "//util:proto_status", - "//protos/public:errors_proto", + "//protos/public:errors_cc_proto", ], ) @@ -527,9 +768,9 @@ cc_library( "//base", "@abseil_repo//absl/strings", "@abseil_repo//absl/synchronization", - "//protos/public:client_identification_proto", - "//protos/public:errors_proto", - "//protos/public:remote_attestation_proto", + "//protos/public:client_identification_cc_proto", + "//protos/public:errors_cc_proto", + "//protos/public:remote_attestation_cc_proto", ], ) @@ -549,10 +790,10 @@ cc_library( "@abseil_repo//absl/strings", "@abseil_repo//absl/synchronization", "//util/gtl:map_util", - "//protos/public:client_identification_proto", - "//protos/public:drm_certificate_proto", - "//protos/public:errors_proto", - "//protos/public:signed_drm_certificate_proto", + "//protos/public:client_identification_cc_proto", + "//protos/public:drm_certificate_cc_proto", + "//protos/public:errors_cc_proto", + "//protos/public:signed_drm_certificate_cc_proto", ], ) @@ -572,11 +813,11 @@ cc_test( "//external:protobuf", "//testing:gunit_main", "@abseil_repo//absl/strings", - "//protos/public:client_identification_proto", - "//protos/public:drm_certificate_proto", - "//protos/public:errors_proto", - "//protos/public:license_server_sdk_proto", - "//protos/public:signed_drm_certificate_proto", + "//protos/public:client_identification_cc_proto", + "//protos/public:drm_certificate_cc_proto", + "//protos/public:errors_cc_proto", + "//protos/public:license_server_sdk_cc_proto", + "//protos/public:signed_drm_certificate_cc_proto", ], ) @@ -587,9 +828,7 @@ cc_library( deps = [ ":status", ":vmp_checker", - "//base", - "@abseil_repo//absl/strings", - "//protos/public:license_protocol_proto", + "//protos/public:license_protocol_cc_proto", ], ) @@ -598,7 +837,6 @@ cc_library( srcs = ["x509_cert.cc"], hdrs = ["x509_cert.h"], deps = [ - ":error_space", ":openssl_util", ":rsa_key", ":status", @@ -629,7 +867,6 @@ cc_test( ":rsa_key", ":test_utils", ":x509_cert", - "//base", "//testing:gunit_main", "@abseil_repo//absl/strings", ], @@ -646,8 +883,8 @@ cc_library( ":status", ":x509_cert", "//base", - "//protos/public:errors_proto", - "//protos/public:verified_media_pipeline_proto", + "//protos/public:errors_cc_proto", + "//protos/public:verified_media_pipeline_cc_proto", ], ) @@ -661,8 +898,8 @@ cc_test( "//base", "//testing:gunit_main", "@abseil_repo//absl/strings", - "//protos/public:errors_proto", - "//protos/public:verified_media_pipeline_proto", + "//protos/public:errors_cc_proto", + "//protos/public:verified_media_pipeline_cc_proto", ], ) @@ -670,10 +907,7 @@ cc_library( name = "string_util", srcs = ["string_util.cc"], hdrs = ["string_util.h"], - deps = [ - ":status", - "//base", - ], + deps = [":status"], ) cc_test( @@ -681,7 +915,121 @@ cc_test( srcs = ["string_util_test.cc"], deps = [ ":string_util", - "//base", "//testing:gunit_main", ], ) + +cc_library( + name = "output_protection_util", + srcs = ["output_protection_util.cc"], + hdrs = ["output_protection_util.h"], + deps = [ + ":status", + "//protos/public:client_identification_cc_proto", + "//protos/public:license_protocol_cc_proto", + ], +) + +cc_test( + name = "output_protection_util_test", + srcs = ["output_protection_util_test.cc"], + deps = [ + ":output_protection_util", + "//testing:gunit_main", + ], +) + +cc_library( + name = "rot_id_util", + srcs = ["rot_id_util.cc"], + hdrs = ["rot_id_util.h"], + deps = [ + ":crypto_util", + ":ec_key", + ":local_ec_key_source", + ":sha_util", + "//base", + "@abseil_repo//absl/strings", + ], +) + +cc_test( + name = "rot_id_util_test", + srcs = ["rot_id_util_test.cc"], + deps = [ + ":rot_id_util", + "//testing:gunit_main", + "@abseil_repo//absl/strings", + ], +) + +cc_library( + name = "rot_id_generator", + srcs = ["rot_id_generator.cc"], + hdrs = ["rot_id_generator.h"], + deps = [ + ":crypto_util", + ":ec_key", + ":ecies_crypto", + ":rot_id_util", + ":sha_util", + ":status", + "//base", + "@abseil_repo//absl/strings", + "//protos/public:drm_certificate_cc_proto", + ], +) + +cc_test( + name = "rot_id_generator_test", + srcs = ["rot_id_generator_test.cc"], + deps = [ + ":ec_key", + ":ec_test_keys", + ":ecies_crypto", + ":fake_ec_key_source", + ":rot_id_generator", + ":rot_id_util", + ":status", + "//external:protobuf", + "//testing:gunit_main", + "@abseil_repo//absl/strings", + "//protos/public:drm_certificate_cc_proto", + ], +) + +cc_library( + name = "signer_public_key", + srcs = ["signer_public_key.cc"], + hdrs = ["signer_public_key.h"], + deps = [ + ":ec_key", + ":rsa_key", + "@abseil_repo//absl/memory", + "//protos/public:drm_certificate_cc_proto", + ], +) + +cc_test( + name = "signer_public_key_test", + srcs = ["signer_public_key_test.cc"], + deps = [ + ":ec_key", + ":ec_test_keys", + ":rsa_key", + ":rsa_test_keys", + ":signer_public_key", + "//testing:gunit_main", + "//protos/public:drm_certificate_cc_proto", + ], +) + +cc_library( + name = "core_message_util", + srcs = ["core_message_util.cc"], + hdrs = ["core_message_util.h"], + deps = [ + ":sha_util", + "//common/oemcrypto_core_message/odk:kdo", + ], +) diff --git a/common/aes_cbc_util.cc b/common/aes_cbc_util.cc index 66e6552..79d6522 100644 --- a/common/aes_cbc_util.cc +++ b/common/aes_cbc_util.cc @@ -19,7 +19,7 @@ namespace crypto_util { // Encrypts the provided plantext std::string using AES-CBC encryption. std::string EncryptAesCbc(const std::string& key, const std::string& iv, - const std::string& plaintext) { + const std::string& plaintext) { const size_t num_padding_bytes = AES_BLOCK_SIZE - (plaintext.size() % AES_BLOCK_SIZE); std::string padded_text = plaintext; @@ -28,7 +28,7 @@ std::string EncryptAesCbc(const std::string& key, const std::string& iv, } std::string EncryptAesCbcNoPad(const std::string& key, const std::string& iv, - const std::string& plaintext) { + const std::string& plaintext) { if (iv.size() != AES_BLOCK_SIZE) { LOG(WARNING) << "Invalid CBC IV size: " << iv.size(); return std::string(); @@ -56,7 +56,7 @@ std::string EncryptAesCbcNoPad(const std::string& key, const std::string& iv, // Decrypts the AES-CBC encrypted text. Returns an empty std::string on error or // the plaintext on success. std::string DecryptAesCbc(const std::string& key, const std::string& iv, - const std::string& ciphertext) { + const std::string& ciphertext) { if (ciphertext.empty()) { LOG(WARNING) << "Empty ciphertext."; return std::string(); @@ -100,7 +100,7 @@ std::string DecryptAesCbc(const std::string& key, const std::string& iv, } std::string DecryptAesCbcNoPad(const std::string& key, const std::string& iv, - const std::string& ciphertext) { + const std::string& ciphertext) { std::vector local_iv(iv.begin(), iv.end()); if (local_iv.empty()) { local_iv.resize(AES_BLOCK_SIZE, '\0'); diff --git a/common/aes_cbc_util.h b/common/aes_cbc_util.h index a1ad59b..ab4283f 100644 --- a/common/aes_cbc_util.h +++ b/common/aes_cbc_util.h @@ -16,18 +16,18 @@ namespace crypto_util { // Helper for wrapping AES CBC encryption. Uses PKCS7 padding. std::string EncryptAesCbc(const std::string& key, const std::string& iv, - const std::string& plaintext); + const std::string& plaintext); // Helper for wrapping AES CBC encryption. Adds no padding, so the input // must be an multiple of the 16-byte AES block size. Returns empty std::string // on error. std::string EncryptAesCbcNoPad(const std::string& key, const std::string& iv, - const std::string& plaintext); + const std::string& plaintext); // Helper for common Keybox decrypt operations; wraps AES-CBC. Returns an // empty std::string on error or the plaintext on success. Expects PKCS7 padding. std::string DecryptAesCbc(const std::string& key, const std::string& iv, - const std::string& ciphertext); + const std::string& ciphertext); // Helper for common Keybox decrypt operations; wraps AES-CBC. Returns an // empty std::string on error or the plaintext on success. @@ -35,7 +35,7 @@ std::string DecryptAesCbc(const std::string& key, const std::string& iv, // This is used to decrypt the encrypted blob in the WVM keyboxes, with // a zero iv. std::string DecryptAesCbcNoPad(const std::string& key, const std::string& iv, - const std::string& ciphertext); + const std::string& ciphertext); } // namespace crypto_util } // namespace widevine diff --git a/common/aes_cbc_util_test.cc b/common/aes_cbc_util_test.cc index 1eded76..c7f67e6 100644 --- a/common/aes_cbc_util_test.cc +++ b/common/aes_cbc_util_test.cc @@ -29,16 +29,18 @@ namespace crypto_util { TEST(CryptoUtilTest, EncryptAndDecryptAesCbc) { std::string plain_text("Foo"); - std::string ciphertext = EncryptAesCbc(std::string(kKey, kKey + sizeof(kKey)), - std::string(kIv, kIv + sizeof(kIv)), plain_text); + std::string ciphertext = + EncryptAesCbc(std::string(kKey, kKey + sizeof(kKey)), + std::string(kIv, kIv + sizeof(kIv)), plain_text); std::string expected_ciphertext( "\xCF\x1A\x3\x1C\x9C\x8C\xB9Z\xEC\xC0\x17\xDCRxX\xD7"); ASSERT_EQ(0, ciphertext.size() % 16); ASSERT_GT(ciphertext.size(), plain_text.size()); ASSERT_EQ(expected_ciphertext, ciphertext); - std::string decrypted = DecryptAesCbc(std::string(kKey, kKey + sizeof(kKey)), - std::string(kIv, kIv + sizeof(kIv)), ciphertext); + std::string decrypted = + DecryptAesCbc(std::string(kKey, kKey + sizeof(kKey)), + std::string(kIv, kIv + sizeof(kIv)), ciphertext); ASSERT_EQ(plain_text, decrypted); } @@ -65,28 +67,29 @@ TEST(CryptoUtilTest, DecryptAesCbcNoPad) { }; std::string decrypted = DecryptAesCbcNoPad( - std::string(kKey, kKey + sizeof(kKey)), std::string(kIv, kIv + sizeof(kIv)), + std::string(kKey, kKey + sizeof(kKey)), + std::string(kIv, kIv + sizeof(kIv)), std::string(kCiphertext, kCiphertext + sizeof(kCiphertext))); ASSERT_EQ(std::string(kExpectedPlaintext, - kExpectedPlaintext + sizeof(kExpectedPlaintext)), + kExpectedPlaintext + sizeof(kExpectedPlaintext)), decrypted); std::string dummy_iv; decrypted = DecryptAesCbcNoPad( std::string(kKey, kKey + sizeof(kKey)), dummy_iv, std::string(kCiphertext, kCiphertext + sizeof(kCiphertext))); - ASSERT_EQ( - std::string(kExpectedPlaintextEmptyIv, - kExpectedPlaintextEmptyIv + sizeof(kExpectedPlaintextEmptyIv)), - decrypted); + ASSERT_EQ(std::string( + kExpectedPlaintextEmptyIv, + kExpectedPlaintextEmptyIv + sizeof(kExpectedPlaintextEmptyIv)), + decrypted); } TEST(CryptoUtilTest, TestFailedEncrypt) { // Test with bogus initialization vector. std::string plain_text("Foo"); std::string bogus_iv("bogus"); - std::string ciphertext = - EncryptAesCbc(std::string(kKey, kKey + sizeof(kKey)), bogus_iv, plain_text); + std::string ciphertext = EncryptAesCbc(std::string(kKey, kKey + sizeof(kKey)), + bogus_iv, plain_text); ASSERT_EQ(ciphertext.size(), 0); // Test with bogus key. @@ -124,14 +127,15 @@ TEST(CryptoUtilTest, TestFailedEncryptNoPad) { TEST(CryptoUtilTest, TestFailedDecrypt) { // First, encrypt the data. std::string plain_text("Foo"); - std::string ciphertext = EncryptAesCbc(std::string(kKey, kKey + sizeof(kKey)), - std::string(kIv, kIv + sizeof(kIv)), plain_text); + std::string ciphertext = + EncryptAesCbc(std::string(kKey, kKey + sizeof(kKey)), + std::string(kIv, kIv + sizeof(kIv)), plain_text); ASSERT_NE(ciphertext.size(), 0); // Test Decrypt with bogus iv. std::string bogus_iv("bogus"); - plain_text = - DecryptAesCbc(std::string(kKey, kKey + sizeof(kKey)), bogus_iv, ciphertext); + plain_text = DecryptAesCbc(std::string(kKey, kKey + sizeof(kKey)), bogus_iv, + ciphertext); ASSERT_EQ(plain_text.size(), 0); // Test Decrypt with bogus key. diff --git a/common/certificate_client_cert.cc b/common/certificate_client_cert.cc new file mode 100644 index 0000000..20da973 --- /dev/null +++ b/common/certificate_client_cert.cc @@ -0,0 +1,268 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#include "common/certificate_client_cert.h" + +#include "glog/logging.h" +#include "absl/memory/memory.h" +#include "common/crypto_util.h" +#include "common/ec_key.h" +#include "common/ec_util.h" +#include "common/error_space.h" +#include "common/openssl_util.h" +#include "common/random_util.h" +#include "common/rsa_key.h" +#include "common/sha_util.h" +#include "common/signing_key_util.h" +#include "protos/public/drm_certificate.pb.h" +#include "protos/public/errors.pb.h" +#include "protos/public/signed_drm_certificate.pb.h" + +namespace widevine { + +using EllipticCurve = ECPrivateKey::EllipticCurve; + +ECPrivateKey::EllipticCurve CertificateAlgorithmToCurve( + DrmCertificate::Algorithm algorithm) { + switch (algorithm) { + case DrmCertificate::ECC_SECP256R1: + return ECPrivateKey::SECP256R1; + case DrmCertificate::ECC_SECP384R1: + return ECPrivateKey::SECP384R1; + case DrmCertificate::ECC_SECP521R1: + return ECPrivateKey::SECP521R1; + default: + return ECPrivateKey::UNDEFINED_CURVE; + } +} + +class ClientCertAlgorithmRSA : public ClientCertAlgorithm { + public: + ClientCertAlgorithmRSA() {} + ~ClientCertAlgorithmRSA() override {} + ClientCertAlgorithmRSA(const ClientCertAlgorithmRSA&) = delete; + ClientCertAlgorithmRSA& operator=(const ClientCertAlgorithmRSA&) = delete; + + Status Initialize(const std::string& public_key, + DrmCertificate::Algorithm /*not_used*/) override { + rsa_public_key_ = + std::unique_ptr(RsaPublicKey::Create(public_key)); + if (!rsa_public_key_) { + return Status(error_space, INVALID_DRM_CERTIFICATE, + "drm-certificate-public-key-failed"); + } + session_key_ = Random16Bytes(); + if (!rsa_public_key_->Encrypt(session_key_, &wrapped_session_key_)) { + return Status(error_space, ENCRYPT_ERROR, + "drm-certificate-failed-encrypt-session-key"); + } + return OkStatus(); + } + + Status VerifySignature(const std::string& message, + const std::string& signature) const override { + CHECK(rsa_public_key_); + + if (!rsa_public_key_->VerifySignature(message, signature)) { + return Status(error_space, INVALID_SIGNATURE, ""); + } + return OkStatus(); + } + + const std::string& session_key() const override { return session_key_; } + + const std::string& wrapped_session_key() const override { + return wrapped_session_key_; + } + + private: + std::unique_ptr rsa_public_key_; + std::string session_key_; + std::string wrapped_session_key_; +}; + +// ClientCertAlgorithmECC implements the Widevine protocol using ECC. It +// verifies an ECC based request and generates keys for use in building a +// license. The curve type value is contained in |algorithm|. +class ClientCertAlgorithmECC : public ClientCertAlgorithm { + public: + ClientCertAlgorithmECC() = default; + ~ClientCertAlgorithmECC() override = default; + ClientCertAlgorithmECC(const ClientCertAlgorithmECC&) = delete; + ClientCertAlgorithmECC& operator=(const ClientCertAlgorithmECC&) = delete; + + Status Initialize(const std::string& public_key, + DrmCertificate::Algorithm algorithm) override { + ECPrivateKey::EllipticCurve curve_id = + CertificateAlgorithmToCurve(algorithm); + if (curve_id == ECPrivateKey::UNDEFINED_CURVE) { + return Status(error_space, INVALID_DRM_CERTIFICATE, + "drm-certificate-unknown-curve"); + } + // Parse the certifcate ECC public key. + client_ecc_public_key_ = ECPublicKey::Create(public_key); + if (client_ecc_public_key_ == nullptr) { + return Status(error_space, INVALID_DRM_CERTIFICATE, + "drm-certificate-public-key-failed"); + } + // Generate an ephemeral ecc key pair with the same curve as used by the + // certificate public key. + ScopedECKEY key = ec_util::GenerateKeyWithCurve(curve_id); + auto new_private_key = absl::make_unique(std::move(key)); + if (new_private_key == nullptr) { + return Status(error_space, DRM_DEVICE_CERTIFICATE_ECC_KEYGEN_FAILED, + "drm-certificate-ephemeral-private-key-failed"); + } + + // Serialize the ephemeral public key for inclusion in a license response. + std::unique_ptr new_public_key = new_private_key->PublicKey(); + if (new_public_key == nullptr || + !new_public_key->SerializedKey(&ephemeral_public_key_)) { + return Status(error_space, DRM_DEVICE_CERTIFICATE_ECC_KEYGEN_FAILED, + "drm-certificate-ephemeral-public-key-failed"); + } + + // Generate the session key from the ephemeral private key and the + // certificate public key. + if (!new_private_key->DeriveSharedSessionKey(*client_ecc_public_key_, + &derived_session_key_)) { + return Status(error_space, DRM_DEVICE_CERTIFICATE_ECC_KEYGEN_FAILED, + "drm-certificate-shared-key-gen-failed"); + } + return OkStatus(); + } + + Status VerifySignature(const std::string& message, + const std::string& signature) const override { + CHECK(client_ecc_public_key_); + + if (!client_ecc_public_key_->VerifySignature(message, signature)) { + return Status(error_space, INVALID_SIGNATURE, ""); + } + return OkStatus(); + } + // Returns an aes key generated from the sha256 hash of the shared ecc secret. + // This key is used for key derivation. + const std::string& session_key() const override { + return derived_session_key_; + } + + // Returns an ephemeral serialized ecc public key. This key is added to a + // license response in the SignedMessage::session_key field. The client will + // use this key to generate the shared secret and derived session key. + const std::string& wrapped_session_key() const override { + return ephemeral_public_key_; + } + + private: + std::unique_ptr client_ecc_public_key_; + std::string ephemeral_public_key_; + std::string derived_session_key_; +}; + +Status CertificateClientCert::Initialize( + const DrmRootCertificate* root_certificate, + const std::string& serialized_certificate) { + CHECK(root_certificate); + + if (is_initialized_) { + return Status(error_space, INVALID_PARAMETER, + "certificate-is-already-initialized"); + } + + SignedDrmCertificate signed_device_cert; + Status status = root_certificate->VerifyCertificate( + serialized_certificate, &signed_device_cert, &device_cert_); + if (!status.ok()) { + return status; + } + if (device_cert_.type() != DrmCertificate::DEVICE || + device_cert_.public_key().empty()) { + return Status(error_space, INVALID_DRM_CERTIFICATE, + "expected-device-certificate-type"); + } + + const SignedDrmCertificate& device_cert_signer = signed_device_cert.signer(); + + if (!model_certificate_.ParseFromString( + device_cert_signer.drm_certificate())) { + return Status(error_space, INVALID_DRM_CERTIFICATE, + "drm-certificate-invalid-signer"); + } + if (model_certificate_.type() != DrmCertificate::DEVICE_MODEL) { + return Status(error_space, INVALID_DRM_CERTIFICATE, + "expected-device-model-certificate-type"); + } + if (!model_certificate_.has_serial_number()) { + return Status(error_space, INVALID_DRM_CERTIFICATE, + "missing-signer-serial-number"); + } + // Check to see if this model certificate is signed by a + // provisioner (entity using Widevine Provisioning Server SDK). + if (device_cert_signer.has_signer()) { + if (!provisioner_certificate_.ParseFromString( + device_cert_signer.signer().drm_certificate())) { + return Status(error_space, INVALID_DRM_CERTIFICATE, + "model-certificate-invalid-signer"); + } + if (provisioner_certificate_.type() != DrmCertificate::PROVISIONER) { + return Status(error_space, INVALID_DRM_CERTIFICATE, + "expected-provisioning-provider-certificate-type"); + } + if (!provisioner_certificate_.has_provider_id() || + provisioner_certificate_.provider_id().empty()) { + return Status(error_space, INVALID_DRM_CERTIFICATE, + "missing-provisioning-service-id"); + } + signed_by_provisioner_ = true; + } + if (!model_certificate_.has_system_id()) { + return Status(error_space, INVALID_DRM_CERTIFICATE, + "model-certificate-missing-system-id"); + } + + switch (device_cert_.algorithm()) { + case DrmCertificate::RSA: + algorithm_ = absl::make_unique(); + break; + case DrmCertificate::ECC_SECP256R1: + case DrmCertificate::ECC_SECP384R1: + case DrmCertificate::ECC_SECP521R1: + algorithm_ = absl::make_unique(); + break; + default: + return Status(error_space, INVALID_DRM_CERTIFICATE, + "unsupported-certificate-algorithm"); + } + + status = algorithm_->Initialize(device_cert_.public_key(), + device_cert_.algorithm()); + if (!status.ok()) { + return status; + } + is_initialized_ = true; + return OkStatus(); +} + +Status CertificateClientCert::VerifySignature( + const std::string& message, const std::string& signature, + ProtocolVersion protocol_version) const { + return algorithm_->VerifySignature( + protocol_version < VERSION_2_2 ? message : Sha512_Hash(message), + signature); +} + +void CertificateClientCert::GenerateSigningKey( + const std::string& message, ProtocolVersion protocol_version) { + signing_key_ = crypto_util::DeriveKey( + key(), crypto_util::kSigningKeyLabel, + protocol_version < VERSION_2_2 ? message : Sha512_Hash(message), + SigningKeyMaterialSizeBits(protocol_version)); +} + +} // namespace widevine diff --git a/common/certificate_client_cert.h b/common/certificate_client_cert.h new file mode 100644 index 0000000..3c92677 --- /dev/null +++ b/common/certificate_client_cert.h @@ -0,0 +1,96 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef COMMON_CERTIFICATE_CLIENT_CERT_H_ +#define COMMON_CERTIFICATE_CLIENT_CERT_H_ + +#include "common/client_cert.h" +#include "protos/public/drm_certificate.pb.h" + +namespace widevine { + +class ClientCertAlgorithm { + public: + ClientCertAlgorithm() = default; + virtual ~ClientCertAlgorithm() = default; + ClientCertAlgorithm(const ClientCertAlgorithm&) = delete; + ClientCertAlgorithm& operator=(const ClientCertAlgorithm&) = delete; + + // Initialize the algorithm module using the |public_key| from the drm + // certificate. The |algorithm| value provides additional information needed + // by the ECC implementation of this interface. + virtual Status Initialize(const std::string& public_key, + DrmCertificate::Algorithm algorithm) = 0; + + // Verify the |signature| of an incoming request |message| using the public + // key from the drm certificate. + virtual Status VerifySignature(const std::string& message, + const std::string& signature) const = 0; + + // Returns the key to be used in key derivation of the license + // protocol. + virtual const std::string& session_key() const = 0; + + // Returns a byte std::string to be included in the SignedMessage::session_key + // field of a license response. This key may be either an encrypted aes key, + // or the bytes of an ephemeral public key. + virtual const std::string& wrapped_session_key() const = 0; +}; + +class CertificateClientCert : public ClientCert { + public: + CertificateClientCert() {} + ~CertificateClientCert() override {} + CertificateClientCert(const CertificateClientCert&) = delete; + CertificateClientCert& operator=(const CertificateClientCert&) = delete; + Status Initialize(const DrmRootCertificate* root_certificate, + const std::string& serialized_certificate); + + Status VerifySignature(const std::string& message, + const std::string& signature, + ProtocolVersion protocol_version) const override; + + void GenerateSigningKey(const std::string& message, + ProtocolVersion protocol_version) override; + + const std::string& encrypted_key() const override { + return algorithm_->wrapped_session_key(); + } + const std::string& key() const override { return algorithm_->session_key(); } + const std::string& serial_number() const override { + return device_cert_.serial_number(); + } + const std::string& service_id() const override { + return provisioner_certificate_.provider_id(); + } + const std::string& signing_key() const override { return signing_key_; } + const std::string& signer_serial_number() const override { + return model_certificate_.serial_number(); + } + uint32_t signer_creation_time_seconds() const override { + return model_certificate_.creation_time_seconds(); + } + bool signed_by_provisioner() const override { return signed_by_provisioner_; } + uint32_t system_id() const override { return model_certificate_.system_id(); } + widevine::ClientIdentification::TokenType type() const override { + return ClientIdentification::DRM_DEVICE_CERTIFICATE; + } + + private: + std::unique_ptr algorithm_; + bool signed_by_provisioner_ = false; + std::string signing_key_; + DrmCertificate model_certificate_; + DrmCertificate device_cert_; + DrmCertificate provisioner_certificate_; + bool is_initialized_ = false; +}; + +} // namespace widevine + +#endif // COMMON_CERTIFICATE_CLIENT_CERT_H_ diff --git a/common/client_cert.cc b/common/client_cert.cc index 4940614..e4d3181 100644 --- a/common/client_cert.cc +++ b/common/client_cert.cc @@ -1,5 +1,5 @@ //////////////////////////////////////////////////////////////////////////////// -// Copyright 2017 Google LLC. +// Copyright 2019 Google LLC. // // This software is licensed under the terms defined in the Widevine Master // License Agreement. For a copy of this agreement, please contact @@ -8,19 +8,19 @@ #include "common/client_cert.h" +#include #include -#include -#include +#include #include "glog/logging.h" -#include "strings/serialize.h" +#include "absl/memory/memory.h" #include "absl/strings/escaping.h" -#include "absl/synchronization/mutex.h" -#include "util/gtl/map_util.h" +#include "common/certificate_client_cert.h" #include "common/crypto_util.h" -#include "common/drm_root_certificate.h" #include "common/error_space.h" +#include "common/keybox_client_cert.h" #include "common/random_util.h" +#include "common/rsa_key.h" #include "common/sha_util.h" #include "common/signing_key_util.h" #include "common/status.h" @@ -30,93 +30,6 @@ #include "protos/public/signed_drm_certificate.pb.h" namespace widevine { -namespace { - -const int kKeyboxSizeBytes = 72; - -} // namespace - -// instead of ClientCert** to explicitly assigning ownership of the created -// object to the caller. - -Status ClientCert::Create(const DrmRootCertificate* root_certificate, - ClientIdentification::TokenType token_type, - const std::string& token, ClientCert** client_cert) { - DCHECK(client_cert); - if (token_type == ClientIdentification::KEYBOX) { - *client_cert = nullptr; - if (token.size() < kKeyboxSizeBytes) { - return Status(error_space, INVALID_KEYBOX_TOKEN, - "keybox-token-is-too-short"); - } - return ClientCert::CreateWithKeybox(token, client_cert); - } else if (token_type == ClientIdentification::DRM_DEVICE_CERTIFICATE) { - return CreateWithDrmCertificate(root_certificate, token, client_cert); - } else { - return Status(error_space, error::UNIMPLEMENTED, - "client-type-not-implemented"); - } -} - -Status ClientCert::CreateWithKeybox(const std::string& keybox_token, - ClientCert** client_cert) { - CHECK(client_cert); - *client_cert = nullptr; - - std::unique_ptr new_client_cert(new KeyboxClientCert); - Status status = new_client_cert->Initialize(keybox_token); - if (!status.ok()) { - return status; - } - - *client_cert = new_client_cert.release(); - return OkStatus(); -} - -Status ClientCert::CreateWithDrmCertificate( - const DrmRootCertificate* root_certificate, const std::string& drm_certificate, - ClientCert** client_cert) { - CHECK(client_cert); - *client_cert = nullptr; - - std::unique_ptr new_client_cert( - new CertificateClientCert); - Status status = - new_client_cert->Initialize(root_certificate, drm_certificate); - if (!status.ok()) { - return status; - } - - *client_cert = new_client_cert.release(); - return OkStatus(); -} - -void ClientCert::CreateSignature(const std::string& message, std::string* signature) { - DCHECK(signature); - DCHECK(!signing_key().empty()); - if (signature == nullptr) { - return; - } - using crypto_util::CreateSignatureHmacSha256; - *signature = - CreateSignatureHmacSha256(GetServerSigningKey(signing_key()), message); -} - -void ClientCert::GenerateSigningKey(const std::string& message, - ProtocolVersion protocol_version) { - if (!signing_key_.empty()) return; - DCHECK(!key().empty()); - using crypto_util::DeriveKey; - using crypto_util::kSigningKeyLabel; - set_signing_key( - DeriveKey(key(), kSigningKeyLabel, - protocol_version < VERSION_2_2 ? message : Sha512_Hash(message), - SigningKeyMaterialSizeBits(protocol_version))); -} - -KeyboxClientCert::KeyboxClientCert() {} - -KeyboxClientCert::~KeyboxClientCert() {} void KeyboxClientCert::SetPreProvisioningKeys( const std::multimap& keymap) { @@ -139,124 +52,50 @@ uint32_t KeyboxClientCert::GetSystemId(const std::string& keybox_bytes) { return WvmTokenHandler::GetSystemId(keybox_bytes); } -Status KeyboxClientCert::Initialize(const std::string& keybox_bytes) { - if (keybox_bytes.size() < kKeyboxSizeBytes) { - return Status(error_space, INVALID_KEYBOX_TOKEN, - "keybox-token-is-too-short"); - } +Status ClientCert::Create( + const DrmRootCertificate* root_certificate, + widevine::ClientIdentification::TokenType token_type, + const std::string& token, std::unique_ptr* client_cert) { + CHECK(client_cert); + Status status; + switch (token_type) { + case ClientIdentification::KEYBOX: + return CreateWithKeybox(token, client_cert); - set_system_id(WvmTokenHandler::GetSystemId(keybox_bytes)); - set_serial_number(WvmTokenHandler::GetEncryptedUniqueId(keybox_bytes)); - bool insecure_keybox = false; - Status status = WvmTokenHandler::DecryptDeviceKey(keybox_bytes, &device_key_, - nullptr, &insecure_keybox); - if (!status.ok()) { - Errors new_code = status.error_code() == error::NOT_FOUND - ? MISSING_PRE_PROV_KEY - : KEYBOX_DECRYPT_ERROR; - return Status(error_space, new_code, status.error_message()); + case ClientIdentification::DRM_DEVICE_CERTIFICATE: + return CreateWithDrmCertificate(root_certificate, token, client_cert); + + default: + return Status(error_space, error::UNIMPLEMENTED, + "client-type-not-implemented"); } return OkStatus(); } -Status KeyboxClientCert::VerifySignature(const std::string& message, - const std::string& signature, - ProtocolVersion protocol_version) { - DCHECK(!signing_key().empty()); - using crypto_util::VerifySignatureHmacSha256; - if (!VerifySignatureHmacSha256( - GetClientSigningKey(signing_key(), protocol_version), signature, - message)) { - return Status(error_space, INVALID_SIGNATURE, "invalid-keybox-mac"); +// Creates a Device Certificate based ClientCert. The |client_cert| is a +// caller supplied unique_ptr to receive the new ClientCert. +Status ClientCert::CreateWithDrmCertificate( + const DrmRootCertificate* root_certificate, + const std::string& drm_certificate, + std::unique_ptr* client_cert) { + CHECK(client_cert); + auto device_cert = absl::make_unique(); + Status status = device_cert->Initialize(root_certificate, drm_certificate); + if (status.ok()) { + *client_cert = std::move(device_cert); } - return OkStatus(); + return status; } -CertificateClientCert::CertificateClientCert() {} - -CertificateClientCert::~CertificateClientCert() {} - -Status CertificateClientCert::Initialize( - const DrmRootCertificate* drm_root_certificate, - const std::string& serialized_certificate) { - CHECK(drm_root_certificate); - - SignedDrmCertificate signed_device_cert; - DrmCertificate device_cert; - Status status = drm_root_certificate->VerifyCertificate( - serialized_certificate, &signed_device_cert, &device_cert); - if (!status.ok()) { - return status; +Status ClientCert::CreateWithKeybox(const std::string& keybox_token, + std::unique_ptr* client_cert) { + CHECK(client_cert); + auto kbx_cert = absl::make_unique(); + Status status = kbx_cert->Initialize(keybox_token); + if (status.ok()) { + *client_cert = std::move(kbx_cert); } - - const SignedDrmCertificate& signer = signed_device_cert.signer(); - DrmCertificate model_certificate; - if (!model_certificate.ParseFromString(signer.drm_certificate())) { - return Status(error_space, INVALID_DRM_CERTIFICATE, - "drm-certificate-invalid-signer"); - } - if (!model_certificate.has_serial_number()) { - return Status(error_space, INVALID_DRM_CERTIFICATE, - "missing-signer-serial-number"); - } - // Check to see if this model certificate is signed by a - // provisioner (entity using Widevine Provisioning Server SDK). - if (signer.has_signer()) { - DrmCertificate provisioner_certificate; - if (!provisioner_certificate.ParseFromString( - signer.signer().drm_certificate())) { - return Status(error_space, INVALID_DRM_CERTIFICATE, - "model-certificate-invalid-signer"); - } - if (provisioner_certificate.type() == DrmCertificate::PROVISIONER) { - set_signed_by_provisioner(true); - } else { - return Status(error_space, INVALID_DRM_CERTIFICATE, - "expected-provisioning-provider-certificate-type"); - } - if (!provisioner_certificate.has_provider_id() || - provisioner_certificate.provider_id().empty()) { - return Status(error_space, INVALID_DRM_CERTIFICATE, - "missing-provisioning-service-id"); - } - set_service_id(provisioner_certificate.provider_id()); - } - set_signer_serial_number(model_certificate.serial_number()); - set_signer_creation_time_seconds(model_certificate.creation_time_seconds()); - if (!model_certificate.has_system_id()) { - return Status(error_space, INVALID_DRM_CERTIFICATE, - "model-certificate-missing-system-id"); - } - set_system_id(model_certificate.system_id()); - set_serial_number(device_cert.serial_number()); - set_public_key(device_cert.public_key()); - rsa_public_key_.reset(RsaPublicKey::Create(public_key())); - if (rsa_public_key_ == nullptr) { - return Status(error_space, INVALID_DRM_CERTIFICATE, - "drm-certificate-public-key-failed"); - } - - // TODO(user): Move this somewhere else. It is license protocol. - set_key(Random16Bytes()); - if (!rsa_public_key_->Encrypt(key(), &encrypted_session_key_)) { - return Status(error_space, ENCRYPT_ERROR, - "drm-certificate-failed-encrypt-session-key"); - } - - return OkStatus(); -} - -Status CertificateClientCert::VerifySignature( - const std::string& message, const std::string& signature, - ProtocolVersion protocol_version) { - CHECK(rsa_public_key_); - - if (!rsa_public_key_->VerifySignature( - protocol_version < VERSION_2_2 ? message : Sha512_Hash(message), - signature)) { - return Status(error_space, INVALID_SIGNATURE, ""); - } - return OkStatus(); + return status; } } // namespace widevine diff --git a/common/client_cert.h b/common/client_cert.h index b2dc786..14b7ba5 100644 --- a/common/client_cert.h +++ b/common/client_cert.h @@ -1,5 +1,5 @@ //////////////////////////////////////////////////////////////////////////////// -// Copyright 2017 Google LLC. +// Copyright 2019 Google LLC. // // This software is licensed under the terms defined in the Widevine Master // License Agreement. For a copy of this agreement, please contact @@ -9,179 +9,67 @@ #ifndef COMMON_CLIENT_CERT_H__ #define COMMON_CLIENT_CERT_H__ -#include #include -#include -#include "common/rsa_key.h" +#include "common/drm_root_certificate.h" #include "common/status.h" #include "protos/public/client_identification.pb.h" #include "protos/public/license_protocol.pb.h" namespace widevine { -class DrmRootCertificate; -class SignedDrmCertificate; - // Handler class for LicenseRequests; validates requests and encrypts licenses. -// TODO(user): Remove extra accessors after Keybox parsing is moved -// to a separate class in KeyboxClientCert class. class ClientCert { + protected: + ClientCert() = default; + public: - virtual ~ClientCert() {} + // Creates a ClientCert from the |token|. The type of ClientCert created is + // determined by the |token_type|. static Status Create( const DrmRootCertificate* root_certificate, widevine::ClientIdentification::TokenType token_type, - const std::string& token, ClientCert** client_cert); - // Creates a Keybox based ClientCert. + const std::string& token, std::unique_ptr* client_cert); + + // Creates a Keybox based ClientCert. The |client_cert| is a caller supplied + // unique_ptr to receive the new ClientCert. static Status CreateWithKeybox(const std::string& keybox_token, - ClientCert** client_cert); + std::unique_ptr* client_cert); + // Creates a Device Certificate based ClientCert. static Status CreateWithDrmCertificate( - const DrmRootCertificate* root_certificate, const std::string& drm_certificate, - ClientCert** client_cert); - // Creates a HMAC SHA256 signature based on the message and the key(). - // signature is owned by the caller and can not be NULL. - virtual void CreateSignature(const std::string& message, std::string* signature); + const DrmRootCertificate* root_certificate, + const std::string& drm_certificate, + std::unique_ptr* client_cert); + + virtual ~ClientCert() = default; + ClientCert(const ClientCert&) = delete; + ClientCert& operator=(const ClientCert&) = delete; + // Checks the passed in signature against a signature created used the // classes information and the passed in message. Returns OK if signature // is valid. - virtual Status VerifySignature(const std::string& message, const std::string& signature, - ProtocolVersion protocol_version) = 0; + virtual Status VerifySignature(const std::string& message, + const std::string& signature, + ProtocolVersion protocol_version) const = 0; + // Creates a signing_key that is accessible using signing_key(). Signing_key // is constructed by doing a key derivation using the key() and message. virtual void GenerateSigningKey(const std::string& message, - ProtocolVersion protocol_version); - // Used to create signing keys. For Keybox token types this is the device key. - // For Device Certificate token types this the session key. - virtual const std::string& key() const = 0; - virtual void set_key(const std::string& key) = 0; + ProtocolVersion protocol_version) = 0; + virtual const std::string& encrypted_key() const = 0; - virtual uint32_t system_id() const { return system_id_; } - virtual const std::string& signing_key() const { return signing_key_; } - virtual const std::string& public_key() const { return public_key_; } - virtual const std::string& serial_number() const { return serial_number_; } - virtual void set_serial_number(const std::string& serial_number) { - serial_number_ = serial_number; - } - virtual const std::string& signer_serial_number() const { - return signer_serial_number_; - } - virtual uint32_t signer_creation_time_seconds() const { - return signer_creation_time_seconds_; - } + virtual const std::string& key() const = 0; + virtual const std::string& serial_number() const = 0; + virtual const std::string& service_id() const = 0; + virtual const std::string& signing_key() const = 0; + virtual const std::string& signer_serial_number() const = 0; + virtual uint32_t signer_creation_time_seconds() const = 0; + virtual bool signed_by_provisioner() const = 0; + virtual uint32_t system_id() const = 0; virtual widevine::ClientIdentification::TokenType type() const = 0; - virtual std::string service_id() const { return service_id_; } - virtual bool signed_by_provisioner() const { return signed_by_provisioner_; } - - protected: - ClientCert() {} - - virtual void set_system_id(uint32_t system_id) { system_id_ = system_id; } - virtual void set_signing_key(const std::string& signing_key) { - signing_key_ = signing_key; - } - virtual void set_service_id(const std::string& service_id) { - service_id_ = service_id; - } - virtual void set_signed_by_provisioner(bool provisioner_signed_flag) { - signed_by_provisioner_ = provisioner_signed_flag; - } - - std::string public_key_; - std::string serial_number_; - std::string signer_serial_number_; - uint32_t signer_creation_time_seconds_ = 0; - bool signed_by_provisioner_ = false; - - private: - uint32_t system_id_ = 0; - std::string signing_key_; - std::string service_id_; - - DISALLOW_COPY_AND_ASSIGN(ClientCert); -}; - -// This class implements the crypto operations based on the Widevine keybox. -// It will unpack token and perform all the crypto operations for securing -// the key material in the license response. -class KeyboxClientCert : public ClientCert { - public: - ~KeyboxClientCert() override; - - // Set the system-wide pre-provisioning keys; argument must be human-readable - // hex digits. - // Must be called before any other method of this class is called, unless - // created by ClientCert::CreateWithPreProvisioningKey(...). - static void SetPreProvisioningKeys(const std::multimap& keys); - static bool IsSystemIdKnown(const uint32_t system_id); - static uint32_t GetSystemId(const std::string& keybox_bytes); - - Status Initialize(const std::string& keybox_bytes); - - Status VerifySignature(const std::string& message, const std::string& signature, - ProtocolVersion protocol_version) override; - const std::string& key() const override { return device_key_; } - void set_key(const std::string& key) override { device_key_ = key; } - const std::string& encrypted_key() const override { return encrypted_device_key_; } - widevine::ClientIdentification::TokenType type() const override { - return widevine::ClientIdentification::KEYBOX; - } - - private: - KeyboxClientCert(); - - friend class ClientCert; - friend class MockKeyboxClientCert; - - std::string device_key_; - std::string encrypted_device_key_; - - DISALLOW_COPY_AND_ASSIGN(KeyboxClientCert); -}; -// This class implements the device certificate operations based on RSA keys. -// It will unpack token and perform all the crypto operations for securing -// the key material in the license response. -using widevine::RsaPublicKey; -class CertificateClientCert : public ClientCert { - public: - ~CertificateClientCert() override; - - Status VerifySignature(const std::string& message, const std::string& signature, - ProtocolVersion protocol_version) override; - const std::string& key() const override { return session_key_; } - void set_key(const std::string& key) override { session_key_ = key; } - const std::string& encrypted_key() const override { - return encrypted_session_key_; - } - widevine::ClientIdentification::TokenType type() const override { - return widevine::ClientIdentification::DRM_DEVICE_CERTIFICATE; - } - - protected: - friend class ClientCert; - friend class MockCertificateClientCert; - Status Initialize(const DrmRootCertificate* drm_root_certificate, - const std::string& serialized_certificate); - virtual void set_public_key(const std::string& public_key) { - public_key_ = public_key; - } - virtual void set_signer_serial_number(const std::string& signer_serial_number) { - signer_serial_number_ = signer_serial_number; - } - virtual void set_signer_creation_time_seconds(uint32_t creation_time_seconds) { - signer_creation_time_seconds_ = creation_time_seconds; - } - - std::string session_key_; - std::string encrypted_session_key_; - std::unique_ptr rsa_public_key_; - - private: - CertificateClientCert(); - - DISALLOW_COPY_AND_ASSIGN(CertificateClientCert); }; } // namespace widevine + #endif // COMMON_CLIENT_CERT_H__ diff --git a/common/client_cert_test.cc b/common/client_cert_test.cc index 7a4b579..ba1f759 100644 --- a/common/client_cert_test.cc +++ b/common/client_cert_test.cc @@ -1,5 +1,5 @@ //////////////////////////////////////////////////////////////////////////////// -// Copyright 2017 Google LLC. +// Copyright 2019 Google LLC. // // This software is licensed under the terms defined in the Widevine Master // License Agreement. For a copy of this agreement, please contact @@ -8,41 +8,42 @@ #include "common/client_cert.h" -#include #include -#include -#include -#include "glog/logging.h" #include "testing/gmock.h" #include "testing/gunit.h" #include "absl/strings/escaping.h" -#include "absl/strings/str_cat.h" -#include "absl/synchronization/mutex.h" -#include "absl/time/clock.h" -#include "absl/time/time.h" -#include "common/drm_root_certificate.h" +#include "common/ec_test_keys.h" #include "common/error_space.h" +#include "common/keybox_client_cert.h" +#include "common/rsa_key.h" #include "common/rsa_test_keys.h" #include "common/sha_util.h" +#include "common/status.h" #include "common/test_drm_certificates.h" #include "common/wvm_test_keys.h" #include "protos/public/drm_certificate.pb.h" #include "protos/public/errors.pb.h" #include "protos/public/signed_drm_certificate.pb.h" +using widevine::ClientCert; +using widevine::ClientIdentification; +using widevine::Status; + +namespace widevine { +const DrmCertificate::Type kNoSigner = DrmCertificate::ROOT; +const DrmCertificate::Type kDeviceModelSigner = DrmCertificate::DEVICE_MODEL; +const DrmCertificate::Type kProvisionerSigner = DrmCertificate::PROVISIONER; + // TODO(user): Change these tests to use on-the-fly generated intermediate // and device certificates based on RsaTestKeys. // TODO(user): Add testcase(s) CreateSignature, // and GenerateSigningKey. -namespace widevine { - -using ::testing::_; -using ::testing::Return; - -class ClientCertTest : public ::testing::Test { +class ClientCertTest + : public ::testing::TestWithParam { public: + ~ClientCertTest() override = default; void SetUp() override { if (!setup_preprov_keys_) { KeyboxClientCert::SetPreProvisioningKeys( @@ -82,7 +83,7 @@ class ClientCertTest : public ::testing::Test { : certificate_(certificate), expected_serial_number_(expected_serial_number), expected_system_id_(expected_system_id), - expected_status_(std::move(expected_status)) {} + expected_status_(expected_status) {} }; void TestBasicValidation(const TestTokenAndKeys& expectation, @@ -91,32 +92,41 @@ class ClientCertTest : public ::testing::Test { void TestBasicValidationDrmCertificate( const TestCertificateAndData& expectation, const bool compare_data); - void GenerateSignature(const std::string& message, const std::string& private_key, + void GenerateSignature(const std::string& message, + const std::string& private_key, std::string* signature); SignedDrmCertificate* SignCertificate(const DrmCertificate& certificate, SignedDrmCertificate* signer, const std::string& private_key); - DrmCertificate* GenerateProvisionerCertificate(uint32_t system_id, - const std::string& serial_number, - const std::string& provider_id); + DrmCertificate* GenerateProvisionerCertificate( + uint32_t system_id, const std::string& serial_number, + const std::string& provider_id); SignedDrmCertificate* GenerateSignedProvisionerCertificate( - uint32_t system_id, const std::string& serial_number, const std::string& service_id); - DrmCertificate* GenerateIntermediateCertificate(uint32_t system_id, - const std::string& serial_number); + uint32_t system_id, const std::string& serial_number, + const std::string& service_id); + DrmCertificate* GenerateIntermediateCertificate( + uint32_t system_id, const std::string& serial_number); SignedDrmCertificate* GenerateSignedIntermediateCertificate( SignedDrmCertificate* signer, uint32_t system_id, - const std::string& serial_number); - DrmCertificate* GenerateDrmCertificate(uint32_t system_id, - const std::string& serial_number); + const std::string& serial_number, DrmCertificate::Type signer_cert_type); + DrmCertificate* GenerateDrmCertificate( + uint32_t system_id, const std::string& serial_number, + DrmCertificate::Algorithm = DrmCertificate::RSA); SignedDrmCertificate* GenerateSignedDrmCertificate( SignedDrmCertificate* signer, uint32_t system_id, - const std::string& serial_number); + const std::string& serial_number, + DrmCertificate::Algorithm = DrmCertificate::RSA); + + std::string GetPublicKeyByCertType(DrmCertificate::Type cert_type); + std::string GetPrivateKeyByCertType(DrmCertificate::Type cert_type); + std::string GetECCPublicKey(DrmCertificate::Algorithm algorithm); RsaTestKeys test_rsa_keys_; TestDrmCertificates test_drm_certs_; std::unique_ptr root_cert_; static bool setup_preprov_keys_; }; + bool ClientCertTest::setup_preprov_keys_(false); void ClientCertTest::TestBasicValidation(const TestTokenAndKeys& expectation, @@ -124,32 +134,22 @@ void ClientCertTest::TestBasicValidation(const TestTokenAndKeys& expectation, const bool compare_device_key) { // Test validation of a valid request. Status status; - ClientCert* client_cert_ptr = nullptr; + std::unique_ptr keybox_cert; - // Two ways to create a client cert object, test both. - for (int i = 0; i < 2; i++) { - if (i == 0) { - status = - ClientCert::Create(root_cert_.get(), ClientIdentification::KEYBOX, - expectation.token_, &client_cert_ptr); - } else { - status = - ClientCert::CreateWithKeybox(expectation.token_, &client_cert_ptr); - } - std::unique_ptr keybox_cert(client_cert_ptr); - if (expect_success) { - ASSERT_EQ(OkStatus(), status); - ASSERT_TRUE(keybox_cert.get()); - EXPECT_EQ(expectation.expected_system_id_, keybox_cert->system_id()); - EXPECT_EQ(expectation.expected_serial_number_, - keybox_cert->serial_number()); - if (compare_device_key) { - EXPECT_EQ(expectation.expected_device_key_, keybox_cert->key()); - } - } else { - EXPECT_NE(OkStatus(), status); - EXPECT_FALSE(keybox_cert); + status = ClientCert::Create(root_cert_.get(), ClientIdentification::KEYBOX, + expectation.token_, &keybox_cert); + if (expect_success) { + ASSERT_EQ(OkStatus(), status); + ASSERT_TRUE(keybox_cert.get()); + EXPECT_EQ(expectation.expected_system_id_, keybox_cert->system_id()); + EXPECT_EQ(expectation.expected_serial_number_, + keybox_cert->serial_number()); + if (compare_device_key) { + EXPECT_EQ(expectation.expected_device_key_, keybox_cert->key()); } + } else { + EXPECT_NE(OkStatus(), status); + EXPECT_FALSE(keybox_cert); } } @@ -162,11 +162,10 @@ void ClientCertTest::TestBasicValidationDrmCertificate( // Test validation of a valid request. Status status; - ClientCert* client_cert_ptr = nullptr; + std::unique_ptr drm_certificate_cert; status = ClientCert::Create(root_cert_.get(), ClientIdentification::DRM_DEVICE_CERTIFICATE, - expectation.certificate_, &client_cert_ptr); - std::unique_ptr drm_certificate_cert(client_cert_ptr); + expectation.certificate_, &drm_certificate_cert); ASSERT_EQ(expectation.expected_status_, status); if (expectation.expected_status_.ok()) { ASSERT_TRUE(drm_certificate_cert.get()); @@ -205,13 +204,48 @@ SignedDrmCertificate* ClientCertTest::SignCertificate( return signed_certificate.release(); } +std::string ClientCertTest::GetPublicKeyByCertType( + DrmCertificate::Type cert_type) { + if (cert_type == DrmCertificate::DEVICE) { + return test_rsa_keys_.public_test_key_3_2048_bits(); + } else if (cert_type == DrmCertificate::DEVICE_MODEL) { + return test_rsa_keys_.public_test_key_2_2048_bits(); + } + return test_rsa_keys_.public_test_key_1_3072_bits(); +} + +std::string ClientCertTest::GetPrivateKeyByCertType( + DrmCertificate::Type cert_type) { + if (cert_type == DrmCertificate::DEVICE) { + return test_rsa_keys_.private_test_key_3_2048_bits(); + } else if (cert_type == DrmCertificate::DEVICE_MODEL) { + return test_rsa_keys_.private_test_key_2_2048_bits(); + } + return test_rsa_keys_.private_test_key_1_3072_bits(); +} + +std::string ClientCertTest::GetECCPublicKey( + DrmCertificate::Algorithm algorithm) { + ECTestKeys keys; + switch (algorithm) { + case DrmCertificate::ECC_SECP256R1: + return keys.public_test_key_1_secp256r1(); + case DrmCertificate::ECC_SECP384R1: + return keys.public_test_key_1_secp384r1(); + case DrmCertificate::ECC_SECP521R1: + return keys.public_test_key_1_secp521r1(); + default: + return ""; + } +} + DrmCertificate* ClientCertTest::GenerateIntermediateCertificate( uint32_t system_id, const std::string& serial_number) { std::unique_ptr intermediate_certificate(new DrmCertificate); intermediate_certificate->set_type(DrmCertificate::DEVICE_MODEL); intermediate_certificate->set_serial_number(serial_number); intermediate_certificate->set_public_key( - test_rsa_keys_.public_test_key_2_2048_bits()); + GetPublicKeyByCertType(DrmCertificate::DEVICE_MODEL)); intermediate_certificate->set_system_id(system_id); intermediate_certificate->set_creation_time_seconds(1234); return intermediate_certificate.release(); @@ -219,42 +253,49 @@ DrmCertificate* ClientCertTest::GenerateIntermediateCertificate( SignedDrmCertificate* ClientCertTest::GenerateSignedIntermediateCertificate( SignedDrmCertificate* signer, uint32_t system_id, - const std::string& serial_number) { + const std::string& serial_number, DrmCertificate::Type signer_cert_type) { std::unique_ptr intermediate_certificate( GenerateIntermediateCertificate(system_id, serial_number)); + // Must use the same key pair used by GenerateIntermediateCertificate(). return SignCertificate(*intermediate_certificate, signer, - test_rsa_keys_.private_test_key_1_3072_bits()); + GetPrivateKeyByCertType(signer_cert_type)); } DrmCertificate* ClientCertTest::GenerateDrmCertificate( - uint32_t system_id, const std::string& serial_number) { + uint32_t system_id, const std::string& serial_number, + DrmCertificate::Algorithm algorithm) { std::unique_ptr drm_certificate(new DrmCertificate); drm_certificate->set_type(DrmCertificate::DEVICE); drm_certificate->set_serial_number(serial_number); drm_certificate->set_system_id(system_id); - drm_certificate->set_public_key(test_rsa_keys_.public_test_key_3_2048_bits()); + drm_certificate->set_public_key( + algorithm == DrmCertificate::RSA + ? GetPublicKeyByCertType(DrmCertificate::DEVICE) + : GetECCPublicKey(algorithm)); drm_certificate->set_creation_time_seconds(4321); + drm_certificate->set_algorithm(algorithm); return drm_certificate.release(); } SignedDrmCertificate* ClientCertTest::GenerateSignedDrmCertificate( SignedDrmCertificate* signer, uint32_t system_id, - const std::string& serial_number) { + const std::string& serial_number, DrmCertificate::Algorithm algorithm) { std::unique_ptr drm_certificate( - GenerateDrmCertificate(system_id, serial_number)); - std::unique_ptr signed_drm_certificate(SignCertificate( - *drm_certificate, signer, test_rsa_keys_.private_test_key_2_2048_bits())); + GenerateDrmCertificate(system_id, serial_number, algorithm)); + std::unique_ptr signed_drm_certificate( + SignCertificate(*drm_certificate, signer, + GetPrivateKeyByCertType(DrmCertificate::DEVICE_MODEL))); return signed_drm_certificate.release(); } DrmCertificate* ClientCertTest::GenerateProvisionerCertificate( - uint32_t system_id, const std::string& serial_number, const std::string& provider_id) { + uint32_t system_id, const std::string& serial_number, + const std::string& provider_id) { std::unique_ptr provisioner_certificate(new DrmCertificate); provisioner_certificate->set_type(DrmCertificate::PROVISIONER); provisioner_certificate->set_serial_number(serial_number); - // TODO(user): Need to generate 3072 bit test for provisioner certificates. provisioner_certificate->set_public_key( - test_rsa_keys_.public_test_key_1_3072_bits()); + GetPublicKeyByCertType(DrmCertificate::DrmCertificate::PROVISIONER)); provisioner_certificate->set_system_id(system_id); provisioner_certificate->set_provider_id(provider_id); provisioner_certificate->set_creation_time_seconds(1234); @@ -262,11 +303,12 @@ DrmCertificate* ClientCertTest::GenerateProvisionerCertificate( } SignedDrmCertificate* ClientCertTest::GenerateSignedProvisionerCertificate( - uint32_t system_id, const std::string& serial_number, const std::string& service_id) { + uint32_t system_id, const std::string& serial_number, + const std::string& service_id) { std::unique_ptr provisioner_certificate( GenerateProvisionerCertificate(system_id, serial_number, service_id)); return SignCertificate(*provisioner_certificate, nullptr, - test_rsa_keys_.private_test_key_1_3072_bits()); + GetPrivateKeyByCertType(DrmCertificate::ROOT)); } TEST_F(ClientCertTest, BasicValidation) { @@ -296,19 +338,25 @@ TEST_F(ClientCertTest, BasicValidation) { KeyboxClientCert::GetSystemId(kValidTokenAndExpectedKeys[0].token_)); } -TEST_F(ClientCertTest, BasicCertValidation) { +TEST_P(ClientCertTest, BasicCertValidation) { const uint32_t system_id = 1234; const std::string serial_number("serial_number"); std::unique_ptr signed_cert( - GenerateSignedDrmCertificate(GenerateSignedIntermediateCertificate( - nullptr, system_id, serial_number), - system_id, serial_number + "-device")); + GenerateSignedDrmCertificate( + GenerateSignedIntermediateCertificate(nullptr, system_id, + serial_number, kNoSigner), + system_id, serial_number + "-device", GetParam())); const TestCertificateAndData kValidCertificateAndExpectedData( signed_cert->SerializeAsString(), serial_number, system_id, OkStatus()); const bool compare_data = true; TestBasicValidationDrmCertificate(kValidCertificateAndExpectedData, compare_data); } +INSTANTIATE_TEST_SUITE_P(BasicCertValidation, ClientCertTest, + testing::Values(DrmCertificate::RSA, + DrmCertificate::ECC_SECP256R1, + DrmCertificate::ECC_SECP384R1, + DrmCertificate::ECC_SECP521R1)); TEST_F(ClientCertTest, InvalidKeybox) { const TestTokenAndKeys kInvalidTokenAndExpectedKeys[] = { @@ -350,18 +398,19 @@ TEST_F(ClientCertTest, InvalidCertificate) { GenerateSignature(invalid_drm_cert->drm_certificate(), test_rsa_keys_.private_test_key_2_2048_bits(), invalid_drm_cert->mutable_signature()); - invalid_drm_cert->set_allocated_signer( - GenerateSignedIntermediateCertificate(nullptr, system_id, signer_sn)); + invalid_drm_cert->set_allocated_signer(GenerateSignedIntermediateCertificate( + nullptr, system_id, signer_sn, kNoSigner)); // Invalid device public key. dev_cert.reset(GenerateDrmCertificate(system_id, device_sn)); dev_cert->set_public_key("bad-device-public-key"); - std::unique_ptr bad_device_public_key(SignCertificate( - *dev_cert, - GenerateSignedIntermediateCertificate(nullptr, system_id, signer_sn), - test_rsa_keys_.private_test_key_2_2048_bits())); + std::unique_ptr bad_device_public_key( + SignCertificate(*dev_cert, + GenerateSignedIntermediateCertificate( + nullptr, system_id, signer_sn, kNoSigner), + test_rsa_keys_.private_test_key_2_2048_bits())); // Invalid serialized intermediate certificate. - signed_signer.reset( - GenerateSignedIntermediateCertificate(nullptr, system_id, signer_sn)); + signed_signer.reset(GenerateSignedIntermediateCertificate( + nullptr, system_id, signer_sn, kNoSigner)); signed_signer->set_drm_certificate("bad-serialized-cert"); GenerateSignature(signed_signer->drm_certificate(), test_rsa_keys_.private_test_key_1_3072_bits(), @@ -382,7 +431,8 @@ TEST_F(ClientCertTest, InvalidCertificate) { // Invalid device certificate signature. std::unique_ptr bad_device_signature( GenerateSignedDrmCertificate( - GenerateSignedIntermediateCertificate(nullptr, system_id, signer_sn), + GenerateSignedIntermediateCertificate(nullptr, system_id, signer_sn, + kNoSigner), system_id, device_sn)); bad_device_signature->set_signature("bad-signature"); // Missing model system ID. @@ -405,8 +455,8 @@ TEST_F(ClientCertTest, InvalidCertificate) { test_rsa_keys_.private_test_key_2_2048_bits())); // Invalid serialized intermediate certificate. dev_cert.reset(GenerateDrmCertificate(system_id, device_sn)); - signed_signer.reset( - GenerateSignedIntermediateCertificate(nullptr, system_id, signer_sn)); + signed_signer.reset(GenerateSignedIntermediateCertificate( + nullptr, system_id, signer_sn, kNoSigner)); signed_signer->set_signature("bad-signature"); std::unique_ptr bad_signer_signature( SignCertificate(*dev_cert, signed_signer.release(), @@ -453,8 +503,9 @@ TEST_F(ClientCertTest, MissingPreProvKey) { "00000002012345678e1ebfe037828096ca6538b4f6f4bcb51c2b7191cf037e98" "beaa24924907e128f9ff49b54a165cd9c33e6547537eb4d29fb7e8df3c2c1cd9" "2517a12f4922953e")); - ClientCert* client_cert_ptr = nullptr; - Status status = ClientCert::CreateWithKeybox(token, &client_cert_ptr); + std::unique_ptr client_cert_ptr; + Status status = ClientCert::Create( + root_cert_.get(), ClientIdentification::KEYBOX, token, &client_cert_ptr); ASSERT_EQ(MISSING_PRE_PROV_KEY, status.error_code()); } @@ -470,9 +521,9 @@ TEST_F(ClientCertTest, ValidProvisionerDeviceCert) { service_id)); std::unique_ptr signed_intermediate_cert( - GenerateSignedIntermediateCertificate(signed_provisioner_cert.release(), - system_id, - intermediate_serial_number)); + GenerateSignedIntermediateCertificate( + signed_provisioner_cert.release(), system_id, + intermediate_serial_number, kProvisionerSigner)); std::unique_ptr signed_device_cert( GenerateSignedDrmCertificate(signed_intermediate_cert.release(), @@ -480,13 +531,12 @@ TEST_F(ClientCertTest, ValidProvisionerDeviceCert) { std::string serialized_cert; signed_device_cert->SerializeToString(&serialized_cert); - ClientCert* client_cert_ptr = nullptr; + std::unique_ptr drm_cert; EXPECT_OK(ClientCert::Create(root_cert_.get(), ClientIdentification::DRM_DEVICE_CERTIFICATE, - serialized_cert, &client_cert_ptr)); - ASSERT_TRUE(client_cert_ptr != nullptr); - std::unique_ptr drm_cert(client_cert_ptr); + serialized_cert, &drm_cert)); + ASSERT_TRUE(drm_cert); EXPECT_EQ(service_id, drm_cert->service_id()); EXPECT_EQ(device_serial_number, drm_cert->serial_number()); @@ -506,9 +556,9 @@ TEST_F(ClientCertTest, InvalidProvisionerDeviceCertEmptyServiceId) { service_id)); std::unique_ptr signed_intermediate_cert( - GenerateSignedIntermediateCertificate(signed_provisioner_cert.release(), - system_id, - intermediate_serial_number)); + GenerateSignedIntermediateCertificate( + signed_provisioner_cert.release(), system_id, + intermediate_serial_number, kProvisionerSigner)); std::unique_ptr signed_device_cert( GenerateSignedDrmCertificate(signed_intermediate_cert.release(), @@ -516,7 +566,7 @@ TEST_F(ClientCertTest, InvalidProvisionerDeviceCertEmptyServiceId) { std::string serialized_cert; signed_device_cert->SerializeToString(&serialized_cert); - ClientCert* client_cert_ptr = nullptr; + std::unique_ptr client_cert_ptr; EXPECT_EQ("missing-provisioning-service-id", ClientCert::Create(root_cert_.get(), @@ -535,27 +585,119 @@ TEST_F(ClientCertTest, InvalidProvisionerDeviceCertChain) { const std::string intermediate_serial_number2("intermediate-serial-number-2"); std::unique_ptr signed_intermediate_cert2( - GenerateSignedIntermediateCertificate(nullptr, system_id2, - intermediate_serial_number2)); + GenerateSignedIntermediateCertificate( + nullptr, system_id2, intermediate_serial_number2, kNoSigner)); // Instead of using a provisioner certificate to sign this intermediate // certificate, use another intermediate certificate. This is an invalid // chain and should generate an error when trying to create a client // certificate. std::unique_ptr signed_intermediate_cert( - GenerateSignedIntermediateCertificate(signed_intermediate_cert2.release(), - system_id, - intermediate_serial_number)); + GenerateSignedIntermediateCertificate( + signed_intermediate_cert2.release(), system_id, + intermediate_serial_number, kDeviceModelSigner)); std::unique_ptr signed_device_cert( GenerateSignedDrmCertificate(signed_intermediate_cert.release(), system_id, device_serial_number)); std::string serialized_cert; signed_device_cert->SerializeToString(&serialized_cert); - ClientCert* client_cert_ptr = nullptr; + std::unique_ptr client_cert_ptr; - // TODO(user): Fix this test. It is failing for the right reasons, but the - // certificate chain is broken (intermediate signature does not match signer). - ASSERT_EQ("cache-miss-invalid-signature", + ASSERT_EQ("expected-provisioning-provider-certificate-type", + ClientCert::Create(root_cert_.get(), + ClientIdentification::DRM_DEVICE_CERTIFICATE, + serialized_cert, &client_cert_ptr) + .error_message()); + EXPECT_FALSE(client_cert_ptr); +} + +TEST_F(ClientCertTest, InvalidDeviceCertChainSize_TooLong) { + const uint32_t system_id = 5000; + const std::string service_id("widevine_test.com"); + const std::string device_serial_number("device-serial-number"); + const std::string intermediate_serial_number1("intermediate-serial-number-1"); + const std::string intermediate_serial_number2("intermediate-serial-number-2"); + const std::string provisioner_serial_number("provisioner-serial-number"); + + std::unique_ptr signed_provisioner_cert( + GenerateSignedProvisionerCertificate(system_id, provisioner_serial_number, + service_id)); + + std::unique_ptr signed_intermediate_cert1( + GenerateSignedIntermediateCertificate( + signed_provisioner_cert.release(), system_id, + intermediate_serial_number1, kProvisionerSigner)); + + std::unique_ptr signed_intermediate_cert2( + GenerateSignedIntermediateCertificate( + signed_intermediate_cert1.release(), system_id, + intermediate_serial_number2, kDeviceModelSigner)); + + std::unique_ptr signed_device_cert( + GenerateSignedDrmCertificate(signed_intermediate_cert2.release(), + system_id, device_serial_number)); + + std::string serialized_cert; + signed_device_cert->SerializeToString(&serialized_cert); + std::unique_ptr client_cert_ptr = nullptr; + + ASSERT_EQ("certificate-chain-size-exceeded", + ClientCert::Create(root_cert_.get(), + ClientIdentification::DRM_DEVICE_CERTIFICATE, + serialized_cert, &client_cert_ptr) + .error_message()); + EXPECT_FALSE(client_cert_ptr); +} + +TEST_F(ClientCertTest, DeviceCertTypeNotLeaf) { + const uint32_t system_id = 5000; + const std::string service_id("widevine_test.com"); + const std::string intermediate_serial_number("intermediate-serial-number"); + const std::string provisioner_serial_number("provisioner-serial-number"); + const std::string drm_serial_number("drm-serial-number"); + + std::unique_ptr signed_provisioner_cert( + GenerateSignedProvisionerCertificate(system_id, provisioner_serial_number, + service_id)); + + // Use a DEVICE certificate as the intermediate certificate. + std::unique_ptr signed_intermediate_cert( + GenerateSignedDrmCertificate(signed_provisioner_cert.release(), system_id, + intermediate_serial_number)); + + std::unique_ptr signed_drm_cert( + GenerateSignedDrmCertificate(signed_intermediate_cert.release(), + system_id, drm_serial_number)); + std::string serialized_cert; + signed_drm_cert->SerializeToString(&serialized_cert); + std::unique_ptr client_cert_ptr; + + EXPECT_EQ("device-cert-must-be-leaf", + ClientCert::Create(root_cert_.get(), + ClientIdentification::DRM_DEVICE_CERTIFICATE, + serialized_cert, &client_cert_ptr) + .error_message()); + EXPECT_FALSE(client_cert_ptr); +} + +TEST_F(ClientCertTest, InvalidLeafCertificateType) { + const uint32_t system_id = 5000; + const std::string service_id("widevine_test.com"); + const std::string intermediate_serial_number("intermediate-serial-number"); + const std::string provisioner_serial_number("provisioner-serial-number"); + + std::unique_ptr signed_provisioner_cert( + GenerateSignedProvisionerCertificate(system_id, provisioner_serial_number, + service_id)); + std::unique_ptr signed_intermediate_cert( + GenerateSignedIntermediateCertificate( + signed_provisioner_cert.release(), system_id, + intermediate_serial_number, kProvisionerSigner)); + std::string serialized_cert; + signed_intermediate_cert->SerializeToString(&serialized_cert); + std::unique_ptr client_cert_ptr; + // Leaf certificate must be a device certificate. + EXPECT_EQ("expected-device-certificate-type", ClientCert::Create(root_cert_.get(), ClientIdentification::DRM_DEVICE_CERTIFICATE, serialized_cert, &client_cert_ptr) @@ -566,11 +708,10 @@ TEST_F(ClientCertTest, InvalidProvisionerDeviceCertChain) { TEST_F(ClientCertTest, Protocol21WithDrmCert) { const char message[] = "A weekend wasted is a weekend well spent."; - ClientCert* client_cert_ptr = nullptr; + std::unique_ptr client_cert; ASSERT_OK(ClientCert::Create( root_cert_.get(), ClientIdentification::DRM_DEVICE_CERTIFICATE, - test_drm_certs_.test_user_device_certificate(), &client_cert_ptr)); - std::unique_ptr client_cert(client_cert_ptr); + test_drm_certs_.test_user_device_certificate(), &client_cert)); std::unique_ptr private_key( RsaPrivateKey::Create(test_rsa_keys_.private_test_key_3_2048_bits())); @@ -592,11 +733,10 @@ TEST_F(ClientCertTest, Protocol22WithDrmCert) { const char message[] = "There is nothing permanent except change."; const std::string message_hash(Sha512_Hash(message)); - ClientCert* client_cert_ptr = nullptr; + std::unique_ptr client_cert; ASSERT_OK(ClientCert::Create( root_cert_.get(), ClientIdentification::DRM_DEVICE_CERTIFICATE, - test_drm_certs_.test_user_device_certificate(), &client_cert_ptr)); - std::unique_ptr client_cert(client_cert_ptr); + test_drm_certs_.test_user_device_certificate(), &client_cert)); std::unique_ptr private_key( RsaPrivateKey::Create(test_rsa_keys_.private_test_key_3_2048_bits())); diff --git a/common/client_id_util.cc b/common/client_id_util.cc index 327a3bf..f9ae933 100644 --- a/common/client_id_util.cc +++ b/common/client_id_util.cc @@ -10,9 +10,13 @@ #include "glog/logging.h" #include "common/aes_cbc_util.h" +#include "common/client_cert.h" #include "common/drm_service_certificate.h" #include "common/error_space.h" +#include "common/keybox_client_cert.h" +#include "protos/public/drm_certificate.pb.h" #include "protos/public/errors.pb.h" +#include "protos/public/signed_drm_certificate.pb.h" namespace widevine { @@ -37,12 +41,13 @@ bool SetClientInfo(ClientIdentification* client_id, absl::string_view name, } std::string GetClientInfo(const ClientIdentification& client_id, - absl::string_view name) { + absl::string_view name) { return GetClientInfo(client_id, name, std::string()); } std::string GetClientInfo(const ClientIdentification& client_id, - absl::string_view name, const std::string& default_value) { + absl::string_view name, + const std::string& default_value) { for (const auto& nv : client_id.client_info()) { if (nv.name() == name) { return nv.value(); @@ -86,4 +91,28 @@ Status DecryptEncryptedClientIdentification( return OkStatus(); } +uint32_t GetSystemId(const ClientIdentification& client_id) { + uint32_t system_id = 0; + if (client_id.has_token()) { + switch (client_id.type()) { + case ClientIdentification::KEYBOX: + system_id = KeyboxClientCert::GetSystemId(client_id.token()); + break; + case ClientIdentification::DRM_DEVICE_CERTIFICATE: { + SignedDrmCertificate signed_drm_certificate; + if (signed_drm_certificate.ParseFromString(client_id.token())) { + DrmCertificate drm_certificate; + if (drm_certificate.ParseFromString( + signed_drm_certificate.drm_certificate())) { + system_id = drm_certificate.system_id(); + } + } + } break; + default: + break; + } + } + return system_id; +} + } // namespace widevine diff --git a/common/client_id_util.h b/common/client_id_util.h index 6aef13d..df83733 100644 --- a/common/client_id_util.h +++ b/common/client_id_util.h @@ -32,12 +32,13 @@ bool SetClientInfo(ClientIdentification* client_id, absl::string_view name, // Return the value from client_id.client_info() matching the given name, // or the empty std::string if not found. std::string GetClientInfo(const ClientIdentification& client_id, - absl::string_view name); + absl::string_view name); // Return the value from client_id.client_info() matching the given name, // or the given default value if not found. std::string GetClientInfo(const ClientIdentification& client_id, - absl::string_view name, const std::string& default_value); + absl::string_view name, + const std::string& default_value); // Decrypts the encrypted client identification in |encrypted_client_id| into // |client_id| using the private key for the service certificate which was @@ -56,6 +57,8 @@ Status DecryptEncryptedClientIdentification( const EncryptedClientIdentification& encrypted_client_id, const std::string& privacy_key, ClientIdentification* client_id); +uint32_t GetSystemId(const ClientIdentification& client_id); + } // namespace widevine #endif // COMMON_CLIENT_ID_UTIL_H_ diff --git a/common/content_id_util.cc b/common/content_id_util.cc new file mode 100644 index 0000000..8c7e170 --- /dev/null +++ b/common/content_id_util.cc @@ -0,0 +1,64 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +#include "common/content_id_util.h" + +#include "common/error_space.h" +#include "common/status.h" +#include "license_server_sdk/internal/parse_content_id.h" +#include "protos/public/errors.pb.h" +#include "protos/public/external_license.pb.h" +#include "protos/public/license_protocol.pb.h" +#include "protos/public/license_server_sdk.pb.h" +#include "protos/public/widevine_pssh.pb.h" + +namespace widevine { + +// TODO(user): Move the util methods from +// //license_server_sdk/internal/parse_content_id.h +// into this file. + +Status GetContentIdFromExternalLicenseRequest( + const ExternalLicenseRequest& external_license_request, + std::string* content_id) { + LicenseRequest::ContentIdentification content_identification = + external_license_request.content_id(); + WidevinePsshData widevine_pssh_data; + if (content_identification.has_widevine_pssh_data()) { + widevine_pssh_data.ParseFromString( + content_identification.widevine_pssh_data().pssh_data(0)); + } else if (content_identification.has_webm_key_id()) { + widevine_pssh_data.ParseFromString( + content_identification.webm_key_id().header()); + } else if (content_identification.has_init_data()) { + ContentInfo content_info; + if (ParseContentId(content_identification, &content_info).ok()) { + widevine_pssh_data = + content_info.content_info_entry(0).pssh().widevine_data(); + } + } + *content_id = widevine_pssh_data.content_id(); + return OkStatus(); +} + +Status GetContentIdFromSignedExternalLicenseRequest( + const SignedMessage& signed_message, std::string* content_id) { + if (signed_message.type() != SignedMessage::EXTERNAL_LICENSE_REQUEST) { + return Status( + error_space, error::INVALID_ARGUMENT, + "Unexpected SignedMessage Type. EXTERNAL_LICENSE_REQUEST expected"); + } + ExternalLicenseRequest external_license_request; + if (!external_license_request.ParseFromString(signed_message.msg())) { + return Status(error_space, EXTERNAL_LICENSE_REQUEST_PARSE_ERROR, + "Unable to parse into External License Request"); + } + return GetContentIdFromExternalLicenseRequest(external_license_request, + content_id); +} + +} // namespace widevine diff --git a/common/content_id_util.h b/common/content_id_util.h new file mode 100644 index 0000000..7272982 --- /dev/null +++ b/common/content_id_util.h @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef COMMON_CONTENT_ID_UTIL_H_ +#define COMMON_CONTENT_ID_UTIL_H_ + +#include "common/status.h" +#include "protos/public/external_license.pb.h" +#include "protos/public/license_protocol.pb.h" + +namespace widevine { + +// Get content identifier as a std::string from a SignedMessage that includes a +// serialized ExternalLicenseRequest. +Status GetContentIdFromSignedExternalLicenseRequest( + const SignedMessage& signed_message, std::string* content_id); + +// Get content identifier as a std::string from an ExternalLicenseRequest. +Status GetContentIdFromExternalLicenseRequest( + const ExternalLicenseRequest& external_license_request, + std::string* content_id); + +} // namespace widevine + +#endif // COMMON_CONTENT_ID_UTIL_H_ diff --git a/common/content_id_util_test.cc b/common/content_id_util_test.cc new file mode 100644 index 0000000..a2f82ea --- /dev/null +++ b/common/content_id_util_test.cc @@ -0,0 +1,81 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#include "common/content_id_util.h" + +#include "testing/gmock.h" +#include "testing/gunit.h" +#include "protos/public/errors.pb.h" +#include "protos/public/external_license.pb.h" +#include "protos/public/license_protocol.pb.h" +#include "protos/public/widevine_pssh.pb.h" + +namespace { +const char kContentId[] = "TestContentId"; +const char kPlayReadyChallenge[] = ""; +} // namespace + +namespace widevine { + +// Builds a SignedMessage that includes an ExternalLicenseRequest. +SignedMessage BuildSignedExternalLicenseRequest( + const ExternalLicenseRequest::RequestType type, const std::string& request, + const std::string& content_id) { + ExternalLicenseRequest external_license_request; + external_license_request.set_request_type(type); + external_license_request.set_request(request); + LicenseRequest::ContentIdentification::WidevinePsshData* cenc_id = + external_license_request.mutable_content_id() + ->mutable_widevine_pssh_data(); + WidevinePsshData widevine_pssh_data; + widevine_pssh_data.set_content_id(content_id); + std::string widevine_pssh_string; + widevine_pssh_data.SerializeToString(&widevine_pssh_string); + cenc_id->add_pssh_data(widevine_pssh_string); + SignedMessage signed_message; + signed_message.set_type(SignedMessage::EXTERNAL_LICENSE_REQUEST); + EXPECT_TRUE( + external_license_request.SerializeToString(signed_message.mutable_msg())); + return signed_message; +} + +TEST(ContentIdUtil, GetContentId) { + std::string content_id; + EXPECT_OK(GetContentIdFromSignedExternalLicenseRequest( + BuildSignedExternalLicenseRequest( + ExternalLicenseRequest::PLAYREADY_LICENSE_REQUEST, + kPlayReadyChallenge, kContentId), + &content_id)); + EXPECT_EQ(kContentId, content_id); +} + +TEST(ContentIdUtil, GetContentIdFailureWithIncorrectType) { + std::string content_id; + SignedMessage signed_message = BuildSignedExternalLicenseRequest( + ExternalLicenseRequest::PLAYREADY_LICENSE_REQUEST, kPlayReadyChallenge, + kContentId); + signed_message.set_type(SignedMessage::SERVICE_CERTIFICATE_REQUEST); + Status status = + GetContentIdFromSignedExternalLicenseRequest(signed_message, &content_id); + EXPECT_EQ(error::INVALID_ARGUMENT, status.error_code()); + EXPECT_TRUE(content_id.empty()); +} + +TEST(ContentIdUtil, GetContentIdFailureWithInvalidExternalLicenseRequest) { + std::string content_id; + SignedMessage signed_message = BuildSignedExternalLicenseRequest( + ExternalLicenseRequest::PLAYREADY_LICENSE_REQUEST, kPlayReadyChallenge, + kContentId); + signed_message.set_msg("Invalid payload"); + Status status = + GetContentIdFromSignedExternalLicenseRequest(signed_message, &content_id); + EXPECT_EQ(EXTERNAL_LICENSE_REQUEST_PARSE_ERROR, status.error_code()); + EXPECT_TRUE(content_id.empty()); +} + +} // namespace widevine diff --git a/common/core_message_util.cc b/common/core_message_util.cc new file mode 100644 index 0000000..21ec6ae --- /dev/null +++ b/common/core_message_util.cc @@ -0,0 +1,51 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#include "common/core_message_util.h" + +#include "common/oemcrypto_core_message/odk/include/core_message_deserialize.h" +#include "common/oemcrypto_core_message/odk/include/core_message_serialize.h" +#include "common/oemcrypto_core_message/odk/include/core_message_serialize_proto.h" +#include "common/sha_util.h" + +using oemcrypto_core_message::deserialize::CoreLicenseRequestFromMessage; +using oemcrypto_core_message::deserialize::CoreProvisioningRequestFromMessage; +using oemcrypto_core_message::deserialize::CoreRenewalRequestFromMessage; +using oemcrypto_core_message::serialize::CreateCoreLicenseResponseFromProto; +using oemcrypto_core_message::serialize:: + CreateCoreProvisioningResponseFromProto; +using oemcrypto_core_message::serialize::CreateCoreRenewalResponse; +using widevine::Sha256_Hash; + +namespace widevine { +namespace core_message_util { +void GetCoreProvisioningResponse(const std::string& provisioning_response, + std::string* core_message) { + oemcrypto_core_message::ODK_ProvisioningRequest odk_provisioning_request; + CoreProvisioningRequestFromMessage(*core_message, &odk_provisioning_request); + CreateCoreProvisioningResponseFromProto( + provisioning_response, odk_provisioning_request, core_message); +} + +void GetCoreRenewalOrReleaseLicenseResponse(std::string* core_message) { + oemcrypto_core_message::ODK_RenewalRequest odk_renewal_request; + CoreRenewalRequestFromMessage(*core_message, &odk_renewal_request); + CreateCoreRenewalResponse(odk_renewal_request, core_message); +} + +void GetCoreNewLicenseResponse(const std::string& license, + std::string* core_message) { + oemcrypto_core_message::ODK_LicenseRequest odk_license_request; + CoreLicenseRequestFromMessage(*core_message, &odk_license_request); + std::string core_request_sha256 = Sha256_Hash(*core_message); + CreateCoreLicenseResponseFromProto(license, odk_license_request, + core_request_sha256, core_message); +} + +} // namespace core_message_util +} // namespace widevine diff --git a/common/core_message_util.h b/common/core_message_util.h new file mode 100644 index 0000000..f48cfa4 --- /dev/null +++ b/common/core_message_util.h @@ -0,0 +1,33 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef COMMON_CORE_MESSAGE_UTIL_H_ +#define COMMON_CORE_MESSAGE_UTIL_H_ + +#include + +namespace widevine { +namespace core_message_util { +// Gets the core message from |provisioning_response|. The output is held in +// |core_message|. The provisioning response to be sent will be updated with +// this |core_message|. +void GetCoreProvisioningResponse(const std::string& provisioning_response, + std::string* core_message); + +// Gets the core message for renewal or release license response. The output +// is held in |core_message|. +void GetCoreRenewalOrReleaseLicenseResponse(std::string* core_message); + +// Gets the core message from |license|. The output is held in |core_message|. +// The license to be sent will be updated with this |core_message|. +void GetCoreNewLicenseResponse(const std::string& license, + std::string* core_message); + +} // namespace core_message_util +} // namespace widevine +#endif // COMMON_CORE_MESSAGE_UTIL_H_ diff --git a/common/crypto_util.cc b/common/crypto_util.cc index 2a1ab2a..52d1352 100644 --- a/common/crypto_util.cc +++ b/common/crypto_util.cc @@ -17,7 +17,6 @@ #include "openssl/evp.h" #include "openssl/hmac.h" #include "openssl/sha.h" -#include "util/endian/endian.h" namespace widevine { namespace crypto_util { @@ -41,6 +40,11 @@ const char kGroupKeyLabel[] = "GROUP_ENCRYPTION"; const char kPhonyGroupMasterKey[] = "fedcba9876543210"; const int kAes128KeySizeBits = 128; const int kAes128KeySizeBytes = 16; +const int kAes256KeySizeBytes = 32; +const char kKeyboxV3Label[] = "Keyboxv3"; + +const int kAesBlockSizeBits = AES_BLOCK_SIZE * 8; +const int kAesMaxDerivedBlocks = 255; const uint32_t kCENCSchemeID = 0x63656E63; // 'cenc' (AES-CTR): 0x63656E63 const uint32_t kCBC1SchemeID = 0x63626331; // 'cbc1' (AES-CBC): 0x63626331 @@ -51,7 +55,7 @@ const uint32_t kCBCSSchemeID = // Creates a SHA-256 HMAC signature for the given message. std::string CreateSignatureHmacSha256(absl::string_view key, - absl::string_view message) { + absl::string_view message) { HMAC_CTX ctx; HMAC_CTX_init(&ctx); HMAC_Init(&ctx, key.data(), key.size(), EVP_sha256()); @@ -74,7 +78,7 @@ bool VerifySignatureHmacSha256(absl::string_view key, // Creates a SHA-1 HMAC signature for the given message. std::string CreateSignatureHmacSha1(absl::string_view key, - absl::string_view message) { + absl::string_view message) { HMAC_CTX ctx; HMAC_CTX_init(&ctx); HMAC_Init(&ctx, key.data(), key.size(), EVP_sha1()); @@ -94,31 +98,45 @@ bool VerifySignatureHmacSha1(absl::string_view key, absl::string_view signature, return CreateSignatureHmacSha1(key, message) == signature; } -// Derives an AES 128 key from the provided key and additional info. +// Derives a key from the provided AES 128 or 256 key and additional info. std::string DeriveKey(absl::string_view key, absl::string_view label, - absl::string_view context, const uint32_t size_bits) { - if (key.size() != kAes128KeySizeBytes) return ""; - - // We only handle even multiples of 16 bytes (128 bits) right now. - if ((size_bits % 128) || (size_bits > (128 * 255))) { + absl::string_view context, const uint32_t size_bits) { + // We only handle multiples of AES blocks (16 bytes) with a maximum of 255 + // blocks. + const uint32_t output_block_count = size_bits / kAesBlockSizeBits; + if (size_bits % kAesBlockSizeBits || + output_block_count > kAesMaxDerivedBlocks) { return ""; } - std::string result; + const EVP_CIPHER* cipher = nullptr; + const size_t key_size_bytes = key.size(); - const EVP_CIPHER* cipher = EVP_aes_128_cbc(); + switch (key_size_bytes) { + case kAes128KeySizeBytes: + cipher = EVP_aes_128_cbc(); + break; + case kAes256KeySizeBytes: + cipher = EVP_aes_256_cbc(); + break; + default: + return ""; + } + + std::string result; CMAC_CTX* cmac_ctx = CMAC_CTX_new(); - for (unsigned char counter = 1; counter <= (size_bits / 128); counter++) { - if (CMAC_Init(cmac_ctx, key.data(), key.size(), cipher, 0)) { + for (unsigned char counter = 0; counter < output_block_count; counter++) { + if (CMAC_Init(cmac_ctx, key.data(), key_size_bytes, cipher, nullptr)) { std::string message; - message.append(1, counter); + message.append(1, counter + 1); message.append(label.data(), label.size()); message.append(1, '\0'); message.append(context.data(), context.size()); - char size_string[4]; - BigEndian::Store32(&size_string, size_bits); - message.append(&size_string[0], &size_string[0] + 4); + message.append(1, (size_bits >> 24) & 0xFF); + message.append(1, (size_bits >> 16) & 0xFF); + message.append(1, (size_bits >> 8) & 0xFF); + message.append(1, size_bits & 0xFF); if (CMAC_Update(cmac_ctx, reinterpret_cast(message.data()), message.size())) { size_t reslen; @@ -146,12 +164,12 @@ std::string DeriveKeyId(absl::string_view context) { } std::string DeriveGroupSessionKey(absl::string_view context, - const uint32_t size_bits) { + const uint32_t size_bits) { return DeriveKey(kPhonyGroupMasterKey, kGroupKeyLabel, context, size_bits); } std::string DeriveSigningKey(absl::string_view key, absl::string_view context, - const uint32_t size_bits) { + const uint32_t size_bits) { return DeriveKey(key, kSigningKeyLabel, context, size_bits); } diff --git a/common/crypto_util.h b/common/crypto_util.h index 75e8dc0..0b6e31d 100644 --- a/common/crypto_util.h +++ b/common/crypto_util.h @@ -14,7 +14,6 @@ #include -#include "base/macros.h" #include "absl/strings/string_view.h" namespace widevine { @@ -32,6 +31,7 @@ extern const char kIvLabel[]; extern const int kIvSizeBits; extern const int kAes128KeySizeBits; extern const int kAes128KeySizeBytes; +extern const char kKeyboxV3Label[]; extern const uint32_t kCENCSchemeID; // 'cenc' (AES-CTR): 0x63656E63 extern const uint32_t kCBC1SchemeID; // 'cbc1' (AES-CBC): 0x63626331 @@ -44,7 +44,7 @@ extern const uint32_t kCBCSSchemeID; // 'cbcs' (AES-CBC subsample): 0x63626373 // AES-CMAC: // http://tools.ietf.org/html/rfc4493 std::string DeriveKey(absl::string_view key, absl::string_view label, - absl::string_view context, const uint32_t size_bits); + absl::string_view context, const uint32_t size_bits); // Derives an IV from the provided |context|. std::string DeriveIv(absl::string_view context); @@ -53,15 +53,16 @@ std::string DeriveIv(absl::string_view context); std::string DeriveKeyId(absl::string_view context); // Helper function to derive a key using the group master key and context. -std::string DeriveGroupSessionKey(absl::string_view context, const uint32_t size_bits); +std::string DeriveGroupSessionKey(absl::string_view context, + const uint32_t size_bits); // Helper function to derive a signing key for from the signing context. std::string DeriveSigningKey(absl::string_view key, absl::string_view context, - const uint32_t size_bits); + const uint32_t size_bits); // Helper function to create a SHA-256 HMAC signature for the given message. std::string CreateSignatureHmacSha256(absl::string_view key, - absl::string_view message); + absl::string_view message); // Helper function which compares the SHA-256 HMAC against the provided // signature. @@ -71,7 +72,7 @@ bool VerifySignatureHmacSha256(absl::string_view key, // Helper function to create a SHA-1 HMAC signature for the given message. std::string CreateSignatureHmacSha1(absl::string_view key, - absl::string_view message); + absl::string_view message); // Helper function which compares the SHA-1 HMAC against the provided // signature. diff --git a/common/crypto_util_test.cc b/common/crypto_util_test.cc index b939609..c7613c1 100644 --- a/common/crypto_util_test.cc +++ b/common/crypto_util_test.cc @@ -8,6 +8,8 @@ // Unit tests for the crypto_util helper functions. +#include "common/crypto_util.h" + #include #include "testing/gmock.h" @@ -15,7 +17,7 @@ #include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" -#include "common/crypto_util.h" +#include "openssl/aes.h" namespace widevine { namespace crypto_util { @@ -25,19 +27,33 @@ const char kCBC1Str[] = "cbc1"; const char kCENSStr[] = "cens"; const char kCBCSStr[] = "cbcs"; -static unsigned char key_data[] = - { 0x87, 0x27, 0xa4, 0x0e, 0xbd, 0x82, 0x32, 0x9e, - 0x6b, 0x3b, 0x4e, 0x29, 0xfa, 0x3b, 0x00, 0x4b }; +static unsigned char kAes128KeyData[] = {0x87, 0x27, 0xa4, 0x0e, 0xbd, 0x82, + 0x32, 0x9e, 0x6b, 0x3b, 0x4e, 0x29, + 0xfa, 0x3b, 0x00, 0x4b}; -static std::string key_str(key_data, key_data + sizeof(key_data)); +static unsigned char kAes256KeyData[] = { + 0x87, 0x27, 0xa4, 0x0e, 0xbd, 0x82, 0x32, 0x9e, 0x6b, 0x3b, 0x4e, + 0x29, 0xfa, 0x3b, 0x00, 0x4b, 0x87, 0x27, 0xa4, 0x0e, 0xbd, 0x82, + 0x32, 0x9e, 0x6b, 0x3b, 0x4e, 0x29, 0xfa, 0x3b, 0x00, 0x4b}; -static unsigned char iv_data[] = - { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; +static unsigned char kAes128IvData[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f}; -static std::string iv_str(iv_data, iv_data + sizeof(iv_data)); +class CryptoUtilTest : public ::testing::Test { + public: + CryptoUtilTest() + : aes_128_key_(kAes128KeyData, kAes128KeyData + sizeof(kAes128KeyData)), + aes_256_key_(kAes256KeyData, kAes256KeyData + sizeof(kAes256KeyData)), + iv_128_(kAes128IvData, kAes128IvData + sizeof(kAes128IvData)) {} -TEST(CryptoUtilTest, DeriveAes128KeyTest) { + protected: + std::string aes_128_key_; + std::string aes_256_key_; + std::string iv_128_; +}; + +TEST_F(CryptoUtilTest, DeriveAes128MasterKeyTest) { unsigned char label[] = { 0x16, 0xf1, 0xa4, 0x32, 0x9f, 0x94, 0x55, 0xc1, 0x92, 0xa0, 0x34, 0x8a, 0x8b, 0x6b, 0x77, 0x08, 0xbc, 0x23, 0x70, 0x16, 0xbc, 0xda, 0xfb, 0x60, @@ -60,22 +76,88 @@ TEST(CryptoUtilTest, DeriveAes128KeyTest) { 0x4a, 0x47, 0x2f, 0x04, 0xe0, 0x34, 0x75, 0x22 }; std::string label_str(label, label + sizeof(label)); - std::string key_str(key_data, key_data + sizeof(key_data)); std::string context_str(context, context + sizeof(context)); - std::string result = DeriveKey(key_str, label_str, context_str, 128); + std::string result = DeriveKey(aes_128_key_, label_str, context_str, 128); std::string output_128(output0, output0 + sizeof(output0)); ASSERT_EQ(result, output_128); - result = DeriveKey(key_str, label_str, context_str, 384); + result = DeriveKey(aes_128_key_, label_str, context_str, 384); std::string output_384(output1, output1 + sizeof(output1)); ASSERT_EQ(result, output_384); } -TEST(CryptoUtilTest, DeriveGroupSesionKey) { +TEST_F(CryptoUtilTest, DeriveAes256MasterKeyTest) { + const unsigned char label[] = { + 0x16, 0xf1, 0xa4, 0x32, 0x9f, 0x94, 0x55, 0xc1, 0x92, 0xa0, 0x34, + 0x8a, 0x8b, 0x6b, 0x77, 0x08, 0xbc, 0x23, 0x70, 0x16, 0xbc, 0xda, + 0xfb, 0x60, 0xd1, 0xcf, 0x6a, 0x4d, 0x40, 0xa1, 0xe3, 0xfe, 0xd3, + 0xe9, 0xa6, 0x58, 0x4c, 0xd4, 0xad, 0xa4, 0xa2}; + + const unsigned char context[] = {0x4c, 0x53, 0xc0, 0xe9, 0x9e, 0x7f, 0x7d, + 0x6d, 0x0a, 0x76, 0x7c, 0xc7, 0x25, 0xb5, + 0x5b, 0x80, 0x81, 0x91, 0xff}; + + const unsigned char expected_128[] = {0x76, 0x36, 0x33, 0x0e, 0x0b, 0x2c, + 0x38, 0xc2, 0x9e, 0x53, 0x23, 0x8d, + 0x2e, 0xc6, 0x3a, 0x46}; + + const std::string label_str(label, label + sizeof(label)); + const std::string context_str(context, context + sizeof(context)); + std::string result = DeriveKey(aes_256_key_, label_str, context_str, 128); + EXPECT_EQ(std::string(expected_128, expected_128 + sizeof(expected_128)), + result) + << absl::BytesToHexString(result); + + const unsigned char expected_256[] = { + 0xfb, 0x8f, 0xdf, 0x0e, 0x22, 0xfe, 0xf7, 0x2b, 0xd1, 0x9a, 0x1d, + 0xd2, 0xcb, 0xb0, 0x11, 0x5c, 0x6c, 0xa7, 0xe1, 0x7f, 0x72, 0xce, + 0x3a, 0x60, 0x34, 0x89, 0x6d, 0x08, 0xef, 0xde, 0x19, 0x45}; + result = DeriveKey(aes_256_key_, label_str, context_str, 256); + EXPECT_EQ(std::string(expected_256, expected_256 + sizeof(expected_256)), + result) + << absl::BytesToHexString(result); + + const unsigned char expected_384[] = { + 0x65, 0xbc, 0xe3, 0xf3, 0xfb, 0xfa, 0xce, 0x1d, 0x24, 0x63, 0x9c, 0x8f, + 0x48, 0x0e, 0xbd, 0x76, 0xd1, 0x14, 0x0b, 0xb1, 0x3a, 0x3d, 0x6e, 0x30, + 0xa9, 0xf4, 0x40, 0x35, 0x0d, 0x6b, 0xc5, 0x1e, 0x9c, 0xa9, 0x5f, 0xf9, + 0xde, 0x96, 0xa0, 0xa4, 0x22, 0x62, 0x21, 0xc5, 0xd6, 0xd4, 0xf4, 0x6f}; + result = DeriveKey(aes_256_key_, label_str, context_str, 384); + EXPECT_EQ(std::string(expected_384, expected_384 + sizeof(expected_384)), + result) + << absl::BytesToHexString(result); +} + +TEST_F(CryptoUtilTest, DeriveAesInvalidSizeModulus) { + // This is the control case that we correctly derive 128 bits. + EXPECT_NE("", DeriveKey(aes_128_key_, "foo", "bar", 128)); + EXPECT_EQ("", DeriveKey(aes_128_key_, "foo", "bar", 127)); +} + +TEST_F(CryptoUtilTest, DeriveAesMaxBlocks) { + EXPECT_EQ( + 255 * AES_BLOCK_SIZE, + DeriveKey(aes_128_key_, "foo", "bar", AES_BLOCK_SIZE * 8 * 255).size()); +} + +TEST_F(CryptoUtilTest, DeriveAesTooManyBlocks) { + EXPECT_EQ("", + DeriveKey(aes_128_key_, "foo", "bar", AES_BLOCK_SIZE * 8 * 256)); +} + +TEST_F(CryptoUtilTest, DeriveAes128InvalidKeySize) { + EXPECT_EQ("", DeriveKey(aes_128_key_.substr(0, 15), "foo", "bar", 128)); +} + +TEST_F(CryptoUtilTest, DeriveAes256InvalidKeySize) { + EXPECT_EQ("", DeriveKey(aes_256_key_.substr(0, 31), "foo", "bar", 128)); +} + +TEST_F(CryptoUtilTest, DeriveGroupSesionKey) { unsigned char output[] = { 0x92, 0x6c, 0x2f, 0x5, 0xa6, 0x4f, 0xff, 0xb1, 0x86, 0x4a, 0x1a, 0x14, 0x95, 0xeb, 0xb0, 0xf1 }; std::string group_session_key = DeriveGroupSessionKey("test_group_id", 128); @@ -84,7 +166,7 @@ TEST(CryptoUtilTest, DeriveGroupSesionKey) { ASSERT_EQ(output_128, group_session_key); } -TEST(CryptoUtilTest, TestCreateAndVerifySignatureHmacSha256) { +TEST_F(CryptoUtilTest, TestCreateAndVerifySignatureHmacSha256) { unsigned char message_data[] = { 0xd9, 0x24, 0x2d, 0x03, 0x93, 0x6f, 0x22, 0x53, 0x99, 0x7a, 0x7d, 0x9b, 0x0c, 0xcf, 0xfd, 0xb2, @@ -96,14 +178,14 @@ TEST(CryptoUtilTest, TestCreateAndVerifySignatureHmacSha256) { 0x97, 0x69, 0x23, 0x74, 0x34, 0x9a, 0x34, 0xda }; std::string message(message_data, message_data + sizeof(message_data)); - std::string signature(CreateSignatureHmacSha256(key_str, message)); + std::string signature(CreateSignatureHmacSha256(aes_128_key_, message)); ASSERT_EQ(signature.size(), 32); - ASSERT_TRUE(VerifySignatureHmacSha256(key_str, signature, message)); + ASSERT_TRUE(VerifySignatureHmacSha256(aes_128_key_, signature, message)); } -TEST(CryptoUtilTest, TestFailCreateAndVerifyHmacSha256) { +TEST_F(CryptoUtilTest, TestFailCreateAndVerifyHmacSha256) { unsigned char message_data[] = { 0xd9, 0x24, 0x2d, 0x03, 0x93, 0x6f, 0x22, 0x53, 0x99, 0x7a, 0x7d, 0x9b, 0x0c, 0xcf, 0xfd, 0xb2, @@ -123,20 +205,20 @@ TEST(CryptoUtilTest, TestFailCreateAndVerifyHmacSha256) { ASSERT_EQ(signature.size(), 32); // Create valid signature to compare. - signature = CreateSignatureHmacSha256(key_str, message); + signature = CreateSignatureHmacSha256(aes_128_key_, message); // Test with bogus key. ASSERT_FALSE(VerifySignatureHmacSha256(bogus_key, signature, message)); // Test with munged signature. signature[0] = 0xFF; - ASSERT_FALSE(VerifySignatureHmacSha256(key_str, signature, message)); + ASSERT_FALSE(VerifySignatureHmacSha256(aes_128_key_, signature, message)); // Test with bogus signature. - ASSERT_FALSE(VerifySignatureHmacSha256(key_str, "bogus", message)); + ASSERT_FALSE(VerifySignatureHmacSha256(aes_128_key_, "bogus", message)); } -TEST(CryptoUtilTest, TestCreateAndVerifySignatureHmacSha1) { +TEST_F(CryptoUtilTest, TestCreateAndVerifySignatureHmacSha1) { unsigned char message_data[] = { 0xd9, 0x24, 0x2d, 0x03, 0x93, 0x6f, 0x22, 0x53, 0x99, 0x7a, 0x7d, 0x9b, 0x0c, 0xcf, 0xfd, 0xb2, @@ -148,13 +230,13 @@ TEST(CryptoUtilTest, TestCreateAndVerifySignatureHmacSha1) { 0x97, 0x69, 0x23, 0x74, 0x34, 0x9a, 0x34, 0xda }; std::string message(message_data, message_data + sizeof(message_data)); - std::string signature(CreateSignatureHmacSha1(key_str, message)); + std::string signature(CreateSignatureHmacSha1(aes_128_key_, message)); ASSERT_EQ(20, signature.size()); - ASSERT_TRUE(VerifySignatureHmacSha1(key_str, signature, message)); + ASSERT_TRUE(VerifySignatureHmacSha1(aes_128_key_, signature, message)); } -TEST(CryptoUtilTest, TestFailCreateAndVerifyHmacSha1) { +TEST_F(CryptoUtilTest, TestFailCreateAndVerifyHmacSha1) { unsigned char message_data[] = { 0xd9, 0x24, 0x2d, 0x03, 0x93, 0x6f, 0x22, 0x53, 0x99, 0x7a, 0x7d, 0x9b, 0x0c, 0xcf, 0xfd, 0xb2, @@ -173,17 +255,17 @@ TEST(CryptoUtilTest, TestFailCreateAndVerifyHmacSha1) { // This should still produce an hmac signature. ASSERT_EQ(20, signature.size()); // Create valid signature to compare. - signature = CreateSignatureHmacSha1(key_str, message); + signature = CreateSignatureHmacSha1(aes_128_key_, message); // Test with bogus key. ASSERT_FALSE(VerifySignatureHmacSha1(bogus_key, signature, message)); // Test with munged signature. signature[0] = 0xFF; - ASSERT_FALSE(VerifySignatureHmacSha1(key_str, signature, message)); + ASSERT_FALSE(VerifySignatureHmacSha1(aes_128_key_, signature, message)); // Test with bogus signature. - ASSERT_FALSE(VerifySignatureHmacSha1(key_str, "bogus", message)); + ASSERT_FALSE(VerifySignatureHmacSha1(aes_128_key_, "bogus", message)); } -TEST(CryptoUtilTest, DeriveIv) { +TEST_F(CryptoUtilTest, DeriveIv) { // First value in the pair is the key_id, second value is the expected IV. std::pair id_iv_pairs[] = { {"1234567890123456", "3278234c7682d1a2e153af4912975f5f"}, @@ -198,7 +280,7 @@ TEST(CryptoUtilTest, DeriveIv) { } } -TEST(CryptoUtilTest, DeriveKeyId) { +TEST_F(CryptoUtilTest, DeriveKeyId) { // First value in the pair is the context, second value is the expected id. std::pair context_id_pairs[] = { {"1234567890123456", "a3c4a8c0d0e24e96f38f492254186a9d"}, @@ -213,14 +295,14 @@ TEST(CryptoUtilTest, DeriveKeyId) { } } -TEST(CryptoUtilTest, Verify4CCEncryptionIDFromBadString) { +TEST_F(CryptoUtilTest, Verify4CCEncryptionIDFromBadString) { uint32_t cc_code; ASSERT_FALSE(FourCCEncryptionSchemeIDFromString("garbage", &cc_code)); ASSERT_FALSE(FourCCEncryptionSchemeIDFromString("junk", &cc_code)); ASSERT_FALSE(FourCCEncryptionSchemeIDFromString("cencc", &cc_code)); } -TEST(CryptoUtilTest, Verify4CCEncryptionIDFromString) { +TEST_F(CryptoUtilTest, Verify4CCEncryptionIDFromString) { uint32_t cc_code = 0; ASSERT_TRUE(FourCCEncryptionSchemeIDFromString(kCENCStr, &cc_code)); ASSERT_EQ(kCENCSchemeID, cc_code); diff --git a/common/device_info_util.cc b/common/device_info_util.cc new file mode 100644 index 0000000..8190d82 --- /dev/null +++ b/common/device_info_util.cc @@ -0,0 +1,41 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +// Implements the device info helper function. +#include "common/device_info_util.h" + +#include "absl/strings/ascii.h" + +namespace widevine { +bool IsMatchedMakeModel(const std::string& expected_make, + const std::string& expected_model, + const std::string& make_from_client, + const std::string& model_from_client) { + return absl::AsciiStrToLower(expected_make) == + absl::AsciiStrToLower(make_from_client) && + absl::AsciiStrToLower(expected_model) == + absl::AsciiStrToLower(model_from_client); +} + +bool VerifyMakeModel(const ProvisionedDeviceInfo& device_info, + const std::string& make_from_client, + const std::string& model_from_client) { + if (IsMatchedMakeModel(device_info.manufacturer(), device_info.model(), + make_from_client, model_from_client)) { + return true; + } + for (ProvisionedDeviceInfo::ModelInfo product_info : + device_info.model_info()) { + if (IsMatchedMakeModel(product_info.manufacturer(), product_info.model(), + make_from_client, model_from_client)) { + return true; + } + } + return false; +} +} // namespace widevine diff --git a/common/device_info_util.h b/common/device_info_util.h new file mode 100644 index 0000000..6347f6d --- /dev/null +++ b/common/device_info_util.h @@ -0,0 +1,29 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +#ifndef COMMON_DEVICE_INFO_UTIL_H_ +#define COMMON_DEVICE_INFO_UTIL_H_ +#include + +#include "protos/public/provisioned_device_info.pb.h" + +namespace widevine { + +// Helpers function to compare the expected and actual make model field. +bool IsMatchedMakeModel(const std::string& expected_make, + const std::string& expected_model, + const std::string& make_from_client, + const std::string& model_from_client); +/** + * Return true if make/model from client in device_info matches any of the + * registered makes/models. + */ +bool VerifyMakeModel(const ProvisionedDeviceInfo& device_info, + const std::string& make_from_client, + const std::string& model_from_client); +} // namespace widevine +#endif // COMMON_DEVICE_INFO_UTIL_H_ diff --git a/common/device_status_list.cc b/common/device_status_list.cc index da97d3f..7a3fbcc 100644 --- a/common/device_status_list.cc +++ b/common/device_status_list.cc @@ -11,9 +11,13 @@ #include "common/device_status_list.h" #include + +#include #include +#include #include "glog/logging.h" +#include "absl/strings/ascii.h" #include "absl/strings/escaping.h" #include "absl/strings/numbers.h" #include "absl/strings/str_split.h" @@ -23,9 +27,17 @@ #include "common/client_cert.h" #include "common/drm_service_certificate.h" #include "common/error_space.h" +#include "common/keybox_client_cert.h" #include "common/rsa_key.h" +#include "common/status.h" #include "protos/public/client_identification.pb.h" +#include "protos/public/device_certificate_status.pb.h" #include "protos/public/errors.pb.h" +#include "protos/public/signed_device_info.pb.h" + +using ::widevine::DeviceCertificateStatusListRequest; +using ::widevine::SignedDeviceInfo; +using ::widevine::SignedDeviceInfoRequest; namespace widevine { @@ -44,29 +56,19 @@ DeviceStatusList* DeviceStatusList::Instance() { return device_status_list; } -DeviceStatusList::DeviceStatusList() - : creation_time_seconds_(0), - expiration_period_seconds_(0), - allow_unknown_devices_(true), - allow_test_only_devices_(false) {} +DeviceStatusList::DeviceStatusList() {} DeviceStatusList::~DeviceStatusList() {} Status DeviceStatusList::UpdateStatusList( const std::string& root_certificate_public_key, - const std::string& serialized_certificate_status_list, - uint32_t expiration_period_seconds) { - SignedDeviceCertificateStatusList signed_certificate_status_list; - if (!signed_certificate_status_list.ParseFromString( - serialized_certificate_status_list)) { - return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST, - "signed-certificate-status-list-parse-error"); - } - if (!signed_certificate_status_list.has_certificate_status_list()) { + const std::string& serialized_device_certificate_status_list, + const std::string& signature, uint32_t expiration_period_seconds) { + if (serialized_device_certificate_status_list.empty()) { return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST, "missing-status-list"); } - if (!signed_certificate_status_list.has_signature()) { + if (signature.empty()) { return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST, "missing-status-list-signature"); } @@ -76,17 +78,16 @@ Status DeviceStatusList::UpdateStatusList( return Status(error_space, INVALID_DRM_CERTIFICATE, "invalid-root-public-key"); } - if (!root_key->VerifySignature( - signed_certificate_status_list.certificate_status_list(), - signed_certificate_status_list.signature())) { + if (!root_key->VerifySignature(serialized_device_certificate_status_list, + signature)) { return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST, "invalid-status-list-signature"); } DeviceCertificateStatusList certificate_status_list; if (!certificate_status_list.ParseFromString( - signed_certificate_status_list.certificate_status_list())) { + serialized_device_certificate_status_list)) { return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST, - "certificate-status-list-parse-error"); + "signed-certificate-status-list-parse-error"); } if (expiration_period_seconds && (GetCurrentTime() > (certificate_status_list.creation_time_seconds() + @@ -117,6 +118,7 @@ Status DeviceStatusList::UpdateStatusList( } Status DeviceStatusList::GetCertStatus(const ClientCert& client_cert, + const std::string& device_manufacturer, ProvisionedDeviceInfo* device_info) { CHECK(device_info); @@ -162,8 +164,19 @@ Status DeviceStatusList::GetCertStatus(const ClientCert& client_cert, if ((device_cert_status->status() == DeviceCertificateStatus::STATUS_TEST_ONLY) && !allow_test_only_devices_) { - return Status(error_space, DEVELOPMENT_CERTIFICATE_NOT_ALLOWED, - "test-only-drm-certificate-not-allowed"); + if (IsTestOnlyDeviceAllowed(client_cert.system_id(), + device_manufacturer)) { + LOG(WARNING) << "Allowing TEST_ONLY device with systemId = " + << client_cert.system_id() + << "make = " << device_manufacturer + << ", device info = " << device_info->ShortDebugString(); + } else { + VLOG(2) << "Not allowing TEST ONLY device with systemId = " + << client_cert.system_id() << "make = " << device_manufacturer + << ", device info = " << device_info->ShortDebugString(); + return Status(error_space, DEVELOPMENT_CERTIFICATE_NOT_ALLOWED, + "test-only-drm-certificate-not-allowed"); + } } if (!client_cert.signed_by_provisioner() && (client_cert.signer_serial_number() != @@ -198,13 +211,27 @@ bool DeviceStatusList::GetDeviceInfo(const ClientCert& client_cert, absl::ReaderMutexLock lock(&status_map_lock_); DeviceCertificateStatus* device_cert_status = gtl::FindOrNull(device_status_map_, client_cert.system_id()); - if (device_cert_status) { + if (device_cert_status != nullptr) { *device_info = device_cert_status->device_info(); return true; } return false; } +bool DeviceStatusList::GetRevokedIdentifiers( + uint32_t system_id, + DeviceCertificateStatus::RevokedIdentifiers* revoked_identifiers) { + CHECK(revoked_identifiers); + absl::ReaderMutexLock lock(&status_map_lock_); + DeviceCertificateStatus* device_cert_status = + gtl::FindOrNull(device_status_map_, system_id); + if (device_cert_status) { + *revoked_identifiers = device_cert_status->revoked_identifiers(); + return true; + } + return false; +} + bool DeviceStatusList::IsSystemIdActive(uint32_t system_id) { absl::ReaderMutexLock lock(&status_map_lock_); DeviceCertificateStatus* device_cert_status = @@ -241,31 +268,121 @@ void DeviceStatusList::AllowRevokedDevices(const std::string& system_id_list) { std::sort(allowed_revoked_devices_.begin(), allowed_revoked_devices_.end()); } +void DeviceStatusList::AllowTestOnlyDevices(const std::string& device_list) { + absl::WriterMutexLock lock(&allowed_test_only_devices_mutex_); + if (device_list.empty()) { + allowed_test_only_devices_.clear(); + return; + } + for (absl::string_view device : absl::StrSplit(device_list, ',')) { + const std::pair device_split = + absl::StrSplit(device, ':'); + if (device_split.second.empty() || device_split.second == "*") { + allowed_test_only_devices_.emplace( + std::stoi(std::string(device_split.first)), "*"); + VLOG(2) << "Whitelisting TEST_ONLY device: systemId = " + << std::stoi(std::string(device_split.first)) + << ", manufacturer = *"; + } else { + allowed_test_only_devices_.emplace( + std::stoi(std::string(device_split.first)), + absl::AsciiStrToUpper(device_split.second)); + VLOG(2) << "Whitelisting TEST_ONLY device: systemId = " + << std::stoi(std::string(device_split.first)) + << ", manufacturer = " + << absl::AsciiStrToUpper(device_split.second); + } + } +} + bool DeviceStatusList::IsRevokedSystemIdAllowed(uint32_t system_id) { auto it = std::binary_search(allowed_revoked_devices_.begin(), allowed_revoked_devices_.end(), system_id); return it; } -Status DeviceStatusList::ExtractFromProvisioningServiceResponse( - const std::string& certificate_provisioning_service_response, - std::string* signed_certificate_status_list, std::string* certificate_status_list) { - Status status = OkStatus(); - size_t signed_list_start = - certificate_provisioning_service_response.find(kSignedList); - if (signed_list_start != std::string::npos) { - size_t signed_list_end = certificate_provisioning_service_response.find( - kSignedListTerminator, signed_list_start); - if (signed_list_end == std::string::npos) { +bool DeviceStatusList::IsTestOnlyDeviceAllowed(uint32_t system_id, + const std::string manufacturer) { + absl::ReaderMutexLock lock(&allowed_test_only_devices_mutex_); + std::pair::iterator, + std::multimap::iterator> + allowed_manufacturers = allowed_test_only_devices_.equal_range(system_id); + for (auto it = allowed_manufacturers.first; + it != allowed_manufacturers.second; ++it) { + std::string allowed_manufacturer = (*it).second; + if (allowed_manufacturer == "*" || + allowed_manufacturer == absl::AsciiStrToUpper(manufacturer)) { + return true; + } + } + return false; +} + +Status DeviceStatusList::DetermineAndDeserializeServiceResponse( + const std::string& service_response, + DeviceCertificateStatusList* certificate_status_list, + std::string* serialized_certificate_status_list, std::string* signature) { + if (certificate_status_list == nullptr) { + return Status(error_space, error::INVALID_ARGUMENT, + "certificate_status_list is empty"); + } else if (serialized_certificate_status_list == nullptr) { + return Status(error_space, error::INVALID_ARGUMENT, + "serialized_certificate_status_list is empty"); + } else if (signature == nullptr) { + return Status(error_space, error::INVALID_ARGUMENT, "signature is empty"); + } + + // We support three types of payload parsing. The legacy path checks for a + // JSON encoded payload as well as just a plain base64 (web safe or normal) + // payload. If that doesn't match, then the method will try to parse the + // serialized PublishedDeviceInfo proto. + Status status = ExtractPublishedDevicesInfo( + service_response, serialized_certificate_status_list, signature); + + // If the payload was not correctly parsed as a PublishedDevices proto. + // then attempt to parse it as a legacy payload. + if (!status.ok()) { + status = ExtractLegacyDeviceList( + service_response, serialized_certificate_status_list, signature); + // The payload could not be parsed in either format, return the failure + // information. + if (!status.ok()) { + return status; + } + } + + if (!certificate_status_list->ParseFromString( + *serialized_certificate_status_list)) { + return Status(error_space, widevine::INVALID_CERTIFICATE_STATUS_LIST, + "certificate-status-list-parse-error"); + } + return OkStatus(); +} + +Status DeviceStatusList::ExtractLegacyDeviceList( + const std::string& raw_certificate_provisioning_service_response, + std::string* serialized_certificate_status_list, std::string* signature) { + // First, attempt to extract the legacy JSON response. Example legacy format. + // "signedList":"" + // where the b64 encoded data is a DeviceCertificateStatusListResponse. + size_t b64_list_response_start = + raw_certificate_provisioning_service_response.find(kSignedList); + std::string serialized_signed_certificate_status_list; + if (b64_list_response_start != std::string::npos) { + size_t b64_list_response_end = + raw_certificate_provisioning_service_response.find( + kSignedListTerminator, b64_list_response_start); + if (b64_list_response_end == std::string::npos) { return Status( error_space, error::INVALID_ARGUMENT, "Unable to parse the certificate_provisioning_service_response. " "SignedList not terminated."); } std::string signed_list( - certificate_provisioning_service_response.begin() + signed_list_start + - kSignedListLen, - certificate_provisioning_service_response.begin() + signed_list_end); + raw_certificate_provisioning_service_response.begin() + + b64_list_response_start + kSignedListLen, + raw_certificate_provisioning_service_response.begin() + + b64_list_response_end); // Strip off quotes. signed_list.erase(std::remove(signed_list.begin(), signed_list.end(), '\"'), @@ -281,46 +398,55 @@ Status DeviceStatusList::ExtractFromProvisioningServiceResponse( // Strip off carriage return (the control-M character) signed_list.erase(std::remove(signed_list.begin(), signed_list.end(), '\r'), signed_list.end()); - if (!absl::WebSafeBase64Unescape(signed_list, - signed_certificate_status_list)) { - if (!absl::Base64Unescape(signed_list, signed_certificate_status_list)) { + if (!absl::WebSafeBase64Unescape( + signed_list, &serialized_signed_certificate_status_list)) { + if (!absl::Base64Unescape(signed_list, + &serialized_signed_certificate_status_list)) { return Status(error_space, error::INVALID_ARGUMENT, "Base64 decode of signedlist failed."); } } } else { - // certificate_provisioning_service_response is the signed list and not a - // JSON message. - if (!absl::WebSafeBase64Unescape(certificate_provisioning_service_response, - signed_certificate_status_list)) { - if (!absl::Base64Unescape(certificate_provisioning_service_response, - signed_certificate_status_list)) { + // If this was not a legacy JSON response, attempt to deserialize the base64 + // response. + if (!absl::WebSafeBase64Unescape( + raw_certificate_provisioning_service_response, + &serialized_signed_certificate_status_list)) { + if (!absl::Base64Unescape(raw_certificate_provisioning_service_response, + &serialized_signed_certificate_status_list)) { return Status(error_space, error::INVALID_ARGUMENT, "Base64 decode of certList failed."); } } } - SignedDeviceCertificateStatusList signed_status_list; - if (!signed_status_list.ParseFromString(*signed_certificate_status_list)) { + + // Attempt to parse the legacy serialized signed status list into the proto + // and extract the serialized status list and signature. + return ParseLegacySignedDeviceCertificateStatusList( + serialized_signed_certificate_status_list, + serialized_certificate_status_list, signature); +} + +Status DeviceStatusList::ExtractPublishedDevicesInfo( + const std::string& serialized_published_devices, + std::string* serialized_certificate_status_list, std::string* signature) { + // TODO(b/139067045): Change from using the SignedDeviceInfo proto + // to using the correct proto from the API. This duplicate, wire-compatible + // proto was a temporary way to workaround Proto2/Proto3 compatibility issues. + SignedDeviceInfo devices_info; + if (!devices_info.ParseFromString(serialized_published_devices)) { return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST, - "signed-certificate-status-list-parse-error"); + "published-devices-info-parse-error"); } - if (!signed_status_list.has_certificate_status_list()) { - return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST, - "missing-status-list"); - } - DeviceCertificateStatusList device_certificate_status_list; - if (!device_certificate_status_list.ParseFromString( - signed_status_list.certificate_status_list())) { - return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST, - "certificate-status-list-parse-error"); - } - *certificate_status_list = signed_status_list.certificate_status_list(); + *serialized_certificate_status_list = + devices_info.device_certificate_status_list(); + *signature = devices_info.signature(); return OkStatus(); } Status DeviceStatusList::GenerateSignedDeviceCertificateStatusListRequest( const std::string& version, + const std::string& serialized_service_certificate, std::string* signed_device_certificate_status_list_request) { if (version.empty()) { return Status(error_space, error::INVALID_ARGUMENT, "SDK version is empty"); @@ -334,9 +460,10 @@ Status DeviceStatusList::GenerateSignedDeviceCertificateStatusListRequest( DeviceCertificateStatusListRequest request; request.set_sdk_version(version); request.set_sdk_time_seconds(DeviceStatusList::Instance()->GetCurrentTime()); + request.set_service_certificate(serialized_service_certificate); std::string device_certificate_status_list_request; request.SerializeToString(&device_certificate_status_list_request); - SignedDeviceCertificateStatusListRequest signed_request; + SignedDeviceInfoRequest signed_request; signed_request.set_device_certificate_status_list_request( device_certificate_status_list_request); const DrmServiceCertificate* sc = @@ -359,4 +486,50 @@ Status DeviceStatusList::GenerateSignedDeviceCertificateStatusListRequest( signed_device_certificate_status_list_request); return OkStatus(); } + +Status DeviceStatusList::ParseLegacySignedDeviceCertificateStatusList( + const std::string& serialized_signed_device_certificate_status_list, + std::string* serialized_device_certificate_status_list, + std::string* signature) { + // Parse the serialized_signed_device_certificate_status_list to extract the + // serialized_device_certificate_status_list + SignedDeviceCertificateStatusList signed_device_list; + if (!signed_device_list.ParseFromString( + serialized_signed_device_certificate_status_list)) { + return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST, + "signed-certificate-status-list-parse-error"); + } + if (signed_device_list.certificate_status_list().empty()) { + return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST, + "missing-status-list"); + } + if (signed_device_list.signature().empty()) { + return Status(error_space, INVALID_CERTIFICATE_STATUS_LIST, + "missing-status-list-signature"); + } + *serialized_device_certificate_status_list = + signed_device_list.certificate_status_list(); + *signature = signed_device_list.signature(); + return OkStatus(); +} + +void DeviceStatusList::RevokedDrmCertificateSerialNumbers( + const std::string& drm_certificate_serial_numbers) { + for (absl::string_view drm_certificate_serial_number : + absl::StrSplit(drm_certificate_serial_numbers, ',')) { + revoked_drm_certificate_serial_numbers_.insert( + std::string(drm_certificate_serial_number)); + } +} + +bool DeviceStatusList::IsDrmCertificateRevoked( + const std::string& device_certificate_serial_number) { + if (revoked_drm_certificate_serial_numbers_.find( + device_certificate_serial_number) != + revoked_drm_certificate_serial_numbers_.end()) { + return true; + } + return false; +} + } // namespace widevine diff --git a/common/device_status_list.h b/common/device_status_list.h index 13782c7..a4e19a0 100644 --- a/common/device_status_list.h +++ b/common/device_status_list.h @@ -12,9 +12,9 @@ #define COMMON_DEVICE_STATUS_LIST_H__ #include +#include #include -#include "base/macros.h" #include "absl/synchronization/mutex.h" #include "common/status.h" #include "protos/public/device_certificate_status.pb.h" @@ -35,14 +35,20 @@ class DeviceStatusList { static DeviceStatusList* Instance(); DeviceStatusList(); + + DeviceStatusList(const DeviceStatusList&) = delete; + DeviceStatusList& operator=(const DeviceStatusList&) = delete; + virtual ~DeviceStatusList(); - // Takes |serialized_certificate_status_list| and copies to an internal map of - // device certifcate status list. The internal map is used to verify - // a device was not revoked. Returns true is the list was successfully parsed. - Status UpdateStatusList(const std::string& root_certificate_public_key, - const std::string& serialized_certificate_status_list, - uint32_t expiration_period_seconds); + // Takes |serialized_device_certificate_status_list| and copies to an + // internal map of device certificate status list. The internal map is used + // to verify a device was not revoked. Returns true is the list was + // successfully parsed. + Status UpdateStatusList( + const std::string& root_certificate_public_key, + const std::string& serialized_device_certificate_status_list, + const std::string& signature, uint32_t expiration_period_seconds); void set_allow_unknown_devices(bool flag) { allow_unknown_devices_ = flag; } bool allow_unknown_devices() const { return allow_unknown_devices_; } void set_allow_test_only_devices(bool allow) { @@ -50,15 +56,20 @@ class DeviceStatusList { } bool allow_test_only_devices() const { return allow_test_only_devices_; } - // Checks the device status list and returns either: + // Checks the device status list and handles the case when a TEST_ONLY device + // made the request. Returns one of // OK // UNSUPPORTED_SYSTEM_ID // INVALID_DRM_CERTIFICATE // DRM_DEVICE_CERTIFICATE_REVOKED // DRM_DEVICE_CERTIFICATE_UNKNOWN + // If a TEST_ONLY device using "make" as identified by |device_manufacturer|, + // was not whitelisted, then will return + // DEVELOPMENT_CERTIFICATE_NOT_ALLOWED // If status is OK, a copy of the provisioned device info is copied // into |device_info|. Caller owns |device_info| and it must not be null. Status GetCertStatus(const ClientCert& client_cert, + const std::string& device_manufacturer, widevine::ProvisionedDeviceInfo* device_info); // Returns true if the pre-provisioning key or certificate for the specified // system ID are active (not disallowed or revoked). @@ -69,6 +80,14 @@ class DeviceStatusList { // Caller owns and it must not be null. bool GetDeviceInfo(const ClientCert& client_cert, widevine::ProvisionedDeviceInfo* device_info); + + // Returns true if device certificate status list contains revoked_identifiers + // with specific |system_id|. + // Caller owns and it must not be null. + bool GetRevokedIdentifiers( + uint32_t system_id, + DeviceCertificateStatus::RevokedIdentifiers* revoked_identifiers); + // Returns the current POSIX time. virtual uint32_t GetCurrentTime() const; @@ -76,18 +95,56 @@ class DeviceStatusList { // a comma separated list of systems Ids to allow even if revoked. virtual void AllowRevokedDevices(const std::string& system_id_list); - /** - * Parses signed device certificate status list and certificate status list - * from certificateProvisoningServer response. - * - * @param certificate_provisioning_service_response - * @param signed_certificate_status_list - * @param certificate_status_list - * @return WvPLStatus - Status::OK if success, else error. - */ - static Status ExtractFromProvisioningServiceResponse( - const std::string& certificate_provisioning_service_response, - std::string* signed_certificate_status_list, std::string* certificate_status_list); + // Enable delivery of licenses to TEST_ONLY client devices. |device_list| is + // a comma separated list of devices to allow even if the device state is + // TEST_ONLY. Each device is specified by a colon separated system_id and + // manufacturer. If the manufacturer is not specified, all manufacturers for + // that system_id are allowed. + // 'device_list' is expected to be of the format ,..., and + // each 'device' will contain a 'system_id' and 'manufacturer' OR will contain + // only a 'system_id'. + // 'device' is expected to be of the format : OR + // of the format : + // Example usage: + // const std::string device_list = "4121:LG,7912:*" + // AllowTestOnlyDevices(device_list); + virtual void AllowTestOnlyDevices(const std::string& device_list); + + // A comma separated list of DRM Certificate Serial Numbers that are revoked. + virtual void RevokedDrmCertificateSerialNumbers( + const std::string& drm_certificate_serial_numbers); + + // Return true, if the specified |device_certificate_serial_number| was + // revoked ... else, false. + bool IsDrmCertificateRevoked( + const std::string& device_certificate_serial_number); + + // Parses the serialized certificate status list and the signature from the + // service_response. The service_response is the JSON payload that comes + // in the response to a certificate status list request. Both the legacy + // format and the newer SignedDeviceInfo format are supported. + // + // |service_response| is the response provided from the Widevine API that + // produces the certificate list. The response can be in one of a few + // formats: + // 1) The JSON response from the legacy API. + // 2) The Base 64 encoded payload within the JSON response that contains + // the serialized certificate list (Web safe or regular base64). + // 3) The raw bytes of the serialized PublishedDevices proto returned from + // the new Widevine API that generates the serialized certificate list. + // The |certificate_status_list| is the deserialized list from the + // service_response. + // The |serialized_certificate_status_list| is the binary serialized status + // list. This is an out parameter which allows the caller to verify the + // serialized proto against the |signature|. + // The |signature| is the signature of the serialized_certificate_status_list + // using RSASSA-PSS signed with the root certificate private key. + // Returns WvPLStatus - Status::OK if success, else error. + static Status DetermineAndDeserializeServiceResponse( + const std::string& service_response, + DeviceCertificateStatusList* certificate_status_list, + std::string* serialized_certificate_status_list, std::string* signature); + /** * Constructs signed device certificate status list request string. * @@ -97,25 +154,80 @@ class DeviceStatusList { */ static Status GenerateSignedDeviceCertificateStatusListRequest( const std::string& version, + const std::string& serialized_service_certificate, std::string* signed_device_certificate_status_list_request); private: + friend class DeviceStatusListTest; + + /** + * Parses the serialized legacy device certificate status list and signature. + * The certificate_provisioning_service_response is the JSON payload that + * comes in the response to a certificate status list request. + * + * @param legacy_certificate_provisioning_service_response + * @param serialized_certificate_status_list + * @param signature + * @return WvPLStatus - Status::OK if success, else error. + */ + static Status ExtractLegacyDeviceList( + const std::string& raw_certificate_provisioning_service_response, + std::string* serialized_certificate_status_list, std::string* signature); + + /** + * Parses the serialized published devices response. + * The published_devices_info_response is the JSON payload that comes in the + * response to a PublishedDevices request. + * + * @param published_devices_response the serialized PublishedDevices proto + * containing the certificate status list. + * @param serialized_certificate_status_list + * @param signature + * @return WvPLStatus - Status::OK if success, else error. + */ + static Status ExtractPublishedDevicesInfo( + const std::string& serialized_published_devices, + std::string* serialized_certificate_status_list, std::string* signature); + + /** + * Returns a |serialized_device_certificate_status_list| in its output + * parameter by parsing |serialized_signed_device_certificate_status_list| + * returned from Widevine Certificate Provisioning Server. + * + * @param serialized_signed_device_certificate_status_list + * @param serialized_device_certificate_status_list + * + * @return Status - Status::OK if success, else error. + */ + static Status ParseLegacySignedDeviceCertificateStatusList( + const std::string& serialized_signed_device_certificate_status_list, + std::string* serialized_device_certificate_status_list, + std::string* signature); + // Returns true if the system ID is allowed to be revoked. // Caller owns |system_id|. They must not be null. bool IsRevokedSystemIdAllowed(uint32_t system_id); + // Returns true if the device, which is identified by system_id and + // device_manufacturer, is present in |allowed_test_only_devices_|. + bool IsTestOnlyDeviceAllowed(uint32_t system_id, + const std::string device_manufacturer); absl::Mutex status_map_lock_; // Key is the system id for the device. std::map device_status_map_; - uint32_t creation_time_seconds_; - uint32_t expiration_period_seconds_; - bool allow_unknown_devices_; - bool allow_test_only_devices_; + uint32_t creation_time_seconds_ = 0; + uint32_t expiration_period_seconds_ = 0; + bool allow_unknown_devices_ = false; + bool allow_test_only_devices_ = false; // Contains the list of system_id values that are allowed to succeed even if // revoked. std::vector allowed_revoked_devices_; - - DISALLOW_COPY_AND_ASSIGN(DeviceStatusList); + absl::Mutex allowed_test_only_devices_mutex_; + // Contains a map of 'system_id' to 'make'. If 'make' value is "*", any + // 'make' for that 'system_id' is allowed. + std::multimap allowed_test_only_devices_; + // Revoked DRM certificate serial numbers. + std::set revoked_drm_certificate_serial_numbers_; }; } // namespace widevine diff --git a/common/device_status_list_test.cc b/common/device_status_list_test.cc index 73d4683..40ad55b 100644 --- a/common/device_status_list_test.cc +++ b/common/device_status_list_test.cc @@ -9,22 +9,39 @@ #include "common/device_status_list.h" #include + #include #include #include #include "glog/logging.h" +#include "google/protobuf/util/message_differencer.h" #include "testing/gmock.h" #include "testing/gunit.h" +#include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" #include "common/client_cert.h" +#include "common/keybox_client_cert.h" #include "common/rsa_key.h" #include "common/rsa_test_keys.h" +#include "common/status.h" #include "protos/public/client_identification.pb.h" +#include "protos/public/device_certificate_status.pb.h" #include "protos/public/errors.pb.h" #include "protos/public/provisioned_device_info.pb.h" +#include "protos/public/signed_device_info.pb.h" #include "protos/public/signed_drm_certificate.pb.h" +namespace { +const char kTestSystemId_1[] = "4121"; +const char kTestManufacturer_LG[] = "LG"; +const char kTestManufacturer_LGE[] = "LGE"; +const char kTestSystemId_2[] = "8242"; +const char kTestManufacturer_Samsung[] = "Samsung"; +const char kTestSystemId_3[] = "6556"; +const char kTestManufacturer[] = "TestManufacturer"; +} // namespace + namespace widevine { using ::testing::_; @@ -42,6 +59,10 @@ const char kValidSerialNumber[] = "valid-serial-number"; const char kRevokedSerialNumber[] = "revoked-serial-number"; const char kRevokedAllowDeviceSerialNumber[] = "revoked-allow-device-serial-number"; +const char kRevokedDeviceCertificateSerialNumber1[] = "revoked-serial-number_1"; +const char kRevokedDeviceCertificateSerialNumber2[] = "revoked-serial-number_2"; +const char kRevokedDeviceCertificateSerialNumber3[] = "revoked-serial-number_3"; +const char kRevokedUniqueIdentifiers[] = "revoked-unique-identifiers"; const char kTestOnlySerialNumber[] = "test_only-serial-number"; const char kMismatchSerialNumber[] = "mismatch-serial-number"; const char kDeviceModel[] = "device-model-x"; @@ -49,21 +70,25 @@ const char kTestPreprovKey[] = "00112233445566778899aabbccddeeff"; const uint32_t kStatusListCreationTime = 17798001; const uint32_t kDefaultExpirePeriod = 0; -class MockCertificateClientCert : public CertificateClientCert { +class MockClientCert : public ClientCert { public: - MockCertificateClientCert() {} + MockClientCert() {} + ~MockClientCert() override {} MOCK_CONST_METHOD0(system_id, uint32_t()); MOCK_CONST_METHOD0(signer_serial_number, std::string &()); MOCK_CONST_METHOD0(signer_creation_time_seconds, uint32_t()); MOCK_CONST_METHOD0(type, ClientIdentification::TokenType()); MOCK_CONST_METHOD0(signed_by_provisioner, bool()); -}; - -class MockKeyboxClientCert : public KeyboxClientCert { - public: - MockKeyboxClientCert() {} - MOCK_CONST_METHOD0(system_id, uint32_t()); - MOCK_CONST_METHOD0(type, ClientIdentification::TokenType()); + MOCK_CONST_METHOD3(VerifySignature, Status(const std::string &message, + const std::string &signature, + ProtocolVersion protocol_version)); + MOCK_METHOD2(GenerateSigningKey, void(const std::string &message, + ProtocolVersion protocol_version)); + MOCK_CONST_METHOD0(serial_number, const std::string &()); + MOCK_CONST_METHOD0(key, const std::string &()); + MOCK_CONST_METHOD0(service_id, const std::string &()); + MOCK_CONST_METHOD0(encrypted_key, const std::string &()); + MOCK_CONST_METHOD0(signing_key, const std::string &()); }; class DeviceStatusListTest : public ::testing::Test { @@ -79,6 +104,10 @@ class DeviceStatusListTest : public ::testing::Test { cert_status->set_drm_serial_number(kValidSerialNumber); cert_status->mutable_device_info()->set_model(kDeviceModel); cert_status->set_status(DeviceCertificateStatus::STATUS_RELEASED); + cert_status->mutable_revoked_identifiers()->add_revoked_unique_id_hashes( + kRevokedUniqueIdentifiers); + cert_status->mutable_revoked_identifiers() + ->add_revoked_certificate_serial_numbers(kRevokedSerialNumber); // Device cert with status REVOKED. cert_status = cert_status_list_.add_certificate_status(); @@ -102,36 +131,77 @@ class DeviceStatusListTest : public ::testing::Test { cert_status->set_status(DeviceCertificateStatus::STATUS_TEST_ONLY); cert_status_list_.set_creation_time_seconds(kStatusListCreationTime); - cert_status_list_.SerializeToString( - signed_cert_status_list_.mutable_certificate_status_list()); + + // Generate the serialized list and signature. std::unique_ptr root_key( RsaPrivateKey::Create(test_keys_.private_test_key_1_3072_bits())); ASSERT_TRUE(root_key); + cert_status_list_.SerializeToString(&serialized_cert_status_list_); + ASSERT_TRUE(root_key->GenerateSignature(serialized_cert_status_list_, + &cert_status_list_signature_)); - ASSERT_TRUE(root_key->GenerateSignature( - signed_cert_status_list_.certificate_status_list(), - signed_cert_status_list_.mutable_signature())); + // Update the device_status_list_ with the serialized status list + // and signature. + ASSERT_EQ(OkStatus(), + device_status_list_.UpdateStatusList( + test_keys_.public_test_key_1_3072_bits(), + serialized_cert_status_list_, cert_status_list_signature_, + kDefaultExpirePeriod)); + } + + void GenerateTrivialValidStatusList(std::string *serialized_cert_status_list, + std::string *signature) { + DeviceCertificateStatusList cert_status_list; + DeviceCertificateStatus *cert_status; + + // Device cert with status RELEASED. + cert_status = cert_status_list.add_certificate_status(); + cert_status->mutable_device_info()->set_system_id(kValidCertSystemId); + cert_status->set_drm_serial_number(kValidSerialNumber); + cert_status->mutable_device_info()->set_model(kDeviceModel); + cert_status->set_status(DeviceCertificateStatus::STATUS_RELEASED); + + cert_status_list.set_creation_time_seconds(kStatusListCreationTime); + + // Generate the serialized list and signature. + std::unique_ptr root_key( + RsaPrivateKey::Create(test_keys_.private_test_key_1_3072_bits())); + ASSERT_TRUE(root_key); + cert_status_list.SerializeToString(serialized_cert_status_list); ASSERT_TRUE( - signed_cert_status_list_.SerializeToString(&serialized_status_list_)); + root_key->GenerateSignature(*serialized_cert_status_list, signature)); + } - ASSERT_EQ(OkStatus(), device_status_list_.UpdateStatusList( - test_keys_.public_test_key_1_3072_bits(), - serialized_status_list_, kDefaultExpirePeriod)); + int VerifyAllowedTestOnlyDevicesAdded() { + return device_status_list_.allowed_test_only_devices_.size(); + } + + bool VerifyIsTestOnlyDeviceAllowed(uint32_t system_id, + std::string manufacturer) { + return device_status_list_.IsTestOnlyDeviceAllowed(system_id, manufacturer); + } + + int VerifyRevokedDeviceCertificatesCount() { + return device_status_list_.revoked_drm_certificate_serial_numbers_.size(); + } + + bool VerifyIsDeviceCertificateRevoked( + std::string device_certificate_serial_number) { + return device_status_list_.IsDrmCertificateRevoked( + device_certificate_serial_number); } DeviceStatusList device_status_list_; RsaTestKeys test_keys_; DeviceCertificateStatusList cert_status_list_; - SignedDeviceCertificateStatusList signed_cert_status_list_; - std::string serialized_status_list_; + std::string serialized_cert_status_list_; + std::string cert_status_list_signature_; }; -// Returns the number of DevcieCertificateStatus messages in the list. - TEST_F(DeviceStatusListTest, CheckForValidAndRevokedCert) { // Test case where the Certificate status is set to Valid. ProvisionedDeviceInfo device_info; - MockCertificateClientCert valid_client_cert; + MockClientCert valid_client_cert; std::string valid_drm_serial_number(kValidSerialNumber); EXPECT_CALL(valid_client_cert, type()) .WillRepeatedly(Return(ClientIdentification::DRM_DEVICE_CERTIFICATE)); @@ -140,12 +210,13 @@ TEST_F(DeviceStatusListTest, CheckForValidAndRevokedCert) { EXPECT_CALL(valid_client_cert, signer_serial_number()) .WillRepeatedly(ReturnRef(valid_drm_serial_number)); EXPECT_EQ(OkStatus(), - device_status_list_.GetCertStatus(valid_client_cert, &device_info)); + device_status_list_.GetCertStatus(valid_client_cert, + kTestManufacturer, &device_info)); EXPECT_TRUE(device_info.has_model()); EXPECT_EQ(kDeviceModel, device_info.model()); // Test case where the Certificate status is Revoked. - MockCertificateClientCert revoked_client_cert; + MockClientCert revoked_client_cert; std::string revoked_drm_serial_number(kRevokedSerialNumber); EXPECT_CALL(revoked_client_cert, type()) .WillRepeatedly(Return(ClientIdentification::DRM_DEVICE_CERTIFICATE)); @@ -153,19 +224,21 @@ TEST_F(DeviceStatusListTest, CheckForValidAndRevokedCert) { .WillRepeatedly(Return(kRevokedCertSystemId)); EXPECT_CALL(revoked_client_cert, signer_serial_number()) .WillRepeatedly(ReturnRef(revoked_drm_serial_number)); - EXPECT_EQ(DRM_DEVICE_CERTIFICATE_REVOKED, - device_status_list_.GetCertStatus(revoked_client_cert, &device_info) - .error_code()); + EXPECT_EQ( + DRM_DEVICE_CERTIFICATE_REVOKED, + device_status_list_ + .GetCertStatus(revoked_client_cert, kTestManufacturer, &device_info) + .error_code()); // Test case where the revoked cert is allowed. device_status_list_.AllowRevokedDevices(absl::StrCat(kRevokedCertSystemId)); - EXPECT_OK( - device_status_list_.GetCertStatus(revoked_client_cert, &device_info)); + EXPECT_OK(device_status_list_.GetCertStatus(revoked_client_cert, + kTestManufacturer, &device_info)); } -TEST_F(DeviceStatusListTest, TestOnlyCertAllowed) { +TEST_F(DeviceStatusListTest, TestOnlyCertNotAllowed) { ProvisionedDeviceInfo device_info; - MockCertificateClientCert test_only_client_cert; + MockClientCert test_only_client_cert; std::string test_only_drm_serial_number(kTestOnlySerialNumber); EXPECT_CALL(test_only_client_cert, type()) .WillRepeatedly(Return(ClientIdentification::DRM_DEVICE_CERTIFICATE)); @@ -175,13 +248,26 @@ TEST_F(DeviceStatusListTest, TestOnlyCertAllowed) { .WillRepeatedly(ReturnRef(test_only_drm_serial_number)); EXPECT_EQ( DEVELOPMENT_CERTIFICATE_NOT_ALLOWED, - device_status_list_.GetCertStatus(test_only_client_cert, &device_info) + device_status_list_ + .GetCertStatus(test_only_client_cert, kTestManufacturer, &device_info) .error_code()); } -TEST_F(DeviceStatusListTest, TestOnlyCertNotAllowed) { +TEST_F(DeviceStatusListTest, GetRevokedIfentifiers) { + DeviceCertificateStatus::RevokedIdentifiers revoked_identifiers; + ASSERT_TRUE(device_status_list_.GetRevokedIdentifiers(kValidCertSystemId, + &revoked_identifiers)); + EXPECT_EQ(kRevokedSerialNumber, + revoked_identifiers.revoked_certificate_serial_numbers(0)); + EXPECT_EQ(kRevokedUniqueIdentifiers, + revoked_identifiers.revoked_unique_id_hashes(0)); + ASSERT_FALSE(device_status_list_.GetRevokedIdentifiers(kUnknownSystemId, + &revoked_identifiers)); +} + +TEST_F(DeviceStatusListTest, TestOnlyCertAllowed) { ProvisionedDeviceInfo device_info; - MockCertificateClientCert test_only_client_cert; + MockClientCert test_only_client_cert; std::string test_only_drm_serial_number(kTestOnlySerialNumber); device_status_list_.set_allow_test_only_devices(true); EXPECT_CALL(test_only_client_cert, type()) @@ -190,8 +276,9 @@ TEST_F(DeviceStatusListTest, TestOnlyCertNotAllowed) { .WillRepeatedly(Return(kTestOnlyCertSystemId)); EXPECT_CALL(test_only_client_cert, signer_serial_number()) .WillRepeatedly(ReturnRef(test_only_drm_serial_number)); - EXPECT_EQ(OkStatus(), device_status_list_.GetCertStatus(test_only_client_cert, - &device_info)); + EXPECT_EQ(OkStatus(), + device_status_list_.GetCertStatus(test_only_client_cert, + kTestManufacturer, &device_info)); } TEST_F(DeviceStatusListTest, ValidAndUnknownKeybox) { @@ -201,25 +288,27 @@ TEST_F(DeviceStatusListTest, ValidAndUnknownKeybox) { // Test case where the Certificate status is set to Valid. ProvisionedDeviceInfo device_info; - MockKeyboxClientCert valid_client_keybox; + MockClientCert valid_client_keybox; std::string valid_drm_serial_number(kValidSerialNumber); EXPECT_CALL(valid_client_keybox, type()) .WillRepeatedly(Return(ClientIdentification::KEYBOX)); EXPECT_CALL(valid_client_keybox, system_id()) .WillRepeatedly(Return(kValidCertSystemId)); - EXPECT_EQ(OkStatus(), device_status_list_.GetCertStatus(valid_client_keybox, - &device_info)); + EXPECT_EQ(OkStatus(), + device_status_list_.GetCertStatus(valid_client_keybox, + kTestManufacturer, &device_info)); EXPECT_TRUE(device_info.has_model()); EXPECT_EQ(kDeviceModel, device_info.model()); - MockKeyboxClientCert unknown_client_keybox; + MockClientCert unknown_client_keybox; EXPECT_CALL(unknown_client_keybox, type()) .WillRepeatedly(Return(ClientIdentification::KEYBOX)); EXPECT_CALL(unknown_client_keybox, system_id()) .WillRepeatedly(Return(kUnknownSystemId)); EXPECT_EQ( UNSUPPORTED_SYSTEM_ID, - device_status_list_.GetCertStatus(unknown_client_keybox, &device_info) + device_status_list_ + .GetCertStatus(unknown_client_keybox, kTestManufacturer, &device_info) .error_code()); EXPECT_TRUE(device_info.has_model()); EXPECT_EQ(kDeviceModel, device_info.model()); @@ -230,7 +319,7 @@ TEST_F(DeviceStatusListTest, SignerSerialNumberMismatch) { // Test case where the signer certificate is older than the current status // list. - MockCertificateClientCert older_client_cert; + MockClientCert older_client_cert; ProvisionedDeviceInfo device_info; std::string mismatch_drm_serial_number(kMismatchSerialNumber); EXPECT_CALL(older_client_cert, type()) @@ -241,21 +330,24 @@ TEST_F(DeviceStatusListTest, SignerSerialNumberMismatch) { .WillRepeatedly(ReturnRef(mismatch_drm_serial_number)); EXPECT_CALL(older_client_cert, signer_creation_time_seconds()) .WillRepeatedly(Return(kStatusListCreationTime - 1)); - EXPECT_EQ(INVALID_DRM_CERTIFICATE, - device_status_list_.GetCertStatus(older_client_cert, &device_info) - .error_code()); + EXPECT_EQ( + INVALID_DRM_CERTIFICATE, + device_status_list_ + .GetCertStatus(older_client_cert, kTestManufacturer, &device_info) + .error_code()); // We allow this case only for certs signed by a provisioner cert. EXPECT_CALL(older_client_cert, signed_by_provisioner()) .WillOnce(Return(true)); EXPECT_EQ(OkStatus(), - device_status_list_.GetCertStatus(older_client_cert, &device_info)); + device_status_list_.GetCertStatus(older_client_cert, + kTestManufacturer, &device_info)); EXPECT_TRUE(device_info.has_system_id()); EXPECT_EQ(kValidCertSystemId, device_info.system_id()); // Test case where the signer certificate is newer than the current status // list, and unknown devices are allowed. - MockCertificateClientCert newer_client_cert1; + MockClientCert newer_client_cert1; EXPECT_CALL(newer_client_cert1, type()) .WillRepeatedly(Return(ClientIdentification::DRM_DEVICE_CERTIFICATE)); EXPECT_CALL(newer_client_cert1, system_id()) @@ -264,14 +356,16 @@ TEST_F(DeviceStatusListTest, SignerSerialNumberMismatch) { .WillRepeatedly(ReturnRef(mismatch_drm_serial_number)); EXPECT_CALL(newer_client_cert1, signer_creation_time_seconds()) .WillRepeatedly(Return(kStatusListCreationTime)); - EXPECT_EQ(DRM_DEVICE_CERTIFICATE_UNKNOWN, - device_status_list_.GetCertStatus(newer_client_cert1, &device_info) - .error_code()); + EXPECT_EQ( + DRM_DEVICE_CERTIFICATE_UNKNOWN, + device_status_list_ + .GetCertStatus(newer_client_cert1, kTestManufacturer, &device_info) + .error_code()); // Test case where the signer certificate is newer than the current status // list, and unknown devices are not allowed. device_status_list_.set_allow_unknown_devices(false); - MockCertificateClientCert newer_client_cert2; + MockClientCert newer_client_cert2; EXPECT_CALL(newer_client_cert2, type()) .WillRepeatedly(Return(ClientIdentification::DRM_DEVICE_CERTIFICATE)); EXPECT_CALL(newer_client_cert2, system_id()) @@ -280,25 +374,27 @@ TEST_F(DeviceStatusListTest, SignerSerialNumberMismatch) { .WillRepeatedly(ReturnRef(mismatch_drm_serial_number)); EXPECT_CALL(newer_client_cert2, signer_creation_time_seconds()) .WillRepeatedly(Return(kStatusListCreationTime + 1)); - EXPECT_EQ(DRM_DEVICE_CERTIFICATE_UNKNOWN, - device_status_list_.GetCertStatus(newer_client_cert2, &device_info) - .error_code()); + EXPECT_EQ( + DRM_DEVICE_CERTIFICATE_UNKNOWN, + device_status_list_ + .GetCertStatus(newer_client_cert2, kTestManufacturer, &device_info) + .error_code()); } TEST_F(DeviceStatusListTest, InvalidStatusList) { EXPECT_EQ(INVALID_CERTIFICATE_STATUS_LIST, device_status_list_ .UpdateStatusList(test_keys_.public_test_key_2_2048_bits(), - serialized_status_list_, 0) + serialized_cert_status_list_, + cert_status_list_signature_, 0) .error_code()); - ++(*signed_cert_status_list_.mutable_certificate_status_list())[4]; - ASSERT_TRUE( - signed_cert_status_list_.SerializeToString(&serialized_status_list_)); + ++(serialized_cert_status_list_)[4]; EXPECT_EQ(INVALID_CERTIFICATE_STATUS_LIST, device_status_list_ .UpdateStatusList(test_keys_.public_test_key_1_3072_bits(), - serialized_status_list_, 0) + serialized_cert_status_list_, + cert_status_list_signature_, 0) .error_code()); } @@ -315,11 +411,13 @@ TEST_F(DeviceStatusListTest, ExpiredStatusListOnSet) { .WillOnce(Return(kStatusListCreationTime + 101)); EXPECT_EQ(OkStatus(), mock_device_status_list.UpdateStatusList( test_keys_.public_test_key_1_3072_bits(), - serialized_status_list_, 100)); + serialized_cert_status_list_, + cert_status_list_signature_, 100)); EXPECT_EQ(EXPIRED_CERTIFICATE_STATUS_LIST, mock_device_status_list .UpdateStatusList(test_keys_.public_test_key_1_3072_bits(), - serialized_status_list_, 100) + serialized_cert_status_list_, + cert_status_list_signature_, 100) .error_code()); } @@ -332,10 +430,11 @@ TEST_F(DeviceStatusListTest, ExpiredStatusListOnCertCheck) { .WillOnce(Return(kStatusListCreationTime + 101)); EXPECT_EQ(OkStatus(), mock_device_status_list.UpdateStatusList( test_keys_.public_test_key_1_3072_bits(), - serialized_status_list_, 100)); + serialized_cert_status_list_, + cert_status_list_signature_, 100)); ProvisionedDeviceInfo device_info; - MockCertificateClientCert valid_client_cert; + MockClientCert valid_client_cert; std::string valid_drm_serial_number(kValidSerialNumber); EXPECT_CALL(valid_client_cert, type()) .WillRepeatedly(Return(ClientIdentification::DRM_DEVICE_CERTIFICATE)); @@ -345,12 +444,14 @@ TEST_F(DeviceStatusListTest, ExpiredStatusListOnCertCheck) { .WillRepeatedly(ReturnRef(valid_drm_serial_number)); EXPECT_CALL(valid_client_cert, signer_creation_time_seconds()) .WillRepeatedly(Return(kStatusListCreationTime - 1)); - EXPECT_EQ(OkStatus(), mock_device_status_list.GetCertStatus(valid_client_cert, - &device_info)); + EXPECT_EQ(OkStatus(), + mock_device_status_list.GetCertStatus( + valid_client_cert, kTestManufacturer, &device_info)); EXPECT_EQ( EXPIRED_CERTIFICATE_STATUS_LIST, - mock_device_status_list.GetCertStatus(valid_client_cert, &device_info) + mock_device_status_list + .GetCertStatus(valid_client_cert, kTestManufacturer, &device_info) .error_code()); } @@ -373,4 +474,219 @@ TEST_F(DeviceStatusListTest, IsSystemIdActive) { device_status_list_.IsSystemIdActive(kRevokedAllowedDeviceCertSystemId)); } +TEST_F(DeviceStatusListTest, IsTestOnlyDeviceAllowed) { + std::string whitelisted_device_list = + std::string(kTestSystemId_1) + ":" + std::string(kTestManufacturer_LG); + whitelisted_device_list += "," + std::string(kTestSystemId_2) + ":" + + std::string(kTestManufacturer_Samsung); + whitelisted_device_list += "," + std::string(kTestSystemId_3) + ":"; + whitelisted_device_list += ", " + std::string(kTestSystemId_1) + ":" + + std::string(kTestManufacturer_LGE); + device_status_list_.AllowTestOnlyDevices(whitelisted_device_list); + EXPECT_EQ(4, VerifyAllowedTestOnlyDevicesAdded()); + // Verify that device with system_id = kTestSystemId_1 and + // manufacturer = kTestManufacturer_LG is allowed. + EXPECT_TRUE(VerifyIsTestOnlyDeviceAllowed(std::stoi(kTestSystemId_1), + kTestManufacturer_LG)); + // Verify that device with system_id = kTestSystemId_1 and + // manufacturer = kTestManufacturer_LGE is allowed. + EXPECT_TRUE(VerifyIsTestOnlyDeviceAllowed(std::stoi(kTestSystemId_1), + kTestManufacturer_LGE)); + // Verify that device with system_id = kTestSystemId_2 and + // manufacturer = kTestManufacturer_LGE is not allowed. + // This is because this combination is not 'whitelisted'. + EXPECT_FALSE(VerifyIsTestOnlyDeviceAllowed(std::stoi(kTestSystemId_2), + kTestManufacturer_LGE)); + // Verify that device with system_id = kTestSystemId_2 and + // manufacturer = kTestManufacturer_Samsung is allowed. + EXPECT_TRUE(VerifyIsTestOnlyDeviceAllowed(std::stoi(kTestSystemId_2), + kTestManufacturer_Samsung)); + // Verifes that device with mixed case succeeds. + EXPECT_TRUE( + VerifyIsTestOnlyDeviceAllowed(std::stoi(kTestSystemId_2), "samSung")); + EXPECT_TRUE( + VerifyIsTestOnlyDeviceAllowed(std::stoi(kTestSystemId_2), "SAMsung")); + // Verify that device with system_id = kTestSystemId_3 and + // any manufacturer is allowed. This checks that any manufacturer is + // allowed for this system_id. + EXPECT_TRUE( + VerifyIsTestOnlyDeviceAllowed(std::stoi(kTestSystemId_3), "Cisco")); + EXPECT_TRUE(VerifyIsTestOnlyDeviceAllowed(std::stoi(kTestSystemId_3), + "ScientificAtlanta")); + uint32_t unknown_system_id = 7890; + // Verify that device with system_id = unknown_system_id and + // manufacturer = "Cisco" is not allowed. + EXPECT_FALSE(VerifyIsTestOnlyDeviceAllowed(unknown_system_id, "Cisco")); +} + +TEST_F(DeviceStatusListTest, IsDrmDeviceCertificateRevoked) { + std::string revoked_device_certificate_serial_numbers = + std::string(kRevokedDeviceCertificateSerialNumber1); + revoked_device_certificate_serial_numbers += + "," + std::string(kRevokedDeviceCertificateSerialNumber2); + revoked_device_certificate_serial_numbers += + "," + std::string(kRevokedDeviceCertificateSerialNumber3); + device_status_list_.RevokedDrmCertificateSerialNumbers( + revoked_device_certificate_serial_numbers); + EXPECT_EQ(3, VerifyRevokedDeviceCertificatesCount()); + // Verify that device certificates with serial number, + // 'kRevokedDeviceCertificateSerialNumber1', + // 'kRevokedDeviceCertificateSerialNumber2', + // 'kRevokedDeviceCertificateSerialNumber3' are revoked. + EXPECT_TRUE( + VerifyIsDeviceCertificateRevoked(kRevokedDeviceCertificateSerialNumber1)); + EXPECT_TRUE( + VerifyIsDeviceCertificateRevoked(kRevokedDeviceCertificateSerialNumber2)); + EXPECT_TRUE( + VerifyIsDeviceCertificateRevoked(kRevokedDeviceCertificateSerialNumber3)); + const std::string unrevoked_device_certificate_serial_number = + "unrevoked_device_certificate_serial_number"; + EXPECT_FALSE(VerifyIsDeviceCertificateRevoked( + unrevoked_device_certificate_serial_number)); +} + +TEST_F(DeviceStatusListTest, DetermineAndDeserializeServiceResponseSuccess) { + SignedDeviceInfo published_devices; + GenerateTrivialValidStatusList( + published_devices.mutable_device_certificate_status_list(), + published_devices.mutable_signature()); + + std::string serialized_published_devices; + ASSERT_TRUE( + published_devices.SerializeToString(&serialized_published_devices)); + + DeviceCertificateStatusList actual_cert_status_list; + std::string actual_serialized_cert_status_list; + std::string actual_signature; + ASSERT_EQ(OkStatus(), + DeviceStatusList::DetermineAndDeserializeServiceResponse( + serialized_published_devices, &actual_cert_status_list, + &actual_serialized_cert_status_list, &actual_signature)); + EXPECT_EQ(published_devices.device_certificate_status_list(), + actual_serialized_cert_status_list); + EXPECT_EQ(published_devices.signature(), actual_signature); + + DeviceCertificateStatusList expected_cert_status_list; + ASSERT_TRUE(expected_cert_status_list.ParseFromString( + published_devices.device_certificate_status_list())); + EXPECT_TRUE(google::protobuf::util::MessageDifferencer::Equals( + expected_cert_status_list, actual_cert_status_list)); +} + +TEST_F(DeviceStatusListTest, + DetermineAndDeserializeServiceResponseLegacySuccess) { + std::string serialized_cert_status_list; + std::string signature; + GenerateTrivialValidStatusList(&serialized_cert_status_list, &signature); + + SignedDeviceCertificateStatusList legacy_signed_cert_status_list; + *(legacy_signed_cert_status_list.mutable_certificate_status_list()) = + serialized_cert_status_list; + *(legacy_signed_cert_status_list.mutable_signature()) = signature; + + std::string serialized_signed_cert_status_list; + ASSERT_TRUE(legacy_signed_cert_status_list.SerializeToString( + &serialized_signed_cert_status_list)); + + std::string websafe_b64_serialized_signed_cert_status_list; + absl::WebSafeBase64Escape(serialized_signed_cert_status_list, + &websafe_b64_serialized_signed_cert_status_list); + + std::string server_response = absl::StrCat( + "{\n" + " \"kind\": \"certificateprovisioning#certificateStatusListResponse\"\n" + " \"signedList\": \"", + websafe_b64_serialized_signed_cert_status_list, "\"\n}\n"); + + std::string actual_serialized_cert_status_list; + std::string actual_signature; + DeviceCertificateStatusList actual_cert_status_list; + ASSERT_EQ(OkStatus(), + DeviceStatusList::DetermineAndDeserializeServiceResponse( + server_response, &actual_cert_status_list, + &actual_serialized_cert_status_list, &actual_signature)); + EXPECT_EQ(serialized_cert_status_list, actual_serialized_cert_status_list); + EXPECT_EQ(signature, actual_signature); + + DeviceCertificateStatusList expected_cert_status_list; + ASSERT_TRUE( + expected_cert_status_list.ParseFromString(serialized_cert_status_list)); + EXPECT_TRUE(google::protobuf::util::MessageDifferencer::Equals( + expected_cert_status_list, actual_cert_status_list)); +} + +TEST_F(DeviceStatusListTest, + DetermineAndDeserializeServiceResponseLegacyWebSafeBase64Success) { + std::string serialized_cert_status_list; + std::string signature; + GenerateTrivialValidStatusList(&serialized_cert_status_list, &signature); + + SignedDeviceCertificateStatusList legacy_signed_cert_status_list; + *(legacy_signed_cert_status_list.mutable_certificate_status_list()) = + serialized_cert_status_list; + *(legacy_signed_cert_status_list.mutable_signature()) = signature; + + std::string serialized_signed_cert_status_list; + ASSERT_TRUE(legacy_signed_cert_status_list.SerializeToString( + &serialized_signed_cert_status_list)); + + std::string websafe_b64_serialized_signed_cert_status_list; + absl::WebSafeBase64Escape(serialized_signed_cert_status_list, + &websafe_b64_serialized_signed_cert_status_list); + + std::string actual_serialized_cert_status_list; + std::string actual_signature; + DeviceCertificateStatusList actual_cert_status_list; + ASSERT_EQ(OkStatus(), + DeviceStatusList::DetermineAndDeserializeServiceResponse( + websafe_b64_serialized_signed_cert_status_list, + &actual_cert_status_list, &actual_serialized_cert_status_list, + &actual_signature)); + EXPECT_EQ(serialized_cert_status_list, actual_serialized_cert_status_list); + EXPECT_EQ(signature, actual_signature); + + DeviceCertificateStatusList expected_cert_status_list; + ASSERT_TRUE( + expected_cert_status_list.ParseFromString(serialized_cert_status_list)); + EXPECT_TRUE(google::protobuf::util::MessageDifferencer::Equals( + expected_cert_status_list, actual_cert_status_list)); +} + +TEST_F(DeviceStatusListTest, + DetermineAndDeserializeServiceResponseLegacyBase64Success) { + std::string serialized_cert_status_list; + std::string signature; + GenerateTrivialValidStatusList(&serialized_cert_status_list, &signature); + + SignedDeviceCertificateStatusList legacy_signed_cert_status_list; + *(legacy_signed_cert_status_list.mutable_certificate_status_list()) = + serialized_cert_status_list; + *(legacy_signed_cert_status_list.mutable_signature()) = signature; + + std::string serialized_signed_cert_status_list; + ASSERT_TRUE(legacy_signed_cert_status_list.SerializeToString( + &serialized_signed_cert_status_list)); + + std::string websafe_b64_serialized_signed_cert_status_list; + absl::WebSafeBase64Escape(serialized_signed_cert_status_list, + &websafe_b64_serialized_signed_cert_status_list); + + std::string actual_serialized_cert_status_list; + std::string actual_signature; + DeviceCertificateStatusList actual_cert_status_list; + ASSERT_EQ(OkStatus(), + DeviceStatusList::DetermineAndDeserializeServiceResponse( + websafe_b64_serialized_signed_cert_status_list, + &actual_cert_status_list, &actual_serialized_cert_status_list, + &actual_signature)); + EXPECT_EQ(serialized_cert_status_list, actual_serialized_cert_status_list); + EXPECT_EQ(signature, actual_signature); + + DeviceCertificateStatusList expected_cert_status_list; + ASSERT_TRUE( + expected_cert_status_list.ParseFromString(serialized_cert_status_list)); + EXPECT_TRUE(google::protobuf::util::MessageDifferencer::Equals( + expected_cert_status_list, actual_cert_status_list)); +} + } // namespace widevine diff --git a/common/drm_root_certificate.cc b/common/drm_root_certificate.cc index b211a96..61cde09 100644 --- a/common/drm_root_certificate.cc +++ b/common/drm_root_certificate.cc @@ -16,9 +16,11 @@ #include "absl/memory/memory.h" #include "absl/strings/escaping.h" #include "absl/synchronization/mutex.h" +#include "common/ec_key.h" #include "common/error_space.h" #include "common/rsa_key.h" #include "common/sha_util.h" +#include "common/signer_public_key.h" #include "protos/public/drm_certificate.pb.h" #include "protos/public/errors.pb.h" #include "protos/public/signed_drm_certificate.pb.h" @@ -33,6 +35,13 @@ const char kTestingString[] = "test"; // Code development / unit tests. const bool kUseCache = true; +// Restrict the certificate chain size. All leaf DRM certificates must be a +// DEVICE certificate. The DEVICE certificate must be signed by a MODEL +// certificate. The MODEL certificate can be signed by Widevine or by a +// PROVISIONER certificate which is signed by Widevine. Do not allow any +// additional links. +const uint32_t kMaxCertificateChainSize = 3; + // From common::TestDrmCertificates. // TODO(user): common::test_certificates is a testonly target, consider // how to use instead of dupliciating the test cert here. @@ -248,12 +257,17 @@ static const unsigned char kProdRootCertificate[] = { // number (signer). struct VerifiedCertSignature { VerifiedCertSignature(const std::string& cert, const std::string& sig, - const std::string& signer_sn) - : signed_cert(cert), signature(sig), signer_serial(signer_sn) {} + const std::string& signer_sn, + const std::string& signer_pub_key) + : signed_cert(cert), + signature(sig), + signer_serial(signer_sn), + signer_public_key(signer_pub_key) {} std::string signed_cert; std::string signature; std::string signer_serial; + std::string signer_public_key; }; // Map of certificate serial number to its signature. @@ -265,50 +279,57 @@ class VerifiedCertSignatureCache { // Checks cache, on miss, uses public key. If successful, adds to // cache. - Status VerifySignature(const std::string& cert, const std::string& serial_number, + Status VerifySignature(const std::string& cert, + const std::string& serial_number, const std::string& signature, - const std::string& signer_public_key, - const std::string& signer_serial_number) { + const DrmCertificate& signer) { { VerifiedCertSignatures::iterator cached_signature; absl::ReaderMutexLock read_lock(&signature_cache_mutex_); cached_signature = signature_cache_.find(serial_number); if (cached_signature != signature_cache_.end()) { - // TODO(user): Log which of the following three conditions occurs. - if ((cert != cached_signature->second.signed_cert) || - (signature != cached_signature->second.signature) || - (signer_serial_number != cached_signature->second.signer_serial)) { - // Cached signature mismatch. + if (cert != cached_signature->second.signed_cert) { + return Status(error_space, INVALID_SIGNATURE, "cached-cert-mismatch"); + } + if (signature != cached_signature->second.signature) { return Status(error_space, INVALID_SIGNATURE, "cached-signature-mismatch"); } - // Cached signature match. + if (signer.serial_number() != cached_signature->second.signer_serial) { + return Status(error_space, INVALID_SIGNATURE, + "cached-serial-number-mismatch"); + } + if (signer.public_key() != cached_signature->second.signer_public_key) { + return Status(error_space, INVALID_SIGNATURE, + "cached-signer-public-key-mismatch"); + } return OkStatus(); } } // Cache miss. Verify signature. - std::unique_ptr signer_key( - key_factory_->CreateFromPkcs1PublicKey(signer_public_key)); - if (!signer_key) { + std::unique_ptr signer_public_key = + SignerPublicKey::Create(signer.public_key(), signer.algorithm()); + if (signer_public_key == nullptr) { return Status(error_space, INVALID_DRM_CERTIFICATE, "invalid-signer-public-key"); } - if (!signer_key->VerifySignature(cert, signature)) { + if (!signer_public_key->VerifySignature(cert, signature)) { return Status(error_space, INVALID_SIGNATURE, "cache-miss-invalid-signature"); } - // Add signature to cache. absl::WriterMutexLock write_lock(&signature_cache_mutex_); signature_cache_.emplace( serial_number, - VerifiedCertSignature(cert, signature, signer_serial_number)); + VerifiedCertSignature(cert, signature, signer.serial_number(), + signer.public_key())); return OkStatus(); } private: - VerifiedCertSignatures signature_cache_ GUARDED_BY(&signature_cache_mutex_); + VerifiedCertSignatures signature_cache_ + ABSL_GUARDED_BY(&signature_cache_mutex_); absl::Mutex signature_cache_mutex_; const RsaKeyFactory* key_factory_; }; @@ -330,7 +351,8 @@ std::unique_ptr DrmRootCertificate::CreateByType( } Status DrmRootCertificate::CreateByTypeString( - const std::string& cert_type_string, std::unique_ptr* cert) { + const std::string& cert_type_string, + std::unique_ptr* cert) { CHECK(cert); CertificateType cert_type; @@ -400,9 +422,9 @@ Status DrmRootCertificate::Create(CertificateType cert_type, "missing-root-certificate-signature"); } - std::unique_ptr public_key( - key_factory->CreateFromPkcs1PublicKey(root_cert.public_key())); - if (!public_key) { + std::unique_ptr public_key = + SignerPublicKey::Create(root_cert.public_key(), root_cert.algorithm()); + if (public_key == nullptr) { return Status(error_space, INVALID_DRM_CERTIFICATE, "invalid-root-public-key"); } @@ -424,10 +446,11 @@ DrmRootCertificate::DrmRootCertificate( std::unique_ptr key_factory) : type_(type), serialized_certificate_(serialized_certificate), - serial_number_(serial_number), - public_key_(public_key), key_factory_(std::move(key_factory)), - signature_cache_(new VerifiedCertSignatureCache(key_factory_.get())) {} + signature_cache_(new VerifiedCertSignatureCache(key_factory_.get())) { + root_cert_.set_public_key(public_key); + root_cert_.set_serial_number(serial_number); +} DrmRootCertificate::~DrmRootCertificate() {} @@ -472,8 +495,9 @@ Status DrmRootCertificate::VerifyCertificate( } // Verify signature chain, but do not use cache for leaf certificates. + uint32_t certs_in_chain = 0; return VerifySignatures(*signed_certificate, certificate->serial_number(), - !kUseCache); + !kUseCache, &certs_in_chain); } // Recursively verifies certificates with their signing certs or the root. @@ -483,39 +507,48 @@ Status DrmRootCertificate::VerifyCertificate( // Signatures for root-signed certificates are always cached, even if they are // leaf certificates. For example service, and provisioner certificates. Status DrmRootCertificate::VerifySignatures( - const SignedDrmCertificate& signed_cert, const std::string& cert_serial_number, - bool use_cache) const { + const SignedDrmCertificate& signed_cert, + const std::string& cert_serial_number, bool use_cache, + uint32_t* certs_in_chain) const { + CHECK(certs_in_chain); + if (++(*certs_in_chain) > kMaxCertificateChainSize) { + return Status(error_space, INVALID_DRM_CERTIFICATE, + "certificate-chain-size-exceeded"); + } if (!signed_cert.has_signer()) { // Always use cache for root-signed certificates. return signature_cache_->VerifySignature( signed_cert.drm_certificate(), cert_serial_number, - signed_cert.signature(), public_key(), serial_number_); + signed_cert.signature(), root_cert_); } - DrmCertificate signer; if (!signer.ParseFromString(signed_cert.signer().drm_certificate())) { return Status(error_space, INVALID_DRM_CERTIFICATE, "invalid-signer-certificate"); } - + // Signer cannot be a device certificate. + if (signer.type() == DrmCertificate::DEVICE) { + return Status(error_space, INVALID_DRM_CERTIFICATE, + "device-cert-must-be-leaf"); + } // Verify the signer before verifying signed_cert. - Status status = - VerifySignatures(signed_cert.signer(), signer.serial_number(), kUseCache); + Status status = VerifySignatures(signed_cert.signer(), signer.serial_number(), + kUseCache, certs_in_chain); if (!status.ok()) { return status; } if (use_cache) { - status = signature_cache_->VerifySignature( - signed_cert.drm_certificate(), cert_serial_number, - signed_cert.signature(), signer.public_key(), signer.serial_number()); + status = signature_cache_->VerifySignature(signed_cert.drm_certificate(), + cert_serial_number, + signed_cert.signature(), signer); if (!status.ok()) { return status; } } else { - std::unique_ptr signer_public_key( - key_factory_->CreateFromPkcs1PublicKey(signer.public_key())); - if (!signer_public_key) { + std::unique_ptr signer_public_key = + SignerPublicKey::Create(signer.public_key(), signer.algorithm()); + if (signer_public_key == nullptr) { return Status(error_space, INVALID_DRM_CERTIFICATE, "invalid-leaf-signer-public-key"); } diff --git a/common/drm_root_certificate.h b/common/drm_root_certificate.h index e786351..630f28c 100644 --- a/common/drm_root_certificate.h +++ b/common/drm_root_certificate.h @@ -18,10 +18,10 @@ #include #include -#include "base/macros.h" -#include "common/status.h" - #include "common/certificate_type.h" +#include "common/signer_public_key.h" +#include "common/status.h" +#include "protos/public/drm_certificate.pb.h" namespace widevine { @@ -35,6 +35,9 @@ class VerifiedCertSignatureCache; // This object is thread-safe. class DrmRootCertificate { public: + DrmRootCertificate(const DrmRootCertificate&) = delete; + DrmRootCertificate& operator=(const DrmRootCertificate&) = delete; + virtual ~DrmRootCertificate(); // Creates a DrmRootCertificate object given a certificate type. @@ -72,12 +75,15 @@ class DrmRootCertificate { const CertificateType type() const { return type_; } - const std::string& public_key() const { return public_key_; } + virtual const std::string& public_key() const { + return root_cert_.public_key(); + } protected: DrmRootCertificate(CertificateType cert_type, const std::string& serialized_certificate, - const std::string& serial_number, const std::string& public_key, + const std::string& serial_number, + const std::string& public_key, std::unique_ptr key_factory); private: @@ -88,17 +94,16 @@ class DrmRootCertificate { std::unique_ptr* cert); Status VerifySignatures(const SignedDrmCertificate& signed_cert, - const std::string& cert_serial_number, - bool use_cache) const; + const std::string& cert_serial_number, bool use_cache, + uint32_t* certs_in_chain) const; CertificateType type_; std::string serialized_certificate_; - std::string serial_number_; - std::string public_key_; + DrmCertificate root_cert_; + // TODO(b/143309971): Either add an ec key_factory object, or drop the rsa + // |key_factory_|. std::unique_ptr key_factory_; mutable std::unique_ptr signature_cache_; - - DISALLOW_IMPLICIT_CONSTRUCTORS(DrmRootCertificate); }; } // namespace widevine diff --git a/common/drm_root_certificate_test.cc b/common/drm_root_certificate_test.cc index 24c3509..2c5f6f8 100644 --- a/common/drm_root_certificate_test.cc +++ b/common/drm_root_certificate_test.cc @@ -13,9 +13,13 @@ #include +#include "glog/logging.h" #include "google/protobuf/util/message_differencer.h" #include "testing/gmock.h" #include "testing/gunit.h" +#include "absl/memory/memory.h" +#include "common/ec_key.h" +#include "common/ec_test_keys.h" #include "common/error_space.h" #include "common/rsa_key.h" #include "common/rsa_test_keys.h" @@ -25,6 +29,7 @@ #include "protos/public/signed_drm_certificate.pb.h" using google::protobuf::util::MessageDifferencer; +using ::testing::Values; namespace widevine { @@ -80,76 +85,214 @@ TEST(DrmRootCertificateTestCertificatesTest, Success) { ->VerifyCertificate(test_certs.test_user_device_certificate(), nullptr, nullptr) .ok()); - EXPECT_TRUE(root_cert - ->VerifyCertificate(test_certs.test_service_certificate(), - nullptr, nullptr) - .ok()); + EXPECT_TRUE( + root_cert + ->VerifyCertificate(test_certs.test_service_certificate_no_type(), + nullptr, nullptr) + .ok()); } -class DrmRootCertificateTest : public testing::Test { +TEST(DrmRootCertificateTestCertificatesTest, NonWidevineRootSigner) { + // TODO(b/138929855): Add test to verify certificate chain is signed by + // Widevine. +} + +class SignerPrivateKey { + public: + virtual ~SignerPrivateKey() {} + virtual bool GenerateSignature(const std::string& message, + std::string* signature) const = 0; + virtual DrmCertificate::Algorithm algorithm() const = 0; + static std::unique_ptr Create( + const std::string& private_key, + widevine::DrmCertificate::Algorithm algorithm); + protected: - DrmRootCertificateTest() { - private_keys_.emplace_back( - RsaPrivateKey::Create(test_keys_.private_test_key_1_3072_bits())); - private_keys_.emplace_back( - RsaPrivateKey::Create(test_keys_.private_test_key_2_2048_bits())); - private_keys_.emplace_back( - RsaPrivateKey::Create(test_keys_.private_test_key_3_2048_bits())); + SignerPrivateKey() {} +}; + +template +class SignerPrivateKeyImpl : public SignerPrivateKey { + public: + SignerPrivateKeyImpl(std::unique_ptr private_key, + DrmCertificate::Algorithm algorithm) + : private_key_(std::move(private_key)), algorithm_(algorithm) {} + ~SignerPrivateKeyImpl() override {} + bool GenerateSignature(const std::string& message, + std::string* signature) const override { + return private_key_->GenerateSignature(message, signature); } + DrmCertificate::Algorithm algorithm() const override { return algorithm_; } + + private: + std::unique_ptr private_key_; + DrmCertificate::Algorithm algorithm_; +}; + +std::unique_ptr SignerPrivateKey::Create( + const std::string& private_key, + widevine::DrmCertificate::Algorithm algorithm) { + DCHECK(algorithm != DrmCertificate::UNKNOWN_ALGORITHM); + switch (algorithm) { + case DrmCertificate::RSA: { + auto rsa_key = + std::unique_ptr(RsaPrivateKey::Create(private_key)); + CHECK(rsa_key); + std::unique_ptr new_rsa_signer_private_key = + absl::make_unique>( + std::move(rsa_key), algorithm); + CHECK(new_rsa_signer_private_key); + return new_rsa_signer_private_key; + } + case DrmCertificate::ECC_SECP256R1: + case DrmCertificate::ECC_SECP384R1: + case DrmCertificate::ECC_SECP521R1: { + auto ec_key = ECPrivateKey::Create(private_key); + CHECK(ec_key); + std::unique_ptr new_ec_signer_private_key = + absl::make_unique>( + std::move(ec_key), algorithm); + CHECK(new_ec_signer_private_key); + return new_ec_signer_private_key; + } + default: + break; + } + return nullptr; +} + +static const int kDrmRootKey = 0; +static const int kInterMediateKey = 1; +static const int kClientKey = 2; + +class DrmRootCertificateTest : public testing::TestWithParam { + protected: + DrmRootCertificateTest() {} void SetUp() override { - drm_certificates_[0].set_serial_number("level 0"); - drm_certificates_[0].set_creation_time_seconds(0); - drm_certificates_[0].set_public_key( - test_keys_.public_test_key_1_3072_bits()); - drm_certificates_[1].set_serial_number("level 1"); - drm_certificates_[1].set_creation_time_seconds(1); - drm_certificates_[1].set_public_key( - test_keys_.public_test_key_2_2048_bits()); - drm_certificates_[2].set_serial_number("level 2"); - drm_certificates_[2].set_creation_time_seconds(2); - drm_certificates_[2].set_public_key( - test_keys_.public_test_key_3_2048_bits()); + bool algorithm_status = false; + std::string algorithm(GetParam()); + if (algorithm == "RSA") { + RsaTestSetup(); + algorithm_status = true; + } + if (algorithm == "ECC") { + EcTestSetup(); + algorithm_status = true; + } + + CHECK(algorithm_status); ASSERT_EQ(OkStatus(), DrmRootCertificate::CreateByType( kCertificateTypeTesting, &root_cert_)); } + void RsaTestSetup() { + private_keys_.resize(3); + private_keys_[kDrmRootKey] = + SignerPrivateKey::Create(rsa_test_keys_.private_test_key_1_3072_bits(), + widevine::DrmCertificate::RSA); + drm_certificates_[kDrmRootKey].set_serial_number("level 0"); + drm_certificates_[kDrmRootKey].set_creation_time_seconds(0); + drm_certificates_[kDrmRootKey].set_public_key( + rsa_test_keys_.public_test_key_1_3072_bits()); + + private_keys_[kInterMediateKey] = + SignerPrivateKey::Create(rsa_test_keys_.private_test_key_2_2048_bits(), + widevine::DrmCertificate::RSA); + drm_certificates_[kInterMediateKey].set_serial_number("level 1"); + drm_certificates_[kInterMediateKey].set_creation_time_seconds(1); + drm_certificates_[kInterMediateKey].set_public_key( + rsa_test_keys_.public_test_key_2_2048_bits()); + + private_keys_[kClientKey] = + SignerPrivateKey::Create(rsa_test_keys_.private_test_key_1_3072_bits(), + widevine::DrmCertificate::RSA); + drm_certificates_[kClientKey].set_serial_number("level 2"); + drm_certificates_[kClientKey].set_creation_time_seconds(2); + drm_certificates_[kClientKey].set_public_key( + rsa_test_keys_.public_test_key_3_2048_bits()); + } + + void EcTestSetup() { + private_keys_.resize(3); + private_keys_[kDrmRootKey] = + SignerPrivateKey::Create(rsa_test_keys_.private_test_key_1_3072_bits(), + widevine::DrmCertificate::RSA); + drm_certificates_[kDrmRootKey].set_serial_number("level 0"); + drm_certificates_[kDrmRootKey].set_creation_time_seconds(0); + drm_certificates_[kDrmRootKey].set_public_key( + rsa_test_keys_.public_test_key_1_3072_bits()); + + private_keys_[kInterMediateKey] = + SignerPrivateKey::Create(ec_test_keys_.private_test_key_1_secp521r1(), + DrmCertificate::ECC_SECP521R1); + drm_certificates_[kInterMediateKey].set_serial_number("level 1"); + drm_certificates_[kInterMediateKey].set_creation_time_seconds(1); + drm_certificates_[kInterMediateKey].set_public_key( + ec_test_keys_.public_test_key_1_secp521r1()); + drm_certificates_[kInterMediateKey].set_algorithm( + DrmCertificate::ECC_SECP521R1); + + private_keys_[kClientKey] = + SignerPrivateKey::Create(ec_test_keys_.private_test_key_1_secp256r1(), + DrmCertificate::ECC_SECP256R1); + drm_certificates_[kClientKey].set_serial_number("level 2"); + drm_certificates_[kClientKey].set_creation_time_seconds(2); + drm_certificates_[kClientKey].set_public_key( + ec_test_keys_.public_test_key_1_secp256r1()); + drm_certificates_[kClientKey].set_algorithm(DrmCertificate::ECC_SECP256R1); + + // Client certificate. + + // Intermediate certificate. + } + void GenerateSignedDrmCertificate() { SignedDrmCertificate* current_sc(&signed_drm_certificate_); - ASSERT_TRUE(drm_certificates_[2].SerializeToString( + drm_certificates_[kClientKey].set_algorithm( + private_keys_[kClientKey]->algorithm()); + ASSERT_TRUE(drm_certificates_[kClientKey].SerializeToString( current_sc->mutable_drm_certificate())); - ASSERT_TRUE(private_keys_[1]->GenerateSignature( + ASSERT_TRUE(private_keys_[kInterMediateKey]->GenerateSignature( current_sc->drm_certificate(), current_sc->mutable_signature())); current_sc = current_sc->mutable_signer(); - ASSERT_TRUE(drm_certificates_[1].SerializeToString( + drm_certificates_[kInterMediateKey].set_algorithm( + private_keys_[kInterMediateKey]->algorithm()); + ASSERT_TRUE(drm_certificates_[kInterMediateKey].SerializeToString( current_sc->mutable_drm_certificate())); - ASSERT_TRUE(private_keys_[0]->GenerateSignature( + ASSERT_TRUE(private_keys_[kDrmRootKey]->GenerateSignature( current_sc->drm_certificate(), current_sc->mutable_signature())); current_sc = current_sc->mutable_signer(); - ASSERT_TRUE(drm_certificates_[0].SerializeToString( + drm_certificates_[kDrmRootKey].set_algorithm( + private_keys_[kDrmRootKey]->algorithm()); + ASSERT_TRUE(drm_certificates_[kDrmRootKey].SerializeToString( current_sc->mutable_drm_certificate())); - ASSERT_TRUE(private_keys_[0]->GenerateSignature( + ASSERT_TRUE(private_keys_[kDrmRootKey]->GenerateSignature( current_sc->drm_certificate(), current_sc->mutable_signature())); } - RsaTestKeys test_keys_; - std::vector> private_keys_; + RsaTestKeys rsa_test_keys_; + ECTestKeys ec_test_keys_; + std::vector> private_keys_; SignedDrmCertificate signed_drm_certificate_; DrmCertificate drm_certificates_[3]; std::unique_ptr root_cert_; }; -TEST_F(DrmRootCertificateTest, SuccessNoOutput) { +INSTANTIATE_TEST_SUITE_P(SuccessNoOutput, DrmRootCertificateTest, + Values("RSA", "ECC")); + +TEST_P(DrmRootCertificateTest, SuccessNoOutput) { GenerateSignedDrmCertificate(); ASSERT_EQ(OkStatus(), root_cert_->VerifyCertificate( signed_drm_certificate_.SerializeAsString(), nullptr, nullptr)); } -TEST_F(DrmRootCertificateTest, SuccessWithOutput) { +TEST_P(DrmRootCertificateTest, SuccessWithOutput) { GenerateSignedDrmCertificate(); SignedDrmCertificate out_signed_cert; DrmCertificate out_cert; @@ -161,13 +304,13 @@ TEST_F(DrmRootCertificateTest, SuccessWithOutput) { EXPECT_TRUE(MessageDifferencer::Equals(out_cert, drm_certificates_[2])); } -TEST_F(DrmRootCertificateTest, InvalidSignedDrmCertificate) { +TEST_P(DrmRootCertificateTest, InvalidSignedDrmCertificate) { EXPECT_EQ(Status(error_space, INVALID_DRM_CERTIFICATE, "invalid-signed-drm-certificate"), root_cert_->VerifyCertificate("pure garbage", nullptr, nullptr)); } -TEST_F(DrmRootCertificateTest, InvalidSignerCertificate) { +TEST_P(DrmRootCertificateTest, InvalidSignerCertificate) { GenerateSignedDrmCertificate(); signed_drm_certificate_.mutable_signer()->set_drm_certificate("more garbage"); EXPECT_EQ(Status(error_space, INVALID_DRM_CERTIFICATE, @@ -176,7 +319,7 @@ TEST_F(DrmRootCertificateTest, InvalidSignerCertificate) { signed_drm_certificate_.SerializeAsString(), nullptr, nullptr)); } -TEST_F(DrmRootCertificateTest, MissingDrmCertificate) { +TEST_P(DrmRootCertificateTest, MissingDrmCertificate) { GenerateSignedDrmCertificate(); signed_drm_certificate_.clear_drm_certificate(); EXPECT_EQ( @@ -185,7 +328,7 @@ TEST_F(DrmRootCertificateTest, MissingDrmCertificate) { nullptr, nullptr)); } -TEST_F(DrmRootCertificateTest, InvalidDrmCertificate) { +TEST_P(DrmRootCertificateTest, InvalidDrmCertificate) { GenerateSignedDrmCertificate(); signed_drm_certificate_.set_drm_certificate("junk"); EXPECT_EQ( @@ -194,7 +337,7 @@ TEST_F(DrmRootCertificateTest, InvalidDrmCertificate) { nullptr, nullptr)); } -TEST_F(DrmRootCertificateTest, InvalidPublicKey) { +TEST_P(DrmRootCertificateTest, InvalidPublicKey) { drm_certificates_[0].set_public_key("rubbish"); GenerateSignedDrmCertificate(); EXPECT_EQ( @@ -203,7 +346,7 @@ TEST_F(DrmRootCertificateTest, InvalidPublicKey) { nullptr, nullptr)); } -TEST_F(DrmRootCertificateTest, MissingPublicKey) { +TEST_P(DrmRootCertificateTest, MissingPublicKey) { drm_certificates_[2].clear_public_key(); GenerateSignedDrmCertificate(); EXPECT_EQ(Status(error_space, INVALID_DRM_CERTIFICATE, "missing-public-key"), @@ -211,7 +354,7 @@ TEST_F(DrmRootCertificateTest, MissingPublicKey) { signed_drm_certificate_.SerializeAsString(), nullptr, nullptr)); } -TEST_F(DrmRootCertificateTest, MissingCreationTime) { +TEST_P(DrmRootCertificateTest, MissingCreationTime) { drm_certificates_[2].clear_creation_time_seconds(); GenerateSignedDrmCertificate(); EXPECT_EQ( @@ -220,7 +363,7 @@ TEST_F(DrmRootCertificateTest, MissingCreationTime) { nullptr, nullptr)); } -TEST_F(DrmRootCertificateTest, MissingSerialNumber) { +TEST_P(DrmRootCertificateTest, MissingSerialNumber) { drm_certificates_[2].set_serial_number(""); GenerateSignedDrmCertificate(); EXPECT_EQ( @@ -229,7 +372,7 @@ TEST_F(DrmRootCertificateTest, MissingSerialNumber) { nullptr, nullptr)); } -TEST_F(DrmRootCertificateTest, InvalidSignatureWithNoCache) { +TEST_P(DrmRootCertificateTest, InvalidSignatureWithNoCache) { GenerateSignedDrmCertificate(); signed_drm_certificate_.mutable_signer()->set_signature( "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); @@ -239,7 +382,7 @@ TEST_F(DrmRootCertificateTest, InvalidSignatureWithNoCache) { nullptr, nullptr)); } -TEST_F(DrmRootCertificateTest, InvalidSignatureWithCache) { +TEST_P(DrmRootCertificateTest, InvalidSignatureWithCache) { GenerateSignedDrmCertificate(); // Verify and cache. ASSERT_EQ(OkStatus(), diff --git a/common/drm_service_certificate.cc b/common/drm_service_certificate.cc index 9229859..67674b9 100644 --- a/common/drm_service_certificate.cc +++ b/common/drm_service_certificate.cc @@ -44,16 +44,18 @@ class DrmServiceCertificateMap { void AddCert(std::unique_ptr new_cert); void ClearDefaultDrmServiceCertificate(); const DrmServiceCertificate* GetDefaultCert(); - const DrmServiceCertificate* GetCert(const std::string& serial_number); - + const DrmServiceCertificate* GetCertBySerialNumber( + const std::string& serial_number); + const DrmServiceCertificate* GetCertByProvider( + const std::string& provider_id); static DrmServiceCertificateMap* GetInstance(); private: absl::Mutex mutex_; // Certificate serial number to certificate map. std::map> map_ - GUARDED_BY(mutex_); - DrmServiceCertificate* default_cert_ GUARDED_BY(mutex_); + ABSL_GUARDED_BY(mutex_); + DrmServiceCertificate* default_cert_ ABSL_GUARDED_BY(mutex_); }; DrmServiceCertificateMap::DrmServiceCertificateMap() : default_cert_(nullptr) {} @@ -94,12 +96,30 @@ const DrmServiceCertificate* DrmServiceCertificateMap::GetDefaultCert() { return default_cert_; } -const DrmServiceCertificate* DrmServiceCertificateMap::GetCert( +const DrmServiceCertificate* DrmServiceCertificateMap::GetCertBySerialNumber( const std::string& serial_number) { absl::ReaderMutexLock lock(&mutex_); return map_[serial_number].get(); } +const DrmServiceCertificate* DrmServiceCertificateMap::GetCertByProvider( + const std::string& provider_id) { + absl::ReaderMutexLock lock(&mutex_); + DrmServiceCertificate* provider_drm_cert = nullptr; + for (const auto& drm_cert : map_) { + if (drm_cert.second->provider_id() == provider_id) { + if (provider_drm_cert == nullptr) { + provider_drm_cert = drm_cert.second.get(); + } else if (drm_cert.second->creation_time_seconds() > + provider_drm_cert->creation_time_seconds()) { + // Use the newest cert. + provider_drm_cert = drm_cert.second.get(); + } + } + } + return provider_drm_cert; +} + DrmServiceCertificateMap* DrmServiceCertificateMap::GetInstance() { static auto* const kInstance = new DrmServiceCertificateMap(); return kInstance; @@ -108,7 +128,8 @@ DrmServiceCertificateMap* DrmServiceCertificateMap::GetInstance() { } // namespace Status DrmServiceCertificate::AddDrmServiceCertificate( - const DrmRootCertificate* root_drm_cert, const std::string& service_certificate, + const DrmRootCertificate* root_drm_cert, + const std::string& service_certificate, const std::string& service_private_key, const std::string& service_private_key_passphrase) { DrmCertificate drm_cert; @@ -166,13 +187,22 @@ DrmServiceCertificate::GetDefaultDrmServiceCertificateOrDie() { return default_cert; } -const DrmServiceCertificate* DrmServiceCertificate::GetDrmServiceCertificate( +const DrmServiceCertificate* +DrmServiceCertificate::GetDrmServiceCertificateBySerialNumber( const std::string& serial_number) { - return DrmServiceCertificateMap::GetInstance()->GetCert(serial_number); + return DrmServiceCertificateMap::GetInstance()->GetCertBySerialNumber( + serial_number); +} + +const DrmServiceCertificate* +DrmServiceCertificate::GetDrmServiceCertificateByProvider( + const std::string& provider) { + return DrmServiceCertificateMap::GetInstance()->GetCertByProvider(provider); } Status DrmServiceCertificate::SetDefaultDrmServiceCertificate( - const DrmRootCertificate* root_drm_cert, const std::string& service_certificate, + const DrmRootCertificate* root_drm_cert, + const std::string& service_certificate, const std::string& service_private_key, const std::string& service_private_key_passphrase) { DrmServiceCertificateMap::GetInstance()->ClearDefaultDrmServiceCertificate(); @@ -207,7 +237,7 @@ Status DrmServiceCertificate::DecryptClientIdentification( } std::string privacy_key; std::string provider_id; - const DrmServiceCertificate* cert = GetDrmServiceCertificate( + const DrmServiceCertificate* cert = GetDrmServiceCertificateBySerialNumber( encrypted_client_id.service_certificate_serial_number()); if (!cert) { return Status( diff --git a/common/drm_service_certificate.h b/common/drm_service_certificate.h index fec6055..7d4e33f 100644 --- a/common/drm_service_certificate.h +++ b/common/drm_service_certificate.h @@ -17,7 +17,6 @@ #include #include -#include "base/macros.h" #include "common/certificate_type.h" #include "common/rsa_key.h" #include "common/status.h" @@ -36,6 +35,9 @@ class EncryptedClientIdentification; // functionality. class DrmServiceCertificate { public: + DrmServiceCertificate(const DrmServiceCertificate&) = delete; + DrmServiceCertificate& operator=(const DrmServiceCertificate&) = delete; + // Create a new DrmServiceCertificate object and add it to the list of valid // service certificates. |drm_root_cert| is the root certificate for the type // of certifiate being added. |service_certificate| is a @@ -50,7 +52,8 @@ class DrmServiceCertificate { // This method is thread-safe. static Status AddDrmServiceCertificate( const DrmRootCertificate* root_drm_cert, - const std::string& service_certificate, const std::string& service_private_key, + const std::string& service_certificate, + const std::string& service_private_key, const std::string& service_private_key_passphrase); // Same as AddDrmServiceCertificate(), but will clear the default service @@ -58,7 +61,8 @@ class DrmServiceCertificate { // being set as the default service certificate. static Status SetDefaultDrmServiceCertificate( const DrmRootCertificate* root_drm_cert, - const std::string& service_certificate, const std::string& service_private_key, + const std::string& service_certificate, + const std::string& service_private_key, const std::string& service_private_key_passphrase); // Returns the default service certificate. Will return null if no default @@ -69,11 +73,17 @@ class DrmServiceCertificate { // Certificate is set. This method is thread-safe. static const DrmServiceCertificate* GetDefaultDrmServiceCertificateOrDie(); - // Returns the service certificate with the given serial number if found, or + // Returns the service certificate with the given |cert_serial_number|, or // null otherwise. - static const DrmServiceCertificate* GetDrmServiceCertificate( + static const DrmServiceCertificate* GetDrmServiceCertificateBySerialNumber( const std::string& cert_serial_number); + // Returns the service certificate with the given |provider_id|, or + // null otherwise. If multple certificates exist for the provider, the + // newest certificate is returned. + static const DrmServiceCertificate* GetDrmServiceCertificateByProvider( + const std::string& provider_id); + // Decrypts the EncryptedClientIdentification message passed in // |encrypted_client_id| into |client_id| using the private key for the // certificate which was used to encrypt the information. |client_id| must @@ -86,6 +96,7 @@ class DrmServiceCertificate { const std::string& certificate() const { return certificate_; } const std::string& provider_id() const { return provider_id_; } const std::string& serial_number() const { return serial_number_; } + uint32_t creation_time_seconds() const { return creation_time_seconds_; } const RsaPrivateKey* const private_key() const { return private_key_.get(); } const RsaPublicKey* const public_key() const { return public_key_.get(); } @@ -100,17 +111,20 @@ class DrmServiceCertificate { friend class widevine::RequestInspectorTest; static Status AddDrmServiceCertificate( - const std::string& root_public_key, const std::string& service_certificate, + const std::string& root_public_key, + const std::string& service_certificate, const std::string& service_private_key, const std::string& service_private_key_passphrase); static Status SetDefaultDrmServiceCertificate( - const std::string& root_public_key, const std::string& service_certificate, + const std::string& root_public_key, + const std::string& service_certificate, const std::string& service_private_key, const std::string& service_private_key_passphrase); DrmServiceCertificate(const std::string& service_certificate, - const std::string& provider_id, const std::string& serial_number, + const std::string& provider_id, + const std::string& serial_number, const uint32_t creation_time_seconds, std::unique_ptr public_key, std::unique_ptr private_key); @@ -123,8 +137,6 @@ class DrmServiceCertificate { uint32_t creation_time_seconds_; std::unique_ptr public_key_; std::unique_ptr private_key_; - - DISALLOW_IMPLICIT_CONSTRUCTORS(DrmServiceCertificate); }; } // namespace widevine diff --git a/common/drm_service_certificate_test.cc b/common/drm_service_certificate_test.cc index b5eef32..b60e108 100644 --- a/common/drm_service_certificate_test.cc +++ b/common/drm_service_certificate_test.cc @@ -51,9 +51,9 @@ class DrmServiceCertificateTest : public ::testing::Test { protected: std::string GenerateDrmServiceCertificate(const std::string& serial_number, - const std::string& provider_id, - uint32_t creation_time_seconds, - const std::string& public_key) { + const std::string& provider_id, + uint32_t creation_time_seconds, + const std::string& public_key) { DrmCertificate cert; cert.set_type(DrmCertificate::SERVICE); cert.set_serial_number(serial_number); @@ -160,6 +160,9 @@ TEST_F(DrmServiceCertificateTest, MultipleDrmServiceCertificates) { uint32_t creation_time_seconds1(1234); std::string serial_number2("serial_number2"); uint32_t creation_time_seconds2(1234); + std::string serial_number3("serial_number3"); + std::string provider_id3("service3.com"); + uint32_t creation_time_seconds3(1235); std::string bogus_serial_number("bogus-serial-number2"); EXPECT_EQ(nullptr, DrmServiceCertificate::GetDefaultDrmServiceCertificate()); @@ -172,6 +175,9 @@ TEST_F(DrmServiceCertificateTest, MultipleDrmServiceCertificates) { EXPECT_OK(AddDrmServiceCertificate(serial_number2, provider_id1, creation_time_seconds2)); + EXPECT_OK(AddDrmServiceCertificate(serial_number3, provider_id3, + creation_time_seconds3)); + EncryptedClientIdentification encrypted_client_id; EncryptClientIdentification(serial_number1, provider_id1, test_keys_.public_test_key_2_2048_bits(), @@ -199,6 +205,61 @@ TEST_F(DrmServiceCertificateTest, MultipleDrmServiceCertificates) { .error_code()); } +TEST_F(DrmServiceCertificateTest, MultipleDrmServiceCertificatesLookup) { + std::string serial_number1("serial_number1"); + std::string provider_id1("service1.com"); + uint32_t creation_time_seconds1(1231); + std::string serial_number2("serial_number2"); + std::string provider_id2("service2.com"); + uint32_t creation_time_seconds2(1232); + std::string serial_number3("serial_number3"); + std::string provider_id3("service3.com"); + uint32_t creation_time_seconds3(1234); + std::string serial_number4("serial_number4"); + std::string bogus_serial_number("bogus-serial-number"); + + EXPECT_OK(AddDrmServiceCertificate(serial_number1, provider_id1, + creation_time_seconds1)); + EXPECT_OK(AddDrmServiceCertificate(serial_number2, provider_id2, + creation_time_seconds2)); + EXPECT_OK(AddDrmServiceCertificate(serial_number3, provider_id3, + creation_time_seconds3)); + + EXPECT_EQ(provider_id1, + DrmServiceCertificate::GetDrmServiceCertificateBySerialNumber( + serial_number1) + ->provider_id()); + EXPECT_EQ(provider_id2, + DrmServiceCertificate::GetDrmServiceCertificateBySerialNumber( + serial_number2) + ->provider_id()); + EXPECT_EQ(provider_id3, + DrmServiceCertificate::GetDrmServiceCertificateBySerialNumber( + serial_number3) + ->provider_id()); + + EXPECT_EQ( + serial_number1, + DrmServiceCertificate::GetDrmServiceCertificateByProvider(provider_id1) + ->serial_number()); + EXPECT_EQ( + serial_number2, + DrmServiceCertificate::GetDrmServiceCertificateByProvider(provider_id2) + ->serial_number()); + EXPECT_EQ( + serial_number3, + DrmServiceCertificate::GetDrmServiceCertificateByProvider(provider_id3) + ->serial_number()); + + // Add a second cert for provider 3. + EXPECT_OK(AddDrmServiceCertificate(serial_number4, provider_id3, + creation_time_seconds3 + 60)); + EXPECT_EQ( + serial_number4, + DrmServiceCertificate::GetDrmServiceCertificateByProvider(provider_id3) + ->serial_number()); +} + TEST_F(DrmServiceCertificateTest, MultipleCertsPerService) { std::string serial_number1("serial_number1"); std::string serial_number2("serial_number2"); diff --git a/common/ec_key.cc b/common/ec_key.cc new file mode 100644 index 0000000..f16fb84 --- /dev/null +++ b/common/ec_key.cc @@ -0,0 +1,280 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Definition of elliptic curve public and private key classes. + +#include "common/ec_key.h" + +#include "glog/logging.h" +#include "absl/memory/memory.h" +#include "openssl/base.h" +#include "openssl/bn.h" +#include "openssl/ec.h" +#include "openssl/ecdh.h" +#include "openssl/ecdsa.h" +#include "openssl/err.h" +#include "openssl/evp.h" +#include "openssl/sha.h" +#include "common/aes_cbc_util.h" +#include "common/ec_util.h" +#include "common/openssl_util.h" +#include "common/sha_util.h" + +namespace widevine { + +namespace { + +void* SHA256KDF(const void* in, size_t inlen, void* out, size_t* outlen) { + std::string shared_session_key(static_cast(in), inlen); + std::string derived_shared_session_key = Sha256_Hash(shared_session_key); + if (*outlen != SHA256_DIGEST_LENGTH) { + return nullptr; + } + memcpy(out, derived_shared_session_key.data(), *outlen); + return out; +} + +ECPrivateKey::EllipticCurve ECKeyToCurve(const EC_KEY* key) { + if (key == nullptr) { + return ECPrivateKey::UNDEFINED_CURVE; + } + return ec_util::NidToCurve(EC_GROUP_get_curve_name(EC_KEY_get0_group(key))); +} + +std::string OpenSSLErrorString(uint32_t error) { + char buf[ERR_ERROR_STRING_BUF_LEN]; + ERR_error_string_n(error, buf, sizeof(buf)); + return buf; +} + +} // namespace + +ECPrivateKey::ECPrivateKey(EC_KEY* ec_key) : key_(ec_key) { + CHECK(key() != nullptr); + CHECK(EC_KEY_get0_private_key(key()) != nullptr); + CHECK_NE(ECKeyToCurve(key()), ECPrivateKey::UNDEFINED_CURVE); + CHECK_EQ(EC_KEY_check_key(key()), 1); +} + +ECPrivateKey::ECPrivateKey(ScopedECKEY ec_key) : key_(std::move(ec_key)) {} + +ECPrivateKey::ECPrivateKey(const ECPrivateKey& ec_key) + : ECPrivateKey(EC_KEY_dup(ec_key.key())) {} + +std::unique_ptr ECPrivateKey::Create( + const std::string& serialized_key) { + EC_KEY* key; + if (!ec_util::DeserializeECPrivateKey(serialized_key, &key)) { + return nullptr; + } + ScopedECKEY scoped_ec_key(key); + if (EC_KEY_check_key(scoped_ec_key.get()) != 1) { + LOG(ERROR) << "Invalid private EC key: " + << OpenSSLErrorString(ERR_get_error()); + return nullptr; + } + if (ECKeyToCurve(scoped_ec_key.get()) == ECPrivateKey::UNDEFINED_CURVE) { + LOG(ERROR) << "Key has unsupported curve"; + return nullptr; + } + return absl::make_unique(scoped_ec_key.release()); +} + +std::unique_ptr ECPrivateKey::PublicKey() const { + if (key_ == nullptr) { + return nullptr; + } + return absl::make_unique(ScopedECKEY(EC_KEY_dup(key_.get()))); +} + +bool ECPrivateKey::DeriveSharedSessionKey( + const ECPublicKey& public_key, + std::string* derived_shared_session_key) const { + if (derived_shared_session_key == nullptr) { + LOG(ERROR) << "|derived_shared_session_key| cannot be nullptr"; + return false; + } + const EC_GROUP* group1 = EC_KEY_get0_group(key()); + const EC_GROUP* group2 = EC_KEY_get0_group(public_key.key()); + if (EC_GROUP_cmp(group1, group2, nullptr) != 0) { + LOG(ERROR) << "EC_GROUPs do not match"; + return false; + } + const EC_POINT* public_key_point = EC_KEY_get0_public_key(public_key.key()); + derived_shared_session_key->resize(SHA256_DIGEST_LENGTH, 0); + int result = ECDH_compute_key( + reinterpret_cast( + const_cast(derived_shared_session_key->data())), + derived_shared_session_key->size(), public_key_point, key(), SHA256KDF); + if (result == -1) { + LOG(ERROR) << "Could not write shared session key: " + << OpenSSLErrorString(ERR_get_error()); + return false; + } + if (result != SHA256_DIGEST_LENGTH) { + LOG(ERROR) << "Wrote " << result + << " bytes to derived shared session key instead of " + << SHA256_DIGEST_LENGTH << " bytes"; + return false; + } + return true; +} + +bool ECPrivateKey::GenerateSignature(const std::string& message, + std::string* signature) const { + if (message.empty()) { + LOG(ERROR) << "|message| cannot be empty"; + return false; + } + if (signature == nullptr) { + LOG(ERROR) << "|signature| cannot be nullptr"; + return false; + } + std::string message_digest = Sha256_Hash(message); + size_t max_signature_size = ECDSA_size(key()); + if (max_signature_size == 0) { + LOG(ERROR) << "key_ does not have a group set"; + return false; + } + signature->resize(max_signature_size); + unsigned int bytes_written = 0; + int result = ECDSA_sign( + 0 /* unused type */, + reinterpret_cast(message_digest.data()), + message_digest.size(), + reinterpret_cast(const_cast(signature->data())), + &bytes_written, key()); + if (result != 1) { + LOG(ERROR) << "Could not calculate signature: " + << OpenSSLErrorString(ERR_get_error()); + return false; + } + signature->resize(bytes_written); + return true; +} + +bool ECPrivateKey::MatchesPrivateKey(const ECPrivateKey& private_key) const { + return BN_cmp(EC_KEY_get0_private_key(key()), + EC_KEY_get0_private_key(private_key.key())) == 0; +} + +bool ECPrivateKey::MatchesPublicKey(const ECPublicKey& public_key) const { + return EC_POINT_cmp(EC_KEY_get0_group(key()), EC_KEY_get0_public_key(key()), + EC_KEY_get0_public_key(public_key.key()), nullptr) == 0; +} + +ECPrivateKey::EllipticCurve ECPrivateKey::Curve() const { + return ECKeyToCurve(key()); +} + +bool ECPrivateKey::SerializedKey(std::string* serialized_key) const { + CHECK(serialized_key); + return ec_util::SerializeECPrivateKey(key_.get(), serialized_key); +} + +ECPublicKey::ECPublicKey(EC_KEY* ec_key) : key_(ec_key) { + CHECK(key() != nullptr); + CHECK(EC_KEY_get0_private_key(key()) == nullptr); + CHECK_NE(ECKeyToCurve(key()), ECPrivateKey::UNDEFINED_CURVE); + CHECK_EQ(EC_KEY_check_key(key()), 1); +} + +ECPublicKey::ECPublicKey(ScopedECKEY ec_key) : key_(std::move(ec_key)) {} + +ECPublicKey::ECPublicKey(const ECPublicKey& ec_key) + : ECPublicKey(EC_KEY_dup(ec_key.key())) {} + +std::unique_ptr ECPublicKey::Create( + const std::string& serialized_key) { + EC_KEY* key; + if (!ec_util::DeserializeECPublicKey(serialized_key, &key)) { + return nullptr; + } + ScopedECKEY scoped_ec_key(key); + if (EC_KEY_check_key(scoped_ec_key.get()) != 1) { + LOG(ERROR) << "Invalid public EC key: " + << OpenSSLErrorString(ERR_get_error()); + return nullptr; + } + if (ECKeyToCurve(scoped_ec_key.get()) == ECPrivateKey::UNDEFINED_CURVE) { + LOG(ERROR) << "Key has unsupported curve"; + return nullptr; + } + return absl::make_unique(scoped_ec_key.release()); +} + +std::unique_ptr ECPublicKey::CreateFromKeyPoint( + ECPrivateKey::EllipticCurve curve, const std::string& key_point) { + EC_KEY* key; + if (!ec_util::GetPublicKeyFromKeyPoint(curve, key_point, &key)) { + return nullptr; + } + + ScopedECKEY scoped_ec_key(key); + if (EC_KEY_check_key(scoped_ec_key.get()) != 1) { + LOG(ERROR) << "Invalid public EC key: " + << OpenSSLErrorString(ERR_get_error()); + return nullptr; + } + if (ECKeyToCurve(scoped_ec_key.get()) == ECPrivateKey::UNDEFINED_CURVE) { + LOG(ERROR) << "Key has unsupported curve"; + return nullptr; + } + return absl::make_unique(scoped_ec_key.release()); +} + +bool ECPublicKey::VerifySignature(const std::string& message, + const std::string& signature) const { + if (message.empty()) { + LOG(ERROR) << "|message| cannot be empty"; + return false; + } + if (signature.empty()) { + LOG(ERROR) << "|signature| cannot be empty"; + return false; + } + std::string message_digest = Sha256_Hash(message); + int result = ECDSA_verify( + 0 /* unused type */, + reinterpret_cast(message_digest.data()), + message_digest.size(), + reinterpret_cast(const_cast(signature.data())), + signature.size(), key()); + if (result != 1) { + LOG(ERROR) << "Could not verify signature: " + << OpenSSLErrorString(ERR_get_error()); + return false; + } + return true; +} + +bool ECPublicKey::MatchesPrivateKey(const ECPrivateKey& private_key) const { + return private_key.MatchesPublicKey(*this); +} + +bool ECPublicKey::MatchesPublicKey(const ECPublicKey& public_key) const { + return EC_POINT_cmp(EC_KEY_get0_group(key()), EC_KEY_get0_public_key(key()), + EC_KEY_get0_public_key(public_key.key()), nullptr) == 0; +} + +ECPrivateKey::EllipticCurve ECPublicKey::Curve() const { + return ECKeyToCurve(key()); +} + +bool ECPublicKey::SerializedKey(std::string* serialized_key) const { + CHECK(serialized_key); + return ec_util::SerializeECPublicKey(key_.get(), serialized_key); +} + +bool ECPublicKey::GetPointEncodedKey(std::string* encoded_key) const { + CHECK(encoded_key); + return ec_util::GetPublicKeyPoint(key_.get(), encoded_key); +} + +} // namespace widevine diff --git a/common/ec_key.h b/common/ec_key.h new file mode 100644 index 0000000..fe9726d --- /dev/null +++ b/common/ec_key.h @@ -0,0 +1,144 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Generic elliptic curve classes for private and public keys. Declares +// methods for deriving shared session keys and signatures. + +#ifndef COMMON_EC_KEY_H_ +#define COMMON_EC_KEY_H_ + +#include +#include + +#include "openssl/ec.h" +#include "common/openssl_util.h" + +namespace widevine { + +class ECPublicKey; + +class ECPrivateKey { + public: + explicit ECPrivateKey(EC_KEY* ec_key); + explicit ECPrivateKey(ScopedECKEY ec_key); + ECPrivateKey(const ECPrivateKey&); + ECPrivateKey() = delete; + virtual ~ECPrivateKey() = default; + + // Accepted standard curves for key generation. Names are derived from SECG. + enum EllipticCurve { + UNDEFINED_CURVE = 0, + SECP256R1, + SECP384R1, + SECP521R1, + }; + + // Creates an ECPrivateKey using a DER encoded RFC5915 std::string + // representing a private key. Returns an ECPrivateKey on success or nullptr + // on failure. + static std::unique_ptr Create( + const std::string& serialized_key); + + // Exports the matching public key for this private key. + virtual std::unique_ptr PublicKey() const; + + // Calculates a shared session key using ECDH and then uses a key derivation + // function, SHA256, to derive a key. + // |public_key| is an EC public key with the same curve parameters as key_. It + // is used as the public key component of ECDH. + // |derived_shared_session_key| will be the result of the derivation. + // Caller retains ownership of all pointers. + // Returns true on success and false on error. + virtual bool DeriveSharedSessionKey( + const ECPublicKey& public_key, + std::string* derived_shared_session_key) const; + + // Given a message, calculates a signature using ECDSA with the key_. + // |message| is the message to be signed. + // |signature| will contain the resulting signature. This will be an ASN.1 + // DER-encoded signature. + // Caller retains ownership of all pointers. + // Returns true on success and false on error. + virtual bool GenerateSignature(const std::string& message, + std::string* signature) const; + + // Returns whether the given private key is the same as key_. + virtual bool MatchesPrivateKey(const ECPrivateKey& private_key) const; + + // Returns whether the given public key is part of the same key pair as key_. + virtual bool MatchesPublicKey(const ECPublicKey& public_key) const; + + // Returns the EllipticCurve associated with key_. + virtual EllipticCurve Curve() const; + + virtual bool SerializedKey(std::string* serialized_key) const; + + private: + friend class ECPublicKey; + + ECPrivateKey& operator=(const ECPrivateKey&) = delete; + + const EC_KEY* key() const { return key_.get(); } + + ScopedECKEY key_; +}; + +class ECPublicKey { + public: + explicit ECPublicKey(EC_KEY* ec_key); + explicit ECPublicKey(ScopedECKEY ec_key); + ECPublicKey(const ECPublicKey&); + virtual ~ECPublicKey() = default; + + // Creates an ECPublicKey using a DER encoded RFC5208 std::string representing + // a public key. Returns an ECPublicKey on success or nullptr on failure. + static std::unique_ptr Create(const std::string& serialized_key); + + // Creates an ECPublicKey from an uncompressed point compatible with X9.62. + // Returns an ECPublicKey on success or nullptr on failure. + static std::unique_ptr CreateFromKeyPoint( + ECPrivateKey::EllipticCurve curve, const std::string& key_point); + + // Given a message and a signature, verifies that the signature was created + // using the private key associated with key_. + // |message| is the message that was signed. + // |signature| is an ASN.1 DER-encoded signature. + // Returns true on success and false on error. + virtual bool VerifySignature(const std::string& message, + const std::string& signature) const; + + // Returns whether the given private key is part of the same key pair as key_. + virtual bool MatchesPrivateKey(const ECPrivateKey& private_key) const; + + // Returns whether the given public key is the same as key_. + virtual bool MatchesPublicKey(const ECPublicKey& public_key) const; + + // Returns the EllipticCurve associated with key_. + virtual ECPrivateKey::EllipticCurve Curve() const; + + virtual bool SerializedKey(std::string* serialized_key) const; + + // Returns the key in the uncompressed point format. Compatible with X9.62 + // The lead byte indicates uncompressed format (0x4). This is followed by the + // octets for the X and Y coordinates. + virtual bool GetPointEncodedKey(std::string* encoded_key) const; + + private: + friend class ECPrivateKey; + + ECPublicKey& operator=(const ECPublicKey&) = delete; + + const EC_KEY* key() const { return key_.get(); } + + ScopedECKEY key_; +}; + +} // namespace widevine + +#endif // COMMON_EC_KEY_H_ diff --git a/common/ec_key_source.h b/common/ec_key_source.h new file mode 100644 index 0000000..e0333a3 --- /dev/null +++ b/common/ec_key_source.h @@ -0,0 +1,43 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Declaration for abstract EC key generation class. + +#ifndef COMMON_EC_KEY_SOURCE_H_ +#define COMMON_EC_KEY_SOURCE_H_ + +#include + +#include "common/ec_key.h" + +namespace widevine { + +class ECKeySource { + public: + ECKeySource() = default; + virtual ~ECKeySource() = default; + + // Get an EC key pair given a specific curve defined by EllipticCurve. + // Parameter |curve| is a standard curve which defines the parameters of the + // key generation. + // Parameter |private_key| is the serialized EC private key. + // Parameter |public_key| is the serialized EC public key. + // Caller retains ownership of all pointers and they cannot be nullptr. + // Returns true if successful, false otherwise. + virtual bool GetECKey(ECPrivateKey::EllipticCurve curve, + std::string* private_key, std::string* public_key) = 0; + + private: + ECKeySource(const ECKeySource&) = delete; + ECKeySource& operator=(const ECKeySource&) = delete; +}; + +} // namespace widevine + +#endif // COMMON_EC_KEY_SOURCE_H_ diff --git a/common/ec_key_test.cc b/common/ec_key_test.cc new file mode 100644 index 0000000..4a06e7d --- /dev/null +++ b/common/ec_key_test.cc @@ -0,0 +1,274 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Unit tests for the ECPrivateKey and ECPublicKey classes. + +#include "common/ec_key.h" + +#include +#include + +#include "testing/gmock.h" +#include "testing/gunit.h" +#include "openssl/sha.h" +#include "common/ec_test_keys.h" +#include "common/ec_util.h" +#include "common/random_util.h" + +using ::testing::IsNull; + +namespace widevine { + +class ECKeyTest : public ::testing::Test { + public: + static std::vector > GetTestKeyList() { + std::vector > test_key_list; + ECTestKeys test_keys; + test_key_list.push_back( + std::make_tuple(test_keys.private_test_key_1_secp521r1(), + test_keys.public_test_key_1_secp521r1())); + test_key_list.push_back( + std::make_tuple(test_keys.private_test_key_1_secp384r1(), + test_keys.public_test_key_1_secp384r1())); + test_key_list.push_back( + std::make_tuple(test_keys.private_test_key_1_secp256r1(), + test_keys.public_test_key_1_secp256r1())); + return test_key_list; + } + + protected: + ECKeyTest() { plaintext_message_ = "This is a plaintext message."; } + + void GenerateEphemeralKeyPair(ECPrivateKey::EllipticCurve curve) { + ASSERT_NE(curve, ECPrivateKey::UNDEFINED_CURVE); + bssl::UniquePtr ephemeral_key_pair( + EC_KEY_new_by_curve_name(ec_util::CurveToNid(curve))); + ASSERT_TRUE(ephemeral_key_pair != nullptr); + ASSERT_TRUE(EC_KEY_generate_key(ephemeral_key_pair.get())); + // Separate them out into their private and public parts. + std::string private_key; + std::string public_key; + ASSERT_TRUE( + ec_util::SerializeECPrivateKey(ephemeral_key_pair.get(), &private_key)); + ASSERT_TRUE( + ec_util::SerializeECPublicKey(ephemeral_key_pair.get(), &public_key)); + ephemeral_private_key_ = ECPrivateKey::Create(private_key); + ephemeral_public_key_ = ECPublicKey::Create(public_key); + } + + std::unique_ptr ephemeral_private_key_; + std::unique_ptr ephemeral_public_key_; + std::string plaintext_message_; +}; + +TEST_F(ECKeyTest, KeyCurve) { + ECTestKeys test_keys; + EXPECT_EQ( + ECPrivateKey::Create(test_keys.private_test_key_1_secp521r1())->Curve(), + ECPrivateKey::SECP521R1); + EXPECT_EQ( + ECPrivateKey::Create(test_keys.private_test_key_1_secp384r1())->Curve(), + ECPrivateKey::SECP384R1); + EXPECT_EQ( + ECPrivateKey::Create(test_keys.private_test_key_1_secp256r1())->Curve(), + ECPrivateKey::SECP256R1); + + EXPECT_EQ( + ECPublicKey::Create(test_keys.public_test_key_1_secp521r1())->Curve(), + ECPrivateKey::SECP521R1); + EXPECT_EQ( + ECPublicKey::Create(test_keys.public_test_key_1_secp384r1())->Curve(), + ECPrivateKey::SECP384R1); + EXPECT_EQ( + ECPublicKey::Create(test_keys.public_test_key_1_secp256r1())->Curve(), + ECPrivateKey::SECP256R1); +} + +TEST_F(ECKeyTest, KeyPointEncodingNullDeath) { + EXPECT_DEATH(ephemeral_public_key_->GetPointEncodedKey(nullptr), ""); +} + +class ECKeyTestKeyPairs : public ECKeyTest, + public ::testing::WithParamInterface< + std::tuple > { + protected: + ECKeyTestKeyPairs() { + test_private_key_ = std::get<0>(GetParam()); + test_public_key_ = std::get<1>(GetParam()); + private_key_ = ECPrivateKey::Create(test_private_key_); + public_key_ = ECPublicKey::Create(test_public_key_); + } + + std::string test_private_key_; + std::string test_public_key_; + std::unique_ptr private_key_; + std::unique_ptr public_key_; +}; + +TEST_P(ECKeyTestKeyPairs, CreateWrongKey) { + EXPECT_EQ(ECPrivateKey::Create(test_public_key_), nullptr); + EXPECT_EQ(ECPublicKey::Create(test_private_key_), nullptr); +} + +TEST_P(ECKeyTestKeyPairs, InvalidKeyConstructorParameters) { + EXPECT_DEATH(ECPrivateKey(nullptr), ""); + EXPECT_DEATH(ECPublicKey(nullptr), ""); + + EC_KEY* key; + ASSERT_TRUE(ec_util::DeserializeECPrivateKey(test_private_key_, &key)); + // Brace initialization to avoid most vexing parse. + EXPECT_DEATH(ECPublicKey{key}, ""); + EC_KEY_free(key); + + ASSERT_TRUE(ec_util::DeserializeECPublicKey(test_public_key_, &key)); + EXPECT_DEATH(ECPrivateKey{key}, ""); + EC_KEY_free(key); +} + +TEST_P(ECKeyTestKeyPairs, KeyMatch) { + EXPECT_TRUE(private_key_->MatchesPrivateKey(*private_key_)); + EXPECT_TRUE(private_key_->MatchesPublicKey(*public_key_)); + EXPECT_TRUE(public_key_->MatchesPrivateKey(*private_key_)); + EXPECT_TRUE(public_key_->MatchesPublicKey(*public_key_)); +} + +TEST_P(ECKeyTestKeyPairs, DeriveSharedSessionKey) { + std::string derived_shared_session_key_1; + // Generate a temporary key pair as part of ECDH. + GenerateEphemeralKeyPair(private_key_->Curve()); + EXPECT_TRUE(private_key_->DeriveSharedSessionKey( + *ephemeral_public_key_, &derived_shared_session_key_1)); + EXPECT_EQ(derived_shared_session_key_1.size(), SHA256_DIGEST_LENGTH); + std::string derived_shared_session_key_2; + EXPECT_TRUE(ephemeral_private_key_->DeriveSharedSessionKey( + *public_key_, &derived_shared_session_key_2)); + EXPECT_EQ(derived_shared_session_key_1, derived_shared_session_key_2); +} + +TEST_P(ECKeyTestKeyPairs, InvalidDeriveSharedSessionKeyParameters) { + GenerateEphemeralKeyPair(private_key_->Curve()); + EXPECT_FALSE( + private_key_->DeriveSharedSessionKey(*ephemeral_public_key_, nullptr)); + EXPECT_FALSE( + ephemeral_private_key_->DeriveSharedSessionKey(*public_key_, nullptr)); +} + +TEST_P(ECKeyTestKeyPairs, SignVerify) { + std::string signature; + EXPECT_TRUE(private_key_->GenerateSignature(plaintext_message_, &signature)); + EXPECT_TRUE(public_key_->VerifySignature(plaintext_message_, signature)); +} + +TEST_P(ECKeyTestKeyPairs, InvalidSignVerifyParameters) { + std::string signature; + EXPECT_FALSE(private_key_->GenerateSignature("", &signature)); + EXPECT_FALSE(public_key_->VerifySignature("", "signature")); + EXPECT_FALSE(private_key_->GenerateSignature(plaintext_message_, nullptr)); + EXPECT_FALSE(public_key_->VerifySignature(plaintext_message_, "")); +} + +TEST_P(ECKeyTestKeyPairs, KeyMismatch) { + GenerateEphemeralKeyPair(private_key_->Curve()); + EXPECT_FALSE(private_key_->MatchesPrivateKey(*ephemeral_private_key_)); + EXPECT_FALSE(private_key_->MatchesPublicKey(*ephemeral_public_key_)); + EXPECT_FALSE(public_key_->MatchesPrivateKey(*ephemeral_private_key_)); + EXPECT_FALSE(public_key_->MatchesPublicKey(*ephemeral_public_key_)); +} + +TEST_P(ECKeyTestKeyPairs, SignVerifyKeyMismatch) { + std::string signature; + GenerateEphemeralKeyPair(private_key_->Curve()); + EXPECT_TRUE(private_key_->GenerateSignature(plaintext_message_, &signature)); + EXPECT_FALSE( + ephemeral_public_key_->VerifySignature(plaintext_message_, signature)); +} + +TEST_P(ECKeyTestKeyPairs, KeyPointEncodingCreateBadKeyPoint) { + ASSERT_THAT(ECPublicKey::CreateFromKeyPoint(public_key_->Curve(), ""), + IsNull()); +} + +TEST_P(ECKeyTestKeyPairs, KeyPointEncodingCreateBadCurve) { + std::string serialized_key_point; + ASSERT_TRUE(public_key_->GetPointEncodedKey(&serialized_key_point)); + ASSERT_THAT(ECPublicKey::CreateFromKeyPoint(ECPrivateKey::UNDEFINED_CURVE, + serialized_key_point), + IsNull()); +} + +TEST_P(ECKeyTestKeyPairs, KeyPointEncodingSuccess) { + std::string serialized_key_point; + ASSERT_TRUE(public_key_->GetPointEncodedKey(&serialized_key_point)); + ASSERT_EQ(ec_util::GetPublicKeyPointSize(public_key_->Curve()), + serialized_key_point.size()); + // Check that the leading byte indicates uncompressed. + ASSERT_EQ(4, serialized_key_point[0]); + + std::unique_ptr new_public_key = ECPublicKey::CreateFromKeyPoint( + public_key_->Curve(), serialized_key_point); + ASSERT_THAT(new_public_key, testing::NotNull()); + EXPECT_TRUE(new_public_key->MatchesPublicKey(*public_key_)); + EXPECT_TRUE(new_public_key->MatchesPrivateKey(*private_key_)); +}; + +INSTANTIATE_TEST_SUITE_P(ECKeyTestKeyPairs, ECKeyTestKeyPairs, + ::testing::ValuesIn(ECKeyTest::GetTestKeyList())); + +class ECKeyTestCurveMismatch + : public ECKeyTest, + public ::testing::WithParamInterface< + std::tuple, + std::tuple > > { + protected: + ECKeyTestCurveMismatch() { + private_key_ = ECPrivateKey::Create(std::get<0>(std::get<0>(GetParam()))); + public_key_ = ECPublicKey::Create(std::get<1>(std::get<0>(GetParam()))); + wrong_curve_private_key_ = + ECPrivateKey::Create(std::get<0>(std::get<1>(GetParam()))); + wrong_curve_public_key_ = + ECPublicKey::Create(std::get<1>(std::get<1>(GetParam()))); + } + + void SetUp() override { + // Only run combinations where the two curves differ. + if (std::get<0>(GetParam()) == std::get<1>(GetParam())) GTEST_SKIP(); + } + + std::unique_ptr private_key_; + std::unique_ptr public_key_; + std::unique_ptr wrong_curve_private_key_; + std::unique_ptr wrong_curve_public_key_; +}; + +TEST_P(ECKeyTestCurveMismatch, CurveMismatch) { + EXPECT_FALSE(private_key_->MatchesPrivateKey(*wrong_curve_private_key_)); + EXPECT_FALSE(private_key_->MatchesPublicKey(*wrong_curve_public_key_)); + EXPECT_FALSE(public_key_->MatchesPrivateKey(*wrong_curve_private_key_)); + EXPECT_FALSE(public_key_->MatchesPublicKey(*wrong_curve_public_key_)); +} + +TEST_P(ECKeyTestCurveMismatch, DeriveSharedSessionKeyCurveMismatch) { + std::string derived_shared_session_key; + EXPECT_FALSE(private_key_->DeriveSharedSessionKey( + *wrong_curve_public_key_, &derived_shared_session_key)); +} + +TEST_P(ECKeyTestCurveMismatch, SignVerifyCurveMismatch) { + std::string signature; + EXPECT_TRUE(private_key_->GenerateSignature(plaintext_message_, &signature)); + EXPECT_FALSE( + wrong_curve_public_key_->VerifySignature(plaintext_message_, signature)); +} + +INSTANTIATE_TEST_SUITE_P( + ECKeyTestCurveMismatch, ECKeyTestCurveMismatch, + ::testing::Combine(::testing::ValuesIn(ECKeyTest::GetTestKeyList()), + ::testing::ValuesIn(ECKeyTest::GetTestKeyList()))); + +} // namespace widevine diff --git a/common/ec_test_keys.cc b/common/ec_test_keys.cc new file mode 100644 index 0000000..a5c305a --- /dev/null +++ b/common/ec_test_keys.cc @@ -0,0 +1,223 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Test EC keys generated using openssl. + +#include "common/ec_test_keys.h" + +namespace widevine { + +static const unsigned char kTestECPrivateKey1Secp521r1[] = { + 0x30, 0x81, 0xdc, 0x02, 0x01, 0x01, 0x04, 0x42, 0x01, 0x4a, 0xf8, 0x9d, + 0xd5, 0x54, 0x6b, 0x11, 0xf7, 0xee, 0x52, 0xf3, 0xe7, 0xe1, 0xd0, 0x9a, + 0x31, 0xab, 0x35, 0xfb, 0xcc, 0x4a, 0x01, 0x48, 0xc4, 0x9c, 0xf1, 0xb5, + 0x20, 0x26, 0x7a, 0x08, 0x28, 0x07, 0xb2, 0xb5, 0xd1, 0x5b, 0xe5, 0x3c, + 0xec, 0xee, 0x32, 0xdf, 0xe5, 0x04, 0x6c, 0x1a, 0xb5, 0x13, 0x5b, 0xda, + 0xe1, 0xd6, 0xfa, 0x2e, 0x1b, 0x15, 0xde, 0xc2, 0x8c, 0x11, 0x75, 0xc6, + 0x51, 0x9c, 0xa0, 0x07, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x23, 0xa1, + 0x81, 0x89, 0x03, 0x81, 0x86, 0x00, 0x04, 0x00, 0x91, 0x11, 0x34, 0xfd, + 0x37, 0x4d, 0x3f, 0xb9, 0x87, 0x87, 0xf4, 0x60, 0xaa, 0xfc, 0xe7, 0xe5, + 0x8e, 0x54, 0x9a, 0x92, 0xef, 0xd1, 0x87, 0xe6, 0x83, 0x03, 0x81, 0x70, + 0x2c, 0xdf, 0x1e, 0x81, 0x14, 0x4e, 0x76, 0xbc, 0xb8, 0x61, 0x52, 0xd7, + 0xb4, 0x1f, 0xd2, 0x78, 0x87, 0x40, 0x65, 0xc3, 0x37, 0x87, 0xab, 0x12, + 0x49, 0xd6, 0x64, 0x59, 0x8b, 0x2c, 0x4f, 0x78, 0x41, 0x13, 0xa2, 0x25, + 0x87, 0x01, 0x22, 0xe9, 0x09, 0x8c, 0x63, 0x61, 0x1e, 0xee, 0x44, 0x0f, + 0xe7, 0x89, 0xc0, 0x0b, 0xf5, 0xc8, 0x68, 0xbb, 0xd0, 0x25, 0xda, 0x7f, + 0x9e, 0x30, 0xf7, 0x02, 0x74, 0x93, 0xb1, 0xc7, 0x00, 0xb4, 0x10, 0x6c, + 0xa6, 0x5c, 0x45, 0xe7, 0x6d, 0xdc, 0x94, 0xc0, 0x14, 0xe8, 0xcc, 0x5a, + 0xfb, 0x55, 0xbd, 0x3e, 0x85, 0x54, 0x06, 0xae, 0x31, 0xf0, 0xda, 0x55, + 0x91, 0x3e, 0xc3, 0x41, 0x60, 0x70, 0xd9, +}; + +static const unsigned char kTestECPublicKey1Secp521r1[] = { + 0x30, 0x81, 0x9b, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, + 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x23, 0x03, 0x81, 0x86, + 0x00, 0x04, 0x00, 0x91, 0x11, 0x34, 0xfd, 0x37, 0x4d, 0x3f, 0xb9, 0x87, + 0x87, 0xf4, 0x60, 0xaa, 0xfc, 0xe7, 0xe5, 0x8e, 0x54, 0x9a, 0x92, 0xef, + 0xd1, 0x87, 0xe6, 0x83, 0x03, 0x81, 0x70, 0x2c, 0xdf, 0x1e, 0x81, 0x14, + 0x4e, 0x76, 0xbc, 0xb8, 0x61, 0x52, 0xd7, 0xb4, 0x1f, 0xd2, 0x78, 0x87, + 0x40, 0x65, 0xc3, 0x37, 0x87, 0xab, 0x12, 0x49, 0xd6, 0x64, 0x59, 0x8b, + 0x2c, 0x4f, 0x78, 0x41, 0x13, 0xa2, 0x25, 0x87, 0x01, 0x22, 0xe9, 0x09, + 0x8c, 0x63, 0x61, 0x1e, 0xee, 0x44, 0x0f, 0xe7, 0x89, 0xc0, 0x0b, 0xf5, + 0xc8, 0x68, 0xbb, 0xd0, 0x25, 0xda, 0x7f, 0x9e, 0x30, 0xf7, 0x02, 0x74, + 0x93, 0xb1, 0xc7, 0x00, 0xb4, 0x10, 0x6c, 0xa6, 0x5c, 0x45, 0xe7, 0x6d, + 0xdc, 0x94, 0xc0, 0x14, 0xe8, 0xcc, 0x5a, 0xfb, 0x55, 0xbd, 0x3e, 0x85, + 0x54, 0x06, 0xae, 0x31, 0xf0, 0xda, 0x55, 0x91, 0x3e, 0xc3, 0x41, 0x60, + 0x70, 0xd9, +}; + +static const unsigned char kTestECPrivateKey1Secp384r1[] = { + 0x30, 0x81, 0xa4, 0x02, 0x01, 0x01, 0x04, 0x30, 0xc1, 0xee, 0x91, 0x6a, + 0xb3, 0x23, 0x90, 0xdf, 0x0a, 0x7a, 0x82, 0x86, 0x60, 0x72, 0x4f, 0xc0, + 0x02, 0x64, 0x0c, 0x3f, 0xc8, 0xdf, 0x8c, 0x06, 0x7a, 0x89, 0x39, 0xa0, + 0x9e, 0xb4, 0xf6, 0x7f, 0x3a, 0xcd, 0xf4, 0x69, 0xfb, 0xdb, 0x59, 0xdf, + 0x29, 0x40, 0x90, 0x34, 0x16, 0x42, 0x3a, 0x06, 0xa0, 0x07, 0x06, 0x05, + 0x2b, 0x81, 0x04, 0x00, 0x22, 0xa1, 0x64, 0x03, 0x62, 0x00, 0x04, 0xb0, + 0x50, 0x2a, 0x13, 0x20, 0x3e, 0x66, 0x67, 0xdf, 0x11, 0x2a, 0xbc, 0x0f, + 0x76, 0x69, 0x4b, 0xa1, 0x88, 0xec, 0xb8, 0x71, 0xcf, 0xc9, 0xbb, 0xd2, + 0xbc, 0xf8, 0x53, 0xfd, 0x8b, 0x8d, 0x14, 0x6f, 0xda, 0xea, 0x60, 0x51, + 0xc8, 0xd3, 0x3a, 0xd4, 0x75, 0x81, 0x05, 0x16, 0x03, 0x0b, 0xcb, 0x33, + 0x2c, 0x8b, 0xe6, 0xd3, 0x57, 0x6c, 0xfb, 0x81, 0x4b, 0xfe, 0x79, 0x56, + 0xf7, 0x6a, 0x2b, 0xca, 0xa7, 0x04, 0xe9, 0x37, 0xd6, 0x49, 0xe5, 0x8b, + 0x2c, 0xe9, 0x8e, 0xcd, 0xe7, 0xe3, 0xc9, 0xf5, 0x4c, 0x90, 0x82, 0x5f, + 0xf0, 0x53, 0xd2, 0xa4, 0x1a, 0xb3, 0x53, 0x3d, 0xa7, 0xa7, 0xfd, +}; + +static const unsigned char kTestECPublicKey1Secp384r1[] = { + 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, + 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, + 0xb0, 0x50, 0x2a, 0x13, 0x20, 0x3e, 0x66, 0x67, 0xdf, 0x11, 0x2a, 0xbc, + 0x0f, 0x76, 0x69, 0x4b, 0xa1, 0x88, 0xec, 0xb8, 0x71, 0xcf, 0xc9, 0xbb, + 0xd2, 0xbc, 0xf8, 0x53, 0xfd, 0x8b, 0x8d, 0x14, 0x6f, 0xda, 0xea, 0x60, + 0x51, 0xc8, 0xd3, 0x3a, 0xd4, 0x75, 0x81, 0x05, 0x16, 0x03, 0x0b, 0xcb, + 0x33, 0x2c, 0x8b, 0xe6, 0xd3, 0x57, 0x6c, 0xfb, 0x81, 0x4b, 0xfe, 0x79, + 0x56, 0xf7, 0x6a, 0x2b, 0xca, 0xa7, 0x04, 0xe9, 0x37, 0xd6, 0x49, 0xe5, + 0x8b, 0x2c, 0xe9, 0x8e, 0xcd, 0xe7, 0xe3, 0xc9, 0xf5, 0x4c, 0x90, 0x82, + 0x5f, 0xf0, 0x53, 0xd2, 0xa4, 0x1a, 0xb3, 0x53, 0x3d, 0xa7, 0xa7, 0xfd, +}; + +static const unsigned char kTestECPrivateKey1Secp256r1[] = { + 0x30, 0x77, 0x02, 0x01, 0x01, 0x04, 0x20, 0x7b, 0xa9, 0xe0, 0xe6, + 0xa5, 0x22, 0xfd, 0x5b, 0x7a, 0x73, 0xe7, 0x0e, 0x9d, 0x13, 0x97, + 0x79, 0x35, 0x95, 0x56, 0x67, 0xd0, 0x35, 0x8e, 0xc6, 0xa3, 0xb5, + 0xaa, 0x97, 0x64, 0xdb, 0xdb, 0xa9, 0xa0, 0x0a, 0x06, 0x08, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0xa1, 0x44, 0x03, 0x42, + 0x00, 0x04, 0x8a, 0xea, 0x3f, 0x16, 0xff, 0x24, 0xa9, 0xbf, 0x03, + 0x28, 0x30, 0x15, 0xee, 0x52, 0x50, 0x9a, 0x55, 0x1c, 0x60, 0xc7, + 0xa7, 0xcc, 0x4b, 0x99, 0x5b, 0x40, 0x55, 0xce, 0x46, 0x19, 0xd4, + 0xd4, 0x5e, 0xfd, 0xe0, 0x68, 0x27, 0xea, 0x78, 0xf3, 0x07, 0x1f, + 0x02, 0x4a, 0x78, 0x52, 0x44, 0xd3, 0xdf, 0xbe, 0xac, 0x5f, 0xa5, + 0x1c, 0x8a, 0x49, 0x8d, 0xa6, 0x5a, 0xac, 0xa1, 0x25, 0x2b, 0xd1, +}; + +static const unsigned char kTestECPublicKey1Secp256r1[] = { + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, + 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, + 0x42, 0x00, 0x04, 0x8a, 0xea, 0x3f, 0x16, 0xff, 0x24, 0xa9, 0xbf, 0x03, + 0x28, 0x30, 0x15, 0xee, 0x52, 0x50, 0x9a, 0x55, 0x1c, 0x60, 0xc7, 0xa7, + 0xcc, 0x4b, 0x99, 0x5b, 0x40, 0x55, 0xce, 0x46, 0x19, 0xd4, 0xd4, 0x5e, + 0xfd, 0xe0, 0x68, 0x27, 0xea, 0x78, 0xf3, 0x07, 0x1f, 0x02, 0x4a, 0x78, + 0x52, 0x44, 0xd3, 0xdf, 0xbe, 0xac, 0x5f, 0xa5, 0x1c, 0x8a, 0x49, 0x8d, + 0xa6, 0x5a, 0xac, 0xa1, 0x25, 0x2b, 0xd1, +}; + +static const unsigned char kTestECPrivateKey2Secp521r1[] = { + 0x30, 0x81, 0xdc, 0x02, 0x01, 0x01, 0x04, 0x42, 0x00, 0xbc, 0xe2, 0x3b, + 0xae, 0xba, 0xc3, 0xa4, 0x43, 0x68, 0x2b, 0xee, 0x83, 0x83, 0x49, 0x52, + 0x53, 0x22, 0xd8, 0xd3, 0xe2, 0xc6, 0x3c, 0xe5, 0xa1, 0xbb, 0x90, 0x03, + 0x37, 0xc8, 0xea, 0x26, 0x98, 0x20, 0xe5, 0x04, 0xaf, 0x55, 0x0a, 0x0f, + 0x40, 0x20, 0x71, 0x3e, 0xdd, 0x62, 0x88, 0xce, 0x74, 0x64, 0x2e, 0xbe, + 0x66, 0x8e, 0x5a, 0xf5, 0x77, 0x23, 0x13, 0x43, 0xc2, 0x6e, 0xaf, 0xdf, + 0x75, 0x9e, 0xa0, 0x07, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x23, 0xa1, + 0x81, 0x89, 0x03, 0x81, 0x86, 0x00, 0x04, 0x00, 0xf5, 0x2b, 0x67, 0x95, + 0x4c, 0xcb, 0xfc, 0x76, 0xe5, 0x4d, 0x13, 0x81, 0xbc, 0xcf, 0xd8, 0x9f, + 0x2e, 0x8b, 0xfd, 0x74, 0x93, 0x56, 0xa3, 0x51, 0x56, 0xa6, 0x17, 0x32, + 0xfc, 0x69, 0x15, 0xf3, 0x60, 0xca, 0xb4, 0x23, 0x66, 0x2c, 0x70, 0x08, + 0x1b, 0x92, 0x9a, 0x0e, 0x2e, 0xeb, 0x7c, 0xb6, 0x08, 0xe1, 0xf1, 0xb0, + 0x68, 0x44, 0x8b, 0x7c, 0x33, 0xd3, 0xd9, 0xbc, 0x0e, 0x90, 0x7b, 0x86, + 0x57, 0x00, 0x08, 0x3e, 0xd4, 0x0d, 0x44, 0x3e, 0x31, 0x16, 0x72, 0xf9, + 0x9f, 0xcb, 0x20, 0xa8, 0x3f, 0x00, 0xa3, 0xf7, 0x3c, 0xae, 0x90, 0x69, + 0xe1, 0x01, 0xd7, 0x59, 0x42, 0xb6, 0xcf, 0xc8, 0x5f, 0x19, 0x64, 0x17, + 0x1e, 0x42, 0x04, 0x92, 0xc2, 0x60, 0x9a, 0x06, 0x38, 0x5b, 0xf5, 0x4e, + 0xab, 0xd6, 0x96, 0x46, 0xdb, 0x55, 0x64, 0xc8, 0xf2, 0x75, 0xc7, 0x36, + 0xbe, 0x89, 0x8e, 0xb5, 0xcd, 0x2b, 0xb4}; + +static const unsigned char kTestECPublicKey2Secp521r1[] = { + 0x30, 0x81, 0x9b, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, + 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x23, 0x03, 0x81, 0x86, + 0x00, 0x04, 0x00, 0xf5, 0x2b, 0x67, 0x95, 0x4c, 0xcb, 0xfc, 0x76, 0xe5, + 0x4d, 0x13, 0x81, 0xbc, 0xcf, 0xd8, 0x9f, 0x2e, 0x8b, 0xfd, 0x74, 0x93, + 0x56, 0xa3, 0x51, 0x56, 0xa6, 0x17, 0x32, 0xfc, 0x69, 0x15, 0xf3, 0x60, + 0xca, 0xb4, 0x23, 0x66, 0x2c, 0x70, 0x08, 0x1b, 0x92, 0x9a, 0x0e, 0x2e, + 0xeb, 0x7c, 0xb6, 0x08, 0xe1, 0xf1, 0xb0, 0x68, 0x44, 0x8b, 0x7c, 0x33, + 0xd3, 0xd9, 0xbc, 0x0e, 0x90, 0x7b, 0x86, 0x57, 0x00, 0x08, 0x3e, 0xd4, + 0x0d, 0x44, 0x3e, 0x31, 0x16, 0x72, 0xf9, 0x9f, 0xcb, 0x20, 0xa8, 0x3f, + 0x00, 0xa3, 0xf7, 0x3c, 0xae, 0x90, 0x69, 0xe1, 0x01, 0xd7, 0x59, 0x42, + 0xb6, 0xcf, 0xc8, 0x5f, 0x19, 0x64, 0x17, 0x1e, 0x42, 0x04, 0x92, 0xc2, + 0x60, 0x9a, 0x06, 0x38, 0x5b, 0xf5, 0x4e, 0xab, 0xd6, 0x96, 0x46, 0xdb, + 0x55, 0x64, 0xc8, 0xf2, 0x75, 0xc7, 0x36, 0xbe, 0x89, 0x8e, 0xb5, 0xcd, + 0x2b, 0xb4}; + +static const unsigned char kTestECPrivateKey2Secp384r1[] = { + 0x30, 0x81, 0xa4, 0x02, 0x01, 0x01, 0x04, 0x30, 0xb7, 0x43, 0x85, 0x40, + 0x4c, 0x0b, 0x1e, 0x75, 0x11, 0xf9, 0x74, 0x3a, 0x77, 0xc5, 0x35, 0x76, + 0x31, 0xc9, 0x33, 0x56, 0x88, 0x23, 0x7c, 0x3b, 0x4c, 0xbc, 0x6a, 0x9e, + 0x8e, 0xc7, 0x0f, 0x4f, 0x25, 0xda, 0xeb, 0x60, 0xa2, 0x6e, 0x63, 0xbe, + 0x1f, 0x11, 0x77, 0x6d, 0xf3, 0x69, 0x42, 0x62, 0xa0, 0x07, 0x06, 0x05, + 0x2b, 0x81, 0x04, 0x00, 0x22, 0xa1, 0x64, 0x03, 0x62, 0x00, 0x04, 0x04, + 0xa7, 0x50, 0xdc, 0xff, 0x2c, 0x47, 0x28, 0x83, 0x67, 0x92, 0x67, 0xbb, + 0x4f, 0x69, 0xaf, 0xda, 0x7c, 0xd9, 0x79, 0xd8, 0x59, 0x8c, 0xd1, 0x4b, + 0x84, 0xf0, 0x7e, 0x04, 0xd3, 0x1b, 0xf1, 0x40, 0x83, 0x19, 0xe2, 0x22, + 0xcb, 0x8a, 0xee, 0x7f, 0x28, 0x5b, 0xea, 0xed, 0x24, 0x45, 0xae, 0xbc, + 0x2e, 0xa4, 0x3f, 0xdf, 0x60, 0xf2, 0x7c, 0x8b, 0xa8, 0xb7, 0x03, 0xc7, + 0x18, 0x68, 0x07, 0x0f, 0xfc, 0x8b, 0x0b, 0xec, 0x1e, 0xc6, 0xc5, 0x21, + 0xfb, 0xab, 0x3a, 0x5a, 0x83, 0x59, 0x62, 0x0b, 0xda, 0xcd, 0xd5, 0x35, + 0x7b, 0xf1, 0xd5, 0x51, 0xa4, 0x37, 0x97, 0x90, 0x1b, 0xe4, 0xf2}; + +static const unsigned char kTestECPublicKey2Secp384r1[] = { + 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, + 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, + 0x04, 0xa7, 0x50, 0xdc, 0xff, 0x2c, 0x47, 0x28, 0x83, 0x67, 0x92, 0x67, + 0xbb, 0x4f, 0x69, 0xaf, 0xda, 0x7c, 0xd9, 0x79, 0xd8, 0x59, 0x8c, 0xd1, + 0x4b, 0x84, 0xf0, 0x7e, 0x04, 0xd3, 0x1b, 0xf1, 0x40, 0x83, 0x19, 0xe2, + 0x22, 0xcb, 0x8a, 0xee, 0x7f, 0x28, 0x5b, 0xea, 0xed, 0x24, 0x45, 0xae, + 0xbc, 0x2e, 0xa4, 0x3f, 0xdf, 0x60, 0xf2, 0x7c, 0x8b, 0xa8, 0xb7, 0x03, + 0xc7, 0x18, 0x68, 0x07, 0x0f, 0xfc, 0x8b, 0x0b, 0xec, 0x1e, 0xc6, 0xc5, + 0x21, 0xfb, 0xab, 0x3a, 0x5a, 0x83, 0x59, 0x62, 0x0b, 0xda, 0xcd, 0xd5, + 0x35, 0x7b, 0xf1, 0xd5, 0x51, 0xa4, 0x37, 0x97, 0x90, 0x1b, 0xe4, 0xf2}; + +static const unsigned char kTestECPrivateKey2Secp256r1[] = { + 0x30, 0x77, 0x02, 0x01, 0x01, 0x04, 0x20, 0x34, 0x9a, 0xf2, 0x95, + 0x94, 0xd4, 0xca, 0xb9, 0xa0, 0x81, 0xe4, 0x1c, 0xf5, 0xde, 0x8d, + 0x23, 0xf6, 0x79, 0xba, 0x3c, 0x6e, 0xc9, 0x0b, 0x56, 0x0f, 0x07, + 0x5e, 0x9f, 0xe9, 0x38, 0x18, 0xfc, 0xa0, 0x0a, 0x06, 0x08, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0xa1, 0x44, 0x03, 0x42, + 0x00, 0x04, 0x7b, 0x2a, 0x61, 0x59, 0xe5, 0x1b, 0xb6, 0x30, 0x7b, + 0x59, 0x98, 0x42, 0x59, 0x37, 0xfb, 0x46, 0xfe, 0x53, 0xfe, 0x32, + 0xa1, 0x5c, 0x93, 0x36, 0x11, 0xb0, 0x5a, 0xae, 0xa4, 0x48, 0xe3, + 0x20, 0x12, 0xce, 0x78, 0xa7, 0x7f, 0xfd, 0x73, 0x5e, 0x09, 0x77, + 0x53, 0x77, 0x8f, 0xd6, 0x1b, 0x26, 0xfa, 0xc4, 0x2c, 0xc4, 0x11, + 0xfa, 0x72, 0x6a, 0xbe, 0x94, 0x78, 0x4d, 0x74, 0x20, 0x27, 0x86}; + +static const unsigned char kTestECPublicKey2Secp256r1[] = { + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, + 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, + 0x42, 0x00, 0x04, 0x7b, 0x2a, 0x61, 0x59, 0xe5, 0x1b, 0xb6, 0x30, 0x7b, + 0x59, 0x98, 0x42, 0x59, 0x37, 0xfb, 0x46, 0xfe, 0x53, 0xfe, 0x32, 0xa1, + 0x5c, 0x93, 0x36, 0x11, 0xb0, 0x5a, 0xae, 0xa4, 0x48, 0xe3, 0x20, 0x12, + 0xce, 0x78, 0xa7, 0x7f, 0xfd, 0x73, 0x5e, 0x09, 0x77, 0x53, 0x77, 0x8f, + 0xd6, 0x1b, 0x26, 0xfa, 0xc4, 0x2c, 0xc4, 0x11, 0xfa, 0x72, 0x6a, 0xbe, + 0x94, 0x78, 0x4d, 0x74, 0x20, 0x27, 0x86}; + +ECTestKeys::ECTestKeys() + : private_test_key_1_secp521r1_(std::begin(kTestECPrivateKey1Secp521r1), + std::end(kTestECPrivateKey1Secp521r1)), + public_test_key_1_secp521r1_(std::begin(kTestECPublicKey1Secp521r1), + std::end(kTestECPublicKey1Secp521r1)), + private_test_key_1_secp384r1_(std::begin(kTestECPrivateKey1Secp384r1), + std::end(kTestECPrivateKey1Secp384r1)), + public_test_key_1_secp384r1_(std::begin(kTestECPublicKey1Secp384r1), + std::end(kTestECPublicKey1Secp384r1)), + private_test_key_1_secp256r1_(std::begin(kTestECPrivateKey1Secp256r1), + std::end(kTestECPrivateKey1Secp256r1)), + public_test_key_1_secp256r1_(std::begin(kTestECPublicKey1Secp256r1), + std::end(kTestECPublicKey1Secp256r1)), + private_test_key_2_secp521r1_(std::begin(kTestECPrivateKey2Secp521r1), + std::end(kTestECPrivateKey2Secp521r1)), + public_test_key_2_secp521r1_(std::begin(kTestECPublicKey2Secp521r1), + std::end(kTestECPublicKey2Secp521r1)), + private_test_key_2_secp384r1_(std::begin(kTestECPrivateKey2Secp384r1), + std::end(kTestECPrivateKey2Secp384r1)), + public_test_key_2_secp384r1_(std::begin(kTestECPublicKey2Secp384r1), + std::end(kTestECPublicKey2Secp384r1)), + private_test_key_2_secp256r1_(std::begin(kTestECPrivateKey2Secp256r1), + std::end(kTestECPrivateKey2Secp256r1)), + public_test_key_2_secp256r1_(std::begin(kTestECPublicKey2Secp256r1), + std::end(kTestECPublicKey2Secp256r1)) {} +} // namespace widevine diff --git a/common/ec_test_keys.h b/common/ec_test_keys.h new file mode 100644 index 0000000..4a5bcad --- /dev/null +++ b/common/ec_test_keys.h @@ -0,0 +1,91 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Test EC keys generated for testing purposes. + +#ifndef COMMON_EC_TEST_KEYS_H_ +#define COMMON_EC_TEST_KEYS_H_ + +#include + +namespace widevine { + +class ECTestKeys { + public: + ECTestKeys(); + + const std::string& private_test_key_1_secp521r1() const { + return private_test_key_1_secp521r1_; + } + + const std::string& public_test_key_1_secp521r1() const { + return public_test_key_1_secp521r1_; + } + + const std::string& private_test_key_2_secp521r1() const { + return private_test_key_2_secp521r1_; + } + + const std::string& public_test_key_2_secp521r1() const { + return public_test_key_2_secp521r1_; + } + + const std::string& private_test_key_1_secp384r1() const { + return private_test_key_1_secp384r1_; + } + + const std::string& public_test_key_1_secp384r1() const { + return public_test_key_1_secp384r1_; + } + + const std::string& private_test_key_2_secp384r1() const { + return private_test_key_2_secp384r1_; + } + + const std::string& public_test_key_2_secp384r1() const { + return public_test_key_2_secp384r1_; + } + + const std::string& private_test_key_1_secp256r1() const { + return private_test_key_1_secp256r1_; + } + + const std::string& public_test_key_1_secp256r1() const { + return public_test_key_1_secp256r1_; + } + + const std::string& private_test_key_2_secp256r1() const { + return private_test_key_2_secp256r1_; + } + + const std::string& public_test_key_2_secp256r1() const { + return public_test_key_2_secp256r1_; + } + + private: + ECTestKeys(const ECTestKeys&) = delete; + ECTestKeys& operator=(const ECTestKeys&) = delete; + + std::string private_test_key_1_secp521r1_; + std::string public_test_key_1_secp521r1_; + std::string private_test_key_1_secp384r1_; + std::string public_test_key_1_secp384r1_; + std::string private_test_key_1_secp256r1_; + std::string public_test_key_1_secp256r1_; + std::string private_test_key_2_secp521r1_; + std::string public_test_key_2_secp521r1_; + std::string private_test_key_2_secp384r1_; + std::string public_test_key_2_secp384r1_; + std::string private_test_key_2_secp256r1_; + std::string public_test_key_2_secp256r1_; +}; + +} // namespace widevine + +#endif // COMMON_EC_TEST_KEYS_H_ diff --git a/common/ec_util.cc b/common/ec_util.cc new file mode 100644 index 0000000..860fb06 --- /dev/null +++ b/common/ec_util.cc @@ -0,0 +1,198 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Implementation of EC utility functions. + +#include "common/ec_util.h" + +#include +#include + +#include "glog/logging.h" +#include "absl/memory/memory.h" +#include "openssl/ec.h" +#include "openssl/ec_key.h" +#include "openssl/pem.h" +#include "common/ec_key.h" +#include "common/openssl_util.h" +#include "common/private_key_util.h" + +namespace widevine { +namespace ec_util { + +namespace { +// Expected X9.62 format. . +// tag = 0x04 indicating uncompressed format. +// size of X and Y equal. But differ per curve. +// Secp256R1 32 bytes per value. +// Secp384R1 48 bytes per value. +// Secp521R1 66 bytes per value. +const size_t kSecp256R1ExpectedPubKeySize = 256 / 8 * 2 + 1; // 65. +const size_t kSecp384R1ExpectedPubKeySize = 384 / 8 * 2 + 1; // 97. +// 521 has to be rounded up to next block size, 528. +const size_t kSecp521R1ExpectedPubKeySize = (521 + 7) / 8 * 2 + 1; // 133. + +std::string OpenSSLErrorString(uint32_t error) { + char buf[ERR_ERROR_STRING_BUF_LEN]; + ERR_error_string_n(error, buf, sizeof(buf)); + return buf; +} + +} // namespace + +bool SerializeECPrivateKey(const EC_KEY* private_key, + std::string* serialized_private_key) { + return private_key_util::SerializeKey( + private_key, i2d_ECPrivateKey_bio, serialized_private_key); +} + +bool DeserializeECPrivateKey(const std::string& serialized_private_key, + EC_KEY** private_key) { + return private_key_util::DeserializeKey( + serialized_private_key, d2i_ECPrivateKey_bio, private_key); +} + +bool SerializeECPublicKey(const EC_KEY* public_key, + std::string* serialized_public_key) { + return private_key_util::SerializeKey(public_key, i2d_EC_PUBKEY_bio, + serialized_public_key); +} + +bool DeserializeECPublicKey(const std::string& serialized_public_key, + EC_KEY** public_key) { + return private_key_util::DeserializeKey( + serialized_public_key, d2i_EC_PUBKEY_bio, public_key); +} + +size_t GetPublicKeyPointSize(ECPrivateKey::EllipticCurve curve) { + switch (curve) { + case widevine::ECPrivateKey::SECP256R1: + return kSecp256R1ExpectedPubKeySize; + case widevine::ECPrivateKey::SECP384R1: + return kSecp384R1ExpectedPubKeySize; + case widevine::ECPrivateKey::SECP521R1: + return kSecp521R1ExpectedPubKeySize; + default: + return 0; + } +} + +bool GetPublicKeyPoint(const EC_KEY* ec_key, std::string* encoded_key) { + CHECK(ec_key); + CHECK(encoded_key); + const EC_POINT* public_key = EC_KEY_get0_public_key(ec_key); + if (public_key == nullptr) { + LOG(ERROR) << "Could not retrieve EC_POINT from EC_KEY."; + return false; + } + + size_t len = + EC_POINT_point2oct(EC_KEY_get0_group(ec_key), public_key, + POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, nullptr); + if (len == 0) { + LOG(ERROR) << "Unexpected error getting the size of the public key."; + return false; + } + encoded_key->resize(len); + uint8_t* encoded_data = reinterpret_cast(&(*encoded_key)[0]); + len = EC_POINT_point2oct(EC_KEY_get0_group(ec_key), public_key, + POINT_CONVERSION_UNCOMPRESSED, encoded_data, + encoded_key->size(), nullptr); + if (len == 0) { + LOG(ERROR) << "Unexpected error encoding the public key."; + return false; + } + + return true; +} + +bool GetPublicKeyFromKeyPoint(ECPrivateKey::EllipticCurve curve, + const std::string& key_point, + EC_KEY** public_key) { + CHECK(public_key); + ScopedECGROUP group(EC_GROUP_new_by_curve_name(CurveToNid(curve))); + if (group == nullptr) { + LOG(ERROR) << "Could not load the EC Group for curve " << curve; + return false; + } + + ScopedECPOINT point(EC_POINT_new(group.get())); + if (EC_POINT_oct2point(group.get(), point.get(), + reinterpret_cast(key_point.data()), + key_point.size(), nullptr) == 0) { + LOG(ERROR) << "Failed to convert the serialized point to EC_POINT."; + return false; + } + + ScopedECKEY key(EC_KEY_new_by_curve_name(ec_util::CurveToNid(curve))); + if (EC_KEY_set_public_key(key.get(), point.get()) == 0) { + LOG(ERROR) << "Failed to convert the EC_POINT to EC_KEY"; + return false; + } + + *public_key = key.release(); + return true; +} + +ECPrivateKey::EllipticCurve NidToCurve(int nid) { + switch (nid) { + case NID_X9_62_prime256v1: + return ECPrivateKey::SECP256R1; + case NID_secp384r1: + return ECPrivateKey::SECP384R1; + case NID_secp521r1: + return ECPrivateKey::SECP521R1; + default: + LOG(ERROR) << "Unaccepted nid: " << nid; + return ECPrivateKey::UNDEFINED_CURVE; + } +} + +int CurveToNid(ECPrivateKey::EllipticCurve curve) { + switch (curve) { + case ECPrivateKey::SECP256R1: + // This is the ANSI X9.62 name for secp256r1. openssl uses prime256v1 + // instead of secp256r1. + // https://tools.ietf.org/search/rfc4492#appendix-A + return NID_X9_62_prime256v1; + case ECPrivateKey::SECP384R1: + return NID_secp384r1; + case ECPrivateKey::SECP521R1: + return NID_secp521r1; + default: + LOG(ERROR) << "EllipticCurve " << curve << " not accepted"; + return NID_undef; + } +} + +// TODO(user): Benchmark ecc key generation and evaluate whether additional +// changes are needed to improve the implementation. Currently an ephemeral key +// pair is generated per license request. We will also test for the impact of +// multiple threads generating keys. +ScopedECKEY GenerateKeyWithCurve(ECPrivateKey::EllipticCurve curve) { + if (curve == ECPrivateKey::UNDEFINED_CURVE) { + LOG(ERROR) << "Requested key uses unsupported curve"; + return nullptr; + } + ScopedECKEY new_ec_key(EC_KEY_new_by_curve_name(ec_util::CurveToNid(curve))); + if (EC_KEY_generate_key(new_ec_key.get()) == 0) { + LOG(ERROR) << "Unable to generate private key: " + << OpenSSLErrorString(ERR_get_error()); + return nullptr; + } + if (EC_KEY_check_key(new_ec_key.get()) == 0) { + LOG(ERROR) << "Invalid private EC key: " + << OpenSSLErrorString(ERR_get_error()); + return nullptr; + } + return new_ec_key; +} + +} // namespace ec_util +} // namespace widevine diff --git a/common/ec_util.h b/common/ec_util.h new file mode 100644 index 0000000..71d6b68 --- /dev/null +++ b/common/ec_util.h @@ -0,0 +1,87 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Utility functions to deserialize and serialize EC keys. + +#ifndef COMMON_EC_UTIL_H_ +#define COMMON_EC_UTIL_H_ + +#include + +#include "openssl/ec.h" +#include "common/ec_key.h" + +namespace widevine { +namespace ec_util { + +// Serialize EC private key into DER encoded RFC5915 std::string. +// Parameter |private_key| is the EC private key to be serialized. +// Parameter |serialized_private_key| will hold the serialized private key. +// Caller retains ownership of all pointers and they cannot be nullptr. +// Returns true if successful, false otherwise. +bool SerializeECPrivateKey(const EC_KEY* private_key, + std::string* serialized_private_key); + +// Deserialized DER encoded RFC5915 std::string into an EC private key. +// Parameter |serialized_private_key| contains the serialized private key. +// Parameter |private_key| will hold the deserialized EC private key. +// Caller retains ownership of all pointers and they cannot be nullptr. +// Returns true if successful, false otherwise. +bool DeserializeECPrivateKey(const std::string& serialized_private_key, + EC_KEY** private_key); + +// Serialize EC public key into DER encoded RFC5208 std::string. +// Parameter |public_key| is the EC public key to be serialized. +// Parameter |serialized_public_key| will hold the serialized public key. +// Caller retains ownership of all pointers and they cannot be nullptr. +// Returns true if successful, false otherwise. +bool SerializeECPublicKey(const EC_KEY* public_key, + std::string* serialized_public_key); + +// Deserialized DER encoded RFC5208 std::string into an EC private key. +// Parameter |serialized_public_key| contains the serialized public key. +// Parameter |public_key| will hold the deserialized EC public key. +// Caller retains ownership of all pointers and they cannot be nullptr. +// Returns true if successful, false otherwise. +bool DeserializeECPublicKey(const std::string& serialized_public_key, + EC_KEY** public_key); + +// Returns the expected size of the encoded public key keypoint for the given +// key |curve|. +size_t GetPublicKeyPointSize(ECPrivateKey::EllipticCurve curve); + +// Returns the public key from the |ec_key| encoded per X9.62. +bool GetPublicKeyPoint(const EC_KEY* ec_key, std::string* encoded_key); + +// Returns a |public_key| for the given curve, deserialized from the +// X9.62 |key_point|. |key_point| is required to be in uncompressed format and +// the correct size for the given curve. +bool GetPublicKeyFromKeyPoint(ECPrivateKey::EllipticCurve curve, + const std::string& key_point, + EC_KEY** public_key); + +// Convert |nid| to its corresponding EllipticCurve value if supported. +// Parameter |nid| corresponds to one of the macros in nid.h. +// Returns a valid EllipticCurve if |nid| is supported and UNDEFINED_CURVE +// otherwise. +ECPrivateKey::EllipticCurve NidToCurve(int nid); + +// Convert |curve| to its corresponding NID value. +// Parameter |curve| is an accepted curve value. +// Returns the NID that corresponds to the curve in nid.h if it exists and +// NID_undef otherwise. +int CurveToNid(ECPrivateKey::EllipticCurve curve); + +// Creates a new ECKey using the provided |curve|. +ScopedECKEY GenerateKeyWithCurve(ECPrivateKey::EllipticCurve curve); + +} // namespace ec_util +} // namespace widevine + +#endif // COMMON_EC_UTIL_H_ diff --git a/common/ec_util_test.cc b/common/ec_util_test.cc new file mode 100644 index 0000000..e19ea0f --- /dev/null +++ b/common/ec_util_test.cc @@ -0,0 +1,202 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Unit tests for ec_util. + +#include "common/ec_util.h" + +#include + +#include + +#include "glog/logging.h" +#include "testing/gmock.h" +#include "testing/gunit.h" +#include "common/ec_test_keys.h" +#include "common/openssl_util.h" + +namespace widevine { +namespace ec_util { + +class ECUtilTest : public ::testing::Test { + protected: + ECTestKeys test_keys_; + + // Given serialization and deserialization methods for an EC_KEY and a test + // key, this checks that that those methods can deserialize to a valid EC_KEY + // and serialize to the same test key. + template + void SerializeDeserializeECKeyCheck(const std::string& test_key) { + EC_KEY* key = nullptr; + std::string serialized_key; + EXPECT_TRUE(DeserializeECKey(test_key, &key)); + ASSERT_TRUE(key != nullptr); + EXPECT_TRUE(SerializeECKey(key, &serialized_key)); + EXPECT_EQ(serialized_key, test_key); + EXPECT_EQ(EC_KEY_check_key(key), 1); + EC_KEY_free(key); + } + + // Given serialization and deserialization methods for an EC_KEY and a test + // key, this method checks that deserialization fails for a bad input std::string + // and both serialization and deserialization fail on nullptrs. + template + void SerializeDeserializeECKeyValidateInput(const std::string& test_key) { + EC_KEY* key = nullptr; + // Invalid key + EXPECT_FALSE(DeserializeECKey("this is a bad key", &key)); + EXPECT_EQ(key, nullptr); + // Invalid pointers + EXPECT_TRUE(DeserializeECKey(test_key, &key)); + EXPECT_FALSE(SerializeECKey(key, nullptr)); + EC_KEY_free(key); + EXPECT_FALSE(DeserializeECKey(test_key, nullptr)); + } +}; + +TEST_F(ECUtilTest, SerializeDeserializePrivateKey) { + SerializeDeserializeECKeyCheck( + test_keys_.private_test_key_1_secp521r1()); + SerializeDeserializeECKeyCheck( + test_keys_.private_test_key_1_secp384r1()); + SerializeDeserializeECKeyCheck( + test_keys_.private_test_key_1_secp256r1()); + SerializeDeserializeECKeyValidateInput( + test_keys_.private_test_key_1_secp521r1()); +} + +TEST_F(ECUtilTest, SerializeDeserializePublicKey) { + SerializeDeserializeECKeyCheck( + test_keys_.public_test_key_1_secp521r1()); + SerializeDeserializeECKeyCheck( + test_keys_.public_test_key_1_secp384r1()); + SerializeDeserializeECKeyCheck( + test_keys_.public_test_key_1_secp256r1()); + SerializeDeserializeECKeyValidateInput( + test_keys_.public_test_key_1_secp521r1()); +} + +TEST_F(ECUtilTest, PublicKeyExtraction) { + EC_KEY* private_key = nullptr; + std::string serialized_public_key; + // Key 1 + EXPECT_TRUE(DeserializeECPrivateKey(test_keys_.private_test_key_1_secp521r1(), + &private_key)); + ASSERT_TRUE(private_key != nullptr); + EXPECT_TRUE(SerializeECPublicKey(private_key, &serialized_public_key)); + EXPECT_EQ(serialized_public_key, test_keys_.public_test_key_1_secp521r1()); + EC_KEY_free(private_key); + private_key = nullptr; + // Key 2 + EXPECT_TRUE(DeserializeECPrivateKey(test_keys_.private_test_key_1_secp384r1(), + &private_key)); + ASSERT_TRUE(private_key != nullptr); + EXPECT_TRUE(SerializeECPublicKey(private_key, &serialized_public_key)); + EXPECT_EQ(serialized_public_key, test_keys_.public_test_key_1_secp384r1()); + EC_KEY_free(private_key); + private_key = nullptr; + // Key 3 + EXPECT_TRUE(DeserializeECPrivateKey(test_keys_.private_test_key_1_secp256r1(), + &private_key)); + ASSERT_TRUE(private_key != nullptr); + EXPECT_TRUE(SerializeECPublicKey(private_key, &serialized_public_key)); + EXPECT_EQ(serialized_public_key, test_keys_.public_test_key_1_secp256r1()); + EC_KEY_free(private_key); +} + +TEST_F(ECUtilTest, GetPublicKeyPointSizeAll) { + ASSERT_GT(ec_util::GetPublicKeyPointSize(ECPrivateKey::SECP256R1), 0); + ASSERT_GT(ec_util::GetPublicKeyPointSize(ECPrivateKey::SECP384R1), 0); + ASSERT_GT(ec_util::GetPublicKeyPointSize(ECPrivateKey::SECP521R1), 0); + + ASSERT_EQ(0, ec_util::GetPublicKeyPointSize(ECPrivateKey::UNDEFINED_CURVE)); +} + +TEST_F(ECUtilTest, GetPublicKeyFromKeyPointDeath) { + EXPECT_DEATH(GetPublicKeyFromKeyPoint(ECPrivateKey::SECP256R1, "", nullptr), + ""); +} + +class ECKeyPointTest : public ::testing::Test, + public ::testing::WithParamInterface { + public: + static std::vector GetTestKeyList() { + ECTestKeys test_keys; + return {test_keys.public_test_key_1_secp256r1(), + test_keys.public_test_key_1_secp384r1(), + test_keys.public_test_key_1_secp521r1()}; + } + + void SetUp() override { + serialized_public_key_ = GetParam(); + + EC_KEY* public_key = nullptr; + ASSERT_TRUE(DeserializeECPublicKey(serialized_public_key_, &public_key)); + ASSERT_TRUE(public_key != nullptr); + ec_public_key_.reset(public_key); + + curve_ = NidToCurve( + EC_GROUP_get_curve_name(EC_KEY_get0_group(ec_public_key_.get()))); + } + + protected: + std::string serialized_public_key_; + ScopedECKEY ec_public_key_; + ECPrivateKey::EllipticCurve curve_; +}; + +INSTANTIATE_TEST_SUITE_P(ECUtilKeyPointTest, ECKeyPointTest, + ::testing::ValuesIn(ECKeyPointTest::GetTestKeyList())); + +TEST_P(ECKeyPointTest, GetPublicKeyPointDeath) { + EXPECT_DEATH(GetPublicKeyPoint(ec_public_key_.get(), nullptr), ""); +} + +TEST_P(ECKeyPointTest, GetPublicKeyFromKeyPointInvalidCurve) { + std::string encoded_key; + ASSERT_TRUE(GetPublicKeyPoint(ec_public_key_.get(), &encoded_key)); + EC_KEY* new_public_key = nullptr; + EXPECT_FALSE(GetPublicKeyFromKeyPoint(ECPrivateKey::UNDEFINED_CURVE, + encoded_key, &new_public_key)); +} + +TEST_P(ECKeyPointTest, GetPublicKeyFromKeyPointInvalidSize) { + EC_KEY* new_public_key = nullptr; + EXPECT_FALSE(GetPublicKeyFromKeyPoint(curve_, "", &new_public_key)); +} + +TEST_P(ECKeyPointTest, PublicKeyPointFormatTest) { + std::string encoded_key; + ASSERT_TRUE(GetPublicKeyPoint(ec_public_key_.get(), &encoded_key)); + ASSERT_FALSE(encoded_key.empty()); + EXPECT_EQ(4, encoded_key[0]); + EXPECT_EQ(GetPublicKeyPointSize(curve_), encoded_key.size()); + + // Create a new EC key from the key point. + EC_KEY* ec_key = nullptr; + ASSERT_TRUE(GetPublicKeyFromKeyPoint(curve_, encoded_key, &ec_key)); + ASSERT_TRUE(ec_key != nullptr); + ScopedECKEY new_public_key(ec_key); + + // EC_POINT_cmp returns 0 if the keys match. + ASSERT_EQ( + 0, EC_POINT_cmp(EC_KEY_get0_group(ec_public_key_.get()), + EC_KEY_get0_public_key(ec_public_key_.get()), + EC_KEY_get0_public_key(new_public_key.get()), nullptr)); +} + +} // namespace ec_util +} // namespace widevine diff --git a/common/ecb_util.cc b/common/ecb_util.cc index de7377d..799b9c3 100644 --- a/common/ecb_util.cc +++ b/common/ecb_util.cc @@ -55,15 +55,18 @@ static bool EncryptOrDecrypt3DesCbc(absl::string_view key, return true; } -bool Encrypt3DesEcb(absl::string_view key, absl::string_view src, std::string* dst) { +bool Encrypt3DesEcb(absl::string_view key, absl::string_view src, + std::string* dst) { return EncryptOrDecrypt3DesCbc(key, src, dst, DES_ENCRYPT); } -bool Decrypt3DesEcb(absl::string_view key, absl::string_view src, std::string* dst) { +bool Decrypt3DesEcb(absl::string_view key, absl::string_view src, + std::string* dst) { return EncryptOrDecrypt3DesCbc(key, src, dst, DES_DECRYPT); } -bool EncryptAesEcb(absl::string_view key, absl::string_view src, std::string* dst) { +bool EncryptAesEcb(absl::string_view key, absl::string_view src, + std::string* dst) { CHECK(dst); dst->clear(); if (src.size() % 16 != 0) { @@ -86,7 +89,8 @@ bool EncryptAesEcb(absl::string_view key, absl::string_view src, std::string* ds return true; } -bool DecryptAesEcb(absl::string_view key, absl::string_view src, std::string* dst) { +bool DecryptAesEcb(absl::string_view key, absl::string_view src, + std::string* dst) { CHECK(dst); dst->clear(); if (src.size() % 16 != 0) { diff --git a/common/ecb_util.h b/common/ecb_util.h index 0b5e82b..ecbbbd4 100644 --- a/common/ecb_util.h +++ b/common/ecb_util.h @@ -25,7 +25,8 @@ namespace crypto_util { // unsuccessful. Key should be 16 bytes. The first 8 are key2 of the 3DES key // bundle, and the last 8 bytes are key1 and key3. |src| must be a multiple of // 8 bytes. -bool Encrypt3DesEcb(absl::string_view key, absl::string_view src, std::string* dst); +bool Encrypt3DesEcb(absl::string_view key, absl::string_view src, + std::string* dst); // Decrypts |src| into |dst| using 3DES ECB2 mode with the given (16-byte) // key. This is used for protecting content keys on some older Widevine @@ -35,7 +36,8 @@ bool Encrypt3DesEcb(absl::string_view key, absl::string_view src, std::string* d // Key should be 16 bytes. The first 8 are key2 of the 3DES key bundle, // and the last 8 bytes are key1 and key3. |src| must be a multiple of // 8 bytes. -bool Decrypt3DesEcb(absl::string_view key, absl::string_view src, std::string* dst); +bool Decrypt3DesEcb(absl::string_view key, absl::string_view src, + std::string* dst); // Encrypts |src| into |dst| using AES ECB mode with the given // key. This is used for protecting content keys on Widevine devices, @@ -43,7 +45,8 @@ bool Decrypt3DesEcb(absl::string_view key, absl::string_view src, std::string* d // Returns false and sets *|dst|="" if unsuccessful. Note that it can only // fail if invalid key or data sizes are passed in. // Key must be 16 bytes, and src must be a multiple of 16 bytes. -bool EncryptAesEcb(absl::string_view key, absl::string_view src, std::string* dst); +bool EncryptAesEcb(absl::string_view key, absl::string_view src, + std::string* dst); // Decrypts |src| into |dst| using AES ECB mode with the given // key. This is used for protecting content keys on Widevine devices, @@ -51,7 +54,8 @@ bool EncryptAesEcb(absl::string_view key, absl::string_view src, std::string* ds // Returns false and sets *|dst|="" if unsuccessful. Note that it can only // fail if invalid key or data sizes are passed in. // Key must be 16 bytes, and src must be a multiple of 16 bytes. -bool DecryptAesEcb(absl::string_view key, absl::string_view src, std::string* dst); +bool DecryptAesEcb(absl::string_view key, absl::string_view src, + std::string* dst); } // namespace crypto_util } // namespace widevine diff --git a/common/ecies_crypto.cc b/common/ecies_crypto.cc new file mode 100644 index 0000000..5e61036 --- /dev/null +++ b/common/ecies_crypto.cc @@ -0,0 +1,239 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#include "common/ecies_crypto.h" + +#include + +#include "glog/logging.h" +#include "absl/strings/escaping.h" +#include "openssl/ec.h" +#include "common/aes_cbc_util.h" +#include "common/crypto_util.h" +#include "common/ec_key.h" +#include "common/ec_util.h" +#include "common/openssl_util.h" + +namespace { + +const char kZeroIv[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +const size_t kMacSizeBytes = 32; +const size_t kMasterKeySizeBytes = 32; +const size_t kEncryptionKeySizeBytes = kMasterKeySizeBytes; +const size_t kSigningKeySizeBytes = kMasterKeySizeBytes; +const size_t kEncryptionKeySizeBits = kEncryptionKeySizeBytes * 8; +const size_t kSigningKeySizeBits = kSigningKeySizeBytes * 8; + +const char kWrappingKeyLabel[] = "ECIESEncryptionLabel"; +const char kSigningKeyLabel[] = "ECIESSigningLabel"; + +bool DeriveWrappingAndSigningKeys(const std::string& key, + const std::string& context, + std::string* wrapping_key, + std::string* signing_key) { + if (wrapping_key == nullptr || signing_key == nullptr) return false; + if (key.size() != kMasterKeySizeBytes) { + LOG(ERROR) << "Invalid master key size"; + return false; + } + + *wrapping_key = widevine::crypto_util::DeriveKey( + key, kWrappingKeyLabel, context, kEncryptionKeySizeBits); + if (wrapping_key->empty()) { + LOG(WARNING) << "Failed to derive encryption key."; + return false; + } + + *signing_key = widevine::crypto_util::DeriveKey( + key, kSigningKeyLabel, context, kSigningKeySizeBits); + if (signing_key->empty()) { + LOG(WARNING) << "Failed to derive sigining key."; + return false; + } + return true; +} + +} // anonymous namespace + +namespace widevine { + +EciesEncryptor::EciesEncryptor(std::unique_ptr public_key, + ECKeySource* key_source) + : public_key_(std::move(public_key)), key_source_(key_source) {} + +std::unique_ptr EciesEncryptor::Create( + const std::string& serialized_public_key, ECKeySource* key_source) { + if (key_source == nullptr) { + return nullptr; + } + std::unique_ptr ec_key = + ECPublicKey::Create(serialized_public_key); + if (ec_key == nullptr) { + return nullptr; + } + std::unique_ptr encryptor( + new EciesEncryptor(std::move(ec_key), key_source)); + return encryptor; +} + +bool EciesEncryptor::Encrypt(const std::string& plaintext, + const std::string& context, + std::string* ecies_message) const { + if (ecies_message == nullptr) { + LOG(ERROR) << "ecies_message cannot be null"; + return false; + } + + std::string serialized_private_key; + std::string serialized_public_key; + if (!key_source_->GetECKey(public_key_->Curve(), &serialized_private_key, + &serialized_public_key)) { + LOG(WARNING) << "Failed to get key pair from key source."; + return false; + } + + // Convert the ephemeral public key to an encoded EC point. + std::unique_ptr ephemeral_public_key = + ECPublicKey::Create(serialized_public_key); + std::string encoded_public_key; + if (!ephemeral_public_key->GetPointEncodedKey(&encoded_public_key)) { + LOG(ERROR) << "Could not encode the public key. "; + return false; + } + + // This condition is just an indication that the serialized_public_key doesn't + // match our size expectations. It shouldn't block the encryption operation. + size_t expected_size = ec_util::GetPublicKeyPointSize(public_key_->Curve()); + if (encoded_public_key.size() != expected_size) { + LOG(WARNING) << "Unexpected key size for public key. " + << "Curve " << public_key_->Curve() << ". Expected " + << expected_size << ". Found " << encoded_public_key.size() + << "."; + } + + std::unique_ptr ephemeral_private_key = + ECPrivateKey::Create(serialized_private_key); + + std::string session_key; + if (!ephemeral_private_key->DeriveSharedSessionKey(*public_key_, + &session_key)) { + LOG(WARNING) << "Failed to derive shared session key."; + return false; + } + + std::string encryption_key; + std::string signing_key; + if (!DeriveWrappingAndSigningKeys(session_key, context, &encryption_key, + &signing_key)) { + return false; + } + + std::string zero_iv(kZeroIv, sizeof(kZeroIv)); + std::string ciphertext = + crypto_util::EncryptAesCbc(encryption_key, zero_iv, plaintext); + if (ciphertext.empty()) { + LOG(WARNING) << "Failed to encrypt plaintext."; + return false; + } + + std::string signature = + crypto_util::CreateSignatureHmacSha256(signing_key, ciphertext); + if (signature.empty()) { + LOG(WARNING) << "Failed to sign plaintext."; + return false; + } + + *ecies_message = encoded_public_key + ciphertext + signature; + + return true; +} + +std::unique_ptr EciesDecryptor::Create( + const std::string& serialized_private_key) { + std::unique_ptr ec_key = + ECPrivateKey::Create(serialized_private_key); + if (ec_key == nullptr) { + return nullptr; + } + std::unique_ptr decryptor( + new EciesDecryptor(std::move(ec_key))); + if (decryptor == nullptr) { + LOG(ERROR) << "Failed to create EciesDecryptor."; + } + return decryptor; +} + +EciesDecryptor::EciesDecryptor(std::unique_ptr private_key) + : private_key_(std::move(private_key)) {} + +bool EciesDecryptor::Decrypt(const std::string& ecies_message, + const std::string& context, + std::string* plaintext) const { + if (plaintext == nullptr) { + LOG(ERROR) << "plaintext cannot be null"; + return false; + } + + // Check the minimum std::string size. + size_t key_size = ec_util::GetPublicKeyPointSize(private_key_->Curve()); + if (key_size + kMacSizeBytes > ecies_message.size()) { + LOG(ERROR) << "The size of the message is too small. Expected > " + << key_size + kMacSizeBytes << ". found " + << ecies_message.size(); + return false; + } + + std::string ciphertext = ecies_message.substr( + key_size, ecies_message.size() - kMacSizeBytes - key_size); + std::string signature = + ecies_message.substr(ecies_message.size() - kMacSizeBytes, kMacSizeBytes); + + std::unique_ptr public_key = ECPublicKey::CreateFromKeyPoint( + private_key_->Curve(), ecies_message.substr(0, key_size)); + if (public_key == nullptr) { + LOG(WARNING) << "Failed to deserialize public key."; + return false; + } + + std::string session_key; + if (!private_key_->DeriveSharedSessionKey(*public_key, &session_key)) { + LOG(WARNING) << "Failed to derive shared session key."; + return false; + } + + std::string encryption_key; + std::string signing_key; + if (!DeriveWrappingAndSigningKeys(session_key, context, &encryption_key, + &signing_key)) { + LOG(WARNING) << "Failed to derive shared wrapping and signing keys."; + return false; + } + + if (!crypto_util::VerifySignatureHmacSha256(signing_key, signature, + ciphertext)) { + LOG(WARNING) << "Failed to verify signature on ciphertext."; + return false; + } + + std::string zero_iv(kZeroIv, sizeof(kZeroIv)); + // In theory, we should be able to decrypt a cipher block that was originally + // the empty string. But DecryptAesCbc uses an empty std::string to indicate an + // error. This means that we can't distinguish between an error and correctly + // decrypted empty string. + *plaintext = crypto_util::DecryptAesCbc(encryption_key, zero_iv, ciphertext); + if (plaintext->empty()) { + LOG(WARNING) << "Failed to decrypt plaintext."; + return false; + } + + return true; +} + +} // namespace widevine. diff --git a/common/ecies_crypto.h b/common/ecies_crypto.h new file mode 100644 index 0000000..f7207ee --- /dev/null +++ b/common/ecies_crypto.h @@ -0,0 +1,82 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef COMMON_ECIES_CRYPTO_H_ +#define COMMON_ECIES_CRYPTO_H_ + +#include +#include + +#include "common/ec_key.h" +#include "common/ec_key_source.h" + +namespace widevine { + +class EciesEncryptor { + public: + static std::unique_ptr Create(const std::string& public_key, + ECKeySource* key_source); + virtual ~EciesEncryptor() = default; + EciesEncryptor(const EciesEncryptor&) = delete; + EciesEncryptor& operator=(const EciesEncryptor&) = delete; + + // Generates an encrypted EC-IES message using the public key, an ephemeral + // private key and context. This function uses AES 256 bit encryption with a + // master key derived from EC shared key generated from the public key and + // ephemeral private key. + // |plaintext| is the value to be encrypted. + // |context| is used as part of the key derivation. + // |ecies_message| is the concatenation of + // 1) the ephemeral public key. + // 2) the plaintext encrypted with the derived AES key using AES CBC, + // PKCS7 padding and a zerio iv. + // 3) The HMAC SHA256 of the cipher text. + // Returns false if there is a problem encrypting the content, true otherwise. + virtual bool Encrypt(const std::string& plaintext, const std::string& context, + std::string* ecies_message) const; + + protected: + // Creates the EciesEncryptor with a given ECKey. This is protected in order + // to support mock tests. + EciesEncryptor(std::unique_ptr public_key, + ECKeySource* key_source); + + private: + std::unique_ptr public_key_; + ECKeySource* key_source_; +}; + +class EciesDecryptor { + public: + static std::unique_ptr Create( + const std::string& serialized_private_key); + + virtual ~EciesDecryptor() = default; + EciesDecryptor(const EciesDecryptor&) = delete; + EciesDecryptor& operator=(const EciesDecryptor&) = delete; + + // Decrypts and verifies an EC-IES message using the private key, the + // ephemeral public key embedded in |ecies_message| and the |context|. + // This function uses a master AES key to decrypt the content and validate the + // signature. The content is encrypted with AES CBC, PKCS7 padded with a + // zero iv. + // |plaintext| will be populated iff decryption is successful and the + // signature is valid. + // Returns false if there is a problem decrypting the content, true otherwise. + virtual bool Decrypt(const std::string& ecies_message, + const std::string& context, + std::string* plaintext) const; + + private: + explicit EciesDecryptor(std::unique_ptr private_key); + std::unique_ptr private_key_; +}; + +} // namespace widevine + +#endif // COMMON_ECIES_CRYPTO_H_ diff --git a/common/ecies_crypto_test.cc b/common/ecies_crypto_test.cc new file mode 100644 index 0000000..783ec1c --- /dev/null +++ b/common/ecies_crypto_test.cc @@ -0,0 +1,258 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#include "common/ecies_crypto.h" + +#include +#include +#include + +#include "testing/gmock.h" +#include "testing/gunit.h" +#include "absl/strings/escaping.h" +#include "common/ec_key.h" +#include "common/ec_key_source.h" +#include "common/ec_test_keys.h" +#include "common/ec_util.h" +#include "common/fake_ec_key_source.h" + +using ::testing::_; +using ::testing::Return; + +namespace { +const size_t kMacSizeBytes = 32; +} // anonymous namespace + +namespace widevine { + +class EciesCryptoTest + : public ::testing::Test, + public ::testing::WithParamInterface< + std::tuple > { + public: + static std::vector< + std::tuple > + GetTestKeyList() { + ECTestKeys test_keys; + + std::vector > + keys({std::make_tuple(test_keys.private_test_key_1_secp521r1(), + test_keys.public_test_key_1_secp521r1(), + test_keys.private_test_key_2_secp521r1(), + test_keys.public_test_key_2_secp521r1()), + std::make_tuple(test_keys.private_test_key_1_secp384r1(), + test_keys.public_test_key_1_secp384r1(), + test_keys.private_test_key_2_secp384r1(), + test_keys.public_test_key_2_secp384r1()), + std::make_tuple(test_keys.private_test_key_1_secp256r1(), + test_keys.public_test_key_1_secp256r1(), + test_keys.private_test_key_2_secp256r1(), + test_keys.public_test_key_2_secp256r1())}); + return keys; + } + + protected: + EciesCryptoTest() { + test_private_key_ = std::get<0>(GetParam()); + test_public_key_ = std::get<1>(GetParam()); + test_ephemeral_private_key_ = std::get<2>(GetParam()); + test_ephemeral_public_key_ = std::get<3>(GetParam()); + private_key_ = ECPrivateKey::Create(test_private_key_); + public_key_ = ECPublicKey::Create(test_public_key_); + ephemeral_private_key_ = ECPrivateKey::Create(test_ephemeral_private_key_); + ephemeral_public_key_ = ECPublicKey::Create(test_ephemeral_public_key_); + } + + std::string test_private_key_; + std::string test_public_key_; + std::string test_ephemeral_private_key_; + std::string test_ephemeral_public_key_; + std::unique_ptr private_key_; + std::unique_ptr public_key_; + std::unique_ptr ephemeral_private_key_; + std::unique_ptr ephemeral_public_key_; +}; + +TEST_P(EciesCryptoTest, EciesEncryptSuccess) { + std::string serialized_public_key; + + const std::string context = "test context"; + const std::string plaintext = "test plaintext"; + + std::string ecies_message; + + // Use the test ephemeral key in the key source. + FakeECKeySource fake_key_source; + ASSERT_TRUE(fake_key_source.SetKey(ephemeral_public_key_->Curve(), + test_ephemeral_private_key_, + test_ephemeral_public_key_)); + std::unique_ptr encryptor = + EciesEncryptor::Create(test_public_key_, &fake_key_source); + + ASSERT_TRUE(encryptor->Encrypt(plaintext, context, &ecies_message)); + EXPECT_TRUE(ecies_message.find(plaintext) == std::string::npos); + + // Verify the decrypted_message. + std::string decrypted_message; + std::unique_ptr decryptor = + EciesDecryptor::Create(test_private_key_); + ASSERT_TRUE(decryptor != nullptr); + ASSERT_TRUE(decryptor->Decrypt(ecies_message, context, &decrypted_message)); + EXPECT_EQ(plaintext, decrypted_message); +} + +TEST_P(EciesCryptoTest, EciesDecryptShortEciesMessage) { + std::unique_ptr decryptor = + EciesDecryptor::Create(test_private_key_); + ASSERT_NE(nullptr, decryptor.get()); + std::string decrypted_message; + std::string short_message( + ec_util::GetPublicKeyPointSize(private_key_->Curve()) + kMacSizeBytes - 1, + 'a'); + + ASSERT_FALSE( + decryptor->Decrypt(short_message, "test context", &decrypted_message)); +} + +TEST_P(EciesCryptoTest, EciesDecryptBadSignature) { + std::string serialized_public_key; + + const std::string context = "test context"; + const std::string plaintext = "test plaintext"; + + std::string ecies_message; + + // Use the test ephemeral key in the key source. + FakeECKeySource fake_key_source; + ASSERT_TRUE(fake_key_source.SetKey(ephemeral_public_key_->Curve(), + test_ephemeral_private_key_, + test_ephemeral_public_key_)); + std::unique_ptr encryptor = + EciesEncryptor::Create(test_public_key_, &fake_key_source); + + ASSERT_TRUE(encryptor->Encrypt(plaintext, context, &ecies_message)); + + // Corrupt the signature (at end of message) and verify that decryption fails. + ecies_message[ecies_message.size() - 1]++; + std::string decrypted_message; + std::unique_ptr decryptor = + EciesDecryptor::Create(test_private_key_); + ASSERT_TRUE(decryptor != nullptr); + ASSERT_FALSE(decryptor->Decrypt(ecies_message, context, &decrypted_message)); + EXPECT_EQ("", decrypted_message); +} + +TEST_P(EciesCryptoTest, EciesEncryptMismatchContext) { + std::string serialized_public_key; + + const std::string context = "test context"; + const std::string bogus_context = "bogus_context"; + const std::string plaintext = "test plaintext"; + + std::string ecies_message; + + // Use the test ephemeral key in the key source. + FakeECKeySource fake_key_source; + ASSERT_TRUE(fake_key_source.SetKey(ephemeral_public_key_->Curve(), + test_ephemeral_private_key_, + test_ephemeral_public_key_)); + std::unique_ptr encryptor = + EciesEncryptor::Create(test_public_key_, &fake_key_source); + ASSERT_TRUE(encryptor != nullptr); + ASSERT_TRUE(encryptor->Encrypt(plaintext, context, &ecies_message)); + EXPECT_GT( + ecies_message.size(), + kMacSizeBytes + ec_util::GetPublicKeyPointSize(public_key_->Curve())); + + // Verify the decrypted_message using the invalid context. + std::string decrypted_message; + std::unique_ptr decryptor = + EciesDecryptor::Create(test_private_key_); + ASSERT_TRUE(decryptor != nullptr); + ASSERT_FALSE( + decryptor->Decrypt(ecies_message, bogus_context, &decrypted_message)); + EXPECT_TRUE(decrypted_message.empty()); +} + +INSTANTIATE_TEST_SUITE_P( + EciesCryptoTest, EciesCryptoTest, + ::testing::ValuesIn(EciesCryptoTest::GetTestKeyList())); + +TEST(EciesEncryptorTest, EciesEncryptBadPublicKey) { + // Use the test ephemeral key in the key source. + FakeECKeySource fake_key_source; + std::unique_ptr encryptor = + EciesEncryptor::Create("bad public key.", &fake_key_source); + ASSERT_TRUE(encryptor == nullptr); +} + +TEST(EciesEncryptorTest, EciesEncryptNullKeySource) { + std::unique_ptr encryptor = + EciesEncryptor::Create("bad public key.", nullptr); + ASSERT_TRUE(encryptor == nullptr); +} + +class MockEcKeySource : public ECKeySource { + public: + MockEcKeySource() = default; + MOCK_METHOD3(GetECKey, + bool(ECPrivateKey::EllipticCurve curve, std::string* private_key, + std::string* public_key)); +}; + +TEST(EciesEncryptorTest, EciesEncryptKeysourceFail) { + MockEcKeySource mock_ec_key_source; + EXPECT_CALL(mock_ec_key_source, GetECKey(_, _, _)).WillOnce(Return(false)); + + ECTestKeys test_keys; + std::unique_ptr encryptor = EciesEncryptor::Create( + test_keys.public_test_key_1_secp256r1(), &mock_ec_key_source); + ASSERT_TRUE(encryptor != nullptr); + std::string ecies_message; + ASSERT_FALSE( + encryptor->Encrypt("test plaintext", "test context", &ecies_message)); +} + +TEST(EciesEncryptorTest, EciesEncryptNullEciesMessage) { + FakeECKeySource fake_key_source; + ECTestKeys test_keys; + std::unique_ptr encryptor = EciesEncryptor::Create( + test_keys.public_test_key_1_secp256r1(), &fake_key_source); + ASSERT_TRUE(encryptor != nullptr); + ASSERT_FALSE(encryptor->Encrypt("test plaintext", "test context", nullptr)); +} + +TEST(EciesDecryptorTest, EciesDecryptBadPrivateKey) { + ECTestKeys test_keys; + std::string invalid_private_key(test_keys.private_test_key_1_secp521r1()); + invalid_private_key[0]++; + + std::unique_ptr decryptor = + EciesDecryptor::Create(invalid_private_key); + ASSERT_TRUE(decryptor == nullptr); +} + +TEST(EciesDecryptorTest, EciesDecryptEmptyEciesMessage) { + ECTestKeys test_keys; + std::unique_ptr decryptor = + EciesDecryptor::Create(test_keys.private_test_key_1_secp521r1()); + ASSERT_NE(nullptr, decryptor); + std::string decrypted_message; + ASSERT_FALSE(decryptor->Decrypt("", "test context", &decrypted_message)); +} + +TEST(EciesDecryptorTest, EciesDecryptNullPlaintext) { + ECTestKeys test_keys; + std::unique_ptr decryptor = + EciesDecryptor::Create(test_keys.private_test_key_1_secp521r1()); + ASSERT_NE(nullptr, decryptor); + std::string decrypted_message; + ASSERT_FALSE(decryptor->Decrypt("foo", "test context", nullptr)); +} + +} // namespace widevine diff --git a/common/fake_ec_key_source.cc b/common/fake_ec_key_source.cc new file mode 100644 index 0000000..16a551f --- /dev/null +++ b/common/fake_ec_key_source.cc @@ -0,0 +1,78 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Implementation of fake EC key generator. Returns precomputed test keys. + +#include "common/fake_ec_key_source.h" + +#include +#include +#include + +#include "glog/logging.h" +#include "common/ec_key.h" +#include "common/ec_test_keys.h" + +namespace widevine { + +FakeECKeySource::FakeECKeySource() { + ECTestKeys ec_test_keys; + CHECK(SetKey(ECPrivateKey::SECP256R1, + ec_test_keys.private_test_key_1_secp256r1(), + ec_test_keys.public_test_key_1_secp256r1())); + + CHECK(SetKey(ECPrivateKey::SECP384R1, + ec_test_keys.private_test_key_1_secp384r1(), + ec_test_keys.public_test_key_1_secp384r1())); + + CHECK(SetKey(ECPrivateKey::SECP521R1, + ec_test_keys.private_test_key_1_secp521r1(), + ec_test_keys.public_test_key_1_secp521r1())); +} + +bool FakeECKeySource::SetKey(ECPrivateKey::EllipticCurve curve, + const std::string& private_key, + const std::string& public_key) { + std::unique_ptr pri_key = ECPrivateKey::Create(private_key); + std::unique_ptr pub_key = ECPublicKey::Create(public_key); + if (pub_key == nullptr || pri_key == nullptr) { + LOG(ERROR) << "public key or private key was null."; + return false; + } + if (!pri_key->MatchesPublicKey(*pub_key)) { + LOG(ERROR) << "Public and private keys do not match."; + return false; + } + if (pri_key->Curve() != curve) { + LOG(ERROR) << "Curve does not match specified curve."; + return false; + } + + test_keys_[curve] = std::make_pair(private_key, public_key); + + return true; +} + +bool FakeECKeySource::GetECKey(ECPrivateKey::EllipticCurve curve, + std::string* private_key, + std::string* public_key) { + CHECK(private_key); + CHECK(public_key); + + const auto keys = test_keys_.find(curve); + if (keys == test_keys_.end()) { + return false; + } + + std::tie(*private_key, *public_key) = keys->second; + + return true; +} + +} // namespace widevine diff --git a/common/fake_ec_key_source.h b/common/fake_ec_key_source.h new file mode 100644 index 0000000..da6f8fe --- /dev/null +++ b/common/fake_ec_key_source.h @@ -0,0 +1,53 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Declaration for fake EC key source used for testing. + +#ifndef COMMON_FAKE_EC_KEY_SOURCE_H_ +#define COMMON_FAKE_EC_KEY_SOURCE_H_ + +#include +#include + +#include "common/ec_key_source.h" +#include "common/ec_test_keys.h" + +namespace widevine { + +// This is a fake implementation of the ECKeySource. It returns the values +// specified in ec_test_keys.h. +class FakeECKeySource : public ECKeySource { + public: + FakeECKeySource(); + FakeECKeySource(const FakeECKeySource&) = delete; + FakeECKeySource& operator=(const FakeECKeySource&) = delete; + + // This method allows a caller to specify which keys to return from GetKeys. + // This overrides the default test keys. + bool SetKey(ECPrivateKey::EllipticCurve curve, const std::string& private_key, + const std::string& public_key); + + // Gets precomputed test keys based on |curve|. + // Parameter |curve| is a standard curve which defines the parameters of the + // key generation. + // Parameter |private_key| is the serialized EC private key. + // Parameter |public_key| is the serialized EC public key. + // Caller retains ownership of all pointers and they cannot be nullptr. + // Returns true if successful, false otherwise. + bool GetECKey(ECPrivateKey::EllipticCurve curve, std::string* private_key, + std::string* public_key) override; + + private: + std::map> + test_keys_; +}; + +} // namespace widevine + +#endif // COMMON_FAKE_EC_KEY_SOURCE_H_ diff --git a/common/file_util_test.cc b/common/file_util_test.cc index 33399c9..97e94cd 100644 --- a/common/file_util_test.cc +++ b/common/file_util_test.cc @@ -19,7 +19,8 @@ TEST(FileUtilTest, EmptyFileName) { } TEST(FileUtilTest, BasicTest) { - const std::string file_path = absl::StrCat("/tmp", "/file_util_test"); + const std::string file_path = + absl::StrCat("/tmp", "/file_util_test"); EXPECT_TRUE(SetContents(file_path, "test content")); std::string contents; EXPECT_TRUE(GetContents(file_path, &contents)); diff --git a/common/keybox_client_cert.cc b/common/keybox_client_cert.cc new file mode 100644 index 0000000..c4f4899 --- /dev/null +++ b/common/keybox_client_cert.cc @@ -0,0 +1,56 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#include "common/keybox_client_cert.h" + +#include "glog/logging.h" +#include "common/crypto_util.h" +#include "common/error_space.h" +#include "common/sha_util.h" +#include "common/signing_key_util.h" +#include "common/wvm_token_handler.h" +#include "protos/public/errors.pb.h" + +namespace widevine { + +Status KeyboxClientCert::Initialize(const std::string& keybox_token) { + system_id_ = WvmTokenHandler::GetSystemId(keybox_token); + serial_number_ = WvmTokenHandler::GetEncryptedUniqueId(keybox_token); + bool insecure_keybox = false; + Status status = WvmTokenHandler::DecryptDeviceKey(keybox_token, &device_key_, + nullptr, &insecure_keybox); + if (!status.ok()) { + Errors new_code = status.error_code() == error::NOT_FOUND + ? MISSING_PRE_PROV_KEY + : KEYBOX_DECRYPT_ERROR; + return Status(error_space, new_code, status.error_message()); + } + return OkStatus(); +} + +Status KeyboxClientCert::VerifySignature( + const std::string& message, const std::string& signature, + ProtocolVersion protocol_version) const { + DCHECK(!signing_key_.empty()); + using crypto_util::VerifySignatureHmacSha256; + if (!VerifySignatureHmacSha256( + GetClientSigningKey(signing_key_, protocol_version), signature, + message)) { + return Status(error_space, INVALID_SIGNATURE, "invalid-keybox-mac"); + } + return OkStatus(); +} + +void KeyboxClientCert::GenerateSigningKey(const std::string& message, + ProtocolVersion protocol_version) { + signing_key_ = crypto_util::DeriveKey( + key(), crypto_util::kSigningKeyLabel, + protocol_version < VERSION_2_2 ? message : Sha512_Hash(message), + SigningKeyMaterialSizeBits(protocol_version)); +} +} // namespace widevine diff --git a/common/keybox_client_cert.h b/common/keybox_client_cert.h new file mode 100644 index 0000000..25d5391 --- /dev/null +++ b/common/keybox_client_cert.h @@ -0,0 +1,66 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef COMMON_KEYBOX_CLIENT_CERT_H_ +#define COMMON_KEYBOX_CLIENT_CERT_H_ + +#include "common/client_cert.h" + +namespace widevine { + +// +class KeyboxClientCert : public ClientCert { + public: + KeyboxClientCert() {} + ~KeyboxClientCert() override {} + KeyboxClientCert(const KeyboxClientCert&) = delete; + KeyboxClientCert& operator=(const KeyboxClientCert&) = delete; + Status Initialize(const std::string& keybox_token); + + Status VerifySignature(const std::string& message, + const std::string& signature, + ProtocolVersion protocol_version) const override; + + void GenerateSigningKey(const std::string& message, + ProtocolVersion protocol_version) override; + + const std::string& encrypted_key() const override { return unimplemented_; } + const std::string& key() const override { return device_key_; } + const std::string& serial_number() const override { return serial_number_; } + const std::string& service_id() const override { return unimplemented_; } + const std::string& signing_key() const override { return signing_key_; } + const std::string& signer_serial_number() const override { + return unimplemented_; + } + uint32_t signer_creation_time_seconds() const override { return 0; } + bool signed_by_provisioner() const override { return false; } + uint32_t system_id() const override { return system_id_; } + widevine::ClientIdentification::TokenType type() const override { + return ClientIdentification::KEYBOX; + } + + // Set the system-wide pre-provisioning keys; argument must be human-readable + // hex digits. + // Must be called before any other method of this class is called, unless + // created by ClientCert::CreateWithPreProvisioningKey(...). + static void SetPreProvisioningKeys( + const std::multimap& keymap); + static bool IsSystemIdKnown(const uint32_t system_id); + static uint32_t GetSystemId(const std::string& keybox_bytes); + + private: + std::string unimplemented_; + std::string device_key_; + uint32_t system_id_; + std::string serial_number_; + std::string signing_key_; +}; + +} // namespace widevine + +#endif // COMMON_KEYBOX_CLIENT_CERT_H_ diff --git a/common/local_ec_key_source.cc b/common/local_ec_key_source.cc new file mode 100644 index 0000000..46517d6 --- /dev/null +++ b/common/local_ec_key_source.cc @@ -0,0 +1,50 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Implementation of local EC key generator. + +#include "common/local_ec_key_source.h" + +#include +#include + +#include "glog/logging.h" +#include "openssl/ec.h" +#include "openssl/nid.h" +#include "common/ec_key.h" +#include "common/ec_util.h" + +using widevine::ec_util::SerializeECPrivateKey; +using widevine::ec_util::SerializeECPublicKey; + +namespace widevine { + +bool LocalECKeySource::GetECKey(ECPrivateKey::EllipticCurve curve, + std::string* private_key, + std::string* public_key) { + DCHECK(private_key); + DCHECK(public_key); + + int nid = ec_util::CurveToNid(curve); + if (nid <= 0) { + return false; + } + bssl::UniquePtr key_pair(EC_KEY_new_by_curve_name(nid)); + if (key_pair == nullptr || !EC_KEY_generate_key(key_pair.get())) { + return false; + } + if (!SerializeECPrivateKey(key_pair.get(), private_key) || + !SerializeECPublicKey(key_pair.get(), public_key)) { + return false; + } + + return true; +} + +} // namespace widevine diff --git a/common/local_ec_key_source.h b/common/local_ec_key_source.h new file mode 100644 index 0000000..e6022e2 --- /dev/null +++ b/common/local_ec_key_source.h @@ -0,0 +1,42 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Class to generate EC keys locally. + +#ifndef COMMON_LOCAL_EC_KEY_SOURCE_H_ +#define COMMON_LOCAL_EC_KEY_SOURCE_H_ + +#include "common/ec_key.h" +#include "common/ec_key_source.h" + +namespace widevine { + +// Class which generates new EC keys. +class LocalECKeySource : public ECKeySource { + public: + LocalECKeySource() = default; + + // Creates an EC key pair using openssl and |curve|. + // Parameter |curve| is a standard curve which defines the parameters of the + // key generation. + // Parameter |private_key| is the serialized EC private key. + // Parameter |public_key| is the serialized EC public key. + // Caller retains ownership of all pointers and they cannot be nullptr. + // Returns true if successful, false otherwise. + bool GetECKey(ECPrivateKey::EllipticCurve curve, std::string* private_key, + std::string* public_key) override; + + private: + LocalECKeySource(const LocalECKeySource&) = delete; + LocalECKeySource& operator=(const LocalECKeySource&) = delete; +}; + +} // namespace widevine + +#endif // COMMON_LOCAL_EC_KEY_SOURCE_H_ diff --git a/common/local_ec_key_source_test.cc b/common/local_ec_key_source_test.cc new file mode 100644 index 0000000..0f23bd3 --- /dev/null +++ b/common/local_ec_key_source_test.cc @@ -0,0 +1,64 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Unit tests for LocalECKeySource. + +#include "common/local_ec_key_source.h" + +#include "testing/gmock.h" +#include "testing/gunit.h" +#include "common/ec_test_keys.h" +#include "common/ec_util.h" + +namespace widevine { + +using ec_util::DeserializeECPrivateKey; +using ec_util::DeserializeECPublicKey; +using ec_util::SerializeECPublicKey; + +class LocalECKeySourceTest : public ::testing::Test { + public: + void ValidateKeyPair(ECPrivateKey::EllipticCurve curve, + const std::string& private_key, + const std::string& public_key) { + EC_KEY* ec_key; + std::string expected_public_key; + + int nid = ec_util::CurveToNid(curve); + ASSERT_GT(nid, 0); + + EXPECT_TRUE(DeserializeECPrivateKey(private_key, &ec_key)); + ASSERT_TRUE(ec_key != nullptr); + EXPECT_EQ(nid, EC_GROUP_get_curve_name(EC_KEY_get0_group(ec_key))); + EXPECT_TRUE(SerializeECPublicKey(ec_key, &expected_public_key)); + EXPECT_EQ(expected_public_key, public_key); + EC_KEY_free(ec_key); + ec_key = nullptr; + EXPECT_TRUE(DeserializeECPublicKey(public_key, &ec_key)); + ASSERT_TRUE(ec_key != nullptr); + EC_KEY_free(ec_key); + } +}; + +TEST_F(LocalECKeySourceTest, GenerateKeys) { + LocalECKeySource key_source; + std::string private_key; + std::string public_key; + + std::vector curves = {ECPrivateKey::SECP256R1, + ECPrivateKey::SECP384R1, + ECPrivateKey::SECP521R1}; + + for (auto curve : curves) { + ASSERT_TRUE(key_source.GetECKey(curve, &private_key, &public_key)); + ValidateKeyPair(curve, private_key, public_key); + } +} + +} // namespace widevine diff --git a/common/mock_rsa_key.h b/common/mock_rsa_key.h index e221785..711cba9 100644 --- a/common/mock_rsa_key.h +++ b/common/mock_rsa_key.h @@ -39,8 +39,8 @@ class MockRsaPublicKey : public RsaPublicKey { MOCK_CONST_METHOD2(Encrypt, bool(const std::string& clear_message, std::string* encrypted_message)); - MOCK_CONST_METHOD2(VerifySignature, - bool(const std::string& message, const std::string& signature)); + MOCK_CONST_METHOD2(VerifySignature, bool(const std::string& message, + const std::string& signature)); MOCK_CONST_METHOD1(MatchesPrivateKey, bool(const RsaPrivateKey& private_key)); MOCK_CONST_METHOD1(MatchesPublicKey, bool(const RsaPublicKey& public_key)); @@ -54,14 +54,16 @@ class MockRsaKeyFactory : public RsaKeyFactory { MockRsaKeyFactory() {} ~MockRsaKeyFactory() override {} - MOCK_CONST_METHOD1(CreateFromPkcs1PrivateKey, - std::unique_ptr(const std::string& private_key)); - MOCK_CONST_METHOD2( - CreateFromPkcs8PrivateKey, - std::unique_ptr(const std::string& private_key, - const std::string& private_key_passphrase)); - MOCK_CONST_METHOD1(CreateFromPkcs1PublicKey, - std::unique_ptr(const std::string& public_key)); + MOCK_CONST_METHOD1( + CreateFromPkcs1PrivateKey, + std::unique_ptr(const std::string& private_key)); + MOCK_CONST_METHOD2(CreateFromPkcs8PrivateKey, + std::unique_ptr( + const std::string& private_key, + const std::string& private_key_passphrase)); + MOCK_CONST_METHOD1( + CreateFromPkcs1PublicKey, + std::unique_ptr(const std::string& public_key)); private: MockRsaKeyFactory(const MockRsaKeyFactory&) = delete; diff --git a/common/openssl_util.h b/common/openssl_util.h index 824aa45..789a36e 100644 --- a/common/openssl_util.h +++ b/common/openssl_util.h @@ -47,6 +47,9 @@ using ScopedOpenSSLStackOnly = using ScopedBIGNUM = ScopedOpenSSLType; using ScopedBIO = ScopedOpenSSLType; +using ScopedECGROUP = ScopedOpenSSLType; +using ScopedECKEY = ScopedOpenSSLType; +using ScopedECPOINT = ScopedOpenSSLType; using ScopedPKCS7 = ScopedOpenSSLType; using ScopedPKEY = ScopedOpenSSLType; using ScopedRSA = ScopedOpenSSLType; diff --git a/common/output_protection_util.cc b/common/output_protection_util.cc new file mode 100644 index 0000000..d677d92 --- /dev/null +++ b/common/output_protection_util.cc @@ -0,0 +1,54 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#include "common/output_protection_util.h" + +namespace widevine { +namespace op_util { + +Status VerifyDeviceCapabilities( + const ClientIdentification::ClientCapabilities& device_capabilities, + const License::KeyContainer::OutputProtection& output_protection, + bool* should_disable_analog_output) { + bool device_has_analog_output = + device_capabilities.analog_output_capabilities() != + ClientIdentification::ClientCapabilities::ANALOG_OUTPUT_NONE && + device_capabilities.analog_output_capabilities() != + ClientIdentification::ClientCapabilities::ANALOG_OUTPUT_UNKNOWN; + + if (should_disable_analog_output != nullptr) { + *should_disable_analog_output = false; + } + if (device_has_analog_output) { + if (output_protection.disable_analog_output() && + !device_capabilities.can_disable_analog_output()) { + return Status(error::PERMISSION_DENIED, + "Analog out cannot be disabled on this device."); + } + if (output_protection.cgms_flags() != + License::KeyContainer::OutputProtection::CGMS_NONE) { + if (device_capabilities.analog_output_capabilities() != + ClientIdentification::ClientCapabilities:: + ANALOG_OUTPUT_SUPPORTS_CGMS_A) { + if (!device_capabilities.can_disable_analog_output()) { + return Status( + error::PERMISSION_DENIED, + "Analog out cannot be disabled on this device, no CGMS."); + } + // This device does not have support for CGMS protections. + if (should_disable_analog_output != nullptr) { + *should_disable_analog_output = true; + } + } + } + } + return OkStatus(); +} + +} // namespace op_util +} // namespace widevine diff --git a/common/output_protection_util.h b/common/output_protection_util.h new file mode 100644 index 0000000..568e8ec --- /dev/null +++ b/common/output_protection_util.h @@ -0,0 +1,31 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef COMMON_OUTPUT_PROTECTION_UTIL_H_ +#define COMMON_OUTPUT_PROTECTION_UTIL_H_ + +#include "common/status.h" +#include "protos/public/client_identification.pb.h" +#include "protos/public/license_protocol.pb.h" + +namespace widevine { +namespace op_util { + +// Verify the device meets the provider's output requirements. Set +// |should_disable_analog_output| to true if device does not meet analog output +// requirements, otherwise |should_disable_analog_error| is false including +// error cases. +Status VerifyDeviceCapabilities( + const ClientIdentification::ClientCapabilities& device_capabilities, + const License::KeyContainer::OutputProtection& output_protection, + bool* should_disable_analog_output); + +} // namespace op_util +} // namespace widevine + +#endif // COMMON_OUTPUT_PROTECTION_UTIL_H_ diff --git a/common/output_protection_util_test.cc b/common/output_protection_util_test.cc new file mode 100644 index 0000000..68e0702 --- /dev/null +++ b/common/output_protection_util_test.cc @@ -0,0 +1,155 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#include "common/output_protection_util.h" + +#include "testing/gmock.h" +#include "testing/gunit.h" + +namespace widevine { +namespace op_util { + +TEST(OutputProtectionUtilTest, AnalogOutputAllowed_DeviceWithoutAnalogOutput) { + License::KeyContainer::OutputProtection output_protection; + output_protection.set_disable_analog_output(false); + ClientIdentification::ClientCapabilities client_capabilities; + client_capabilities.set_analog_output_capabilities( + ClientIdentification::ClientCapabilities::ANALOG_OUTPUT_UNKNOWN); + bool should_disable_output_protection = true; + ASSERT_OK(VerifyDeviceCapabilities(client_capabilities, output_protection, + &should_disable_output_protection)); + EXPECT_FALSE(should_disable_output_protection); +} + +TEST(OutputProtectionUtilTest, + AnalogOutputNotAllowed_DeviceWithoutAnalogOutput) { + License::KeyContainer::OutputProtection output_protection; + output_protection.set_disable_analog_output(true); + ClientIdentification::ClientCapabilities client_capabilities; + client_capabilities.set_analog_output_capabilities( + ClientIdentification::ClientCapabilities::ANALOG_OUTPUT_NONE); + bool should_disable_output_protection = true; + ASSERT_OK(VerifyDeviceCapabilities(client_capabilities, output_protection, + &should_disable_output_protection)); + EXPECT_FALSE(should_disable_output_protection); +} + +TEST(OutputProtectionUtilTest, AnalogOutputNotAllowed_DeviceWithAnalogOutput) { + License::KeyContainer::OutputProtection output_protection; + output_protection.set_disable_analog_output(true); + ClientIdentification::ClientCapabilities client_capabilities; + client_capabilities.set_analog_output_capabilities( + ClientIdentification::ClientCapabilities::ANALOG_OUTPUT_SUPPORTED); + client_capabilities.set_can_disable_analog_output(false); + bool should_disable_output_protection = true; + ASSERT_EQ(error::PERMISSION_DENIED, + VerifyDeviceCapabilities(client_capabilities, output_protection, + &should_disable_output_protection) + .error_code()); + EXPECT_FALSE(should_disable_output_protection); + // Client has the ability to disable it's analog output. + client_capabilities.set_can_disable_analog_output(true); + EXPECT_OK(VerifyDeviceCapabilities(client_capabilities, output_protection, + &should_disable_output_protection)); +} + +TEST(OutputProtectionUtilTest, CGMSRequired) { + License::KeyContainer::OutputProtection output_protection; + output_protection.set_disable_analog_output(false); + output_protection.set_cgms_flags( + License::KeyContainer::OutputProtection::COPY_NEVER); + ClientIdentification::ClientCapabilities client_capabilities; + client_capabilities.set_analog_output_capabilities( + ClientIdentification::ClientCapabilities::ANALOG_OUTPUT_SUPPORTED); + bool should_disable_output_protection = true; + EXPECT_EQ(error::PERMISSION_DENIED, + VerifyDeviceCapabilities(client_capabilities, output_protection, + &should_disable_output_protection) + .error_code()); + client_capabilities.set_analog_output_capabilities( + ClientIdentification::ClientCapabilities::ANALOG_OUTPUT_SUPPORTS_CGMS_A); + ASSERT_OK(VerifyDeviceCapabilities(client_capabilities, output_protection, + &should_disable_output_protection)); + EXPECT_FALSE(should_disable_output_protection); +} +// TODO(user): enable this test. +TEST(OutputProtectionUtilTest, DISABLED_VerifyDeviceHdcpCapabilities) { + ClientIdentification::ClientCapabilities device_capabilities; + License::KeyContainer::OutputProtection required; + + // Device capabilities was not specified by the client. + device_capabilities.clear_max_hdcp_version(); + required.set_hdcp(License::KeyContainer::OutputProtection::HDCP_NONE); + bool should_disable_output_protection; + EXPECT_OK(VerifyDeviceCapabilities(device_capabilities, required, + &should_disable_output_protection)); + required.set_hdcp(License::KeyContainer::OutputProtection::HDCP_V1); + EXPECT_OK(VerifyDeviceCapabilities(device_capabilities, required, + &should_disable_output_protection)); + required.set_hdcp(License::KeyContainer::OutputProtection::HDCP_V2); + EXPECT_OK(VerifyDeviceCapabilities(device_capabilities, required, + &should_disable_output_protection)); + required.set_hdcp(License::KeyContainer::OutputProtection::HDCP_V2_1); + EXPECT_OK(VerifyDeviceCapabilities(device_capabilities, required, + &should_disable_output_protection)); + required.set_hdcp(License::KeyContainer::OutputProtection::HDCP_V2_2); + EXPECT_OK(VerifyDeviceCapabilities(device_capabilities, required, + &should_disable_output_protection)); + required.set_hdcp( + License::KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT); + EXPECT_OK(VerifyDeviceCapabilities(device_capabilities, required, + &should_disable_output_protection)); + + // Device capabilities are too low for any HDCP. Only fail if the required + // protection is HDCP_NO_DIGITAL_OUTPUT. + device_capabilities.set_max_hdcp_version( + ClientIdentification::ClientCapabilities::HDCP_NONE); + required.set_hdcp(License::KeyContainer::OutputProtection::HDCP_NONE); + EXPECT_OK(VerifyDeviceCapabilities(device_capabilities, required, + &should_disable_output_protection)); + required.set_hdcp(License::KeyContainer::OutputProtection::HDCP_V1); + EXPECT_OK(VerifyDeviceCapabilities(device_capabilities, required, + &should_disable_output_protection)); + required.set_hdcp(License::KeyContainer::OutputProtection::HDCP_V2); + EXPECT_OK(VerifyDeviceCapabilities(device_capabilities, required, + &should_disable_output_protection)); + required.set_hdcp( + License::KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT); + EXPECT_EQ(error::PERMISSION_DENIED, + VerifyDeviceCapabilities(device_capabilities, required, + &should_disable_output_protection) + .error_code()); + + // Device does not have any digital output ports. In this case, the CDM + // cannot enforce this situation. For now, allow all HDCP requests, the KCB + // will enforce the HDCP settings. + device_capabilities.set_max_hdcp_version( + ClientIdentification::ClientCapabilities::HDCP_NO_DIGITAL_OUTPUT); + required.set_hdcp(License::KeyContainer::OutputProtection::HDCP_NONE); + EXPECT_OK(VerifyDeviceCapabilities(device_capabilities, required, + &should_disable_output_protection)); + required.set_hdcp(License::KeyContainer::OutputProtection::HDCP_V1); + EXPECT_OK(VerifyDeviceCapabilities(device_capabilities, required, + &should_disable_output_protection)); + required.set_hdcp(License::KeyContainer::OutputProtection::HDCP_V2); + EXPECT_OK(VerifyDeviceCapabilities(device_capabilities, required, + &should_disable_output_protection)); + required.set_hdcp(License::KeyContainer::OutputProtection::HDCP_V2_1); + EXPECT_OK(VerifyDeviceCapabilities(device_capabilities, required, + &should_disable_output_protection)); + required.set_hdcp(License::KeyContainer::OutputProtection::HDCP_V2_2); + EXPECT_OK(VerifyDeviceCapabilities(device_capabilities, required, + &should_disable_output_protection)); + required.set_hdcp( + License::KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT); + EXPECT_OK(VerifyDeviceCapabilities(device_capabilities, required, + &should_disable_output_protection)); +} + +} // namespace op_util +} // namespace widevine diff --git a/common/private_key_util.h b/common/private_key_util.h new file mode 100644 index 0000000..9b12ec1 --- /dev/null +++ b/common/private_key_util.h @@ -0,0 +1,84 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Shared private key utilities between RSA and EC. + +#ifndef COMMON_PRIVATE_KEY_UTIL_H_ +#define COMMON_PRIVATE_KEY_UTIL_H_ + +#include + +#include "glog/logging.h" +#include "openssl/bio.h" + +namespace widevine { +namespace private_key_util { + +template +bool SerializeKey(const Key* key, int (*serialization_func)(BIO*, Key*), + std::string* serialized_key) { + if (key == nullptr) { + LOG(ERROR) << "Key is nullptr."; + return false; + } + if (serialized_key == nullptr) { + LOG(ERROR) << "Pointer to hold serialized key is nullptr."; + return false; + } + BIO* bio = BIO_new(BIO_s_mem()); + if (bio == nullptr) { + LOG(ERROR) << "BIO_new returned nullptr"; + return false; + } + bool success = false; + if (serialization_func(bio, const_cast(key)) != 0) { + int serialized_size = BIO_pending(bio); + serialized_key->assign(serialized_size, 0); + if (BIO_read(bio, &(*serialized_key)[0], serialized_size) == + serialized_size) { + success = true; + } else { + LOG(ERROR) << "BIO_read failure"; + } + } else { + LOG(ERROR) << "Key serialization failure"; + } + BIO_free(bio); + return success; +} + +template +bool DeserializeKey(const std::string& serialized_key, + Key* (*deserialization_func)(BIO*, Key**), Key** key) { + if (serialized_key.empty()) { + LOG(ERROR) << "Serialized key is empty."; + return false; + } + if (key == nullptr) { + LOG(ERROR) << "Pointer to hold new key is nullptr."; + return false; + } + BIO* bio = BIO_new_mem_buf(const_cast(serialized_key.data()), + serialized_key.size()); + if (bio == nullptr) { + LOG(ERROR) << "BIO_new_mem_buf returned nullptr"; + return false; + } + *key = deserialization_func(bio, nullptr); + BIO_free(bio); + if (*key == nullptr) { + LOG(ERROR) << "Key deserialization failure"; + } + return *key != nullptr; +} + +} // namespace private_key_util +} // namespace widevine + +#endif // COMMON_PRIVATE_KEY_UTIL_H_ diff --git a/common/remote_attestation_verifier.cc b/common/remote_attestation_verifier.cc index 5d9f4c0..a9daa9d 100644 --- a/common/remote_attestation_verifier.cc +++ b/common/remote_attestation_verifier.cc @@ -185,7 +185,8 @@ Status RemoteAttestationVerifier::VerifyRemoteAttestation( Status RemoteAttestationVerifier::VerifyRemoteAttestation( const std::string& message, const RemoteAttestation& remote_attestation, - const ClientIdentification& client_id, std::string* remote_attestation_cert_sn) { + const ClientIdentification& client_id, + std::string* remote_attestation_cert_sn) { if (!client_id.has_token()) { return (Status(error_space, INVALID_MESSAGE, "remote-attestation-token-missing")); @@ -217,9 +218,10 @@ Status RemoteAttestationVerifier::VerifyRemoteAttestation( status = ca_->VerifyCertChain(*cert_chain); ca_mutex_.ReaderUnlock(); if (!status.ok()) { - return (Status(error_space, REMOTE_ATTESTATION_FAILED, - std::string("remote-attestation-cert-chain-validation-failed: ") + - status.error_message())); + return (Status( + error_space, REMOTE_ATTESTATION_FAILED, + std::string("remote-attestation-cert-chain-validation-failed: ") + + status.error_message())); } // Verify the remote attestation signature. std::unique_ptr leaf_key; diff --git a/common/remote_attestation_verifier.h b/common/remote_attestation_verifier.h index 40462ce..d32db1d 100644 --- a/common/remote_attestation_verifier.h +++ b/common/remote_attestation_verifier.h @@ -16,7 +16,6 @@ #include #include -#include "base/macros.h" #include "base/thread_annotations.h" #include "absl/synchronization/mutex.h" #include "common/status.h" @@ -33,6 +32,11 @@ namespace widevine { class RemoteAttestationVerifier { public: RemoteAttestationVerifier() : enable_test_certificates_(false) {} + + RemoteAttestationVerifier(const RemoteAttestationVerifier&) = delete; + RemoteAttestationVerifier& operator=(const RemoteAttestationVerifier&) = + delete; + virtual ~RemoteAttestationVerifier() {} // Singleton accessor. @@ -82,9 +86,7 @@ class RemoteAttestationVerifier { bool enable_test_certificates_; absl::Mutex ca_mutex_; - std::unique_ptr ca_ GUARDED_BY(ca_mutex_); - - DISALLOW_COPY_AND_ASSIGN(RemoteAttestationVerifier); + std::unique_ptr ca_ ABSL_GUARDED_BY(ca_mutex_); }; } // namespace widevine diff --git a/common/rot_id_generator.cc b/common/rot_id_generator.cc new file mode 100644 index 0000000..da48749 --- /dev/null +++ b/common/rot_id_generator.cc @@ -0,0 +1,107 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Classes for generating and decrypting the root of trust id which is +// included in generated DRM Certificates. + +#include "common/rot_id_generator.h" + +#include + +#include "glog/logging.h" +#include "absl/strings/str_cat.h" +#include "common/crypto_util.h" +#include "common/ec_key.h" +#include "common/rot_id_util.h" +#include "common/sha_util.h" +#include "common/status.h" +#include "protos/public/drm_certificate.pb.h" + +namespace { + +constexpr char kRotIdLabel[] = "ROOT_OF_TRUST_ID ENCRYPTION LABEL"; +constexpr int32_t kKeyId = 0; + +std::string GenerateContext(uint32_t system_id) { + return absl::StrCat(kRotIdLabel, system_id); +} + +} // anonymous namespace + +namespace widevine { + +Status RootOfTrustIdGenerator::Generate(uint32_t system_id, + const std::string& unique_id, + RootOfTrustId* root_of_trust_id) const { + CHECK(root_of_trust_id != nullptr) << "root_of_trust_id was null."; + + if (system_id == 0) { + return Status(error::INVALID_ARGUMENT, "system id should not be 0."); + } + if (unique_id.empty()) { + return Status(error::INVALID_ARGUMENT, + "The unique id should not be empty."); + } + + root_of_trust_id->set_version( + widevine::RootOfTrustId::ROOT_OF_TRUST_ID_VERSION_1); + root_of_trust_id->set_key_id(kKeyId); + if (!ecies_encryptor_->Encrypt( + unique_id, GenerateContext(system_id), + root_of_trust_id->mutable_encrypted_unique_id())) { + root_of_trust_id->Clear(); + return Status(error::INTERNAL, "Encrypt failed."); + } + + std::string unique_id_hash = GenerateUniqueIdHash(unique_id); + if (unique_id_hash.empty()) { + root_of_trust_id->Clear(); + return Status(error::INTERNAL, "Could not generate unique id hash."); + } + + root_of_trust_id->set_unique_id_hash(GenerateRotIdHash( + root_of_trust_id->encrypted_unique_id(), system_id, unique_id_hash)); + if (root_of_trust_id->unique_id_hash().empty()) { + root_of_trust_id->Clear(); + return Status(error::INTERNAL, "Failed to generate revoked id hash."); + } + + return OkStatus(); +} + +std::string RootOfTrustIdGenerator::GenerateUniqueIdHash( + const std::string& unique_id) const { + if (unique_id.empty()) { + LOG(WARNING) << "unique_id should not be empty."; + return ""; + } + return Sha256_Hash(absl::StrCat(unique_id, wv_shared_salt_)); +} + +Status RootOfTrustIdDecryptor::DecryptUniqueId( + uint32_t system_id, const std::string& rot_encrypted_id, + std::string* unique_id) const { + CHECK(unique_id != nullptr) << "unique_id was null."; + if (system_id == 0) { + return Status(error::INVALID_ARGUMENT, "system id should not be 0."); + } + if (rot_encrypted_id.empty()) { + return Status(error::INVALID_ARGUMENT, + "The rot_encrypted_id should not be empty."); + } + + if (!ecies_decryptor_->Decrypt(rot_encrypted_id, GenerateContext(system_id), + unique_id)) { + return Status(error::INTERNAL, "Failed to decrypt rot_encrypted_id"); + } + + return OkStatus(); +} + +} // namespace widevine diff --git a/common/rot_id_generator.h b/common/rot_id_generator.h new file mode 100644 index 0000000..cece136 --- /dev/null +++ b/common/rot_id_generator.h @@ -0,0 +1,91 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Classes for generating and decrypting the root of trust id which is +// included in generated DRM Certificates. + +#ifndef COMMON_ROT_ID_GENERATOR_H_ +#define COMMON_ROT_ID_GENERATOR_H_ + +#include +#include + +#include +#include "common/ecies_crypto.h" +#include "common/rot_id_util.h" +#include "common/status.h" +#include "protos/public/drm_certificate.pb.h" + +namespace widevine { + +// The RootOfTrustIdGenerator is used to create a root of trust id that is +// associated with the keybox or OEM X.509 certificate that was used as +// authentication during provisioning. +class RootOfTrustIdGenerator { + public: + // The constructor for creating the RootOfTrustIdGenerator. |ecies_encryptor| + // is used to encrypt the unique identifier. It must not be null. The + // |wv_shared_salt| is a secret salt used in creating the hash + // component of the Root of Trust Id. The secret salt is stored securely. The + // same value is used as part of generating all Root of Trust Ids. + // The |wv_shared_salt| is also used for creating the unique id hash + // values. The unique id hash values identify revoked devices and are + // published in the DCSL and consumed by the License SDK. + RootOfTrustIdGenerator(std::unique_ptr ecies_encryptor, + std::string wv_shared_salt) + : ecies_encryptor_(std::move(ecies_encryptor)), + wv_shared_salt_(std::move(wv_shared_salt)) {} + + virtual ~RootOfTrustIdGenerator() {} + + // Creates the root of trust identifier. This fills the fields of the + // |root_of_trust_id|. The fields are generated per the spec at go/wv-kb-id. + // The |system_id| is required and must match the system id to which the + // |unique_id| belongs. + // Returns INVALID_ARGUMENT if |system_id| is 0, |unique_id| is empty. + // |roof_of_trust_id| is owned by the caller and must not be null. + virtual Status Generate(uint32_t system_id, const std::string& unique_id, + RootOfTrustId* root_of_trust_id) const; + + // Generates the hash of the |unique_id| per the spec in go/wv-kb-id. This + // unique id hash (aka inner hash) is the identifier used when revoking an + // individual device. The result of this hash is one of the values used to + // generate Root of Trust Id Hash. + // |unique_id| should not be empty. If it is, an empty hash + // is returned. + std::string GenerateUniqueIdHash(const std::string& unique_id) const; + + private: + std::unique_ptr ecies_encryptor_; + std::string wv_shared_salt_; +}; + +// The RootOfTrustIdDecryptor is used to decrypt the root of trust id. It +// requires an |ecies_decryptor| which must use the private key that matches +// the public key used with the RootOfTrustIdGenerator. |ecies_decryptor| +// must not be null. The RootOfTrustIdDecryptor will take ownership. +class RootOfTrustIdDecryptor { + public: + explicit RootOfTrustIdDecryptor( + std::unique_ptr ecies_decryptor) + : ecies_decryptor_(std::move(ecies_decryptor)) {} + + // Decrypts the |rot_encrypted_id| using the |system_id| as part of the + // context. |unique_id| contains the decrypted value on success. + // |rot_encrypted_id| must not be empty. |unique_id| must not be null. + // Returns true on success, false on failure. + Status DecryptUniqueId(uint32_t system_id, const std::string& rot_encrypted_id, + std::string* unique_id) const; + + private: + std::unique_ptr ecies_decryptor_; +}; + +} // namespace widevine +#endif // COMMON_ROT_ID_GENERATOR_H_ diff --git a/common/rot_id_generator_test.cc b/common/rot_id_generator_test.cc new file mode 100644 index 0000000..138dee9 --- /dev/null +++ b/common/rot_id_generator_test.cc @@ -0,0 +1,258 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Unit tests for RootOfTrustIdGenerator and RootO +#include "common/rot_id_generator.h" + +#include + +#include "google/protobuf/util/message_differencer.h" +#include "testing/gmock.h" +#include "testing/gunit.h" +#include "absl/strings/escaping.h" +#include "common/ec_key.h" +#include "common/ec_test_keys.h" +#include "common/ecies_crypto.h" +#include "common/fake_ec_key_source.h" +#include "common/rot_id_util.h" +#include "common/status.h" +#include "protos/public/drm_certificate.pb.h" + +using ::testing::_; +using ::testing::NotNull; +using ::testing::Return; + +namespace { +constexpr char kTestSharedSalt[] = "test shared salt"; +constexpr char kTestUniqueId[] = "test unique id"; +constexpr uint32_t kTestSystemId = 1234; +constexpr char kExpectedRotIdEncryptedIdHex[] = + "048aea3f16ff24a9bf03283015ee52509a551c60c7a7cc4b995b4055ce4619d4d45efde068" + "27ea78f3071f024a785244d3dfbeac5fa51c8a498da65aaca1252bd1f174166d9645b6ecd6" + "669aa0afb7b766682c02f794d67050f779ebd104a767bedef288be13d321ae79d7209b5cd3" + "4698"; + +constexpr char kExpectedRotIdHashHex[] = + "2c4dbc37092ab3e55897639d4f0dd3c824001d07eb69a3f9e6db3b846bf31828"; + +} // anonymous namespace + +namespace widevine { + +class RootOfTrustIdGeneratorTest : public ::testing::Test { + public: + RootOfTrustIdGeneratorTest() { + expected_root_of_trust_id_.set_encrypted_unique_id( + absl::HexStringToBytes(kExpectedRotIdEncryptedIdHex)); + expected_root_of_trust_id_.set_unique_id_hash( + absl::HexStringToBytes(kExpectedRotIdHashHex)); + expected_root_of_trust_id_.set_key_id(0); + expected_root_of_trust_id_.set_version( + RootOfTrustId::ROOT_OF_TRUST_ID_VERSION_1); + } + + std::unique_ptr CreateEncryptor() { + return EciesEncryptor::Create(test_keys_.public_test_key_2_secp256r1(), + &fake_ec_key_source_); + } + std::unique_ptr CreateDecryptor() { + return EciesDecryptor::Create(test_keys_.private_test_key_2_secp256r1()); + } + + protected: + ECTestKeys test_keys_; + FakeECKeySource fake_ec_key_source_; + RootOfTrustId expected_root_of_trust_id_; +}; + +class MockEciesEncryptor : public EciesEncryptor { + public: + static MockEciesEncryptor* Create() { + ECTestKeys test_keys; + std::unique_ptr ec_key = + ECPublicKey::Create(test_keys.public_test_key_1_secp256r1()); + return new MockEciesEncryptor(std::move(ec_key)); + } + MOCK_CONST_METHOD3(Encrypt, bool(const std::string&, const std::string&, + std::string*)); + + private: + explicit MockEciesEncryptor(std::unique_ptr ec_key) + : EciesEncryptor(std::move(ec_key), &fake_ec_key_source_) {} + + FakeECKeySource fake_ec_key_source_; +}; + +TEST_F(RootOfTrustIdGeneratorTest, GenerateIdSuccess) { + RootOfTrustIdGenerator generator(CreateEncryptor(), kTestSharedSalt); + RootOfTrustIdDecryptor decryptor(CreateDecryptor()); + + // Generate the root of trust id. + RootOfTrustId root_of_trust_id; + ASSERT_OK( + generator.Generate(kTestSystemId, kTestUniqueId, &root_of_trust_id)); + EXPECT_TRUE(google::protobuf::util::MessageDifferencer::Equals( + expected_root_of_trust_id_, root_of_trust_id)); + + // Verify decrypted unique id. + std::string decrypted_unique_id; + EXPECT_OK(decryptor.DecryptUniqueId(kTestSystemId, + root_of_trust_id.encrypted_unique_id(), + &decrypted_unique_id)); + EXPECT_EQ(kTestUniqueId, decrypted_unique_id); + + // Verify hashed unique id matches. + std::string unique_id_hash = generator.GenerateUniqueIdHash(kTestUniqueId); + EXPECT_TRUE(IsRotIdRevoked(root_of_trust_id.encrypted_unique_id(), + kTestSystemId, root_of_trust_id.unique_id_hash(), + {unique_id_hash})); +} + +TEST_F(RootOfTrustIdGeneratorTest, GenerateIdUniqueSuccess) { + RootOfTrustIdGenerator generator(CreateEncryptor(), kTestSharedSalt); + RootOfTrustIdDecryptor decryptor(CreateDecryptor()); + + std::string rot_encrypted_id; + std::string rot_id_hash; + + // Generate the root of trust id. + RootOfTrustId root_of_trust_id; + ASSERT_OK( + generator.Generate(kTestSystemId, kTestUniqueId, &root_of_trust_id)); + EXPECT_TRUE(google::protobuf::util::MessageDifferencer::Equals( + expected_root_of_trust_id_, root_of_trust_id)); + + // Generate a second root of trust id for the same unique id. + // This must generate a different looking id. + // First, assign a new ephemeral to the fake key source. + fake_ec_key_source_.SetKey(ECPrivateKey::SECP256R1, + test_keys_.private_test_key_2_secp256r1(), + test_keys_.public_test_key_2_secp256r1()); + RootOfTrustId second_root_of_trust_id; + ASSERT_OK(generator.Generate(kTestSystemId, kTestUniqueId, + &second_root_of_trust_id)); + EXPECT_FALSE(second_root_of_trust_id.encrypted_unique_id().empty()); + EXPECT_NE(kTestUniqueId, second_root_of_trust_id.encrypted_unique_id()); + EXPECT_FALSE(second_root_of_trust_id.unique_id_hash().empty()); + + // Verify that the second id does not equal the first. + EXPECT_NE(root_of_trust_id.encrypted_unique_id(), + second_root_of_trust_id.encrypted_unique_id()); + EXPECT_NE(root_of_trust_id.unique_id_hash(), + second_root_of_trust_id.unique_id_hash()); + + // Verify second decrypted unique id. + std::string decrypted_unique_id; + EXPECT_OK(decryptor.DecryptUniqueId( + kTestSystemId, second_root_of_trust_id.encrypted_unique_id(), + &decrypted_unique_id)); + EXPECT_EQ(kTestUniqueId, decrypted_unique_id); + + // Verify hashed unique id matches. + std::string unique_id_hash = generator.GenerateUniqueIdHash(kTestUniqueId); + EXPECT_TRUE(IsRotIdRevoked( + second_root_of_trust_id.encrypted_unique_id(), kTestSystemId, + second_root_of_trust_id.unique_id_hash(), {unique_id_hash})); +} + +TEST_F(RootOfTrustIdGeneratorTest, GenerateIdFailedEncryption) { + MockEciesEncryptor* mock_encryptor = MockEciesEncryptor::Create(); + ASSERT_THAT(mock_encryptor, NotNull()); + RootOfTrustIdGenerator generator( + std::unique_ptr(mock_encryptor), kTestSharedSalt); + + EXPECT_CALL(*mock_encryptor, Encrypt(_, _, _)) + .Times(1) + .WillOnce(Return(false)) + .RetiresOnSaturation(); + + std::string rot_encrypted_id; + std::string rot_id_hash; + + // Attempt to generate the root of trust id. + RootOfTrustId root_of_trust_id; + EXPECT_EQ(error::INTERNAL, + generator.Generate(kTestSystemId, kTestUniqueId, &root_of_trust_id) + .error_code()); + EXPECT_TRUE(google::protobuf::util::MessageDifferencer::Equals( + RootOfTrustId::default_instance(), root_of_trust_id)); +} + +TEST_F(RootOfTrustIdGeneratorTest, GenerateIdEmptyIdFail) { + RootOfTrustIdGenerator generator(CreateEncryptor(), kTestSharedSalt); + RootOfTrustId root_of_trust_id; + + // Should fail because the id is blank. + EXPECT_EQ( + error::INVALID_ARGUMENT, + generator.Generate(kTestSystemId, "", &root_of_trust_id).error_code()); + EXPECT_TRUE(google::protobuf::util::MessageDifferencer::Equals( + RootOfTrustId::default_instance(), root_of_trust_id)); +} + +TEST_F(RootOfTrustIdGeneratorTest, GenerateIdNullRotIdFail) { + RootOfTrustIdGenerator generator(CreateEncryptor(), kTestSharedSalt); + + // Should fail because the id is blank. + EXPECT_DEATH(generator.Generate(kTestSystemId, kTestUniqueId, + nullptr /* root of trust id*/), + "root_of_trust_id"); +} + +TEST_F(RootOfTrustIdGeneratorTest, DecryptorSystemIdMismatchFails) { + RootOfTrustIdGenerator generator(CreateEncryptor(), kTestSharedSalt); + RootOfTrustIdDecryptor decryptor(CreateDecryptor()); + + // Generate the root of trust id. + RootOfTrustId root_of_trust_id; + ASSERT_OK( + generator.Generate(kTestSystemId, kTestUniqueId, &root_of_trust_id)); + EXPECT_TRUE(google::protobuf::util::MessageDifferencer::Equals( + expected_root_of_trust_id_, root_of_trust_id)); + + // Attempt to decrypt with different system id. Should fail. + std::string decrypted_unique_id; + EXPECT_EQ(error::INTERNAL, + decryptor + .DecryptUniqueId(kTestSystemId + 1, + root_of_trust_id.encrypted_unique_id(), + &decrypted_unique_id) + .error_code()); +} + +TEST_F(RootOfTrustIdGeneratorTest, DecryptorBlankUniqueId) { + RootOfTrustIdDecryptor decryptor(CreateDecryptor()); + + // Attempt to decrypt empty encrypted id. + std::string decrypted_unique_id; + EXPECT_EQ(error::INVALID_ARGUMENT, + decryptor.DecryptUniqueId(kTestSystemId, "", &decrypted_unique_id) + .error_code()); +} + +TEST_F(RootOfTrustIdGeneratorTest, DecryptorSystemIdNullDecryptedIdFails) { + RootOfTrustIdGenerator generator(CreateEncryptor(), kTestSharedSalt); + RootOfTrustIdDecryptor decryptor(CreateDecryptor()); + + // Generate the root of trust id. + RootOfTrustId root_of_trust_id; + ASSERT_OK( + generator.Generate(kTestSystemId, kTestUniqueId, &root_of_trust_id)); + EXPECT_TRUE(google::protobuf::util::MessageDifferencer::Equals( + expected_root_of_trust_id_, root_of_trust_id)); + + // Attempt to decrypt with a nullptr for the decrypted id. + std::string decrypted_unique_id; + EXPECT_DEATH( + decryptor.DecryptUniqueId( + kTestSystemId, root_of_trust_id.encrypted_unique_id(), nullptr), + "unique_id"); +} + +} // namespace widevine diff --git a/common/rot_id_util.cc b/common/rot_id_util.cc new file mode 100644 index 0000000..aba65e5 --- /dev/null +++ b/common/rot_id_util.cc @@ -0,0 +1,51 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Helper methods for the Root of Trust Id. + +#include "common/rot_id_util.h" + +#include + +#include "glog/logging.h" +#include "absl/strings/str_cat.h" +#include "common/crypto_util.h" +#include "common/ec_key.h" +#include "common/sha_util.h" + +namespace widevine { + +bool IsRotIdRevoked(const std::string& encrypted_unique_id, uint32_t system_id, + const std::string& rot_id_hash, + const std::vector& revoked_ids) { + // This could conceivably happen for legacy DRM certificates without a ROT id. + // No need to match if there's nothing to match against. + if (encrypted_unique_id.empty() || rot_id_hash.empty()) { + return false; + } + + for (const auto& revoked_id : revoked_ids) { + std::string revoked_hash = + GenerateRotIdHash(encrypted_unique_id, system_id, revoked_id); + if (rot_id_hash == revoked_hash) { + return true; + } + } + return false; +} + +std::string GenerateRotIdHash(const std::string& salt, uint32_t system_id, + const std::string& unique_id_hash) { + if (salt.empty() || unique_id_hash.empty()) { + return ""; + } + return Sha256_Hash(absl::StrCat(salt, system_id, unique_id_hash)); +} + +} // namespace widevine diff --git a/common/rot_id_util.h b/common/rot_id_util.h new file mode 100644 index 0000000..ca1fe5a --- /dev/null +++ b/common/rot_id_util.h @@ -0,0 +1,45 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Helper methods for the Root of Trust Id. + +#ifndef COMMON_ROT_ID_UTIL_H_ +#define COMMON_ROT_ID_UTIL_H_ + +#include +#include + +#include +#include "common/ec_key.h" +#include "common/local_ec_key_source.h" + +namespace widevine { + +// Helper function that compares the |rot_id_hash| to a hash of each of the +// |revoked_ids|. The |revoked_ids| are the unique id hash (aka inner hash) +// values as defined in the spec at go/wv-kb-id. The |encrypted_unique_id| and +// |system_id| are used to compute the hash of each of the |revoked_ids|. +// Returns true if any of the revoked_ids match. +bool IsRotIdRevoked(const std::string& encrypted_unique_id, uint32_t system_id, + const std::string& rot_id_hash, + const std::vector& revoked_ids); + +// Helper function that generates the hash for the ROT id from the +// |unique_id_hash|, the |system_id| and the |salt|. |salt| is typically an +// encrypted unique id. Since we use an ephemeral eliptic curve key as part of +// the encrypted unique id, the value is effectively random can be used as a +// salt. +// Returns the hash value on success. +// If |salt| or |unique_id_hash| are empty, this will return an empty +// string. +std::string GenerateRotIdHash(const std::string& salt, uint32_t system_id, + const std::string& unique_id_hash); + +} // namespace widevine +#endif // COMMON_ROT_ID_UTIL_H_ diff --git a/common/rot_id_util_test.cc b/common/rot_id_util_test.cc new file mode 100644 index 0000000..050bed0 --- /dev/null +++ b/common/rot_id_util_test.cc @@ -0,0 +1,66 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Unit tests for the rot_id_util helper methods. + +#include "common/rot_id_util.h" + +#include "testing/gmock.h" +#include "testing/gunit.h" +#include "absl/strings/escaping.h" + +namespace { +constexpr char kFakeEncryptedId[] = "fake encrypted id"; +constexpr char kFakeUniqueIdHash[] = "fake unique_id hash"; + +// This is the ROT ID Hash generated from the fake values. +constexpr char kRotIdHashHex[] = + "0a757dde0f1080b60f34bf8e46af573ce987b5ed1c831b44952e2feed5243a95"; + +constexpr uint32_t kFakeSystemId = 1234; +constexpr uint32_t kOtherFakeSystemId = 9876; + +} // anonymous namespace + +namespace widevine { + +TEST(RotIdUtilTest, IsRotIdRevokedMatches) { + ASSERT_TRUE(IsRotIdRevoked(kFakeEncryptedId, kFakeSystemId, + absl::HexStringToBytes(kRotIdHashHex), + {"NO MATCH UNIQUE ID HASH 1", kFakeUniqueIdHash})); +} + +TEST(RotIdUtilTest, IsRotIdRevokedNoMatchSystemId) { + ASSERT_FALSE( + IsRotIdRevoked(kFakeEncryptedId, kOtherFakeSystemId, + absl::HexStringToBytes(kRotIdHashHex), + {"NO MATCH UNIQUE ID HASH 1", kFakeUniqueIdHash})); +} + +TEST(RotIdUtilTest, IsRotIdRevokedNoMatch) { + ASSERT_FALSE(IsRotIdRevoked( + kFakeEncryptedId, kFakeSystemId, kFakeUniqueIdHash, + {"NO MATCH UNIQUE ID HASH 1", "NO MATCH UNIQUE ID HASH 2"})); +} + +TEST(RotIdUtilTest, IsRotIdRevokedEmptyList) { + ASSERT_FALSE(IsRotIdRevoked(kFakeEncryptedId, kFakeSystemId, + kFakeUniqueIdHash, + {/* Intentionally empty vector */})); +} + +// This test really only ensures the stability of the implementation. If the +// hash ever changes, then it will introduce problems into the ecosystem. +TEST(RotIdUtilTest, GenerateRotIdHashSuccess) { + ASSERT_EQ( + absl::HexStringToBytes(kRotIdHashHex), + GenerateRotIdHash(kFakeEncryptedId, kFakeSystemId, kFakeUniqueIdHash)); +} + +} // namespace widevine diff --git a/common/rsa_key.cc b/common/rsa_key.cc index 6a14527..a57935b 100644 --- a/common/rsa_key.cc +++ b/common/rsa_key.cc @@ -172,6 +172,11 @@ RsaPublicKey::RsaPublicKey(const RsaPublicKey& rsa_key) CHECK(key_ != nullptr); } +RsaPublicKey::RsaPublicKey(const RsaPrivateKey& rsa_key) + : key_(RSAPublicKey_dup(rsa_key.key_)) { + CHECK(key_ != nullptr); +} + RsaPublicKey::~RsaPublicKey() { RSA_free(key_); } RsaPublicKey* RsaPublicKey::Create(const std::string& serialized_key) { @@ -248,8 +253,8 @@ bool RsaPublicKey::VerifySignature(const std::string& message, return true; } -bool RsaPublicKey::VerifySignatureSha256Pkcs7(const std::string& message, - const std::string& signature) const { +bool RsaPublicKey::VerifySignatureSha256Pkcs7( + const std::string& message, const std::string& signature) const { if (message.empty()) { LOG(ERROR) << "Empty signature verification message"; return false; @@ -291,7 +296,8 @@ std::unique_ptr RsaKeyFactory::CreateFromPkcs1PrivateKey( } std::unique_ptr RsaKeyFactory::CreateFromPkcs8PrivateKey( - const std::string& private_key, const std::string& private_key_passphrase) const { + const std::string& private_key, + const std::string& private_key_passphrase) const { std::string pkcs1_key; const bool result = private_key_passphrase.empty() diff --git a/common/rsa_key.h b/common/rsa_key.h index cb15ff5..68e6ce2 100644 --- a/common/rsa_key.h +++ b/common/rsa_key.h @@ -18,7 +18,6 @@ #include #include -#include "base/macros.h" #include "openssl/rsa.h" namespace widevine { @@ -79,7 +78,13 @@ class RsaPrivateKey { class RsaPublicKey { public: explicit RsaPublicKey(RSA* key); - RsaPublicKey(const RsaPublicKey&); + + // Copy constructor. + RsaPublicKey(const RsaPublicKey& rsa_key); + + // Construct RsaPublicKey object from RsaPrivateKey. + explicit RsaPublicKey(const RsaPrivateKey& rsa_key); + virtual ~RsaPublicKey(); // Create an RsaPublicKey object using a DER encoded PKCS#1 RSAPublicKey. @@ -132,6 +137,10 @@ class RsaPublicKey { class RsaKeyFactory { public: RsaKeyFactory(); + + RsaKeyFactory(const RsaKeyFactory&) = delete; + RsaKeyFactory& operator=(const RsaKeyFactory&) = delete; + virtual ~RsaKeyFactory(); // Create an RsaPrivateKey object using a DER encoded PKCS#1 RSAPrivateKey. @@ -141,14 +150,12 @@ class RsaKeyFactory { // Create a PKCS#1 RsaPrivateKey object using an PKCS#8 PrivateKeyInfo or // EncryptedPrivateKeyInfo (if |private_key_passprhase| is not empty). virtual std::unique_ptr CreateFromPkcs8PrivateKey( - const std::string& private_key, const std::string& private_key_passphrase) const; + const std::string& private_key, + const std::string& private_key_passphrase) const; // Create an RsaPublicKey object using a DER encoded PKCS#1 RSAPublicKey. virtual std::unique_ptr CreateFromPkcs1PublicKey( const std::string& public_key) const; - - private: - DISALLOW_COPY_AND_ASSIGN(RsaKeyFactory); }; } // namespace widevine diff --git a/common/rsa_key_test.cc b/common/rsa_key_test.cc index f4b716a..b1153f1 100644 --- a/common/rsa_key_test.cc +++ b/common/rsa_key_test.cc @@ -161,6 +161,19 @@ TEST_F(RsaKeyTest, EncryptAndDecrypt_2048) { factory_.CreateFromPkcs1PublicKey(public_key)); } +TEST_F(RsaKeyTest, RsaPublicKeyFromPrivateKey) { + std::unique_ptr private_key( + RsaPrivateKey::Create(test_keys_.private_test_key_2_2048_bits())); + ASSERT_TRUE(private_key); + std::unique_ptr public_key(new RsaPublicKey(*private_key)); + ASSERT_TRUE(public_key); + + EXPECT_TRUE(private_key->MatchesPublicKey(*public_key)); + EXPECT_TRUE(public_key->MatchesPrivateKey(*private_key)); + + TestEncryption(std::move(private_key), std::move(public_key)); +} + TEST_F(RsaKeyTest, SignAndVerify_3072) { const std::string& private_key = test_keys_.private_test_key_1_3072_bits(); const std::string& public_key = test_keys_.public_test_key_1_3072_bits(); diff --git a/common/rsa_util.cc b/common/rsa_util.cc index d9a4b5c..344ea20 100644 --- a/common/rsa_util.cc +++ b/common/rsa_util.cc @@ -5,7 +5,6 @@ // License Agreement. For a copy of this agreement, please contact // widevine-licensing@google.com. //////////////////////////////////////////////////////////////////////////////// - // // Description: // RSA utility functions for serializing and deserializing RSA keys, @@ -14,12 +13,14 @@ #include "common/rsa_util.h" #include + #include #include #include "glog/logging.h" #include "openssl/pem.h" #include "openssl/x509.h" +#include "common/private_key_util.h" namespace { int BigNumGreaterThanPow2(const BIGNUM* b, int n) { @@ -34,92 +35,28 @@ int BigNumGreaterThanPow2(const BIGNUM* b, int n) { namespace widevine { namespace rsa_util { -static bool SerializeRsaKey(const RSA* key, std::string* serialized_key, - bool serialize_private_key) { - if (key == nullptr) { - LOG(ERROR) << (serialize_private_key ? "Private" : "Public") - << " RSA key is nullptr."; - return false; - } - if (serialized_key == nullptr) { - LOG(ERROR) << "Pointer to hold serialized RSA" - << (serialize_private_key ? "Private" : "Public") - << "Key is nullptr."; - return false; - } - BIO* bio = BIO_new(BIO_s_mem()); - if (bio == nullptr) { - LOG(ERROR) << "BIO_new returned nullptr"; - return false; - } - bool success = false; - if ((serialize_private_key - ? i2d_RSAPrivateKey_bio(bio, const_cast(key)) - : i2d_RSAPublicKey_bio(bio, const_cast(key))) != 0) { - int serialized_size = BIO_pending(bio); - serialized_key->assign(serialized_size, 0); - if (BIO_read(bio, &(*serialized_key)[0], serialized_size) == - serialized_size) { - success = true; - } else { - LOG(ERROR) << "BIO_read failure"; - } - } else { - LOG(ERROR) << (serialize_private_key ? "Private" : "Public") - << " key serialization failure"; - } - BIO_free(bio); - return success; -} - -static bool DeserializeRsaKey(const std::string& serialized_key, RSA** key, - bool deserialize_private_key) { - if (serialized_key.empty()) { - LOG(ERROR) << "Serialized RSA" - << (deserialize_private_key ? "Private" : "Public") - << "Key is empty."; - return false; - } - if (key == nullptr) { - LOG(ERROR) << "Pointer to hold new RSA " - << (deserialize_private_key ? "private" : "public") - << " key is nullptr."; - return false; - } - BIO* bio = BIO_new_mem_buf(const_cast(serialized_key.data()), - serialized_key.size()); - if (bio == nullptr) { - LOG(ERROR) << "BIO_new_mem_buf returned nullptr"; - return false; - } - *key = deserialize_private_key ? d2i_RSAPrivateKey_bio(bio, nullptr) - : d2i_RSAPublicKey_bio(bio, nullptr); - BIO_free(bio); - if (*key == nullptr) { - LOG(ERROR) << (deserialize_private_key ? "Private" : "Public") - << " RSA key deserialization failure"; - } - return *key != nullptr; -} - bool SerializeRsaPrivateKey(const RSA* private_key, std::string* serialized_private_key) { - return SerializeRsaKey(private_key, serialized_private_key, true); + return private_key_util::SerializeKey(private_key, i2d_RSAPrivateKey_bio, + serialized_private_key); } bool DeserializeRsaPrivateKey(const std::string& serialized_private_key, RSA** private_key) { - return DeserializeRsaKey(serialized_private_key, private_key, true); + return private_key_util::DeserializeKey( + serialized_private_key, d2i_RSAPrivateKey_bio, private_key); } bool SerializeRsaPublicKey(const RSA* public_key, std::string* serialized_public_key) { - return SerializeRsaKey(public_key, serialized_public_key, false); + return private_key_util::SerializeKey(public_key, i2d_RSAPublicKey_bio, + serialized_public_key); } bool DeserializeRsaPublicKey(const std::string& serialized_public_key, RSA** public_key) { - return DeserializeRsaKey(serialized_public_key, public_key, false); + return private_key_util::DeserializeKey( + serialized_public_key, d2i_RSAPublicKey_bio, public_key); } bool SerializePrivateKeyInfo(const RSA* private_key, @@ -327,9 +264,9 @@ int get_password(char* buf, int size, int rwflag, void* u) { } } // namespace -bool DeserializeEncryptedPrivateKeyInfo(const std::string& serialized_private_key, - const std::string& passphrase, - RSA** private_key) { +bool DeserializeEncryptedPrivateKeyInfo( + const std::string& serialized_private_key, const std::string& passphrase, + RSA** private_key) { if (serialized_private_key.empty()) { LOG(ERROR) << "Serialized RSAEncryptedPrivateKeyInfo is empty."; return false; @@ -349,8 +286,8 @@ bool DeserializeEncryptedPrivateKeyInfo(const std::string& serialized_private_ke return false; } bool success = false; - EVP_PKEY* evp = d2i_PKCS8PrivateKey_bio(bio, nullptr, get_password, - const_cast(&passphrase)); + EVP_PKEY* evp = d2i_PKCS8PrivateKey_bio( + bio, nullptr, get_password, const_cast(&passphrase)); if (evp == nullptr) { LOG(ERROR) << "d2i_PKCS8PrivateKey_bio returned nullptr."; goto cleanup; diff --git a/common/rsa_util.h b/common/rsa_util.h index 63e91f7..a09356b 100644 --- a/common/rsa_util.h +++ b/common/rsa_util.h @@ -119,9 +119,9 @@ bool SerializeEncryptedPrivateKeyInfo(const RSA* private_key, // which is not allocated if the method fails. This parameter must not be // NULL. // Returns true if successful, false otherwise. -bool DeserializeEncryptedPrivateKeyInfo(const std::string& serialized_private_key, - const std::string& passphrase, - RSA** private_key); +bool DeserializeEncryptedPrivateKeyInfo( + const std::string& serialized_private_key, const std::string& passphrase, + RSA** private_key); // Convert DER-encoded PKCS#1 RSAPrivateKey to DER-encoded PKCS#8 // EncryptedPrivateKeyInfo. diff --git a/common/security_profile_list.cc b/common/security_profile_list.cc new file mode 100644 index 0000000..6ebbaae --- /dev/null +++ b/common/security_profile_list.cc @@ -0,0 +1,185 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2020 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// Implementation of the SecurityProfileList class. + +#include "common/security_profile_list.h" + +#include + +#include "common/client_id_util.h" +#include "protos/public/client_identification.pb.h" +#include "protos/public/provisioned_device_info.pb.h" +#include "protos/public/security_profile.pb.h" + +namespace widevine { +using ClientCapabilities = ClientIdentification::ClientCapabilities; + +const char kModDrmMake[] = "company_name"; +const char kModDrmModel[] = "model_name"; + +int SecurityProfileList::Init() { return AddDefaultProfiles(); } + +int SecurityProfileList::AddDefaultProfiles() { + const uint32_t oemcrypto_8 = 8; + const uint32_t oemcrypto_12 = 12; + const bool make_model_not_verified = false; + SecurityProfile profile; + + PopulateProfile(SecurityProfile::SECURITY_PROFILE_LEVEL_1, + ClientCapabilities::HDCP_NONE, + ClientCapabilities::ANALOG_OUTPUT_UNKNOWN, oemcrypto_8, + make_model_not_verified, ProvisionedDeviceInfo::LEVEL_3, + kResourceTierLow, &profile); + InsertProfile(profile); + + PopulateProfile(SecurityProfile::SECURITY_PROFILE_LEVEL_2, + ClientCapabilities::HDCP_NONE, + ClientCapabilities::ANALOG_OUTPUT_SUPPORTS_CGMS_A, + oemcrypto_12, make_model_not_verified, + ProvisionedDeviceInfo::LEVEL_2, kResourceTierLow, &profile); + InsertProfile(profile); + + PopulateProfile(SecurityProfile::SECURITY_PROFILE_LEVEL_3, + ClientCapabilities::HDCP_V1, + ClientCapabilities::ANALOG_OUTPUT_SUPPORTS_CGMS_A, + oemcrypto_12, make_model_not_verified, + ProvisionedDeviceInfo::LEVEL_1, kResourceTierMed, &profile); + InsertProfile(profile); + + PopulateProfile(SecurityProfile::SECURITY_PROFILE_LEVEL_4, + ClientCapabilities::HDCP_V2_2, + ClientCapabilities::ANALOG_OUTPUT_SUPPORTS_CGMS_A, + oemcrypto_12, make_model_not_verified, + ProvisionedDeviceInfo::LEVEL_1, kResourceTierHigh, &profile); + InsertProfile(profile); + absl::ReaderMutexLock lock(&mutex_); + return security_profiles_.size(); +} + +SecurityProfile::Level SecurityProfileList::GetProfileLevel( + const ClientIdentification& client_id, + const ProvisionedDeviceInfo& device_info, + SecurityProfile::DrmInfo* drm_info) const { + // Iterate through each SP starting from the strictest first. + absl::ReaderMutexLock lock(&mutex_); + for (auto& profile : security_profiles_) { + if (profile.min_security_requirements().security_level() < + device_info.security_level()) { + continue; + } + if (profile.min_security_requirements().oemcrypto_version() > + client_id.client_capabilities().oem_crypto_api_version()) { + continue; + } + if (profile.min_output_requirements().hdcp_version() > + client_id.client_capabilities().max_hdcp_version()) { + continue; + } + if (profile.min_output_requirements().analog_output_capabilities() > + client_id.client_capabilities().analog_output_capabilities()) { + continue; + } + if (profile.min_security_requirements().resource_rating_tier() > + client_id.client_capabilities().resource_rating_tier()) { + continue; + } + if (drm_info != nullptr) { + GetDrmInfo(client_id, device_info, drm_info); + } + return profile.level(); + } + return SecurityProfile::SECURITY_PROFILE_LEVEL_UNDEFINED; +} + +bool SecurityProfileList::GetDrmInfo(const ClientIdentification& client_id, + const ProvisionedDeviceInfo& device_info, + SecurityProfile::DrmInfo* drm_info) const { + if (drm_info == nullptr) { + return false; + } + drm_info->mutable_output()->set_hdcp_version( + client_id.client_capabilities().max_hdcp_version()); + drm_info->mutable_output()->set_analog_output_capabilities( + client_id.client_capabilities().analog_output_capabilities()); + drm_info->mutable_security()->set_oemcrypto_version( + client_id.client_capabilities().oem_crypto_api_version()); + drm_info->mutable_security()->set_resource_rating_tier( + client_id.client_capabilities().resource_rating_tier()); + drm_info->mutable_security()->set_security_level( + device_info.security_level()); + drm_info->mutable_security()->set_request_model_info_status(false); + drm_info->mutable_request_model_info()->set_manufacturer( + GetClientInfo(client_id, kModDrmMake)); + drm_info->mutable_request_model_info()->set_model( + GetClientInfo(client_id, kModDrmModel)); + drm_info->set_system_id(device_info.system_id()); + return true; +} + +bool SecurityProfileList::PopulateProfile( + const SecurityProfile::Level profile_level, + const ClientCapabilities::HdcpVersion min_hdcp_version, + const ClientCapabilities::AnalogOutputCapabilities + analog_output_capabilities, + const uint32_t min_oemcrypto_version, const bool make_model_verified, + const ProvisionedDeviceInfo::WvSecurityLevel security_level, + const uint32_t resource_rating_tier, + SecurityProfile* profile_to_create) const { + if (profile_to_create == nullptr) { + return false; + } + profile_to_create->set_level(profile_level); + profile_to_create->mutable_min_output_requirements()->set_hdcp_version( + min_hdcp_version); + profile_to_create->mutable_min_output_requirements() + ->set_analog_output_capabilities(analog_output_capabilities); + profile_to_create->mutable_min_security_requirements()->set_oemcrypto_version( + min_oemcrypto_version); + profile_to_create->mutable_min_security_requirements()->set_security_level( + security_level); + profile_to_create->mutable_min_security_requirements() + ->set_resource_rating_tier(resource_rating_tier); + profile_to_create->mutable_min_security_requirements() + ->set_request_model_info_status(make_model_verified); + return true; +} + +bool SecurityProfileList::GetProfile(SecurityProfile::Level level, + SecurityProfile* security_profile) { + absl::ReaderMutexLock lock(&mutex_); + for (auto& profile : security_profiles_) { + if (profile.level() == level) { + if (security_profile != nullptr) { + *security_profile = profile; + } + return true; + } + } + return false; +} + +bool SecurityProfileList::InsertProfile( + const SecurityProfile& profile_to_insert) { + // Check if profile already exist. + if (GetProfile(profile_to_insert.level(), nullptr)) { + return false; + } + absl::WriterMutexLock lock(&mutex_); + security_profiles_.push_back(profile_to_insert); + sort(security_profiles_.begin(), security_profiles_.end(), + CompareProfileLevel); + return true; +} + +bool SecurityProfileList::CompareProfileLevel(const SecurityProfile& p1, + const SecurityProfile& p2) { + // Profiles are sorted from highest to lowest (strictest) level. + return (p1.level() > p2.level()); +} + +} // namespace widevine diff --git a/common/security_profile_list.h b/common/security_profile_list.h new file mode 100644 index 0000000..835c0b6 --- /dev/null +++ b/common/security_profile_list.h @@ -0,0 +1,96 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2020 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Container of Widevine security profiles. Security profiles indicate the +// level of security of a device based on the device's output protections, +// version of OEMCrypto and security level. + +#ifndef COMMON_SECURITY_PROFILE_LIST_H_ +#define COMMON_SECURITY_PROFILE_LIST_H_ + +#include "absl/synchronization/mutex.h" +#include "protos/public/client_identification.pb.h" +#include "protos/public/provisioned_device_info.pb.h" +#include "protos/public/security_profile.pb.h" + +namespace widevine { +using ClientCapabilities = ClientIdentification::ClientCapabilities; + +const uint32_t kResourceTierLow = 1; +const uint32_t kResourceTierMed = 2; +const uint32_t kResourceTierHigh = 3; + +// The SecurityProfileList will hold all security profiles. During license +// acquisition, information from the client and information from the server are +// combined to deternmine the device's security profile level. + +class SecurityProfileList { + public: + SecurityProfileList() {} + ~SecurityProfileList() {} + + // Initialize the security profile list. The list is initially empty, this + // function will populate the list with default profiles. The size of the + // list is returned. + int Init(); + + // Add the specified profile to the existing list of profiles. Returns true + // if successfully inserted, false if unable to insert. + bool InsertProfile(const SecurityProfile& profile_to_insert); + + // Return the highest security level based on the device capabilities. + // If |drm_info| is not null, |drm_info| is populated with the device data. + SecurityProfile::Level GetProfileLevel( + const ClientIdentification& client_id, + const ProvisionedDeviceInfo& device_info, + SecurityProfile::DrmInfo* drm_info) const; + + // Return the device security capabilities. |drm_info| is populated with + // data from |client_id| and |device_info|. |drm_info| must not be null and + // is owned by the caller. + bool GetDrmInfo(const ClientIdentification& client_id, + const ProvisionedDeviceInfo& device_info, + SecurityProfile::DrmInfo* drm_info) const; + + // Populate |profile_to_create| with the specified output protections and + // security parameters. All input parameters are used hence should be set. + bool PopulateProfile( + const SecurityProfile::Level profile_level, + const ClientCapabilities::HdcpVersion min_hdcp_version, + const ClientCapabilities::AnalogOutputCapabilities + analog_output_capabilities, + const uint32_t min_oemcrypto_version, const bool make_model_verified, + const ProvisionedDeviceInfo::WvSecurityLevel security_level, + const uint32_t resource_rating_tier, + SecurityProfile* profile_to_create) const; + + // Return true if a profile exist matching the specified |level|. + // |security_profile| is owned by the caller and is populated if a profile + // exist. + bool GetProfile(SecurityProfile::Level level, + SecurityProfile* security_profile); + + private: + // Initialize the list with Widevine default profiles. The size of the + // profile list after the additions is returned. + int AddDefaultProfiles(); + + static bool CompareProfileLevel(const SecurityProfile& p1, + const SecurityProfile& p2); + + mutable absl::Mutex mutex_; + // Widevine security profiles + std::vector security_profiles_ GUARDED_BY(mutex_); + // Custom security profiles + std::map custom_security_profiles_ + GUARDED_BY(mutex_); +}; + +} // namespace widevine +#endif // COMMON_SECURITY_PROFILE_LIST_H_ diff --git a/common/security_profile_list_test.cc b/common/security_profile_list_test.cc new file mode 100644 index 0000000..b469dc1 --- /dev/null +++ b/common/security_profile_list_test.cc @@ -0,0 +1,157 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2020 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#include "common/security_profile_list.h" + +#include "glog/logging.h" +#include "google/protobuf/util/message_differencer.h" +#include "testing/gmock.h" +#include "testing/gunit.h" +#include "protos/public/security_profile.pb.h" + +namespace widevine { +namespace security_profile { + +const char kMakeName[] = "company_name"; +const char kMakeValue[] = "Google"; +const char kModelName[] = "model_name"; +const char kModelValue[] = "model1"; +const uint32_t kSystemId = 1234; + +class SecurityProfileListTest : public ::testing::Test { + public: + SecurityProfileListTest() {} + ~SecurityProfileListTest() override {} + + void SetUp() override { + const uint32_t oemcrypto_12 = 12; + const bool make_model_not_verified = false; + const ClientIdentification::ClientCapabilities::HdcpVersion hdcp_version = + ClientCapabilities::HDCP_V2_2; + test_profile_1_.set_level(SecurityProfile::SECURITY_PROFILE_LEVEL_1); + test_profile_1_.mutable_min_output_requirements()->set_hdcp_version( + hdcp_version); + test_profile_1_.mutable_min_output_requirements() + ->set_analog_output_capabilities( + ClientCapabilities::ANALOG_OUTPUT_SUPPORTS_CGMS_A); + test_profile_1_.mutable_min_security_requirements()->set_oemcrypto_version( + oemcrypto_12); + test_profile_1_.mutable_min_security_requirements()->set_security_level( + ProvisionedDeviceInfo::LEVEL_1); + test_profile_1_.mutable_min_security_requirements() + ->set_resource_rating_tier(kResourceTierHigh); + test_profile_1_.mutable_min_security_requirements() + ->set_request_model_info_status(make_model_not_verified); + + ClientIdentification_NameValue *nv = client_id_.add_client_info(); + nv->set_name(kMakeName); + nv->set_value(kMakeValue); + nv = client_id_.add_client_info(); + nv->set_name(kModelName); + nv->set_value(kModelValue); + client_id_.mutable_client_capabilities()->set_oem_crypto_api_version( + oemcrypto_12); + client_id_.mutable_client_capabilities()->set_max_hdcp_version( + hdcp_version); + client_id_.mutable_client_capabilities()->set_resource_rating_tier( + kResourceTierHigh); + + device_info_.set_security_level(ProvisionedDeviceInfo::LEVEL_1); + device_info_.set_system_id(kSystemId); + } + SecurityProfile test_profile_1_; + SecurityProfileList profile_list_; + ClientIdentification client_id_; + ProvisionedDeviceInfo device_info_; +}; + +TEST_F(SecurityProfileListTest, InsertProfile) { + // This test will not initialize the SecurityProfileList, hence it's empty. + // Insert test profile 1 into the list. + EXPECT_TRUE(profile_list_.InsertProfile(test_profile_1_)); + // Should not allow insertion of an alreadfy existing level. + EXPECT_FALSE(profile_list_.InsertProfile(test_profile_1_)); + SecurityProfile profile; + ASSERT_EQ(SecurityProfile::SECURITY_PROFILE_LEVEL_1, + profile_list_.GetProfile(test_profile_1_.level(), &profile)); + EXPECT_TRUE( + google::protobuf::util::MessageDifferencer::Equals(test_profile_1_, profile)); +} + +TEST_F(SecurityProfileListTest, GetDrmInfo) { + SecurityProfile::DrmInfo drm_info; + ASSERT_TRUE(profile_list_.GetDrmInfo(client_id_, device_info_, &drm_info)); + EXPECT_EQ(client_id_.client_capabilities().max_hdcp_version(), + drm_info.output().hdcp_version()); + EXPECT_EQ(client_id_.client_capabilities().analog_output_capabilities(), + drm_info.output().analog_output_capabilities()); + EXPECT_EQ(client_id_.client_capabilities().oem_crypto_api_version(), + drm_info.security().oemcrypto_version()); + EXPECT_EQ(client_id_.client_capabilities().resource_rating_tier(), + drm_info.security().resource_rating_tier()); + + EXPECT_EQ(device_info_.security_level(), + drm_info.security().security_level()); + EXPECT_EQ(device_info_.system_id(), drm_info.system_id()); + + // make_mode status is currently hard-coded to false. + EXPECT_EQ(false, drm_info.security().request_model_info_status()); + EXPECT_EQ(kMakeValue, drm_info.request_model_info().manufacturer()); + EXPECT_EQ(kModelValue, drm_info.request_model_info().model()); +} + +TEST_F(SecurityProfileListTest, ProfileLevels) { + SecurityProfile::DrmInfo drm_info; + profile_list_.Init(); + + client_id_.mutable_client_capabilities()->set_max_hdcp_version( + ClientCapabilities::HDCP_NONE); + client_id_.mutable_client_capabilities()->set_analog_output_capabilities( + ClientCapabilities::ANALOG_OUTPUT_UNKNOWN); + client_id_.mutable_client_capabilities()->set_oem_crypto_api_version(7); + client_id_.mutable_client_capabilities()->set_resource_rating_tier( + kResourceTierLow); + device_info_.set_security_level(ProvisionedDeviceInfo::LEVEL_3); + + // Lowest profile level requires OEMCrypto version 8. + ASSERT_EQ(SecurityProfile::SECURITY_PROFILE_LEVEL_UNDEFINED, + profile_list_.GetProfileLevel(client_id_, device_info_, &drm_info)); + + // Move up to profile 1 + client_id_.mutable_client_capabilities()->set_oem_crypto_api_version(8); + ASSERT_EQ(SecurityProfile::SECURITY_PROFILE_LEVEL_1, + profile_list_.GetProfileLevel(client_id_, device_info_, &drm_info)); + + // Move up to profile 2 + client_id_.mutable_client_capabilities()->set_analog_output_capabilities( + ClientCapabilities::ANALOG_OUTPUT_SUPPORTS_CGMS_A); + client_id_.mutable_client_capabilities()->set_oem_crypto_api_version(12); + device_info_.set_security_level(ProvisionedDeviceInfo::LEVEL_2); + ASSERT_EQ(SecurityProfile::SECURITY_PROFILE_LEVEL_2, + profile_list_.GetProfileLevel(client_id_, device_info_, &drm_info)); + + // Move up to profile 3 + client_id_.mutable_client_capabilities()->set_max_hdcp_version( + ClientCapabilities::HDCP_V1); + device_info_.set_security_level(ProvisionedDeviceInfo::LEVEL_1); + client_id_.mutable_client_capabilities()->set_resource_rating_tier( + kResourceTierMed); + ASSERT_EQ(SecurityProfile::SECURITY_PROFILE_LEVEL_3, + profile_list_.GetProfileLevel(client_id_, device_info_, &drm_info)); + + // Move up to profile 4 + client_id_.mutable_client_capabilities()->set_max_hdcp_version( + ClientCapabilities::HDCP_V2_2); + client_id_.mutable_client_capabilities()->set_resource_rating_tier( + kResourceTierHigh); + ASSERT_EQ(SecurityProfile::SECURITY_PROFILE_LEVEL_4, + profile_list_.GetProfileLevel(client_id_, device_info_, &drm_info)); +} + +} // namespace security_profile +} // namespace widevine diff --git a/common/sha_util.h b/common/sha_util.h index 1e2b70a..b9ed829 100644 --- a/common/sha_util.h +++ b/common/sha_util.h @@ -27,7 +27,8 @@ std::string Sha512_Hash(const std::string& message); // Generates a UUID as specified in ITU-T X.667 ch. 14, SHA-1 name-based, // 16-byte binary representation. Name_space is a GUID prefix; name is a unique // name in the namespace. -std::string GenerateSha1Uuid(const std::string& name_space, const std::string& name); +std::string GenerateSha1Uuid(const std::string& name_space, + const std::string& name); } // namespace widevine diff --git a/common/sha_util_test.cc b/common/sha_util_test.cc index 8b3eb7c..e0354d2 100644 --- a/common/sha_util_test.cc +++ b/common/sha_util_test.cc @@ -17,7 +17,8 @@ TEST(ShaUtilTest, Sha1Empty) { 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09, }; - EXPECT_EQ(std::string(kExpected, kExpected + sizeof(kExpected)), Sha1_Hash("")); + EXPECT_EQ(std::string(kExpected, kExpected + sizeof(kExpected)), + Sha1_Hash("")); } TEST(ShaUtilTest, Sha256Empty) { @@ -26,7 +27,8 @@ TEST(ShaUtilTest, Sha256Empty) { 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55, }; - EXPECT_EQ(std::string(kExpected, kExpected + sizeof(kExpected)), Sha256_Hash("")); + EXPECT_EQ(std::string(kExpected, kExpected + sizeof(kExpected)), + Sha256_Hash("")); } TEST(ShaUtilTest, Sha1) { diff --git a/common/signature_util.cc b/common/signature_util.cc index 1e80e09..3927390 100644 --- a/common/signature_util.cc +++ b/common/signature_util.cc @@ -19,7 +19,8 @@ namespace widevine { namespace signature_util { -Status GenerateAesSignature(const std::string& message, const std::string& aes_key, +Status GenerateAesSignature(const std::string& message, + const std::string& aes_key, const std::string& aes_iv, std::string* signature) { if (signature == nullptr) { return Status(error::INVALID_ARGUMENT, "signature is nullptr"); @@ -36,7 +37,8 @@ Status GenerateAesSignature(const std::string& message, const std::string& aes_k return OkStatus(); } -Status GenerateRsaSignature(const std::string& message, const std::string& private_key, +Status GenerateRsaSignature(const std::string& message, + const std::string& private_key, std::string* signature) { if (signature == nullptr) { return Status(error::INVALID_ARGUMENT, "signature is nullptr"); diff --git a/common/signature_util.h b/common/signature_util.h index 25238fb..ee64d47 100644 --- a/common/signature_util.h +++ b/common/signature_util.h @@ -19,13 +19,15 @@ namespace signature_util { // Generates an AES signature of |message| using |aes_key| and |aes_iv|. // Signature is returned via |signature| if generation was successful. // Returns a Status that carries the details of error if generation failed. -Status GenerateAesSignature(const std::string& message, const std::string& aes_key, +Status GenerateAesSignature(const std::string& message, + const std::string& aes_key, const std::string& aes_iv, std::string* signature); // Generates a RSA signature of |message| using |private_key|. // Signature is returned via |sigature| if generation was successful. // Returns a Status that carries the details of error if generation failed. -Status GenerateRsaSignature(const std::string& message, const std::string& private_key, +Status GenerateRsaSignature(const std::string& message, + const std::string& private_key, std::string* signature); } // namespace signature_util diff --git a/common/signer_public_key.cc b/common/signer_public_key.cc new file mode 100644 index 0000000..c1ac103 --- /dev/null +++ b/common/signer_public_key.cc @@ -0,0 +1,69 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#include "common/signer_public_key.h" + +#include "absl/memory/memory.h" +#include "common/ec_key.h" +#include "common/rsa_key.h" + +namespace widevine { + +// SignerPublicKeyImpl is a generic implementation of SignerPublicKey. The +// initialization details are in the SignerPublicKey factory method. +template +class SignerPublicKeyImpl : public SignerPublicKey { + public: + explicit SignerPublicKeyImpl(std::unique_ptr signer_public_key) + : signer_public_key_(std::move(signer_public_key)) {} + ~SignerPublicKeyImpl() override {} + SignerPublicKeyImpl(const SignerPublicKeyImpl&) = delete; + SignerPublicKeyImpl& operator=(const SignerPublicKeyImpl&) = delete; + + bool VerifySignature(const std::string& message, + const std::string& signature) const override { + if (!signer_public_key_->VerifySignature(message, signature)) { + return false; + } + return true; + } + + private: + std::unique_ptr signer_public_key_; +}; + +std::unique_ptr SignerPublicKey::Create( + const std::string& signer_public_key, DrmCertificate::Algorithm algorithm) { + switch (algorithm) { + case DrmCertificate::RSA: { + std::unique_ptr public_key( + RsaPublicKey::Create(signer_public_key)); + if (public_key == nullptr) { + return nullptr; + } + return absl::make_unique>( + std::move(public_key)); + } + // All supported ECC curves are specified here. + case DrmCertificate::ECC_SECP256R1: + case DrmCertificate::ECC_SECP384R1: + case DrmCertificate::ECC_SECP521R1: { + std::unique_ptr public_key = + ECPublicKey::Create(signer_public_key); + if (public_key == nullptr) { + return nullptr; + } + return absl::make_unique>( + std::move(public_key)); + } + default: + return nullptr; + } +} + +} // namespace widevine diff --git a/common/signer_public_key.h b/common/signer_public_key.h new file mode 100644 index 0000000..228db48 --- /dev/null +++ b/common/signer_public_key.h @@ -0,0 +1,40 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef COMMON_SIGNER_PUBLIC_KEY_H_ +#define COMMON_SIGNER_PUBLIC_KEY_H_ + +#include + +#include "protos/public/drm_certificate.pb.h" + +namespace widevine { + +// SignerPublicKey is implemented for each provisioning key type that are +// defined in video/widevine/proto/public.drm_certificate.proto. +class SignerPublicKey { + public: + SignerPublicKey() = default; + virtual ~SignerPublicKey() = default; + SignerPublicKey(const SignerPublicKey&) = delete; + SignerPublicKey& operator=(const SignerPublicKey&) = delete; + + // Verify message using |signer_public_key_|. + virtual bool VerifySignature(const std::string& message, + const std::string& signature) const = 0; + + // A factory method to create a SignerPublicKey. The |algorithm| is used to + // create an appropriate SignerPublicKey for the key type. + // The returned pointer is a nullptr if the key cannot be deserialized. + static std::unique_ptr Create( + const std::string& signer_public_key, + DrmCertificate::Algorithm algorithm); +}; + +} // namespace widevine +#endif // COMMON_SIGNER_PUBLIC_KEY_H_ diff --git a/common/signer_public_key_test.cc b/common/signer_public_key_test.cc new file mode 100644 index 0000000..7f079d0 --- /dev/null +++ b/common/signer_public_key_test.cc @@ -0,0 +1,78 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +#include "common/signer_public_key.h" + +#include + +#include "testing/gunit.h" +#include "common/ec_key.h" +#include "common/ec_test_keys.h" +#include "common/rsa_key.h" +#include "common/rsa_test_keys.h" +#include "protos/public/drm_certificate.pb.h" + +namespace widevine { + +static const char kMessage[] = "The rain in Spain falls mainly in the blank?"; + +class SignerPublicKeyTest : public ::testing::Test { + public: + RsaTestKeys rsa_test_keys_; + ECTestKeys ec_test_keys_; +}; + +TEST_F(SignerPublicKeyTest, RSA) { + std::unique_ptr private_key( + RsaPrivateKey::Create(rsa_test_keys_.private_test_key_1_3072_bits())); + + std::string signature; + ASSERT_TRUE(private_key->GenerateSignature(kMessage, &signature)); + + std::unique_ptr public_key = SignerPublicKey::Create( + rsa_test_keys_.public_test_key_1_3072_bits(), DrmCertificate::RSA); + ASSERT_NE(public_key, nullptr); + EXPECT_TRUE(public_key->VerifySignature(kMessage, signature)); +} + +TEST_F(SignerPublicKeyTest, ECC) { + std::unique_ptr private_key = + ECPrivateKey::Create(ec_test_keys_.private_test_key_1_secp521r1()); + + std::string signature; + ASSERT_TRUE(private_key->GenerateSignature(kMessage, &signature)); + + std::unique_ptr public_key = + SignerPublicKey::Create(ec_test_keys_.public_test_key_1_secp521r1(), + DrmCertificate::ECC_SECP521R1); + ASSERT_NE(public_key, nullptr); + EXPECT_TRUE(public_key->VerifySignature(kMessage, signature)); +} + +TEST_F(SignerPublicKeyTest, IncorrectAlgorithm) { + std::unique_ptr rsa_public_key = + SignerPublicKey::Create(rsa_test_keys_.public_test_key_1_3072_bits(), + DrmCertificate::ECC_SECP521R1); + ASSERT_EQ(rsa_public_key, nullptr); + + std::unique_ptr ec_public_key = SignerPublicKey::Create( + ec_test_keys_.public_test_key_1_secp521r1(), DrmCertificate::RSA); + ASSERT_EQ(ec_public_key, nullptr); +} + +TEST_F(SignerPublicKeyTest, BadKey) { + std::unique_ptr rsa_public_key = + SignerPublicKey::Create("GobbletyGook", DrmCertificate::RSA); + ASSERT_EQ(rsa_public_key, nullptr); + + std::unique_ptr ec_public_key = SignerPublicKey::Create( + "MoreGobbletyGook", DrmCertificate::ECC_SECP521R1); + ASSERT_EQ(ec_public_key, nullptr); +} + +} // namespace widevine diff --git a/common/signing_key_util.cc b/common/signing_key_util.cc index 6b7b596..65ca55e 100644 --- a/common/signing_key_util.cc +++ b/common/signing_key_util.cc @@ -26,7 +26,7 @@ uint32_t SigningKeyMaterialSizeBits(ProtocolVersion protocol_version) { using crypto_util::kSigningKeySizeBytes; std::string GetClientSigningKey(const std::string& derived_key, - ProtocolVersion protocol_version) { + ProtocolVersion protocol_version) { if (protocol_version == VERSION_2_0) { DCHECK(derived_key.size() >= kSigningKeySizeBytes); return derived_key.substr(0, kSigningKeySizeBytes); diff --git a/common/signing_key_util.h b/common/signing_key_util.h index 34179cd..7e7557f 100644 --- a/common/signing_key_util.h +++ b/common/signing_key_util.h @@ -28,7 +28,6 @@ #include -#include "base/macros.h" #include "protos/public/license_protocol.pb.h" namespace widevine { @@ -44,7 +43,7 @@ uint32_t SigningKeyMaterialSizeBits(ProtocolVersion protocol_version); // keys are returned. Keys that are 256 bits in length are returned // in there entirety, version 2.0 keys. std::string GetClientSigningKey(const std::string& derived_key, - ProtocolVersion protocol_version); + ProtocolVersion protocol_version); // Returns the server portion of the derived_key. The server portion // is the first 256 bits of the key. diff --git a/common/signing_key_util_test.cc b/common/signing_key_util_test.cc index 6c887d2..cb9fb56 100644 --- a/common/signing_key_util_test.cc +++ b/common/signing_key_util_test.cc @@ -20,7 +20,8 @@ const char* kFrontKeyHex = const char* kBackKeyHex = "0c1c2c3c4c5c6c7c8c9c0d1d2d3d4d5d0c1c2c3c4c5c6c7c8c9c0d1d2d3d4d5d"; -std::string GenerateDerivedKey(widevine::ProtocolVersion protocol_version) { +std::string GenerateDerivedKey( + widevine::ProtocolVersion protocol_version) { if (protocol_version == widevine::VERSION_2_0) { return absl::HexStringToBytes(kFrontKeyHex); } else { diff --git a/common/status.h b/common/status.h index 0f44f62..28d5a86 100644 --- a/common/status.h +++ b/common/status.h @@ -37,6 +37,12 @@ enum StatusCode { // instead for those errors). PERMISSION_DENIED = 7, + // The operation was rejected because the system is not in a state + // required for the operation's execution. For example, the directory + // to be deleted is non-empty, an rmdir operation is applied to + // a non-directory, etc. + FAILED_PRECONDITION = 9, + // Operation is not implemented or not supported/enabled in this service. UNIMPLEMENTED = 12, @@ -70,7 +76,8 @@ class Status { const std::string& error_message) : error_space_(e), status_code_(c), error_message_(error_message) {} - Status(const util::ErrorSpace* e, int error, const std::string& error_message) + Status(const util::ErrorSpace* e, int error, + const std::string& error_message) : error_space_(e), status_code_(error), error_message_(error_message) {} bool ok() const { return status_code_ == error::OK; } diff --git a/common/string_util.cc b/common/string_util.cc index 1db23e6..2a9f9d4 100644 --- a/common/string_util.cc +++ b/common/string_util.cc @@ -18,7 +18,8 @@ namespace widevine { namespace string_util { -Status BitsetStringToBinaryString(const std::string& bitset, std::string* output) { +Status BitsetStringToBinaryString(const std::string& bitset, + std::string* output) { if (output == nullptr) { return Status(error::INTERNAL, "output is nullptr."); } diff --git a/common/string_util.h b/common/string_util.h index 9c9922d..37f8e57 100644 --- a/common/string_util.h +++ b/common/string_util.h @@ -17,7 +17,8 @@ namespace string_util { // Converts std::string representation of a bitset to its binary equivalent string. // For example, converts "01110100011001010111001101110100" to "test". -Status BitsetStringToBinaryString(const std::string& bitset, std::string* output); +Status BitsetStringToBinaryString(const std::string& bitset, + std::string* output); } // namespace string_util } // namespace widevine diff --git a/common/test_drm_certificates.cc b/common/test_drm_certificates.cc index 69faa48..3dabb44 100644 --- a/common/test_drm_certificates.cc +++ b/common/test_drm_certificates.cc @@ -13,7 +13,7 @@ namespace widevine { static const unsigned char kTestRootCertificate[] = { - 0x0a, 0x99, 0x03, 0x08, 0x00, 0x12, 0x01, 0x00, 0x18, 0xb9, 0x60, 0x22, + 0x0a, 0x9b, 0x03, 0x08, 0x00, 0x12, 0x01, 0x00, 0x18, 0xb9, 0x60, 0x22, 0x8e, 0x03, 0x30, 0x82, 0x01, 0x8a, 0x02, 0x82, 0x01, 0x81, 0x00, 0xa5, 0x62, 0x07, 0xdf, 0xc8, 0x84, 0x74, 0xe1, 0x2a, 0xb7, 0xbb, 0xc0, 0x78, 0x76, 0xbe, 0x13, 0x3b, 0xe6, 0x2c, 0x09, 0x9d, 0x35, 0x3f, 0xf3, 0x0f, @@ -47,42 +47,43 @@ static const unsigned char kTestRootCertificate[] = { 0x38, 0xff, 0x2f, 0x71, 0xf5, 0x30, 0x18, 0x1e, 0x6f, 0xd7, 0xf0, 0x33, 0x61, 0x53, 0x7e, 0x55, 0x7f, 0x0d, 0x60, 0x83, 0xf3, 0x8a, 0x2b, 0x67, 0xd5, 0xf0, 0x2e, 0x23, 0x23, 0x60, 0x0b, 0x83, 0x9c, 0xc2, 0x87, 0x02, - 0x03, 0x01, 0x00, 0x01, 0x12, 0x80, 0x03, 0x7f, 0x83, 0xde, 0xf0, 0x6a, - 0x07, 0x2b, 0x8c, 0xd7, 0x0c, 0xb8, 0x75, 0x50, 0xce, 0xe8, 0xa9, 0x35, - 0xcb, 0x9d, 0xe3, 0x83, 0x89, 0xe6, 0x78, 0xb2, 0x12, 0x12, 0x16, 0xfe, - 0x62, 0xf9, 0xed, 0x1d, 0x1d, 0xda, 0x82, 0x67, 0x82, 0x30, 0xf8, 0x49, - 0xc2, 0x49, 0x65, 0x3b, 0xa3, 0x69, 0xaa, 0xd4, 0xaa, 0xfa, 0x74, 0xa6, - 0xf1, 0xc3, 0xd8, 0xd0, 0x84, 0x27, 0x00, 0xa2, 0xec, 0xbd, 0xcf, 0x58, - 0xf2, 0xf6, 0x60, 0x00, 0xeb, 0x50, 0xae, 0x06, 0x9e, 0x5c, 0xd2, 0xce, - 0xc0, 0xbc, 0x73, 0xdb, 0x66, 0xc4, 0x93, 0x39, 0x22, 0x92, 0x92, 0x27, - 0x71, 0x3c, 0x25, 0x66, 0x96, 0x2e, 0xda, 0x66, 0x65, 0xbc, 0x38, 0xf5, - 0x4e, 0x8e, 0x68, 0x4d, 0x5f, 0x8f, 0xf5, 0x90, 0xcc, 0xfb, 0xf3, 0x8c, - 0x63, 0x3f, 0xe2, 0xf9, 0x4a, 0x37, 0xec, 0x68, 0x0b, 0x00, 0xcd, 0x0e, - 0x13, 0x66, 0x06, 0x2f, 0x37, 0xc7, 0x3a, 0xa3, 0x7a, 0x1e, 0xb8, 0x12, - 0x1d, 0xf4, 0x09, 0xba, 0xfc, 0x55, 0x1d, 0xa8, 0x54, 0x4a, 0x4c, 0x54, - 0xda, 0x32, 0xe3, 0x4c, 0xa2, 0x03, 0xae, 0x65, 0xf0, 0x81, 0x4a, 0xe8, - 0xc7, 0x93, 0x78, 0xdf, 0xc0, 0x3d, 0xc5, 0x24, 0xdc, 0x45, 0x27, 0xe1, - 0xba, 0xc8, 0xe2, 0x1f, 0x27, 0x7c, 0x61, 0xba, 0x1b, 0x31, 0xc0, 0xf1, - 0xad, 0x13, 0xdd, 0x61, 0x31, 0xf4, 0xc0, 0xe9, 0x0e, 0x8c, 0x8e, 0xe8, - 0xd1, 0xf8, 0xdb, 0x76, 0xdf, 0x3f, 0x1a, 0x25, 0x28, 0x46, 0xc4, 0xf4, - 0xdb, 0x8a, 0x3b, 0x03, 0x16, 0x96, 0x6b, 0x28, 0x0f, 0x05, 0xe6, 0xa9, - 0xcb, 0x0d, 0x95, 0x57, 0x89, 0x3e, 0x4c, 0x70, 0xed, 0x84, 0x45, 0xdd, - 0x88, 0x43, 0x4b, 0xc1, 0x9e, 0x52, 0xb3, 0x3a, 0xa1, 0xd9, 0xd4, 0xf9, - 0x68, 0x08, 0x0b, 0x83, 0x35, 0x75, 0xf1, 0x2a, 0xa7, 0xce, 0xf6, 0x3f, - 0x4a, 0x84, 0xd0, 0x0c, 0xfa, 0xf2, 0x0f, 0x42, 0x28, 0x1a, 0x1a, 0x92, - 0xa7, 0x7d, 0x6f, 0xad, 0x57, 0x82, 0x44, 0x1a, 0x6d, 0x35, 0x85, 0x15, - 0x2c, 0xd4, 0x28, 0xb4, 0x7c, 0xde, 0x66, 0x3b, 0xeb, 0x6d, 0x32, 0xc0, - 0x30, 0xdf, 0x16, 0x99, 0x2e, 0xce, 0x8d, 0x23, 0x43, 0x06, 0x00, 0xe9, - 0xb1, 0x94, 0x20, 0x42, 0x2a, 0xf5, 0xf1, 0x79, 0x4f, 0x2c, 0xd9, 0xe1, - 0xc7, 0x2e, 0xd4, 0x8a, 0x31, 0x5a, 0x80, 0x27, 0x57, 0xa6, 0xfc, 0xb2, - 0x47, 0x4c, 0x5b, 0x05, 0x22, 0x82, 0x77, 0x76, 0xbe, 0xd4, 0x23, 0x8c, - 0xdf, 0xfc, 0xe9, 0xbc, 0x01, 0xc0, 0x16, 0x60, 0xff, 0x00, 0x45, 0x36, - 0x2f, 0x29, 0x5f, 0x5f, 0xa8, 0x83, 0x8a, 0x55, 0xc2, 0x39, 0x72, 0x35, - 0xc2, 0xb4, 0x81, 0xf7, 0xd7, 0x40, 0x15, 0x0c, 0xf1, 0xef, 0x58, 0xe7, - 0xc4, 0xc1, 0x23, 0x47, 0x92, 0x29, 0x44}; + 0x03, 0x01, 0x00, 0x01, 0x48, 0x01, 0x12, 0x80, 0x03, 0x45, 0x3d, 0x03, + 0x60, 0xd1, 0x13, 0x9e, 0xcd, 0x69, 0x5f, 0xd5, 0xa7, 0x62, 0x12, 0x28, + 0x49, 0x4a, 0x73, 0x05, 0x1b, 0xf3, 0xd4, 0x4e, 0x54, 0x3f, 0x5f, 0x43, + 0x2c, 0x17, 0x56, 0xbf, 0xc3, 0xb9, 0xe1, 0xb8, 0xb7, 0xc7, 0xd6, 0x52, + 0x8e, 0xfb, 0x1c, 0x24, 0x9b, 0x84, 0x13, 0x08, 0xec, 0x0b, 0xd9, 0xfa, + 0xe3, 0x9d, 0x37, 0x55, 0x72, 0x69, 0xfc, 0x39, 0x50, 0xbb, 0x49, 0x86, + 0xe2, 0x85, 0x01, 0x20, 0x3e, 0x08, 0x2c, 0xdc, 0xee, 0x36, 0x04, 0xff, + 0x24, 0x50, 0x88, 0x17, 0xfb, 0x8e, 0x86, 0xf6, 0xc5, 0xd6, 0xc5, 0x5b, + 0x32, 0xe1, 0x3f, 0xff, 0x9c, 0x23, 0xd8, 0x84, 0x61, 0x26, 0x1d, 0x46, + 0x82, 0x99, 0x3f, 0x1a, 0x5a, 0xc7, 0xd5, 0x97, 0x6d, 0xdb, 0x3a, 0x80, + 0xef, 0x80, 0x2d, 0x11, 0x06, 0xf2, 0x14, 0x2b, 0x40, 0x61, 0x6f, 0x91, + 0xea, 0x8a, 0xc5, 0xde, 0xad, 0x68, 0x31, 0xda, 0x11, 0x82, 0x11, 0x2b, + 0x19, 0x3c, 0x89, 0xbc, 0x4a, 0xed, 0x87, 0x44, 0x1b, 0x79, 0xa9, 0x22, + 0xb7, 0x81, 0xb3, 0xa9, 0xa2, 0x9b, 0x77, 0xf9, 0x40, 0x31, 0x4a, 0x9a, + 0x5a, 0x9d, 0x56, 0xf9, 0x81, 0x2f, 0x9b, 0xe1, 0xd1, 0xca, 0xe7, 0xc5, + 0xdc, 0x43, 0x92, 0x96, 0x5a, 0x22, 0x07, 0xcd, 0x0e, 0xec, 0x70, 0xe8, + 0xd7, 0xdb, 0x52, 0xbe, 0x23, 0x23, 0x4c, 0xb8, 0x9e, 0x0a, 0x94, 0x64, + 0xa7, 0xc8, 0xd8, 0x30, 0x78, 0xb9, 0x31, 0x8f, 0x5f, 0x98, 0x71, 0x24, + 0xbd, 0xc2, 0xdc, 0x52, 0xf5, 0x0a, 0xf7, 0x0d, 0x48, 0x58, 0x6b, 0xdd, + 0xa9, 0x95, 0xc6, 0x03, 0x13, 0x39, 0x87, 0xf8, 0x7a, 0x0e, 0x32, 0xd5, + 0x77, 0x46, 0x59, 0x12, 0xae, 0x52, 0xd1, 0x48, 0xdf, 0x4c, 0xdd, 0xbf, + 0xd7, 0xcc, 0x38, 0x1e, 0x07, 0x35, 0x3f, 0x1b, 0xe5, 0xa4, 0x2a, 0x01, + 0x77, 0x22, 0xe6, 0x02, 0x90, 0x4d, 0x8b, 0x02, 0x75, 0x07, 0x36, 0xb0, + 0xfa, 0x82, 0xf6, 0x7e, 0x74, 0xde, 0xba, 0xfa, 0x0e, 0x5a, 0x9a, 0x70, + 0x50, 0xf4, 0x42, 0x05, 0xb1, 0xca, 0xc7, 0x18, 0xb7, 0x76, 0xff, 0x04, + 0x8e, 0x2e, 0xe3, 0x44, 0x41, 0x38, 0x16, 0xa4, 0x34, 0x84, 0x66, 0x72, + 0x0f, 0xc8, 0x2f, 0x9c, 0xe1, 0x5f, 0xe6, 0x35, 0x79, 0x64, 0x67, 0xa0, + 0x53, 0x89, 0x4c, 0x51, 0xc8, 0x34, 0x6e, 0x70, 0xba, 0xfe, 0xdd, 0xca, + 0xc2, 0xc6, 0x91, 0x8b, 0x08, 0x5e, 0x25, 0x96, 0xd0, 0x0d, 0xe7, 0xee, + 0x25, 0x92, 0x39, 0xa3, 0xba, 0xa4, 0x0b, 0xab, 0xa4, 0x2e, 0x16, 0xfc, + 0xad, 0xed, 0xcf, 0x12, 0xda, 0x9b, 0xe9, 0x67, 0x4d, 0xb2, 0x4e, 0xe9, + 0xb3, 0xe8, 0x53, 0xc8, 0x5a, 0xc7, 0xbd, 0x69, 0xa7, 0x12, 0x4e, 0x43, + 0x20, 0x62, 0x34, 0xb0, 0xbd, 0xb2, 0xea, 0x95, 0xf6, +}; const unsigned char kTestIntermediateCertificate[] = { - 0x0a, 0xaf, 0x02, 0x08, 0x01, 0x12, 0x10, 0x30, 0x31, 0x32, 0x33, 0x34, + 0x0a, 0xb1, 0x02, 0x08, 0x01, 0x12, 0x10, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x18, 0xb2, 0x92, 0x04, 0x22, 0x8e, 0x02, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa7, 0x00, 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, 0x5a, @@ -107,42 +108,92 @@ const unsigned char kTestIntermediateCertificate[] = { 0xed, 0x27, 0x29, 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, 0xd3, 0x5b, 0x3d, 0xdb, 0x9c, 0x5e, 0x1b, 0x7b, 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, 0xbb, 0xbb, 0x2c, 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, 0x01, 0x00, 0x01, - 0x28, 0xd2, 0x85, 0xd8, 0xcc, 0x04, 0x12, 0x80, 0x03, 0x7b, 0xd3, 0x40, - 0xa8, 0xd0, 0x31, 0x1e, 0x95, 0x35, 0xdd, 0xb3, 0x20, 0xcf, 0xc2, 0xcf, - 0xc9, 0x26, 0x49, 0x53, 0xc8, 0x58, 0xd5, 0x12, 0xf0, 0x71, 0xf4, 0xd4, - 0x33, 0x8e, 0xd7, 0x6f, 0x79, 0xbe, 0x17, 0xeb, 0x36, 0x71, 0xf2, 0x3b, - 0xc3, 0x4f, 0x3a, 0xeb, 0xc7, 0xfb, 0xf6, 0x40, 0xf8, 0xe6, 0xe4, 0x51, - 0xce, 0x45, 0x5c, 0xf0, 0x66, 0xd1, 0x22, 0x55, 0x72, 0xcd, 0x50, 0xb4, - 0x5a, 0x02, 0x2f, 0xb7, 0x11, 0x24, 0x61, 0x12, 0x9f, 0x80, 0x5f, 0xc9, - 0xee, 0xc9, 0xd4, 0x7b, 0x62, 0x76, 0x34, 0xdd, 0x45, 0xae, 0x42, 0xbb, - 0x1f, 0x7a, 0x18, 0x85, 0xc7, 0xcf, 0xc9, 0x86, 0x47, 0xfd, 0x23, 0xd9, - 0x26, 0xbe, 0x47, 0x3e, 0x80, 0x45, 0x41, 0x39, 0x92, 0xe4, 0x0e, 0x25, - 0xdb, 0x85, 0x35, 0x77, 0x34, 0x3a, 0x67, 0xbf, 0xea, 0xfa, 0x84, 0xba, - 0xb9, 0x3d, 0x03, 0x89, 0xa8, 0x13, 0x9f, 0x35, 0xa1, 0x12, 0x0e, 0x80, - 0x12, 0x72, 0x24, 0x4e, 0xc2, 0x6d, 0x2b, 0x77, 0x19, 0xb8, 0xa1, 0x98, - 0xab, 0x73, 0x43, 0x79, 0xf6, 0x7b, 0x9e, 0xc9, 0x4f, 0xb8, 0xb5, 0xf1, - 0x75, 0x79, 0x7a, 0x48, 0x01, 0x0e, 0xb6, 0xb9, 0x3e, 0x46, 0xf0, 0x98, - 0xe8, 0x40, 0x6a, 0x60, 0xeb, 0x8f, 0x51, 0x78, 0x31, 0x5c, 0xe1, 0x0f, - 0x6f, 0x23, 0x36, 0xf3, 0xd4, 0x7a, 0x68, 0x74, 0x32, 0x3c, 0xf6, 0x30, - 0xaa, 0xcf, 0x4f, 0xb7, 0xdf, 0xc4, 0xe0, 0x1b, 0x8c, 0xa8, 0x2b, 0x1b, - 0x7f, 0x91, 0xf9, 0x98, 0xb9, 0xac, 0xf4, 0x50, 0x3e, 0xc1, 0x1c, 0x7a, - 0x98, 0xad, 0x88, 0x68, 0xe6, 0xe8, 0x4f, 0x8b, 0x5f, 0xf7, 0xf6, 0x0e, - 0x6e, 0x9d, 0xe1, 0x55, 0xe2, 0xf7, 0x5b, 0x2c, 0x73, 0x5e, 0x77, 0x04, - 0x4f, 0x32, 0x5d, 0x13, 0x51, 0x8f, 0x1a, 0x53, 0xad, 0xff, 0x1e, 0x52, - 0xfc, 0xcc, 0xa5, 0x80, 0x92, 0x9b, 0x89, 0x64, 0x18, 0x49, 0xd9, 0xaa, - 0xb3, 0x77, 0xf3, 0x60, 0x4c, 0x6e, 0x9f, 0x0d, 0xf0, 0xbc, 0x8e, 0x2d, - 0x3c, 0x74, 0xff, 0x3b, 0xc0, 0x3f, 0xc4, 0xa8, 0xf2, 0x4c, 0x40, 0x2f, - 0x13, 0x97, 0x01, 0xb8, 0x29, 0x1f, 0x8f, 0x04, 0xfb, 0xd7, 0xaa, 0x94, - 0x3b, 0x31, 0x54, 0xcc, 0x58, 0x19, 0x60, 0xb1, 0xe7, 0x16, 0x24, 0x0b, - 0x65, 0xe9, 0x19, 0x51, 0xb5, 0x14, 0x95, 0x66, 0x3f, 0x0b, 0x05, 0x3d, - 0x0a, 0xfd, 0x14, 0xb7, 0x1a, 0x90, 0xe8, 0xe6, 0xbc, 0xdf, 0x9f, 0xd4, - 0x83, 0xcf, 0xe7, 0xd4, 0x1c, 0x17, 0xe8, 0x13, 0xdb, 0x99, 0xb7, 0x16, - 0x7b, 0x66, 0x35, 0xf6, 0x56, 0x92, 0x9c, 0x35, 0xa0, 0xe0, 0x90, 0x4d, - 0x94, 0x5d, 0x82, 0xc8, 0xff, 0x4d, 0xef, 0x98, 0xcf, 0xb5, 0x6f, 0x6b, - 0x55, 0xf8, 0xd4, 0x4a, 0xa2, 0x84, 0x3c, 0xec, 0x1a}; + 0x28, 0xd2, 0x85, 0xd8, 0xcc, 0x04, 0x48, 0x01, 0x12, 0x80, 0x03, 0x06, + 0xe2, 0xc2, 0x94, 0x0e, 0x81, 0x87, 0x59, 0xe3, 0xe8, 0x15, 0x7f, 0xc6, + 0xff, 0x6b, 0xc8, 0x7e, 0x0c, 0xd9, 0x9b, 0x40, 0x34, 0x22, 0x44, 0x00, + 0xdf, 0x0e, 0x9e, 0xcd, 0xb9, 0x1d, 0x3d, 0xfe, 0x5a, 0xb9, 0x28, 0xdc, + 0x94, 0x43, 0xc4, 0x1c, 0x66, 0xa9, 0x8a, 0xa4, 0x61, 0xdf, 0x8a, 0xf3, + 0x7c, 0xf0, 0xbe, 0x66, 0xe9, 0xdf, 0x65, 0x93, 0x6c, 0xc7, 0xb5, 0x1a, + 0x76, 0x07, 0x40, 0xde, 0xa1, 0xc5, 0x40, 0xde, 0xac, 0x5b, 0x9f, 0x32, + 0xbb, 0xd4, 0xf2, 0x09, 0x13, 0x20, 0xbe, 0xee, 0xf4, 0xb5, 0xb0, 0xec, + 0xeb, 0x1e, 0xfa, 0x03, 0x1b, 0x9d, 0x5a, 0xa0, 0x2f, 0x71, 0x1a, 0x76, + 0xe7, 0x6f, 0x71, 0x7d, 0x3a, 0x7d, 0x8c, 0x46, 0xaf, 0x93, 0x94, 0x47, + 0x27, 0xec, 0x1b, 0x1e, 0xd7, 0x8c, 0x7c, 0xec, 0x42, 0xaf, 0x55, 0x82, + 0x3b, 0x6d, 0x07, 0x24, 0xb3, 0xfa, 0x2d, 0x1e, 0x12, 0x02, 0x94, 0x04, + 0x23, 0xeb, 0xf3, 0x74, 0x04, 0x7e, 0x2a, 0x7f, 0x00, 0x34, 0x2b, 0x5c, + 0x5b, 0x10, 0xe7, 0x36, 0x52, 0xde, 0x9f, 0x56, 0x10, 0xe3, 0x0b, 0xa5, + 0x29, 0x85, 0xa5, 0x95, 0xed, 0xf5, 0x39, 0x0a, 0x03, 0x51, 0x29, 0x64, + 0xa1, 0x4f, 0x38, 0xde, 0x3b, 0x4d, 0x0a, 0xf3, 0x7e, 0x37, 0x14, 0xce, + 0xdf, 0x9d, 0x86, 0x16, 0xad, 0x62, 0xa8, 0xf8, 0xa7, 0xc2, 0xa4, 0xc1, + 0xe2, 0xd6, 0x40, 0xa4, 0x7b, 0x20, 0x1b, 0x6d, 0x7c, 0x97, 0x0b, 0x73, + 0x85, 0xbf, 0xdb, 0xc3, 0xa1, 0xf5, 0xd4, 0xb7, 0x95, 0xf2, 0xe7, 0x10, + 0x77, 0xc6, 0x82, 0xb2, 0x68, 0x24, 0x31, 0xdc, 0x69, 0x43, 0x56, 0xf5, + 0x76, 0x20, 0x0a, 0x82, 0x1a, 0x98, 0xb3, 0x02, 0x0f, 0x67, 0xcd, 0x4f, + 0xab, 0x43, 0x44, 0xbd, 0xdb, 0x07, 0xd3, 0xff, 0x8b, 0x68, 0x33, 0x24, + 0x35, 0xe5, 0xc6, 0x1a, 0x94, 0x14, 0x4f, 0x40, 0xef, 0x92, 0xfb, 0xfd, + 0x72, 0x15, 0xd4, 0x10, 0x60, 0x22, 0x3e, 0x60, 0x49, 0x3d, 0x58, 0xc6, + 0x3d, 0x28, 0x70, 0x55, 0x32, 0xd5, 0x78, 0x03, 0x51, 0xff, 0xd6, 0x4f, + 0x4e, 0x89, 0x0e, 0x50, 0x85, 0x6e, 0x1c, 0x6a, 0x5f, 0x11, 0xd0, 0xf5, + 0xee, 0xe5, 0x1c, 0xa8, 0xb2, 0xdb, 0x26, 0x93, 0xb1, 0xe2, 0xc1, 0x05, + 0xe0, 0x7f, 0x16, 0xe7, 0x9c, 0xcf, 0xe7, 0xb7, 0x7e, 0xaa, 0x96, 0x21, + 0x64, 0x39, 0x6d, 0x7a, 0xdc, 0x70, 0x6e, 0xc8, 0xf5, 0x44, 0x2e, 0x9f, + 0xc1, 0xe9, 0x46, 0x8c, 0x1b, 0x58, 0xec, 0x73, 0x1b, 0x9a, 0x04, 0xcb, + 0x68, 0x58, 0x21, 0x0e, 0xd6, 0xd7, 0x7a, 0x2b, 0x60, 0x02, 0x20, 0x7b, + 0x85, 0xe5, 0x84, 0x2c, 0x5f, 0x24, 0x90, 0x2d, 0xc5, 0x19, 0xea, 0xf3, + 0x91, 0x78, 0xc2, 0xa7, 0x36, 0x5a, 0x72, 0x64, 0x45, 0x13, 0x49, +}; + +const unsigned char kTestIntermediateCertificateWithECKey[] = { + 0x0a, 0x9a, 0x01, 0x08, 0x01, 0x12, 0x10, 0x30, 0x31, 0x32, 0x33, 0x34, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x18, + 0xb2, 0x92, 0x04, 0x22, 0x78, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, + 0x22, 0x03, 0x62, 0x00, 0x04, 0xb0, 0x50, 0x2a, 0x13, 0x20, 0x3e, 0x66, + 0x67, 0xdf, 0x11, 0x2a, 0xbc, 0x0f, 0x76, 0x69, 0x4b, 0xa1, 0x88, 0xec, + 0xb8, 0x71, 0xcf, 0xc9, 0xbb, 0xd2, 0xbc, 0xf8, 0x53, 0xfd, 0x8b, 0x8d, + 0x14, 0x6f, 0xda, 0xea, 0x60, 0x51, 0xc8, 0xd3, 0x3a, 0xd4, 0x75, 0x81, + 0x05, 0x16, 0x03, 0x0b, 0xcb, 0x33, 0x2c, 0x8b, 0xe6, 0xd3, 0x57, 0x6c, + 0xfb, 0x81, 0x4b, 0xfe, 0x79, 0x56, 0xf7, 0x6a, 0x2b, 0xca, 0xa7, 0x04, + 0xe9, 0x37, 0xd6, 0x49, 0xe5, 0x8b, 0x2c, 0xe9, 0x8e, 0xcd, 0xe7, 0xe3, + 0xc9, 0xf5, 0x4c, 0x90, 0x82, 0x5f, 0xf0, 0x53, 0xd2, 0xa4, 0x1a, 0xb3, + 0x53, 0x3d, 0xa7, 0xa7, 0xfd, 0x28, 0xd2, 0x85, 0xd8, 0xcc, 0x04, 0x48, + 0x03, 0x12, 0x80, 0x03, 0x42, 0x90, 0xc4, 0x87, 0x0b, 0x55, 0x78, 0xb5, + 0x25, 0x64, 0x23, 0xf2, 0x6a, 0x28, 0x7b, 0x1e, 0x12, 0xeb, 0x94, 0x08, + 0x4f, 0xce, 0x6b, 0x53, 0x35, 0xda, 0xa6, 0xf3, 0x90, 0x3b, 0x1b, 0xa8, + 0x2f, 0x17, 0x8e, 0x09, 0x12, 0x4b, 0xc6, 0x10, 0xfc, 0x8a, 0x63, 0xda, + 0xf6, 0x7e, 0x18, 0x3e, 0x49, 0x4c, 0x85, 0x5b, 0x2c, 0xcb, 0x09, 0x67, + 0x3b, 0xd3, 0xf3, 0x90, 0xe7, 0x4e, 0x06, 0x2f, 0x25, 0xbe, 0x22, 0x7f, + 0xd6, 0x5c, 0xd5, 0xda, 0xac, 0x60, 0x29, 0x83, 0x53, 0x54, 0x73, 0x0d, + 0x96, 0xca, 0x50, 0x6e, 0x45, 0xd6, 0x3c, 0xe6, 0xab, 0xf7, 0xe8, 0x69, + 0x9e, 0xe2, 0x8e, 0xfd, 0x46, 0x11, 0x40, 0x9d, 0xfc, 0xd8, 0x2d, 0xe0, + 0x94, 0xbc, 0x05, 0x74, 0x3c, 0x0a, 0xdd, 0x64, 0x37, 0x93, 0x9d, 0x5a, + 0x08, 0xfe, 0x5f, 0xae, 0xa3, 0xc6, 0x48, 0xbd, 0x8a, 0x4e, 0x3c, 0xac, + 0x7c, 0x66, 0xad, 0xc4, 0x7b, 0x7b, 0x89, 0xd9, 0xae, 0xf5, 0x8d, 0xf3, + 0x0e, 0x3b, 0x1c, 0xb6, 0xf0, 0xff, 0x52, 0x22, 0xbc, 0xdd, 0x1e, 0xe5, + 0x90, 0xe1, 0x09, 0xe2, 0x65, 0x42, 0x70, 0x4b, 0xfa, 0xf0, 0x41, 0x41, + 0x53, 0xa2, 0x2c, 0x32, 0xc4, 0x1a, 0x42, 0x0d, 0xbe, 0x8d, 0x5b, 0x14, + 0xae, 0x8f, 0xca, 0x85, 0xda, 0xfb, 0xe1, 0x25, 0x71, 0xc6, 0x8a, 0x3c, + 0xe1, 0x99, 0x09, 0x30, 0x9d, 0xa7, 0xec, 0x10, 0x7b, 0x43, 0xee, 0xf8, + 0xa5, 0x58, 0x9d, 0xd7, 0x31, 0x6a, 0x22, 0x45, 0xf5, 0x0c, 0x30, 0xb2, + 0x77, 0x05, 0x13, 0x10, 0xfd, 0xc1, 0xf9, 0x13, 0xc2, 0x88, 0xc8, 0x71, + 0xd9, 0x14, 0x5f, 0xc9, 0xde, 0x96, 0xe7, 0x55, 0xb9, 0x4a, 0xb0, 0x18, + 0x22, 0x17, 0x9f, 0x95, 0xe2, 0xa7, 0x66, 0x9b, 0xfd, 0x38, 0xf9, 0x5c, + 0xa0, 0xaa, 0xf4, 0x60, 0xee, 0x00, 0x53, 0x87, 0x29, 0x63, 0x53, 0x55, + 0xfb, 0x32, 0x7a, 0x80, 0x56, 0xea, 0xaa, 0x95, 0x22, 0x08, 0x9e, 0x25, + 0x53, 0x50, 0xb4, 0xd0, 0x07, 0x3c, 0x29, 0x3f, 0x03, 0xab, 0x68, 0xf5, + 0xa5, 0xc6, 0xd2, 0x73, 0xf6, 0xee, 0xa2, 0x6c, 0xec, 0xd4, 0xf3, 0x20, + 0xdc, 0x56, 0x00, 0x3d, 0xea, 0x57, 0x14, 0xc7, 0x90, 0x86, 0x82, 0x1b, + 0x14, 0x57, 0x68, 0xec, 0x24, 0x0e, 0x8d, 0x6b, 0xcc, 0x5f, 0x7a, 0x53, + 0xac, 0x60, 0x20, 0x5f, 0xe7, 0x79, 0xb6, 0x2c, 0xfb, 0x23, 0xa3, 0x43, + 0x91, 0x0f, 0x53, 0x38, 0x0e, 0xcc, 0x27, 0xaf, 0x57, 0x01, 0x26, 0xd8, + 0x01, 0x41, 0x27, 0x63, 0xca, 0x9f, 0xf5, 0xa7, 0x43, 0x26, 0x74, 0x59, + 0xec, 0xce, 0x71, 0x09, 0x0d, 0xda, 0x5d, 0x63, 0xef, 0xfd, 0x6e, 0x92, + 0x53, 0x12, 0xbc, 0x6a, 0x5b, 0x4d, 0x4a, 0x43, 0x04, 0x5d, 0x8e, 0x93, + 0xd7, 0x89, 0x21, 0xff, +}; const unsigned char kTestUserDrmCertificate[] = { - 0x0a, 0xc1, 0x02, 0x08, 0x02, 0x12, 0x10, 0x46, 0x45, 0x44, 0x43, 0x42, + 0x0a, 0xc3, 0x02, 0x08, 0x02, 0x12, 0x10, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, 0x18, 0x91, 0xab, 0x4b, 0x22, 0x8e, 0x02, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa5, 0xd0, 0xd7, 0x3e, 0x0e, 0x2d, 0xfb, 0x43, 0x51, @@ -169,89 +220,282 @@ const unsigned char kTestUserDrmCertificate[] = { 0x49, 0x98, 0x7b, 0x6f, 0xdd, 0x69, 0x6d, 0x02, 0x03, 0x01, 0x00, 0x01, 0x28, 0xd2, 0x85, 0xd8, 0xcc, 0x04, 0x3a, 0x10, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x63, 0x6f, 0x6d, - 0x12, 0x80, 0x02, 0x62, 0xd5, 0x8b, 0xb6, 0x52, 0x94, 0xcb, 0x25, 0xba, - 0x68, 0x44, 0xdc, 0x6f, 0x03, 0xf6, 0x24, 0xc5, 0xba, 0x46, 0xd1, 0xa1, - 0x83, 0xe7, 0xaf, 0x94, 0x96, 0x8b, 0x57, 0x89, 0xd6, 0xa0, 0x99, 0x6f, - 0xed, 0xac, 0xfe, 0x6c, 0x9d, 0x80, 0x1c, 0xae, 0x34, 0xda, 0x49, 0x4f, - 0x10, 0x22, 0x3c, 0xdd, 0x77, 0xa0, 0x9a, 0x79, 0x73, 0x68, 0x66, 0xa4, - 0x6d, 0x1e, 0x82, 0xbf, 0xce, 0x06, 0x1a, 0x83, 0xcd, 0xa3, 0xed, 0x91, - 0xbe, 0xb1, 0xfe, 0xf3, 0xde, 0x63, 0x96, 0xd5, 0x24, 0x44, 0x46, 0x94, - 0x7f, 0xc2, 0x14, 0x19, 0x42, 0x08, 0x64, 0xef, 0x93, 0x81, 0x7a, 0x54, - 0x8b, 0x6e, 0xd9, 0xf5, 0x14, 0x88, 0x6c, 0x39, 0x6f, 0x0f, 0x70, 0x91, - 0x97, 0xd4, 0x24, 0x73, 0x9d, 0x12, 0x7a, 0xc8, 0x83, 0xd7, 0x2b, 0xc7, - 0xb7, 0xe1, 0x20, 0x6c, 0x28, 0x11, 0x6f, 0x56, 0x82, 0xf6, 0x1c, 0x4f, - 0x2d, 0x51, 0x0f, 0xd6, 0xd4, 0x14, 0xea, 0xac, 0x28, 0x66, 0xeb, 0x37, - 0xca, 0x00, 0x49, 0xff, 0xed, 0x8e, 0x8c, 0x3e, 0x4b, 0x9b, 0x12, 0x0e, - 0xbf, 0xcd, 0xb7, 0xe6, 0xed, 0xd6, 0x1f, 0x88, 0xe8, 0x99, 0x68, 0x1a, - 0xf8, 0xbb, 0xa2, 0x33, 0xfa, 0xb6, 0x21, 0xdf, 0xba, 0x24, 0x5c, 0x19, - 0xa2, 0xe7, 0x6f, 0x61, 0x90, 0x78, 0x21, 0xca, 0x2f, 0x84, 0xab, 0x9f, - 0xff, 0x37, 0x14, 0x33, 0x83, 0x43, 0x98, 0xeb, 0xa9, 0x88, 0xde, 0xad, - 0x3a, 0xd9, 0xe2, 0x5c, 0x26, 0xd3, 0x95, 0x72, 0xba, 0x8c, 0x77, 0xdf, - 0x90, 0x67, 0x4e, 0xbc, 0xda, 0x83, 0x09, 0x22, 0x70, 0x51, 0x84, 0x70, - 0x31, 0x25, 0x8b, 0xae, 0x5e, 0x19, 0xba, 0x97, 0xd7, 0x1f, 0x6a, 0xd7, - 0x95, 0xcf, 0xde, 0x8f, 0x93, 0x69, 0x88, 0x11, 0xbe, 0x8c, 0x6a, 0xfb, - 0x3c, 0x13, 0x87, 0x0e, 0x6c, 0xa5, 0xa0, 0x1a, 0xb5, 0x05, 0x0a, 0xaf, - 0x02, 0x08, 0x01, 0x12, 0x10, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, - 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x18, 0xb2, 0x92, - 0x04, 0x22, 0x8e, 0x02, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, - 0x00, 0xa7, 0x00, 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, 0x5a, 0x2a, 0x40, - 0xb4, 0xe1, 0x15, 0x94, 0x58, 0x11, 0x4f, 0x94, 0x58, 0xdd, 0xde, 0xa7, - 0x1f, 0x3c, 0x2c, 0xe0, 0x88, 0x09, 0x29, 0x61, 0x57, 0x67, 0x5e, 0x56, - 0x7e, 0xee, 0x27, 0x8f, 0x59, 0x34, 0x9a, 0x2a, 0xaa, 0x9d, 0xb4, 0x4e, - 0xfa, 0xa7, 0x6a, 0xd4, 0xc9, 0x7a, 0x53, 0xc1, 0x4e, 0x9f, 0xe3, 0x34, - 0xf7, 0x3d, 0xb7, 0xc9, 0x10, 0x47, 0x4f, 0x28, 0xda, 0x3f, 0xce, 0x31, - 0x7b, 0xfd, 0x06, 0x10, 0xeb, 0xf7, 0xbe, 0x92, 0xf9, 0xaf, 0xfb, 0x3e, - 0x68, 0xda, 0xee, 0x1a, 0x64, 0x4c, 0xf3, 0x29, 0xf2, 0x73, 0x9e, 0x39, - 0xd8, 0xf6, 0x6f, 0xd8, 0xb2, 0x80, 0x82, 0x71, 0x8e, 0xb5, 0xa4, 0xf2, - 0xc2, 0x3e, 0xcd, 0x0a, 0xca, 0xb6, 0x04, 0xcd, 0x9a, 0x13, 0x8b, 0x54, - 0x73, 0x54, 0x25, 0x54, 0x8c, 0xbe, 0x98, 0x7a, 0x67, 0xad, 0xda, 0xb3, - 0x4e, 0xb3, 0xfa, 0x82, 0xa8, 0x4a, 0x67, 0x98, 0x56, 0x57, 0x54, 0x71, - 0xcd, 0x12, 0x7f, 0xed, 0xa3, 0x01, 0xc0, 0x6a, 0x8b, 0x24, 0x03, 0x96, - 0x88, 0xbe, 0x97, 0x66, 0x2a, 0xbc, 0x53, 0xc9, 0x83, 0x06, 0x51, 0x5a, - 0x88, 0x65, 0x13, 0x18, 0xe4, 0x3a, 0xed, 0x6b, 0xf1, 0x61, 0x5b, 0x4c, - 0xc8, 0x1e, 0xf4, 0xc2, 0xae, 0x08, 0x5e, 0x2d, 0x5f, 0xf8, 0x12, 0x7f, - 0xa2, 0xfc, 0xbb, 0x21, 0x18, 0x30, 0xda, 0xfe, 0x40, 0xfb, 0x01, 0xca, - 0x2e, 0x37, 0x0e, 0xce, 0xdd, 0x76, 0x87, 0x82, 0x46, 0x0b, 0x3a, 0x77, - 0x8f, 0xc0, 0x72, 0x07, 0x2c, 0x7f, 0x9d, 0x1e, 0x86, 0x5b, 0xed, 0x27, - 0x29, 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, 0xd3, 0x5b, 0x3d, 0xdb, 0x9c, - 0x5e, 0x1b, 0x7b, 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, 0xbb, 0xbb, 0x2c, - 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, 0x01, 0x00, 0x01, 0x28, 0xd2, - 0x85, 0xd8, 0xcc, 0x04, 0x12, 0x80, 0x03, 0x7b, 0xd3, 0x40, 0xa8, 0xd0, - 0x31, 0x1e, 0x95, 0x35, 0xdd, 0xb3, 0x20, 0xcf, 0xc2, 0xcf, 0xc9, 0x26, - 0x49, 0x53, 0xc8, 0x58, 0xd5, 0x12, 0xf0, 0x71, 0xf4, 0xd4, 0x33, 0x8e, - 0xd7, 0x6f, 0x79, 0xbe, 0x17, 0xeb, 0x36, 0x71, 0xf2, 0x3b, 0xc3, 0x4f, - 0x3a, 0xeb, 0xc7, 0xfb, 0xf6, 0x40, 0xf8, 0xe6, 0xe4, 0x51, 0xce, 0x45, - 0x5c, 0xf0, 0x66, 0xd1, 0x22, 0x55, 0x72, 0xcd, 0x50, 0xb4, 0x5a, 0x02, - 0x2f, 0xb7, 0x11, 0x24, 0x61, 0x12, 0x9f, 0x80, 0x5f, 0xc9, 0xee, 0xc9, - 0xd4, 0x7b, 0x62, 0x76, 0x34, 0xdd, 0x45, 0xae, 0x42, 0xbb, 0x1f, 0x7a, - 0x18, 0x85, 0xc7, 0xcf, 0xc9, 0x86, 0x47, 0xfd, 0x23, 0xd9, 0x26, 0xbe, - 0x47, 0x3e, 0x80, 0x45, 0x41, 0x39, 0x92, 0xe4, 0x0e, 0x25, 0xdb, 0x85, - 0x35, 0x77, 0x34, 0x3a, 0x67, 0xbf, 0xea, 0xfa, 0x84, 0xba, 0xb9, 0x3d, - 0x03, 0x89, 0xa8, 0x13, 0x9f, 0x35, 0xa1, 0x12, 0x0e, 0x80, 0x12, 0x72, - 0x24, 0x4e, 0xc2, 0x6d, 0x2b, 0x77, 0x19, 0xb8, 0xa1, 0x98, 0xab, 0x73, - 0x43, 0x79, 0xf6, 0x7b, 0x9e, 0xc9, 0x4f, 0xb8, 0xb5, 0xf1, 0x75, 0x79, - 0x7a, 0x48, 0x01, 0x0e, 0xb6, 0xb9, 0x3e, 0x46, 0xf0, 0x98, 0xe8, 0x40, - 0x6a, 0x60, 0xeb, 0x8f, 0x51, 0x78, 0x31, 0x5c, 0xe1, 0x0f, 0x6f, 0x23, - 0x36, 0xf3, 0xd4, 0x7a, 0x68, 0x74, 0x32, 0x3c, 0xf6, 0x30, 0xaa, 0xcf, - 0x4f, 0xb7, 0xdf, 0xc4, 0xe0, 0x1b, 0x8c, 0xa8, 0x2b, 0x1b, 0x7f, 0x91, - 0xf9, 0x98, 0xb9, 0xac, 0xf4, 0x50, 0x3e, 0xc1, 0x1c, 0x7a, 0x98, 0xad, - 0x88, 0x68, 0xe6, 0xe8, 0x4f, 0x8b, 0x5f, 0xf7, 0xf6, 0x0e, 0x6e, 0x9d, - 0xe1, 0x55, 0xe2, 0xf7, 0x5b, 0x2c, 0x73, 0x5e, 0x77, 0x04, 0x4f, 0x32, - 0x5d, 0x13, 0x51, 0x8f, 0x1a, 0x53, 0xad, 0xff, 0x1e, 0x52, 0xfc, 0xcc, - 0xa5, 0x80, 0x92, 0x9b, 0x89, 0x64, 0x18, 0x49, 0xd9, 0xaa, 0xb3, 0x77, - 0xf3, 0x60, 0x4c, 0x6e, 0x9f, 0x0d, 0xf0, 0xbc, 0x8e, 0x2d, 0x3c, 0x74, - 0xff, 0x3b, 0xc0, 0x3f, 0xc4, 0xa8, 0xf2, 0x4c, 0x40, 0x2f, 0x13, 0x97, - 0x01, 0xb8, 0x29, 0x1f, 0x8f, 0x04, 0xfb, 0xd7, 0xaa, 0x94, 0x3b, 0x31, - 0x54, 0xcc, 0x58, 0x19, 0x60, 0xb1, 0xe7, 0x16, 0x24, 0x0b, 0x65, 0xe9, - 0x19, 0x51, 0xb5, 0x14, 0x95, 0x66, 0x3f, 0x0b, 0x05, 0x3d, 0x0a, 0xfd, - 0x14, 0xb7, 0x1a, 0x90, 0xe8, 0xe6, 0xbc, 0xdf, 0x9f, 0xd4, 0x83, 0xcf, - 0xe7, 0xd4, 0x1c, 0x17, 0xe8, 0x13, 0xdb, 0x99, 0xb7, 0x16, 0x7b, 0x66, - 0x35, 0xf6, 0x56, 0x92, 0x9c, 0x35, 0xa0, 0xe0, 0x90, 0x4d, 0x94, 0x5d, - 0x82, 0xc8, 0xff, 0x4d, 0xef, 0x98, 0xcf, 0xb5, 0x6f, 0x6b, 0x55, 0xf8, - 0xd4, 0x4a, 0xa2, 0x84, 0x3c, 0xec, 0x1a}; + 0x48, 0x01, 0x12, 0x80, 0x02, 0x23, 0x61, 0xfb, 0xd0, 0xf4, 0xcf, 0xf2, + 0x58, 0xd7, 0xb0, 0x79, 0x4e, 0x4e, 0xf3, 0x2c, 0x83, 0x63, 0x34, 0x6c, + 0x49, 0x80, 0xdd, 0x85, 0xf4, 0xa5, 0x23, 0x89, 0x95, 0x0c, 0x8f, 0xf6, + 0xc6, 0xdc, 0x90, 0x8b, 0x83, 0xd3, 0x0b, 0x1c, 0x34, 0xd2, 0xa0, 0x08, + 0xdc, 0x05, 0x76, 0x8f, 0xff, 0xa3, 0x2e, 0xf8, 0x93, 0x9e, 0xe6, 0xf3, + 0x62, 0x0f, 0x70, 0x1c, 0x31, 0x15, 0xbb, 0x98, 0xf4, 0xa6, 0x22, 0x2c, + 0x90, 0x59, 0xc2, 0x16, 0x48, 0xe0, 0x5a, 0xb8, 0x94, 0x6f, 0xde, 0x80, + 0xaf, 0x83, 0x8e, 0x77, 0x6a, 0xa4, 0xf4, 0x9b, 0xf8, 0x76, 0xd1, 0x1b, + 0x6d, 0x87, 0x85, 0x35, 0xd9, 0xd0, 0x62, 0x55, 0xfe, 0x11, 0xed, 0x4a, + 0x6c, 0xc9, 0x14, 0x67, 0x72, 0xb6, 0x46, 0x56, 0xbc, 0x81, 0xac, 0xe6, + 0xf0, 0x7a, 0x0e, 0x57, 0x95, 0x4d, 0x53, 0xf5, 0x33, 0x2e, 0xa5, 0x7e, + 0x71, 0x8e, 0x04, 0x64, 0x50, 0x88, 0x6b, 0xb9, 0x6e, 0xbc, 0x6b, 0x74, + 0xfc, 0x69, 0xa3, 0x81, 0x30, 0x1f, 0xac, 0x9d, 0x7b, 0xa0, 0xf5, 0x7f, + 0x42, 0xfd, 0x14, 0xca, 0x89, 0x5b, 0xb0, 0xcd, 0xa2, 0x4b, 0xef, 0xcf, + 0x84, 0x8f, 0xe8, 0xe4, 0xf7, 0xd2, 0x63, 0xe2, 0x95, 0x94, 0x45, 0xd5, + 0xc2, 0xe3, 0x99, 0xfc, 0x34, 0xcb, 0x6a, 0x15, 0x74, 0x6e, 0x16, 0xe3, + 0x6f, 0x8e, 0xe7, 0x9b, 0x01, 0xed, 0x7f, 0xf8, 0x90, 0xc6, 0x87, 0xf4, + 0x9e, 0x45, 0x64, 0x09, 0xf9, 0xaa, 0x46, 0xe4, 0x83, 0x3b, 0x4f, 0x36, + 0xdb, 0x32, 0x72, 0x00, 0xcf, 0x3c, 0x4c, 0x41, 0x67, 0x59, 0xf2, 0x93, + 0xff, 0x4e, 0x07, 0x22, 0x6e, 0x5a, 0x03, 0xf5, 0xe1, 0x48, 0x72, 0x9d, + 0x2f, 0xfc, 0xcd, 0x38, 0x5f, 0x2d, 0x69, 0x47, 0xd3, 0xa8, 0x09, 0x8e, + 0xd5, 0x9b, 0x24, 0x45, 0x43, 0x08, 0xca, 0xc6, 0xed, 0x1a, 0xb7, 0x05, + 0x0a, 0xb1, 0x02, 0x08, 0x01, 0x12, 0x10, 0x30, 0x31, 0x32, 0x33, 0x34, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x18, + 0xb2, 0x92, 0x04, 0x22, 0x8e, 0x02, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, + 0x01, 0x01, 0x00, 0xa7, 0x00, 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, 0x5a, + 0x2a, 0x40, 0xb4, 0xe1, 0x15, 0x94, 0x58, 0x11, 0x4f, 0x94, 0x58, 0xdd, + 0xde, 0xa7, 0x1f, 0x3c, 0x2c, 0xe0, 0x88, 0x09, 0x29, 0x61, 0x57, 0x67, + 0x5e, 0x56, 0x7e, 0xee, 0x27, 0x8f, 0x59, 0x34, 0x9a, 0x2a, 0xaa, 0x9d, + 0xb4, 0x4e, 0xfa, 0xa7, 0x6a, 0xd4, 0xc9, 0x7a, 0x53, 0xc1, 0x4e, 0x9f, + 0xe3, 0x34, 0xf7, 0x3d, 0xb7, 0xc9, 0x10, 0x47, 0x4f, 0x28, 0xda, 0x3f, + 0xce, 0x31, 0x7b, 0xfd, 0x06, 0x10, 0xeb, 0xf7, 0xbe, 0x92, 0xf9, 0xaf, + 0xfb, 0x3e, 0x68, 0xda, 0xee, 0x1a, 0x64, 0x4c, 0xf3, 0x29, 0xf2, 0x73, + 0x9e, 0x39, 0xd8, 0xf6, 0x6f, 0xd8, 0xb2, 0x80, 0x82, 0x71, 0x8e, 0xb5, + 0xa4, 0xf2, 0xc2, 0x3e, 0xcd, 0x0a, 0xca, 0xb6, 0x04, 0xcd, 0x9a, 0x13, + 0x8b, 0x54, 0x73, 0x54, 0x25, 0x54, 0x8c, 0xbe, 0x98, 0x7a, 0x67, 0xad, + 0xda, 0xb3, 0x4e, 0xb3, 0xfa, 0x82, 0xa8, 0x4a, 0x67, 0x98, 0x56, 0x57, + 0x54, 0x71, 0xcd, 0x12, 0x7f, 0xed, 0xa3, 0x01, 0xc0, 0x6a, 0x8b, 0x24, + 0x03, 0x96, 0x88, 0xbe, 0x97, 0x66, 0x2a, 0xbc, 0x53, 0xc9, 0x83, 0x06, + 0x51, 0x5a, 0x88, 0x65, 0x13, 0x18, 0xe4, 0x3a, 0xed, 0x6b, 0xf1, 0x61, + 0x5b, 0x4c, 0xc8, 0x1e, 0xf4, 0xc2, 0xae, 0x08, 0x5e, 0x2d, 0x5f, 0xf8, + 0x12, 0x7f, 0xa2, 0xfc, 0xbb, 0x21, 0x18, 0x30, 0xda, 0xfe, 0x40, 0xfb, + 0x01, 0xca, 0x2e, 0x37, 0x0e, 0xce, 0xdd, 0x76, 0x87, 0x82, 0x46, 0x0b, + 0x3a, 0x77, 0x8f, 0xc0, 0x72, 0x07, 0x2c, 0x7f, 0x9d, 0x1e, 0x86, 0x5b, + 0xed, 0x27, 0x29, 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, 0xd3, 0x5b, 0x3d, + 0xdb, 0x9c, 0x5e, 0x1b, 0x7b, 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, 0xbb, + 0xbb, 0x2c, 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, 0x01, 0x00, 0x01, + 0x28, 0xd2, 0x85, 0xd8, 0xcc, 0x04, 0x48, 0x01, 0x12, 0x80, 0x03, 0x06, + 0xe2, 0xc2, 0x94, 0x0e, 0x81, 0x87, 0x59, 0xe3, 0xe8, 0x15, 0x7f, 0xc6, + 0xff, 0x6b, 0xc8, 0x7e, 0x0c, 0xd9, 0x9b, 0x40, 0x34, 0x22, 0x44, 0x00, + 0xdf, 0x0e, 0x9e, 0xcd, 0xb9, 0x1d, 0x3d, 0xfe, 0x5a, 0xb9, 0x28, 0xdc, + 0x94, 0x43, 0xc4, 0x1c, 0x66, 0xa9, 0x8a, 0xa4, 0x61, 0xdf, 0x8a, 0xf3, + 0x7c, 0xf0, 0xbe, 0x66, 0xe9, 0xdf, 0x65, 0x93, 0x6c, 0xc7, 0xb5, 0x1a, + 0x76, 0x07, 0x40, 0xde, 0xa1, 0xc5, 0x40, 0xde, 0xac, 0x5b, 0x9f, 0x32, + 0xbb, 0xd4, 0xf2, 0x09, 0x13, 0x20, 0xbe, 0xee, 0xf4, 0xb5, 0xb0, 0xec, + 0xeb, 0x1e, 0xfa, 0x03, 0x1b, 0x9d, 0x5a, 0xa0, 0x2f, 0x71, 0x1a, 0x76, + 0xe7, 0x6f, 0x71, 0x7d, 0x3a, 0x7d, 0x8c, 0x46, 0xaf, 0x93, 0x94, 0x47, + 0x27, 0xec, 0x1b, 0x1e, 0xd7, 0x8c, 0x7c, 0xec, 0x42, 0xaf, 0x55, 0x82, + 0x3b, 0x6d, 0x07, 0x24, 0xb3, 0xfa, 0x2d, 0x1e, 0x12, 0x02, 0x94, 0x04, + 0x23, 0xeb, 0xf3, 0x74, 0x04, 0x7e, 0x2a, 0x7f, 0x00, 0x34, 0x2b, 0x5c, + 0x5b, 0x10, 0xe7, 0x36, 0x52, 0xde, 0x9f, 0x56, 0x10, 0xe3, 0x0b, 0xa5, + 0x29, 0x85, 0xa5, 0x95, 0xed, 0xf5, 0x39, 0x0a, 0x03, 0x51, 0x29, 0x64, + 0xa1, 0x4f, 0x38, 0xde, 0x3b, 0x4d, 0x0a, 0xf3, 0x7e, 0x37, 0x14, 0xce, + 0xdf, 0x9d, 0x86, 0x16, 0xad, 0x62, 0xa8, 0xf8, 0xa7, 0xc2, 0xa4, 0xc1, + 0xe2, 0xd6, 0x40, 0xa4, 0x7b, 0x20, 0x1b, 0x6d, 0x7c, 0x97, 0x0b, 0x73, + 0x85, 0xbf, 0xdb, 0xc3, 0xa1, 0xf5, 0xd4, 0xb7, 0x95, 0xf2, 0xe7, 0x10, + 0x77, 0xc6, 0x82, 0xb2, 0x68, 0x24, 0x31, 0xdc, 0x69, 0x43, 0x56, 0xf5, + 0x76, 0x20, 0x0a, 0x82, 0x1a, 0x98, 0xb3, 0x02, 0x0f, 0x67, 0xcd, 0x4f, + 0xab, 0x43, 0x44, 0xbd, 0xdb, 0x07, 0xd3, 0xff, 0x8b, 0x68, 0x33, 0x24, + 0x35, 0xe5, 0xc6, 0x1a, 0x94, 0x14, 0x4f, 0x40, 0xef, 0x92, 0xfb, 0xfd, + 0x72, 0x15, 0xd4, 0x10, 0x60, 0x22, 0x3e, 0x60, 0x49, 0x3d, 0x58, 0xc6, + 0x3d, 0x28, 0x70, 0x55, 0x32, 0xd5, 0x78, 0x03, 0x51, 0xff, 0xd6, 0x4f, + 0x4e, 0x89, 0x0e, 0x50, 0x85, 0x6e, 0x1c, 0x6a, 0x5f, 0x11, 0xd0, 0xf5, + 0xee, 0xe5, 0x1c, 0xa8, 0xb2, 0xdb, 0x26, 0x93, 0xb1, 0xe2, 0xc1, 0x05, + 0xe0, 0x7f, 0x16, 0xe7, 0x9c, 0xcf, 0xe7, 0xb7, 0x7e, 0xaa, 0x96, 0x21, + 0x64, 0x39, 0x6d, 0x7a, 0xdc, 0x70, 0x6e, 0xc8, 0xf5, 0x44, 0x2e, 0x9f, + 0xc1, 0xe9, 0x46, 0x8c, 0x1b, 0x58, 0xec, 0x73, 0x1b, 0x9a, 0x04, 0xcb, + 0x68, 0x58, 0x21, 0x0e, 0xd6, 0xd7, 0x7a, 0x2b, 0x60, 0x02, 0x20, 0x7b, + 0x85, 0xe5, 0x84, 0x2c, 0x5f, 0x24, 0x90, 0x2d, 0xc5, 0x19, 0xea, 0xf3, + 0x91, 0x78, 0xc2, 0xa7, 0x36, 0x5a, 0x72, 0x64, 0x45, 0x13, 0x49, +}; -const unsigned char kTestDrmServiceCertificate[] = { - 0x0a, 0xbc, 0x02, 0x08, 0x03, 0x12, 0x10, 0x30, 0x30, 0x31, 0x31, 0x32, +const unsigned char kTestUserDrmCertificateWithECKey[] = { + 0x0a, 0x8f, 0x01, 0x08, 0x02, 0x12, 0x10, 0x46, 0x45, 0x44, 0x43, 0x42, + 0x41, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, 0x18, + 0x91, 0xab, 0x4b, 0x22, 0x5b, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0x8a, 0xea, 0x3f, 0x16, + 0xff, 0x24, 0xa9, 0xbf, 0x03, 0x28, 0x30, 0x15, 0xee, 0x52, 0x50, 0x9a, + 0x55, 0x1c, 0x60, 0xc7, 0xa7, 0xcc, 0x4b, 0x99, 0x5b, 0x40, 0x55, 0xce, + 0x46, 0x19, 0xd4, 0xd4, 0x5e, 0xfd, 0xe0, 0x68, 0x27, 0xea, 0x78, 0xf3, + 0x07, 0x1f, 0x02, 0x4a, 0x78, 0x52, 0x44, 0xd3, 0xdf, 0xbe, 0xac, 0x5f, + 0xa5, 0x1c, 0x8a, 0x49, 0x8d, 0xa6, 0x5a, 0xac, 0xa1, 0x25, 0x2b, 0xd1, + 0x28, 0xd2, 0x85, 0xd8, 0xcc, 0x04, 0x3a, 0x10, 0x73, 0x6f, 0x6d, 0x65, + 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + 0x48, 0x02, 0x12, 0x66, 0x30, 0x64, 0x02, 0x30, 0x4c, 0x7a, 0x79, 0x4a, + 0x18, 0xf2, 0x2f, 0x9f, 0x29, 0x3b, 0x6f, 0x8f, 0x8f, 0xe4, 0xe0, 0xf2, + 0xd9, 0x38, 0x18, 0x8a, 0x9a, 0x88, 0x85, 0x95, 0x72, 0xb7, 0x3c, 0xb6, + 0x47, 0xa4, 0x6b, 0x0a, 0x56, 0x4a, 0x38, 0x1d, 0x2f, 0x4a, 0xc6, 0x61, + 0x97, 0x35, 0x81, 0x87, 0x4b, 0xca, 0xdc, 0x20, 0x02, 0x30, 0x28, 0x4e, + 0xf1, 0x23, 0x6d, 0x3f, 0x4f, 0x29, 0x29, 0x86, 0x75, 0x46, 0x83, 0x03, + 0xa0, 0xe7, 0x23, 0x07, 0x2b, 0x28, 0x4d, 0xa9, 0x72, 0xb6, 0x5e, 0x3b, + 0xd2, 0x90, 0x05, 0xd3, 0x33, 0x35, 0x99, 0xdc, 0xe9, 0x54, 0xa0, 0x6e, + 0xca, 0x38, 0x63, 0x4d, 0x95, 0xab, 0x99, 0x77, 0x87, 0x38, 0x1a, 0xa0, + 0x04, 0x0a, 0x9a, 0x01, 0x08, 0x01, 0x12, 0x10, 0x30, 0x31, 0x32, 0x33, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, + 0x18, 0xb2, 0x92, 0x04, 0x22, 0x78, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, + 0x00, 0x22, 0x03, 0x62, 0x00, 0x04, 0xb0, 0x50, 0x2a, 0x13, 0x20, 0x3e, + 0x66, 0x67, 0xdf, 0x11, 0x2a, 0xbc, 0x0f, 0x76, 0x69, 0x4b, 0xa1, 0x88, + 0xec, 0xb8, 0x71, 0xcf, 0xc9, 0xbb, 0xd2, 0xbc, 0xf8, 0x53, 0xfd, 0x8b, + 0x8d, 0x14, 0x6f, 0xda, 0xea, 0x60, 0x51, 0xc8, 0xd3, 0x3a, 0xd4, 0x75, + 0x81, 0x05, 0x16, 0x03, 0x0b, 0xcb, 0x33, 0x2c, 0x8b, 0xe6, 0xd3, 0x57, + 0x6c, 0xfb, 0x81, 0x4b, 0xfe, 0x79, 0x56, 0xf7, 0x6a, 0x2b, 0xca, 0xa7, + 0x04, 0xe9, 0x37, 0xd6, 0x49, 0xe5, 0x8b, 0x2c, 0xe9, 0x8e, 0xcd, 0xe7, + 0xe3, 0xc9, 0xf5, 0x4c, 0x90, 0x82, 0x5f, 0xf0, 0x53, 0xd2, 0xa4, 0x1a, + 0xb3, 0x53, 0x3d, 0xa7, 0xa7, 0xfd, 0x28, 0xd2, 0x85, 0xd8, 0xcc, 0x04, + 0x48, 0x03, 0x12, 0x80, 0x03, 0x42, 0x90, 0xc4, 0x87, 0x0b, 0x55, 0x78, + 0xb5, 0x25, 0x64, 0x23, 0xf2, 0x6a, 0x28, 0x7b, 0x1e, 0x12, 0xeb, 0x94, + 0x08, 0x4f, 0xce, 0x6b, 0x53, 0x35, 0xda, 0xa6, 0xf3, 0x90, 0x3b, 0x1b, + 0xa8, 0x2f, 0x17, 0x8e, 0x09, 0x12, 0x4b, 0xc6, 0x10, 0xfc, 0x8a, 0x63, + 0xda, 0xf6, 0x7e, 0x18, 0x3e, 0x49, 0x4c, 0x85, 0x5b, 0x2c, 0xcb, 0x09, + 0x67, 0x3b, 0xd3, 0xf3, 0x90, 0xe7, 0x4e, 0x06, 0x2f, 0x25, 0xbe, 0x22, + 0x7f, 0xd6, 0x5c, 0xd5, 0xda, 0xac, 0x60, 0x29, 0x83, 0x53, 0x54, 0x73, + 0x0d, 0x96, 0xca, 0x50, 0x6e, 0x45, 0xd6, 0x3c, 0xe6, 0xab, 0xf7, 0xe8, + 0x69, 0x9e, 0xe2, 0x8e, 0xfd, 0x46, 0x11, 0x40, 0x9d, 0xfc, 0xd8, 0x2d, + 0xe0, 0x94, 0xbc, 0x05, 0x74, 0x3c, 0x0a, 0xdd, 0x64, 0x37, 0x93, 0x9d, + 0x5a, 0x08, 0xfe, 0x5f, 0xae, 0xa3, 0xc6, 0x48, 0xbd, 0x8a, 0x4e, 0x3c, + 0xac, 0x7c, 0x66, 0xad, 0xc4, 0x7b, 0x7b, 0x89, 0xd9, 0xae, 0xf5, 0x8d, + 0xf3, 0x0e, 0x3b, 0x1c, 0xb6, 0xf0, 0xff, 0x52, 0x22, 0xbc, 0xdd, 0x1e, + 0xe5, 0x90, 0xe1, 0x09, 0xe2, 0x65, 0x42, 0x70, 0x4b, 0xfa, 0xf0, 0x41, + 0x41, 0x53, 0xa2, 0x2c, 0x32, 0xc4, 0x1a, 0x42, 0x0d, 0xbe, 0x8d, 0x5b, + 0x14, 0xae, 0x8f, 0xca, 0x85, 0xda, 0xfb, 0xe1, 0x25, 0x71, 0xc6, 0x8a, + 0x3c, 0xe1, 0x99, 0x09, 0x30, 0x9d, 0xa7, 0xec, 0x10, 0x7b, 0x43, 0xee, + 0xf8, 0xa5, 0x58, 0x9d, 0xd7, 0x31, 0x6a, 0x22, 0x45, 0xf5, 0x0c, 0x30, + 0xb2, 0x77, 0x05, 0x13, 0x10, 0xfd, 0xc1, 0xf9, 0x13, 0xc2, 0x88, 0xc8, + 0x71, 0xd9, 0x14, 0x5f, 0xc9, 0xde, 0x96, 0xe7, 0x55, 0xb9, 0x4a, 0xb0, + 0x18, 0x22, 0x17, 0x9f, 0x95, 0xe2, 0xa7, 0x66, 0x9b, 0xfd, 0x38, 0xf9, + 0x5c, 0xa0, 0xaa, 0xf4, 0x60, 0xee, 0x00, 0x53, 0x87, 0x29, 0x63, 0x53, + 0x55, 0xfb, 0x32, 0x7a, 0x80, 0x56, 0xea, 0xaa, 0x95, 0x22, 0x08, 0x9e, + 0x25, 0x53, 0x50, 0xb4, 0xd0, 0x07, 0x3c, 0x29, 0x3f, 0x03, 0xab, 0x68, + 0xf5, 0xa5, 0xc6, 0xd2, 0x73, 0xf6, 0xee, 0xa2, 0x6c, 0xec, 0xd4, 0xf3, + 0x20, 0xdc, 0x56, 0x00, 0x3d, 0xea, 0x57, 0x14, 0xc7, 0x90, 0x86, 0x82, + 0x1b, 0x14, 0x57, 0x68, 0xec, 0x24, 0x0e, 0x8d, 0x6b, 0xcc, 0x5f, 0x7a, + 0x53, 0xac, 0x60, 0x20, 0x5f, 0xe7, 0x79, 0xb6, 0x2c, 0xfb, 0x23, 0xa3, + 0x43, 0x91, 0x0f, 0x53, 0x38, 0x0e, 0xcc, 0x27, 0xaf, 0x57, 0x01, 0x26, + 0xd8, 0x01, 0x41, 0x27, 0x63, 0xca, 0x9f, 0xf5, 0xa7, 0x43, 0x26, 0x74, + 0x59, 0xec, 0xce, 0x71, 0x09, 0x0d, 0xda, 0x5d, 0x63, 0xef, 0xfd, 0x6e, + 0x92, 0x53, 0x12, 0xbc, 0x6a, 0x5b, 0x4d, 0x4a, 0x43, 0x04, 0x5d, 0x8e, + 0x93, 0xd7, 0x89, 0x21, 0xff, +}; + +const unsigned char kTestUserDrmCertificateWithRotId[] = { + 0x0a, 0xdf, 0x03, 0x08, 0x02, 0x12, 0x10, 0x46, 0x45, 0x44, 0x43, 0x42, + 0x41, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, 0x18, + 0x91, 0xab, 0x4b, 0x22, 0x8e, 0x02, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, + 0x01, 0x01, 0x00, 0xa5, 0xd0, 0xd7, 0x3e, 0x0e, 0x2d, 0xfb, 0x43, 0x51, + 0x99, 0xea, 0x40, 0x1e, 0x2d, 0x89, 0xe4, 0xa2, 0x3e, 0xfc, 0x51, 0x3d, + 0x0e, 0x83, 0xa7, 0xe0, 0xa5, 0x41, 0x04, 0x1e, 0x14, 0xc5, 0xa7, 0x5c, + 0x61, 0x36, 0x44, 0xb3, 0x08, 0x05, 0x5b, 0x14, 0xde, 0x01, 0x0c, 0x32, + 0x3c, 0x9a, 0x91, 0x00, 0x50, 0xa8, 0x1d, 0xcc, 0x9f, 0x8f, 0x35, 0xb7, + 0xc2, 0x75, 0x08, 0x32, 0x8b, 0x10, 0x3a, 0x86, 0xf9, 0xd7, 0x78, 0xa3, + 0x9d, 0x74, 0x10, 0xc6, 0x24, 0xb1, 0x7f, 0xa5, 0xbf, 0x5f, 0xc2, 0xd7, + 0x15, 0xa3, 0x1d, 0xe0, 0x15, 0x6b, 0x1b, 0x0e, 0x38, 0xba, 0x34, 0xbc, + 0x95, 0x47, 0x94, 0x40, 0x70, 0xac, 0x99, 0x1f, 0x0b, 0x8e, 0x56, 0x93, + 0x36, 0x2b, 0x6d, 0x04, 0xe7, 0x95, 0x1a, 0x37, 0xda, 0x16, 0x57, 0x99, + 0xee, 0x03, 0x68, 0x16, 0x31, 0xaa, 0xc3, 0xb7, 0x92, 0x75, 0x53, 0xfc, + 0xf6, 0x20, 0x55, 0x44, 0xf8, 0xd4, 0x8d, 0x78, 0x15, 0xc7, 0x1a, 0xb6, + 0xde, 0x6c, 0xe8, 0x49, 0x5d, 0xaf, 0xa8, 0x4e, 0x6f, 0x7c, 0xe2, 0x6a, + 0x4c, 0xd5, 0xe7, 0x8c, 0x8f, 0x0b, 0x5d, 0x3a, 0x09, 0xd6, 0xb3, 0x44, + 0xab, 0xe0, 0x35, 0x52, 0x7c, 0x66, 0x85, 0xa4, 0x40, 0xd7, 0x20, 0xec, + 0x24, 0x05, 0x06, 0xd9, 0x84, 0x51, 0x5a, 0xd2, 0x38, 0xd5, 0x1d, 0xea, + 0x70, 0x2a, 0x21, 0xe6, 0x82, 0xfd, 0xa4, 0x46, 0x1c, 0x4f, 0x59, 0x6e, + 0x29, 0x3d, 0xae, 0xb8, 0x8e, 0xee, 0x77, 0x1f, 0x15, 0x33, 0xcf, 0x94, + 0x1d, 0x87, 0x3c, 0x37, 0xc5, 0x89, 0xe8, 0x7d, 0x85, 0xb3, 0xbc, 0xe8, + 0x62, 0x6a, 0x84, 0x7f, 0xfe, 0x9a, 0x85, 0x3f, 0x39, 0xe8, 0xaa, 0x16, + 0xa6, 0x8f, 0x87, 0x7f, 0xcb, 0xc1, 0xd6, 0xf2, 0xec, 0x2b, 0xa7, 0xdd, + 0x49, 0x98, 0x7b, 0x6f, 0xdd, 0x69, 0x6d, 0x02, 0x03, 0x01, 0x00, 0x01, + 0x28, 0xd2, 0x85, 0xd8, 0xcc, 0x04, 0x3a, 0x10, 0x73, 0x6f, 0x6d, 0x65, + 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + 0x48, 0x01, 0x52, 0x99, 0x01, 0x08, 0x01, 0x10, 0x00, 0x1a, 0x71, 0x04, + 0x8a, 0xea, 0x3f, 0x16, 0xff, 0x24, 0xa9, 0xbf, 0x03, 0x28, 0x30, 0x15, + 0xee, 0x52, 0x50, 0x9a, 0x55, 0x1c, 0x60, 0xc7, 0xa7, 0xcc, 0x4b, 0x99, + 0x5b, 0x40, 0x55, 0xce, 0x46, 0x19, 0xd4, 0xd4, 0x5e, 0xfd, 0xe0, 0x68, + 0x27, 0xea, 0x78, 0xf3, 0x07, 0x1f, 0x02, 0x4a, 0x78, 0x52, 0x44, 0xd3, + 0xdf, 0xbe, 0xac, 0x5f, 0xa5, 0x1c, 0x8a, 0x49, 0x8d, 0xa6, 0x5a, 0xac, + 0xa1, 0x25, 0x2b, 0xd1, 0xd9, 0xeb, 0xa7, 0x91, 0x26, 0x46, 0x3a, 0xe4, + 0x5b, 0x06, 0x6b, 0x77, 0x83, 0xa9, 0x0f, 0xa3, 0xf3, 0x2a, 0x39, 0x36, + 0xc1, 0xbd, 0x37, 0xeb, 0xd7, 0x83, 0x3e, 0xbd, 0x17, 0x53, 0x82, 0x69, + 0xc3, 0xe4, 0x48, 0xb5, 0x0d, 0x8c, 0xe1, 0x30, 0x17, 0xef, 0x01, 0x88, + 0x30, 0x62, 0x5a, 0xb3, 0x22, 0x20, 0x91, 0x69, 0x9e, 0xbc, 0xa2, 0x5c, + 0xd4, 0x51, 0x79, 0xfd, 0xbc, 0x2f, 0x92, 0xcd, 0x48, 0x2d, 0xd3, 0x30, + 0xe6, 0x1e, 0xbd, 0x4e, 0x23, 0x96, 0x2b, 0xb0, 0x3a, 0xfc, 0xb4, 0x7b, + 0x0e, 0x3d, 0x12, 0x80, 0x02, 0xa0, 0xe0, 0x2b, 0x1d, 0xe4, 0x28, 0x7b, + 0x57, 0x64, 0x3f, 0xe2, 0xf7, 0x10, 0xda, 0x97, 0x7f, 0x49, 0xf4, 0xd2, + 0x57, 0xc5, 0xf5, 0xe1, 0xd9, 0xd9, 0xbb, 0x34, 0xac, 0x90, 0x0c, 0xfa, + 0x9f, 0x0d, 0xd7, 0x14, 0xfc, 0xbb, 0xc9, 0xfa, 0xa5, 0x41, 0xd8, 0x87, + 0xa5, 0xef, 0x39, 0xf8, 0x70, 0x4f, 0x93, 0xfc, 0xb8, 0x62, 0xb8, 0x7e, + 0x6e, 0x9f, 0x25, 0xe9, 0x47, 0x08, 0xe6, 0x89, 0xe9, 0xc2, 0x41, 0x6a, + 0xc6, 0x0f, 0x84, 0x71, 0xa7, 0x90, 0xf2, 0x86, 0x7b, 0xbc, 0x99, 0xca, + 0xf1, 0xd4, 0xa5, 0xc0, 0x9f, 0x4e, 0x53, 0x27, 0xde, 0x3e, 0x4b, 0x7b, + 0x7d, 0x6a, 0xa4, 0xaa, 0x53, 0x9c, 0x4f, 0xff, 0xf6, 0x61, 0xbe, 0x4d, + 0xa7, 0x9b, 0xa6, 0xc9, 0xb1, 0x00, 0x57, 0xd6, 0x47, 0xb2, 0x0e, 0xf6, + 0x56, 0x1b, 0xc3, 0x7d, 0xe0, 0x97, 0x85, 0x93, 0xe9, 0xa4, 0x43, 0xfa, + 0x02, 0xd9, 0x40, 0x97, 0x22, 0x86, 0x15, 0xd8, 0x24, 0x92, 0x35, 0x32, + 0x15, 0xc2, 0x19, 0xbc, 0x32, 0x21, 0x3f, 0x8c, 0xdf, 0x9f, 0x5c, 0x3a, + 0x57, 0x73, 0x4f, 0x25, 0x3b, 0xa1, 0x88, 0x6f, 0xbb, 0x4a, 0xb7, 0xe5, + 0xc6, 0x33, 0x1b, 0x59, 0xa9, 0xe9, 0xc3, 0x0b, 0xdb, 0xe9, 0x41, 0xb0, + 0x06, 0x49, 0xdf, 0x0a, 0xa9, 0x85, 0xf5, 0xc7, 0xe6, 0x2c, 0x20, 0x25, + 0x50, 0x45, 0xf2, 0x86, 0x57, 0xdb, 0x3f, 0x28, 0x0b, 0xd6, 0xc4, 0xac, + 0xb0, 0x1c, 0xc3, 0xed, 0x7a, 0x8d, 0xa9, 0x83, 0x20, 0x43, 0xc7, 0x42, + 0x03, 0xca, 0x23, 0xc8, 0xf7, 0xbf, 0x2e, 0x70, 0x9a, 0xff, 0x23, 0xff, + 0x17, 0x22, 0xca, 0xe0, 0x58, 0x2f, 0xd3, 0x0d, 0xa4, 0xa2, 0x90, 0x6c, + 0xf1, 0x78, 0x7c, 0xee, 0x1a, 0xe7, 0x0c, 0xe2, 0x89, 0xf0, 0x5b, 0x9a, + 0x24, 0x4e, 0x10, 0xcf, 0x58, 0xa1, 0xdb, 0x3f, 0x1b, 0x1a, 0xb7, 0x05, + 0x0a, 0xb1, 0x02, 0x08, 0x01, 0x12, 0x10, 0x30, 0x31, 0x32, 0x33, 0x34, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x18, + 0xb2, 0x92, 0x04, 0x22, 0x8e, 0x02, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, + 0x01, 0x01, 0x00, 0xa7, 0x00, 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, 0x5a, + 0x2a, 0x40, 0xb4, 0xe1, 0x15, 0x94, 0x58, 0x11, 0x4f, 0x94, 0x58, 0xdd, + 0xde, 0xa7, 0x1f, 0x3c, 0x2c, 0xe0, 0x88, 0x09, 0x29, 0x61, 0x57, 0x67, + 0x5e, 0x56, 0x7e, 0xee, 0x27, 0x8f, 0x59, 0x34, 0x9a, 0x2a, 0xaa, 0x9d, + 0xb4, 0x4e, 0xfa, 0xa7, 0x6a, 0xd4, 0xc9, 0x7a, 0x53, 0xc1, 0x4e, 0x9f, + 0xe3, 0x34, 0xf7, 0x3d, 0xb7, 0xc9, 0x10, 0x47, 0x4f, 0x28, 0xda, 0x3f, + 0xce, 0x31, 0x7b, 0xfd, 0x06, 0x10, 0xeb, 0xf7, 0xbe, 0x92, 0xf9, 0xaf, + 0xfb, 0x3e, 0x68, 0xda, 0xee, 0x1a, 0x64, 0x4c, 0xf3, 0x29, 0xf2, 0x73, + 0x9e, 0x39, 0xd8, 0xf6, 0x6f, 0xd8, 0xb2, 0x80, 0x82, 0x71, 0x8e, 0xb5, + 0xa4, 0xf2, 0xc2, 0x3e, 0xcd, 0x0a, 0xca, 0xb6, 0x04, 0xcd, 0x9a, 0x13, + 0x8b, 0x54, 0x73, 0x54, 0x25, 0x54, 0x8c, 0xbe, 0x98, 0x7a, 0x67, 0xad, + 0xda, 0xb3, 0x4e, 0xb3, 0xfa, 0x82, 0xa8, 0x4a, 0x67, 0x98, 0x56, 0x57, + 0x54, 0x71, 0xcd, 0x12, 0x7f, 0xed, 0xa3, 0x01, 0xc0, 0x6a, 0x8b, 0x24, + 0x03, 0x96, 0x88, 0xbe, 0x97, 0x66, 0x2a, 0xbc, 0x53, 0xc9, 0x83, 0x06, + 0x51, 0x5a, 0x88, 0x65, 0x13, 0x18, 0xe4, 0x3a, 0xed, 0x6b, 0xf1, 0x61, + 0x5b, 0x4c, 0xc8, 0x1e, 0xf4, 0xc2, 0xae, 0x08, 0x5e, 0x2d, 0x5f, 0xf8, + 0x12, 0x7f, 0xa2, 0xfc, 0xbb, 0x21, 0x18, 0x30, 0xda, 0xfe, 0x40, 0xfb, + 0x01, 0xca, 0x2e, 0x37, 0x0e, 0xce, 0xdd, 0x76, 0x87, 0x82, 0x46, 0x0b, + 0x3a, 0x77, 0x8f, 0xc0, 0x72, 0x07, 0x2c, 0x7f, 0x9d, 0x1e, 0x86, 0x5b, + 0xed, 0x27, 0x29, 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, 0xd3, 0x5b, 0x3d, + 0xdb, 0x9c, 0x5e, 0x1b, 0x7b, 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, 0xbb, + 0xbb, 0x2c, 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, 0x01, 0x00, 0x01, + 0x28, 0xd2, 0x85, 0xd8, 0xcc, 0x04, 0x48, 0x01, 0x12, 0x80, 0x03, 0x06, + 0xe2, 0xc2, 0x94, 0x0e, 0x81, 0x87, 0x59, 0xe3, 0xe8, 0x15, 0x7f, 0xc6, + 0xff, 0x6b, 0xc8, 0x7e, 0x0c, 0xd9, 0x9b, 0x40, 0x34, 0x22, 0x44, 0x00, + 0xdf, 0x0e, 0x9e, 0xcd, 0xb9, 0x1d, 0x3d, 0xfe, 0x5a, 0xb9, 0x28, 0xdc, + 0x94, 0x43, 0xc4, 0x1c, 0x66, 0xa9, 0x8a, 0xa4, 0x61, 0xdf, 0x8a, 0xf3, + 0x7c, 0xf0, 0xbe, 0x66, 0xe9, 0xdf, 0x65, 0x93, 0x6c, 0xc7, 0xb5, 0x1a, + 0x76, 0x07, 0x40, 0xde, 0xa1, 0xc5, 0x40, 0xde, 0xac, 0x5b, 0x9f, 0x32, + 0xbb, 0xd4, 0xf2, 0x09, 0x13, 0x20, 0xbe, 0xee, 0xf4, 0xb5, 0xb0, 0xec, + 0xeb, 0x1e, 0xfa, 0x03, 0x1b, 0x9d, 0x5a, 0xa0, 0x2f, 0x71, 0x1a, 0x76, + 0xe7, 0x6f, 0x71, 0x7d, 0x3a, 0x7d, 0x8c, 0x46, 0xaf, 0x93, 0x94, 0x47, + 0x27, 0xec, 0x1b, 0x1e, 0xd7, 0x8c, 0x7c, 0xec, 0x42, 0xaf, 0x55, 0x82, + 0x3b, 0x6d, 0x07, 0x24, 0xb3, 0xfa, 0x2d, 0x1e, 0x12, 0x02, 0x94, 0x04, + 0x23, 0xeb, 0xf3, 0x74, 0x04, 0x7e, 0x2a, 0x7f, 0x00, 0x34, 0x2b, 0x5c, + 0x5b, 0x10, 0xe7, 0x36, 0x52, 0xde, 0x9f, 0x56, 0x10, 0xe3, 0x0b, 0xa5, + 0x29, 0x85, 0xa5, 0x95, 0xed, 0xf5, 0x39, 0x0a, 0x03, 0x51, 0x29, 0x64, + 0xa1, 0x4f, 0x38, 0xde, 0x3b, 0x4d, 0x0a, 0xf3, 0x7e, 0x37, 0x14, 0xce, + 0xdf, 0x9d, 0x86, 0x16, 0xad, 0x62, 0xa8, 0xf8, 0xa7, 0xc2, 0xa4, 0xc1, + 0xe2, 0xd6, 0x40, 0xa4, 0x7b, 0x20, 0x1b, 0x6d, 0x7c, 0x97, 0x0b, 0x73, + 0x85, 0xbf, 0xdb, 0xc3, 0xa1, 0xf5, 0xd4, 0xb7, 0x95, 0xf2, 0xe7, 0x10, + 0x77, 0xc6, 0x82, 0xb2, 0x68, 0x24, 0x31, 0xdc, 0x69, 0x43, 0x56, 0xf5, + 0x76, 0x20, 0x0a, 0x82, 0x1a, 0x98, 0xb3, 0x02, 0x0f, 0x67, 0xcd, 0x4f, + 0xab, 0x43, 0x44, 0xbd, 0xdb, 0x07, 0xd3, 0xff, 0x8b, 0x68, 0x33, 0x24, + 0x35, 0xe5, 0xc6, 0x1a, 0x94, 0x14, 0x4f, 0x40, 0xef, 0x92, 0xfb, 0xfd, + 0x72, 0x15, 0xd4, 0x10, 0x60, 0x22, 0x3e, 0x60, 0x49, 0x3d, 0x58, 0xc6, + 0x3d, 0x28, 0x70, 0x55, 0x32, 0xd5, 0x78, 0x03, 0x51, 0xff, 0xd6, 0x4f, + 0x4e, 0x89, 0x0e, 0x50, 0x85, 0x6e, 0x1c, 0x6a, 0x5f, 0x11, 0xd0, 0xf5, + 0xee, 0xe5, 0x1c, 0xa8, 0xb2, 0xdb, 0x26, 0x93, 0xb1, 0xe2, 0xc1, 0x05, + 0xe0, 0x7f, 0x16, 0xe7, 0x9c, 0xcf, 0xe7, 0xb7, 0x7e, 0xaa, 0x96, 0x21, + 0x64, 0x39, 0x6d, 0x7a, 0xdc, 0x70, 0x6e, 0xc8, 0xf5, 0x44, 0x2e, 0x9f, + 0xc1, 0xe9, 0x46, 0x8c, 0x1b, 0x58, 0xec, 0x73, 0x1b, 0x9a, 0x04, 0xcb, + 0x68, 0x58, 0x21, 0x0e, 0xd6, 0xd7, 0x7a, 0x2b, 0x60, 0x02, 0x20, 0x7b, + 0x85, 0xe5, 0x84, 0x2c, 0x5f, 0x24, 0x90, 0x2d, 0xc5, 0x19, 0xea, 0xf3, + 0x91, 0x78, 0xc2, 0xa7, 0x36, 0x5a, 0x72, 0x64, 0x45, 0x13, 0x49}; + +const unsigned char kTestDrmServiceCertificateLicenseSdk[] = { + 0x0a, 0xc0, 0x02, 0x08, 0x03, 0x12, 0x10, 0x30, 0x30, 0x31, 0x31, 0x32, 0x32, 0x33, 0x33, 0x34, 0x34, 0x35, 0x35, 0x36, 0x36, 0x37, 0x37, 0x18, 0xb1, 0x97, 0xd3, 0x03, 0x22, 0x8e, 0x02, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa7, 0x00, 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, @@ -277,39 +521,166 @@ const unsigned char kTestDrmServiceCertificate[] = { 0x3d, 0xdb, 0x9c, 0x5e, 0x1b, 0x7b, 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, 0xbb, 0xbb, 0x2c, 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, 0x01, 0x00, 0x01, 0x3a, 0x10, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x12, 0x80, 0x03, 0x6e, 0xc3, - 0x5a, 0x17, 0xa8, 0xf9, 0xef, 0xee, 0x67, 0x4d, 0x0a, 0xef, 0x57, 0x5e, - 0xbc, 0x59, 0x3d, 0x22, 0x84, 0xa0, 0x0a, 0xf5, 0x84, 0x26, 0xb7, 0x8b, - 0xab, 0x91, 0x3e, 0x4b, 0xb9, 0x91, 0x3c, 0x50, 0xc9, 0x08, 0x2f, 0x97, - 0x0a, 0x91, 0xb5, 0x48, 0xe4, 0xba, 0xfd, 0x7b, 0xbd, 0xf0, 0xba, 0x08, - 0xb3, 0x29, 0xb4, 0x23, 0x74, 0xaf, 0x3f, 0xe9, 0x77, 0x78, 0x3f, 0xdc, - 0x3d, 0x8a, 0x37, 0xec, 0x1c, 0x3a, 0xff, 0x60, 0x8e, 0x10, 0x72, 0xaa, - 0x97, 0x98, 0x56, 0xa0, 0x35, 0xa9, 0xbf, 0x43, 0x21, 0x6a, 0x15, 0x88, - 0xba, 0xc0, 0x68, 0x01, 0x7b, 0xd7, 0x88, 0x2f, 0x1a, 0xc5, 0x1f, 0x54, - 0xf0, 0xea, 0x36, 0xb7, 0xed, 0x49, 0x78, 0x09, 0xb1, 0x07, 0x46, 0xfe, - 0xf4, 0xfa, 0x16, 0x0c, 0x46, 0x91, 0xe2, 0xa9, 0xe0, 0x8e, 0x97, 0xe5, - 0xea, 0x2f, 0xd9, 0x94, 0x1e, 0xe7, 0xba, 0x28, 0x98, 0x92, 0xae, 0xb8, - 0xb6, 0x6e, 0xf6, 0xd2, 0x50, 0xd3, 0x5b, 0x25, 0x12, 0x68, 0x5e, 0x07, - 0x82, 0x64, 0x27, 0xfe, 0x1a, 0xcd, 0x38, 0xa8, 0x00, 0x53, 0x8c, 0x69, - 0x51, 0x75, 0x71, 0xc2, 0x6a, 0x5f, 0x05, 0x13, 0x77, 0x2b, 0xc8, 0x6c, - 0xab, 0xd2, 0x64, 0x27, 0xbd, 0x21, 0xfc, 0x33, 0x0a, 0x3a, 0x53, 0xa6, - 0x28, 0x1c, 0x2a, 0xad, 0x23, 0x0a, 0x95, 0xe4, 0x38, 0x6b, 0x9b, 0x3e, - 0x77, 0x7d, 0x96, 0x20, 0x42, 0xf5, 0x18, 0xbe, 0xb0, 0x78, 0xe4, 0xf0, - 0x95, 0x6c, 0xd5, 0x30, 0xd6, 0xfc, 0x04, 0xe2, 0xf7, 0xff, 0x06, 0x6b, - 0xaf, 0xf1, 0x9c, 0x10, 0xa6, 0xdb, 0xed, 0x4a, 0x18, 0x68, 0x87, 0xda, - 0x43, 0x2c, 0x60, 0xc6, 0x0a, 0x72, 0x1e, 0x9f, 0x4b, 0x05, 0x80, 0x15, - 0x17, 0x84, 0xf1, 0xee, 0xcc, 0x80, 0x25, 0x33, 0x87, 0x74, 0x02, 0x8c, - 0xa1, 0xbb, 0xd9, 0x29, 0x33, 0x97, 0xbd, 0x5b, 0x1c, 0xed, 0xcc, 0x47, - 0xda, 0x73, 0xae, 0xb1, 0x75, 0xac, 0xf7, 0x39, 0xbe, 0x67, 0xc3, 0xaf, - 0x60, 0x07, 0xf5, 0xba, 0x81, 0xf4, 0x42, 0xad, 0x28, 0x8d, 0xe6, 0x63, - 0xea, 0x8a, 0x0e, 0x71, 0x53, 0x6e, 0x62, 0x8a, 0x23, 0x4f, 0xad, 0x2a, - 0x9a, 0xf6, 0xeb, 0xa8, 0x82, 0x83, 0xbb, 0x5f, 0xc9, 0x86, 0xd8, 0x76, - 0xb9, 0xf3, 0xe7, 0x32, 0xdd, 0xe0, 0x44, 0x6a, 0xab, 0x78, 0xa0, 0x8c, - 0xa4, 0x99, 0x6f, 0x71, 0x42, 0x8b, 0x31, 0x32, 0xbb, 0x80, 0x36, 0x61, - 0x1c, 0xe5, 0x6d, 0x87, 0xf2, 0x68, 0xca, 0xcd, 0xe0, 0x5f, 0xa2, 0x68, - 0x5b, 0xfc, 0x73, 0xc9, 0x26, 0x2b, 0x13, 0x05, 0x1c, 0xde, 0x19, 0xdf, - 0x34, 0xba, 0xf5, 0xec, 0xaf, 0x26, 0xfb, 0x64, 0xc4, 0x38, 0x7e, 0xdb, - 0x51, 0x28, 0x49, 0xa7, 0x12, 0x88, 0xa5, 0x6d, 0xa2, 0xfa}; + 0x69, 0x63, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x40, 0x01, 0x48, 0x01, 0x12, + 0x80, 0x03, 0x20, 0x54, 0x65, 0x70, 0x93, 0x53, 0x0a, 0x76, 0x95, 0xe5, + 0xaa, 0x48, 0x94, 0xa1, 0xb8, 0x25, 0xa6, 0xa4, 0x5a, 0x56, 0x59, 0x8d, + 0x80, 0x64, 0x7c, 0xb2, 0x1e, 0xeb, 0xda, 0xfb, 0x30, 0x26, 0x09, 0xc0, + 0x25, 0x65, 0xdb, 0xb8, 0x7d, 0x0e, 0xe0, 0x2b, 0xfb, 0xfe, 0xe6, 0x3a, + 0x5b, 0x9b, 0x1f, 0xbb, 0xa4, 0x89, 0xf5, 0x7c, 0x52, 0x6f, 0x52, 0xe3, + 0xb3, 0xc7, 0x27, 0x9d, 0xca, 0x01, 0x78, 0x08, 0x9b, 0x59, 0x37, 0x9f, + 0x27, 0x52, 0x90, 0x80, 0x22, 0xfb, 0x0d, 0xca, 0x57, 0xc2, 0xd9, 0x89, + 0xb6, 0x69, 0x45, 0x1f, 0x15, 0x23, 0xf1, 0xf8, 0x39, 0xbb, 0x45, 0xb9, + 0x39, 0xe5, 0x1e, 0x8b, 0x71, 0x82, 0x25, 0x4a, 0x32, 0xc2, 0x44, 0xee, + 0x76, 0x91, 0x61, 0xa2, 0xe2, 0x7a, 0xb4, 0x68, 0x56, 0xaf, 0x33, 0xe4, + 0x97, 0x44, 0xfe, 0x6d, 0x70, 0x85, 0x4f, 0x16, 0x1a, 0xda, 0xa4, 0x30, + 0x66, 0xf7, 0x95, 0xe9, 0x7b, 0x84, 0x42, 0xd6, 0x7c, 0x4b, 0x05, 0xca, + 0x67, 0x2f, 0xf4, 0xdc, 0x81, 0x9b, 0x7c, 0x80, 0xc4, 0x9e, 0x25, 0x98, + 0x84, 0xc4, 0x43, 0x35, 0x13, 0xf4, 0x9d, 0x57, 0x02, 0x1e, 0x67, 0x86, + 0x00, 0x6c, 0x46, 0xde, 0x91, 0x9f, 0x1f, 0x42, 0xbb, 0xa7, 0xd1, 0xb8, + 0x80, 0x2c, 0x33, 0x51, 0x87, 0x93, 0x6d, 0x75, 0x03, 0xb0, 0x42, 0xc9, + 0xe6, 0xa1, 0xc7, 0xa5, 0xd3, 0x40, 0xe7, 0x99, 0x6d, 0x07, 0x78, 0x13, + 0x8a, 0x01, 0x4d, 0x3e, 0xb4, 0x9a, 0x1b, 0x52, 0xb7, 0xac, 0x6d, 0x27, + 0xda, 0x5c, 0xa2, 0x78, 0x01, 0xe3, 0x4d, 0x5d, 0x0a, 0xd0, 0xc7, 0xb5, + 0x73, 0xcf, 0x6e, 0xdd, 0x89, 0xc6, 0xd4, 0x9c, 0xc7, 0xfa, 0x87, 0xe9, + 0x74, 0x01, 0xe9, 0xdd, 0x16, 0x0f, 0x3a, 0x8e, 0x38, 0x8d, 0x0b, 0x5a, + 0xc8, 0x01, 0xca, 0xb2, 0x7f, 0xcb, 0xe3, 0x25, 0xaa, 0x10, 0xc9, 0x4f, + 0x5a, 0x17, 0xe0, 0x31, 0x30, 0x34, 0xe8, 0xe6, 0x06, 0x27, 0xb3, 0x26, + 0xee, 0x44, 0x5d, 0x34, 0x2d, 0xc0, 0xff, 0x98, 0x1d, 0x33, 0x99, 0x96, + 0x29, 0xa9, 0xc6, 0x31, 0xc1, 0xe1, 0x2f, 0xb9, 0x3a, 0xd2, 0x80, 0x16, + 0xc2, 0x4a, 0x38, 0x58, 0x9f, 0x78, 0x7b, 0x11, 0x6c, 0x4e, 0xb0, 0x6b, + 0x3b, 0x8f, 0x77, 0x59, 0xb7, 0xca, 0x08, 0x85, 0xc5, 0xe2, 0x03, 0xa7, + 0x33, 0xe7, 0x34, 0xc5, 0x64, 0x37, 0x9b, 0x19, 0x48, 0x54, 0xa7, 0xe5, + 0x74, 0xe3, 0xa9, 0xfc, 0xe7, 0x6f, 0x9f, 0x04, 0xd4, 0xbd, 0x4a, 0x70, + 0xc9, 0x09, 0x67, 0x1a, 0xc5, 0x7b, 0xe9, 0x88, 0x71, 0xcb, 0x96, 0x46, + 0x2f, 0x5e, 0x47, 0x19, 0x48, 0xc0, 0xc6, 0xc1, 0xef, 0xb1, 0x26, 0x95, + 0x0c, 0x16, 0xed, 0x88, 0x4d, 0xaf, 0x96, 0x3f, 0xa2, 0xf8, 0xc7, 0x38, + 0x95, 0x48, +}; + +const unsigned char kTestDrmServiceCertificateAllTypes[] = { + 0x0a, 0xc4, 0x02, 0x08, 0x03, 0x12, 0x10, 0x30, 0x30, 0x31, 0x31, 0x32, + 0x32, 0x33, 0x33, 0x34, 0x34, 0x35, 0x35, 0x36, 0x36, 0x37, 0x37, 0x18, + 0xb1, 0x97, 0xd3, 0x03, 0x22, 0x8e, 0x02, 0x30, 0x82, 0x01, 0x0a, 0x02, + 0x82, 0x01, 0x01, 0x00, 0xa7, 0x00, 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, + 0x5a, 0x2a, 0x40, 0xb4, 0xe1, 0x15, 0x94, 0x58, 0x11, 0x4f, 0x94, 0x58, + 0xdd, 0xde, 0xa7, 0x1f, 0x3c, 0x2c, 0xe0, 0x88, 0x09, 0x29, 0x61, 0x57, + 0x67, 0x5e, 0x56, 0x7e, 0xee, 0x27, 0x8f, 0x59, 0x34, 0x9a, 0x2a, 0xaa, + 0x9d, 0xb4, 0x4e, 0xfa, 0xa7, 0x6a, 0xd4, 0xc9, 0x7a, 0x53, 0xc1, 0x4e, + 0x9f, 0xe3, 0x34, 0xf7, 0x3d, 0xb7, 0xc9, 0x10, 0x47, 0x4f, 0x28, 0xda, + 0x3f, 0xce, 0x31, 0x7b, 0xfd, 0x06, 0x10, 0xeb, 0xf7, 0xbe, 0x92, 0xf9, + 0xaf, 0xfb, 0x3e, 0x68, 0xda, 0xee, 0x1a, 0x64, 0x4c, 0xf3, 0x29, 0xf2, + 0x73, 0x9e, 0x39, 0xd8, 0xf6, 0x6f, 0xd8, 0xb2, 0x80, 0x82, 0x71, 0x8e, + 0xb5, 0xa4, 0xf2, 0xc2, 0x3e, 0xcd, 0x0a, 0xca, 0xb6, 0x04, 0xcd, 0x9a, + 0x13, 0x8b, 0x54, 0x73, 0x54, 0x25, 0x54, 0x8c, 0xbe, 0x98, 0x7a, 0x67, + 0xad, 0xda, 0xb3, 0x4e, 0xb3, 0xfa, 0x82, 0xa8, 0x4a, 0x67, 0x98, 0x56, + 0x57, 0x54, 0x71, 0xcd, 0x12, 0x7f, 0xed, 0xa3, 0x01, 0xc0, 0x6a, 0x8b, + 0x24, 0x03, 0x96, 0x88, 0xbe, 0x97, 0x66, 0x2a, 0xbc, 0x53, 0xc9, 0x83, + 0x06, 0x51, 0x5a, 0x88, 0x65, 0x13, 0x18, 0xe4, 0x3a, 0xed, 0x6b, 0xf1, + 0x61, 0x5b, 0x4c, 0xc8, 0x1e, 0xf4, 0xc2, 0xae, 0x08, 0x5e, 0x2d, 0x5f, + 0xf8, 0x12, 0x7f, 0xa2, 0xfc, 0xbb, 0x21, 0x18, 0x30, 0xda, 0xfe, 0x40, + 0xfb, 0x01, 0xca, 0x2e, 0x37, 0x0e, 0xce, 0xdd, 0x76, 0x87, 0x82, 0x46, + 0x0b, 0x3a, 0x77, 0x8f, 0xc0, 0x72, 0x07, 0x2c, 0x7f, 0x9d, 0x1e, 0x86, + 0x5b, 0xed, 0x27, 0x29, 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, 0xd3, 0x5b, + 0x3d, 0xdb, 0x9c, 0x5e, 0x1b, 0x7b, 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, + 0xbb, 0xbb, 0x2c, 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, 0x01, 0x00, + 0x01, 0x3a, 0x10, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x40, 0x01, 0x40, 0x02, 0x40, + 0x03, 0x48, 0x01, 0x12, 0x80, 0x03, 0x72, 0x7c, 0xb3, 0x19, 0x62, 0x88, + 0x4d, 0xc5, 0x46, 0x11, 0xcc, 0x6b, 0x32, 0x47, 0x65, 0x7a, 0x5e, 0xe7, + 0xf7, 0x58, 0x7e, 0xbc, 0x76, 0xa6, 0x55, 0x75, 0x32, 0x19, 0xaa, 0xf6, + 0xb7, 0x02, 0x16, 0x75, 0xfd, 0x05, 0x0b, 0x1c, 0x2c, 0xc5, 0x44, 0x6c, + 0xcf, 0x96, 0x02, 0x93, 0x23, 0x60, 0x4b, 0xd4, 0x97, 0x81, 0xa4, 0xe5, + 0x5f, 0x0a, 0xe0, 0x5f, 0x53, 0x23, 0x66, 0x19, 0x70, 0xe0, 0x8c, 0x08, + 0x19, 0x88, 0xf2, 0xda, 0xd9, 0x7c, 0x69, 0x52, 0xc0, 0x8a, 0x7a, 0x62, + 0x81, 0xaa, 0x2b, 0x0e, 0xe8, 0x28, 0x21, 0xce, 0x4a, 0x63, 0x52, 0x23, + 0xab, 0xea, 0x65, 0xf9, 0x0f, 0x8a, 0xd2, 0x77, 0xa5, 0x94, 0x05, 0x77, + 0x8b, 0x35, 0x99, 0x8b, 0xa9, 0xdd, 0xe4, 0x69, 0x00, 0xbe, 0x4d, 0xd2, + 0xce, 0x6c, 0xe4, 0xf9, 0x34, 0x97, 0xaa, 0x59, 0xd8, 0xc9, 0x58, 0x11, + 0xa9, 0xb4, 0x9b, 0x0e, 0xce, 0xb7, 0x15, 0x73, 0x6b, 0xb5, 0x5f, 0x87, + 0xea, 0xf1, 0x1c, 0x8d, 0x66, 0x5a, 0x28, 0x7e, 0x9a, 0x01, 0x15, 0xfb, + 0xb1, 0x92, 0x28, 0x3b, 0x62, 0x6a, 0xcb, 0xb0, 0xe7, 0x11, 0x76, 0x44, + 0x5a, 0x97, 0x4b, 0x1a, 0x26, 0xbe, 0x3c, 0xa6, 0x33, 0xd8, 0x99, 0x7b, + 0x97, 0xb2, 0xec, 0xa3, 0x18, 0xdf, 0x6b, 0xeb, 0x9f, 0x43, 0x14, 0x0a, + 0x49, 0xdb, 0xb0, 0xe2, 0xc0, 0xe0, 0xe3, 0x73, 0x66, 0x0f, 0x14, 0x1c, + 0x48, 0x21, 0x28, 0x3a, 0x90, 0xfd, 0x32, 0x1d, 0x35, 0x9a, 0x11, 0x6c, + 0xf8, 0x39, 0xd1, 0xcb, 0x45, 0x76, 0xc0, 0x5e, 0xc5, 0xa8, 0xba, 0xd9, + 0x09, 0xfc, 0x44, 0xf2, 0x9e, 0xaf, 0x95, 0xc4, 0xe0, 0x06, 0x0e, 0xbf, + 0x99, 0x6e, 0x57, 0xfa, 0xa8, 0xcd, 0x2b, 0x4b, 0x97, 0x62, 0xf7, 0x92, + 0x14, 0x8f, 0xf4, 0x8c, 0xba, 0xb1, 0xb4, 0x8e, 0x07, 0xd2, 0x7b, 0x93, + 0xd1, 0xbf, 0x90, 0x60, 0xe0, 0xbf, 0x1a, 0x3b, 0xd2, 0xee, 0xad, 0xf8, + 0x4f, 0x5b, 0xee, 0xe5, 0xf4, 0x8e, 0x97, 0x5b, 0x24, 0xd2, 0xa6, 0x80, + 0x5c, 0x09, 0x27, 0x8e, 0x14, 0xa9, 0xcc, 0xff, 0x5a, 0xc1, 0xb4, 0x5f, + 0xb5, 0x07, 0x04, 0xd0, 0x3a, 0xef, 0xa9, 0x45, 0xa1, 0x23, 0x0f, 0xc2, + 0x13, 0x4f, 0xc8, 0xd4, 0x7f, 0xef, 0x54, 0x5c, 0xcc, 0xdb, 0xdf, 0x89, + 0x4c, 0x31, 0x8f, 0x27, 0x50, 0x2b, 0x99, 0xd8, 0xee, 0xdf, 0x8b, 0x06, + 0x93, 0xc0, 0xd0, 0x59, 0xf5, 0x66, 0xaf, 0x4b, 0x98, 0x68, 0xb6, 0x8c, + 0xd7, 0x70, 0xad, 0x69, 0x60, 0xca, 0xae, 0x64, 0xc2, 0x53, 0xcb, 0xa1, + 0x39, 0xb7, 0x08, 0x50, 0x31, 0x12, 0xc1, 0x02, 0x08, 0x58, 0x59, 0xc8, + 0x69, 0x9e, 0x43, 0xac, 0x60, 0x7d, 0x4c, 0x8b, 0x1a, 0xe8, 0xa8, 0x3d, + 0x65, 0x29, 0x83, 0x85, 0x9b, 0x8e, +}; + +const unsigned char kTestDrmServiceCertificateNoType[] = { + 0x0a, 0xbe, 0x02, 0x08, 0x03, 0x12, 0x10, 0x30, 0x30, 0x31, 0x31, 0x32, + 0x32, 0x33, 0x33, 0x34, 0x34, 0x35, 0x35, 0x36, 0x36, 0x37, 0x37, 0x18, + 0xb1, 0x97, 0xd3, 0x03, 0x22, 0x8e, 0x02, 0x30, 0x82, 0x01, 0x0a, 0x02, + 0x82, 0x01, 0x01, 0x00, 0xa7, 0x00, 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, + 0x5a, 0x2a, 0x40, 0xb4, 0xe1, 0x15, 0x94, 0x58, 0x11, 0x4f, 0x94, 0x58, + 0xdd, 0xde, 0xa7, 0x1f, 0x3c, 0x2c, 0xe0, 0x88, 0x09, 0x29, 0x61, 0x57, + 0x67, 0x5e, 0x56, 0x7e, 0xee, 0x27, 0x8f, 0x59, 0x34, 0x9a, 0x2a, 0xaa, + 0x9d, 0xb4, 0x4e, 0xfa, 0xa7, 0x6a, 0xd4, 0xc9, 0x7a, 0x53, 0xc1, 0x4e, + 0x9f, 0xe3, 0x34, 0xf7, 0x3d, 0xb7, 0xc9, 0x10, 0x47, 0x4f, 0x28, 0xda, + 0x3f, 0xce, 0x31, 0x7b, 0xfd, 0x06, 0x10, 0xeb, 0xf7, 0xbe, 0x92, 0xf9, + 0xaf, 0xfb, 0x3e, 0x68, 0xda, 0xee, 0x1a, 0x64, 0x4c, 0xf3, 0x29, 0xf2, + 0x73, 0x9e, 0x39, 0xd8, 0xf6, 0x6f, 0xd8, 0xb2, 0x80, 0x82, 0x71, 0x8e, + 0xb5, 0xa4, 0xf2, 0xc2, 0x3e, 0xcd, 0x0a, 0xca, 0xb6, 0x04, 0xcd, 0x9a, + 0x13, 0x8b, 0x54, 0x73, 0x54, 0x25, 0x54, 0x8c, 0xbe, 0x98, 0x7a, 0x67, + 0xad, 0xda, 0xb3, 0x4e, 0xb3, 0xfa, 0x82, 0xa8, 0x4a, 0x67, 0x98, 0x56, + 0x57, 0x54, 0x71, 0xcd, 0x12, 0x7f, 0xed, 0xa3, 0x01, 0xc0, 0x6a, 0x8b, + 0x24, 0x03, 0x96, 0x88, 0xbe, 0x97, 0x66, 0x2a, 0xbc, 0x53, 0xc9, 0x83, + 0x06, 0x51, 0x5a, 0x88, 0x65, 0x13, 0x18, 0xe4, 0x3a, 0xed, 0x6b, 0xf1, + 0x61, 0x5b, 0x4c, 0xc8, 0x1e, 0xf4, 0xc2, 0xae, 0x08, 0x5e, 0x2d, 0x5f, + 0xf8, 0x12, 0x7f, 0xa2, 0xfc, 0xbb, 0x21, 0x18, 0x30, 0xda, 0xfe, 0x40, + 0xfb, 0x01, 0xca, 0x2e, 0x37, 0x0e, 0xce, 0xdd, 0x76, 0x87, 0x82, 0x46, + 0x0b, 0x3a, 0x77, 0x8f, 0xc0, 0x72, 0x07, 0x2c, 0x7f, 0x9d, 0x1e, 0x86, + 0x5b, 0xed, 0x27, 0x29, 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, 0xd3, 0x5b, + 0x3d, 0xdb, 0x9c, 0x5e, 0x1b, 0x7b, 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, + 0xbb, 0xbb, 0x2c, 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, 0x01, 0x00, + 0x01, 0x3a, 0x10, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x48, 0x01, 0x12, 0x80, 0x03, + 0x21, 0xa4, 0x86, 0x36, 0x40, 0x9d, 0x0f, 0x7d, 0x31, 0x8a, 0xbe, 0x98, + 0x85, 0x0f, 0x4a, 0xe6, 0x5e, 0x2e, 0x94, 0xc3, 0x4a, 0x69, 0x26, 0x55, + 0x76, 0xf6, 0x11, 0xfe, 0xd4, 0x1d, 0xfc, 0xb3, 0xbe, 0x94, 0x6a, 0x5c, + 0x51, 0x2d, 0xda, 0x95, 0x70, 0x6f, 0x62, 0x23, 0x69, 0xfa, 0x3d, 0xce, + 0x09, 0x0c, 0x2f, 0xc7, 0x30, 0xac, 0xf2, 0x46, 0x6a, 0x14, 0x42, 0x24, + 0x8f, 0x2a, 0xb4, 0xa7, 0x32, 0xbb, 0x8c, 0x02, 0xe2, 0x1f, 0x63, 0xfd, + 0xf4, 0x40, 0x14, 0xd9, 0x12, 0x88, 0xa6, 0x46, 0x15, 0x67, 0x85, 0xa3, + 0xe2, 0xe4, 0x88, 0x5f, 0xdd, 0x20, 0xcd, 0xa7, 0xd1, 0x26, 0x71, 0x63, + 0x89, 0x2e, 0x1a, 0x8a, 0x52, 0x59, 0xba, 0x3b, 0x09, 0x8a, 0x1c, 0x67, + 0x0c, 0xfd, 0xc9, 0xad, 0x7b, 0x4f, 0xd8, 0x0e, 0x47, 0x9e, 0x47, 0x44, + 0x20, 0x67, 0x7a, 0xec, 0x3b, 0xfe, 0x58, 0xf2, 0x25, 0xb2, 0x72, 0xe1, + 0x05, 0xbd, 0x7c, 0x02, 0xc9, 0xdb, 0x40, 0x3c, 0x90, 0x04, 0xab, 0xa7, + 0xb6, 0x06, 0xb2, 0x30, 0x64, 0x42, 0x84, 0x3e, 0x89, 0xd7, 0xd5, 0x90, + 0xaa, 0x14, 0x00, 0xa6, 0x45, 0x26, 0xe1, 0x16, 0xa8, 0x24, 0x65, 0x01, + 0xe1, 0x0e, 0xd3, 0xff, 0x68, 0x0c, 0x0f, 0x8f, 0xc3, 0x68, 0xed, 0x82, + 0x7c, 0xae, 0x21, 0x97, 0x6a, 0x5a, 0x55, 0x76, 0x4b, 0x4e, 0x65, 0x9b, + 0xbd, 0x18, 0x8b, 0xf4, 0xc4, 0x28, 0xab, 0x6c, 0x52, 0xc7, 0xfc, 0xb3, + 0x12, 0x79, 0x3c, 0x29, 0x82, 0x15, 0xd1, 0x45, 0xae, 0x0b, 0xbc, 0x46, + 0x13, 0x24, 0x80, 0xb1, 0x6a, 0x15, 0x7f, 0x68, 0x91, 0xfe, 0xa6, 0x5d, + 0x7a, 0xdb, 0x81, 0xc4, 0x14, 0xf0, 0x7d, 0x59, 0x15, 0x7c, 0x11, 0x6d, + 0xe5, 0x97, 0xc2, 0x5e, 0x00, 0xfc, 0x36, 0x4f, 0xf7, 0xc1, 0xe6, 0x62, + 0x92, 0x03, 0x92, 0x4d, 0x91, 0x61, 0x7e, 0xdc, 0xe9, 0xd3, 0x16, 0x17, + 0xa7, 0x21, 0xb8, 0xf7, 0x27, 0xc4, 0x72, 0xe9, 0xff, 0x66, 0x60, 0x65, + 0x50, 0x6d, 0x2d, 0xe9, 0x02, 0xf2, 0x48, 0xbc, 0xfa, 0x7b, 0x3e, 0xdc, + 0x51, 0x4a, 0xd8, 0x2a, 0xd2, 0x32, 0x95, 0xff, 0x32, 0xf0, 0x68, 0xae, + 0x74, 0xb3, 0xaf, 0x93, 0x79, 0x2e, 0x4e, 0xd8, 0x23, 0x61, 0xf4, 0xca, + 0x34, 0x3f, 0x2d, 0x12, 0x5d, 0xef, 0x05, 0xd0, 0xdc, 0x72, 0x0b, 0x02, + 0xc4, 0x2d, 0x97, 0x65, 0x6d, 0x44, 0x25, 0x50, 0x8e, 0xd1, 0x34, 0x8b, + 0xc3, 0xff, 0xca, 0x17, 0xcb, 0x5c, 0x64, 0x8e, 0xd1, 0xe5, 0x81, 0xb2, + 0x5c, 0xd6, 0xd3, 0x2a, 0x0c, 0x74, 0x41, 0x14, 0xc5, 0x27, 0x3c, 0x3f, + 0x2b, 0xf8, 0xba, 0x33, 0x0b, 0xf4, 0x88, 0x9c, 0x8b, 0x75, 0xf5, 0xf0, + 0x29, 0x08, 0x23, 0x91, 0xe1, 0xcc, 0xc3, 0x6e, 0x5e, 0x60, 0x5d, 0x5a, +}; TestDrmCertificates::TestDrmCertificates() : test_root_certificate_( @@ -318,11 +689,32 @@ TestDrmCertificates::TestDrmCertificates() test_intermediate_certificate_( kTestIntermediateCertificate, kTestIntermediateCertificate + sizeof(kTestIntermediateCertificate)), + test_intermediate_certificate_with_ec_key_( + kTestIntermediateCertificateWithECKey, + kTestIntermediateCertificateWithECKey + + sizeof(kTestIntermediateCertificateWithECKey)), test_user_device_certificate_( kTestUserDrmCertificate, kTestUserDrmCertificate + sizeof(kTestUserDrmCertificate)), - test_service_certificate_( - kTestDrmServiceCertificate, - kTestDrmServiceCertificate + sizeof(kTestDrmServiceCertificate)) {} + test_user_device_certificate_with_ec_key_( + kTestUserDrmCertificateWithECKey, + kTestUserDrmCertificateWithECKey + + sizeof(kTestUserDrmCertificateWithECKey)), + test_user_device_certificate_with_rot_id_( + kTestUserDrmCertificateWithRotId, + kTestUserDrmCertificateWithRotId + + sizeof(kTestUserDrmCertificateWithRotId)), + test_service_certificate_license_sdk_( + kTestDrmServiceCertificateLicenseSdk, + kTestDrmServiceCertificateLicenseSdk + + sizeof(kTestDrmServiceCertificateLicenseSdk)), + test_service_certificate_all_types_( + kTestDrmServiceCertificateAllTypes, + kTestDrmServiceCertificateAllTypes + + sizeof(kTestDrmServiceCertificateAllTypes)), + test_service_certificate_no_type_( + kTestDrmServiceCertificateNoType, + kTestDrmServiceCertificateNoType + + sizeof(kTestDrmServiceCertificateNoType)) {} } // namespace widevine diff --git a/common/test_drm_certificates.h b/common/test_drm_certificates.h index 666d693..ae3b8f5 100644 --- a/common/test_drm_certificates.h +++ b/common/test_drm_certificates.h @@ -14,41 +14,78 @@ #define COMMON_TEST_DRM_CERTIFICATES_H_ #include -#include "base/macros.h" namespace widevine { class TestDrmCertificates { public: TestDrmCertificates(); + + TestDrmCertificates(const TestDrmCertificates&) = delete; + TestDrmCertificates& operator=(const TestDrmCertificates&) = delete; + virtual ~TestDrmCertificates() {} // returns a test root certificate - const std::string& test_root_certificate() const { return test_root_certificate_; } + const std::string& test_root_certificate() const { + return test_root_certificate_; + } + + // returns a test intermediate certificate with an RSA key - // returns a test intermediate certificate const std::string& test_intermediate_certificate() const { return test_intermediate_certificate_; } - // returns an user device certificate + // returns a test intermediate certificate with an EC key + const std::string& test_intermediate_certificate_with_ec_key() const { + return test_intermediate_certificate_with_ec_key_; + } + + // returns a user device certificate with an RSA key + const std::string& test_user_device_certificate() const { return test_user_device_certificate_; } - // returns a service certificate - const std::string& test_service_certificate() const { - return test_service_certificate_; + // returns a user device certificate with an EC key + const std::string& test_user_device_certificate_with_ec_key() const { + return test_user_device_certificate_with_ec_key_; + } + + // returns a user device certificate with a Root of Trust Id. + const std::string& test_user_device_certificate_with_rot_id() const { + return test_user_device_certificate_with_rot_id_; + } + + // returns a service certificate with license sdk service type + const std::string& test_service_certificate_license_sdk() const { + return test_service_certificate_license_sdk_; + } + + // returns a service certificate with all service types + const std::string& test_service_certificate_all_types() const { + return test_service_certificate_all_types_; + } + + // returns a service certificate prior to a change requiring the service + // type to be specified. + const std::string& test_service_certificate_no_type() const { + return test_service_certificate_no_type_; } private: const std::string test_root_certificate_; const std::string test_intermediate_certificate_; + const std::string test_intermediate_certificate_with_ec_key_; const std::string test_user_device_certificate_; - const std::string test_service_certificate_; - - DISALLOW_COPY_AND_ASSIGN(TestDrmCertificates); + const std::string test_user_device_certificate_with_ec_key_; + const std::string test_user_device_certificate_with_rot_id_; + const std::string test_service_certificate_license_sdk_; + const std::string test_service_certificate_all_types_; + const std::string test_service_certificate_no_type_; }; } // namespace widevine + #endif // COMMON_TEST_DRM_CERTIFICATES_H_ diff --git a/common/vmp_checker.cc b/common/vmp_checker.cc index a6d15c8..64f1474 100644 --- a/common/vmp_checker.cc +++ b/common/vmp_checker.cc @@ -253,11 +253,11 @@ Status VmpChecker::SelectCertificateType(CertificateType cert_type) { Status status = ca_cert->LoadDer( cert_type == kCertificateTypeProduction ? std::string(reinterpret_cast( - kProdVmpCodeSigningDrmRootCertificate), - sizeof(kProdVmpCodeSigningDrmRootCertificate)) + kProdVmpCodeSigningDrmRootCertificate), + sizeof(kProdVmpCodeSigningDrmRootCertificate)) : std::string(reinterpret_cast( - kDevVmpCodeSigningDrmRootCertificate), - sizeof(kDevVmpCodeSigningDrmRootCertificate))); + kDevVmpCodeSigningDrmRootCertificate), + sizeof(kDevVmpCodeSigningDrmRootCertificate))); if (!status.ok()) return status; ca_.reset(new X509CA(ca_cert.release())); diff --git a/common/vmp_checker_test.cc b/common/vmp_checker_test.cc index 005dc86..5c67830 100644 --- a/common/vmp_checker_test.cc +++ b/common/vmp_checker_test.cc @@ -167,7 +167,8 @@ class VmpCheckerTest : public ::testing::Test { // |kSameAsPrevious| (empty), then the binary is signed using the same // certificate as the previously added binary. This means that the first // call to this function should not use |kSameAsPrevious|. - void AddVmpBinary(const std::string& signing_cert, const std::string& file_name, + void AddVmpBinary(const std::string& signing_cert, + const std::string& file_name, const std::string& binary_hash, uint32_t flags) { DCHECK(!signing_cert.empty() || !vmp_data_.certificates().empty()); diff --git a/common/wvm_token_handler.cc b/common/wvm_token_handler.cc index 188adc1..11e3cb8 100644 --- a/common/wvm_token_handler.cc +++ b/common/wvm_token_handler.cc @@ -85,7 +85,8 @@ PreprovKeysMap* PreprovKeysMap::GetSingleton() { } // namespace WvmTokenHandler::PreprovKey::PreprovKey(uint32_t system_id, - const std::string& key_bytes, Cipher cipher, + const std::string& key_bytes, + Cipher cipher, const std::string& model_filter) : system_id(system_id), key_bytes(key_bytes), @@ -93,7 +94,8 @@ WvmTokenHandler::PreprovKey::PreprovKey(uint32_t system_id, model_filter(model_filter) {} WvmTokenHandler::PreprovKey::PreprovKey(uint32_t system_id, - const std::string& key_bytes, Cipher cipher) + const std::string& key_bytes, + Cipher cipher) : system_id(system_id), key_bytes(key_bytes), cipher(cipher) {} WvmTokenHandler::PreprovKey::PreprovKey(uint32_t system_id, @@ -230,8 +232,10 @@ Status WvmTokenHandler::DecryptDeviceKeyWithPreprovKey( return Status(error::INVALID_ARGUMENT, "Keybox token is too short."); } if (version) { + // Bytes 0-3 contain the keybox version. *version = BigEndian::Load32(token.data()); } + // This was checked at initialization, so if it fails now something is wrong. CHECK_EQ(preprov_key.size(), kPreProvisioningKeySizeBytes); diff --git a/common/wvm_token_handler.h b/common/wvm_token_handler.h index e300928..1250f2a 100644 --- a/common/wvm_token_handler.h +++ b/common/wvm_token_handler.h @@ -12,7 +12,6 @@ #include #include -#include "base/macros.h" #include "absl/strings/string_view.h" #include "common/status.h" @@ -34,6 +33,9 @@ namespace widevine { // INTERNAL_ERROR - something went wrong that shouldn't have been able to. class WvmTokenHandler { public: + WvmTokenHandler(const WvmTokenHandler&) = delete; + WvmTokenHandler& operator=(const WvmTokenHandler&) = delete; + // Cipher type to use for encrypting asset keys. This matches the enum in // video/widevine/lockbox/public/key.proto. enum Cipher { @@ -71,22 +73,23 @@ class WvmTokenHandler { // insecure_out may be null; if not, *insecure_out will be set to the // decrypted value of the 'insecure keybox' flag. static Status DecryptDeviceKey(absl::string_view token, - std::string* device_key_out, Cipher* cipher_out, - bool* insecure_out); + std::string* device_key_out, + Cipher* cipher_out, bool* insecure_out); // Same as above, except takes in the make/model from the license request. // For legacy WVM license, we have some special cases where we need to inspect // the make/model as we apply alternate keys. static Status DecryptDeviceKey(absl::string_view token, const std::string& make_model, - std::string* device_key_out, Cipher* cipher_out, - bool* insecure_out); + std::string* device_key_out, + Cipher* cipher_out, bool* insecure_out); // Decrypt a token using the preprov key for its system ID, and use the // decrypted device key to encrypt the given asset key. Returns the encrypted // asset key in result. static Status GetEncryptedAssetKey(absl::string_view token, absl::string_view raw_asset_key, - const std::string& make_model, std::string* result); + const std::string& make_model, + std::string* result); // Extract the system ID component of a token (bytes 4-8). static uint32_t GetSystemId(absl::string_view token); @@ -115,9 +118,6 @@ class WvmTokenHandler { static Status EncryptAssetKey(absl::string_view device_key, absl::string_view raw_asset_key, Cipher cipher, std::string* result); - - private: - DISALLOW_IMPLICIT_CONSTRUCTORS(WvmTokenHandler); }; } // namespace widevine diff --git a/common/x509_cert.cc b/common/x509_cert.cc index 5699486..8627f6e 100644 --- a/common/x509_cert.cc +++ b/common/x509_cert.cc @@ -203,7 +203,8 @@ bool X509Cert::IsCaCertificate() const { return X509_check_ca(openssl_cert_) != 0; } -bool X509Cert::GetV3BooleanExtension(const std::string& oid, bool* value) const { +bool X509Cert::GetV3BooleanExtension(const std::string& oid, + bool* value) const { ScopedAsn1Object extension_name(OBJ_txt2obj(oid.c_str(), 1)); int ext_pos = X509_get_ext_by_OBJ(openssl_cert_, extension_name.get(), -1); if (ext_pos < 0) return false; diff --git a/common/x509_cert.h b/common/x509_cert.h index d773cba..6de4dcb 100644 --- a/common/x509_cert.h +++ b/common/x509_cert.h @@ -18,7 +18,6 @@ #include #include -#include "base/macros.h" #include "base/thread_annotations.h" #include "absl/synchronization/mutex.h" #include "openssl/pem.h" @@ -39,6 +38,10 @@ class X509Cert { static std::unique_ptr FromOpenSslCert(ScopedX509 openssl_cert_); X509Cert(); + + X509Cert(const X509Cert&) = delete; + X509Cert& operator=(const X509Cert&) = delete; + virtual ~X509Cert(); // Load an X.509 certificate. Takes a single parameter, |pem_cert|, which is @@ -98,14 +101,15 @@ class X509Cert { std::string subject_name_; friend class X509CertChain; - - DISALLOW_COPY_AND_ASSIGN(X509Cert); }; // Class which holds a chain of X.509 certificates. class X509CertChain { public: - X509CertChain() {} + X509CertChain() = default; + X509CertChain(const X509CertChain&) = delete; + X509CertChain& operator=(const X509CertChain&) = delete; + virtual ~X509CertChain(); // Loads a chain of PEM-encoded X.509 certificates. Takes a single parameter, @@ -135,8 +139,6 @@ class X509CertChain { void Reset(); std::vector cert_chain_; - - DISALLOW_COPY_AND_ASSIGN(X509CertChain); }; // CA class which holds the root CA cert, and verifies certificate chains. @@ -144,6 +146,10 @@ class X509CA { public: // New object assumes ownership of |ca_cert|. explicit X509CA(X509Cert* ca_cert); + + X509CA(const X509CA&) = delete; + X509CA& operator=(const X509CA&) = delete; + virtual ~X509CA(); // Does X.509 PKI validation of |cert| against the root CA certificate @@ -166,9 +172,7 @@ class X509CA { std::unique_ptr ca_cert_; absl::Mutex openssl_store_mutex_; - X509_STORE* openssl_store_ GUARDED_BY(openssl_store_mutex_); - - DISALLOW_IMPLICIT_CONSTRUCTORS(X509CA); + X509_STORE* openssl_store_ ABSL_GUARDED_BY(openssl_store_mutex_); }; } // namespace widevine diff --git a/example/BUILD b/example/BUILD index e41dd69..c6f7dce 100644 --- a/example/BUILD +++ b/example/BUILD @@ -59,7 +59,7 @@ cc_binary( "//base", "//common:status", "//media_cas_packager_sdk/public:wv_cas_key_fetcher", - "//protos/public:media_cas_encryption_proto", + "//protos/public:media_cas_encryption_cc_proto", ], ) diff --git a/example/test_ecmg_messages.h b/example/test_ecmg_messages.h index b4c7ae5..3890e99 100644 --- a/example/test_ecmg_messages.h +++ b/example/test_ecmg_messages.h @@ -14,7 +14,7 @@ namespace widevine { namespace cas { -const char kTestEcmgChannelSetup[] = { +constexpr char kTestEcmgChannelSetup[] = { '\x03', // protocol_version '\x00', '\x01', // message_type - Channel_setup '\x00', '\x0e', // message_length @@ -26,7 +26,55 @@ const char kTestEcmgChannelSetup[] = { '\x4a', '\xd4', '\x00', '\x00' // parameter_value }; -const char kTestEcmgChannelStatus[] = { +constexpr char kTestEcmgChannelSetupWithPrivateParameters[] = { + '\x03', // protocol_version + '\x00', '\x01', // message_type - Channel_setup + '\x00', '\x70', // message_length + '\x00', '\x0e', // parameter_type - ECM_channel_id + '\x00', '\x02', // parameter_length + '\x00', '\x01', // parameter_value + '\x00', '\x01', // parameter_type- SUPER_CAS_ID + '\x00', '\x04', // parameter_length + '\x4a', '\xd4', '\x00', '\x00', // parameter_value + '\x80', '\x00', // parameter_type - AGE_RESTRICTION + '\x00', '\x01', // parameter_length + '\x00', // parameter_value + '\x80', '\x01', // parameter_type - CRYPTO_MODE + '\x00', '\x07', // parameter_length + 'A', 'e', 's', 'S', 'c', 't', 'e', // parameter_value + '\x80', '\x04', // parameter_type - TRACK_TYPES + '\x00', '\x02', // parameter_length + 'S', 'D', // parameter_value + '\x80', '\x04', // parameter_type - TRACK_TYPES + '\x00', '\x02', // parameter_length + 'H', 'D', // parameter_value + '\x80', '\x02', // parameter_type - CONTENT_ID + '\x00', '\x09', // parameter_length + 'C', 'a', 's', 'T', 's', 'F', 'a', 'k', + 'e', // parameter_value - CasTsFake + '\x80', '\x03', // parameter_type - CONTENT_PROVIDER + '\x00', '\x0d', // parameter_length + 'w', 'i', 'd', 'e', 'v', 'i', 'n', 'e', + '_', 't', 'e', 's', 't', // parameter_value - widevine_test + '\x80', '\x06', // parameter_type - CONTENT_IV + '\x00', '\x10', // parameter_length + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', + '\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', + '\x80', '\x06', // parameter_type - CONTENT_IV + '\x00', '\x10', // parameter_length + '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', + '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f'}; + +constexpr char kTestEcmgChannelTest[] = { + '\x03', // protocol_version + '\x00', '\x02', // message_type - Channel_test + '\x00', '\x06', // message_length + '\x00', '\x0e', // parameter_type - ECM_channel_id + '\x00', '\x02', // parameter_length + '\x00', '\x01', // parameter_value +}; + +constexpr char kTestEcmgChannelStatus[] = { '\x03', // protocol_version '\x00', '\x03', // message_type - Channel_status '\x00', '\x39', // message_length @@ -62,7 +110,28 @@ const char kTestEcmgChannelStatus[] = { '\x00', '\x64' // parameter_value }; -const char kTestEcmgStreamSetup[] = { +constexpr char kTestEcmgStreamSetupWithPrivateParameters[] = { + '\x03', // protocol_version + '\x01', '\x01', // message_type - Stream_setup + '\x00', '\x1e', // message_length + '\x00', '\x0e', // parameter_type - ECM_channel_id + '\x00', '\x02', // parameter_length + '\x00', '\x01', // parameter_value + '\x00', '\x0f', // parameter_type - ECM_stream_id + '\x00', '\x02', // parameter_length + '\x00', '\x01', // parameter_value + '\x00', '\x19', // parameter_type - ECM_id + '\x00', '\x02', // parameter_length + '\x00', '\x02', // parameter_value + '\x00', '\x10', // parameter_type - nominal_CP_duration + '\x00', '\x02', // parameter_length + '\x00', '\x64', // parameter_value + '\x80', '\x05', // parameter_type - STREAM_TRACK_TYPE + '\x00', '\x02', // parameter_length + 'S', 'D', // parameter_value +}; + +constexpr char kTestEcmgStreamSetup[] = { '\x03', // protocol_version '\x01', '\x01', // message_type - Stream_setup '\x00', '\x18', // message_length @@ -80,7 +149,19 @@ const char kTestEcmgStreamSetup[] = { '\x00', '\x64' // parameter_value }; -const char kTestEcmgStreamStatus[] = { +constexpr char kTestEcmgStreamTest[] = { + '\x03', // protocol_version + '\x01', '\x02', // message_type - Stream_test + '\x00', '\x0c', // message_length + '\x00', '\x0e', // parameter_type - ECM_channel_id + '\x00', '\x02', // parameter_length + '\x00', '\x01', // parameter_value + '\x00', '\x0f', // parameter_type - ECM_stream_id + '\x00', '\x02', // parameter_length + '\x00', '\x01', // parameter_value +}; + +constexpr char kTestEcmgStreamStatus[] = { '\x03', // protocol_version '\x01', '\x03', // message_type - Stream_status '\x00', '\x17', // message_length @@ -98,7 +179,7 @@ const char kTestEcmgStreamStatus[] = { '\x01' // parameter_value }; -const char kTestEcmgCwProvision[] = { +constexpr char kTestEcmgCwProvision[] = { '\x03', // protocol_version '\x02', '\x01', // message_type - CW_provision '\x00', '\x44', // message_length @@ -125,8 +206,87 @@ const char kTestEcmgCwProvision[] = { '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f'}; +constexpr char kTestEcmgCwProvisionWithAccessCriteria[] = { + '\x03', // protocol_version + '\x02', '\x01', // message_type - CW_provision + '\x00', '\xaa', // message_length + '\x00', '\x0e', // parameter_type - ECM_channel_id + '\x00', '\x02', // parameter_length + '\x00', '\x01', // parameter_value + '\x00', '\x0f', // parameter_type - ECM_stream_id + '\x00', '\x02', // parameter_length + '\x00', '\x01', // parameter_value + '\x00', '\x12', // parameter_type - CP_number + '\x00', '\x02', // parameter_length + '\x00', '\x00', // parameter_value + '\x00', '\x13', // parameter_type - CP_duration + '\x00', '\x02', // parameter_length + '\x00', '\x64', // parameter_value + '\x00', '\x14', // parameter_type - CP_CW_Combination + '\x00', '\x12', // parameter_length + '\x00', '\x00', // parameter_value - CP (2 bytes) then CW next (16 bytes) + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', + '\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', + '\x00', '\x14', // parameter_type - CP_CW_Combination + '\x00', '\x12', // parameter_length + '\x00', '\x01', // parameter_value - CP (2 bytes) then CW next (16 bytes) + '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', + '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', + '\x00', '\x0d', // parameter_type - access_criteria + '\x00', '\x62', // parameter_length + '\x80', '\x00', // access_criteria parameter_type - AGE_RESTRICTION + '\x00', '\x01', // parameter_length + '\x00', // parameter_value + '\x80', '\x01', // access_criteria parameter_type - CRYPTO_MODE + '\x00', '\x07', // parameter_length + 'A', 'e', 's', 'S', 'c', 't', 'e', // parameter_value + '\x80', '\x04', // access_criteria parameter_type - TRACK_TYPES + '\x00', '\x02', // parameter_length + 'S', 'D', // parameter_value + '\x80', '\x05', // access_criteria parameter_type - STREAM_TRACK_TYPE + '\x00', '\x02', // parameter_length + 'S', 'D', // parameter_value + '\x80', '\x02', // access_criteria parameter_type - CONTENT_ID + '\x00', '\x09', // parameter_length + 'C', 'a', 's', 'T', 's', 'F', 'a', 'k', + 'e', // parameter_value - CasTsFake + '\x80', '\x03', // access_criteria parameter_type - CONTENT_PROVIDER + '\x00', '\x0d', // parameter_length + 'w', 'i', 'd', 'e', 'v', 'i', 'n', 'e', + '_', 't', 'e', 's', 't', // parameter_value - widevine_test + '\x80', '\x06', // access_criteria parameter_type - CONTENT_IV + '\x00', '\x10', // parameter_length + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', + '\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', + '\x80', '\x06', // access_criteria parameter_type - CONTENT_IV + '\x00', '\x10', // parameter_length + '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', + '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f'}; + +constexpr char kTestEcmgCwProvisionSingleKey[] = { + '\x03', // protocol_version + '\x02', '\x01', // message_type - CW_provision + '\x00', '\x2e', // message_length + '\x00', '\x0e', // parameter_type - ECM_channel_id + '\x00', '\x02', // parameter_length + '\x00', '\x01', // parameter_value + '\x00', '\x0f', // parameter_type - ECM_stream_id + '\x00', '\x02', // parameter_length + '\x00', '\x01', // parameter_value + '\x00', '\x12', // parameter_type - CP_number + '\x00', '\x02', // parameter_length + '\x00', '\x00', // parameter_value + '\x00', '\x13', // parameter_type - CP_duration + '\x00', '\x02', // parameter_length + '\x00', '\x64', // parameter_value + '\x00', '\x14', // parameter_type - CP_CW_Combination + '\x00', '\x12', // parameter_length + '\x00', '\x00', // parameter_value - CP (2 bytes) then CW next (16 bytes) + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', + '\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f'}; + // CW is encrypted using hardcoded fixed entitlement key. -const char kTestEcmgEcmResponse[] = { +constexpr char kTestEcmgEcmResponse[] = { '\x03', // protocol_version '\x02', '\x02', // message_type - ECM_response '\x00', '\xd2', // message_length @@ -142,8 +302,8 @@ const char kTestEcmgEcmResponse[] = { '\x00', '\x15', // parameter_type - ECM_datagram '\x00', '\xbc', // parameter_length // parameter_value - ECM_datagram - '\x47', '\x40', '\x02', '\x10', '\x00', '\x80', '\x70', '\x95', '\x4a', - '\xd4', '\x01', '\x05', '\x80', '\x66', '\x61', '\x6b', '\x65', '\x5f', + '\x47', '\x40', '\x00', '\x10', '\x00', '\x80', '\x70', '\xa5', '\x4a', + '\xd4', '\x02', '\x0b', '\xc0', '\x66', '\x61', '\x6b', '\x65', '\x5f', '\x6b', '\x65', '\x79', '\x5f', '\x69', '\x64', '\x31', '\x2e', '\x2e', '\x2e', '\x2e', '\xef', '\x40', '\x57', '\x48', '\xa7', '\xad', '\xdd', '\x34', '\x73', '\xfe', '\x5d', '\x1c', '\x65', '\xa0', '\xbf', '\x93', @@ -164,7 +324,7 @@ const char kTestEcmgEcmResponse[] = { '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff'}; -const char kTestEcmgStreamCloseRequest[] = { +constexpr char kTestEcmgStreamClose[] = { '\x03', // protocol_version '\x01', '\x04', // message_type - Stream_close_request '\x00', '\x0c', // message_length @@ -176,7 +336,7 @@ const char kTestEcmgStreamCloseRequest[] = { '\x00', '\x01' // parameter_value }; -const char kTestEcmgStreamCloseResponse[] = { +constexpr char kTestEcmgStreamCloseResponse[] = { '\x03', // protocol_version '\x01', '\x05', // message_type - Stream_close_response '\x00', '\x0c', // message_length @@ -188,7 +348,7 @@ const char kTestEcmgStreamCloseResponse[] = { '\x00', '\x01' // parameter_value }; -const char kTestEcmgChannelClose[] = { +constexpr char kTestEcmgChannelClose[] = { '\x03', // protocol_version '\x00', '\x04', // message_type - Channel_close '\x00', '\x06', // message_length @@ -197,6 +357,33 @@ const char kTestEcmgChannelClose[] = { '\x00', '\x01' // parameter_value }; +constexpr char kTestChannelErrorResponse[] = { + '\x03', // protocol_version + '\x00', '\x05', // message_type - Channel_error_response + '\x00', '\x0c', // message_length + '\x00', '\x0e', // parameter_type - ECM_channel_id + '\x00', '\x02', // parameter_length + '\x00', '\x01', // parameter_value + '\x70', '\x00', // parameter_type - error_status + '\x00', '\x02', // parameter_length + '\x00', '\x00' // parameter_value: actual value varies. +}; + +constexpr char kTestStreamErrorResponse[] = { + '\x03', // protocol_version + '\x01', '\x06', // message_type - Stream_error_response + '\x00', '\x12', // message_length + '\x00', '\x0e', // parameter_type - ECM_channel_id + '\x00', '\x02', // parameter_length + '\x00', '\x01', // parameter_value + '\x00', '\x0f', // parameter_type - ECM_stream_id + '\x00', '\x02', // parameter_length + '\x00', '\x01', // parameter_value + '\x70', '\x00', // parameter_type - error_status + '\x00', '\x02', // parameter_length + '\x00', '\x00' // parameter_value: actual value varies. +}; + } // namespace cas } // namespace widevine diff --git a/media_cas_packager_sdk/internal/BUILD b/media_cas_packager_sdk/internal/BUILD index 9ee1a9a..803ac9d 100644 --- a/media_cas_packager_sdk/internal/BUILD +++ b/media_cas_packager_sdk/internal/BUILD @@ -32,8 +32,8 @@ cc_library( "//common:status", "//common:string_util", "//media_cas_packager_sdk/public:wv_cas_types", - "//protos/public:media_cas_encryption_proto", - "//protos/public:media_cas_proto", + "//protos/public:media_cas_cc_proto", + "//protos/public:media_cas_encryption_cc_proto", ], ) @@ -46,7 +46,7 @@ cc_test( "//testing:gunit_main", "//common:status", "//media_cas_packager_sdk/public:wv_cas_types", - "//protos/public:media_cas_encryption_proto", + "//protos/public:media_cas_encryption_cc_proto", ], ) @@ -71,7 +71,7 @@ cc_test( "//testing:gunit_main", "@abseil_repo//absl/memory", "//common:aes_cbc_util", - "//protos/public:media_cas_encryption_proto", + "//protos/public:media_cas_encryption_cc_proto", ], ) @@ -85,19 +85,23 @@ cc_library( ], deps = [ ":ecm", - ":ecm_generator", ":fixed_key_fetcher", + ":key_fetcher", ":mpeg2ts", ":simulcrypt_util", ":util", "//base", "@abseil_repo//absl/base:core_headers", + "@abseil_repo//absl/container:node_hash_map", "@abseil_repo//absl/memory", "@abseil_repo//absl/strings", + "@abseil_repo//absl/strings:str_format", "//common:crypto_util", + "//common:random_util", "//common:status", "//example:constants", "//media_cas_packager_sdk/public:wv_cas_ecm", + "//media_cas_packager_sdk/public:wv_cas_key_fetcher", "//media_cas_packager_sdk/public:wv_cas_types", ], ) @@ -108,8 +112,12 @@ cc_test( srcs = ["ecmg_client_handler_test.cc"], deps = [ ":ecmg_client_handler", + ":simulcrypt_util", + ":util", "//testing:gunit_main", "@abseil_repo//absl/memory", + "@abseil_repo//absl/strings", + "@abseil_repo//absl/strings:str_format", "//example:test_ecmg_messages", ], ) @@ -127,6 +135,7 @@ cc_library( ":util", "//base", "@abseil_repo//absl/base:core_headers", + "@abseil_repo//absl/strings:str_format", "//common:status", "//example:test_emmg_messages", ], @@ -140,6 +149,7 @@ cc_test( ":emmg", "//testing:gunit_main", "@abseil_repo//absl/memory", + "@abseil_repo//absl/strings:str_format", "//example:test_emmg_messages", ], ) @@ -155,7 +165,7 @@ cc_library( deps = [ ":key_fetcher", "//common:status", - "//protos/public:media_cas_encryption_proto", + "//protos/public:media_cas_encryption_cc_proto", ], ) diff --git a/media_cas_packager_sdk/internal/ecm.cc b/media_cas_packager_sdk/internal/ecm.cc index 50b910f..a1c8290 100644 --- a/media_cas_packager_sdk/internal/ecm.cc +++ b/media_cas_packager_sdk/internal/ecm.cc @@ -37,14 +37,12 @@ static constexpr int kNumBitsCaSystemIdField = 16; static constexpr int kNumBitsEcmVersionField = 8; // Byte 3 -static constexpr int kNumBitsEcmGenerationCountField = 5; +// Unused bits (mbz, must be zero) +static constexpr int kNumBitsUnusedFieldByte3 = 3; // Values for Decrypt Mode are from enum CryptoMode -static constexpr int kNumBitsDecryptModeField = 2; +static constexpr int kNumBitsDecryptModeField = 4; static constexpr int kNumBitsRotationEnabledField = 1; -static constexpr int kMaxGeneration = - (1 << kNumBitsEcmGenerationCountField) - 1; - // Byte 4 // Size of IVs. // Values for IV size fields are from enum EcmIvSize @@ -52,8 +50,12 @@ static constexpr int kMaxGeneration = // always be set to 1. static constexpr int kNumBitsWrappedKeyIvSizeField = 1; static constexpr int kNumBitsContentIvSizeField = 1; +static constexpr int kNumBitsAgeRestriction = 5; // Unused bits (mbz, must be zero) -static constexpr int kNumBitsUnusedField = 6; +static constexpr int kNumBitsUnusedFieldByte4 = 1; + +// Total bits equals number of bytes * 8. +static constexpr int kBitSetTotalSize = 40; // Remaining bytes (starting from the 6th byte) hold entitled key info. static constexpr size_t kKeyIdSizeBytes = 16; @@ -71,7 +73,7 @@ static constexpr int kWvCasCaSystemId = 0x4AD4; // Version - this should be incremented if there are non-backwards compatible // changes to the ECM. -static constexpr int kEcmVersion = 1; +static constexpr int kEcmVersion = 2; // Settings for RotationEnabled field. static constexpr int kRotationDisabled = 0; @@ -144,10 +146,7 @@ Status Ecm::Initialize(const std::string& content_id, content_provider_ = content_provider; paired_keys_required_ = ecm_init_parameters.key_rotation_enabled; track_types_ = ecm_init_parameters.track_types; - // Generation is incremented before the ECM is generated. - // Initializing to kMaxGeneration ensures the first generated ECM has a gen - // count of zero. - generation_ = kMaxGeneration; + age_restriction_ = ecm_init_parameters.age_restriction; // Construct and return CasEncryptionRequest message for caller to use. Status status = CreateEntitlementRequest(key_request_message); @@ -162,6 +161,43 @@ Status Ecm::Initialize(const std::string& content_id, return OkStatus(); } +Status Ecm::Initialize( + const EcmInitParameters& ecm_init_parameters, + const std::vector& injected_entitlements) { + if (initialized_) { + return {error::INTERNAL, "Already initialized."}; + } + if (ecm_init_parameters.track_types.empty()) { + return {error::INVALID_ARGUMENT, + "Parameter track_ids must be set with one or more Track IDs."}; + } + if (!ConvertIvSizeParam(ecm_init_parameters.content_iv_size, + &content_iv_size_)) { + return {error::INVALID_ARGUMENT, + "Parameter content_iv_size must be kIvSize8 or kIvSize16."}; + } + + crypto_mode_ = ecm_init_parameters.crypto_mode; + paired_keys_required_ = ecm_init_parameters.key_rotation_enabled; + track_types_ = ecm_init_parameters.track_types; + age_restriction_ = ecm_init_parameters.age_restriction; + + ClearEntitlementKeys(); + for (const auto& entitlement : injected_entitlements) { + PushEntitlementKey(entitlement.track_type, entitlement.is_even_key, + {entitlement.key_id, entitlement.key_value}); + } + + if (!CheckEntitlementKeys()) { + return Status(error::INVALID_ARGUMENT, + "Improper injected entitlement keys."); + } + + // Everything is set up including entitlement keys. + initialized_ = true; + return OkStatus(); +} + Status Ecm::ProcessCasEncryptionResponse(const std::string& response) { if (!initialized_) { return {error::INTERNAL, "Not initialized."}; @@ -173,8 +209,8 @@ Status Ecm::ProcessCasEncryptionResponse(const std::string& response) { } Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key, - const std::string& track_type, std::string* serialized_ecm, - uint32_t* generation) { + const std::string& track_type, + std::string* serialized_ecm) const { if (!initialized_) { return {error::INTERNAL, "Not initialized."}; } @@ -190,11 +226,12 @@ Status Ecm::GenerateEcm(EntitledKeyInfo* even_key, EntitledKeyInfo* odd_key, keys.push_back(even_key); keys.push_back(odd_key); - return GenerateEcmCommon(keys, track_type, serialized_ecm, generation); + return GenerateEcmCommon(keys, track_type, serialized_ecm); } -Status Ecm::GenerateSingleKeyEcm(EntitledKeyInfo* key, const std::string& track_type, - std::string* serialized_ecm, uint32_t* generation) { +Status Ecm::GenerateSingleKeyEcm(EntitledKeyInfo* key, + const std::string& track_type, + std::string* serialized_ecm) const { if (!initialized_) { return {error::INTERNAL, "Not initialized."}; } @@ -209,18 +246,15 @@ Status Ecm::GenerateSingleKeyEcm(EntitledKeyInfo* key, const std::string& track_ std::vector keys; keys.push_back(key); - return GenerateEcmCommon(keys, track_type, serialized_ecm, generation); + return GenerateEcmCommon(keys, track_type, serialized_ecm); } Status Ecm::GenerateEcmCommon(const std::vector& keys, - const std::string& track_type, std::string* serialized_ecm, - uint32_t* generation) { + const std::string& track_type, + std::string* serialized_ecm) const { if (serialized_ecm == nullptr) { return {error::INVALID_ARGUMENT, "No return ecm std::string pointer."}; } - if (generation == nullptr) { - return {error::INVALID_ARGUMENT, "No return generation pointer."}; - } Status status = ValidateKeys(keys); if (!status.ok()) { @@ -240,29 +274,13 @@ Status Ecm::GenerateEcmCommon(const std::vector& keys, // TODO(user): validate inputs, compare against current values // TODO(user): replace current values. - // Updates complete, we are complete and consistent. - // Update generation before serializing. - uint32_t previous_generation = generation_; - IncrementGeneration(); - // Generate TS packet payload for ECM, pass back to caller. serialized_ecm->assign(SerializeEcm(keys)); - if (kMaxEcmSizeBytes < serialized_ecm->size()) { - generation_ = previous_generation; - serialized_ecm->clear(); - return Status(error::INTERNAL, "Maximum size of ECM has been exceeded."); - } - *generation = generation_; - return OkStatus(); } -void Ecm::IncrementGeneration() { - generation_ = (generation_ >= kMaxGeneration) ? 0 : generation_ + 1; -} - Status Ecm::WrapEntitledKeys(const std::string& track_type, - const std::vector keys) { + const std::vector& keys) const { if (!initialized_) { return {error::INTERNAL, "Not initialized."}; } @@ -296,8 +314,10 @@ Status Ecm::WrapEntitledKeys(const std::string& track_type, return OkStatus(); } -Status Ecm::WrapKey(const std::string& wrapping_key, const std::string& wrapping_iv, - const std::string& key_value, std::string* wrapped_key) { +Status Ecm::WrapKey(const std::string& wrapping_key, + const std::string& wrapping_iv, + const std::string& key_value, + std::string* wrapped_key) const { Status status = ValidateKeyValue(wrapping_key, kWrappingKeySizeBytes); if (!status.ok()) { return status; @@ -319,7 +339,7 @@ Status Ecm::WrapKey(const std::string& wrapping_key, const std::string& wrapping return OkStatus(); } -Status Ecm::ValidateKeys(const std::vector& keys) { +Status Ecm::ValidateKeys(const std::vector& keys) const { for (const auto& key : keys) { Status status; status = ValidateKeyId(key->key_id); @@ -342,7 +362,8 @@ Status Ecm::ValidateKeys(const std::vector& keys) { return OkStatus(); } -Status Ecm::ValidateWrappedKeys(const std::vector& keys) { +Status Ecm::ValidateWrappedKeys( + const std::vector& keys) const { for (const auto& key : keys) { Status status; status = ValidateKeyId(key->key_id); @@ -366,14 +387,15 @@ Status Ecm::ValidateWrappedKeys(const std::vector& keys) { return OkStatus(); } -Status Ecm::ValidateKeyId(const std::string& key_id) { +Status Ecm::ValidateKeyId(const std::string& key_id) const { if (key_id.size() != kKeyIdSizeBytes) { return {error::INVALID_ARGUMENT, "Key ID must be 16 bytes."}; } return OkStatus(); } -Status Ecm::ValidateKeyValue(const std::string& key_value, size_t key_value_size) { +Status Ecm::ValidateKeyValue(const std::string& key_value, + size_t key_value_size) const { if (key_value.size() != key_value_size) { return Status( error::INVALID_ARGUMENT, @@ -382,19 +404,18 @@ Status Ecm::ValidateKeyValue(const std::string& key_value, size_t key_value_size return OkStatus(); } -Status Ecm::ValidateIv(const std::string& iv, size_t size) { +Status Ecm::ValidateIv(const std::string& iv, size_t size) const { if (iv.size() != size) { return {error::INVALID_ARGUMENT, "IV is wrong size."}; } return OkStatus(); } -std::string Ecm::SerializeEcm(const std::vector& keys) { +std::string Ecm::SerializeEcm(const std::vector& keys) const { // Five bytes (40 bits including padding) std::bitset ca_system_id(kWvCasCaSystemId); std::bitset ecm_version(kEcmVersion); - std::bitset ecm_generation_count( - generation()); + std::bitset unused(kUnusedZero); std::bitset decrypt_mode( static_cast(crypto_mode())); std::bitset rotation_enabled( @@ -403,21 +424,23 @@ std::string Ecm::SerializeEcm(const std::vector& keys) { IvSizeFieldValue(kWrappedKeyIvSizeBytes)); std::bitset content_iv_size( IvSizeFieldValue(this->content_iv_size())); - std::bitset padding(kUnusedZero); + std::bitset age_restriction( + static_cast(this->age_restriction())); + std::bitset padding(kUnusedZero); // Converts bitset to string. std::string ecm_bitset = absl::StrCat( - ca_system_id.to_string(), ecm_version.to_string(), - ecm_generation_count.to_string(), decrypt_mode.to_string(), - rotation_enabled.to_string(), wrapped_key_iv_size.to_string(), - content_iv_size.to_string(), padding.to_string()); - if (ecm_bitset.size() != 40) { - LOG(FATAL) << "ECM bitset incorret size: " << ecm_bitset.size(); + ca_system_id.to_string(), ecm_version.to_string(), unused.to_string(), + decrypt_mode.to_string(), rotation_enabled.to_string(), + wrapped_key_iv_size.to_string(), content_iv_size.to_string(), + age_restriction.to_string(), padding.to_string()); + if (ecm_bitset.size() != kBitSetTotalSize) { + LOG(FATAL) << "ECM bitset incorrect size: " << ecm_bitset.size(); } std::string serialized_ecm; Status status = string_util::BitsetStringToBinaryString(ecm_bitset, &serialized_ecm); - if (!status.ok()) { + if (!status.ok() || serialized_ecm.empty()) { LOG(FATAL) << "Failed to convert ECM bitset to std::string"; } @@ -429,7 +452,7 @@ std::string Ecm::SerializeEcm(const std::vector& keys) { return serialized_ecm; } -Status Ecm::CreateEntitlementRequest(std::string* request_string) { +Status Ecm::CreateEntitlementRequest(std::string* request_string) const { CasEncryptionRequest request; request.set_content_id(content_id_); @@ -460,7 +483,7 @@ Status Ecm::ParseEntitlementResponse(const std::string& response_string) { if (!response.ParseFromString(signed_response.response())) { return {error::INTERNAL, "Failure parsing signed response."}; } - if (response.status() != CasEncryptionResponse_Status_OK) { + if (response.status() != CasEncryptionResponse::OK) { return Status(error::INTERNAL, absl::StrCat("Failure reported by server: ", response.status(), " : ", response.status_message())); @@ -507,13 +530,13 @@ Status Ecm::ParseEntitlementResponse(const std::string& response_string) { continue; } if (paired_keys_required()) { - if (key.key_slot() == CasEncryptionResponse_KeyInfo_KeySlot_EVEN) { + if (key.key_slot() == CasEncryptionResponse::KeyInfo::EVEN) { PushEntitlementKey(key.track_type(), /* is_even_key= */ true, ekey); - } else if (key.key_slot() == CasEncryptionResponse_KeyInfo_KeySlot_ODD) { + } else if (key.key_slot() == CasEncryptionResponse::KeyInfo::ODD) { PushEntitlementKey(key.track_type(), /* is_even_key= */ false, ekey); } } else { - if (key.key_slot() == CasEncryptionResponse_KeyInfo_KeySlot_SINGLE) { + if (key.key_slot() == CasEncryptionResponse::KeyInfo::SINGLE) { PushEntitlementKey(key.track_type(), /* is_even_key= */ true, ekey); } } diff --git a/media_cas_packager_sdk/internal/ecm.h b/media_cas_packager_sdk/internal/ecm.h index aadd25d..d42afcf 100644 --- a/media_cas_packager_sdk/internal/ecm.h +++ b/media_cas_packager_sdk/internal/ecm.h @@ -53,11 +53,22 @@ enum EcmIvSize { kIvSize8 = 0, kIvSize16 = 1 }; // A constant of type CryptoMode. // |track_types| a vector of track ID (std::string) that specify the set of track // types of interest; controls the entitlement keys returned by the server. +// |age_restriction| minimum age required; the value represents actual age. struct EcmInitParameters { EcmIvSize content_iv_size = kIvSize8; bool key_rotation_enabled = true; CryptoMode crypto_mode = CryptoMode::kAesCtr; std::vector track_types; + uint8_t age_restriction = 0; +}; + +// Information needed for the injected entitlement keys. Used for Ecm +// initialization. +struct InjectedEntitlementKeyInfo { + std::string track_type; + bool is_even_key; + std::string key_id; // must be 16 bytes. + std::string key_value; // must be 32 bytes. }; // Generator for producing Widevine CAS-compliant ECMs. Used to construct the @@ -83,7 +94,7 @@ class Ecm { // Args: // |content_id| uniquely identifies the content (with |content_provider|) // |content_provider| unique std::string for provider of the content stream. - // |wv_ECM_parameters| encryption-related parameters for configuring + // |ecm_init_parameters| encryption-related parameters for configuring // the ECM stream. // |key_request_message| pointer to a std::string to receive a CasEncryptionRequest // message. @@ -95,6 +106,16 @@ class Ecm { const EcmInitParameters& ecm_init_parameters, std::string* key_request_message); + // Perform initialization for a new ECM stream with injected entitlement keys. + // Args: + // |ecm_init_parameters| encryption-related parameters for configuring + // the ECM stream. + // |injected_entitlement| entitlement key info directly provided. No need to + // fetch keys from server. + virtual Status Initialize( + const EcmInitParameters& ecm_init_parameters, + const std::vector& injected_entitlements); + // Parse a CasEncryptionResponse message holding the entitlement keys for // generating the ECM stream. The entitlement keys are used to encrypt the // keys conveyed in the ECM. The entitlement key IDs are also part of the ECM. @@ -110,16 +131,14 @@ class Ecm { // |odd_key| information for odd key to be encoded into ECM. // |track_type| the track that the keys are being used to encrypt. // |serialized_ecm| caller-supplied std::string pointer to receive the ECM. - // |generation| pointer to uint32_t to receive the generation number of the ECM. // The |even_key| and |odd_key| contents (specifically IV sizes) must be // consistent with the initialized settings. // The even_key and odd_key will be wrapped using the appropriate // entitlement key. Wrapping modifies the original structure. - // Generation is a mod 32 counter. If the ECM has any changes from the - // previous ECM, the generation is increased by one. virtual Status GenerateEcm(EntitledKeyInfo* even_key, - EntitledKeyInfo* odd_key, const std::string& track_type, - std::string* serialized_ecm, uint32_t* generation); + EntitledKeyInfo* odd_key, + const std::string& track_type, + std::string* serialized_ecm) const; // Accept a key and IV and construct an ECM that will fit into a Transport // Stream packet payload (184 bytes). This call is specifically for the case @@ -128,23 +147,17 @@ class Ecm { // |key| information for key to be encoded into ECM. // |track_type| the track that the key is being used to encrypt. // |serialized_ecm| caller-supplied std::string pointer to receive the ECM. - // |generation| pointer to uint32_t to receive the generation number of the ECM. // The |key| contents (specifically IV sizes) must be consistent // with the initialized settings. - // Generation is a mod 32 counter. If the ECM has any changes from the - // previous ECM, the generation is increased by one. virtual Status GenerateSingleKeyEcm(EntitledKeyInfo* key, const std::string& track_type, - std::string* serialized_ecm, - uint32_t* generation); + std::string* serialized_ecm) const; protected: // For unit tests. // Take the input entitled |keys| and our current state, and generate // the ECM representing the keys. - virtual std::string SerializeEcm(const std::vector& keys); - - // Increment the ECM's generation count by one (mod kMaxGeneration). - virtual void IncrementGeneration(); + virtual std::string SerializeEcm( + const std::vector& keys) const; // Apply the Entitlement key with the given track type and polarity // (even vs. odd key) to wrap the given Entitled key. @@ -152,8 +165,9 @@ class Ecm { // |track_type| the track type for the keys. The type_track must match one // of the |EcmInitParameters::track_types| strings passed into Initialize(). // |key| the Entitled Key to be wrapped. - virtual Status WrapEntitledKeys(const std::string& track_type, - const std::vector keys); + virtual Status WrapEntitledKeys( + const std::string& track_type, + const std::vector& keys) const; private: // Entitlement key - |key_value| is used to wrap the content key, and |key_id| @@ -194,36 +208,37 @@ class Ecm { // Common helper for GenerateEcm() and GenerateSingleKeyEcm() virtual Status GenerateEcmCommon(const std::vector& keys, const std::string& track_type, - std::string* serialized_ecm, uint32_t* generation); + std::string* serialized_ecm) const; // Wrap |key_value| using |wrapping_key| (entitlement key) and |wrapping_iv|. // Returns the resulting wrapped key in |wrapped_key|. // Return a status indicating whether there has been any error. - virtual Status WrapKey(const std::string& wrapping_key, const std::string& wrapping_iv, - const std::string& key_value, std::string* wrapped_key); + virtual Status WrapKey(const std::string& wrapping_key, + const std::string& wrapping_iv, + const std::string& key_value, + std::string* wrapped_key) const; - virtual Status ValidateKeys(const std::vector& keys); - virtual Status ValidateWrappedKeys(const std::vector& keys); + virtual Status ValidateKeys(const std::vector& keys) const; + virtual Status ValidateWrappedKeys( + const std::vector& keys) const; - Status ValidateKeyId(const std::string& key_id); - Status ValidateKeyValue(const std::string& key_value, size_t key_value_size); - Status ValidateIv(const std::string& iv, size_t size); + Status ValidateKeyId(const std::string& key_id) const; + Status ValidateKeyValue(const std::string& key_value, + size_t key_value_size) const; + Status ValidateIv(const std::string& iv, size_t size) const; // TODO(user): need unit tests for CreateEntitlementRequest. - virtual Status CreateEntitlementRequest(std::string* request_string); + virtual Status CreateEntitlementRequest(std::string* request_string) const; // TODO(user): need unit tests for ParseEntitlementResponse. virtual Status ParseEntitlementResponse(const std::string& response_string); - virtual uint32_t generation() const { return generation_; } virtual CryptoMode crypto_mode() const { return crypto_mode_; } + virtual uint8_t age_restriction() const { return age_restriction_; } virtual bool paired_keys_required() const { return paired_keys_required_; } virtual size_t content_iv_size() const { return content_iv_size_; } // Set to true when the object has been properly initialized. bool initialized_ = false; - // Current generation. This will be incremented (mod 32) each time a new - // ECM has changes from the previous ECM. - uint32_t generation_ = 0; // Content ID for this ECM stream. std::string content_id_; // Provider ID for this ECM stream. @@ -235,6 +250,7 @@ class Ecm { // Remember if a pair of keys is required (for key rotation). bool paired_keys_required_ = false; CryptoMode crypto_mode_ = CryptoMode::kAesCtr; + uint8_t age_restriction_ = 0; // Entitlement keys needed for ECM generation. // The keys are added when the CasEncryptionResponse is processed. std::map> entitlement_keys_; diff --git a/media_cas_packager_sdk/internal/ecm_generator.cc b/media_cas_packager_sdk/internal/ecm_generator.cc index c22708f..a58d82b 100644 --- a/media_cas_packager_sdk/internal/ecm_generator.cc +++ b/media_cas_packager_sdk/internal/ecm_generator.cc @@ -26,15 +26,11 @@ std::string EcmGenerator::GenerateEcm(const EcmParameters& params) { return ""; } std::string serialized_ecm; - uint32_t generation; - // TODO(user): need track_type - std::string track_type = "SD"; + std::string track_type = params.track_type; if (params.rotation_enabled) { - status = ecm_->GenerateEcm(&keys[0], &keys[1], track_type, &serialized_ecm, - &generation); + status = ecm_->GenerateEcm(&keys[0], &keys[1], track_type, &serialized_ecm); } else { - status = ecm_->GenerateSingleKeyEcm(&keys[0], track_type, &serialized_ecm, - &generation); + status = ecm_->GenerateSingleKeyEcm(&keys[0], track_type, &serialized_ecm); } if (!status.ok()) { LOG(ERROR) << " Generate ECM call failed: " << status; @@ -55,6 +51,11 @@ Status EcmGenerator::ProcessEcmParameters(const EcmParameters& ecm_params, return {error::INVALID_ARGUMENT, "Number of supplied keys is wrong (check rotation periods)."}; } + // If content_iv_size_ is zero, use the size of first content IV as the + // expected size for all future IVs in this stream. + if (content_iv_size_ == 0) { + content_iv_size_ = ecm_params.key_params[0].content_iv.size(); + } for (int i = 0; i < keys_needed; i++) { Status status = ValidateKeyParameters(ecm_params.key_params[i]); if (!status.ok()) { @@ -65,7 +66,7 @@ Status EcmGenerator::ProcessEcmParameters(const EcmParameters& ecm_params, key.key_id = ecm_params.key_params[i].key_id; key.key_value = ecm_params.key_params[i].key_data; key.wrapped_key_iv = ecm_params.key_params[i].wrapped_key_iv; - key.content_iv = ecm_params.key_params[i].content_ivs[0]; + key.content_iv = ecm_params.key_params[i].content_iv; } current_key_index_ = 0; current_key_even_ = true; @@ -73,7 +74,7 @@ Status EcmGenerator::ProcessEcmParameters(const EcmParameters& ecm_params, return OkStatus(); } -Status EcmGenerator::ValidateKeyId(const std::string& id) { +Status EcmGenerator::ValidateKeyId(const std::string& id) const { if (id.empty()) { return {error::INVALID_ARGUMENT, "Key id is empty."}; } @@ -83,7 +84,7 @@ Status EcmGenerator::ValidateKeyId(const std::string& id) { return OkStatus(); } -Status EcmGenerator::ValidateKeyData(const std::string& key_data) { +Status EcmGenerator::ValidateKeyData(const std::string& key_data) const { if (key_data.empty()) { return {error::INVALID_ARGUMENT, "Key data is empty."}; } @@ -93,7 +94,8 @@ Status EcmGenerator::ValidateKeyData(const std::string& key_data) { return OkStatus(); } -Status EcmGenerator::ValidateIv(const std::string& iv, size_t required_size) { +Status EcmGenerator::ValidateIv(const std::string& iv, + size_t required_size) const { if (iv.empty()) { return {error::INVALID_ARGUMENT, "IV is empty."}; } @@ -107,7 +109,7 @@ Status EcmGenerator::ValidateIv(const std::string& iv, size_t required_size) { return OkStatus(); } -Status EcmGenerator::ValidateWrappedKeyIv(const std::string& iv) { +Status EcmGenerator::ValidateWrappedKeyIv(const std::string& iv) const { // All wrapped key IVs must be 16 bytes. Status status = ValidateIv(iv, kIvSize16); if (!status.ok()) { @@ -116,12 +118,7 @@ Status EcmGenerator::ValidateWrappedKeyIv(const std::string& iv) { return status; } -Status EcmGenerator::ValidateContentIv(const std::string& iv) { - // If content_iv_size_ is zero, use this IV as the size for all future IVs in - // this stream. - if (content_iv_size_ == 0) { - content_iv_size_ = iv.size(); - } +Status EcmGenerator::ValidateContentIv(const std::string& iv) const { Status status = ValidateIv(iv, content_iv_size_); if (!status.ok()) { LOG(ERROR) << " Content IV is not valid: " << status; @@ -129,18 +126,12 @@ Status EcmGenerator::ValidateContentIv(const std::string& iv) { return status; } -Status EcmGenerator::ValidateKeyParameters(const KeyParameters& key_params) { - Status status; - status = ValidateKeyId(key_params.key_id); +Status EcmGenerator::ValidateKeyParameters( + const KeyParameters& key_params) const { + Status status = ValidateKeyId(key_params.key_id); if (!status.ok()) return status; - if (key_params.content_ivs.empty()) { - return {error::INVALID_ARGUMENT, "Content IVs is empty."}; - } - for (int i = 0; i < key_params.content_ivs.size(); i++) { - status = ValidateContentIv(key_params.content_ivs[i]); - if (!status.ok()) return status; - } - return OkStatus(); + + return ValidateContentIv(key_params.content_iv); } } // namespace cas diff --git a/media_cas_packager_sdk/internal/ecm_generator.h b/media_cas_packager_sdk/internal/ecm_generator.h index 91a60e3..3ad0a5b 100644 --- a/media_cas_packager_sdk/internal/ecm_generator.h +++ b/media_cas_packager_sdk/internal/ecm_generator.h @@ -10,6 +10,7 @@ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECM_GENERATOR_H_ #include + #include #include #include @@ -27,14 +28,8 @@ namespace cas { struct KeyParameters { std::string key_id; std::string key_data; - // TODO(user): wrapped_key_data does not seem to be used, but assumed - // to exist by unit tests. - std::string wrapped_key_data; - // wrapped_key_iv is randomly generated right before it is used to encrypt - // key_data in ecm.cc. std::string wrapped_key_iv; - // TODO(user): Probably only need a single content_iv instead of a vector. - std::vector content_ivs; + std::string content_iv; }; // EcmParameters holds information that is needed by the EcmGenerator. It is @@ -48,6 +43,7 @@ struct EcmParameters { // TODO(user): rotation_periods does not seem to be used, but assumed // to exist by unit tests. uint32_t rotation_periods; + std::string track_type; // TODO(user): Consider changing the vector to just two variables, // one for even key, the other for odd key. std::vector key_params; @@ -64,8 +60,8 @@ class EcmGenerator { virtual std::string GenerateEcm(const EcmParameters& params); // Query the state of this ECM Generator - bool initialized() { return initialized_; } - bool rotation_enabled() { return rotation_enabled_; } + bool initialized() const { return initialized_; } + bool rotation_enabled() const { return rotation_enabled_; } void set_ecm(std::unique_ptr ecm) { ecm_ = std::move(ecm); } @@ -75,16 +71,14 @@ class EcmGenerator { Status ProcessEcmParameters(const EcmParameters& ecm_params, std::vector* keys); - Status ProcessEcmParameters(const EcmParameters& ecm_params); - Status ValidateKeyId(const std::string& id); - Status ValidateKeyData(const std::string& key_data); - Status ValidateWrappedKeyIv(const std::string& iv); - Status ValidateIv(const std::string& iv, size_t required_size); - Status ValidateContentIv(const std::string& iv); - Status ValidateKeyParameters(const KeyParameters& key_params); + Status ValidateKeyId(const std::string& id) const; + Status ValidateKeyData(const std::string& key_data) const; + Status ValidateWrappedKeyIv(const std::string& iv) const; + Status ValidateIv(const std::string& iv, size_t required_size) const; + Status ValidateContentIv(const std::string& iv) const; + Status ValidateKeyParameters(const KeyParameters& key_params) const; bool initialized_ = false; - uint32_t generation_ = 0; bool rotation_enabled_ = false; uint32_t current_key_index_ = 0; bool current_key_even_ = true; diff --git a/media_cas_packager_sdk/internal/ecm_generator_test.cc b/media_cas_packager_sdk/internal/ecm_generator_test.cc index 0c999e4..7e6fbde 100644 --- a/media_cas_packager_sdk/internal/ecm_generator_test.cc +++ b/media_cas_packager_sdk/internal/ecm_generator_test.cc @@ -34,19 +34,16 @@ constexpr char kEntitlementKeyDouble[] = "testEKIdabcdefgh"; constexpr char kEcmKeyIdSingle[] = "key-id-One123456"; constexpr char kEcmKeyDataSingle[] = "0123456701234567"; -constexpr char kEcmWrappedKeySingle[] = "1234567890123456"; constexpr char kEcmWrappedKeyIvSingle[] = "abcdefghacbdefgh"; constexpr char kEcmContentIvSingle[] = "ABCDEFGH"; constexpr char kEcmKeyIdEven[] = "key-Id-One123456"; constexpr char kEcmKeyDataEven[] = "KKEEYYDDAATTAAee"; -constexpr char kEcmWrappedKeyEven[] = "1234567890123456"; constexpr char kEcmWrappedKeyIvEven[] = "abcdefghacbdefgh"; constexpr char kEcmContentIvEven[] = "ABCDEFGH"; constexpr char kEcmKeyIdOdd[] = "key-Id-Two456789"; constexpr char kEcmKeyDataOdd[] = "kkeeyyddaattaaOO"; -constexpr char kEcmWrappedKeyOdd[] = "9876543210654321"; constexpr char kEcmWrappedKeyIvOdd[] = "a1c2e3g4a1c2e3g4"; constexpr char kEcmContentIvOdd[] = "AaCbEcGd"; @@ -54,13 +51,15 @@ constexpr char kFakeCasEncryptionResponseKeyId[] = "fake_key_id....."; constexpr char kFakeCasEncryptionResponseKeyData[] = "fakefakefakefakefakefakefakefake"; +constexpr char kTrackType[] = "SD"; + Status HandleCasEncryptionRequest(const std::string& request_string, std::string* signed_response_string) { CasEncryptionRequest request; request.ParseFromString(request_string); CasEncryptionResponse response; - response.set_status(CasEncryptionResponse_Status_OK); + response.set_status(CasEncryptionResponse::OK); response.set_content_id(request.content_id()); for (const auto& track_type : request.track_types()) { if (request.key_rotation()) { @@ -69,19 +68,19 @@ Status HandleCasEncryptionRequest(const std::string& request_string, key->set_key_id(kFakeCasEncryptionResponseKeyId); key->set_key(kFakeCasEncryptionResponseKeyData); key->set_track_type(track_type); - key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_EVEN); + key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN); // Add the Odd key. key = response.add_entitlement_keys(); key->set_key_id(kFakeCasEncryptionResponseKeyId); key->set_key(kFakeCasEncryptionResponseKeyData); key->set_track_type(track_type); - key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_ODD); + key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD); } else { auto key = response.add_entitlement_keys(); key->set_key_id(kFakeCasEncryptionResponseKeyId); key->set_key(kFakeCasEncryptionResponseKeyData); key->set_track_type(track_type); - key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_SINGLE); + key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE); } } std::string response_string; @@ -96,8 +95,7 @@ Status HandleCasEncryptionRequest(const std::string& request_string, class EcmGeneratorTest : public testing::Test { protected: - void SetUp() override { - } + void SetUp() override {} Status ProcessEcmParameters(const EcmParameters& params, std::vector* keys) { @@ -112,6 +110,7 @@ class EcmGeneratorTest : public testing::Test { params->entitlement_key_id = kEntitlementKeySingle; params->rotation_enabled = false; params->key_params.push_back(kKeyParamsSingle); + params->track_type = kTrackType; } void SetTestConfig2(EcmParameters* params) { @@ -122,6 +121,7 @@ class EcmGeneratorTest : public testing::Test { params->rotation_periods = 2; params->key_params.push_back(kKeyParamsEven); params->key_params.push_back(kKeyParamsOdd); + params->track_type = kTrackType; } // Call this to setup the guts (Ecm) of the ECM Generator. @@ -130,7 +130,7 @@ class EcmGeneratorTest : public testing::Test { std::string entitlement_request; std::string entitlement_response; ecm_init_params_.key_rotation_enabled = key_rotation_enabled; - ecm_init_params_.track_types.push_back("SD"); + ecm_init_params_.track_types.push_back(kTrackType); ASSERT_OK(ecm_->Initialize(kContentId, kProvider, ecm_init_params_, &entitlement_request)); ASSERT_OK( @@ -141,17 +141,14 @@ class EcmGeneratorTest : public testing::Test { const KeyParameters kKeyParamsSingle{kEcmKeyIdSingle, kEcmKeyDataSingle, - kEcmWrappedKeySingle, kEcmWrappedKeyIvSingle, {kEcmContentIvSingle}}; const KeyParameters kKeyParamsEven{kEcmKeyIdEven, kEcmKeyDataEven, - kEcmWrappedKeyEven, kEcmWrappedKeyIvEven, {kEcmContentIvEven}}; const KeyParameters kKeyParamsOdd{kEcmKeyIdOdd, kEcmKeyDataOdd, - kEcmWrappedKeyOdd, kEcmWrappedKeyIvOdd, {kEcmContentIvOdd}}; @@ -189,7 +186,7 @@ TEST_F(EcmGeneratorTest, GenerateNoRotation) { // Expected size (bytes): // CA system ID: 2 bytes // version: 1 byte - // generation + flags: 1 byte + // flags: 1 byte // flags: 1 byte // entitlement key ID: 16 bytes // Single key: ID (16), Data (16), IV (16), IV (8) = 56 @@ -197,12 +194,13 @@ TEST_F(EcmGeneratorTest, GenerateNoRotation) { ASSERT_EQ(77, ecm_string.size()); EXPECT_EQ('\x4A', ecm_string[0]); // CA System ID first byte. EXPECT_EQ('\xD4', ecm_string[1]); // CA System ID second byte. - EXPECT_EQ('\x01', ecm_string[2]); // ECM version - EXPECT_EQ('\x02', ecm_string[3]); // generation + flags + EXPECT_EQ('\x02', ecm_string[2]); // ECM version + EXPECT_EQ('\x02', ecm_string[3]); // flags EXPECT_EQ('\x80', ecm_string[4]); // flags EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(5, 16)); EXPECT_EQ(kEcmKeyIdSingle, ecm_string.substr(21, 16)); - EXPECT_NE(kEcmWrappedKeySingle, ecm_string.substr(37, 16)); + // Key data has been wrapped (encrypted). + EXPECT_NE(kEcmKeyDataSingle, ecm_string.substr(37, 16)); EXPECT_EQ(kEcmWrappedKeyIvSingle, ecm_string.substr(53, 16)); // Unwrap key and compare with original. std::string wrapping_key = kFakeCasEncryptionResponseKeyData; @@ -223,16 +221,16 @@ TEST_F(EcmGeneratorTest, Generate2NoRotation) { std::string ecm_string = ecm_gen_.GenerateEcm(ecm_params); ecm_string = ecm_gen_.GenerateEcm(ecm_params); - // Second generate should have higher generation number. ASSERT_EQ(77, ecm_string.size()); EXPECT_EQ('\x4A', ecm_string[0]); // CA System ID first byte. EXPECT_EQ('\xD4', ecm_string[1]); // CA System ID second byte. - EXPECT_EQ('\x01', ecm_string[2]); // ECM version - EXPECT_EQ('\x0A', ecm_string[3]); // generation + flags + EXPECT_EQ('\x02', ecm_string[2]); // ECM version + EXPECT_EQ('\x02', ecm_string[3]); // flags EXPECT_EQ('\x80', ecm_string[4]); // flags EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(5, 16)); EXPECT_EQ(kEcmKeyIdSingle, ecm_string.substr(21, 16)); - EXPECT_NE(kEcmWrappedKeySingle, ecm_string.substr(37, 16)); + // Key data has been wrapped (encrypted). + EXPECT_NE(kEcmKeyDataSingle, ecm_string.substr(37, 16)); EXPECT_EQ(kEcmWrappedKeyIvSingle, ecm_string.substr(53, 16)); // Unwrap key and compare with original. std::string wrapping_key = kFakeCasEncryptionResponseKeyData; @@ -278,17 +276,19 @@ TEST_F(EcmGeneratorTest, GenerateSimpleRotation) { ASSERT_EQ(149, ecm_string.size()); EXPECT_EQ('\x4A', ecm_string[0]); // CA System ID first byte. EXPECT_EQ('\xD4', ecm_string[1]); // CA System ID second byte. - EXPECT_EQ('\x01', ecm_string[2]); // ECM version - EXPECT_EQ('\x03', ecm_string[3]); // generation + flags + EXPECT_EQ('\x02', ecm_string[2]); // ECM version + EXPECT_EQ('\x03', ecm_string[3]); // flags EXPECT_EQ('\x80', ecm_string[4]); // flags EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(5, 16)); EXPECT_EQ(kEcmKeyIdEven, ecm_string.substr(21, 16)); - EXPECT_NE(kEcmWrappedKeyEven, ecm_string.substr(37, 16)); + // Key data has been wrapped (encrypted). + EXPECT_NE(kEcmKeyDataEven, ecm_string.substr(37, 16)); EXPECT_EQ(kEcmWrappedKeyIvEven, ecm_string.substr(53, 16)); EXPECT_EQ(kEcmContentIvEven, ecm_string.substr(69, 8)); EXPECT_EQ(kFakeCasEncryptionResponseKeyId, ecm_string.substr(77, 16)); EXPECT_EQ(kEcmKeyIdOdd, ecm_string.substr(93, 16)); - EXPECT_NE(kEcmWrappedKeyOdd, ecm_string.substr(109, 16)); + // Key data has been wrapped (encrypted). + EXPECT_NE(kEcmKeyDataOdd, ecm_string.substr(109, 16)); EXPECT_EQ(kEcmWrappedKeyIvOdd, ecm_string.substr(125, 16)); EXPECT_EQ(kEcmContentIvOdd, ecm_string.substr(141, 8)); // Unwrap even key and compare with original. diff --git a/media_cas_packager_sdk/internal/ecm_test.cc b/media_cas_packager_sdk/internal/ecm_test.cc index 712a5f7..098c21d 100644 --- a/media_cas_packager_sdk/internal/ecm_test.cc +++ b/media_cas_packager_sdk/internal/ecm_test.cc @@ -9,6 +9,7 @@ #include "media_cas_packager_sdk/internal/ecm.h" #include + #include #include "testing/gmock.h" @@ -61,7 +62,7 @@ class MockEcm : public Ecm { MockEcm() = default; ~MockEcm() override = default; - MOCK_CONST_METHOD0(generation, uint32_t()); + MOCK_CONST_METHOD0(age_restriction, uint8_t()); MOCK_CONST_METHOD0(crypto_mode, CryptoMode()); MOCK_CONST_METHOD0(paired_keys_required, bool()); MOCK_CONST_METHOD0(content_iv_size, size_t()); @@ -71,7 +72,8 @@ class MockEcm : public Ecm { } virtual Status MockWrapEntitledKeys( - const std::string& track_type, const std::vector& keys) { + const std::string& track_type, + const std::vector& keys) { for (auto entitled_key : keys) { entitled_key->entitlement_key_id = "entitlement_Mock"; entitled_key->wrapped_key_value = "MockMockMockMock"; @@ -80,9 +82,11 @@ class MockEcm : public Ecm { return OkStatus(); } - void MockSetup(bool two_keys, const std::string& track_type, uint32_t generation, - CryptoMode crypto_mode, size_t civ_size) { - EXPECT_CALL(*this, generation()).WillRepeatedly(Return(generation)); + void MockSetup(bool two_keys, const std::string& track_type, + uint8_t age_restriction, CryptoMode crypto_mode, + size_t civ_size) { + EXPECT_CALL(*this, age_restriction()) + .WillRepeatedly(Return(age_restriction)); EXPECT_CALL(*this, crypto_mode()).WillRepeatedly(Return(crypto_mode)); EXPECT_CALL(*this, paired_keys_required()).WillRepeatedly(Return(two_keys)); EXPECT_CALL(*this, content_iv_size()).WillRepeatedly(Return(civ_size)); @@ -108,27 +112,28 @@ class EcmTest : public testing::Test { params_simple_.track_types.push_back(kTrackTypeSD); } - virtual void InitParams(EcmInitParameters* params, const std::string& track_type) { + virtual void InitParams(EcmInitParameters* params, + const std::string& track_type) { params->track_types.push_back(track_type); } - virtual void InitParams(EcmInitParameters* params, const std::string& track_type, - EcmIvSize content_iv) { + virtual void InitParams(EcmInitParameters* params, + const std::string& track_type, EcmIvSize content_iv) { params->track_types.push_back(track_type); params->content_iv_size = content_iv; } virtual void ServerCall(const std::string& request_string, - std::string* signed_response_string, bool report_status_ok, - bool generate_valid_ecm) { + std::string* signed_response_string, + bool report_status_ok, bool generate_valid_ecm) { CasEncryptionRequest request; ASSERT_TRUE(request.ParseFromString(request_string)); CasEncryptionResponse response; if (!report_status_ok) { - response.set_status(CasEncryptionResponse_Status_INTERNAL_ERROR); + response.set_status(CasEncryptionResponse::INTERNAL_ERROR); } else { - response.set_status(CasEncryptionResponse_Status_OK); + response.set_status(CasEncryptionResponse::OK); response.set_content_id(request.content_id()); for (const auto& track_type : request.track_types()) { if (request.key_rotation()) { @@ -137,18 +142,18 @@ class EcmTest : public testing::Test { key->set_key_id("fake_key_id....."); key->set_key("fakefakefakefakefakefakefakefake"); key->set_track_type(track_type); - key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_EVEN); + key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN); // Add the Odd key. key = response.add_entitlement_keys(); key->set_key_id("fake_key_id....."); key->set_key("fakefakefakefakefakefakefakefake"); key->set_track_type(track_type); - key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_ODD); + key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD); if (!generate_valid_ecm) { auto key = response.add_entitlement_keys(); key->set_key_id("fake_key_id....."); key->set_key("fakefakefakefakefakefakefakefake"); - key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_EVEN); + key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN); key->set_track_type(track_type); } } else { @@ -156,12 +161,12 @@ class EcmTest : public testing::Test { key->set_key_id("fake_key_id....."); key->set_key("fakefakefakefakefakefakefakefake"); key->set_track_type(track_type); - key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_SINGLE); + key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE); if (!generate_valid_ecm) { auto key = response.add_entitlement_keys(); key->set_key_id("fake_key_id....."); key->set_key("fakefakefakefakefakefakefakefake"); - key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_SINGLE); + key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE); key->set_track_type(track_type); } } @@ -189,37 +194,41 @@ class EcmTest : public testing::Test { class EcmSerializeEcmTest : public EcmTest { public: - void ValidateEcmHeaderFields(const std::string& ecm_string, bool rotation_enabled, - int gen, CryptoMode crypto_mode, - EcmIvSize content_iv) { + void ValidateEcmHeaderFields(const std::string& ecm_string, + bool rotation_enabled, int age_restriction, + CryptoMode crypto_mode, EcmIvSize content_iv) { EXPECT_THAT('\x4A', ecm_string[0]); EXPECT_THAT('\xD4', ecm_string[1]); - EXPECT_THAT('\x01', ecm_string[2]); // version - EXPECT_THAT(gen, ((ecm_string[3] >> 3) & 31)); - EXPECT_THAT(static_cast(crypto_mode), ((ecm_string[3] >> 1) & 3)); + EXPECT_THAT('\x02', ecm_string[2]); // version + EXPECT_THAT(static_cast(crypto_mode), ((ecm_string[3] >> 1) & 0xf)); EXPECT_THAT(rotation_enabled ? 1 : 0, ecm_string[3] & 1); EXPECT_THAT(1, ((ecm_string[4] >> 7) & 1)); // wrapped key IV size, MB 1 EXPECT_THAT(static_cast(content_iv), ((ecm_string[4] >> 6) & 1)); - EXPECT_THAT(0, ecm_string[4] & 63); // zero padding + EXPECT_THAT(age_restriction, ((ecm_string[4] >> 1) & 0x1f)); + EXPECT_THAT(0, ecm_string[4] & 1); // zero padding } - void ValidateEcmFieldsOneKey(const std::string& buf_string, int gen, - CryptoMode crypto_mode, EcmIvSize content_iv) { + void ValidateEcmFieldsOneKey(const std::string& buf_string, + int age_restriction, CryptoMode crypto_mode, + EcmIvSize content_iv) { size_t expected_size = kEcmHeaderSize + kEcmKeyInfoSize + kEcmIvSize16 + IvExpectedSize(content_iv); size_t ecm_len = buf_string.size(); EXPECT_THAT(expected_size, ecm_len); - ValidateEcmHeaderFields(buf_string, false, gen, crypto_mode, content_iv); + ValidateEcmHeaderFields(buf_string, false, age_restriction, crypto_mode, + content_iv); } - void ValidateEcmFieldsTwoKeys(const std::string& buf_string, int gen, - CryptoMode crypto_mode, EcmIvSize content_iv) { + void ValidateEcmFieldsTwoKeys(const std::string& buf_string, + int age_restriction, CryptoMode crypto_mode, + EcmIvSize content_iv) { size_t expected_size = kEcmHeaderSize + (2 * (kEcmKeyInfoSize + kEcmIvSize16 + IvExpectedSize(content_iv))); size_t ecm_len = buf_string.size(); EXPECT_THAT(expected_size, ecm_len); - ValidateEcmHeaderFields(buf_string, true, gen, crypto_mode, content_iv); + ValidateEcmHeaderFields(buf_string, true, age_restriction, crypto_mode, + content_iv); } }; @@ -227,9 +236,8 @@ TEST_F(EcmTest, GenerateEcmNotInitialized) { Ecm ecm_gen; EntitledKeyInfo key1; std::string ecm_data; - uint32_t gen; EXPECT_EQ(error::INTERNAL, - ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm_data, &gen) + ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm_data) .error_code()); } @@ -238,10 +246,9 @@ TEST_F(EcmTest, GenerateEcm2NotInitialized) { EntitledKeyInfo key1; EntitledKeyInfo key2; std::string ecm_data; - uint32_t gen; - EXPECT_EQ(error::INTERNAL, - ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm_data, &gen) - .error_code()); + EXPECT_EQ( + error::INTERNAL, + ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm_data).error_code()); } TEST_F(EcmTest, SetResponseNotInitialized) { @@ -314,12 +321,11 @@ TEST_F(EcmTest, GenerateWithNoEntitlementOneKeyFail) { ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request)); std::string ecm; - uint32_t gen; // This will fail because the entitlement key has not been acquired // (via server call and ProcessCasEncryptionResponse()). - EXPECT_EQ(error::INTERNAL, - ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm, &gen) - .error_code()); + EXPECT_EQ( + error::INTERNAL, + ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm).error_code()); } TEST_F(EcmTest, GenerateWithNoEntitlementTwoKeysFail) { @@ -332,12 +338,10 @@ TEST_F(EcmTest, GenerateWithNoEntitlementTwoKeysFail) { ecm_gen.Initialize(content_id_, provider_, params_two_keys_, &request)); std::string ecm; - uint32_t gen; // This will fail because the entitlement keys have not been acquired // (via server call and ProcessCasEncryptionResponse()). - EXPECT_EQ( - error::INTERNAL, - ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen).error_code()); + EXPECT_EQ(error::INTERNAL, + ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm).error_code()); } TEST_F(EcmTest, RequestNullFail) { @@ -361,8 +365,7 @@ TEST_F(EcmTest, GenerateOneKeyOK) { ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response)); std::string ecm; - uint32_t gen; - ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm, &gen)); + ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm)); } TEST_F(EcmTest, BadResponseFail) { @@ -395,27 +398,7 @@ TEST_F(EcmTest, GenerateTwoKeysOK) { ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response)); std::string ecm; - uint32_t gen; - ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen)); -} - -TEST_F(EcmTest, GenerateOneKeyNoGenFail) { - Ecm ecm_gen; - EntitledKeyInfo key1 = valid1_iv_16_8_; - InitParams(¶ms_one_key_, kTrackTypeSD, kIvSize8); - std::string request; - ASSERT_OK( - ecm_gen.Initialize(content_id_, provider_, params_one_key_, &request)); - - std::string response; - ServerCall(request, &response, true, true); - - ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response)); - - std::string ecm; - EXPECT_EQ(error::INVALID_ARGUMENT, - ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm, nullptr) - .error_code()); + ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm)); } TEST_F(EcmTest, GenerateOneKeyBadKeyIdFail) { @@ -432,10 +415,9 @@ TEST_F(EcmTest, GenerateOneKeyBadKeyIdFail) { ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response)); std::string ecm; - uint32_t gen; - EXPECT_EQ(error::INVALID_ARGUMENT, - ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm, &gen) - .error_code()); + EXPECT_EQ( + error::INVALID_ARGUMENT, + ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm).error_code()); } TEST_F(EcmTest, GenerateOneKeyWrong) { @@ -452,13 +434,10 @@ TEST_F(EcmTest, GenerateOneKeyWrong) { ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response)); std::string ecm; - uint32_t gen; - ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm, &gen)); - EXPECT_THAT(0, gen); + ASSERT_OK(ecm_gen.GenerateSingleKeyEcm(&key1, kTrackTypeSD, &ecm)); EXPECT_THAT(77, ecm.size()); - EXPECT_EQ( - error::INVALID_ARGUMENT, - ecm_gen.GenerateEcm(&key1, &key1, kTrackTypeSD, &ecm, &gen).error_code()); + EXPECT_EQ(error::INVALID_ARGUMENT, + ecm_gen.GenerateEcm(&key1, &key1, kTrackTypeSD, &ecm).error_code()); } TEST_F(EcmTest, GenerateTwoKeysIvSizeFail) { @@ -477,10 +456,8 @@ TEST_F(EcmTest, GenerateTwoKeysIvSizeFail) { ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response)); std::string ecm; - uint32_t gen; - EXPECT_EQ( - error::INVALID_ARGUMENT, - ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen).error_code()); + EXPECT_EQ(error::INVALID_ARGUMENT, + ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm).error_code()); } TEST_F(EcmTest, GenerateTwoKeysIvSize16x8OK) { @@ -498,12 +475,9 @@ TEST_F(EcmTest, GenerateTwoKeysIvSize16x8OK) { ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response)); std::string ecm; - uint32_t gen; - ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen)); - EXPECT_THAT(0, gen); + ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm)); EXPECT_THAT(149, ecm.size()); - ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen)); - EXPECT_THAT(1, gen); + ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm)); EXPECT_THAT(149, ecm.size()); } @@ -523,12 +497,9 @@ TEST_F(EcmTest, GenerateTwoKeysIvSize16x16OK) { ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response)); std::string ecm; - uint32_t gen; - ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen)); - EXPECT_THAT(0, gen); + ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm)); EXPECT_THAT(165, ecm.size()); - ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen)); - EXPECT_THAT(1, gen); + ASSERT_OK(ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm)); EXPECT_THAT(165, ecm.size()); } @@ -548,10 +519,8 @@ TEST_F(EcmTest, GenerateThreeKeysIvSize16x16Fail) { ASSERT_OK(ecm_gen.ProcessCasEncryptionResponse(response)); std::string ecm; - uint32_t gen; - EXPECT_EQ( - error::INTERNAL, - ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm, &gen).error_code()); + EXPECT_EQ(error::INTERNAL, + ecm_gen.GenerateEcm(&key1, &key2, kTrackTypeSD, &ecm).error_code()); } // TODO(user): Add more unit tests for error paths around SerializeEcm. @@ -568,14 +537,10 @@ TEST_F(EcmSerializeEcmTest, SerializeEcmDoubleKey16ByteIvs) { ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys)); std::string buf_string = ecm_gen.CallSerializeEcm(keys); - ValidateEcmFieldsTwoKeys(buf_string, 0, CryptoMode::kAesCtr, kIvSize16); - EXPECT_CALL(ecm_gen, generation()).WillRepeatedly(Return(1)); - buf_string = ecm_gen.CallSerializeEcm(keys); - - ValidateEcmFieldsTwoKeys(buf_string, 1, CryptoMode::kAesCtr, kIvSize16); + ValidateEcmFieldsTwoKeys(buf_string, 0, CryptoMode::kAesCtr, kIvSize16); } TEST_F(EcmSerializeEcmTest, SerializeEcmSingleKey16ByteIvs) { @@ -589,14 +554,10 @@ TEST_F(EcmSerializeEcmTest, SerializeEcmSingleKey16ByteIvs) { ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys)); std::string buf_string = ecm_gen.CallSerializeEcm(keys); - ValidateEcmFieldsOneKey(buf_string, 0, CryptoMode::kAesCtr, kIvSize16); - EXPECT_CALL(ecm_gen, generation()).WillRepeatedly(Return(1)); - buf_string = ecm_gen.CallSerializeEcm(keys); - - ValidateEcmFieldsOneKey(buf_string, 1, CryptoMode::kAesCtr, kIvSize16); + ValidateEcmFieldsOneKey(buf_string, 0, CryptoMode::kAesCtr, kIvSize16); } TEST_F(EcmSerializeEcmTest, SerializeEcmDoubleKey16x8ByteIvs) { @@ -612,14 +573,10 @@ TEST_F(EcmSerializeEcmTest, SerializeEcmDoubleKey16x8ByteIvs) { ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys)); std::string buf_string = ecm_gen.CallSerializeEcm(keys); - ValidateEcmFieldsTwoKeys(buf_string, 0, CryptoMode::kAesCtr, kIvSize8); - EXPECT_CALL(ecm_gen, generation()).WillRepeatedly(Return(1)); - buf_string = ecm_gen.CallSerializeEcm(keys); - - ValidateEcmFieldsTwoKeys(buf_string, 1, CryptoMode::kAesCtr, kIvSize8); + ValidateEcmFieldsTwoKeys(buf_string, 0, CryptoMode::kAesCtr, kIvSize8); } TEST_F(EcmSerializeEcmTest, SerializeEcmSingleKey16x8ByteIvs) { @@ -633,14 +590,35 @@ TEST_F(EcmSerializeEcmTest, SerializeEcmSingleKey16x8ByteIvs) { ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys)); std::string buf_string = ecm_gen.CallSerializeEcm(keys); - ValidateEcmFieldsOneKey(buf_string, 0, CryptoMode::kAesCtr, kIvSize8); - EXPECT_CALL(ecm_gen, generation()).WillRepeatedly(Return(1)); + buf_string = ecm_gen.CallSerializeEcm(keys); + ValidateEcmFieldsOneKey(buf_string, 0, CryptoMode::kAesCtr, kIvSize8); +} + +TEST_F(EcmSerializeEcmTest, SerializeEcmAgeRestriction) { + MockEcm ecm_gen; + EntitledKeyInfo key1 = valid3_iv_16_16_; + EntitledKeyInfo key2 = valid4_iv_16_16_; + uint8_t age_restriction = 18; + + ecm_gen.MockSetup(true, kTrackTypeSD, age_restriction, CryptoMode::kAesCtr, + 16); + + std::vector keys; + keys.push_back(&key1); + keys.push_back(&key2); + ASSERT_OK(ecm_gen.MockWrapEntitledKeys(kTrackTypeSD, keys)); + + EXPECT_CALL(ecm_gen, age_restriction()) + .WillRepeatedly(Return(age_restriction)); + std::string buf_string = ecm_gen.CallSerializeEcm(keys); + ValidateEcmFieldsTwoKeys(buf_string, age_restriction, CryptoMode::kAesCtr, + kIvSize16); buf_string = ecm_gen.CallSerializeEcm(keys); - - ValidateEcmFieldsOneKey(buf_string, 1, CryptoMode::kAesCtr, kIvSize8); + ValidateEcmFieldsTwoKeys(buf_string, age_restriction, CryptoMode::kAesCtr, + kIvSize16); } } // namespace cas diff --git a/media_cas_packager_sdk/internal/ecmg_client_handler.cc b/media_cas_packager_sdk/internal/ecmg_client_handler.cc index 5d10e06..7db3f59 100644 --- a/media_cas_packager_sdk/internal/ecmg_client_handler.cc +++ b/media_cas_packager_sdk/internal/ecmg_client_handler.cc @@ -19,18 +19,23 @@ #include "glog/logging.h" #include "absl/memory/memory.h" #include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" #include "common/crypto_util.h" +#include "common/random_util.h" #include "common/status.h" #include "example/constants.h" -#include "media_cas_packager_sdk/internal/ecm_generator.h" #include "media_cas_packager_sdk/internal/ecmg_constants.h" +#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h" #include "media_cas_packager_sdk/internal/mpeg2ts.h" #include "media_cas_packager_sdk/internal/simulcrypt_constants.h" #include "media_cas_packager_sdk/internal/simulcrypt_util.h" #include "media_cas_packager_sdk/internal/util.h" #include "media_cas_packager_sdk/public/wv_cas_ecm.h" +#include "media_cas_packager_sdk/public/wv_cas_key_fetcher.h" #include "media_cas_packager_sdk/public/wv_cas_types.h" +// CA System ID for Widevine. +static constexpr uint16_t kWidevineSystemId = 0x4AD4; // 'section_TSpkt_flag' defines the format of the ECM. // We only support MPEG-2 transport stream packet format for now. // We do NOT support MPEG-2 section format yet. @@ -40,66 +45,145 @@ static constexpr uint8_t kSectionTSpktFlag = 0x01; // suppported by an ECMG on a channel. // A value of 0 means that this maximum is not known. static constexpr uint16_t kMaxStream = 0; -// 'lead_CW' parameter defines the number of contro lwords required in -// advance to build an ECM. -// TODO(user): Support other values of 'lead_CW' in combination with -// other values for 'CW_per_msg'. -static constexpr uint8_t kLeadCw = 1; -// ' CW_per_msg' parameter defines the number of control words needed by the -// ECMG per control word provision message. -static constexpr uint8_t kCwPerMsg = 2; +// Size of Wrapped_Key_IV (IV for decrypting the wrapped key) in bytes. +static constexpr size_t kWrappedKeyIvSizeBytes = 16; +// Largest age restriction value allowed. +static constexpr size_t kMaxAllowedAgeRestriction = 99; +// Size of entitlement key id in bytes. +static constexpr size_t kEntitlementKeyIdSizeBytes = 16; +// Size of entitlement key value in bytes. +static constexpr size_t kEntitlementKeyValueSizeBytes = 32; namespace widevine { namespace cas { namespace { +Status ProcessPrivateParameters(const char* const request, uint16_t param_type, + uint16_t param_length, size_t* offset, + EcmgParameters* params) { + switch (param_type) { + case CRYPTO_MODE: + params->crypto_mode = std::string(request + *offset, param_length); + *offset += param_length; + break; + case TRACK_TYPES: + params->track_types.push_back( + std::string(request + *offset, param_length)); + *offset += param_length; + break; + case STREAM_TRACK_TYPE: + params->stream_track_type = std::string(request + *offset, param_length); + *offset += param_length; + break; + case CONTENT_ID: + params->content_id = std::string(request + *offset, param_length); + *offset += param_length; + break; + case CONTENT_PROVIDER: + params->content_provider = std::string(request + *offset, param_length); + *offset += param_length; + break; + case CONTENT_IV: + if (params->content_ivs.size() >= 2) { + return Status(error::INVALID_ARGUMENT, + "We only support up to 2 content ivs in the " + "CW_provision request"); + } + params->content_ivs.push_back( + std::string(request + *offset, param_length)); + *offset += param_length; + break; + case AGE_RESTRICTION: + if (param_length != AGE_RESTRICTION_SIZE) { + return Status(error::INVALID_ARGUMENT, + absl::StrCat("Invalid parameter length ", param_length, + " for parameter type ", param_type)); + } + params->age_restriction = request[*offset]; + *offset += param_length; + break; + case ENTITLEMENT_ID_KEY_COMBINATION: { + if (param_length != + kEntitlementKeyIdSizeBytes + kEntitlementKeyValueSizeBytes) { + return Status(error::INVALID_ARGUMENT, + absl::StrCat("Invalid parameter length ", param_length, + " for parameter type ", param_type)); + } + params->entitlement_comb.emplace_back(); + EntitlementIdKeyComb* combination = ¶ms->entitlement_comb.back(); + combination->key_id = + std::string(request + *offset, kEntitlementKeyIdSizeBytes); + *offset += kEntitlementKeyIdSizeBytes; + combination->key_value = + std::string(request + *offset, kEntitlementKeyValueSizeBytes); + *offset += kEntitlementKeyValueSizeBytes; + break; + } + default: + return Status( + error::UNIMPLEMENTED, + absl::StrCat("No implementation yet to process parameter of type ", + param_type)); + break; + } + return OkStatus(); +} + +Status HandleAccessCriteria(const char* const request, size_t request_length, + EcmgParameters* params) { + DCHECK(request); + DCHECK(params); + Status status; + uint16_t param_type; + uint16_t param_length; + size_t offset = 0; + while (offset < request_length) { + BigEndianToHost16(¶m_type, request + offset); + offset += PARAMETER_TYPE_SIZE; + BigEndianToHost16(¶m_length, request + offset); + offset += PARAMETER_LENGTH_SIZE; + status = ProcessPrivateParameters(request, param_type, param_length, + &offset, params); + if (!status.ok()) { + return status; + } + } + return OkStatus(); +} + // Local helper function that processes all the params in an ECMG request. Status HandleParameters(const char* const request, size_t request_length, EcmgParameters* params) { DCHECK(request); DCHECK(params); + Status status; uint16_t param_type; uint16_t param_length; // 'offset' is used to track where we are within |request|. size_t offset = 0; - // There could be CW_per_msg instances of CP_CW_combinations, - // so we need to track how many we have processed so far - // in order to know where to store the next CP_CW_combination. - int current_cp_cw_combination_index = 0; - while (offset != request_length) { + while (offset < request_length) { BigEndianToHost16(¶m_type, request + offset); offset += PARAMETER_TYPE_SIZE; BigEndianToHost16(¶m_length, request + offset); offset += PARAMETER_LENGTH_SIZE; switch (param_type) { - case ACCESS_CRITERIA: { - LOG(WARNING) << "Ignoring access_criteria parameter of " << param_length - << " bytes long"; - offset += param_length; - break; - } case CP_CW_COMBINATION: { - if (current_cp_cw_combination_index > 2) { - // We can have at most 3 CP_CW_Combinations. + if (params->cp_cw_combinations.size() >= 2) { return Status(error::INVALID_ARGUMENT, "We only support up to 2 control words in the " "CW_provision request"); } - EcmgCpCwCombination* combination = - ¶ms->cp_cw_combinations[current_cp_cw_combination_index++]; + params->cp_cw_combinations.emplace_back(); + EcmgCpCwCombination* combination = ¶ms->cp_cw_combinations.back(); BigEndianToHost16(&combination->cp, request + offset); offset += CP_SIZE; size_t cw_size = param_length - CP_SIZE; combination->cw = std::string(request + offset, cw_size); offset += cw_size; - // TODO(user): This is a temporary hack to let the ECM generator - // know how many keys to include in the ECM. - // CW_per_msg should have been set during channel set-up instead. - params->cw_per_msg = current_cp_cw_combination_index; break; } - case CP_DURATION: { + case CP_DURATION: if (param_length != CP_DURATION_SIZE) { return Status(error::INVALID_ARGUMENT, absl::StrCat("Invalid parameter length ", param_length, @@ -108,8 +192,7 @@ Status HandleParameters(const char* const request, size_t request_length, BigEndianToHost16(¶ms->cp_duration, request + offset); offset += param_length; break; - } - case CP_NUMBER: { + case CP_NUMBER: if (param_length != CP_NUMBER_SIZE) { return Status(error::INVALID_ARGUMENT, absl::StrCat("Invalid parameter length ", param_length, @@ -118,14 +201,12 @@ Status HandleParameters(const char* const request, size_t request_length, BigEndianToHost16(¶ms->cp_number, request + offset); offset += param_length; break; - } - case CW_ENCRYPTION: { + case CW_ENCRYPTION: LOG(WARNING) << "Ignoring CW_encryption parameter of " << param_length << " bytes long"; offset += param_length; break; - } - case ECM_CHANNEL_ID: { + case ECM_CHANNEL_ID: if (param_length != ECM_CHANNEL_ID_SIZE) { return Status(error::INVALID_ARGUMENT, absl::StrCat("Invalid parameter length ", param_length, @@ -134,8 +215,7 @@ Status HandleParameters(const char* const request, size_t request_length, BigEndianToHost16(¶ms->ecm_channel_id, request + offset); offset += param_length; break; - } - case ECM_ID: { + case ECM_ID: if (param_length != ECM_ID_SIZE) { return Status(error::INVALID_ARGUMENT, absl::StrCat("Invalid parameter length ", param_length, @@ -144,8 +224,7 @@ Status HandleParameters(const char* const request, size_t request_length, BigEndianToHost16(¶ms->ecm_id, request + offset); offset += param_length; break; - } - case ECM_STREAM_ID: { + case ECM_STREAM_ID: if (param_length != ECM_STREAM_ID_SIZE) { return Status(error::INVALID_ARGUMENT, absl::StrCat("Invalid parameter length ", param_length, @@ -154,8 +233,7 @@ Status HandleParameters(const char* const request, size_t request_length, BigEndianToHost16(¶ms->ecm_stream_id, request + offset); offset += param_length; break; - } - case NOMINAL_CP_DURATION: { + case NOMINAL_CP_DURATION: if (param_length != NOMINAL_CP_DURATION_SIZE) { return Status(error::INVALID_ARGUMENT, absl::StrCat("Invalid parameter length ", param_length, @@ -164,8 +242,7 @@ Status HandleParameters(const char* const request, size_t request_length, BigEndianToHost16(¶ms->nominal_cp_duration, request + offset); offset += param_length; break; - } - case SUPER_CAS_ID: { + case SUPER_CAS_ID: if (param_length != SUPER_CAS_ID_SIZE) { return Status(error::INVALID_ARGUMENT, absl::StrCat("Invalid parameter length ", param_length, @@ -174,20 +251,26 @@ Status HandleParameters(const char* const request, size_t request_length, BigEndianToHost32(¶ms->super_cas_id, request + offset); offset += param_length; break; - } - default: { - return Status( - error::UNIMPLEMENTED, - absl::StrCat("No implementation yet to process parameter of type ", - param_type)); + case ACCESS_CRITERIA: + status = HandleAccessCriteria(request + offset, param_length, params); + if (!status.ok()) { + return status; + } + offset += param_length; break; - } + default: + status = ProcessPrivateParameters(request, param_type, param_length, + &offset, params); + if (!status.ok()) { + return status; + } } } return OkStatus(); } -void BuildChannelError(uint16_t channel_id, uint16_t error_status, char* message, +void BuildChannelError(uint16_t channel_id, uint16_t error_status, + const std::string& error_info, char* message, size_t* message_length) { DCHECK(message); DCHECK(message_length); @@ -197,7 +280,11 @@ void BuildChannelError(uint16_t channel_id, uint16_t error_status, char* message message_length); simulcrypt_util::AddUint16Param(ERROR_STATUS, error_status, message, message_length); - // No setting Error_information parameter yet. + if (!error_info.empty()) { + simulcrypt_util::AddParam( + ERROR_INFORMATION, reinterpret_cast(error_info.c_str()), + error_info.size(), message, message_length); + } uint16_t total_param_length = *message_length - 5; Host16ToBigEndian(message + 3, &total_param_length); } @@ -229,9 +316,13 @@ void BuildChannelStatus(uint16_t channel_id, EcmgConfig* config, char* message, // max_comp_time here for now. simulcrypt_util::AddUint16Param(MIN_CP_DURATION, config->max_comp_time, message, message_length); - simulcrypt_util::AddUint8Param(LEAD_CW, kLeadCw, message, message_length); - simulcrypt_util::AddUint8Param(CW_PER_MESSAGE, kCwPerMsg, message, - message_length); + // LEAD_CW defines the number of control words required in advance to build an + // ECM. When CW_PER_MESSAGE is 2, LEAD_CW should be 1; when CW_PER_MESSAGE is + // 1, LEAD_CW should be 0. + simulcrypt_util::AddUint8Param(LEAD_CW, config->number_of_content_keys - 1, + message, message_length); + simulcrypt_util::AddUint8Param(CW_PER_MESSAGE, config->number_of_content_keys, + message, message_length); simulcrypt_util::AddUint16Param(MAX_COMP_TIME, config->max_comp_time, message, message_length); uint16_t total_param_length = *message_length - 5; @@ -239,7 +330,8 @@ void BuildChannelStatus(uint16_t channel_id, EcmgConfig* config, char* message, } void BuildStreamError(uint16_t channel_id, uint16_t stream_id, uint16_t error_status, - char* message, size_t* message_length) { + const std::string& error_info, char* message, + size_t* message_length) { DCHECK(message); DCHECK(message_length); simulcrypt_util::BuildMessageHeader( @@ -250,11 +342,31 @@ void BuildStreamError(uint16_t channel_id, uint16_t stream_id, uint16_t error_st message_length); simulcrypt_util::AddUint16Param(ERROR_STATUS, error_status, message, message_length); - // No setting Error_information parameter yet. + if (!error_info.empty()) { + simulcrypt_util::AddParam( + ERROR_INFORMATION, reinterpret_cast(error_info.c_str()), + error_info.size(), message, message_length); + } uint16_t total_param_length = *message_length - 5; Host16ToBigEndian(message + 3, &total_param_length); } +uint16_t StatusToDvbErrorCode(const Status& status) { + if (status.ok()) { + LOG(ERROR) << "Converting OK status to error code."; + return 0; + } + switch (status.error_code()) { + case error::INVALID_ARGUMENT: + return INVALID_VALUE_FOR_DVB_PARAMETER; + case error::NOT_FOUND: + return MISSING_MANDATORY_DVB_PARAMETER; + case error::INTERNAL: + default: + return UNKNOWN_ERROR; + } +} + void BuildStreamStatus(uint16_t channel_id, uint16_t stream_id, uint16_t ecm_id, uint8_t access_criteria_transfer_mode, char* message, size_t* message_length) { @@ -329,7 +441,8 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response, size_t offset = 0; memcpy(&protocol_version, request, PROTOCOL_VERSION_SIZE); if (protocol_version != ECMG_SCS_PROTOCOL_VERSION) { - // TODO(user): Should send an error response. + BuildChannelError(0, UNSUPPORTED_PROTOCOL_VERSION, "", response, + response_length); return; } offset += PROTOCOL_VERSION_SIZE; @@ -340,7 +453,23 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response, EcmgParameters params; Status status = HandleParameters(request + offset, request_length, ¶ms); if (!status.ok()) { - // TODO(user): Should send an error response. + LOG(ERROR) << status.ToString(); + switch (status.error_code()) { + case error::INVALID_ARGUMENT: + // TODO(user): Should use INCONSISTENT_LENGTH_FOR_DVB_PARAMETER in most + // cases. + BuildChannelError(params.ecm_channel_id, + INVALID_VALUE_FOR_DVB_PARAMETER, + status.error_message(), response, response_length); + break; + case error::UNIMPLEMENTED: + BuildChannelError(params.ecm_channel_id, UNKNOWN_PARAMETER_TYPE_VALUE, + status.error_message(), response, response_length); + break; + default: + BuildChannelError(params.ecm_channel_id, UNKNOWN_ERROR, + status.error_message(), response, response_length); + } return; } switch (request_type) { @@ -348,6 +477,10 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response, HandleChannelSetup(params, response, response_length); break; } + case ECMG_CHANNEL_TEST: { + HandleChannelTest(params, response, response_length); + break; + } case ECMG_CHANNEL_CLOSE: { HandleChannelClose(params, response, response_length); break; @@ -356,6 +489,10 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response, HandleStreamSetup(params, response, response_length); break; } + case ECMG_STREAM_TEST: { + HandleStreamTest(params, response, response_length); + break; + } case ECMG_STREAM_CLOSE_REQUEST: { HandleStreamCloseRequest(params, response, response_length); break; @@ -365,7 +502,8 @@ void EcmgClientHandler::HandleRequest(const char* const request, char* response, break; } default: { - // Unhandled or unknown request types. + BuildChannelError(params.ecm_channel_id, UNKNOWN_MESSAGE_TYPE_VALUE, "", + response, response_length); break; } } @@ -376,13 +514,19 @@ void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params, size_t* response_length) { DCHECK(response); DCHECK(response_length); - // TODO(user): Check SUPER_CAS_ID, if it is unexpected, return - // UNKNOWN_SUPER_CAS_ID_VALUE error. + // There is always one (and only one) channel per TCP connection. if (channel_id_set_) { - BuildChannelError(params.ecm_channel_id, INVAID_MESSAGE, response, + BuildChannelError(params.ecm_channel_id, INVALID_MESSAGE, "", response, response_length); return; } + // The super_cas_id is a 32-bit identifier formed by the concatenation of the + // CA_system_id (16 bit) and the CA_subsystem_id (16 bit). + if ((params.super_cas_id >> 16) != kWidevineSystemId) { + BuildChannelError(params.ecm_channel_id, UNKNOWN_SUPER_CAS_ID_VALUE, "", + response, response_length); + return; + } // TODO(user): To support multi-threading of serving concurrent clients, // there needs to be a set of channel_ids shared by multiple client handlers // threads. @@ -390,8 +534,29 @@ void EcmgClientHandler::HandleChannelSetup(const EcmgParameters& params, // yes, return an error. channel_id_ = params.ecm_channel_id; channel_id_set_ = true; - BuildChannelStatus(params.ecm_channel_id, ecmg_config_, response, - response_length); + + Status status = UpdatePrivateParameters(params, false); + if (!status.ok()) { + LOG(ERROR) << status.ToString(); + BuildChannelError(params.ecm_channel_id, StatusToDvbErrorCode(status), + status.error_message(), response, response_length); + return; + } + + BuildChannelStatus(channel_id_, ecmg_config_, response, response_length); +} + +void EcmgClientHandler::HandleChannelTest(const EcmgParameters& params, + char* response, + size_t* response_length) const { + DCHECK(response); + DCHECK(response_length); + if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { + BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "", + response, response_length); + return; + } + BuildChannelStatus(channel_id_, ecmg_config_, response, response_length); } void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params, @@ -399,13 +564,13 @@ void EcmgClientHandler::HandleChannelClose(const EcmgParameters& params, size_t* response_length) { DCHECK(response); DCHECK(response_length); - if (channel_id_ != params.ecm_channel_id) { - BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, - UNKNOWN_ECM_CHANNEL_ID_VALUE, response, response_length); + if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { + BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, "", + response, response_length); return; } channel_id_set_ = false; - streams_.clear(); + streams_info_.clear(); *response_length = 0; } @@ -414,46 +579,77 @@ void EcmgClientHandler::HandleStreamSetup(const EcmgParameters& params, size_t* response_length) { DCHECK(response); DCHECK(response_length); - if (channel_id_ != params.ecm_channel_id) { + if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, - UNKNOWN_ECM_CHANNEL_ID_VALUE, response, response_length); - return; - } - // TODO(user): To support multi-threading of serving concurrent clients, - // there needs to be a set of stream_ids shared by multiple client handlers - // threads. - // And here we need to check if the stream_id is already in the set, if - // yes, return an error. - if (streams_.count(params.ecm_stream_id) > 0) { - BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, - ECM_STREAM_ID_VALUE_ALREADY_IN_USE, response, + UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response, response_length); return; } - // TODO(user): What do I do with the ECM_id here, currently not doing - // anything with it? - streams_[params.ecm_stream_id] = params.ecm_id; + if (streams_info_.contains(params.ecm_stream_id)) { + BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, + ECM_STREAM_ID_VALUE_ALREADY_IN_USE, "", response, + response_length); + return; + } + + streams_info_[params.ecm_stream_id] = absl::make_unique(); + streams_info_[params.ecm_stream_id]->ecm_id = params.ecm_id; + + Status status = UpdatePrivateParameters(params, true); + if (!status.ok()) { + LOG(ERROR) << status.ToString(); + BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, + StatusToDvbErrorCode(status), status.error_message(), + response, response_length); + return; + } + BuildStreamStatus(params.ecm_channel_id, params.ecm_stream_id, params.ecm_id, ecmg_config_->access_criteria_transfer_mode, response, response_length); } +void EcmgClientHandler::HandleStreamTest(const EcmgParameters& params, + char* response, + size_t* response_length) const { + DCHECK(response); + DCHECK(response_length); + if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { + BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, + UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response, + response_length); + return; + } + if (!streams_info_.contains(params.ecm_stream_id)) { + BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, + UNKNOWN_ECM_STREAM_ID_VALUE, "", response, + response_length); + return; + } + BuildStreamStatus(params.ecm_channel_id, params.ecm_stream_id, + streams_info_.at(params.ecm_stream_id)->ecm_id, + ecmg_config_->access_criteria_transfer_mode, response, + response_length); +} + void EcmgClientHandler::HandleStreamCloseRequest(const EcmgParameters& params, char* response, size_t* response_length) { DCHECK(response); DCHECK(response_length); - if (channel_id_ != params.ecm_channel_id) { + if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, - UNKNOWN_ECM_CHANNEL_ID_VALUE, response, response_length); + UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response, + response_length); return; } - if (streams_.count(params.ecm_stream_id) == 0) { + if (!streams_info_.contains(params.ecm_stream_id)) { BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, - UNKNOWN_ECM_STREAM_ID_VALUE, response, response_length); + UNKNOWN_ECM_STREAM_ID_VALUE, "", response, + response_length); return; } - streams_.erase(params.ecm_stream_id); + streams_info_.erase(params.ecm_stream_id); BuildStreamCloseResponse(params.ecm_channel_id, params.ecm_stream_id, response, response_length); } @@ -463,120 +659,270 @@ void EcmgClientHandler::HandleCwProvision(const EcmgParameters& params, size_t* response_length) { DCHECK(response); DCHECK(response_length); - if (channel_id_ != params.ecm_channel_id) { - BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_CHANNEL_ID_VALUE, - response, response_length); + if (!channel_id_set_ || channel_id_ != params.ecm_channel_id) { + BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, + UNKNOWN_ECM_CHANNEL_ID_VALUE, "", response, + response_length); return; } - if (streams_.count(params.ecm_stream_id) == 0) { - BuildChannelError(params.ecm_channel_id, UNKNOWN_ECM_STREAM_ID_VALUE, - response, response_length); + if (!streams_info_.contains(params.ecm_stream_id)) { + BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, + UNKNOWN_ECM_STREAM_ID_VALUE, "", response, + response_length); return; } - if (params.cw_per_msg < kCwPerMsg) { - BuildChannelError(params.ecm_channel_id, - NOT_ENOUGH_CONTROL_WORDS_TO_COMPUTE_ECM, response, - response_length); + if (params.cp_cw_combinations.size() < ecmg_config_->number_of_content_keys) { + BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, + NOT_ENOUGH_CONTROL_WORDS_TO_COMPUTE_ECM, "", response, + response_length); return; } + if (params.cp_cw_combinations.size() > ecmg_config_->number_of_content_keys) { + LOG(WARNING) << "Too many control words have been received. Expecting " + << static_cast(ecmg_config_->number_of_content_keys) + << " but received " << params.cp_cw_combinations.size() << "."; + } + + // Update private parameters based on access_criteria if any. + Status status = UpdatePrivateParameters(params, true); + if (!status.ok()) { + LOG(ERROR) << status.ToString(); + BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, + StatusToDvbErrorCode(status), status.error_message(), + response, response_length); + return; + } + + // Request entitlement keys if they do not exist yet. + if (ecm_ == nullptr) { + status = CheckAndInitializeEcm(params); + if (!status.ok()) { + LOG(ERROR) << status.ToString(); + BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, + StatusToDvbErrorCode(status), status.error_message(), + response, response_length); + return; + } + } + + // Build Ecm datagram. uint8_t ecm_datagram[kTsPacketSize]; - BuildEcmDatagram(params, ecm_datagram); + status = BuildEcmDatagram(params, ecm_datagram); + if (!status.ok()) { + LOG(ERROR) << status.ToString(); + BuildStreamError(params.ecm_channel_id, params.ecm_stream_id, + StatusToDvbErrorCode(status), "", response, + response_length); + return; + } + + // Success. Send response message containing Ecm datagram. BuildEcmResponse(params.ecm_channel_id, params.ecm_stream_id, params.cp_number, ecm_datagram, response, response_length); } -void EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params, - uint8_t* ecm_datagram) { - DCHECK(ecm_datagram); - // TODO(user): Remove debug loop below. - for (int i = 0; i < 3; i++) { - std::cout << "CW: "; - for (int j = 0; j < params.cp_cw_combinations[i].cw.size(); j++) { - printf("'\\x%02x', ", - static_cast(params.cp_cw_combinations[i].cw[j])); +Status EcmgClientHandler::UpdatePrivateParameters(const EcmgParameters& params, + bool stream_specific) { + EcmgStreamInfo* stream_info = + stream_specific ? streams_info_[params.ecm_stream_id].get() : nullptr; + + if (params.age_restriction != 0xff) { + if (params.age_restriction > kMaxAllowedAgeRestriction) { + return {error::INVALID_ARGUMENT, "Age restriction too large."}; } - std::cout << std::endl; + age_restriction_ = params.age_restriction; } - // Step 1: Generate entitlement keys. - bool key_rotation_enabled = params.cw_per_msg > 1; - // Create an instance of Ecm in order to set the entitlement keys. - // TODO(user): The section of code below for constructing Ecm should - // be optimized. There should be a single instance of Ecm for each stream. - // Right now, this is hard to do because EcmGenerator contains the Ecm. - std::unique_ptr ecm = absl::make_unique(); - // TODO(user): Revisit this hardcoded ecm_init_params. + if (!params.crypto_mode.empty()) { + if (!StringToCryptoMode(params.crypto_mode, + stream_specific ? &stream_info->crypto_mode + : &ecmg_config_->crypto_mode)) { + return {error::INVALID_ARGUMENT, + absl::StrCat("Unknown crypto mode: ", params.crypto_mode, ".")}; + } + } + + if (!params.track_types.empty()) { + track_types_.assign(params.track_types.begin(), params.track_types.end()); + } + + if (!params.stream_track_type.empty()) { + if (stream_specific) { + stream_info->track_type = params.stream_track_type; + } else { + LOG(WARNING) << "Ignoring stream track type received in channel config."; + } + } + + if (!params.content_id.empty()) { + content_id_ = params.content_id; + } + + if (!params.content_provider.empty()) { + content_provider_ = params.content_provider; + } + + if (!params.content_ivs.empty()) { + if (params.content_ivs.size() < ecmg_config_->number_of_content_keys) { + return {error::INVALID_ARGUMENT, + absl::StrCat("Not enough content ivs, expecting ", + ecmg_config_->number_of_content_keys, ".")}; + } + if (params.content_ivs[0].size() != 8 && + params.content_ivs[0].size() != 16) { + return {error::INVALID_ARGUMENT, + "Wrong content iv size: must be 8 or 16."}; + } + for (size_t i = 1; i < ecmg_config_->number_of_content_keys; i++) { + if (params.content_ivs[0].size() != params.content_ivs[i].size()) { + return {error::INVALID_ARGUMENT, "Size of content ivs must match."}; + } + } + if (stream_specific) { + stream_info->content_ivs.assign(params.content_ivs.begin(), + params.content_ivs.end()); + } else { + content_ivs_.assign(params.content_ivs.begin(), params.content_ivs.end()); + } + } + + if (!params.entitlement_comb.empty()) { + entitlement_comb_.assign(params.entitlement_comb.begin(), + params.entitlement_comb.end()); + } + + return OkStatus(); +} + +Status EcmgClientHandler::CheckAndInitializeEcm(const EcmgParameters& params) { + DCHECK(ecm_ == nullptr); + EcmgStreamInfo* stream_info = streams_info_.at(params.ecm_stream_id).get(); + if (stream_info->content_ivs.empty() && content_ivs_.empty()) { + return {error::NOT_FOUND, "Content iv not specified."}; + } + if (track_types_.empty()) { + return {error::NOT_FOUND, "Track type not specified."}; + } + + bool key_rotation = ecmg_config_->number_of_content_keys > 1; EcmInitParameters ecm_init_params; + ecm_init_params.key_rotation_enabled = key_rotation; + ecm_init_params.age_restriction = age_restriction_; + ecm_init_params.crypto_mode = stream_info->crypto_mode == CryptoMode::kInvalid + ? ecmg_config_->crypto_mode + : stream_info->crypto_mode; ecm_init_params.content_iv_size = kIvSize8; - ecm_init_params.key_rotation_enabled = key_rotation_enabled; - // TODO(user): Allow crypto mode to be set to different value? Via flag? - ecm_init_params.crypto_mode = CryptoMode::kDvbCsa2; - // Only encrypt one video track. - // TODO(user): Support multiple tracks? How? - ecm_init_params.track_types.push_back(kDefaultTrackTypeSd); + if ((!stream_info->content_ivs.empty() && + stream_info->content_ivs[0].size() == 16) || + (!content_ivs_.empty() && content_ivs_[0].size() == 16)) { + ecm_init_params.content_iv_size = kIvSize16; + } + ecm_init_params.track_types.assign(track_types_.begin(), track_types_.end()); + + ecm_ = absl::make_unique(); + if (!entitlement_comb_.empty()) { + // Using injected entitlement keys. + if (entitlement_comb_.size() != + track_types_.size() * ecmg_config_->number_of_content_keys) { + return {error::NOT_FOUND, + "Number of injected entitlement keys must equal to number of " + "track types * number of content keys per ecm."}; + } + std::vector entitlements; + entitlements.reserve(entitlement_comb_.size()); + for (size_t i = 0; i < entitlement_comb_.size(); i++) { + entitlements.emplace_back(); + InjectedEntitlementKeyInfo* entitlement = &entitlements.back(); + entitlement->track_type = track_types_.at(key_rotation ? i / 2 : i); + entitlement->is_even_key = key_rotation ? i % 2 == 0 : true; + entitlement->key_id = entitlement_comb_[i].key_id; + entitlement->key_value = entitlement_comb_[i].key_value; + } + + return ecm_->Initialize(ecm_init_params, entitlements); + } + + // No injected enetitlement keys. Fetching entitlement keys from server. + if (content_id_.empty()) { + return {error::NOT_FOUND, "Content id not specified."}; + } + if (content_provider_.empty()) { + return {error::NOT_FOUND, "Content provider not specified."}; + } + std::string entitlement_request; std::string entitlement_response; - // 'content_id' and 'provider' are used in entitlement key request/response - // only, NOT needed for generating ECM. - // So for initial demo, we can just use hardcoded value because we are using - // hardcoded entitlement key anyway. - // TODO(user): When we want to retrieve entitlement key from License Server - // we need to figure out a way to provide real 'content_id' and 'provder' - // to this function here from the Simulcrypt API. + Status status = ecm_->Initialize(content_id_, content_provider_, + ecm_init_params, &entitlement_request); + if (!status.ok()) { + return status; + } + + if (key_fetcher_ == nullptr) { + if (ecmg_config_->use_fixed_fetcher) { + key_fetcher_ = absl::make_unique(); + } else { + key_fetcher_ = absl::make_unique(); + } + } + status = key_fetcher_->RequestEntitlementKey(entitlement_request, + &entitlement_response); + if (!status.ok()) { + return status; + } + return ecm_->ProcessCasEncryptionResponse(entitlement_response); +} + +Status EcmgClientHandler::BuildEcmDatagram(const EcmgParameters& params, + uint8_t* ecm_datagram) const { + DCHECK(ecm_datagram); + DCHECK(ecm_); + EcmgStreamInfo* stream_info = streams_info_.at(params.ecm_stream_id).get(); + + // Generate serialized ECM. + std::vector keys; + keys.reserve(ecmg_config_->number_of_content_keys); + for (size_t i = 0; i < ecmg_config_->number_of_content_keys; i++) { + DCHECK(params.cp_cw_combinations[i].cp == params.cp_number + i); + keys.emplace_back(); + keys[i].key_value = params.cp_cw_combinations[i].cw; + keys[i].key_id = crypto_util::DeriveKeyId(keys[i].key_value); + keys[i].content_iv = stream_info->content_ivs.empty() + ? content_ivs_[i] + : stream_info->content_ivs[i]; + if (!RandomBytes(kWrappedKeyIvSizeBytes, &keys[i].wrapped_key_iv)) { + return {error::INTERNAL, "Unable to generate random wrapped key iv."}; + } + } + Status status; - if (!(status = ecm->Initialize(kDefaultContentId, kDefaultProvider, - ecm_init_params, &entitlement_request)) - .ok()) { - // TODO(user): Should send an error response. - return; + std::string serialized_ecm; + if (ecmg_config_->number_of_content_keys > 1) { + status = ecm_->GenerateEcm(&keys[0], &keys[1], stream_info->track_type, + &serialized_ecm); + } else { + status = ecm_->GenerateSingleKeyEcm(&keys[0], stream_info->track_type, + &serialized_ecm); } - if (!(status = fixed_key_fetcher_.RequestEntitlementKey( - entitlement_request, &entitlement_response)) - .ok()) { - // TODO(user): Should send an error Channel_status. - return; - } - if (!(status = ecm->ProcessCasEncryptionResponse(entitlement_response)) - .ok()) { - // TODO(user): Should send an error Channel_status. - return; + if (!status.ok()) { + return status; } + LOG(INFO) << "ECM generated with size: " << serialized_ecm.size() << "."; - // Step 2: Generate serialized ECM. - EcmGenerator ecm_generator; - ecm_generator.set_ecm(std::move(ecm)); - EcmParameters ecm_param; - ecm_param.rotation_enabled = key_rotation_enabled; - for (int i = 0; i <= params.cw_per_msg; i++) { - ecm_param.key_params.emplace_back(); - ecm_param.key_params[i].key_data = params.cp_cw_combinations[i].cw; - ecm_param.key_params[i].key_id = widevine::crypto_util::DeriveKeyId( - ecm_param.key_params[i].key_data); - // TODO(user): How to derive this content_iv, maybe based on key? - std::string content_iv = "12345678"; - ecm_param.key_params[i].content_ivs.push_back(content_iv); - } - std::string serialized_ecm = ecm_generator.GenerateEcm(ecm_param); - std::cout << "serialized_ecm: " << serialized_ecm << std::endl; - for (int i = 0; i < serialized_ecm.size(); i++) { - printf("'\\x%x', ", static_cast(serialized_ecm.at(i))); - } - std::cout << std::endl; - LOG(INFO) << "ECM size: " << serialized_ecm.size(); - - // Step 3: Make a TS packet carrying the serialized ECM. - // TODO(user): Is it correct to set 'pid' using ECM_id? - // TODO(user): Where do I get the continuity_counter? - uint8_t continuity_counter = 0; + // Make a TS packet carrying the serialized ECM. + // According to the standard, it is the head-end's responsibility to fill + // the PID field in TS packet header. + uint16_t pid = 0; + uint8_t continuity_counter = params.cp_number & 0xff; WvCasEcm wv_cas_ecm; WvCasStatus cas_status = wv_cas_ecm.GenerateTsPacket( - serialized_ecm, params.ecm_id, + serialized_ecm, pid, params.cp_number % 2 == 0 ? kTsPacketTableId80 : kTsPacketTableId81, &continuity_counter, ecm_datagram); if (cas_status != OK) { - // TODO(user): Should send an error Channel_status. - return; + return {error::INTERNAL, "GenerateTsPacket failed."}; } + return OkStatus(); } } // namespace cas diff --git a/media_cas_packager_sdk/internal/ecmg_client_handler.h b/media_cas_packager_sdk/internal/ecmg_client_handler.h index 9095e6d..d967062 100644 --- a/media_cas_packager_sdk/internal/ecmg_client_handler.h +++ b/media_cas_packager_sdk/internal/ecmg_client_handler.h @@ -13,15 +13,18 @@ #include #include #include +#include #include #include #include #include #include +#include "absl/container/node_hash_map.h" #include "common/status.h" #include "media_cas_packager_sdk/internal/ecm.h" -#include "media_cas_packager_sdk/internal/fixed_key_fetcher.h" +#include "media_cas_packager_sdk/internal/key_fetcher.h" +#include "media_cas_packager_sdk/public/wv_cas_types.h" namespace widevine { namespace cas { @@ -33,27 +36,54 @@ struct EcmgConfig { uint16_t ecm_rep_period; uint16_t max_comp_time; uint8_t access_criteria_transfer_mode; + uint8_t number_of_content_keys; + CryptoMode crypto_mode; + bool use_fixed_fetcher = false; }; // A struct that represent a CP_CW_Combination. struct EcmgCpCwCombination { - uint16_t cp; // crypto period - std::string cw; // control word + uint16_t cp; // crypto period + std::string cw; // control word +}; + +// A struct that represent a Entitlement_Id_Key_Combination. +struct EntitlementIdKeyComb { + std::string key_id; // must be 16 bytes + std::string key_value; // must be 32 bytes }; // A struct that is used to hold all possible params for a ECMG request. struct EcmgParameters { - // CW_per_msg could 1, 2, or 3, - // so there can be up to 3 CP_CW_Combinations - EcmgCpCwCombination cp_cw_combinations[3]; + std::vector cp_cw_combinations; // Size is 1 or 2. uint16_t cp_duration; // crypto period duration uint16_t cp_number; // crypto period number - uint8_t cw_per_msg; uint16_t ecm_channel_id; uint16_t ecm_stream_id; uint16_t ecm_id; uint16_t nominal_cp_duration; uint32_t super_cas_id; + + // User defined paremeters below. + uint8_t age_restriction = 0xff; // Assume 0xff (255) is an invalid value. + std::string crypto_mode; + // All track types that need to be supported in the channel. + // Used to request entitlement keys. + std::vector track_types; + std::string stream_track_type; + std::string content_id; + std::string content_provider; + std::vector content_ivs; // 8 or 16 bytes, one for each key. + std::vector entitlement_comb; +}; + +struct EcmgStreamInfo { + uint16_t ecm_id; + std::string track_type; + // Will use |ecmg_config_|.crypto_mode if invalid. + CryptoMode crypto_mode = CryptoMode::kInvalid; + // 8 or 16 bytes, one for each key. Will use |content_ivs_| if empty. + std::vector content_ivs; }; // A class that handles one (and only one) ECMG client. @@ -74,25 +104,49 @@ class EcmgClientHandler { private: void HandleChannelSetup(const EcmgParameters& params, char* response, size_t* response_length); - // TODO(user): HandleChannelTest() + void HandleChannelTest(const EcmgParameters& params, char* response, + size_t* response_length) const; void HandleChannelClose(const EcmgParameters& params, char* response, size_t* response_length); void HandleStreamSetup(const EcmgParameters& params, char* response, size_t* response_length); - // TODO(user): HandleStreamTest() + void HandleStreamTest(const EcmgParameters& params, char* response, + size_t* response_length) const; void HandleStreamCloseRequest(const EcmgParameters& params, char* response, size_t* response_length); void HandleCwProvision(const EcmgParameters& params, char* response, size_t* response_length); - void BuildEcmDatagram(const EcmgParameters& params, uint8_t* ecm_datagram); + // Update private paprameters using |params|. |stream_specific| indicates if + // |params| is for a single stream or the whole channel. If |stream_specific| + // is true, |params| will only affect values of this stream. If it is false, + // |param| will affect all streams of the channel. + Status UpdatePrivateParameters(const EcmgParameters& params, + bool stream_specific); + // Check if all required parameters have been set. If so, initialize |ecm_| by + // fetching entitlement keys. + Status CheckAndInitializeEcm(const EcmgParameters& params); + // Gather all information needed to build a TS packet |ecm_datagram| + // conatianing an ECM. + Status BuildEcmDatagram(const EcmgParameters& params, + uint8_t* ecm_datagram) const; EcmgConfig* ecmg_config_; // Per spec, "There is always one (and only one) channel per TCP connection". bool channel_id_set_; uint16_t channel_id_; - // Map from ECM_stream_id to ECM_id. - std::unordered_map streams_; - FixedKeyFetcher fixed_key_fetcher_; + + // Channel specific information. + std::unique_ptr ecm_; // |ecm_| is shared within the channel. + uint8_t age_restriction_ = 0; + std::vector track_types_; + std::vector entitlement_comb_; + std::string content_id_; + std::string content_provider_; + std::vector content_ivs_; + + // Map from ECM_stream_id to EcmgStreamInfo. + absl::node_hash_map> streams_info_; + std::unique_ptr key_fetcher_; }; } // namespace cas diff --git a/media_cas_packager_sdk/internal/ecmg_client_handler_test.cc b/media_cas_packager_sdk/internal/ecmg_client_handler_test.cc index 786d574..535744a 100644 --- a/media_cas_packager_sdk/internal/ecmg_client_handler_test.cc +++ b/media_cas_packager_sdk/internal/ecmg_client_handler_test.cc @@ -13,13 +13,44 @@ #include "testing/gmock.h" #include "testing/gunit.h" #include "absl/memory/memory.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" #include "example/test_ecmg_messages.h" +#include "media_cas_packager_sdk/internal/ecmg_constants.h" +#include "media_cas_packager_sdk/internal/simulcrypt_util.h" +#include "media_cas_packager_sdk/internal/util.h" namespace widevine { namespace cas { namespace { -#define BUFFER_SIZE (1024) +using simulcrypt_util::AddParam; +using simulcrypt_util::AddUint16Param; +using simulcrypt_util::AddUint32Param; +using simulcrypt_util::AddUint8Param; +using simulcrypt_util::BuildMessageHeader; + +static constexpr size_t kBufferSize = 1024; +static constexpr size_t kSuperCasId = 0x4AD40000; +static constexpr size_t kChannelId = 1; +static constexpr size_t kStreamId = 1; +static constexpr size_t kEcmId = 2; +static constexpr size_t kNominalCpDuration = 0x64; +static constexpr size_t kCpNumber = 0; +static constexpr char kContentKeyEven[] = "0123456701234567"; +static constexpr char kContentKeyOdd[] = "abcdefghabcdefgh"; +static constexpr char kEntitlementKeyIdEven[] = "0123456701234567"; +static constexpr char kEntitlementKeyValueEven[] = + "01234567012345670123456701234567"; +static constexpr char kEntitlementKeyIdOdd[] = "abcdefghabcdefgh"; +static constexpr char kEntitlementKeyValueOdd[] = + "abcdefghabcdefghabcdefghabcdefgh"; +static constexpr size_t kAgeRestriction = 3; +static constexpr char kCryptoMode[] = "AesScte"; +static constexpr char kTrackTypesSD[] = "SD"; +static constexpr char kTrackTypesHD[] = "HD"; +static constexpr char kContentId[] = "CasTsFake"; +static constexpr char kContentProvider[] = "widevine_test"; class EcmgClientHandlerTest : public ::testing::Test { protected: @@ -29,59 +60,399 @@ class EcmgClientHandlerTest : public ::testing::Test { config_.ecm_rep_period = 100; config_.max_comp_time = 100; config_.access_criteria_transfer_mode = 1; - client_handler_ = absl::make_unique(&config_); + config_.number_of_content_keys = 2; + config_.use_fixed_fetcher = true; + handler_ = absl::make_unique(&config_); } protected: // Helper function for debugging the tests. - void PrintMessage(const std::string &description, const char *const message, + void PrintMessage(const std::string& description, const char* const message, size_t length) { - printf("%s ", description.c_str()); + absl::PrintF("%s ", description); fflush(stdout); for (size_t i = 0; i < length; i++) { - printf("'\\x%02x', ", static_cast(*(message + i))); + absl::PrintF("'\\x%02x', ", static_cast(*(message + i))); fflush(stdout); } - printf("\n"); + absl::PrintF("\n"); fflush(stdout); } + void SetupValidChannel() { + handler_->HandleRequest(kTestEcmgChannelSetup, response_, &response_len_); + EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_)); + } + + void SetupValidChannelStream() { + SetupValidChannel(); + handler_->HandleRequest(kTestEcmgStreamSetup, response_, &response_len_); + EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_)); + } + + void BuildChannelSetupRequest(uint16_t channel_id, uint32_t super_cas_id, + uint8_t age_restriction, + const std::string& crypto_mode, + const std::vector& track_types, + const std::string& content_id, + const std::string& content_provider, + const std::vector& entitlements, + char* message, size_t* message_length) { + EXPECT_TRUE(message != nullptr); + EXPECT_TRUE(message_length != nullptr); + BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CHANNEL_SETUP, message, + message_length); + AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length); + AddUint32Param(SUPER_CAS_ID, super_cas_id, message, message_length); + + if (age_restriction != 0) { + AddUint8Param(AGE_RESTRICTION, age_restriction, message, message_length); + } + if (!crypto_mode.empty()) { + AddParam(CRYPTO_MODE, reinterpret_cast(crypto_mode.c_str()), + crypto_mode.size(), message, message_length); + } + if (!track_types.empty()) { + for (const auto& track_type : track_types) { + AddParam(TRACK_TYPES, + reinterpret_cast(track_type.c_str()), + track_type.size(), message, message_length); + } + } + if (!content_id.empty()) { + AddParam(CONTENT_ID, reinterpret_cast(content_id.c_str()), + content_id.size(), message, message_length); + } + if (!content_provider.empty()) { + AddParam(CONTENT_PROVIDER, + reinterpret_cast(content_provider.c_str()), + content_provider.size(), message, message_length); + } + if (!entitlements.empty()) { + for (const auto& entitlement : entitlements) { + AddParam(ENTITLEMENT_ID_KEY_COMBINATION, + reinterpret_cast(entitlement.c_str()), + entitlement.size(), message, message_length); + } + } + + uint16_t total_param_length = *message_length - 5; + Host16ToBigEndian(message + 3, &total_param_length); + } + + void BuildStreamSetupRequest(uint16_t channel_id, uint16_t stream_id, + uint16_t ecm_id, uint16_t nominal_CP_duration, + const std::string& stream_track_type, + const std::vector& content_ivs, + char* message, size_t* message_length) { + EXPECT_TRUE(message != nullptr); + EXPECT_TRUE(message_length != nullptr); + BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_STREAM_SETUP, message, + message_length); + AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length); + AddUint16Param(ECM_STREAM_ID, stream_id, message, message_length); + AddUint16Param(ECM_ID, ecm_id, message, message_length); + AddUint16Param(NOMINAL_CP_DURATION, nominal_CP_duration, message, + message_length); + + if (!stream_track_type.empty()) { + AddParam(STREAM_TRACK_TYPE, + reinterpret_cast(stream_track_type.c_str()), + stream_track_type.size(), message, message_length); + } + if (!content_ivs.empty()) { + for (auto& content_iv : content_ivs) { + AddParam(CONTENT_IV, reinterpret_cast(content_iv.c_str()), + content_iv.size(), message, message_length); + } + } + + uint16_t total_param_length = *message_length - 5; + Host16ToBigEndian(message + 3, &total_param_length); + } + + void BuildCwProvisionRequest( + uint16_t channel_id, uint16_t stream_id, uint16_t cp_number, + const std::vector& cp_cw_combination, char* message, + size_t* message_length) { + EXPECT_TRUE(message != nullptr); + EXPECT_TRUE(message_length != nullptr); + BuildMessageHeader(ECMG_SCS_PROTOCOL_VERSION, ECMG_CW_PROVISION, message, + message_length); + AddUint16Param(ECM_CHANNEL_ID, channel_id, message, message_length); + AddUint16Param(ECM_STREAM_ID, stream_id, message, message_length); + AddUint16Param(CP_NUMBER, cp_number, message, message_length); + for (auto& cp_cw : cp_cw_combination) { + uint8_t combined[100]; + Host16ToBigEndian(combined, &cp_cw.cp); + memcpy(combined + 2, cp_cw.cw.c_str(), cp_cw.cw.length()); + AddParam(CP_CW_COMBINATION, combined, 2 + cp_cw.cw.length(), message, + message_length); + } + uint16_t total_param_length = *message_length - 5; + Host16ToBigEndian(message + 3, &total_param_length); + } + + void CheckChannelError(uint16_t expected_error_code, const char* const response, + size_t response_length) { + EXPECT_LE(sizeof(kTestChannelErrorResponse), response_length); + // Message version and message type + EXPECT_EQ(0, memcmp(kTestChannelErrorResponse, response, 3)); + // Parameter_type - error_status, and its length + EXPECT_EQ(0, memcmp(&kTestChannelErrorResponse[11], response + 11, 4)); + // Error status code. + EXPECT_EQ(expected_error_code >> 8, response[15]); + EXPECT_EQ(expected_error_code & 0xff, response[16]); + } + + void CheckStreamError(uint16_t expected_error_code, const char* const response, + size_t response_length) { + EXPECT_LE(sizeof(kTestStreamErrorResponse), response_length); + // Message version and message type + EXPECT_EQ(0, memcmp(kTestStreamErrorResponse, response, 3)); + // Parameter_type - error_status, and its length + EXPECT_EQ(0, memcmp(&kTestStreamErrorResponse[17], response + 17, 4)); + // Error status code. + EXPECT_EQ(expected_error_code >> 8, response[21]); + EXPECT_EQ(expected_error_code & 0xff, response[22]); + } + EcmgConfig config_; - std::unique_ptr client_handler_; + std::unique_ptr handler_; + char response_[kBufferSize]; + size_t response_len_ = 0; + char request_[kBufferSize]; + size_t request_len_ = 0; }; -// TODO(user): Add unit tests for error cases. +TEST_F(EcmgClientHandlerTest, SuccessSequenceWithAccessCriteria) { + handler_->HandleRequest(kTestEcmgChannelSetup, response_, &response_len_); + EXPECT_EQ(62, response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_)); -TEST_F(EcmgClientHandlerTest, SuccessSequence) { - char response[BUFFER_SIZE]; - size_t response_length; + handler_->HandleRequest(kTestEcmgChannelTest, response_, &response_len_); + EXPECT_EQ(62, response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_)); - client_handler_->HandleRequest(kTestEcmgChannelSetup, response, - &response_length); - EXPECT_EQ(62, response_length); - EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response, response_length)); + handler_->HandleRequest(kTestEcmgStreamSetup, response_, &response_len_); + EXPECT_EQ(28, response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_)); - client_handler_->HandleRequest(kTestEcmgStreamSetup, response, - &response_length); - EXPECT_EQ(28, response_length); - EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response, response_length)); + handler_->HandleRequest(kTestEcmgStreamTest, response_, &response_len_); + EXPECT_EQ(28, response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_)); - client_handler_->HandleRequest(kTestEcmgCwProvision, response, - &response_length); - EXPECT_EQ(215, response_length); - // Only comparing the bytes in front of the ECM_datagram, because - // random wrapping IV is generated each time causing the ECM_datagram - // to be non-deterministic. - EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response, 27)); + handler_->HandleRequest(kTestEcmgCwProvisionWithAccessCriteria, response_, + &response_len_); + EXPECT_EQ(215, response_len_); + // Only comparing the bytes in front of the ECM_datagram, including TS header, + // ECM header and front of first key data. This is because random wrapping IV + // is generated each time causing the ECM_datagram to be non-deterministic. + EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 72)); - client_handler_->HandleRequest(kTestEcmgStreamCloseRequest, response, - &response_length); - EXPECT_EQ(17, response_length); - EXPECT_EQ(0, memcmp(kTestEcmgStreamCloseResponse, response, response_length)); + // Cw provision request again. + handler_->HandleRequest(kTestEcmgCwProvisionWithAccessCriteria, response_, + &response_len_); + EXPECT_EQ(215, response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 72)); - client_handler_->HandleRequest(kTestEcmgChannelClose, response, - &response_length); - EXPECT_EQ(0, response_length); + handler_->HandleRequest(kTestEcmgStreamClose, response_, &response_len_); + EXPECT_EQ(17, response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgStreamCloseResponse, response_, response_len_)); + + handler_->HandleRequest(kTestEcmgChannelClose, response_, &response_len_); + EXPECT_EQ(0, response_len_); +} + +TEST_F(EcmgClientHandlerTest, SuccessSequenceWithPrivateParameters) { + handler_->HandleRequest(kTestEcmgChannelSetupWithPrivateParameters, response_, + &response_len_); + EXPECT_EQ(62, response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_)); + + handler_->HandleRequest(kTestEcmgChannelTest, response_, &response_len_); + EXPECT_EQ(62, response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_)); + + handler_->HandleRequest(kTestEcmgStreamSetupWithPrivateParameters, response_, + &response_len_); + EXPECT_EQ(28, response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_)); + + handler_->HandleRequest(kTestEcmgStreamTest, response_, &response_len_); + EXPECT_EQ(28, response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_)); + + handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_); + EXPECT_EQ(215, response_len_); + // Only comparing the bytes in front of the ECM_datagram, including TS header, + // ECM header and front of first key data. This is because random wrapping IV + // is generated each time causing the ECM_datagram to be non-deterministic. + EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 72)); + + // Cw provision request again. + handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_); + EXPECT_EQ(215, response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 72)); + + handler_->HandleRequest(kTestEcmgStreamClose, response_, &response_len_); + EXPECT_EQ(17, response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgStreamCloseResponse, response_, response_len_)); + + handler_->HandleRequest(kTestEcmgChannelClose, response_, &response_len_); + EXPECT_EQ(0, response_len_); +} + +TEST_F(EcmgClientHandlerTest, SuccessSequenceGeneratedRequests) { + BuildChannelSetupRequest(kChannelId, kSuperCasId, kAgeRestriction, + kCryptoMode, {kTrackTypesHD, kTrackTypesSD}, + kContentId, kContentProvider, /*entitlements*/ {}, + request_, &request_len_); + handler_->HandleRequest(request_, response_, &response_len_); + EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_)); + + BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration, + kTrackTypesSD, {kContentKeyEven, kContentKeyEven}, + request_, &request_len_); + handler_->HandleRequest(request_, response_, &response_len_); + EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_)); + + const std::vector cp_cw_combination = { + {kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}}; + BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination, + request_, &request_len_); + handler_->HandleRequest(request_, response_, &response_len_); + EXPECT_EQ(sizeof(kTestEcmgEcmResponse), response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 27)); +} + +TEST_F(EcmgClientHandlerTest, SuccessSequenceInjectedEntitlements) { + BuildChannelSetupRequest( + kChannelId, kSuperCasId, kAgeRestriction, kCryptoMode, + {kTrackTypesHD, kTrackTypesSD}, + /*ContentId*/ "", /*ContentProvider*/ "", + {absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven), + absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd), + absl::StrCat(kEntitlementKeyIdEven, kEntitlementKeyValueEven), + absl::StrCat(kEntitlementKeyIdOdd, kEntitlementKeyValueOdd)}, + request_, &request_len_); + handler_->HandleRequest(request_, response_, &response_len_); + EXPECT_EQ(sizeof(kTestEcmgChannelStatus), response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgChannelStatus, response_, response_len_)); + + BuildStreamSetupRequest(kChannelId, kStreamId, kEcmId, kNominalCpDuration, + kTrackTypesSD, {kContentKeyEven, kContentKeyEven}, + request_, &request_len_); + handler_->HandleRequest(request_, response_, &response_len_); + EXPECT_EQ(sizeof(kTestEcmgStreamStatus), response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgStreamStatus, response_, response_len_)); + + const std::vector cp_cw_combination = { + {kCpNumber, kContentKeyEven}, {kCpNumber + 1, kContentKeyOdd}}; + BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination, + request_, &request_len_); + handler_->HandleRequest(request_, response_, &response_len_); + EXPECT_EQ(sizeof(kTestEcmgEcmResponse), response_len_); + EXPECT_EQ(0, memcmp(kTestEcmgEcmResponse, response_, 27)); +} + +TEST_F(EcmgClientHandlerTest, WrongSequenceInvalidChannel) { + // ChannelTest without a valid channel (error). + handler_->HandleRequest(kTestEcmgChannelTest, response_, &response_len_); + CheckChannelError(UNKNOWN_ECM_CHANNEL_ID_VALUE, response_, response_len_); + + // ChannelClose without a valid channel (error). + handler_->HandleRequest(kTestEcmgChannelClose, response_, &response_len_); + CheckChannelError(UNKNOWN_ECM_CHANNEL_ID_VALUE, response_, response_len_); + + // StreamSetup without a valid channel (error). + handler_->HandleRequest(kTestEcmgStreamSetup, response_, &response_len_); + CheckStreamError(UNKNOWN_ECM_CHANNEL_ID_VALUE, response_, response_len_); + + // StreamClose without a valid channel (error). + handler_->HandleRequest(kTestEcmgStreamClose, response_, &response_len_); + CheckStreamError(UNKNOWN_ECM_CHANNEL_ID_VALUE, response_, response_len_); + + // StreamTest without a valid channel (error). + handler_->HandleRequest(kTestEcmgStreamTest, response_, &response_len_); + CheckStreamError(UNKNOWN_ECM_CHANNEL_ID_VALUE, response_, response_len_); + + // Cw provision without a valid channel (error). + handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_); + CheckStreamError(UNKNOWN_ECM_CHANNEL_ID_VALUE, response_, response_len_); +} + +TEST_F(EcmgClientHandlerTest, WrongSequenceMultipleChannel) { + SetupValidChannel(); + + // Setup channels muiltiple times (error). + handler_->HandleRequest(kTestEcmgChannelSetup, response_, &response_len_); + CheckChannelError(INVALID_MESSAGE, response_, response_len_); +} + +TEST_F(EcmgClientHandlerTest, WrongSequenceInvalidStream) { + SetupValidChannel(); + + // Cw provision without a valid stream (error). + handler_->HandleRequest(kTestEcmgCwProvision, response_, &response_len_); + CheckStreamError(UNKNOWN_ECM_STREAM_ID_VALUE, response_, response_len_); + + // StreamClose without a valid stream (error). + handler_->HandleRequest(kTestEcmgStreamClose, response_, &response_len_); + CheckStreamError(UNKNOWN_ECM_STREAM_ID_VALUE, response_, response_len_); + + // StreamTest without a valid channel (error). + handler_->HandleRequest(kTestEcmgStreamTest, response_, &response_len_); + CheckStreamError(UNKNOWN_ECM_STREAM_ID_VALUE, response_, response_len_); +} + +TEST_F(EcmgClientHandlerTest, WrongSequenceMultipleSameIdStream) { + SetupValidChannelStream(); + // Setup streams with the same stream_id muiltiple times (error). + handler_->HandleRequest(kTestEcmgStreamSetup, response_, &response_len_); + CheckStreamError(ECM_STREAM_ID_VALUE_ALREADY_IN_USE, response_, + response_len_); +} + +TEST_F(EcmgClientHandlerTest, WrongParameterInsufficientKey) { + SetupValidChannelStream(); + // Cw provision with only one key while expecting two (error). + handler_->HandleRequest(kTestEcmgCwProvisionSingleKey, response_, + &response_len_); + CheckStreamError(NOT_ENOUGH_CONTROL_WORDS_TO_COMPUTE_ECM, response_, + response_len_); + + // Cw provision with no key while expecting two (error). + std::vector cp_cw_combination = {}; + BuildCwProvisionRequest(kChannelId, kStreamId, kCpNumber, cp_cw_combination, + request_, &request_len_); + handler_->HandleRequest(request_, response_, &response_len_); + CheckStreamError(NOT_ENOUGH_CONTROL_WORDS_TO_COMPUTE_ECM, response_, + response_len_); +} + +TEST_F(EcmgClientHandlerTest, WrongParameterSuperCasId) { + // Setup a channel with an unexpected super cas id (expecting kSuperCasId). + BuildChannelSetupRequest(kChannelId, 0, kAgeRestriction, kCryptoMode, + {kTrackTypesHD, kTrackTypesSD}, kContentId, + kContentProvider, /*entitlements*/ {}, request_, + &request_len_); + handler_->HandleRequest(request_, response_, &response_len_); + CheckChannelError(UNKNOWN_SUPER_CAS_ID_VALUE, response_, response_len_); +} + +TEST_F(EcmgClientHandlerTest, WrongParameterChannelId) { + SetupValidChannel(); + // Setup a stream with an unexpected channel id (expecting kChannelId). + BuildStreamSetupRequest(0, kStreamId, kEcmId, kNominalCpDuration, + kTrackTypesSD, {kContentKeyEven, kContentKeyEven}, + request_, &request_len_); + handler_->HandleRequest(request_, response_, &response_len_); + CheckStreamError(UNKNOWN_ECM_CHANNEL_ID_VALUE, response_, response_len_); } } // namespace diff --git a/media_cas_packager_sdk/internal/ecmg_constants.h b/media_cas_packager_sdk/internal/ecmg_constants.h index d44fd40..59dce27 100644 --- a/media_cas_packager_sdk/internal/ecmg_constants.h +++ b/media_cas_packager_sdk/internal/ecmg_constants.h @@ -60,8 +60,18 @@ #define ERROR_STATUS (0x7000) #define ERROR_INFORMATION (0x7001) +// User defined ECMG parameter type values - 0x8000 to 0xFFFF. +#define AGE_RESTRICTION (0x8000) +#define CRYPTO_MODE (0x8001) +#define CONTENT_ID (0x8002) +#define CONTENT_PROVIDER (0x8003) +#define TRACK_TYPES (0x8004) +#define STREAM_TRACK_TYPE (0x8005) +#define CONTENT_IV (0x8006) +#define ENTITLEMENT_ID_KEY_COMBINATION (0x8007) + // ECMG protocol error values. -#define INVAID_MESSAGE (0x0001) +#define INVALID_MESSAGE (0x0001) #define UNSUPPORTED_PROTOCOL_VERSION (0x0002) #define UNKNOWN_MESSAGE_TYPE_VALUE (0x0003) #define MESSAGE_TOO_LONG (0x0004) @@ -94,5 +104,6 @@ #define CP_NUMBER_SIZE (2) #define CP_DURATION_SIZE (2) #define CP_SIZE (2) +#define AGE_RESTRICTION_SIZE (1) #endif // MEDIA_CAS_PACKAGER_SDK_INTERNAL_ECMG_CONSTANTS_H_ diff --git a/media_cas_packager_sdk/internal/emmg.cc b/media_cas_packager_sdk/internal/emmg.cc index 5e7e0a3..6c15478 100644 --- a/media_cas_packager_sdk/internal/emmg.cc +++ b/media_cas_packager_sdk/internal/emmg.cc @@ -9,12 +9,14 @@ #include "media_cas_packager_sdk/internal/emmg.h" #include + #include #include #include #include #include "glog/logging.h" +#include "absl/strings/str_format.h" #include "example/test_emmg_messages.h" #include "media_cas_packager_sdk/internal/emmg_constants.h" #include "media_cas_packager_sdk/internal/simulcrypt_constants.h" @@ -28,7 +30,7 @@ namespace { void Print(const char* const msg, size_t msg_size) { for (size_t i = 0; i < msg_size; i++) { - printf("'\\x%02x', ", static_cast(*(msg + i))); + absl::PrintF("'\\x%02x', ", static_cast(*(msg + i))); } std::cout << std::endl; } diff --git a/media_cas_packager_sdk/internal/emmg_test.cc b/media_cas_packager_sdk/internal/emmg_test.cc index 2b38773..c51ef87 100644 --- a/media_cas_packager_sdk/internal/emmg_test.cc +++ b/media_cas_packager_sdk/internal/emmg_test.cc @@ -10,6 +10,7 @@ #include "testing/gunit.h" #include "absl/memory/memory.h" +#include "absl/strings/str_format.h" #include "example/test_emmg_messages.h" namespace widevine { @@ -44,13 +45,13 @@ class EmmgTest : public ::testing::Test { // Helper function for debugging the tests. void PrintMessage(const std::string& description, const char* const message, size_t length) { - printf("%s ", description.c_str()); + absl::PrintF("%s ", description); fflush(stdout); for (size_t i = 0; i < length; i++) { - printf("'\\x%02x', ", static_cast(*(message + i))); + absl::PrintF("'\\x%02x', ", static_cast(*(message + i))); fflush(stdout); } - printf("\n"); + absl::PrintF("\n"); fflush(stdout); } diff --git a/media_cas_packager_sdk/internal/fixed_key_fetcher.cc b/media_cas_packager_sdk/internal/fixed_key_fetcher.cc index ba963c4..bdc7641 100644 --- a/media_cas_packager_sdk/internal/fixed_key_fetcher.cc +++ b/media_cas_packager_sdk/internal/fixed_key_fetcher.cc @@ -14,13 +14,14 @@ namespace widevine { namespace cas { -Status FixedKeyFetcher::RequestEntitlementKey(const std::string& request_string, - std::string* signed_response_string) { +Status FixedKeyFetcher::RequestEntitlementKey( + const std::string& request_string, + std::string* signed_response_string) const { CasEncryptionRequest request; request.ParseFromString(request_string); CasEncryptionResponse response; - response.set_status(CasEncryptionResponse_Status_OK); + response.set_status(CasEncryptionResponse::OK); response.set_content_id(request.content_id()); for (const auto& track_type : request.track_types()) { if (request.key_rotation()) { @@ -29,19 +30,19 @@ Status FixedKeyFetcher::RequestEntitlementKey(const std::string& request_string, key->set_key_id(even_entitlement_key_id_); key->set_key(even_entitlement_key_); key->set_track_type(track_type); - key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_EVEN); + key->set_key_slot(CasEncryptionResponse::KeyInfo::EVEN); // Add the Odd key. key = response.add_entitlement_keys(); key->set_key_id(odd_entitlement_key_id_); key->set_key(odd_entitlement_key_); key->set_track_type(track_type); - key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_ODD); + key->set_key_slot(CasEncryptionResponse::KeyInfo::ODD); } else { auto key = response.add_entitlement_keys(); key->set_key_id(even_entitlement_key_id_); key->set_key(even_entitlement_key_); key->set_track_type(track_type); - key->set_key_slot(CasEncryptionResponse_KeyInfo_KeySlot_SINGLE); + key->set_key_slot(CasEncryptionResponse::KeyInfo::SINGLE); } } std::string response_string; diff --git a/media_cas_packager_sdk/internal/fixed_key_fetcher.h b/media_cas_packager_sdk/internal/fixed_key_fetcher.h index 2d8120e..32fd071 100644 --- a/media_cas_packager_sdk/internal/fixed_key_fetcher.h +++ b/media_cas_packager_sdk/internal/fixed_key_fetcher.h @@ -26,7 +26,7 @@ class FixedKeyFetcher : public KeyFetcher { even_entitlement_key_("fakefakefakefakefakefakefake1..."), odd_entitlement_key_id_("fake_key_id2...."), odd_entitlement_key_("fakefakefakefakefakefakefake2...") {} - // Explictly provide the key_id and entitlement keys rather than using the + // Explicitly provide the key_id and entitlement keys rather than using the // hardcoded default. FixedKeyFetcher(const std::string& even_entitlement_key_id, const std::string& even_entitlement_key, @@ -49,8 +49,9 @@ class FixedKeyFetcher : public KeyFetcher { // |signed_response_string| a serialized SignedCasEncryptionResponse // message. It should be passed into // WvCasEcm::ProcessCasEncryptionResponse(). - Status RequestEntitlementKey(const std::string& request_string, - std::string* signed_response_string) override; + Status RequestEntitlementKey( + const std::string& request_string, + std::string* signed_response_string) const override; private: std::string even_entitlement_key_id_; diff --git a/media_cas_packager_sdk/internal/key_fetcher.h b/media_cas_packager_sdk/internal/key_fetcher.h index c2f0224..a387471 100644 --- a/media_cas_packager_sdk/internal/key_fetcher.h +++ b/media_cas_packager_sdk/internal/key_fetcher.h @@ -33,8 +33,9 @@ class KeyFetcher { // |signed_response_string| a serialized SignedCasEncryptionResponse // message. It should be passed into // WvCasEcm::ProcessCasEncryptionResponse(). - virtual Status RequestEntitlementKey(const std::string& request_string, - std::string* signed_response_string) = 0; + virtual Status RequestEntitlementKey( + const std::string& request_string, + std::string* signed_response_string) const = 0; }; } // namespace cas diff --git a/media_cas_packager_sdk/internal/simulcrypt_util.cc b/media_cas_packager_sdk/internal/simulcrypt_util.cc index 8abb16b..5263d6d 100644 --- a/media_cas_packager_sdk/internal/simulcrypt_util.cc +++ b/media_cas_packager_sdk/internal/simulcrypt_util.cc @@ -75,7 +75,7 @@ void AddUint8Param(uint16_t param_type, uint8_t param_value, char* message, *message_length += param_length; } -void AddParam(uint16_t param_type, uint8_t* param_value, uint16_t param_length, +void AddParam(uint16_t param_type, const uint8_t* param_value, uint16_t param_length, char* message, size_t* message_length) { DCHECK(param_value); DCHECK(message); diff --git a/media_cas_packager_sdk/internal/simulcrypt_util.h b/media_cas_packager_sdk/internal/simulcrypt_util.h index acd1f1d..649723f 100644 --- a/media_cas_packager_sdk/internal/simulcrypt_util.h +++ b/media_cas_packager_sdk/internal/simulcrypt_util.h @@ -49,7 +49,7 @@ void AddUint8Param(uint16_t param_type, uint8_t param_value, char* message, size_t* message_length); // Add a param that is |param_length| bytes long. -void AddParam(uint16_t param_type, uint8_t* param_value, uint16_t param_length, +void AddParam(uint16_t param_type, const uint8_t* param_value, uint16_t param_length, char* message, size_t* message_length); } // namespace simulcrypt_util diff --git a/media_cas_packager_sdk/internal/util.cc b/media_cas_packager_sdk/internal/util.cc index c891f55..c844fb1 100644 --- a/media_cas_packager_sdk/internal/util.cc +++ b/media_cas_packager_sdk/internal/util.cc @@ -64,8 +64,8 @@ void Host32ToBigEndian(void* destination, const uint32_t* source) { memcpy(destination, &big_endian_number, 4); } -Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid, uint8_t table_id, - ContinuityCounter* cc, uint8_t* buffer, +Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid, + uint8_t table_id, ContinuityCounter* cc, uint8_t* buffer, ssize_t* bytes_modified) { DCHECK(cc); DCHECK(buffer); diff --git a/media_cas_packager_sdk/internal/util.h b/media_cas_packager_sdk/internal/util.h index fb9472d..0e7c883 100644 --- a/media_cas_packager_sdk/internal/util.h +++ b/media_cas_packager_sdk/internal/util.h @@ -10,6 +10,7 @@ #define MEDIA_CAS_PACKAGER_SDK_INTERNAL_UTIL_H_ #include + #include #include @@ -41,7 +42,7 @@ void Host32ToBigEndian(void* destination, const uint32_t* source); // - |pid| is the PID used for ECMs in the TS header. // - |table_id| is the table ID byte put in the section header, it should be // either 0x80 or 0x81. Changing table ID from 0x80 or 0x81 or -// 0x81 to 0x80 is used to singal to the client that the key contained +// 0x81 to 0x80 is used to signal to the client that the key contained // in the ECM has changed. In other words, if you are building an ECM // with a new key that was not in any previous ECM, you should flip the // table ID so the client knows this is an important ECM it should process. @@ -53,8 +54,8 @@ void Host32ToBigEndian(void* destination, const uint32_t* source); // the |buffer| and is used as an offset. // |bytes_modified| will be incremented by 188 if insertion of ECM into // |buffer| is successful. -Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid, uint8_t table_id, - ContinuityCounter* cc, uint8_t* buffer, +Status InsertEcmAsTsPacket(const std::string& ecm, ProgramId pid, + uint8_t table_id, ContinuityCounter* cc, uint8_t* buffer, ssize_t* bytes_modified); } // namespace cas diff --git a/media_cas_packager_sdk/public/BUILD b/media_cas_packager_sdk/public/BUILD index 6a08d10..58b7857 100644 --- a/media_cas_packager_sdk/public/BUILD +++ b/media_cas_packager_sdk/public/BUILD @@ -46,10 +46,9 @@ cc_library( "@abseil_repo//absl/base:core_headers", "//common:status", "//media_cas_packager_sdk/internal:ecm", - "//media_cas_packager_sdk/internal:ecm_generator", "//media_cas_packager_sdk/internal:key_fetcher", - "//protos/public:media_cas_encryption_proto", - "//protos/public:media_cas_proto", + "//protos/public:media_cas_cc_proto", + "//protos/public:media_cas_encryption_cc_proto", ], ) @@ -65,8 +64,11 @@ cc_library( "@abseil_repo//absl/strings", "//common:status", "//common:string_util", - "//protos/public:media_cas_proto", + "//protos/public:media_cas_cc_proto", ], + # Make sure libmedia_cas_packager_sdk links in symbols defined in this + # target. + alwayslink = 1, ) cc_test( @@ -77,7 +79,7 @@ cc_test( ":wv_cas_ca_descriptor", ":wv_cas_types", "//testing:gunit_main", - "//protos/public:media_cas_proto", + "//protos/public:media_cas_cc_proto", ], ) @@ -96,11 +98,13 @@ cc_library( "//common:status", "//example:constants", "//media_cas_packager_sdk/internal:ecm", - "//media_cas_packager_sdk/internal:ecm_generator", "//media_cas_packager_sdk/internal:fixed_key_fetcher", "//media_cas_packager_sdk/internal:mpeg2ts", "//media_cas_packager_sdk/internal:util", ], + # Make sure libmedia_cas_packager_sdk links in symbols defined in this + # target. + alwayslink = 1, ) cc_test( @@ -126,6 +130,7 @@ cc_library( ], deps = [ "//base", + "//external:protobuf", "@abseil_repo//absl/base:core_headers", "@abseil_repo//absl/strings", "@curl_repo//:curl", @@ -133,7 +138,7 @@ cc_library( "//common:signature_util", "//common:status", "//media_cas_packager_sdk/internal:key_fetcher", - "//protos/public:media_cas_encryption_proto", + "//protos/public:media_cas_encryption_cc_proto", ], ) @@ -149,7 +154,7 @@ cc_test( "//external:protobuf", "//testing:gunit_main", "@abseil_repo//absl/strings", - "//protos/public:media_cas_encryption_proto", + "//protos/public:media_cas_encryption_cc_proto", ], ) @@ -160,8 +165,12 @@ cc_library( copts = PUBLIC_COPTS, deps = [ "//base", - "//protos/public:media_cas_encryption_proto", + "//external:protobuf", + "//protos/public:media_cas_encryption_cc_proto", ], + # Make sure libmedia_cas_packager_sdk links in symbols defined in this + # target. + alwayslink = 1, ) cc_test( @@ -178,6 +187,7 @@ cc_binary( name = "wv_ecmg", srcs = ["wv_ecmg.cc"], deps = [ + ":wv_cas_types", "//base", "@abseil_repo//absl/base:core_headers", "@abseil_repo//absl/strings", diff --git a/media_cas_packager_sdk/public/wv_cas_ca_descriptor.cc b/media_cas_packager_sdk/public/wv_cas_ca_descriptor.cc index 9557715..6a60cc7 100644 --- a/media_cas_packager_sdk/public/wv_cas_ca_descriptor.cc +++ b/media_cas_packager_sdk/public/wv_cas_ca_descriptor.cc @@ -45,8 +45,8 @@ static constexpr uint32_t kCaDescriptorTag = 9; // https://en.wikipedia.org/wiki/Conditional_access static constexpr uint32_t kWidevineCaSystemId = 0x4AD4; -// Value for CA descriptor reserved field. -static constexpr uint32_t kUnusedZero = 0; +// Value for CA descriptor reserved field should be set to 1. +static constexpr uint32_t kReservedBit = 0x0007; // The range of valid PIDs, from section 2.4.3.3, and table 2-3. static constexpr uint32_t kMinValidPID = 0x0010; @@ -83,7 +83,7 @@ WvCasStatus WvCasCaDescriptor::GenerateCaDescriptor( // including private bytes. std::bitset length(descriptor_length); std::bitset ca_system_id(kWidevineCaSystemId); - std::bitset reserved(kUnusedZero); + std::bitset reserved(kReservedBit); std::bitset pid(ca_pid); // Converts bitset to a std::string where each char represents a bit. @@ -110,8 +110,8 @@ size_t WvCasCaDescriptor::CaDescriptorBaseSize() const { return kCaDescriptorBaseSize; } -std::string WvCasCaDescriptor::GeneratePrivateData(const std::string& provider, - const std::string& content_id) const { +std::string WvCasCaDescriptor::GeneratePrivateData( + const std::string& provider, const std::string& content_id) const { CaDescriptorPrivateData private_data; private_data.set_provider(provider); private_data.set_content_id(content_id); diff --git a/media_cas_packager_sdk/public/wv_cas_ca_descriptor.h b/media_cas_packager_sdk/public/wv_cas_ca_descriptor.h index a26e8cf..ad62229 100644 --- a/media_cas_packager_sdk/public/wv_cas_ca_descriptor.h +++ b/media_cas_packager_sdk/public/wv_cas_ca_descriptor.h @@ -53,10 +53,9 @@ class WvCasCaDescriptor { // section (for an EMM stream) or into a TS Program Map Table section (for an // ECM stream). The descriptor will be 6 bytes plus any bytes added as // (user-defined) private data. - virtual WvCasStatus GenerateCaDescriptor(uint16_t ca_pid, - const std::string& provider, - const std::string& content_id, - std::string* serialized_ca_desc) const; + virtual WvCasStatus GenerateCaDescriptor( + uint16_t ca_pid, const std::string& provider, const std::string& content_id, + std::string* serialized_ca_desc) const; // Return the base size (before private data is added) of the CA // descriptor. The user can call this to plan the layout of the Table section @@ -65,7 +64,7 @@ class WvCasCaDescriptor { // Return private data in the CA descriptor. virtual std::string GeneratePrivateData(const std::string& provider, - const std::string& content_id) const; + const std::string& content_id) const; }; } // namespace cas diff --git a/media_cas_packager_sdk/public/wv_cas_ca_descriptor_test.cc b/media_cas_packager_sdk/public/wv_cas_ca_descriptor_test.cc index c0480b9..71037d5 100644 --- a/media_cas_packager_sdk/public/wv_cas_ca_descriptor_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_ca_descriptor_test.cc @@ -42,7 +42,7 @@ TEST_F(WvCasCaDescriptorTest, BaseSize) { TEST_F(WvCasCaDescriptorTest, BasicGoodGen) { EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(kTestPid, "", "", &actual_ca_descriptor_)); - const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x00\x32", 6); + const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xE0\x32", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); } @@ -61,7 +61,7 @@ TEST_F(WvCasCaDescriptorTest, PidMinOK) { const uint32_t min_pid = 0x10; EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(min_pid, "", "", &actual_ca_descriptor_)); - const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x00\x10", 6); + const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xE0\x10", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); } @@ -69,7 +69,7 @@ TEST_F(WvCasCaDescriptorTest, PidMaxOK) { const uint32_t max_pid = 0x1FFE; EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(max_pid, "", "", &actual_ca_descriptor_)); - const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x1f\xfe"); + const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xff\xfe"); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); } @@ -82,77 +82,77 @@ TEST_F(WvCasCaDescriptorTest, PidTooHighFail) { TEST_F(WvCasCaDescriptorTest, PidOneByte) { EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(255, "", "", &actual_ca_descriptor_)); - const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x00\xff", 6); + const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\xff", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); } TEST_F(WvCasCaDescriptorTest, PidSecondByte) { EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x1F00, "", "", &actual_ca_descriptor_)); - const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x1f\x00", 6); + const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xff\x00", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); } TEST_F(WvCasCaDescriptorTest, PidTwelveBits) { EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0xFFF, "", "", &actual_ca_descriptor_)); - const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x0f\xff"); + const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xef\xff"); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); } TEST_F(WvCasCaDescriptorTest, PidThirteenthBit) { EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x1000, "", "", &actual_ca_descriptor_)); - const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x10\x00", 6); + const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xf0\x00", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); } TEST_F(WvCasCaDescriptorTest, PidTwelthBit) { EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x800, "", "", &actual_ca_descriptor_)); - const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x08\x00", 6); + const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe8\x00", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); } TEST_F(WvCasCaDescriptorTest, PidElevenththBit) { EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x400, "", "", &actual_ca_descriptor_)); - const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x04\x00", 6); + const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe4\x00", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); } TEST_F(WvCasCaDescriptorTest, PidTenthBit) { EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x200, "", "", &actual_ca_descriptor_)); - const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x02\x00", 6); + const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe2\x00", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); } TEST_F(WvCasCaDescriptorTest, PidNinthBit) { EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(0x100, "", "", &actual_ca_descriptor_)); - const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x01\x00", 6); + const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe1\x00", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); } TEST_F(WvCasCaDescriptorTest, PrivateDataOnlyProviderIgnored) { EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(kTestPid, kProvider, "", &actual_ca_descriptor_)); - const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x00\x32", 6); + const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\x32", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); } TEST_F(WvCasCaDescriptorTest, PrivateDataOnlyContentIdIgnored) { EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor(kTestPid, "", kContentId, &actual_ca_descriptor_)); - const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\x00\x32", 6); + const std::string expected_ca_descriptor("\x09\x04\x4a\xd4\xe0\x32", 6); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); } TEST_F(WvCasCaDescriptorTest, PrivateData) { EXPECT_EQ(OK, ca_descriptor_.GenerateCaDescriptor( kTestPid, kProvider, kContentId, &actual_ca_descriptor_)); - const std::string expected_ca_descriptor("\x09\x19\x4a\xd4\x00\x32", 6); + const std::string expected_ca_descriptor("\x09\x19\x4a\xd4\xe0\x32", 6); CaDescriptorPrivateData private_data; private_data.set_provider(kProvider); private_data.set_content_id(kContentId); @@ -162,10 +162,13 @@ TEST_F(WvCasCaDescriptorTest, PrivateData) { class FakePrivateDataCaDescriptor : public WvCasCaDescriptor { public: - void set_private_data(std::string private_data) { private_data_ = private_data; } + void set_private_data(std::string private_data) { + private_data_ = private_data; + } - std::string GeneratePrivateData(const std::string& provider, - const std::string& content_id) const override { + std::string GeneratePrivateData( + const std::string& provider, + const std::string& content_id) const override { return private_data_; } @@ -178,7 +181,7 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataOneByte) { fake_descriptor.set_private_data("X"); EXPECT_EQ(OK, fake_descriptor.GenerateCaDescriptor( kTestPid, kProvider, kContentId, &actual_ca_descriptor_)); - const std::string expected_ca_descriptor("\x09\x05\x4a\xd4\x00\x32X", 7); + const std::string expected_ca_descriptor("\x09\x05\x4a\xd4\xe0\x32X", 7); EXPECT_EQ(expected_ca_descriptor, actual_ca_descriptor_); } @@ -188,7 +191,7 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataMultipleBytes) { fake_descriptor.set_private_data(private_data_bytes); EXPECT_EQ(OK, fake_descriptor.GenerateCaDescriptor( kTestPid, kProvider, kContentId, &actual_ca_descriptor_)); - const std::string expected_ca_descriptor("\x09\x0e\x4a\xd4\x00\x32", 6); + const std::string expected_ca_descriptor("\x09\x0e\x4a\xd4\xe0\x32", 6); EXPECT_EQ(expected_ca_descriptor + private_data_bytes, actual_ca_descriptor_); } @@ -198,7 +201,7 @@ TEST_F(WvCasCaDescriptorTest, PrivateDataMaxNumberBytes) { fake_descriptor.set_private_data(private_data_bytes); EXPECT_EQ(OK, fake_descriptor.GenerateCaDescriptor( kTestPid, kProvider, kContentId, &actual_ca_descriptor_)); - const std::string expected_ca_descriptor("\x09\xff\x4a\xd4\x00\x32", 6); + const std::string expected_ca_descriptor("\x09\xff\x4a\xd4\xe0\x32", 6); EXPECT_EQ(expected_ca_descriptor + private_data_bytes, actual_ca_descriptor_); } diff --git a/media_cas_packager_sdk/public/wv_cas_ecm.cc b/media_cas_packager_sdk/public/wv_cas_ecm.cc index cba6949..16579a3 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm.cc +++ b/media_cas_packager_sdk/public/wv_cas_ecm.cc @@ -19,7 +19,6 @@ #include "common/status.h" #include "example/constants.h" #include "media_cas_packager_sdk/internal/ecm.h" -#include "media_cas_packager_sdk/internal/ecm_generator.h" #include "media_cas_packager_sdk/internal/fixed_key_fetcher.h" #include "media_cas_packager_sdk/internal/mpeg2ts.h" #include "media_cas_packager_sdk/internal/util.h" @@ -115,9 +114,9 @@ WvCasStatus WvCasEcm::GenerateEcm( } std::string even_content_iv_str(even_content_iv, content_iv_size_); std::string even_entitlement_key_id_str(even_entitlement_key_id, - kEntitlementKeyIdSizeBytes); + kEntitlementKeyIdSizeBytes); std::string even_entitlement_key_str(even_entitlement_key, - kEntitlementKeySizeBytes); + kEntitlementKeySizeBytes); std::string odd_key_str; if (crypto_mode_ == CryptoMode::kDvbCsa2) { odd_key_str = std::string(odd_key, kCsaContentKeySizeBytes); @@ -127,8 +126,9 @@ WvCasStatus WvCasEcm::GenerateEcm( } std::string odd_content_iv_str(odd_content_iv, content_iv_size_); std::string odd_entitlement_key_id_str(odd_entitlement_key_id, - kEntitlementKeyIdSizeBytes); - std::string odd_entitlement_key_str(odd_entitlement_key, kEntitlementKeySizeBytes); + kEntitlementKeyIdSizeBytes); + std::string odd_entitlement_key_str(odd_entitlement_key, + kEntitlementKeySizeBytes); // Double check some input sizes. if (even_key_str.size() != kContentKeySizeBytes || @@ -143,7 +143,7 @@ WvCasStatus WvCasEcm::GenerateEcm( } // Create an instance of Ecm in order to set the entitlement keys. - std::unique_ptr cas_ecm = absl::make_unique(); + auto cas_ecm = absl::make_unique(); std::string entitlement_request; std::string entitlement_response; EcmInitParameters ecm_init_params = CreateEcmInitParameters( @@ -179,23 +179,21 @@ WvCasStatus WvCasEcm::GenerateEcm( } // Generate ECM. - EcmGenerator ecm_generator; - ecm_generator.set_ecm(std::move(cas_ecm)); - EcmParameters ecm_param; - ecm_param.rotation_enabled = key_rotation_enabled_; + std::vector keys; + keys.reserve(2); // Add even entitlement key. - ecm_param.key_params.emplace_back(); - ecm_param.key_params[0].key_data = even_key_str; - ecm_param.key_params[0].wrapped_key_iv = crypto_util::DeriveIv(even_key_str); - ecm_param.key_params[0].key_id = crypto_util::DeriveKeyId(even_key_str); - ecm_param.key_params[0].content_ivs.push_back(even_content_iv_str); + keys.emplace_back(); + keys[0].key_value = even_key_str; + keys[0].wrapped_key_iv = crypto_util::DeriveIv(even_key_str); + keys[0].key_id = crypto_util::DeriveKeyId(even_key_str); + keys[0].content_iv = even_content_iv_str; // Add odd entitlement key. - ecm_param.key_params.emplace_back(); - ecm_param.key_params[1].key_data = odd_key_str; - ecm_param.key_params[1].wrapped_key_iv = crypto_util::DeriveIv(odd_key_str); - ecm_param.key_params[1].key_id = crypto_util::DeriveKeyId(odd_key_str); - ecm_param.key_params[1].content_ivs.push_back(odd_content_iv_str); - *ecm = ecm_generator.GenerateEcm(ecm_param); + keys.emplace_back(); + keys[1].key_value = odd_key_str; + keys[1].wrapped_key_iv = crypto_util::DeriveIv(odd_key_str); + keys[1].key_id = crypto_util::DeriveKeyId(odd_key_str); + keys[1].content_iv = odd_content_iv_str; + status = cas_ecm->GenerateEcm(&keys[0], &keys[1], kDefaultTrackTypeSd, ecm); size_t expected_ecm_size = content_iv_size_ == 8 ? kEcmWith8BytesContentIvSizeBytes @@ -239,9 +237,9 @@ WvCasStatus WvCasEcm::GenerateSingleKeyEcm( } std::string even_content_iv_str(even_content_iv, content_iv_size_); std::string even_entitlement_key_id_str(even_entitlement_key_id, - kEntitlementKeyIdSizeBytes); + kEntitlementKeyIdSizeBytes); std::string even_entitlement_key_str(even_entitlement_key, - kEntitlementKeySizeBytes); + kEntitlementKeySizeBytes); // Double check some input sizes. if (even_key_str.size() != kContentKeySizeBytes) { @@ -254,7 +252,7 @@ WvCasStatus WvCasEcm::GenerateSingleKeyEcm( } // Create an instance of Ecm in order to set the entitlement keys. - std::unique_ptr cas_ecm = absl::make_unique(); + auto cas_ecm = absl::make_unique(); std::string entitlement_request; std::string entitlement_response; EcmInitParameters ecm_init_params = CreateEcmInitParameters( @@ -290,17 +288,12 @@ WvCasStatus WvCasEcm::GenerateSingleKeyEcm( } // Generate ECM. - EcmGenerator ecm_generator; - ecm_generator.set_ecm(std::move(cas_ecm)); - EcmParameters ecm_param; - ecm_param.rotation_enabled = key_rotation_enabled_; - // Add even entitlement key. - ecm_param.key_params.emplace_back(); - ecm_param.key_params[0].key_data = even_key_str; - ecm_param.key_params[0].wrapped_key_iv = crypto_util::DeriveIv(even_key_str); - ecm_param.key_params[0].key_id = crypto_util::DeriveKeyId(even_key_str); - ecm_param.key_params[0].content_ivs.push_back(even_content_iv_str); - *ecm = ecm_generator.GenerateEcm(ecm_param); + EntitledKeyInfo key; + key.key_value = even_key_str; + key.wrapped_key_iv = crypto_util::DeriveIv(even_key_str); + key.key_id = crypto_util::DeriveKeyId(key.key_value); + key.content_iv = even_content_iv_str; + status = cas_ecm->GenerateSingleKeyEcm(&key, kDefaultTrackTypeSd, ecm); size_t expected_ecm_size = content_iv_size_ == 8 ? kSingleKeyEcmWith8BytesContentIvSizeBytes @@ -317,7 +310,7 @@ WvCasStatus WvCasEcm::GenerateSingleKeyEcm( WvCasStatus WvCasEcm::GenerateTsPacket(const std::string& ecm, uint16_t pid, uint8_t table_id, uint8_t* continuity_counter, - uint8_t* packet) { + uint8_t* packet) const { ssize_t bytes_modified = 0; Status status = InsertEcmAsTsPacket(ecm, pid, table_id, continuity_counter, packet, &bytes_modified); diff --git a/media_cas_packager_sdk/public/wv_cas_ecm.h b/media_cas_packager_sdk/public/wv_cas_ecm.h index d85623c..00f6ff1 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm.h +++ b/media_cas_packager_sdk/public/wv_cas_ecm.h @@ -118,7 +118,7 @@ class WvCasEcm { // - |pid| program ID for the ECM stream // - |table_id| is the table ID byte put in the section header, it should be // either 0x80 or 0x81. Changing table ID from 0x80 or 0x81 or - // 0x81 to 0x80 is used to singal to the client that the key contained + // 0x81 to 0x80 is used to signal to the client that the key contained // in the ECM has changed. In other words, if you are building an ECM // with a new key that was not in any previous ECM, you should flip the // table ID so the client knows this is an important ECM it should process. @@ -132,7 +132,7 @@ class WvCasEcm { virtual WvCasStatus GenerateTsPacket(const std::string& ecm, uint16_t pid, uint8_t table_id, uint8_t* continuity_counter, - uint8_t* packet); + uint8_t* packet) const; private: bool initialized_ = false; diff --git a/media_cas_packager_sdk/public/wv_cas_ecm_test.cc b/media_cas_packager_sdk/public/wv_cas_ecm_test.cc index 083e0ee..d7dc64a 100644 --- a/media_cas_packager_sdk/public/wv_cas_ecm_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_ecm_test.cc @@ -138,7 +138,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_8BytesContentIv_Ctr_Success) { /* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm)); EXPECT_EQ( - "4ad40103806576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952" + "4ad40203806576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952" "a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665" "6e5f69762e6f64645f656e745f6b65795f69642e2e34cd74b6b998889aad0e71b44bdd8c" "0e03e31ea68ab80a6ee79f59f0936bc6aa64fe976b6a4a5db2dc7e3ebba4a0bd876f6464" @@ -160,7 +160,7 @@ TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_8BytesContentIv_Ctr_Success) { /* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm)); EXPECT_EQ( - "4ad40102806576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952" + "4ad40202806576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952" "a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665" "6e5f69762e", absl::BytesToHexString(actual_ecm)); @@ -186,7 +186,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_16BytesContentIv_Ctr_Success) { /* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm)); EXPECT_EQ( - "4ad40103c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952" + "4ad40203c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952" "a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665" "6e5f69762e2e2e2e2e2e2e2e2e6f64645f656e745f6b65795f69642e2e34cd74b6b99888" "9aad0e71b44bdd8c0e03e31ea68ab80a6ee79f59f0936bc6aa64fe976b6a4a5db2dc7e3e" @@ -209,7 +209,7 @@ TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_16BytesContentIv_Ctr_Success) { /* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm)); EXPECT_EQ( - "4ad40102c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952" + "4ad40202c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952" "a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665" "6e5f69762e2e2e2e2e2e2e2e2e", absl::BytesToHexString(actual_ecm)); @@ -235,7 +235,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_16BytesContentIv_Cbc_Success) { /* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm)); EXPECT_EQ( - "4ad40101c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952" + "4ad40201c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952" "a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665" "6e5f69762e2e2e2e2e2e2e2e2e6f64645f656e745f6b65795f69642e2e34cd74b6b99888" "9aad0e71b44bdd8c0e03e31ea68ab80a6ee79f59f0936bc6aa64fe976b6a4a5db2dc7e3e" @@ -258,7 +258,7 @@ TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_16BytesContentIv_Cbc_Success) { /* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm)); EXPECT_EQ( - "4ad40100c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952" + "4ad40200c06576656e5f656e745f6b65795f69642e3d1798a8729c0a316583bd514cf952" "a94350a82ce961f90b1008c9cdce343b2827827aeb1ba30292c0061d80cf50ce7f657665" "6e5f69762e2e2e2e2e2e2e2e2e", absl::BytesToHexString(actual_ecm)); @@ -284,7 +284,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_8BytesContentIv_Csa_Success) { /* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm)); EXPECT_EQ( - "4ad40105806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10" + "4ad40205806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10" "e6b5f391e74f120cfb876efba02d7f506f0ef185b3398096111dafd86ff7d395a2657665" "6e5f69762e6f64645f656e745f6b65795f69642e2e7f655fe61e99e89e03ac23df98cc02" "1cf21dfe9637c72c3480727ab18332d4ee219e81b8f34c9df2704b0595501832736f6464" @@ -307,7 +307,7 @@ TEST_F(WvCasEcmTest, GenerateSingleKeyEcm_8BytesContentIv_Csa_Success) { /* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm)); EXPECT_EQ( - "4ad40104806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10" + "4ad40204806576656e5f656e745f6b65795f69642ee9c009a9d6a07c7bc3ca82f39c1f10" "e6b5f391e74f120cfb876efba02d7f506f0ef185b3398096111dafd86ff7d395a2657665" "6e5f69762e", absl::BytesToHexString(actual_ecm)); @@ -333,7 +333,7 @@ TEST_F(WvCasEcmTest, GenerateEcm_8BytesContentIv_Csa_NulCharInKey_Success) { /* odd_entitlement_key= */ kOddEntitlementKey, &actual_ecm)); EXPECT_EQ( - "4ad40105806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf" + "4ad40205806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf" "d0b085eb9a85a57dbdb799158e829996988524bf3b3cfe01b28d4474f85ec2991d657665" "6e5f69762e6f64645f656e745f6b65795f69642e2e874aab870ffba640875a4521d3cd57" "02f26d0f9c7e9c69d7059c9ad42b091ec1f151aaa190536f4f330edebe84fe5a786f6464" @@ -357,7 +357,7 @@ TEST_F(WvCasEcmTest, /* even_entitlement_key= */ kEvenEntitlementKey, &actual_ecm)); EXPECT_EQ( - "4ad40104806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf" + "4ad40204806576656e5f656e745f6b65795f69642e71ffb7d9500261db7a92974405c2cf" "d0b085eb9a85a57dbdb799158e829996988524bf3b3cfe01b28d4474f85ec2991d657665" "6e5f69762e", absl::BytesToHexString(actual_ecm)); diff --git a/media_cas_packager_sdk/public/wv_cas_key_fetcher.cc b/media_cas_packager_sdk/public/wv_cas_key_fetcher.cc index d8efb49..8960a70 100644 --- a/media_cas_packager_sdk/public/wv_cas_key_fetcher.cc +++ b/media_cas_packager_sdk/public/wv_cas_key_fetcher.cc @@ -40,8 +40,9 @@ DEFINE_string(signing_iv, "", namespace widevine { namespace cas { -Status WvCasKeyFetcher::RequestEntitlementKey(const std::string& request_string, - std::string* signed_response_string) { +Status WvCasKeyFetcher::RequestEntitlementKey( + const std::string& request_string, + std::string* signed_response_string) const { if (FLAGS_signing_provider.empty() || FLAGS_signing_key.empty() || FLAGS_signing_iv.empty()) { return Status( @@ -115,7 +116,8 @@ Status WvCasKeyFetcher::RequestEntitlementKey(const std::string& request_string, return OkStatus(); } -size_t AppendToString(void* ptr, size_t size, size_t count, std::string* output) { +size_t AppendToString(void* ptr, size_t size, size_t count, + std::string* output) { const absl::string_view data(static_cast(ptr), size * count); absl::StrAppend(output, data); return data.size(); @@ -140,8 +142,9 @@ Status WvCasKeyFetcher::MakeHttpRequest(const std::string& signed_request_json, (int64_t)strlen(signed_request_json.c_str())); curl_code = curl_easy_perform(curl); if (curl_code != CURLE_OK) { - return Status(error::INTERNAL, "curl_easy_perform() failed: " + - std::string(curl_easy_strerror(curl_code))); + return Status(error::INTERNAL, + "curl_easy_perform() failed: " + + std::string(curl_easy_strerror(curl_code))); } curl_easy_cleanup(curl); } else { diff --git a/media_cas_packager_sdk/public/wv_cas_key_fetcher.h b/media_cas_packager_sdk/public/wv_cas_key_fetcher.h index f91dbd6..4ae15ec 100644 --- a/media_cas_packager_sdk/public/wv_cas_key_fetcher.h +++ b/media_cas_packager_sdk/public/wv_cas_key_fetcher.h @@ -29,7 +29,7 @@ class WvCasKeyFetcher : public KeyFetcher { WvCasKeyFetcher() = default; WvCasKeyFetcher(const WvCasKeyFetcher&) = delete; WvCasKeyFetcher& operator=(const WvCasKeyFetcher&) = delete; - virtual ~WvCasKeyFetcher() = default; + ~WvCasKeyFetcher() override = default; // Get entitlement keys from the license server. Send a // SignedCasEncryptionRequest message to the license server, receive a @@ -40,8 +40,9 @@ class WvCasKeyFetcher : public KeyFetcher { // |signed_response_string| a serialized SignedCasEncryptionResponse // message. It should be passed into // widevine::cas::Ecm::ProcessCasEncryptionResponse(). - Status RequestEntitlementKey(const std::string& request_string, - std::string* signed_response_string) override; + Status RequestEntitlementKey( + const std::string& request_string, + std::string* signed_response_string) const override; protected: // Makes a HTTP request to License Server for entitlement key(s). diff --git a/media_cas_packager_sdk/public/wv_cas_key_fetcher_test.cc b/media_cas_packager_sdk/public/wv_cas_key_fetcher_test.cc index 3574705..b1445d8 100644 --- a/media_cas_packager_sdk/public/wv_cas_key_fetcher_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_key_fetcher_test.cc @@ -54,8 +54,9 @@ class MockWvCasKeyFetcher : public WvCasKeyFetcher { public: MockWvCasKeyFetcher() : WvCasKeyFetcher() {} ~MockWvCasKeyFetcher() override {} - MOCK_CONST_METHOD2(MakeHttpRequest, Status(const std::string& signed_request_json, - std::string* http_response_json)); + MOCK_CONST_METHOD2(MakeHttpRequest, + Status(const std::string& signed_request_json, + std::string* http_response_json)); }; class WvCasKeyFetcherTest : public ::testing::Test { diff --git a/media_cas_packager_sdk/public/wv_cas_types.cc b/media_cas_packager_sdk/public/wv_cas_types.cc index cd74c90..95694e1 100644 --- a/media_cas_packager_sdk/public/wv_cas_types.cc +++ b/media_cas_packager_sdk/public/wv_cas_types.cc @@ -46,9 +46,13 @@ std::string GetWvCasStatusMessage(WvCasStatus status) { // Numeric value of crypto mode is the index into strings array. static const char* kCrypoModeStrings[] = { - "AesCbc", - "AesCtr", - "DvbCsa2", + "AesCbc", "AesCtr", "DvbCsa2", "DvbCsa3", "AesOfb", "AesScte", +}; + +// Numeric value of scrambling level is the index into strings array. +static const char* kScramblingLevelStrings[] = { + "PES", + "TS", }; bool CryptoModeToString(CryptoMode mode, std::string* str) { @@ -69,7 +73,7 @@ bool StringToCryptoMode(const std::string& str, CryptoMode* mode) { return false; } for (int i = 0; i < arraysize(kCrypoModeStrings); ++i) { - if (str.compare(kCrypoModeStrings[i]) == 0) { + if (str == kCrypoModeStrings[i]) { *mode = static_cast(i); return true; } @@ -78,6 +82,33 @@ bool StringToCryptoMode(const std::string& str, CryptoMode* mode) { return false; } +bool ScramblingLevelToString(ScramblingLevel mode, std::string* str) { + if (str == nullptr) { + return false; + } + int mode_idx = static_cast(mode); + if (mode_idx >= 0 && mode_idx < arraysize(kScramblingLevelStrings)) { + *str = kScramblingLevelStrings[mode_idx]; + return true; + } + LOG(ERROR) << "Invalid scrambling mode: " << mode_idx; + return false; +} + +bool StringToScramblingLevel(const std::string& str, ScramblingLevel* mode) { + if (mode == nullptr) { + return false; + } + for (int i = 0; i < arraysize(kScramblingLevelStrings); ++i) { + if (str == kScramblingLevelStrings[i]) { + *mode = static_cast(i); + return true; + } + } + LOG(ERROR) << "Invalid scrambling mode: " << str; + return false; +} + WvCasStatus CreateWvCasEncryptionRequestJson( const WvCasEncryptionRequest& request, std::string* request_json) { CHECK(request_json); diff --git a/media_cas_packager_sdk/public/wv_cas_types.h b/media_cas_packager_sdk/public/wv_cas_types.h index 883fd5a..cbea067 100644 --- a/media_cas_packager_sdk/public/wv_cas_types.h +++ b/media_cas_packager_sdk/public/wv_cas_types.h @@ -52,19 +52,31 @@ enum WvCasStatus { std::string GetWvCasStatusMessage(WvCasStatus status); // Crypto mode for encryption / decryption. ENUM value should be consistent with -// https://docs.google.com/document/d/1A5vflf8tbKyUheV-xsvfxFqB6YyNLNdsGXYx8ZnhjfY/edit#heading=h.ej4ts3lifoio +// ECM V2 definition. Largest supported value for this CryptoMode ENUM is 15. enum class CryptoMode : int { + kInvalid = -1, kAesCbc = 0, kAesCtr = 1, kDvbCsa2 = 2, + kDvbCsa3 = 3, + kAesOfb = 4, + kAesScte = 5, }; +enum class ScramblingLevel : int { kPES = 0, kTS = 1 }; + // Returns false if mode is not a valid CryptoMode. bool CryptoModeToString(CryptoMode mode, std::string* str); // Returns false if str is not a valid CryptoMode. bool StringToCryptoMode(const std::string& str, CryptoMode* mode); +// Returns false if mode is not a valid ScramblingLevel. +bool ScramblingLevelToString(ScramblingLevel mode, std::string* str); + +// Returns false if str is not a valid ScramblingLevel. +bool StringToScramblingLevel(const std::string& str, ScramblingLevel* mode); + struct WvCasEncryptionRequest { std::string content_id; std::string provider; @@ -111,7 +123,7 @@ struct WvCasEncryptionResponse { // This request JSON can be later put into the 'request' field of a signed // request JSON message. // And that signed JSON message can be sent to Widevine license server for -// aquiring entitlement keys. +// acquiring entitlement keys. WvCasStatus CreateWvCasEncryptionRequestJson( const WvCasEncryptionRequest& request, std::string* request_json); diff --git a/media_cas_packager_sdk/public/wv_cas_types_test.cc b/media_cas_packager_sdk/public/wv_cas_types_test.cc index 04b3cec..31bb873 100644 --- a/media_cas_packager_sdk/public/wv_cas_types_test.cc +++ b/media_cas_packager_sdk/public/wv_cas_types_test.cc @@ -33,6 +33,12 @@ TEST(WvCasTypesTest, CryptoModeToString) { EXPECT_EQ("AesCbc", crypto_mode); ASSERT_TRUE(CryptoModeToString(CryptoMode::kDvbCsa2, &crypto_mode)); EXPECT_EQ("DvbCsa2", crypto_mode); + ASSERT_TRUE(CryptoModeToString(CryptoMode::kDvbCsa3, &crypto_mode)); + EXPECT_EQ("DvbCsa3", crypto_mode); + ASSERT_TRUE(CryptoModeToString(CryptoMode::kAesOfb, &crypto_mode)); + EXPECT_EQ("AesOfb", crypto_mode); + ASSERT_TRUE(CryptoModeToString(CryptoMode::kAesScte, &crypto_mode)); + EXPECT_EQ("AesScte", crypto_mode); EXPECT_FALSE(CryptoModeToString(static_cast(-1), &crypto_mode)); } @@ -44,9 +50,34 @@ TEST(WvCasTypesTest, StringToCryptoMode) { EXPECT_EQ(CryptoMode::kAesCbc, crypto_mode); ASSERT_TRUE(StringToCryptoMode("DvbCsa2", &crypto_mode)); EXPECT_EQ(CryptoMode::kDvbCsa2, crypto_mode); + ASSERT_TRUE(StringToCryptoMode("DvbCsa3", &crypto_mode)); + EXPECT_EQ(CryptoMode::kDvbCsa3, crypto_mode); + ASSERT_TRUE(StringToCryptoMode("AesScte", &crypto_mode)); + EXPECT_EQ(CryptoMode::kAesScte, crypto_mode); EXPECT_FALSE(StringToCryptoMode("invalid crypto mode", &crypto_mode)); } +TEST(WvCasTypesTest, ScramblingLevelToString) { + std::string scrambling_level; + ASSERT_TRUE( + ScramblingLevelToString(ScramblingLevel::kPES, &scrambling_level)); + EXPECT_EQ("PES", scrambling_level); + ASSERT_TRUE(ScramblingLevelToString(ScramblingLevel::kTS, &scrambling_level)); + EXPECT_EQ("TS", scrambling_level); + EXPECT_FALSE(ScramblingLevelToString(static_cast(-1), + &scrambling_level)); +} + +TEST(WvCasTypeStatus, StringToScramblingLevel) { + ScramblingLevel scrambling_level; + ASSERT_TRUE(StringToScramblingLevel("PES", &scrambling_level)); + EXPECT_EQ(ScramblingLevel::kPES, scrambling_level); + ASSERT_TRUE(StringToScramblingLevel("TS", &scrambling_level)); + EXPECT_EQ(ScramblingLevel::kTS, scrambling_level); + EXPECT_FALSE( + StringToScramblingLevel("invalid scrambling level", &scrambling_level)); +} + TEST(WvCasTypesTest, CreateWvCasEncryptionRequestJson) { WvCasEncryptionRequest request; request.content_id = "test_content_id"; diff --git a/media_cas_packager_sdk/public/wv_ecmg.cc b/media_cas_packager_sdk/public/wv_ecmg.cc index 8393aec..fa88faf 100644 --- a/media_cas_packager_sdk/public/wv_ecmg.cc +++ b/media_cas_packager_sdk/public/wv_ecmg.cc @@ -11,6 +11,7 @@ #include #include #include + #include #include #include @@ -22,43 +23,50 @@ #include "glog/logging.h" #include "absl/strings/str_cat.h" #include "media_cas_packager_sdk/internal/ecmg_client_handler.h" +#include "media_cas_packager_sdk/public/wv_cas_types.h" static constexpr int32_t kDefaultDelayStart = 200; static constexpr int32_t kDefaultDelayStop = 200; static constexpr int32_t kDefaultEcmRepPeriod = 100; static constexpr int32_t kDefaultMaxCompTime = 100; -static constexpr int32_t kAccessCriteriaTransferMode = 1; +static constexpr int32_t kAccessCriteriaTransferMode = 0; +static constexpr int32_t kNumberOfContentKeys = 2; +static constexpr char kDefaultCryptoMode[] = "AesCtr"; +static constexpr bool kDefaultUseFixedFetcher = false; DEFINE_int32(port, 0, "Server port number"); // ECMG related flags. // TODO(user): Consider adding flags 'ac_delay_start', 'ac_delay_stop', // 'transition_delay_start', 'transition_delay_stop'. -DEFINE_int32(delay_start, kDefaultDelayStart, - absl::StrCat("This flag sets the DVB SimulCrypt delay_start " - "parameter, in milliseconds. Default: ", - kDefaultDelayStart, " ms") - .c_str()); -DEFINE_int32(delay_stop, kDefaultDelayStop, - absl::StrCat("This flag sets the DVB SimulCrypt delay_stop " - "parameter, in milliseconds. Default: ", - kDefaultDelayStop, " ms") - .c_str()); -DEFINE_int32(ecm_rep_period, kDefaultEcmRepPeriod, - absl::StrCat("It sets the DVB SimulCrypt parameter " - "ECM_rep_period, in milliseconds. Default: ", - kDefaultEcmRepPeriod, " ms") - .c_str()); -DEFINE_int32(max_comp_time, kDefaultMaxCompTime, - absl::StrCat("It sets the DVB SimulCrypt parameter max_comp_time, " - "in milliseconds. Default: ", - kDefaultMaxCompTime, " ms") - .c_str()); -DEFINE_int32(access_criteria_transfer_mode, kAccessCriteriaTransferMode, - absl::StrCat("It sets the DVB SimulCrypt parameter " - "access_criteria_transfer_mode. Default: ", - kAccessCriteriaTransferMode) - .c_str()); +DEFINE_int32( + delay_start, kDefaultDelayStart, + "This flag sets the DVB SimulCrypt delay_start parameter, in milliseconds"); +DEFINE_int32( + delay_stop, kDefaultDelayStop, + "This flag sets the DVB SimulCrypt delay_stop parameter, in milliseconds"); +DEFINE_int32( + ecm_rep_period, kDefaultEcmRepPeriod, + "It sets the DVB SimulCrypt parameter ECM_rep_period, in milliseconds"); +DEFINE_int32( + max_comp_time, kDefaultMaxCompTime, + "It sets the DVB SimulCrypt parameter max_comp_time, in milliseconds."); +DEFINE_int32( + access_criteria_transfer_mode, kAccessCriteriaTransferMode, + "If it equals 0, it indicates that the access_criteria parameter is " + "required in the CW_provision message only when the contents of this " + "parameter change. If it equals 1, it indicates that the ECMG requires the " + "access_criteria parameter be present in each CW_provision message."); +DEFINE_int32(number_of_content_keys, kNumberOfContentKeys, + "It sets the number of content keys (CwPerMsg). Must be 1 (single " + "key) or 2 (key rotation enabled). It also sets LeadCw as " + "number_of_content_keys - 1."); +DEFINE_string(crypto_mode, kDefaultCryptoMode, + "Encryption mode. Choices are \"AesCtr\", \"AesCbc\", " + "\"DvbCsa2\", \"DvbCsa3\", \"AesOfb\", \"AesScte\"."); +DEFINE_bool(use_fixed_fetcher, kDefaultUseFixedFetcher, + "Use fixed fetcher to fetch mocked entitlement licenses for " + "testing purposes."); #define LISTEN_QUEUE_SIZE (20) #define BUFFER_SIZE (1024) @@ -73,6 +81,10 @@ void BuildEcmgConfig(EcmgConfig* config) { config->ecm_rep_period = FLAGS_ecm_rep_period; config->max_comp_time = FLAGS_max_comp_time; config->access_criteria_transfer_mode = FLAGS_access_criteria_transfer_mode; + config->number_of_content_keys = FLAGS_number_of_content_keys; + CHECK(StringToCryptoMode(FLAGS_crypto_mode, &config->crypto_mode)) + << "Unknown crypto mode."; + config->use_fixed_fetcher = FLAGS_use_fixed_fetcher; } void PrintMessage(const std::string& description, const char* const message, @@ -116,6 +128,8 @@ void ServeClient(int socket_fd, EcmgClientHandler* ecmg) { int main(int argc, char** argv) { gflags::ParseCommandLineFlags(&argc, &argv, true); CHECK(FLAGS_port != 0) << "need --port"; + CHECK(FLAGS_number_of_content_keys == 1 || FLAGS_number_of_content_keys == 2) + << "--number_of_content_keys must be 1 or 2."; EcmgConfig ecmg_config; BuildEcmgConfig(&ecmg_config); diff --git a/protos/public/BUILD b/protos/public/BUILD index 8f4205b..fac8889 100644 --- a/protos/public/BUILD +++ b/protos/public/BUILD @@ -10,18 +10,22 @@ package(default_visibility = ["//visibility:public"]) -load("@protobuf_repo//:protobuf.bzl", "cc_proto_library") - -cc_proto_library( +proto_library( name = "media_cas_encryption_proto", srcs = ["media_cas_encryption.proto"], - default_runtime = "@protobuf_repo//:protobuf", - protoc = "@protobuf_repo//:protoc", ) cc_proto_library( + name = "media_cas_encryption_cc_proto", + deps = [":media_cas_encryption_proto"], +) + +proto_library( name = "media_cas_proto", srcs = ["media_cas.proto"], - default_runtime = "@protobuf_repo//:protobuf", - protoc = "@protobuf_repo//:protoc", +) + +cc_proto_library( + name = "media_cas_cc_proto", + deps = [":media_cas_proto"], ) diff --git a/protos/public/media_cas_encryption.proto b/protos/public/media_cas_encryption.proto index 81d0195..84f8044 100644 --- a/protos/public/media_cas_encryption.proto +++ b/protos/public/media_cas_encryption.proto @@ -10,10 +10,10 @@ syntax = "proto2"; -option java_package = "com.google.video.widevine.mediacasencryption"; - package widevine; +option java_package = "com.google.video.widevine.mediacasencryption"; + message CasEncryptionRequest { optional bytes content_id = 1; optional string provider = 2; @@ -43,7 +43,7 @@ message CasEncryptionResponse { SINGLE = 1; EVEN = 2; ODD = 3; - }; + } optional bytes key_id = 1; optional bytes key = 2; // Optional label used for the key.