diff --git a/BUILD b/BUILD index 66d06b2..85e320b 100644 --- a/BUILD +++ b/BUILD @@ -15,6 +15,8 @@ pkg_tar( strip_prefix = "/", files = [ "run_tests.sh", + "//common:provisioning_sdk_binary_release_files", + "//common/python:provisioning_sdk_binary_release_files", "//example:binary_release_files", "//protos/public:binary_release_files", "//provisioning_sdk/public:binary_release_files", diff --git a/WORKSPACE b/WORKSPACE index b147b7c..e9b2463 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1,5 +1,6 @@ workspace(name = "provisioning_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( @@ -10,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( @@ -61,7 +81,7 @@ bind( bind( name = "protobuf", - actual = "@protobuf_repo//:protobuf", + actual = "@com_google_protobuf//:protobuf", ) bind( diff --git a/common/BUILD b/common/BUILD index cb07fbc..221e12b 100644 --- a/common/BUILD +++ b/common/BUILD @@ -16,10 +16,67 @@ filegroup( name = "binary_release_files", srcs = [ "certificate_type.h", + "default_device_security_profile_list.h", + "security_profile_list.h", "status.h", ], ) +filegroup( + name = "provisioning_sdk_binary_release_files", + srcs = [ + "certificate_type.h", + ], +) + +cc_library( + name = "playready_interface", + hdrs = ["playready_interface.h"], + deps = [ + "//util:error_space", + "//protos/public:license_protocol_cc_proto", + ], +) + +cc_library( + name = "playready_sdk_impl", + hdrs = ["playready_sdk_impl.h"], + deps = [ + ":playready_interface", + "//protos/public:license_protocol_cc_proto", + ], +) + +cc_library( + name = "content_id_util", + srcs = ["content_id_util.cc"], + hdrs = ["content_id_util.h"], + deps = [ + ":error_space", + ":status", + "//base", + "//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,16 +89,89 @@ 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", + ":device_status_list", + "//base", + "@com_google_protobuf//:protobuf", + "@abseil_repo//absl/synchronization", + "//protos/public:client_identification_cc_proto", + "//protos/public:device_certificate_status_cc_proto", + "//protos/public:device_common_cc_proto", + "//protos/public:device_security_profile_data_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 = [ + ":client_id_util", + ":security_profile_list", + "//base", + "@com_google_protobuf//:protobuf", + "//testing:gunit_main", + "@abseil_repo//absl/memory", + "//protos/public:device_common_cc_proto", + "//protos/public:device_security_profile_data_cc_proto", + "//protos/public:security_profile_cc_proto", + ], +) + +cc_library( + name = "default_device_security_profile_list", + srcs = ["default_device_security_profile_list.cc"], + hdrs = ["default_device_security_profile_list.h"], + deps = [ + ":client_id_util", + ":device_status_list", + ":security_profile_list", + "//base", + "@com_google_protobuf//:protobuf", + "//protos/public:client_identification_cc_proto", + "//protos/public:device_certificate_status_cc_proto", + "//protos/public:device_common_cc_proto", + "//protos/public:provisioned_device_info_cc_proto", + "//protos/public:security_profile_cc_proto", + ], +) + +cc_test( + name = "default_device_security_profile_list_test", + timeout = "short", + srcs = ["default_device_security_profile_list_test.cc"], + deps = [ + ":client_id_util", + ":default_device_security_profile_list", + "//base", + "@com_google_protobuf//:protobuf", + "//testing:gunit_main", + "@abseil_repo//absl/memory", + "//protos/public:device_common_cc_proto", + "//protos/public:security_profile_cc_proto", + ], +) + cc_library( name = "status", srcs = ["status.cc"], hdrs = ["status.h"], + copts = ["-fvisibility=default"], deps = [ - "//base", "@abseil_repo//absl/base:core_headers", "@abseil_repo//absl/strings", "//util:error_space", ], + # Make sure SDKs links in symbols defined in this + # target. + alwayslink = 1, ) cc_test( @@ -55,12 +185,26 @@ 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", + "dual_certificate_client_cert.cc", + "dual_certificate_client_cert.h", + "keybox_client_cert.cc", + ], + hdrs = [ + "client_cert.h", + "keybox_client_cert.h", + ], deps = [ ":crypto_util", ":drm_root_certificate", + ":ec_key", + ":ec_util", ":error_space", + ":hash_algorithm", + ":openssl_util", ":random_util", ":rsa_key", ":sha_util", @@ -68,16 +212,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 +227,22 @@ cc_test( srcs = ["client_cert_test.cc"], deps = [ ":client_cert", - ":drm_root_certificate", + ":ec_key", + ":ec_test_keys", ":error_space", + ":hash_algorithm", + ":hash_algorithm_util", + ":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 +252,32 @@ cc_library( hdrs = ["device_status_list.h"], deps = [ ":client_cert", - ":crypto_util", - ":drm_root_certificate", ":drm_service_certificate", ":error_space", - ":random_util", + ":hash_algorithm", + ":hash_algorithm_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:device_common_cc_proto", + "//protos/public:provisioned_device_info_cc_proto", ], ) @@ -137,15 +288,21 @@ cc_test( deps = [ ":client_cert", ":device_status_list", + ":hash_algorithm", + ":hash_algorithm_util", + ":rsa_key", + ":rsa_test_keys", + ":status", "//base", + "@com_google_protobuf//: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 +312,21 @@ cc_library( hdrs = ["drm_root_certificate.h"], deps = [ ":certificate_type", + ":ec_key", ":error_space", + ":hash_algorithm", + ":hash_algorithm_util", ":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,16 +336,21 @@ cc_test( srcs = ["drm_root_certificate_test.cc"], deps = [ ":drm_root_certificate", + ":ec_key", + ":ec_test_keys", ":error_space", + ":hash_algorithm", + ":hash_algorithm_util", ":rsa_key", ":rsa_test_keys", ":test_drm_certificates", "//base", - "@protobuf_repo//:protobuf", + "@com_google_protobuf//: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 +360,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 +387,7 @@ cc_library( srcs = ["rsa_util.cc"], hdrs = ["rsa_util.h"], deps = [ + ":private_key_util", "//base", "//external:openssl", ], @@ -243,9 +421,12 @@ cc_library( srcs = ["rsa_key.cc"], hdrs = ["rsa_key.h"], deps = [ + ":hash_algorithm", ":rsa_util", ":sha_util", "//base", + "@abseil_repo//absl/base:core_headers", + "@abseil_repo//absl/strings", "//external:openssl", ], ) @@ -256,6 +437,7 @@ cc_test( timeout = "short", srcs = ["rsa_key_test.cc"], deps = [ + ":hash_algorithm", ":rsa_key", ":rsa_test_keys", ":rsa_util", @@ -269,9 +451,6 @@ cc_library( testonly = 1, srcs = ["rsa_test_keys.cc"], hdrs = ["rsa_test_keys.h"], - deps = [ - "//base", - ], ) cc_library( @@ -279,11 +458,179 @@ cc_library( testonly = 1, hdrs = ["mock_rsa_key.h"], deps = [ + ":hash_algorithm", ":rsa_key", "//testing:gunit", ], ) +cc_library( + name = "ec_util", + srcs = ["ec_util.cc"], + hdrs = [ + "ec_key.h", + "ec_util.h", + ], + deps = [ + ":hash_algorithm", + ":openssl_util", + ":private_key_util", + "//base", + "@abseil_repo//absl/base:core_headers", + "@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", + ":hash_algorithm", + ":openssl_util", + ":sha_util", + "//base", + "@abseil_repo//absl/base:core_headers", + "@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", + ":hash_algorithm", + ":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 +660,6 @@ cc_library( "//base", "@abseil_repo//absl/strings", "//external:openssl", - "//util/endian", ], ) @@ -326,6 +672,7 @@ cc_test( "//testing:gunit", "//testing:gunit_main", "@abseil_repo//absl/strings", + "//external:openssl", ], ) @@ -417,10 +764,10 @@ cc_library( hdrs = ["signature_util.h"], deps = [ ":aes_cbc_util", + ":hash_algorithm", ":rsa_key", ":sha_util", ":status", - "//base", ], ) @@ -431,7 +778,7 @@ cc_library( deps = [ ":crypto_util", "//base", - "//protos/public:license_protocol_proto", + "//protos/public:license_protocol_cc_proto", ], ) @@ -445,7 +792,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 +801,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 +852,7 @@ cc_library( deps = [ "//util:error_space", "//util:proto_status", - "//protos/public:errors_proto", + "//protos/public:errors_cc_proto", ], ) @@ -525,11 +868,12 @@ cc_library( ":status", ":x509_cert", "//base", + "@abseil_repo//absl/base:core_headers", "@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", ], ) @@ -546,13 +890,15 @@ cc_library( ":rsa_util", ":status", "//base", + "@abseil_repo//absl/base:core_headers", "@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:external_license_cc_proto", + "//protos/public:signed_drm_certificate_cc_proto", ], ) @@ -564,19 +910,21 @@ cc_test( ":aes_cbc_util", ":drm_root_certificate", ":drm_service_certificate", + ":hash_algorithm_util", ":rsa_key", ":rsa_test_keys", ":rsa_util", ":test_drm_certificates", "//base", - "@protobuf_repo//:protobuf", + "@com_google_protobuf//: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:external_license_cc_proto", + "//protos/public:license_server_sdk_cc_proto", + "//protos/public:signed_drm_certificate_cc_proto", ], ) @@ -587,9 +935,7 @@ cc_library( deps = [ ":status", ":vmp_checker", - "//base", - "@abseil_repo//absl/strings", - "//protos/public:license_protocol_proto", + "//protos/public:license_protocol_cc_proto", ], ) @@ -598,11 +944,11 @@ cc_library( srcs = ["x509_cert.cc"], hdrs = ["x509_cert.h"], deps = [ - ":error_space", ":openssl_util", ":rsa_key", ":status", "//base", + "@abseil_repo//absl/base:core_headers", "@abseil_repo//absl/strings", "@abseil_repo//absl/synchronization", "//external:openssl", @@ -629,7 +975,6 @@ cc_test( ":rsa_key", ":test_utils", ":x509_cert", - "//base", "//testing:gunit_main", "@abseil_repo//absl/strings", ], @@ -642,12 +987,13 @@ cc_library( deps = [ ":certificate_type", ":error_space", + ":hash_algorithm_util", ":rsa_key", ":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", ], ) @@ -656,13 +1002,14 @@ cc_test( timeout = "short", srcs = ["vmp_checker_test.cc"], deps = [ + ":hash_algorithm_util", ":rsa_key", ":vmp_checker", "//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 +1017,7 @@ cc_library( name = "string_util", srcs = ["string_util.cc"], hdrs = ["string_util.h"], - deps = [ - ":status", - "//base", - ], + deps = [":status"], ) cc_test( @@ -681,7 +1025,161 @@ 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", + "@com_google_protobuf//: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", + ":hash_algorithm", + ":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", + ":hash_algorithm", + ":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", + "//base", + "@abseil_repo//absl/strings", + "//common/oemcrypto_core_message/odk:kdo", + ], +) + +cc_test( + name = "core_message_util_test", + srcs = ["core_message_util_test.cc"], + deps = [ + ":core_message_util", + "//testing:gunit_main", + "@abseil_repo//absl/strings", + ], +) + +cc_library( + name = "hash_algorithm", + hdrs = ["hash_algorithm.h"], +) + +cc_library( + name = "hash_algorithm_util", + srcs = ["hash_algorithm_util.cc"], + hdrs = ["hash_algorithm_util.h"], + deps = [ + ":hash_algorithm", + "//base", + "//protos/public:hash_algorithm_cc_proto", + ], +) + +cc_test( + name = "hash_algorithm_util_test", + srcs = ["hash_algorithm_util_test.cc"], + deps = [ + ":hash_algorithm", + ":hash_algorithm_util", "//testing:gunit_main", ], ) 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..1713ea9 100644 --- a/common/aes_cbc_util_test.cc +++ b/common/aes_cbc_util_test.cc @@ -7,6 +7,7 @@ //////////////////////////////////////////////////////////////////////////////// #include "common/aes_cbc_util.h" + #include "testing/gmock.h" #include "testing/gunit.h" @@ -29,16 +30,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 +68,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 +128,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..be44107 --- /dev/null +++ b/common/certificate_client_cert.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. +//////////////////////////////////////////////////////////////////////////////// + +#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/license_protocol.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, + HashAlgorithm hash_algorithm, + const std::string& signature) const override { + CHECK(rsa_public_key_); + + if (!rsa_public_key_->VerifySignature(message, hash_algorithm, 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_; + } + + SignedMessage::SessionKeyType session_key_type() const override { + return SignedMessage::WRAPPED_AES_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, + HashAlgorithm hash_algorithm, + const std::string& signature) const override { + CHECK(client_ecc_public_key_); + + if (!client_ecc_public_key_->VerifySignature(message, hash_algorithm, + 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_; + } + + SignedMessage::SessionKeyType session_key_type() const override { + return SignedMessage::EPHEMERAL_ECC_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, HashAlgorithm hash_algorithm, + const std::string& signature, ProtocolVersion protocol_version) const { + return algorithm_->VerifySignature( + protocol_version < VERSION_2_2 ? message : Sha512_Hash(message), + hash_algorithm, 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..c412fec --- /dev/null +++ b/common/certificate_client_cert.h @@ -0,0 +1,114 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 "common/hash_algorithm.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, + HashAlgorithm hash_algorithm, + 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; + + // Returns information on the type session key used in this format. This value + // is intended to be included in the SignedMessage::session_key_type field of + // a license response. + virtual SignedMessage::SessionKeyType session_key_type() 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, + HashAlgorithm hash_algorithm, + 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(); } + SignedMessage::SessionKeyType key_type() const override { + return algorithm_->session_key_type(); + } + bool using_dual_certificate() const override { return false; } + 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; + } + const std::string& encrypted_unique_id() const override { + return device_cert_.rot_id().encrypted_unique_id(); + } + const std::string& unique_id_hash() const override { + return device_cert_.rot_id().unique_id_hash(); + } + + 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..ec6d30a 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,20 @@ #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/dual_certificate_client_cert.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 +31,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 +53,78 @@ 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, + const widevine::ClientIdentification& client_id, + std::unique_ptr* client_cert) { + CHECK(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()); - } - return OkStatus(); -} + switch (client_id.type()) { + case ClientIdentification::KEYBOX: + return CreateWithKeybox(client_id.token(), client_cert); + case ClientIdentification::DRM_DEVICE_CERTIFICATE: + if (!client_id.has_device_credentials()) { + return CreateWithDrmCertificate(root_certificate, client_id.token(), + client_cert); + } + // Assumes |client_id.token| is the signing cert and + // |client_id.device_credentials().token| is the encryption cert. + if (client_id.device_credentials().type() != + ClientIdentification::DRM_DEVICE_CERTIFICATE) + return Status(error_space, INVALID_DRM_CERTIFICATE, + "unsupported-encryption-certificate"); -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"); - } - return OkStatus(); -} - -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; - } - - 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 CreateWithDualDrmCertificates( + root_certificate, client_id.token(), + client_id.device_credentials().token(), client_cert); + default: + return Status(error_space, error::UNIMPLEMENTED, + "client-type-not-implemented"); } 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, ""); +// 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(root_certificate); + 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; +} + +Status ClientCert::CreateWithDualDrmCertificates( + const DrmRootCertificate* root_certificate, + const std::string& signing_drm_certificate, + const std::string& encryption_drm_certificate, + std::unique_ptr* client_cert) { + CHECK(root_certificate); + CHECK(client_cert); + auto device_cert = absl::make_unique(); + Status status = device_cert->Initialize( + root_certificate, signing_drm_certificate, encryption_drm_certificate); + if (status.ok()) { + *client_cert = std::move(device_cert); + } + 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); + } + return status; } } // namespace widevine diff --git a/common/client_cert.h b/common/client_cert.h index b2dc786..0bc3414 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,93 @@ #ifndef COMMON_CLIENT_CERT_H__ #define COMMON_CLIENT_CERT_H__ -#include #include -#include -#include "common/rsa_key.h" +#include "absl/strings/str_cat.h" +#include "common/drm_root_certificate.h" +#include "common/error_space.h" +#include "common/hash_algorithm.h" #include "common/status.h" #include "protos/public/client_identification.pb.h" +#include "protos/public/errors.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() {} - static Status Create( - const DrmRootCertificate* root_certificate, - widevine::ClientIdentification::TokenType token_type, - const std::string& token, ClientCert** client_cert); - // Creates a Keybox based ClientCert. - static Status CreateWithKeybox(const std::string& keybox_token, - ClientCert** client_cert); + // Creates a Device Certificate from the supplied |client_id|. + static Status Create(const DrmRootCertificate* root_certificate, + const widevine::ClientIdentification& client_id, + 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); + + // Creates a Device Certificate using the supplied certificates. + // The|signing_drm_certificate| will be used to verify an incoming request. + // The |encryption_drm_certificate| will be used to define the session key + // used to protect a response message. + static Status CreateWithDualDrmCertificates( + const DrmRootCertificate* root_certificate, + const std::string& signing_drm_certificate, + const std::string& encryption_drm_certificate, + 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, + 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, + HashAlgorithm hash_algorithm, + 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 SignedMessage::SessionKeyType key_type() const = 0; + virtual bool using_dual_certificate() 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 const std::string& encrypted_unique_id() const = 0; + virtual const std::string& unique_id_hash() const = 0; + virtual Status SystemIdUnknownError() const { + return Status( + error_space, DRM_DEVICE_CERTIFICATE_UNKNOWN, + absl::StrCat("device-certificate-status-unknown: ", system_id())); } - virtual void set_service_id(const std::string& service_id) { - service_id_ = service_id; + virtual Status SystemIdRevokedError() const { + return Status(error_space, DRM_DEVICE_CERTIFICATE_REVOKED, + absl::StrCat("device-certificate-revoked: ", system_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..e145f60 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,49 @@ #include "common/client_cert.h" -#include #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_key.h" +#include "common/ec_test_keys.h" #include "common/error_space.h" +#include "common/hash_algorithm.h" +#include "common/hash_algorithm_util.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; + +const HashAlgorithm kSha256 = HashAlgorithm::kSha256; + // 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< + std::tuple> { public: + ~ClientCertTest() override = default; void SetUp() override { if (!setup_preprov_keys_) { KeyboxClientCert::SetPreProvisioningKeys( @@ -73,16 +81,30 @@ class ClientCertTest : public ::testing::Test { class TestCertificateAndData { public: const std::string certificate_; + const std::string encryption_certificate_; const std::string expected_serial_number_; uint32_t expected_system_id_; Status expected_status_; + SignedMessage::SessionKeyType expected_key_type_; TestCertificateAndData(const std::string& certificate, const std::string& expected_serial_number, uint32_t expected_system_id, Status expected_status) : certificate_(certificate), expected_serial_number_(expected_serial_number), expected_system_id_(expected_system_id), - expected_status_(std::move(expected_status)) {} + expected_status_(expected_status), + expected_key_type_(SignedMessage::WRAPPED_AES_KEY) {} + TestCertificateAndData(const std::string& certificate, + const std::string& encryption_certificate, + const std::string& expected_serial_number, + uint32_t expected_system_id, Status expected_status, + SignedMessage::SessionKeyType expected_key_type) + : certificate_(certificate), + encryption_certificate_(encryption_certificate), + expected_serial_number_(expected_serial_number), + expected_system_id_(expected_system_id), + expected_status_(expected_status), + expected_key_type_(expected_key_type) {} }; void TestBasicValidation(const TestTokenAndKeys& expectation, @@ -91,32 +113,42 @@ 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, - std::string* signature); - SignedDrmCertificate* SignCertificate(const DrmCertificate& certificate, - SignedDrmCertificate* signer, - const std::string& private_key); - DrmCertificate* GenerateProvisionerCertificate(uint32_t system_id, - const std::string& serial_number, - const std::string& provider_id); - SignedDrmCertificate* GenerateSignedProvisionerCertificate( - uint32_t system_id, const std::string& serial_number, const std::string& service_id); - DrmCertificate* GenerateIntermediateCertificate(uint32_t system_id, - const std::string& serial_number); - SignedDrmCertificate* GenerateSignedIntermediateCertificate( + void GenerateSignature(const std::string& message, + const std::string& private_key, + HashAlgorithm hash_algorithm, std::string* signature); + std::unique_ptr SignCertificate( + const DrmCertificate& certificate, const SignedDrmCertificate* signer, + const std::string& private_key); + std::unique_ptr GenerateProvisionerCertificate( + uint32_t system_id, const std::string& serial_number, + const std::string& provider_id); + std::unique_ptr GenerateSignedProvisionerCertificate( + uint32_t system_id, const std::string& serial_number, + const std::string& service_id); + std::unique_ptr GenerateIntermediateCertificate( + uint32_t system_id, const std::string& serial_number); + std::unique_ptr GenerateSignedIntermediateCertificate( SignedDrmCertificate* signer, uint32_t system_id, - const std::string& serial_number); - DrmCertificate* GenerateDrmCertificate(uint32_t system_id, - const std::string& serial_number); - SignedDrmCertificate* GenerateSignedDrmCertificate( + const std::string& serial_number, DrmCertificate::Type signer_cert_type); + std::unique_ptr GenerateDrmCertificate( + uint32_t system_id, const std::string& serial_number, + DrmCertificate::Algorithm = DrmCertificate::RSA); + std::unique_ptr 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 GetECCPrivateKey(DrmCertificate::Algorithm algorithm); + 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 +156,25 @@ 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); + ClientIdentification client_id; + client_id.set_type(ClientIdentification::KEYBOX); + client_id.set_token(expectation.token_); + + status = ClientCert::Create(root_cert_.get(), client_id, &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,14 +187,26 @@ void ClientCertTest::TestBasicValidationDrmCertificate( // Test validation of a valid request. Status status; - ClientCert* client_cert_ptr = nullptr; - status = ClientCert::Create(root_cert_.get(), - ClientIdentification::DRM_DEVICE_CERTIFICATE, - expectation.certificate_, &client_cert_ptr); - std::unique_ptr drm_certificate_cert(client_cert_ptr); + std::unique_ptr drm_certificate_cert; + ClientIdentification client_id; + client_id.set_type(ClientIdentification::DRM_DEVICE_CERTIFICATE); + client_id.set_token(expectation.certificate_); + if (!expectation.encryption_certificate_.empty()) { + client_id.mutable_device_credentials()->set_token( + expectation.encryption_certificate_); + client_id.mutable_device_credentials()->set_type( + ClientIdentification::DRM_DEVICE_CERTIFICATE); + } + + status = + ClientCert::Create(root_cert_.get(), client_id, &drm_certificate_cert); ASSERT_EQ(expectation.expected_status_, status); if (expectation.expected_status_.ok()) { ASSERT_TRUE(drm_certificate_cert.get()); + if (!expectation.encryption_certificate_.empty()) { + ASSERT_TRUE(drm_certificate_cert->using_dual_certificate()); + } + ASSERT_EQ(expectation.expected_key_type_, drm_certificate_cert->key_type()); if (compare_data) { ASSERT_EQ(expectation.expected_serial_number_, drm_certificate_cert->signer_serial_number()); @@ -183,90 +220,154 @@ void ClientCertTest::TestBasicValidationDrmCertificate( void ClientCertTest::GenerateSignature(const std::string& message, const std::string& private_key, + HashAlgorithm hash_algorithm, std::string* signature) { std::unique_ptr rsa_private_key( RsaPrivateKey::Create(private_key)); ASSERT_TRUE(rsa_private_key != nullptr); - rsa_private_key->GenerateSignature(message, signature); + rsa_private_key->GenerateSignature(message, hash_algorithm, signature); } -// The caller relinquishes ownership of |signer|, which may also be nullptr. -SignedDrmCertificate* ClientCertTest::SignCertificate( - const DrmCertificate& certificate, SignedDrmCertificate* signer, +// The caller retains ownership of |signer|, which may also be nullptr. +std::unique_ptr ClientCertTest::SignCertificate( + const DrmCertificate& certificate, const SignedDrmCertificate* signer, const std::string& private_key) { std::unique_ptr signed_certificate( new SignedDrmCertificate); signed_certificate->set_drm_certificate(certificate.SerializeAsString()); - GenerateSignature(signed_certificate->drm_certificate(), private_key, - signed_certificate->mutable_signature()); + GenerateSignature( + signed_certificate->drm_certificate(), private_key, + HashAlgorithmProtoToEnum(signed_certificate->hash_algorithm()), + signed_certificate->mutable_signature()); if (signer != nullptr) { - signed_certificate->set_allocated_signer(signer); + *(signed_certificate->mutable_signer()) = *signer; } - return signed_certificate.release(); + return signed_certificate; } -DrmCertificate* ClientCertTest::GenerateIntermediateCertificate( +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::GetECCPrivateKey( + DrmCertificate::Algorithm algorithm) { + ECTestKeys keys; + switch (algorithm) { + case DrmCertificate::ECC_SECP256R1: + return keys.private_test_key_1_secp256r1(); + case DrmCertificate::ECC_SECP384R1: + return keys.private_test_key_1_secp384r1(); + case DrmCertificate::ECC_SECP521R1: + return keys.private_test_key_1_secp521r1(); + default: + return ""; + } +} + +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 ""; + } +} + +std::unique_ptr 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(); + return intermediate_certificate; } -SignedDrmCertificate* ClientCertTest::GenerateSignedIntermediateCertificate( +std::unique_ptr +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) { +std::unique_ptr ClientCertTest::GenerateDrmCertificate( + 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); - return drm_certificate.release(); + drm_certificate->set_algorithm(algorithm); + return drm_certificate; } -SignedDrmCertificate* ClientCertTest::GenerateSignedDrmCertificate( +std::unique_ptr +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())); - return signed_drm_certificate.release(); + GenerateDrmCertificate(system_id, serial_number, algorithm)); + std::unique_ptr signed_drm_certificate( + SignCertificate(*drm_certificate, signer, + GetPrivateKeyByCertType(DrmCertificate::DEVICE_MODEL))); + return signed_drm_certificate; } -DrmCertificate* ClientCertTest::GenerateProvisionerCertificate( - uint32_t system_id, const std::string& serial_number, const std::string& provider_id) { +std::unique_ptr ClientCertTest::GenerateProvisionerCertificate( + uint32_t system_id, const std::string& serial_number, + const std::string& provider_id) { std::unique_ptr provisioner_certificate(new DrmCertificate); provisioner_certificate->set_type(DrmCertificate::PROVISIONER); provisioner_certificate->set_serial_number(serial_number); - // TODO(user): Need to generate 3072 bit test for provisioner certificates. provisioner_certificate->set_public_key( - test_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); - return provisioner_certificate.release(); + return provisioner_certificate; } -SignedDrmCertificate* ClientCertTest::GenerateSignedProvisionerCertificate( - uint32_t system_id, const std::string& serial_number, const std::string& service_id) { +std::unique_ptr +ClientCertTest::GenerateSignedProvisionerCertificate( + uint32_t system_id, const std::string& serial_number, + const std::string& service_id) { std::unique_ptr provisioner_certificate( GenerateProvisionerCertificate(system_id, serial_number, service_id)); return SignCertificate(*provisioner_certificate, nullptr, - test_rsa_keys_.private_test_key_1_3072_bits()); + GetPrivateKeyByCertType(DrmCertificate::ROOT)); } TEST_F(ClientCertTest, BasicValidation) { @@ -296,20 +397,52 @@ 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")); + std::unique_ptr intermediate_certificate = + GenerateSignedIntermediateCertificate(nullptr, system_id, serial_number, + kNoSigner); + std::unique_ptr signed_cert = + GenerateSignedDrmCertificate(intermediate_certificate.get(), system_id, + serial_number + "-device1", + std::get<0>(GetParam())); + SignedMessage::SessionKeyType expected_key_type = + std::get<0>(GetParam()) != DrmCertificate::RSA + ? SignedMessage::EPHEMERAL_ECC_PUBLIC_KEY + : SignedMessage::WRAPPED_AES_KEY; + std::unique_ptr encryption_certificate; + if (std::get<1>(GetParam()) != DrmCertificate::UNKNOWN_ALGORITHM) { + encryption_certificate = GenerateSignedDrmCertificate( + intermediate_certificate.get(), system_id, serial_number + "-device2", + std::get<1>(GetParam())); + expected_key_type = std::get<1>(GetParam()) != DrmCertificate::RSA + ? SignedMessage::EPHEMERAL_ECC_PUBLIC_KEY + : SignedMessage::WRAPPED_AES_KEY; + } const TestCertificateAndData kValidCertificateAndExpectedData( - signed_cert->SerializeAsString(), serial_number, system_id, OkStatus()); + signed_cert->SerializeAsString(), + encryption_certificate == nullptr + ? std::string() + : encryption_certificate->SerializeAsString(), + serial_number, system_id, OkStatus(), expected_key_type); const bool compare_data = true; TestBasicValidationDrmCertificate(kValidCertificateAndExpectedData, compare_data); } +INSTANTIATE_TEST_SUITE_P( + BasicCertValidation, ClientCertTest, + testing::Combine(testing::Values(DrmCertificate::RSA, + DrmCertificate::ECC_SECP256R1, + DrmCertificate::ECC_SECP384R1, + DrmCertificate::ECC_SECP521R1), + testing::Values(DrmCertificate::UNKNOWN_ALGORITHM, + DrmCertificate::RSA, + DrmCertificate::ECC_SECP256R1, + DrmCertificate::ECC_SECP384R1, + DrmCertificate::ECC_SECP521R1))); + TEST_F(ClientCertTest, InvalidKeybox) { const TestTokenAndKeys kInvalidTokenAndExpectedKeys[] = { // This tests a malformed, but appropriately sized keybox. @@ -347,69 +480,80 @@ TEST_F(ClientCertTest, InvalidCertificate) { std::unique_ptr invalid_drm_cert( new SignedDrmCertificate); invalid_drm_cert->set_drm_certificate("bad-serialized-cert"); - GenerateSignature(invalid_drm_cert->drm_certificate(), - test_rsa_keys_.private_test_key_2_2048_bits(), - invalid_drm_cert->mutable_signature()); + GenerateSignature( + invalid_drm_cert->drm_certificate(), + test_rsa_keys_.private_test_key_2_2048_bits(), + HashAlgorithmProtoToEnum(invalid_drm_cert->hash_algorithm()), + invalid_drm_cert->mutable_signature()); invalid_drm_cert->set_allocated_signer( - GenerateSignedIntermediateCertificate(nullptr, system_id, signer_sn)); + GenerateSignedIntermediateCertificate(nullptr, system_id, signer_sn, + kNoSigner) + .release()); // Invalid device public key. - dev_cert.reset(GenerateDrmCertificate(system_id, device_sn)); + dev_cert = 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) + .get(), + test_rsa_keys_.private_test_key_2_2048_bits()); // Invalid serialized intermediate certificate. - signed_signer.reset( - GenerateSignedIntermediateCertificate(nullptr, system_id, signer_sn)); + signed_signer = 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(), + HashAlgorithmProtoToEnum(signed_signer->hash_algorithm()), signed_signer->mutable_signature()); - dev_cert.reset(GenerateDrmCertificate(system_id, device_sn)); + dev_cert = GenerateDrmCertificate(system_id, device_sn); std::unique_ptr invalid_signer( - SignCertificate(*dev_cert, signed_signer.release(), + SignCertificate(*dev_cert, signed_signer.get(), test_rsa_keys_.private_test_key_2_2048_bits())); // Invalid signer public key. - dev_cert.reset(GenerateDrmCertificate(system_id, device_sn)); - signer_cert.reset(GenerateIntermediateCertificate(system_id, signer_sn)); + dev_cert = GenerateDrmCertificate(system_id, device_sn); + signer_cert = GenerateIntermediateCertificate(system_id, signer_sn); signer_cert->set_public_key("bad-signer-public-key"); std::unique_ptr bad_signer_public_key(SignCertificate( *dev_cert, SignCertificate(*signer_cert, nullptr, - test_rsa_keys_.private_test_key_1_3072_bits()), + test_rsa_keys_.private_test_key_1_3072_bits()) + .get(), test_rsa_keys_.private_test_key_2_2048_bits())); // Invalid device certificate signature. std::unique_ptr bad_device_signature( - GenerateSignedDrmCertificate( - GenerateSignedIntermediateCertificate(nullptr, system_id, signer_sn), - system_id, device_sn)); + GenerateSignedDrmCertificate(GenerateSignedIntermediateCertificate( + nullptr, system_id, signer_sn, kNoSigner) + .get(), + system_id, device_sn)); bad_device_signature->set_signature("bad-signature"); // Missing model system ID. - dev_cert.reset(GenerateDrmCertificate(system_id, device_sn)); - signer_cert.reset(GenerateIntermediateCertificate(system_id, signer_sn)); + dev_cert = GenerateDrmCertificate(system_id, device_sn); + signer_cert = GenerateIntermediateCertificate(system_id, signer_sn); signer_cert->clear_system_id(); std::unique_ptr missing_model_sn(SignCertificate( *dev_cert, SignCertificate(*signer_cert, nullptr, - test_rsa_keys_.private_test_key_1_3072_bits()), + test_rsa_keys_.private_test_key_1_3072_bits()) + .get(), test_rsa_keys_.private_test_key_2_2048_bits())); // Missing signer serial number. - dev_cert.reset(GenerateDrmCertificate(system_id, device_sn)); - signer_cert.reset(GenerateIntermediateCertificate(system_id, signer_sn)); + dev_cert = GenerateDrmCertificate(system_id, device_sn); + signer_cert = GenerateIntermediateCertificate(system_id, signer_sn); signer_cert->clear_serial_number(); std::unique_ptr missing_signer_sn(SignCertificate( *dev_cert, SignCertificate(*signer_cert, nullptr, - test_rsa_keys_.private_test_key_1_3072_bits()), + test_rsa_keys_.private_test_key_1_3072_bits()) + .get(), 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)); + dev_cert = GenerateDrmCertificate(system_id, device_sn); + signed_signer = 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(), + SignCertificate(*dev_cert, signed_signer.get(), test_rsa_keys_.private_test_key_2_2048_bits())); const TestCertificateAndData kInvalidCertificate[] = { @@ -453,8 +597,12 @@ 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; + ClientIdentification client_id; + client_id.set_type(ClientIdentification::KEYBOX); + client_id.set_token(token); + Status status = + ClientCert::Create(root_cert_.get(), client_id, &client_cert_ptr); ASSERT_EQ(MISSING_PRE_PROV_KEY, status.error_code()); } @@ -465,28 +613,28 @@ TEST_F(ClientCertTest, ValidProvisionerDeviceCert) { const std::string intermediate_serial_number("intermediate-serial-number"); const std::string provisioner_serial_number("provisioner-serial-number"); - std::unique_ptr signed_provisioner_cert( + std::unique_ptr signed_provisioner_cert = GenerateSignedProvisionerCertificate(system_id, provisioner_serial_number, - service_id)); + service_id); - std::unique_ptr signed_intermediate_cert( - GenerateSignedIntermediateCertificate(signed_provisioner_cert.release(), - system_id, - intermediate_serial_number)); + std::unique_ptr signed_intermediate_cert = + GenerateSignedIntermediateCertificate( + signed_provisioner_cert.get(), system_id, intermediate_serial_number, + kProvisionerSigner); - std::unique_ptr signed_device_cert( - GenerateSignedDrmCertificate(signed_intermediate_cert.release(), - system_id, device_serial_number)); + std::unique_ptr signed_device_cert = + GenerateSignedDrmCertificate(signed_intermediate_cert.get(), system_id, + device_serial_number); std::string serialized_cert; signed_device_cert->SerializeToString(&serialized_cert); - ClientCert* client_cert_ptr = nullptr; + std::unique_ptr drm_cert; + ClientIdentification client_id; + client_id.set_type(ClientIdentification::DRM_DEVICE_CERTIFICATE); + client_id.set_token(serialized_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); + EXPECT_OK(ClientCert::Create(root_cert_.get(), client_id, &drm_cert)); + ASSERT_TRUE(drm_cert); EXPECT_EQ(service_id, drm_cert->service_id()); EXPECT_EQ(device_serial_number, drm_cert->serial_number()); @@ -501,27 +649,28 @@ TEST_F(ClientCertTest, InvalidProvisionerDeviceCertEmptyServiceId) { const std::string intermediate_serial_number("intermediate-serial-number"); const std::string provisioner_serial_number("provisioner-serial-number"); - std::unique_ptr signed_provisioner_cert( + std::unique_ptr signed_provisioner_cert = GenerateSignedProvisionerCertificate(system_id, provisioner_serial_number, - service_id)); + service_id); std::unique_ptr signed_intermediate_cert( - GenerateSignedIntermediateCertificate(signed_provisioner_cert.release(), - system_id, - intermediate_serial_number)); + GenerateSignedIntermediateCertificate( + signed_provisioner_cert.get(), system_id, intermediate_serial_number, + kProvisionerSigner)); - std::unique_ptr signed_device_cert( - GenerateSignedDrmCertificate(signed_intermediate_cert.release(), - system_id, device_serial_number)); + std::unique_ptr signed_device_cert = + GenerateSignedDrmCertificate(signed_intermediate_cert.get(), 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; + ClientIdentification client_id; + client_id.set_type(ClientIdentification::DRM_DEVICE_CERTIFICATE); + client_id.set_token(serialized_cert); EXPECT_EQ("missing-provisioning-service-id", - ClientCert::Create(root_cert_.get(), - ClientIdentification::DRM_DEVICE_CERTIFICATE, - serialized_cert, &client_cert_ptr) + ClientCert::Create(root_cert_.get(), client_id, &client_cert_ptr) .error_message()); EXPECT_FALSE(client_cert_ptr); } @@ -534,31 +683,127 @@ TEST_F(ClientCertTest, InvalidProvisionerDeviceCertChain) { const std::string intermediate_serial_number("intermediate-serial-number"); const std::string intermediate_serial_number2("intermediate-serial-number-2"); - std::unique_ptr signed_intermediate_cert2( - GenerateSignedIntermediateCertificate(nullptr, system_id2, - intermediate_serial_number2)); + std::unique_ptr signed_intermediate_cert2 = + 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)); - std::unique_ptr signed_device_cert( - GenerateSignedDrmCertificate(signed_intermediate_cert.release(), - system_id, device_serial_number)); + std::unique_ptr signed_intermediate_cert = + GenerateSignedIntermediateCertificate( + signed_intermediate_cert2.get(), system_id, + intermediate_serial_number, kDeviceModelSigner); + std::unique_ptr signed_device_cert = + GenerateSignedDrmCertificate(signed_intermediate_cert.get(), 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; + ClientIdentification client_id; + client_id.set_type(ClientIdentification::DRM_DEVICE_CERTIFICATE); + client_id.set_token(serialized_cert); - // TODO(user): Fix this test. It is failing for the right reasons, but the - // certificate chain is broken (intermediate signature does not match signer). - ASSERT_EQ("cache-miss-invalid-signature", - ClientCert::Create(root_cert_.get(), - ClientIdentification::DRM_DEVICE_CERTIFICATE, - serialized_cert, &client_cert_ptr) + ASSERT_EQ("expected-provisioning-provider-certificate-type", + ClientCert::Create(root_cert_.get(), client_id, &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.get(), system_id, intermediate_serial_number1, + kProvisionerSigner); + + std::unique_ptr signed_intermediate_cert2 = + GenerateSignedIntermediateCertificate( + signed_intermediate_cert1.get(), system_id, + intermediate_serial_number2, kDeviceModelSigner); + + std::unique_ptr signed_device_cert = + GenerateSignedDrmCertificate(signed_intermediate_cert2.get(), system_id, + device_serial_number); + + std::string serialized_cert; + signed_device_cert->SerializeToString(&serialized_cert); + std::unique_ptr client_cert_ptr = nullptr; + ClientIdentification client_id; + client_id.set_type(ClientIdentification::DRM_DEVICE_CERTIFICATE); + client_id.set_token(serialized_cert); + + ASSERT_EQ("certificate-chain-size-exceeded", + ClientCert::Create(root_cert_.get(), client_id, &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.get(), system_id, + intermediate_serial_number); + + std::unique_ptr signed_drm_cert = + GenerateSignedDrmCertificate(signed_intermediate_cert.get(), system_id, + drm_serial_number); + std::string serialized_cert; + signed_drm_cert->SerializeToString(&serialized_cert); + std::unique_ptr client_cert_ptr; + ClientIdentification client_id; + client_id.set_type(ClientIdentification::DRM_DEVICE_CERTIFICATE); + client_id.set_token(serialized_cert); + + EXPECT_EQ("device-cert-must-be-leaf", + ClientCert::Create(root_cert_.get(), client_id, &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.get(), system_id, intermediate_serial_number, + kProvisionerSigner); + std::string serialized_cert; + signed_intermediate_cert->SerializeToString(&serialized_cert); + std::unique_ptr client_cert_ptr; + ClientIdentification client_id; + client_id.set_type(ClientIdentification::DRM_DEVICE_CERTIFICATE); + client_id.set_token(serialized_cert); + // Leaf certificate must be a device certificate. + EXPECT_EQ("expected-device-certificate-type", + ClientCert::Create(root_cert_.get(), client_id, &client_cert_ptr) .error_message()); EXPECT_FALSE(client_cert_ptr); } @@ -566,11 +811,11 @@ TEST_F(ClientCertTest, InvalidProvisionerDeviceCertChain) { TEST_F(ClientCertTest, Protocol21WithDrmCert) { const char message[] = "A weekend wasted is a weekend well spent."; - ClientCert* client_cert_ptr = nullptr; - 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); + std::unique_ptr client_cert; + ClientIdentification client_id; + client_id.set_type(ClientIdentification::DRM_DEVICE_CERTIFICATE); + client_id.set_token(test_drm_certs_.test_user_device_certificate()); + ASSERT_OK(ClientCert::Create(root_cert_.get(), client_id, &client_cert)); std::unique_ptr private_key( RsaPrivateKey::Create(test_rsa_keys_.private_test_key_3_2048_bits())); @@ -578,25 +823,27 @@ TEST_F(ClientCertTest, Protocol21WithDrmCert) { // Success std::string signature; - ASSERT_TRUE(private_key->GenerateSignature(message, &signature)); - EXPECT_OK(client_cert->VerifySignature(message, signature, VERSION_2_1)); + ASSERT_TRUE(private_key->GenerateSignature(message, kSha256, &signature)); + EXPECT_OK( + client_cert->VerifySignature(message, kSha256, signature, VERSION_2_1)); // Failure ASSERT_EQ(256, signature.size()); ++signature[127]; EXPECT_FALSE( - client_cert->VerifySignature(message, signature, VERSION_2_1).ok()); + client_cert->VerifySignature(message, kSha256, signature, VERSION_2_1) + .ok()); } 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; - 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); + std::unique_ptr client_cert; + ClientIdentification client_id; + client_id.set_type(ClientIdentification::DRM_DEVICE_CERTIFICATE); + client_id.set_token(test_drm_certs_.test_user_device_certificate()); + ASSERT_OK(ClientCert::Create(root_cert_.get(), client_id, &client_cert)); std::unique_ptr private_key( RsaPrivateKey::Create(test_rsa_keys_.private_test_key_3_2048_bits())); @@ -604,14 +851,17 @@ TEST_F(ClientCertTest, Protocol22WithDrmCert) { // Success std::string signature; - ASSERT_TRUE(private_key->GenerateSignature(message_hash, &signature)); - EXPECT_OK(client_cert->VerifySignature(message, signature, VERSION_2_2)); + ASSERT_TRUE( + private_key->GenerateSignature(message_hash, kSha256, &signature)); + EXPECT_OK( + client_cert->VerifySignature(message, kSha256, signature, VERSION_2_2)); // Failure ASSERT_EQ(256, signature.size()); ++signature[127]; EXPECT_FALSE( - client_cert->VerifySignature(message, signature, VERSION_2_2).ok()); + client_cert->VerifySignature(message, kSha256, signature, VERSION_2_2) + .ok()); } } // namespace widevine diff --git a/common/client_id_util.cc b/common/client_id_util.cc index 327a3bf..cb25df8 100644 --- a/common/client_id_util.cc +++ b/common/client_id_util.cc @@ -10,12 +10,24 @@ #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 { +const char kModDrmMake[] = "company_name"; +const char kModDrmModel[] = "model_name"; +const char kModDrmDeviceName[] = "device_name"; +const char kModDrmProductName[] = "product_name"; +const char kModDrmBuildInfo[] = "build_info"; +const char kModDrmOemCryptoSecurityPatchLevel[] = + "oem_crypto_security_patch_level"; + void AddClientInfo(ClientIdentification* client_id, absl::string_view name, absl::string_view value) { ClientIdentification_NameValue* nv = client_id->add_client_info(); @@ -37,12 +49,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 +99,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..025b158 100644 --- a/common/client_id_util.h +++ b/common/client_id_util.h @@ -19,6 +19,13 @@ namespace widevine { +extern const char kModDrmMake[]; +extern const char kModDrmModel[]; +extern const char kModDrmDeviceName[]; +extern const char kModDrmProductName[]; +extern const char kModDrmBuildInfo[]; +extern const char kModDrmOemCryptoSecurityPatchLevel[]; + // Append the given name/value pair to client_id->client_info(). Does not // check for duplicates. void AddClientInfo(ClientIdentification* client_id, absl::string_view name, @@ -32,12 +39,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 +64,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..40c755e --- /dev/null +++ b/common/content_id_util.cc @@ -0,0 +1,83 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 "glog/logging.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 { + +Status GetContentIdFromExternalLicenseRequest( + const ExternalLicenseRequest& external_license_request, + std::string* content_id) { + WidevinePsshData pssh_data; + Status status = ParsePsshData(external_license_request, &pssh_data); + *content_id = 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); +} + +Status ParsePsshData(ExternalLicenseRequest external_license_request, + WidevinePsshData* widevine_pssh_data) { + if (!external_license_request.has_content_id()) { + std::string error = "ExternalLicenseRequest does not include ContentId"; + LOG(ERROR) << error + << ", request = " << external_license_request.ShortDebugString(); + return Status(error_space, MISSING_CONTENT_ID, error); + } + ContentInfo content_info; + Status status = + ParseContentId(external_license_request.content_id(), &content_info); + if (!status.ok()) { + std::string error = + "Unable to retrieve ContentId from ExternalLicenseRequest"; + LOG(ERROR) << error << ", status = " << status + << ", request = " << external_license_request.ShortDebugString(); + return Status(error_space, MISSING_CONTENT_ID, error); + } + switch (external_license_request.content_id().init_data().init_data_type()) { + case LicenseRequest::ContentIdentification::InitData::WEBM: + widevine_pssh_data->ParseFromString( + content_info.content_info_entry(0).key_ids(0)); + break; + default: + *widevine_pssh_data = + content_info.content_info_entry(0).pssh().widevine_data(); + break; + } + if (widevine_pssh_data->content_id().empty()) { + std::string error = + "Missing ContentId within Pssh data for ExternalLicenseRequest"; + LOG(ERROR) << error + << ", request = " << external_license_request.ShortDebugString(); + return Status(error_space, MISSING_CONTENT_ID, error); + } + return OkStatus(); +} +} // namespace widevine diff --git a/common/content_id_util.h b/common/content_id_util.h new file mode 100644 index 0000000..3775cb3 --- /dev/null +++ b/common/content_id_util.h @@ -0,0 +1,37 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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" +#include "protos/public/widevine_pssh.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); + +// Returns OK if successful and |widevine_pssh_data| will be populated by +// parsing |external_license_request|. Else, error and |widevine_pssh_data| +// will not be set within this method. +Status ParsePsshData(ExternalLicenseRequest external_license_request, + WidevinePsshData* widevine_pssh_data); + +} // 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..ca0ea31 --- /dev/null +++ b/common/content_id_util_test.cc @@ -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. +//////////////////////////////////////////////////////////////////////////////// + +#include "common/content_id_util.h" + +#include + +#include + +#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 ExternalLicenseType 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(PLAYREADY_LICENSE_NEW, + kPlayReadyChallenge, kContentId), + &content_id)); + EXPECT_EQ(kContentId, content_id); +} + +TEST(ContentIdUtil, GetContentIdFailureWithIncorrectType) { + std::string content_id; + SignedMessage signed_message = BuildSignedExternalLicenseRequest( + PLAYREADY_LICENSE_NEW, 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( + PLAYREADY_LICENSE_NEW, 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..a967170 --- /dev/null +++ b/common/core_message_util.cc @@ -0,0 +1,94 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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::CoreCommonRequestFromMessage; +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 { +bool GetCoreProvisioningResponse( + const std::string& serialized_provisioning_response, + const std::string& request_core_message, + std::string* response_core_message) { + if (request_core_message.empty()) { + return false; + } + oemcrypto_core_message::ODK_ProvisioningRequest odk_provisioning_request; + if (CoreProvisioningRequestFromMessage(request_core_message, + &odk_provisioning_request)) { + return CreateCoreProvisioningResponseFromProto( + serialized_provisioning_response, odk_provisioning_request, + response_core_message); + } + return false; +} + +bool GetCoreRenewalOrReleaseLicenseResponse( + uint64_t renewal_duration_seconds, const std::string& request_core_message, + std::string* response_core_message) { + if (request_core_message.empty()) { + return false; + } + oemcrypto_core_message::ODK_RenewalRequest odk_renewal_request; + if (!CoreRenewalRequestFromMessage(request_core_message, + &odk_renewal_request)) { + return false; + } + return CreateCoreRenewalResponse( + odk_renewal_request, renewal_duration_seconds, response_core_message); +} + +bool GetCoreNewLicenseResponse(const std::string& license, + const std::string& request_core_message, + const bool nonce_required, + std::string* response_core_message) { + if (request_core_message.empty()) { + return false; + } + oemcrypto_core_message::ODK_LicenseRequest odk_license_request; + if (!CoreLicenseRequestFromMessage(request_core_message, + &odk_license_request)) { + return false; + } + std::string core_request_sha256 = Sha256_Hash(request_core_message); + return CreateCoreLicenseResponseFromProto(license, odk_license_request, + core_request_sha256, nonce_required, + response_core_message); +} + +bool GetCoreVersion(const std::string& request_core_message, + uint16_t* odk_major_version, uint16_t* odk_minor_version) { + if (odk_major_version == nullptr || odk_minor_version == nullptr) { + return false; + } + oemcrypto_core_message::ODK_CommonRequest odk_common_request; + if (!CoreCommonRequestFromMessage(request_core_message, + &odk_common_request)) { + return false; + } + *odk_major_version = odk_common_request.api_major_version; + *odk_minor_version = odk_common_request.api_minor_version; + return true; +} + +} // 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..6bd8fbc --- /dev/null +++ b/common/core_message_util.h @@ -0,0 +1,47 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 + +#include + +namespace widevine { +namespace core_message_util { +// Gets the |response_core_message| by parsing |request_core_message| and +// |serialized_provisioning_response|. The output is held in +// |response_core_message|. +bool GetCoreProvisioningResponse( + const std::string& serialized_provisioning_response, + const std::string& request_core_message, + std::string* response_core_message); + +// Gets the |response_core_message| by parsing |request_core_message| for +// release and renewal response. The output is held in |response_core_message|. +bool GetCoreRenewalOrReleaseLicenseResponse( + uint64_t renewal_duration_seconds, const std::string& request_core_message, + std::string* response_core_message); + +// Gets the |response_core_message| by parsing |request_core_message| and +// |license| for new license response. The output is held in +// |response_core_message|. +bool GetCoreNewLicenseResponse(const std::string& license, + const std::string& request_core_message, + const bool nonce_required, + std::string* response_core_message); + +// Populates the |odk_major_version| and |odk_minor_version| from the ODK core +// message sent in the license request by parsing |request_core_message|. +bool GetCoreVersion(const std::string& request_core_message, + uint16_t* odk_major_version, uint16_t* odk_minor_version); + +} // namespace core_message_util +} // namespace widevine +#endif // COMMON_CORE_MESSAGE_UTIL_H_ diff --git a/common/core_message_util_test.cc b/common/core_message_util_test.cc new file mode 100644 index 0000000..7d0d9b6 --- /dev/null +++ b/common/core_message_util_test.cc @@ -0,0 +1,75 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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: +// Unit tests for the code_message_util helper functions. + +#include "common/core_message_util.h" + +#include "testing/gmock.h" +#include "testing/gunit.h" +#include "absl/strings/escaping.h" + +namespace { + +// Core message header format: +// message_type : 4 bytes +// message_length : 4 bytes +// minor_version : 2 bytes +// major_version : 2 bytes +// nonce : 4 bytes +// session_id : 4 bytes +const char kv16CoreMessage[] = "0000000100000014000300100000000000000000"; +const char kv16CoreMessageRenewal[] = + "000000030000001c000300100000000000000000000000000000005a"; + +} // anonymous namespace + +namespace widevine { +namespace core_message_util { + +class CoreMessageUtilTest : public ::testing::Test { + public: + CoreMessageUtilTest() {} + + protected: +}; + +TEST_F(CoreMessageUtilTest, GetODKVersionFromNewLicenseRequest) { + uint16_t major_version = 0; + uint16_t minor_version = 0; + std::string oemcrypto_core_message = absl::HexStringToBytes(kv16CoreMessage); + ASSERT_TRUE( + GetCoreVersion(oemcrypto_core_message, &major_version, &minor_version)); + EXPECT_EQ(major_version, 16); + EXPECT_EQ(minor_version, 3); +} + +TEST_F(CoreMessageUtilTest, GetODKVersionFromRenewLicenseRequest) { + uint16_t major_version = 0; + uint16_t minor_version = 0; + std::string oemcrypto_core_message = + absl::HexStringToBytes(kv16CoreMessageRenewal); + ASSERT_TRUE( + GetCoreVersion(oemcrypto_core_message, &major_version, &minor_version)); + EXPECT_EQ(major_version, 16); + EXPECT_EQ(minor_version, 3); +} + +TEST_F(CoreMessageUtilTest, GetODKVersionFromBogusRequest) { + uint16_t major_version = 0; + uint16_t minor_version = 0; + std::string oemcrypto_core_message = "bogus message"; + EXPECT_FALSE( + GetCoreVersion(oemcrypto_core_message, &major_version, &minor_version)); + EXPECT_EQ(major_version, 0); + EXPECT_EQ(minor_version, 0); +} + +} // namespace core_message_util +} // namespace widevine diff --git a/common/crypto_util.cc b/common/crypto_util.cc index 2a1ab2a..1ff6747 100644 --- a/common/crypto_util.cc +++ b/common/crypto_util.cc @@ -11,13 +11,13 @@ #include "common/crypto_util.h" #include "glog/logging.h" +#include "absl/strings/escaping.h" #include "absl/strings/string_view.h" #include "openssl/aes.h" #include "openssl/cmac.h" #include "openssl/evp.h" #include "openssl/hmac.h" #include "openssl/sha.h" -#include "util/endian/endian.h" namespace widevine { namespace crypto_util { @@ -38,9 +38,16 @@ const char kGroupKeyLabel[] = "GROUP_ENCRYPTION"; // a real group master key in keystore. // TODO(user): figure out why VerifySignatureHmacSha256 can not crypto_mcmcpy // like VerifySignatureHmacSha1. +// TODO(user): Revert logging signature in VerifySignatureHmacSha256. +// function. 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 +58,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 +81,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 +101,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 +167,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/default_device_security_profile_list.cc b/common/default_device_security_profile_list.cc new file mode 100644 index 0000000..9aa625e --- /dev/null +++ b/common/default_device_security_profile_list.cc @@ -0,0 +1,135 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 DefaultDeviceSecurityProfileList class. + +#include "common/default_device_security_profile_list.h" + +#include + +#include "glog/logging.h" +#include "google/protobuf/text_format.h" +#include "common/client_id_util.h" +#include "common/device_status_list.h" +#include "protos/public/client_identification.pb.h" +#include "protos/public/device_certificate_status.pb.h" +#include "protos/public/device_common.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 kWidevine[] = "widevine"; + +// Definition of Widevine default device security profiles. +// TODO(user): Add an OWNER file with per-file access to restrict changes to the +// profile definition. +const char kWidevineProfileMin[] = + (" name: \"minimum\"" + " min_output_requirements {" + " hdcp_version: HDCP_NONE" + " analog_output_capabilities: ANALOG_OUTPUT_UNKNOWN" + " }" + " min_security_requirements {" + " oemcrypto_api_version: 0" + " security_level: LEVEL_3" + " resource_rating_tier: 0" + " vulnerability_level: VULNERABILITY_HIGH" + " }" + " owner: \"Widevine\""); + +const char kWidevineProfileLow[] = + (" name: \"low\"" + " min_output_requirements {" + " hdcp_version: HDCP_NONE" + " analog_output_capabilities: ANALOG_OUTPUT_UNKNOWN" + " }" + " min_security_requirements {" + " oemcrypto_api_version: 8" + " security_level: LEVEL_3" + " resource_rating_tier: 1" + " vulnerability_level: VULNERABILITY_MEDIUM" + " }" + " owner: \"Widevine\""); + +const char kWidevineProfileMed[] = + (" name: \"medium\"" + " min_output_requirements {" + " hdcp_version: HDCP_V1" + " analog_output_capabilities: ANALOG_OUTPUT_UNKNOWN" + " }" + " min_security_requirements {" + " oemcrypto_api_version: 12" + " security_level: LEVEL_3" + " resource_rating_tier: 1" + " vulnerability_level: VULNERABILITY_LOW" + " }" + " owner: \"Widevine\""); + +const char kWidevineProfileHigh[] = + (" name: \"high\"" + " min_output_requirements {" + " hdcp_version: HDCP_V1" + " analog_output_capabilities: ANALOG_OUTPUT_SUPPORTS_CGMS_A" + " }" + " min_security_requirements {" + " oemcrypto_api_version: 12" + " security_level: LEVEL_1" + " resource_rating_tier: 2" + " vulnerability_level: VULNERABILITY_NONE" + " }" + " owner: \"Widevine\""); + +const char kWidevineProfileStrict[] = + (" name: \"strict\"" + " min_output_requirements {" + " hdcp_version: HDCP_V2_2" + " analog_output_capabilities: ANALOG_OUTPUT_SUPPORTS_CGMS_A" + " }" + " min_security_requirements {" + " oemcrypto_api_version: 12" + " security_level: LEVEL_1" + " resource_rating_tier: 3" + " vulnerability_level: VULNERABILITY_NONE" + " }" + " owner: \"Widevine\""); + +DefaultDeviceSecurityProfileList::DefaultDeviceSecurityProfileList() + : SecurityProfileList(kWidevine) {} + +int DefaultDeviceSecurityProfileList::Init() { return AddDefaultProfiles(); } + +int DefaultDeviceSecurityProfileList::AddDefaultProfiles() { + std::vector default_profile_strings; + GetDefaultProfileStrings(&default_profile_strings); + for (auto& profile_string : default_profile_strings) { + SecurityProfile profile; + if (!google::protobuf::TextFormat::ParseFromString(profile_string, &profile)) { + LOG(ERROR) << "Unable to load default profile: " << profile.name(); + ClearAllProfiles(); + return 0; + } + InsertProfile(profile); + } + return NumProfiles(); +} + +int DefaultDeviceSecurityProfileList::GetDefaultProfileStrings( + std::vector* default_profile_strings) const { + if (default_profile_strings == nullptr) { + return 0; + } + default_profile_strings->push_back(kWidevineProfileMin); + default_profile_strings->push_back(kWidevineProfileLow); + default_profile_strings->push_back(kWidevineProfileMed); + default_profile_strings->push_back(kWidevineProfileHigh); + default_profile_strings->push_back(kWidevineProfileStrict); + return default_profile_strings->size(); +} + +} // namespace widevine diff --git a/common/default_device_security_profile_list.h b/common/default_device_security_profile_list.h new file mode 100644 index 0000000..33d96d9 --- /dev/null +++ b/common/default_device_security_profile_list.h @@ -0,0 +1,39 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 default security profiless. + +#ifndef COMMON_DEFAULT_DEVICE_SECURITY_PROFILE_LIST_H_ +#define COMMON_DEFAULT_DEVICE_SECURITY_PROFILE_LIST_H_ + +#include "common/security_profile_list.h" + +namespace widevine { + +class DefaultDeviceSecurityProfileList : public SecurityProfileList { + public: + DefaultDeviceSecurityProfileList(); + ~DefaultDeviceSecurityProfileList() override {} + + // 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() override; + + private: + // Initialize the list with Widevine default profiles. The size of the + // profile list after the additions is returned. + virtual int AddDefaultProfiles(); + virtual int GetDefaultProfileStrings( + std::vector* default_profile_strings) const; +}; + +} // namespace widevine + +#endif // COMMON_DEFAULT_DEVICE_SECURITY_PROFILE_LIST_H_ diff --git a/common/default_device_security_profile_list_test.cc b/common/default_device_security_profile_list_test.cc new file mode 100644 index 0000000..958836c --- /dev/null +++ b/common/default_device_security_profile_list_test.cc @@ -0,0 +1,186 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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/default_device_security_profile_list.h" + +#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/client_id_util.h" +#include "protos/public/device_common.pb.h" +#include "protos/public/security_profile.pb.h" + +namespace widevine { +namespace security_profile { + +const uint32_t kResourceTierLow = 1; +const uint32_t kResourceTierMed = 2; +const uint32_t kResourceTierHigh = 3; +const char kMinProfileName[] = "minimum"; +const char kLowProfileName[] = "low"; +const char kMedProfileName[] = "medium"; +const char kHighProfileName[] = "high"; +const char kStrictProfileName[] = "strict"; + +class DefaultDeviceSecurityProfileListTest : public ::testing::Test { + public: + DefaultDeviceSecurityProfileListTest() {} + ~DefaultDeviceSecurityProfileListTest() override {} + + void SetUp() override { + SecurityProfile profile; + std::string profile_namespace = "widevine"; + profile_list_ = absl::make_unique(); + const int kNumWidevineProfiles = 5; + ASSERT_EQ(kNumWidevineProfiles, profile_list_->Init()); + } + + // Configure |client_id| and |device_info| with minimum settings. + void SetupMinDrmParams(ClientIdentification* client_id, + ProvisionedDeviceInfo* device_info) { + 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(0); + client_id->mutable_client_capabilities()->set_resource_rating_tier( + kResourceTierLow); + device_info->set_security_level(ProvisionedDeviceInfo::LEVEL_3); + } + + // Configure |client_id| and |device_info| with maximum settings. + void SetupMaxDrmParams(ClientIdentification* client_id, + ProvisionedDeviceInfo* device_info) { + client_id->mutable_client_capabilities()->set_max_hdcp_version( + ClientCapabilities::HDCP_V2_3); + 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(16); + client_id->mutable_client_capabilities()->set_resource_rating_tier( + kResourceTierHigh); + device_info->set_security_level(ProvisionedDeviceInfo::LEVEL_1); + } + + std::unique_ptr profile_list_; +}; + +TEST_F(DefaultDeviceSecurityProfileListTest, QualifiedProfiles) { + ClientIdentification client_id; + ProvisionedDeviceInfo device_info; + SetupMinDrmParams(&client_id, &device_info); + + std::vector qualified_profiles; + // Should only return the minimum profile. + ASSERT_EQ(1, profile_list_->GetQualifiedProfiles(client_id, device_info, + &qualified_profiles)); + EXPECT_TRUE(std::find(qualified_profiles.begin(), qualified_profiles.end(), + kMinProfileName) != qualified_profiles.end()); + + // Increase the device capabilities to include the low profile. + client_id.mutable_client_capabilities()->set_oem_crypto_api_version(8); + ASSERT_EQ(2, profile_list_->GetQualifiedProfiles(client_id, device_info, + &qualified_profiles)); + + EXPECT_TRUE(std::find(qualified_profiles.begin(), qualified_profiles.end(), + kMinProfileName) != qualified_profiles.end()); + EXPECT_TRUE(std::find(qualified_profiles.begin(), qualified_profiles.end(), + kLowProfileName) != qualified_profiles.end()); + + // Increase the device capabilities to include the med profile. + client_id.mutable_client_capabilities()->set_max_hdcp_version( + ClientCapabilities::HDCP_V1); + client_id.mutable_client_capabilities()->set_oem_crypto_api_version(12); + ASSERT_EQ(3, profile_list_->GetQualifiedProfiles(client_id, device_info, + &qualified_profiles)); + EXPECT_TRUE(std::find(qualified_profiles.begin(), qualified_profiles.end(), + kMinProfileName) != qualified_profiles.end()); + EXPECT_TRUE(std::find(qualified_profiles.begin(), qualified_profiles.end(), + kLowProfileName) != qualified_profiles.end()); + EXPECT_TRUE(std::find(qualified_profiles.begin(), qualified_profiles.end(), + kMedProfileName) != qualified_profiles.end()); + + // Increase the device capabilities to include the high profile. + device_info.set_security_level(ProvisionedDeviceInfo::LEVEL_1); + client_id.mutable_client_capabilities()->set_analog_output_capabilities( + ClientCapabilities::ANALOG_OUTPUT_SUPPORTS_CGMS_A); + client_id.mutable_client_capabilities()->set_resource_rating_tier( + kResourceTierMed); + ASSERT_EQ(4, profile_list_->GetQualifiedProfiles(client_id, device_info, + &qualified_profiles)); + EXPECT_TRUE(std::find(qualified_profiles.begin(), qualified_profiles.end(), + kMinProfileName) != qualified_profiles.end()); + EXPECT_TRUE(std::find(qualified_profiles.begin(), qualified_profiles.end(), + kLowProfileName) != qualified_profiles.end()); + EXPECT_TRUE(std::find(qualified_profiles.begin(), qualified_profiles.end(), + kMedProfileName) != qualified_profiles.end()); + EXPECT_TRUE(std::find(qualified_profiles.begin(), qualified_profiles.end(), + kHighProfileName) != qualified_profiles.end()); + + // Increase the device capabilities to include the strict profile. + 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(5, profile_list_->GetQualifiedProfiles(client_id, device_info, + &qualified_profiles)); + EXPECT_TRUE(std::find(qualified_profiles.begin(), qualified_profiles.end(), + kMinProfileName) != qualified_profiles.end()); + EXPECT_TRUE(std::find(qualified_profiles.begin(), qualified_profiles.end(), + kLowProfileName) != qualified_profiles.end()); + EXPECT_TRUE(std::find(qualified_profiles.begin(), qualified_profiles.end(), + kMedProfileName) != qualified_profiles.end()); + EXPECT_TRUE(std::find(qualified_profiles.begin(), qualified_profiles.end(), + kHighProfileName) != qualified_profiles.end()); + EXPECT_TRUE(std::find(qualified_profiles.begin(), qualified_profiles.end(), + kStrictProfileName) != qualified_profiles.end()); +} + +TEST_F(DefaultDeviceSecurityProfileListTest, + DeviceQualifiedProfilesForLowEndDevice) { + ClientIdentification client_id; + ProvisionedDeviceInfo device_info; + SetupMinDrmParams(&client_id, &device_info); + + // Only 1 profile should qualify for this device. + std::vector qualified_profiles; + ASSERT_EQ(1, profile_list_->GetQualifiedProfiles(client_id, device_info, + &qualified_profiles)); + EXPECT_TRUE(std::find(qualified_profiles.begin(), qualified_profiles.end(), + kMinProfileName) != qualified_profiles.end()); +} + +TEST_F(DefaultDeviceSecurityProfileListTest, + QualifiedProfilesForHighEndDevice) { + ClientIdentification client_id; + ProvisionedDeviceInfo device_info; + SetupMaxDrmParams(&client_id, &device_info); + + // All 5 default profiles should qualify for this device. + std::vector qualified_profiles; + ASSERT_EQ(5, profile_list_->GetQualifiedProfiles(client_id, device_info, + &qualified_profiles)); +} + +// TODO(b/160019477): Add test once provisioned device info supports known +// vulnerability. +TEST_F(DefaultDeviceSecurityProfileListTest, + DISABLED_QualifiedProfilesByVunerabilityLevel) { + ClientIdentification client_id; + ProvisionedDeviceInfo device_info; + SetupMaxDrmParams(&client_id, &device_info); + + std::vector qualified_profiles; + ASSERT_EQ(0, profile_list_->GetQualifiedProfiles(client_id, device_info, + &qualified_profiles)); +} + +} // namespace security_profile +} // namespace widevine diff --git a/common/device_info_util.cc b/common/device_info_util.cc new file mode 100644 index 0000000..1579e91 --- /dev/null +++ b/common/device_info_util.cc @@ -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. +//////////////////////////////////////////////////////////////////////////////// + +// Implements the device info helper function. +#include "common/device_info_util.h" + +#include "absl/strings/ascii.h" +#include "protos/public/device_common.pb.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 (const DeviceModel& product_info : device_info.model_info()) { + if (IsMatchedMakeModel(product_info.manufacturer(), + product_info.model_name(), 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..b202519 --- /dev/null +++ b/common/device_info_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_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..ce78c30 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,18 @@ #include "common/client_cert.h" #include "common/drm_service_certificate.h" #include "common/error_space.h" +#include "common/hash_algorithm_util.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 +57,20 @@ 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, + const std::string& serialized_device_certificate_status_list, + HashAlgorithm hash_algorithm, const std::string& signature, 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()) { + 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 +80,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, + hash_algorithm, 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() + @@ -116,28 +119,11 @@ Status DeviceStatusList::UpdateStatusList( return OkStatus(); } -Status DeviceStatusList::GetCertStatus(const ClientCert& client_cert, - ProvisionedDeviceInfo* device_info) { - CHECK(device_info); - - // Keybox checks. - if (client_cert.type() == ClientIdentification::KEYBOX) { - if (!KeyboxClientCert::IsSystemIdKnown(client_cert.system_id())) { - return Status(error_space, UNSUPPORTED_SYSTEM_ID, - "keybox-unsupported-system-id"); - } - // Get device information from certificate status list if available. - if (!GetDeviceInfo(client_cert, device_info)) { - device_info->Clear(); - } - return OkStatus(); - } - - // DRM certificate checks. - if (client_cert.type() != ClientIdentification::DRM_DEVICE_CERTIFICATE) { - return Status(error_space, INVALID_DRM_CERTIFICATE, - "device-certificate-unsupported-token-type"); - } +Status DeviceStatusList::GetCertStatus( + const ClientCert& client_cert, const std::string& make, + const std::string& provider, bool allow_revoked_system_id, + DeviceCertificateStatus* device_certificate_status) { + CHECK(device_certificate_status); absl::ReaderMutexLock lock(&status_map_lock_); if (expiration_period_seconds_ && (GetCurrentTime() > @@ -147,48 +133,68 @@ Status DeviceStatusList::GetCertStatus(const ClientCert& client_cert, } DeviceCertificateStatus* device_cert_status = gtl::FindOrNull(device_status_map_, client_cert.system_id()); - if (device_cert_status) { - *device_info = device_cert_status->device_info(); - if (device_cert_status->status() == - DeviceCertificateStatus::STATUS_REVOKED) { - if (IsRevokedSystemIdAllowed(client_cert.system_id())) { - LOG(WARNING) << "Allowing REVOKED device: " - << device_info->ShortDebugString(); - } else { - return Status(error_space, DRM_DEVICE_CERTIFICATE_REVOKED, - "device-certificate-revoked"); - } + + if (device_cert_status == nullptr) { + if (allow_unknown_devices_ || + client_cert.type() == ClientIdentification::KEYBOX) { + return OkStatus(); } - if ((device_cert_status->status() == - DeviceCertificateStatus::STATUS_TEST_ONLY) && - !allow_test_only_devices_) { + return client_cert.SystemIdUnknownError(); + } + *device_certificate_status = *device_cert_status; + + if (device_cert_status->status() == DeviceCertificateStatus::STATUS_REVOKED) { + if (IsRevokedSystemIdAllowed(client_cert.system_id()) || + allow_revoked_system_id) { + LOG(WARNING) << "Allowing REVOKED device: " + << device_cert_status->device_info().ShortDebugString(); + } else { + return client_cert.SystemIdRevokedError(); + } + } + + // The remainder of this function is for DRM certificates. + if (client_cert.type() == ClientIdentification::KEYBOX) { + return OkStatus(); + } + // DRM certificate checks. + if (client_cert.type() != ClientIdentification::DRM_DEVICE_CERTIFICATE) { + return Status(error_space, INVALID_DRM_CERTIFICATE, + "device-certificate-unsupported-token-type"); + } + if ((device_cert_status->status() == + DeviceCertificateStatus::STATUS_TEST_ONLY) && + !allow_test_only_devices_) { + if (IsTestOnlyDeviceAllowedByMake(client_cert.system_id(), make) && + IsTestOnlyDeviceAllowedByProvider(client_cert.system_id(), provider)) { + LOG(WARNING) << "Allowing TEST_ONLY device with systemId = " + << client_cert.system_id() << ", make = " << make + << ", provider = " << provider << ", device info = " + << device_cert_status->device_info().ShortDebugString(); + } else { + VLOG(2) << "Not allowing TEST ONLY device with systemId = " + << client_cert.system_id() << ", provider = " << provider + << ", device info = " + << device_cert_status->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() != - device_cert_status->drm_serial_number())) { - // Widevine-provisioned device, and the intermediate certificate serial - // number does not match that in the status list. If the status list is - // newer than the certificate, indicate an invalid certificate, so that - // the device re-provisions. If, on the other hand, the certificate status - // list is older than the certificate, the certificate is for all purposes - // unknown. - if (client_cert.signer_creation_time_seconds() < creation_time_seconds_) { - return Status(error_space, INVALID_DRM_CERTIFICATE, - "intermediate-certificate-serial-number-mismatch"); - } - return Status(error_space, DRM_DEVICE_CERTIFICATE_UNKNOWN, - "device-certificate-status-unknown"); - } - } else { - if (!allow_unknown_devices_) { - return Status(error_space, DRM_DEVICE_CERTIFICATE_UNKNOWN, - "device-certificate-status-unknown"); - } - device_info->Clear(); } - + if (!client_cert.signed_by_provisioner() && + (client_cert.signer_serial_number() != + device_cert_status->drm_serial_number())) { + // Widevine-provisioned device, and the intermediate certificate serial + // number does not match that in the status list. If the status list is + // newer than the certificate, indicate an invalid certificate, so that + // the device re-provisions. If, on the other hand, the certificate status + // list is older than the certificate, the certificate is for all purposes + // unknown. + if (client_cert.signer_creation_time_seconds() < creation_time_seconds_) { + return Status(error_space, INVALID_DRM_CERTIFICATE, + "intermediate-certificate-serial-number-mismatch"); + } + return client_cert.SystemIdUnknownError(); + } return OkStatus(); } @@ -198,13 +204,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 +261,168 @@ void DeviceStatusList::AllowRevokedDevices(const std::string& system_id_list) { std::sort(allowed_revoked_devices_.begin(), allowed_revoked_devices_.end()); } +void DeviceStatusList::AllowTestOnlyDevicesByMake( + const std::string& device_list_by_make) { + absl::WriterMutexLock lock(&allowed_test_only_devices_mutex_); + if (device_list_by_make.empty()) { + allowed_test_only_devices_by_make_.clear(); + return; + } + for (absl::string_view device : absl::StrSplit(device_list_by_make, ',')) { + const std::pair device_split = + absl::StrSplit(device, ':'); + if (device_split.second.empty() || device_split.second == "*") { + allowed_test_only_devices_by_make_.emplace( + std::stoi(std::string(device_split.first)), "*"); + VLOG(2) << "Allowing TEST_ONLY device: systemId = " + << std::stoi(std::string(device_split.first)) << ", make *"; + } else { + allowed_test_only_devices_by_make_.emplace( + std::stoi(std::string(device_split.first)), + absl::AsciiStrToUpper(device_split.second)); + VLOG(2) << "Allowing TEST_ONLY device: systemId = " + << std::stoi(std::string(device_split.first)) + << ", make = " << absl::AsciiStrToUpper(device_split.second); + } + } +} + +void DeviceStatusList::AllowTestOnlyDevicesByProvider( + const std::string& device_list_by_provider) { + absl::WriterMutexLock lock(&allowed_test_only_devices_mutex_); + if (device_list_by_provider.empty()) { + allowed_test_only_devices_by_provider_.clear(); + return; + } + for (absl::string_view device : + absl::StrSplit(device_list_by_provider, ',')) { + const std::pair device_split = + absl::StrSplit(device, ':'); + if (device_split.second.empty() || device_split.second == "*") { + allowed_test_only_devices_by_provider_.emplace( + std::stoi(std::string(device_split.first)), "*"); + VLOG(2) << "Allowing TEST_ONLY device: systemId = " + << std::stoi(std::string(device_split.first)) << ", provider *"; + } else { + allowed_test_only_devices_by_provider_.emplace( + std::stoi(std::string(device_split.first)), + absl::AsciiStrToUpper(device_split.second)); + VLOG(2) << "Allowing TEST_ONLY device: systemId = " + << std::stoi(std::string(device_split.first)) + << ", provider = " << 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::IsTestOnlyDeviceAllowedByMake( + uint32_t system_id, const std::string& manufacturer) { + absl::ReaderMutexLock lock(&allowed_test_only_devices_mutex_); + std::pair::iterator, + std::multimap::iterator> + allowed_makes = allowed_test_only_devices_by_make_.equal_range(system_id); + for (auto it = allowed_makes.first; it != allowed_makes.second; ++it) { + std::string allowed_makes = (*it).second; + if (allowed_makes == "*" || + allowed_makes == absl::AsciiStrToUpper(manufacturer)) { + return true; + } + } + return false; +} + +bool DeviceStatusList::IsTestOnlyDeviceAllowedByProvider( + uint32_t system_id, const std::string& provider) { + absl::ReaderMutexLock lock(&allowed_test_only_devices_mutex_); + std::pair::iterator, + std::multimap::iterator> + allowed_providers = + allowed_test_only_devices_by_provider_.equal_range(system_id); + for (auto it = allowed_providers.first; it != allowed_providers.second; + ++it) { + std::string allowed_provider = (*it).second; + if (allowed_provider == "*" || + allowed_provider == absl::AsciiStrToUpper(provider)) { + return true; + } + } + return false; +} + +Status DeviceStatusList::DetermineAndDeserializeServiceResponse( + const std::string& service_response, + DeviceCertificateStatusList* certificate_status_list, + std::string* serialized_certificate_status_list, + HashAlgorithm* hash_algorithm, 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, hash_algorithm, + 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, + hash_algorithm, 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, + HashAlgorithm* hash_algorithm, 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 +438,57 @@ 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, hash_algorithm, signature); +} + +Status DeviceStatusList::ExtractPublishedDevicesInfo( + const std::string& serialized_published_devices, + std::string* serialized_certificate_status_list, + HashAlgorithm* hash_algorithm, 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(); + *hash_algorithm = HashAlgorithmProtoToEnum(devices_info.hash_algorithm()); + *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,29 +502,99 @@ 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 = DrmServiceCertificate::GetDefaultDrmServiceCertificate(); if (sc == nullptr) { signed_device_certificate_status_list_request->clear(); - return Status(error_space, widevine::INVALID_SERVICE_CERTIFICATE, + return Status(error_space, widevine::SERVICE_CERTIFICATE_NOT_FOUND, "Drm service certificate is not loaded."); } const RsaPrivateKey* private_key = sc->private_key(); if (private_key == nullptr) { - return Status(error_space, widevine::INVALID_SERVICE_CERTIFICATE, + return Status(error_space, widevine::INVALID_SERVICE_PRIVATE_KEY, "Private key in the service certificate is null."); } std::string signature; - private_key->GenerateSignature(device_certificate_status_list_request, - &signature); + private_key->GenerateSignature( + device_certificate_status_list_request, + HashAlgorithmProtoToEnum(signed_request.hash_algorithm()), &signature); signed_request.set_signature(signature); signed_request.SerializeToString( 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, + HashAlgorithm* hash_algorithm, 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(); + *hash_algorithm = + HashAlgorithmProtoToEnum(signed_device_list.hash_algorithm()); + *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; +} + +Status DeviceStatusList::GetDeviceCertificateStatusBySystemId( + uint32_t system_id, DeviceCertificateStatus* device_certificate_status) { + absl::ReaderMutexLock lock(&status_map_lock_); + if (expiration_period_seconds_ && + (GetCurrentTime() > + (creation_time_seconds_ + expiration_period_seconds_))) { + return Status(error_space, EXPIRED_CERTIFICATE_STATUS_LIST, + "certificate-status-list-expired"); + } + DeviceCertificateStatus* device_cert_status = + gtl::FindOrNull(device_status_map_, system_id); + if (device_cert_status == nullptr) { + return Status(error_space, DRM_DEVICE_CERTIFICATE_UNKNOWN, + "device-certificate-status-unknown"); + } else { + *device_certificate_status = *device_cert_status; + } + return OkStatus(); +} + } // namespace widevine diff --git a/common/device_status_list.h b/common/device_status_list.h index 13782c7..7607d7f 100644 --- a/common/device_status_list.h +++ b/common/device_status_list.h @@ -12,10 +12,11 @@ #define COMMON_DEVICE_STATUS_LIST_H__ #include +#include #include -#include "base/macros.h" #include "absl/synchronization/mutex.h" +#include "common/hash_algorithm.h" #include "common/status.h" #include "protos/public/device_certificate_status.pb.h" #include "protos/public/provisioned_device_info.pb.h" @@ -35,14 +36,21 @@ 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, + HashAlgorithm hash_algorithm, 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,16 +58,22 @@ 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 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, - widevine::ProvisionedDeviceInfo* device_info); + // DEVELOPMENT_CERTIFICATE_NOT_ALLOWED + // |provider| is the service provider making the license request. + // If status is OK, a copy of the device certificate status is copied + // into |device_certificate_status|. Caller owns |device_certificate_status| + // and it must not be null. + Status GetCertStatus( + const ClientCert& client_cert, const std::string& make, + const std::string& provider, bool allow_revoked_system_id, + widevine::DeviceCertificateStatus* device_certificate_status); // Returns true if the pre-provisioning key or certificate for the specified // system ID are active (not disallowed or revoked). bool IsSystemIdActive(uint32_t system_id); @@ -69,6 +83,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 +98,72 @@ 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:*" + // AllowTestOnlyDevicesByMake(device_list_by_make); + virtual void AllowTestOnlyDevicesByMake( + const std::string& device_list_by_make); + + // Same as above, except by providers instead of by manufacturers. + // Example usage: + // const std::string device_list = "4121:YouTube,4121:AndroidVideo" + // AllowTestOnlyDevicesByProvider(device_list); + virtual void AllowTestOnlyDevicesByProvider( + const std::string& device_list_by_provider); + + // 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); + + // Returns OK if |system_id| was found in the device certificate status list + // and |device_certificate_status| is populated. If |system_id| is not found, + // this call returns an error. + virtual Status GetDeviceCertificateStatusBySystemId( + uint32_t system_id, DeviceCertificateStatus* device_certificate_status); + + // 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. + // The |hash_algorithm| is the hash algorithm used in signature. + // 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, + HashAlgorithm* hash_algorithm, std::string* signature); + /** * Constructs signed device certificate status list request string. * @@ -97,25 +173,106 @@ class DeviceStatusList { */ static Status GenerateSignedDeviceCertificateStatusListRequest( const std::string& version, + const std::string& serialized_service_certificate, std::string* signed_device_certificate_status_list_request); - private: // Returns true if the system ID is allowed to be revoked. // Caller owns |system_id|. They must not be null. bool IsRevokedSystemIdAllowed(uint32_t system_id); - absl::Mutex status_map_lock_; + // Returns true if the device, which is identified by system_id and + // device_manufacturer, is present in |allowed_test_only_devices_by_make_|. + bool IsTestOnlyDeviceAllowedByMake(uint32_t system_id, + const std::string& device_manufacturer); + + // Returns true if the device, which is identified by system_id and + // provider, is present in |allowed_test_only_devices_by_provider_|. + bool IsTestOnlyDeviceAllowedByProvider(uint32_t system_id, + const std::string& provider); + + 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 hash_algorithm + * @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, + HashAlgorithm* hash_algorithm, 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 hash_algorithm + * @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, + HashAlgorithm* hash_algorithm, 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 + * @param hash_algorithm + * @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, + HashAlgorithm* hash_algorithm, std::string* signature); + + virtual size_t allowed_test_only_devices_by_make_size() { + absl::ReaderMutexLock lock(&allowed_test_only_devices_mutex_); + return allowed_test_only_devices_by_make_.size(); + } + + virtual size_t allowed_test_only_devices_by_provider_size() { + absl::ReaderMutexLock lock(&allowed_test_only_devices_mutex_); + return allowed_test_only_devices_by_provider_.size(); + } + + mutable 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_; + std::map device_status_map_ + ABSL_GUARDED_BY(status_map_lock_); + 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); + mutable absl::Mutex allowed_test_only_devices_mutex_; + // Contains a map of 'system_id' to 'manufacturer'. If manufacturer value is + // "*", any manufacturer using that system_id is allowed. + std::multimap allowed_test_only_devices_by_make_ + ABSL_GUARDED_BY(allowed_test_only_devices_mutex_); + // Contains a map of 'system_id' to 'provider'. If provider value is "*", any + // provider using that system_id is allowed. + std::multimap allowed_test_only_devices_by_provider_ + ABSL_GUARDED_BY(allowed_test_only_devices_mutex_); + // 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..4283eb8 100644 --- a/common/device_status_list_test.cc +++ b/common/device_status_list_test.cc @@ -9,28 +9,46 @@ #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/hash_algorithm.h" +#include "common/hash_algorithm_util.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 kTestSystemId_2[] = "8242"; +const char kTestSystemId_3[] = "6556"; +const char kTestManufacturer[] = "TestManufacturer"; +const char kTestProvider[] = "TestProvider"; +const char kRevokedManufacturer[] = "RevokedManufacturer"; +const widevine::HashAlgorithm kHashAlgorithm = + widevine::HashAlgorithm::kSha256; +} // namespace + namespace widevine { -using ::testing::_; using ::testing::Return; using ::testing::ReturnRef; -using ::testing::ReturnRefOfCopy; const uint32_t kValidCertSystemId = 100; const uint32_t kRevokedCertSystemId = 101; @@ -42,28 +60,47 @@ 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"; +const char kRevokedDeviceModel[] = "device-model-revoked"; const char kTestPreprovKey[] = "00112233445566778899aabbccddeeff"; const uint32_t kStatusListCreationTime = 17798001; const uint32_t kDefaultExpirePeriod = 0; +const bool kDenyRevokedDevice = false; +const bool kAllowRevokedDevice = true; -class MockCertificateClientCert : public CertificateClientCert { +class MockClientCert : public ClientCert { public: - MockCertificateClientCert() {} - MOCK_CONST_METHOD0(system_id, uint32_t()); - MOCK_CONST_METHOD0(signer_serial_number, std::string &()); - MOCK_CONST_METHOD0(signer_creation_time_seconds, uint32_t()); - MOCK_CONST_METHOD0(type, ClientIdentification::TokenType()); - MOCK_CONST_METHOD0(signed_by_provisioner, bool()); -}; - -class MockKeyboxClientCert : public KeyboxClientCert { - public: - MockKeyboxClientCert() {} - MOCK_CONST_METHOD0(system_id, uint32_t()); - MOCK_CONST_METHOD0(type, ClientIdentification::TokenType()); + MockClientCert() {} + ~MockClientCert() override {} + MOCK_METHOD(uint32_t, system_id, (), (const, override)); + MOCK_METHOD(std::string &, signer_serial_number, (), (const, override)); + MOCK_METHOD(uint32_t, signer_creation_time_seconds, (), (const, override)); + MOCK_METHOD(ClientIdentification::TokenType, type, (), (const, override)); + MOCK_METHOD(const std::string &, encrypted_unique_id, (), (const, override)); + MOCK_METHOD(const std::string &, unique_id_hash, (), (const, override)); + MOCK_METHOD(bool, signed_by_provisioner, (), (const, override)); + MOCK_METHOD(Status, VerifySignature, + (const std::string &message, HashAlgorithm hash_algorithm, + const std::string &signature, ProtocolVersion protocol_version), + (const, override)); + MOCK_METHOD(void, GenerateSigningKey, + (const std::string &message, ProtocolVersion protocol_version), + (override)); + MOCK_METHOD(const std::string &, serial_number, (), (const, override)); + MOCK_METHOD(const std::string &, key, (), (const, override)); + MOCK_METHOD(SignedMessage::SessionKeyType, key_type, (), (const, override)); + MOCK_METHOD(bool, using_dual_certificate, (), (const override)); + MOCK_METHOD(const std::string &, service_id, (), (const, override)); + MOCK_METHOD(const std::string &, encrypted_key, (), (const, override)); + MOCK_METHOD(const std::string &, signing_key, (), (const, override)); + MOCK_METHOD(Status, SystemIdUnknownError, (), (const, override)); + MOCK_METHOD(Status, SystemIdRevokedError, (), (const, override)); }; class DeviceStatusListTest : public ::testing::Test { @@ -79,11 +116,16 @@ 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(); cert_status->mutable_device_info()->set_system_id(kRevokedCertSystemId); cert_status->set_drm_serial_number(kRevokedSerialNumber); + cert_status->mutable_device_info()->set_model(kRevokedDeviceModel); cert_status->set_status(DeviceCertificateStatus::STATUS_REVOKED); // Device cert with status REVOKED ALLOWED DEVICE. @@ -102,36 +144,89 @@ 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_, + kHashAlgorithm, + &cert_status_list_signature_)); - ASSERT_TRUE(root_key->GenerateSignature( - signed_cert_status_list_.certificate_status_list(), - signed_cert_status_list_.mutable_signature())); - ASSERT_TRUE( - signed_cert_status_list_.SerializeToString(&serialized_status_list_)); + // 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_, kHashAlgorithm, + cert_status_list_signature_, kDefaultExpirePeriod)); + } - ASSERT_EQ(OkStatus(), device_status_list_.UpdateStatusList( - test_keys_.public_test_key_1_3072_bits(), - serialized_status_list_, kDefaultExpirePeriod)); + void GenerateTrivialValidStatusList(std::string *serialized_cert_status_list, + HashAlgorithm hash_algorithm, + 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(root_key->GenerateSignature(*serialized_cert_status_list, + hash_algorithm, signature)); + } + + int AllowedTestOnlyDevicesByMakeSize() { + return device_status_list_.allowed_test_only_devices_by_make_size(); + } + + int AllowedTestOnlyDevicesByProviderSize() { + return device_status_list_.allowed_test_only_devices_by_provider_size(); + } + + bool IsTestOnlyDeviceAllowedByMake(uint32_t system_id, + const std::string &make) { + return device_status_list_.IsTestOnlyDeviceAllowedByMake(system_id, make); + } + + bool IsTestOnlyDeviceAllowedByProvider(uint32_t system_id, + const std::string &provider) { + return device_status_list_.IsTestOnlyDeviceAllowedByProvider(system_id, + provider); + } + + 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; + DeviceCertificateStatus device_certificate_status; + MockClientCert valid_client_cert; std::string valid_drm_serial_number(kValidSerialNumber); EXPECT_CALL(valid_client_cert, type()) .WillRepeatedly(Return(ClientIdentification::DRM_DEVICE_CERTIFICATE)); @@ -139,13 +234,15 @@ TEST_F(DeviceStatusListTest, CheckForValidAndRevokedCert) { .WillRepeatedly(Return(kValidCertSystemId)); 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)); + EXPECT_EQ(OkStatus(), device_status_list_.GetCertStatus( + valid_client_cert, kTestManufacturer, kTestProvider, + kDenyRevokedDevice, &device_certificate_status)); + ProvisionedDeviceInfo device_info = device_certificate_status.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 +250,26 @@ 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_CALL(revoked_client_cert, SystemIdRevokedError()) + .WillRepeatedly( + Return(Status(error_space, DRM_DEVICE_CERTIFICATE_REVOKED, ""))); + EXPECT_EQ( + DRM_DEVICE_CERTIFICATE_REVOKED, + device_status_list_ + .GetCertStatus(revoked_client_cert, kTestManufacturer, kTestProvider, + kDenyRevokedDevice, &device_certificate_status) + .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, kTestProvider, kDenyRevokedDevice, + &device_certificate_status)); } -TEST_F(DeviceStatusListTest, TestOnlyCertAllowed) { - ProvisionedDeviceInfo device_info; - MockCertificateClientCert test_only_client_cert; +TEST_F(DeviceStatusListTest, TestOnlyCertNotAllowed) { + DeviceCertificateStatus device_certificate_status; + 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)); @@ -173,15 +277,29 @@ TEST_F(DeviceStatusListTest, TestOnlyCertAllowed) { .WillRepeatedly(Return(kTestOnlyCertSystemId)); EXPECT_CALL(test_only_client_cert, signer_serial_number()) .WillRepeatedly(ReturnRef(test_only_drm_serial_number)); - EXPECT_EQ( - DEVELOPMENT_CERTIFICATE_NOT_ALLOWED, - device_status_list_.GetCertStatus(test_only_client_cert, &device_info) - .error_code()); + EXPECT_EQ(DEVELOPMENT_CERTIFICATE_NOT_ALLOWED, + device_status_list_ + .GetCertStatus(test_only_client_cert, kTestManufacturer, + kTestProvider, kDenyRevokedDevice, + &device_certificate_status) + .error_code()); } -TEST_F(DeviceStatusListTest, TestOnlyCertNotAllowed) { - ProvisionedDeviceInfo device_info; - MockCertificateClientCert test_only_client_cert; +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) { + DeviceCertificateStatus device_certificate_status; + 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,39 +308,96 @@ 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, kTestProvider, + kDenyRevokedDevice, &device_certificate_status)); } -TEST_F(DeviceStatusListTest, ValidAndUnknownKeybox) { +TEST_F(DeviceStatusListTest, RevokedSystemIdAllowed) { + DeviceCertificateStatus device_certificate_status; + MockClientCert revoked_client_cert; + std::string revoked_drm_serial_number(kRevokedSerialNumber); + EXPECT_CALL(revoked_client_cert, type()) + .WillRepeatedly(Return(ClientIdentification::DRM_DEVICE_CERTIFICATE)); + EXPECT_CALL(revoked_client_cert, system_id()) + .WillRepeatedly(Return(kRevokedCertSystemId)); + EXPECT_CALL(revoked_client_cert, signer_serial_number()) + .WillRepeatedly(ReturnRef(revoked_drm_serial_number)); + EXPECT_EQ(OkStatus(), + device_status_list_.GetCertStatus( + revoked_client_cert, kRevokedManufacturer, kTestProvider, + kAllowRevokedDevice, &device_certificate_status)); +} + +// Test case where the Certificate status is set to Valid. +TEST_F(DeviceStatusListTest, ValidKeybox) { std::multimap preprov_keys; preprov_keys.insert(std::make_pair(kValidCertSystemId, kTestPreprovKey)); KeyboxClientCert::SetPreProvisioningKeys(preprov_keys); + DeviceCertificateStatus device_certificate_status; + MockClientCert valid_client_keybox; - // Test case where the Certificate status is set to Valid. - ProvisionedDeviceInfo device_info; - MockKeyboxClientCert valid_client_keybox; std::string valid_drm_serial_number(kValidSerialNumber); EXPECT_CALL(valid_client_keybox, type()) .WillRepeatedly(Return(ClientIdentification::KEYBOX)); EXPECT_CALL(valid_client_keybox, system_id()) .WillRepeatedly(Return(kValidCertSystemId)); - EXPECT_EQ(OkStatus(), device_status_list_.GetCertStatus(valid_client_keybox, - &device_info)); - EXPECT_TRUE(device_info.has_model()); + EXPECT_EQ(OkStatus(), + device_status_list_.GetCertStatus( + valid_client_keybox, kTestManufacturer, kTestProvider, + kDenyRevokedDevice, &device_certificate_status)); + ProvisionedDeviceInfo device_info = device_certificate_status.device_info(); + ASSERT_TRUE(device_info.has_model()); EXPECT_EQ(kDeviceModel, device_info.model()); +} + +// Test case where the keybox was not loaded into the pre-prov list. +TEST_F(DeviceStatusListTest, UnknownKeybox) { + std::multimap preprov_keys; + preprov_keys.insert(std::make_pair(kValidCertSystemId, kTestPreprovKey)); + KeyboxClientCert::SetPreProvisioningKeys(preprov_keys); + DeviceCertificateStatus device_certificate_status; + MockClientCert unknown_client_keybox; - MockKeyboxClientCert unknown_client_keybox; EXPECT_CALL(unknown_client_keybox, type()) .WillRepeatedly(Return(ClientIdentification::KEYBOX)); EXPECT_CALL(unknown_client_keybox, system_id()) .WillRepeatedly(Return(kUnknownSystemId)); - EXPECT_EQ( - UNSUPPORTED_SYSTEM_ID, - device_status_list_.GetCertStatus(unknown_client_keybox, &device_info) - .error_code()); - EXPECT_TRUE(device_info.has_model()); - EXPECT_EQ(kDeviceModel, device_info.model()); + EXPECT_CALL(unknown_client_keybox, SystemIdUnknownError()).Times(0); + EXPECT_EQ(OkStatus(), + device_status_list_.GetCertStatus( + unknown_client_keybox, kTestManufacturer, kTestProvider, + kDenyRevokedDevice, &device_certificate_status)); + ProvisionedDeviceInfo device_info = device_certificate_status.device_info(); + ASSERT_FALSE(device_info.has_model()); +} + +// Test case where the keybox was loaded into the pre-prov list but it's +// certificate status is REVOKED. +TEST_F(DeviceStatusListTest, RevokedKeybox) { + std::multimap preprov_keys; + preprov_keys.insert(std::make_pair(kRevokedCertSystemId, kTestPreprovKey)); + KeyboxClientCert::SetPreProvisioningKeys(preprov_keys); + DeviceCertificateStatus device_certificate_status; + MockClientCert revoked_client_keybox; + + EXPECT_CALL(revoked_client_keybox, type()) + .WillRepeatedly(Return(ClientIdentification::KEYBOX)); + EXPECT_CALL(revoked_client_keybox, system_id()) + .WillRepeatedly(Return(kRevokedCertSystemId)); + EXPECT_CALL(revoked_client_keybox, SystemIdRevokedError()) + .WillRepeatedly( + Return(Status(error_space, DRM_DEVICE_CERTIFICATE_REVOKED, ""))); + EXPECT_EQ(DRM_DEVICE_CERTIFICATE_REVOKED, + device_status_list_ + .GetCertStatus(revoked_client_keybox, kTestManufacturer, + kTestProvider, kDenyRevokedDevice, + &device_certificate_status) + .error_code()); + ProvisionedDeviceInfo device_info = device_certificate_status.device_info(); + ASSERT_TRUE(device_info.has_model()); + EXPECT_EQ(kRevokedDeviceModel, device_info.model()); } TEST_F(DeviceStatusListTest, SignerSerialNumberMismatch) { @@ -230,8 +405,8 @@ TEST_F(DeviceStatusListTest, SignerSerialNumberMismatch) { // Test case where the signer certificate is older than the current status // list. - MockCertificateClientCert older_client_cert; - ProvisionedDeviceInfo device_info; + MockClientCert older_client_cert; + DeviceCertificateStatus device_certificate_status; std::string mismatch_drm_serial_number(kMismatchSerialNumber); EXPECT_CALL(older_client_cert, type()) .WillRepeatedly(Return(ClientIdentification::DRM_DEVICE_CERTIFICATE)); @@ -241,21 +416,27 @@ 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, kTestProvider, + kDenyRevokedDevice, &device_certificate_status) + .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)); + EXPECT_EQ(OkStatus(), device_status_list_.GetCertStatus( + older_client_cert, kTestManufacturer, kTestProvider, + kDenyRevokedDevice, &device_certificate_status)); + ProvisionedDeviceInfo device_info = device_certificate_status.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; + device_certificate_status.Clear(); + 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 +445,21 @@ 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_CALL(newer_client_cert1, SystemIdUnknownError()) + .WillRepeatedly( + Return(Status(error_space, DRM_DEVICE_CERTIFICATE_UNKNOWN, ""))); + EXPECT_EQ( + DRM_DEVICE_CERTIFICATE_UNKNOWN, + device_status_list_ + .GetCertStatus(newer_client_cert1, kTestManufacturer, kTestProvider, + kDenyRevokedDevice, &device_certificate_status) + .error_code()); // Test case where the signer certificate is newer than the current status // list, and unknown devices are not allowed. + device_certificate_status.Clear(); 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,31 +468,37 @@ 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_CALL(newer_client_cert2, SystemIdUnknownError()) + .WillRepeatedly( + Return(Status(error_space, DRM_DEVICE_CERTIFICATE_UNKNOWN, ""))); + EXPECT_EQ( + DRM_DEVICE_CERTIFICATE_UNKNOWN, + device_status_list_ + .GetCertStatus(newer_client_cert2, kTestManufacturer, kTestProvider, + kDenyRevokedDevice, &device_certificate_status) + .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_, kHashAlgorithm, + 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_, kHashAlgorithm, + cert_status_list_signature_, 0) .error_code()); } class MockDeviceStatusList : public DeviceStatusList { public: - MOCK_CONST_METHOD0(GetCurrentTime, uint32_t()); + MOCK_METHOD(uint32_t, GetCurrentTime, (), (const, override)); }; TEST_F(DeviceStatusListTest, ExpiredStatusListOnSet) { @@ -315,11 +509,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_, kHashAlgorithm, + 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_, kHashAlgorithm, + cert_status_list_signature_, 100) .error_code()); } @@ -332,10 +528,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_, kHashAlgorithm, + cert_status_list_signature_, 100)); - ProvisionedDeviceInfo device_info; - MockCertificateClientCert valid_client_cert; + DeviceCertificateStatus device_certificate_status; + 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 +542,15 @@ 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, kTestProvider, + kDenyRevokedDevice, &device_certificate_status)); 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, kTestProvider, + kDenyRevokedDevice, &device_certificate_status) .error_code()); } @@ -373,4 +573,343 @@ TEST_F(DeviceStatusListTest, IsSystemIdActive) { device_status_list_.IsSystemIdActive(kRevokedAllowedDeviceCertSystemId)); } +TEST_F(DeviceStatusListTest, IsTestOnlyDeviceAllowedByMake) { + const char kTestManufacturer_AA[] = "AA"; + const char kTestManufacturer_AAA[] = "AAA"; + const char kTestManufacturer_BBB[] = "BBB"; + const char kTestManufacturer_BbB[] = "BbB"; + const char kTestManufacturer_bbb[] = "bbb"; + const char kTestManufacturer_CCC[] = "CCC"; + const char kTestManufacturer_DDD[] = "AAA"; + std::string allowed_device_list = + std::string(kTestSystemId_1) + ":" + std::string(kTestManufacturer_AA); + allowed_device_list += "," + std::string(kTestSystemId_2) + ":" + + std::string(kTestManufacturer_BBB); + allowed_device_list += "," + std::string(kTestSystemId_3) + ":"; + allowed_device_list += ", " + std::string(kTestSystemId_1) + ":" + + std::string(kTestManufacturer_AAA); + device_status_list_.AllowTestOnlyDevicesByMake(allowed_device_list); + EXPECT_EQ(4, AllowedTestOnlyDevicesByMakeSize()); + // Verify that device with system_id = kTestSystemId_1 and + // manufacturer AA is allowed. + EXPECT_TRUE(IsTestOnlyDeviceAllowedByMake(std::stoi(kTestSystemId_1), + kTestManufacturer_AA)); + // Verify that device with system_id = kTestSystemId_1 and + // manufacturer AAA is allowed. + EXPECT_TRUE(IsTestOnlyDeviceAllowedByMake(std::stoi(kTestSystemId_1), + kTestManufacturer_AAA)); + // Verify that device with system_id = kTestSystemId_2 and + // manufacturer AAA is not allowed. + // This is because this combination is not in the allowed list. + EXPECT_FALSE(IsTestOnlyDeviceAllowedByMake(std::stoi(kTestSystemId_2), + kTestManufacturer_AAA)); + // Verify that device with system_id = kTestSystemId_2 and + // manufacturer BBB is allowed. + EXPECT_TRUE(IsTestOnlyDeviceAllowedByMake(std::stoi(kTestSystemId_2), + kTestManufacturer_BBB)); + // Verifes that device with mixed case succeeds. + EXPECT_TRUE(IsTestOnlyDeviceAllowedByMake(std::stoi(kTestSystemId_2), + kTestManufacturer_BbB)); + EXPECT_TRUE(IsTestOnlyDeviceAllowedByMake(std::stoi(kTestSystemId_2), + kTestManufacturer_bbb)); + // 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(IsTestOnlyDeviceAllowedByMake(std::stoi(kTestSystemId_3), + kTestManufacturer_CCC)); + EXPECT_TRUE(IsTestOnlyDeviceAllowedByMake(std::stoi(kTestSystemId_3), + kTestManufacturer_DDD)); + uint32_t unknown_system_id = 7890; + // Verify that device with system_id = unknown_system_id and + // manufacturer CCC is not allowed. + EXPECT_FALSE( + IsTestOnlyDeviceAllowedByMake(unknown_system_id, kTestManufacturer_CCC)); +} + +TEST_F(DeviceStatusListTest, IsTestOnlyDeviceAllowedByProvider) { + const char kTestProvider_AA[] = "AA"; + const char kTestProvider_AAA[] = "AAA"; + const char kTestProvider_BBB[] = "BBB"; + const char kTestProvider_BbB[] = "BbB"; + const char kTestProvider_bbb[] = "bbb"; + const char kTestProvider_CCC[] = "CCC"; + std::string allowed_device_list = + std::string(kTestSystemId_1) + ":" + std::string(kTestProvider_AA); + allowed_device_list += + "," + std::string(kTestSystemId_2) + ":" + std::string(kTestProvider_BBB); + allowed_device_list += "," + std::string(kTestSystemId_3) + ":"; + allowed_device_list += ", " + std::string(kTestSystemId_1) + ":" + + std::string(kTestProvider_AAA); + device_status_list_.AllowTestOnlyDevicesByProvider(allowed_device_list); + EXPECT_EQ(4, AllowedTestOnlyDevicesByProviderSize()); + // Verify that device with system_id = kTestSystemId_1 and + // provider AA is allowed. + EXPECT_TRUE(IsTestOnlyDeviceAllowedByProvider(std::stoi(kTestSystemId_1), + kTestProvider_AA)); + // Verify that device with system_id = kTestSystemId_1 and + // provider AAA is allowed. + EXPECT_TRUE(IsTestOnlyDeviceAllowedByProvider(std::stoi(kTestSystemId_1), + kTestProvider_AAA)); + // Verify that device with system_id = kTestSystemId_2 and + // provider AAA is not allowed. + // This is because this combination is not 'whitelisted'. + EXPECT_FALSE(IsTestOnlyDeviceAllowedByProvider(std::stoi(kTestSystemId_2), + kTestProvider_AAA)); + // Verify that device with system_id = kTestSystemId_2 and + // provider BBB is allowed. + EXPECT_TRUE(IsTestOnlyDeviceAllowedByProvider(std::stoi(kTestSystemId_2), + kTestProvider_BBB)); + // Verifes that device with mixed case succeeds. + EXPECT_TRUE(IsTestOnlyDeviceAllowedByProvider(std::stoi(kTestSystemId_2), + kTestProvider_BbB)); + EXPECT_TRUE(IsTestOnlyDeviceAllowedByProvider(std::stoi(kTestSystemId_2), + kTestProvider_bbb)); + // Verify that device with system_id = kTestSystemId_3 and + // any provider is allowed. This checks that any provider is + // allowed for this system_id. + EXPECT_TRUE(IsTestOnlyDeviceAllowedByProvider(std::stoi(kTestSystemId_3), + kTestProvider_CCC)); + EXPECT_TRUE(IsTestOnlyDeviceAllowedByProvider(std::stoi(kTestSystemId_3), + kTestProvider_AAA)); + uint32_t unknown_system_id = 7890; + // Verify that device with system_id = unknown_system_id and + // provider CCC is not allowed. + EXPECT_FALSE( + IsTestOnlyDeviceAllowedByProvider(unknown_system_id, kTestProvider_CCC)); +} + +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(), + HashAlgorithmProtoToEnum(published_devices.hash_algorithm()), + 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; + HashAlgorithm hash_algorithm; + ASSERT_EQ(OkStatus(), + DeviceStatusList::DetermineAndDeserializeServiceResponse( + serialized_published_devices, &actual_cert_status_list, + &actual_serialized_cert_status_list, &hash_algorithm, + &actual_signature)); + EXPECT_EQ(published_devices.device_certificate_status_list(), + actual_serialized_cert_status_list); + EXPECT_EQ(published_devices.signature(), actual_signature); + EXPECT_EQ(HashAlgorithmProtoToEnum(published_devices.hash_algorithm()), + hash_algorithm); + + 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; + SignedDeviceCertificateStatusList legacy_signed_cert_status_list; + GenerateTrivialValidStatusList( + &serialized_cert_status_list, + HashAlgorithmProtoToEnum(legacy_signed_cert_status_list.hash_algorithm()), + &signature); + *(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; + HashAlgorithm hash_algorithm; + ASSERT_EQ(OkStatus(), + DeviceStatusList::DetermineAndDeserializeServiceResponse( + server_response, &actual_cert_status_list, + &actual_serialized_cert_status_list, &hash_algorithm, + &actual_signature)); + EXPECT_EQ(serialized_cert_status_list, actual_serialized_cert_status_list); + EXPECT_EQ(signature, actual_signature); + EXPECT_EQ( + HashAlgorithmProtoToEnum(legacy_signed_cert_status_list.hash_algorithm()), + hash_algorithm); + + 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; + SignedDeviceCertificateStatusList legacy_signed_cert_status_list; + GenerateTrivialValidStatusList( + &serialized_cert_status_list, + HashAlgorithmProtoToEnum(legacy_signed_cert_status_list.hash_algorithm()), + &signature); + *(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; + HashAlgorithm hash_algorithm; + 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, + &hash_algorithm, &actual_signature)); + EXPECT_EQ(serialized_cert_status_list, actual_serialized_cert_status_list); + EXPECT_EQ(signature, actual_signature); + EXPECT_EQ( + HashAlgorithmProtoToEnum(legacy_signed_cert_status_list.hash_algorithm()), + hash_algorithm); + + 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; + SignedDeviceCertificateStatusList legacy_signed_cert_status_list; + GenerateTrivialValidStatusList( + &serialized_cert_status_list, + HashAlgorithmProtoToEnum(legacy_signed_cert_status_list.hash_algorithm()), + &signature); + *(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; + HashAlgorithm hash_algorithm; + 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, + &hash_algorithm, &actual_signature)); + EXPECT_EQ(serialized_cert_status_list, actual_serialized_cert_status_list); + EXPECT_EQ(signature, actual_signature); + EXPECT_EQ( + HashAlgorithmProtoToEnum(legacy_signed_cert_status_list.hash_algorithm()), + hash_algorithm); + + 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, + GetDeviceCertificateStatusBySystemIdExpiredDeviceCertificateStatusList) { + MockDeviceStatusList mock_device_status_list; + EXPECT_CALL(mock_device_status_list, GetCurrentTime()) + .Times(3) + .WillOnce(Return(kStatusListCreationTime + 100)) + .WillOnce(Return(kStatusListCreationTime + 100)) + .WillOnce(Return(kStatusListCreationTime + 101)); + EXPECT_EQ(OkStatus(), mock_device_status_list.UpdateStatusList( + test_keys_.public_test_key_1_3072_bits(), + serialized_cert_status_list_, kHashAlgorithm, + cert_status_list_signature_, 100)); + DeviceCertificateStatus device_certificate_status; + EXPECT_EQ(OkStatus(), + mock_device_status_list.GetDeviceCertificateStatusBySystemId( + kValidCertSystemId, &device_certificate_status)); + EXPECT_EQ(EXPIRED_CERTIFICATE_STATUS_LIST, + mock_device_status_list + .GetDeviceCertificateStatusBySystemId( + kValidCertSystemId, &device_certificate_status) + .error_code()); +} + +TEST_F(DeviceStatusListTest, + GetDeviceCertificateStatusBySystemIdUnknownDevice) { + DeviceCertificateStatus device_certificate_status; + uint32_t unknown_system_id = 2000; + EXPECT_EQ(DRM_DEVICE_CERTIFICATE_UNKNOWN, + device_status_list_ + .GetDeviceCertificateStatusBySystemId( + unknown_system_id, &device_certificate_status) + .error_code()); +} + +TEST_F(DeviceStatusListTest, GetDeviceCertificateStatusBySystemIdOk) { + DeviceCertificateStatus device_certificate_status; + EXPECT_OK(device_status_list_.GetDeviceCertificateStatusBySystemId( + kValidCertSystemId, &device_certificate_status)); +} + } // namespace widevine diff --git a/common/drm_root_certificate.cc b/common/drm_root_certificate.cc index b211a96..eac92c6 100644 --- a/common/drm_root_certificate.cc +++ b/common/drm_root_certificate.cc @@ -16,9 +16,13 @@ #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/hash_algorithm.h" +#include "common/hash_algorithm_util.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 +37,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 +259,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 +281,58 @@ 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, + HashAlgorithm hash_algorithm, 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, hash_algorithm, 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 +354,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,14 +425,16 @@ 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"); } - if (!public_key->VerifySignature(signed_root_cert.drm_certificate(), - signed_root_cert.signature())) { + if (!public_key->VerifySignature( + signed_root_cert.drm_certificate(), + HashAlgorithmProtoToEnum(signed_root_cert.hash_algorithm()), + signed_root_cert.signature())) { return Status(error_space, INVALID_DRM_CERTIFICATE, "invalid-root-certificate-signature"); } @@ -424,10 +451,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 +500,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,24 +512,34 @@ 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_); + HashAlgorithmProtoToEnum(signed_cert.hash_algorithm()), + 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; } @@ -508,19 +547,22 @@ Status DrmRootCertificate::VerifySignatures( if (use_cache) { status = signature_cache_->VerifySignature( signed_cert.drm_certificate(), cert_serial_number, - signed_cert.signature(), signer.public_key(), signer.serial_number()); + HashAlgorithmProtoToEnum(signed_cert.hash_algorithm()), + 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"); } - if (!signer_public_key->VerifySignature(signed_cert.drm_certificate(), - signed_cert.signature())) { + if (!signer_public_key->VerifySignature( + signed_cert.drm_certificate(), + HashAlgorithmProtoToEnum(signed_cert.hash_algorithm()), + signed_cert.signature())) { return Status(error_space, INVALID_SIGNATURE, "cache-miss-invalid-signature"); } 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..a671885 100644 --- a/common/drm_root_certificate_test.cc +++ b/common/drm_root_certificate_test.cc @@ -13,10 +13,16 @@ #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/hash_algorithm.h" +#include "common/hash_algorithm_util.h" #include "common/rsa_key.h" #include "common/rsa_test_keys.h" #include "common/test_drm_certificates.h" @@ -25,6 +31,7 @@ #include "protos/public/signed_drm_certificate.pb.h" using google::protobuf::util::MessageDifferencer; +using ::testing::Values; namespace widevine { @@ -80,76 +87,222 @@ 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, + HashAlgorithm hash_algorithm, + 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, + HashAlgorithm hash_algorithm, + std::string* signature) const override { + return private_key_->GenerateSignature(message, hash_algorithm, 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 GenerateSignedDrmCertificate() { - SignedDrmCertificate* current_sc(&signed_drm_certificate_); - ASSERT_TRUE(drm_certificates_[2].SerializeToString( - current_sc->mutable_drm_certificate())); - ASSERT_TRUE(private_keys_[1]->GenerateSignature( - current_sc->drm_certificate(), current_sc->mutable_signature())); + 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()); - current_sc = current_sc->mutable_signer(); - ASSERT_TRUE(drm_certificates_[1].SerializeToString( - current_sc->mutable_drm_certificate())); - ASSERT_TRUE(private_keys_[0]->GenerateSignature( - current_sc->drm_certificate(), current_sc->mutable_signature())); + 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()); - current_sc = current_sc->mutable_signer(); - ASSERT_TRUE(drm_certificates_[0].SerializeToString( - current_sc->mutable_drm_certificate())); - ASSERT_TRUE(private_keys_[0]->GenerateSignature( - current_sc->drm_certificate(), current_sc->mutable_signature())); + 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()); } - RsaTestKeys test_keys_; - std::vector> private_keys_; + 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_); + drm_certificates_[kClientKey].set_algorithm( + private_keys_[kClientKey]->algorithm()); + ASSERT_TRUE(drm_certificates_[kClientKey].SerializeToString( + current_sc->mutable_drm_certificate())); + ASSERT_TRUE(private_keys_[kInterMediateKey]->GenerateSignature( + current_sc->drm_certificate(), + HashAlgorithmProtoToEnum(current_sc->hash_algorithm()), + current_sc->mutable_signature())); + + current_sc = current_sc->mutable_signer(); + drm_certificates_[kInterMediateKey].set_algorithm( + private_keys_[kInterMediateKey]->algorithm()); + ASSERT_TRUE(drm_certificates_[kInterMediateKey].SerializeToString( + current_sc->mutable_drm_certificate())); + ASSERT_TRUE(private_keys_[kDrmRootKey]->GenerateSignature( + current_sc->drm_certificate(), + HashAlgorithmProtoToEnum(current_sc->hash_algorithm()), + current_sc->mutable_signature())); + + current_sc = current_sc->mutable_signer(); + drm_certificates_[kDrmRootKey].set_algorithm( + private_keys_[kDrmRootKey]->algorithm()); + ASSERT_TRUE(drm_certificates_[kDrmRootKey].SerializeToString( + current_sc->mutable_drm_certificate())); + ASSERT_TRUE(private_keys_[kDrmRootKey]->GenerateSignature( + current_sc->drm_certificate(), + HashAlgorithmProtoToEnum(current_sc->hash_algorithm()), + current_sc->mutable_signature())); + } + + 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 +314,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 +329,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 +338,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 +347,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 +356,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 +364,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 +373,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 +382,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 +392,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..2a91fd9 100644 --- a/common/drm_service_certificate.cc +++ b/common/drm_service_certificate.cc @@ -13,7 +13,7 @@ #include #include "glog/logging.h" -#include "base/thread_annotations.h" +#include "absl/base/thread_annotations.h" #include "absl/strings/escaping.h" #include "absl/synchronization/mutex.h" #include "util/gtl/map_util.h" @@ -22,6 +22,7 @@ #include "common/drm_root_certificate.h" #include "common/error_space.h" #include "common/rsa_util.h" +#include "common/status.h" #include "protos/public/client_identification.pb.h" #include "protos/public/drm_certificate.pb.h" #include "protos/public/errors.pb.h" @@ -44,16 +45,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 +97,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 +129,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 +188,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(); @@ -185,60 +216,22 @@ Status DrmServiceCertificate::DecryptClientIdentification( const EncryptedClientIdentification& encrypted_client_id, ClientIdentification* client_id) { DCHECK(client_id); - if (encrypted_client_id.service_certificate_serial_number().empty()) { + std::string serialized_client_id; + Status status = DecryptEncryptedPayload( + encrypted_client_id.service_certificate_serial_number(), + encrypted_client_id.provider_id(), + encrypted_client_id.encrypted_client_id(), + encrypted_client_id.encrypted_client_id_iv(), + encrypted_client_id.encrypted_privacy_key(), &serialized_client_id); + if (status.error_code() == error::INVALID_ARGUMENT) { return Status(error_space, INVALID_ENCRYPTED_CLIENT_IDENTIFICATION, - "missing-service-certificate-serial-number"); + status.error_message()); } - if (encrypted_client_id.provider_id().empty()) { - return Status(error_space, INVALID_ENCRYPTED_CLIENT_IDENTIFICATION, - "missing-service-id"); - } - if (encrypted_client_id.encrypted_client_id().empty()) { - return Status(error_space, INVALID_ENCRYPTED_CLIENT_IDENTIFICATION, - "missing-encrypted-client-id"); - } - if (encrypted_client_id.encrypted_client_id_iv().empty()) { - return Status(error_space, INVALID_ENCRYPTED_CLIENT_IDENTIFICATION, - "missing-encrypted-client-id-iv"); - } - if (encrypted_client_id.encrypted_privacy_key().empty()) { - return Status(error_space, INVALID_ENCRYPTED_CLIENT_IDENTIFICATION, - "missing-encrypted-privacy-key"); - } - std::string privacy_key; - std::string provider_id; - const DrmServiceCertificate* cert = GetDrmServiceCertificate( - encrypted_client_id.service_certificate_serial_number()); - if (!cert) { - return Status( - error_space, SERVICE_CERTIFICATE_NOT_FOUND, - "service-certificate-not-found (SN " + - absl::BytesToHexString( - encrypted_client_id.service_certificate_serial_number()) + - ")"); - } - if (!cert->private_key()->Decrypt(encrypted_client_id.encrypted_privacy_key(), - &privacy_key)) { - return Status(error_space, INVALID_ENCRYPTED_CLIENT_IDENTIFICATION, - "privacy-key-decryption-failed"); - } - if (cert->provider_id() != encrypted_client_id.provider_id()) { - return Status(error_space, SERVICE_CERTIFICATE_NOT_FOUND, - std::string("provider-id-mismatch (") + cert->provider_id() + - " / " + encrypted_client_id.provider_id() + ")"); - } - std::string serialized_client_id(crypto_util::DecryptAesCbc( - privacy_key, encrypted_client_id.encrypted_client_id_iv(), - encrypted_client_id.encrypted_client_id())); - if (serialized_client_id.empty()) { - return Status(error_space, INVALID_ENCRYPTED_CLIENT_IDENTIFICATION, - "client-id-decryption-failed"); - } - if (!client_id->ParseFromString(serialized_client_id)) { + if (status.ok() && !client_id->ParseFromString(serialized_client_id)) { return Status(error_space, INVALID_ENCRYPTED_CLIENT_IDENTIFICATION, "client-id-parse-failed"); } - return OkStatus(); + return status; } void DrmServiceCertificate::ResetServiceCertificates() { @@ -271,6 +264,67 @@ Status DrmServiceCertificate::ValidateDrmServiceCertificate() { return OkStatus(); } +Status DrmServiceCertificate::DecryptLicenseChallenge( + const EncryptedLicenseRequest& encrypted_license_request, + std::string* license_challenge) { + Status status = DecryptEncryptedPayload( + encrypted_license_request.service_certificate_serial_number(), + encrypted_license_request.provider_id(), + encrypted_license_request.encrypted_license_request(), + encrypted_license_request.encrypted_license_request_iv(), + encrypted_license_request.encrypted_privacy_key(), license_challenge); + if (status.error_code() == error::INVALID_ARGUMENT) { + return Status(error_space, INVALID_ENCRYPTED_LICENSE_CHALLENGE, + status.error_message()); + } + return status; +} + +Status DrmServiceCertificate::DecryptEncryptedPayload( + const std::string& service_certificate_serial_number, + const std::string& provider_id, const std::string& encrypted_payload, + const std::string& iv, const std::string& encrypted_privacy_key, + std::string* payload) { + if (payload == nullptr) { + return Status(error::INVALID_ARGUMENT, "null-payload"); + } + payload->clear(); + if (service_certificate_serial_number.empty()) { + return Status(error::INVALID_ARGUMENT, + "missing-service-certificate-serial-number"); + } else if (provider_id.empty()) { + return Status(error::INVALID_ARGUMENT, "missing-service-id"); + } else if (encrypted_payload.empty()) { + return Status(error::INVALID_ARGUMENT, "missing-encrypted-payload"); + } else if (iv.empty()) { + return Status(error::INVALID_ARGUMENT, "missing-iv"); + } else if (encrypted_privacy_key.empty()) { + return Status(error::INVALID_ARGUMENT, "missing-privacy-key"); + } + std::string privacy_key; + const DrmServiceCertificate* cert = + GetDrmServiceCertificateBySerialNumber(service_certificate_serial_number); + if (!cert) { + return Status( + error_space, SERVICE_CERTIFICATE_NOT_FOUND, + "service-certificate-not-found (SN " + + absl::BytesToHexString(service_certificate_serial_number) + ")"); + } + if (!cert->private_key()->Decrypt(encrypted_privacy_key, &privacy_key)) { + return Status(error::INVALID_ARGUMENT, "privacy-key-decryption-failed"); + } + if (cert->provider_id() != provider_id) { + return Status(error_space, SERVICE_CERTIFICATE_NOT_FOUND, + std::string("provider-id-mismatch (") + cert->provider_id() + + " / " + provider_id + ")"); + } + *payload = crypto_util::DecryptAesCbc(privacy_key, iv, encrypted_payload); + if (payload->empty()) { + return Status(error::INVALID_ARGUMENT, "payload-decryption-failed"); + } + return OkStatus(); +} + DrmServiceCertificate::DrmServiceCertificate( const std::string& service_certificate, const std::string& provider_id, const std::string& serial_number, const uint32_t creation_time_seconds, diff --git a/common/drm_service_certificate.h b/common/drm_service_certificate.h index fec6055..2dcfa6e 100644 --- a/common/drm_service_certificate.h +++ b/common/drm_service_certificate.h @@ -17,10 +17,10 @@ #include #include -#include "base/macros.h" #include "common/certificate_type.h" #include "common/rsa_key.h" #include "common/status.h" +#include "protos/public/external_license.pb.h" namespace widevine { class RequestInspectorTest; @@ -36,6 +36,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 +53,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 +62,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 +74,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 +97,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(); } @@ -95,22 +107,41 @@ class DrmServiceCertificate { // via get deviceCertificate StatusList. static Status ValidateDrmServiceCertificate(); + // Decrypts the EncryptedLicenseRequest message passed in + // |encrypted_license_request|. If successful, the decrypted license request + // is placed into |license_challenge|. The decryption is performed using the + // private key for the certificate which was used to encrypt the information. + // |license_challenge| must not be NULL. Returns status::OK if successful, + // or an appropriate error otherwise. This method is thread-safe. + static Status DecryptLicenseChallenge( + const EncryptedLicenseRequest& encrypted_license_request, + std::string* license_challenge); + private: friend class DrmServiceCertificateTest; 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); + static Status DecryptEncryptedPayload( + const std::string& service_certificate_serial_number, + const std::string& provider_id, const std::string& encrypted_payload, + const std::string& iv, const std::string& privacy_key, + std::string* payload); + 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 +154,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..c0741dd 100644 --- a/common/drm_service_certificate_test.cc +++ b/common/drm_service_certificate_test.cc @@ -17,6 +17,7 @@ #include "absl/strings/escaping.h" #include "common/aes_cbc_util.h" #include "common/drm_root_certificate.h" +#include "common/hash_algorithm_util.h" #include "common/rsa_key.h" #include "common/rsa_test_keys.h" #include "common/rsa_util.h" @@ -24,6 +25,7 @@ #include "protos/public/client_identification.pb.h" #include "protos/public/drm_certificate.pb.h" #include "protos/public/errors.pb.h" // IWYU pragma: keep +#include "protos/public/external_license.pb.h" #include "protos/public/license_server_sdk.pb.h" #include "protos/public/signed_drm_certificate.pb.h" @@ -51,9 +53,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); @@ -62,8 +64,10 @@ class DrmServiceCertificateTest : public ::testing::Test { cert.set_creation_time_seconds(creation_time_seconds); SignedDrmCertificate signed_cert; cert.SerializeToString(signed_cert.mutable_drm_certificate()); - root_private_key_->GenerateSignature(signed_cert.drm_certificate(), - signed_cert.mutable_signature()); + root_private_key_->GenerateSignature( + signed_cert.drm_certificate(), + HashAlgorithmProtoToEnum(signed_cert.hash_algorithm()), + signed_cert.mutable_signature()); std::string serialized_cert; signed_cert.SerializeToString(&serialized_cert); return serialized_cert; @@ -119,6 +123,24 @@ class DrmServiceCertificateTest : public ::testing::Test { encrypted_client_id->mutable_encrypted_privacy_key()); } + void EncryptLicenseChallenge( + const std::string& license_challenge, const std::string& serial_number, + const std::string& provider_id, const std::string& public_key, + EncryptedLicenseRequest* encrypted_license_request) { + CHECK(encrypted_license_request); + encrypted_license_request->set_provider_id(provider_id); + encrypted_license_request->set_service_certificate_serial_number( + serial_number); + encrypted_license_request->set_encrypted_license_request( + crypto_util::EncryptAesCbc(privacy_key_, iv_, license_challenge)); + encrypted_license_request->set_encrypted_license_request_iv(iv_); + std::unique_ptr rsa_key(RsaPublicKey::Create(public_key)); + ASSERT_TRUE(rsa_key.get()); + rsa_key->Encrypt( + privacy_key_, + encrypted_license_request->mutable_encrypted_privacy_key()); + } + RsaTestKeys test_keys_; TestDrmCertificates test_certs_; std::string privacy_key_; @@ -160,6 +182,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 +197,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 +227,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"); @@ -298,7 +381,7 @@ TEST_F(DrmServiceCertificateTest, InvalidEncryptedClientIdentification) { invalid.clear_encrypted_privacy_key(); EXPECT_EQ( "Errors::INVALID_ENCRYPTED_CLIENT_IDENTIFICATION: " - "missing-encrypted-privacy-key", + "missing-privacy-key", DrmServiceCertificate::DecryptClientIdentification(invalid, &decrypted_client_id) .ToString()); @@ -311,7 +394,7 @@ TEST_F(DrmServiceCertificateTest, InvalidEncryptedClientIdentification) { invalid.clear_encrypted_client_id_iv(); EXPECT_EQ( "Errors::INVALID_ENCRYPTED_CLIENT_IDENTIFICATION: " - "missing-encrypted-client-id-iv", + "missing-iv", DrmServiceCertificate::DecryptClientIdentification(invalid, &decrypted_client_id) .ToString()); @@ -324,7 +407,7 @@ TEST_F(DrmServiceCertificateTest, InvalidEncryptedClientIdentification) { invalid.clear_encrypted_client_id(); EXPECT_EQ( "Errors::INVALID_ENCRYPTED_CLIENT_IDENTIFICATION: " - "missing-encrypted-client-id", + "missing-encrypted-payload", DrmServiceCertificate::DecryptClientIdentification(invalid, &decrypted_client_id) .ToString()); @@ -358,6 +441,142 @@ TEST_F(DrmServiceCertificateTest, PrivateKeyDecryptError) { .ToString()); } +TEST_F(DrmServiceCertificateTest, DecryptLicenseChallenge) { + std::string serial_number("serial_number"); + std::string provider_id("someservice.com"); + uint32_t creation_time_seconds(1234); + + EXPECT_OK(AddDrmServiceCertificate(serial_number, provider_id, + creation_time_seconds)); + EncryptedLicenseRequest encrypted_license_request; + const std::string license_challenge = + "val"; + EncryptLicenseChallenge(license_challenge, serial_number, provider_id, + test_keys_.public_test_key_2_2048_bits(), + &encrypted_license_request); + std::string decrypted_license_challenge; + EXPECT_EQ(OkStatus(), + DrmServiceCertificate::DecryptLicenseChallenge( + encrypted_license_request, &decrypted_license_challenge)); + EXPECT_EQ(license_challenge, decrypted_license_challenge); +} + +TEST_F(DrmServiceCertificateTest, DecryptLicenseChallengeError) { + std::string serial_number("serial_number"); + std::string provider_id("someservice.com"); + uint32_t creation_time_seconds(1234); + + EXPECT_OK(AddDrmServiceCertificate(serial_number, provider_id, + creation_time_seconds)); + EncryptedLicenseRequest encrypted_license_request; + const std::string license_challenge = + "val"; + EncryptLicenseChallenge(license_challenge, serial_number, provider_id, + test_keys_.public_test_key_2_2048_bits(), + &encrypted_license_request); + { + EXPECT_EQ( + "Errors::INVALID_ENCRYPTED_LICENSE_CHALLENGE: " + "null-payload", + DrmServiceCertificate::DecryptLicenseChallenge( + encrypted_license_request, nullptr) + .ToString()); + } + std::string decrypted_license_challenge; + { + const std::string assigned_serial_number = + encrypted_license_request.service_certificate_serial_number(); + encrypted_license_request.clear_service_certificate_serial_number(); + EXPECT_EQ( + "Errors::INVALID_ENCRYPTED_LICENSE_CHALLENGE: " + "missing-service-certificate-serial-number", + DrmServiceCertificate::DecryptLicenseChallenge( + encrypted_license_request, &decrypted_license_challenge) + .ToString()); + encrypted_license_request.set_service_certificate_serial_number( + assigned_serial_number); + } + { + const std::string assigned_provider_id = + encrypted_license_request.provider_id(); + encrypted_license_request.clear_provider_id(); + EXPECT_EQ( + "Errors::INVALID_ENCRYPTED_LICENSE_CHALLENGE: " + "missing-service-id", + DrmServiceCertificate::DecryptLicenseChallenge( + encrypted_license_request, &decrypted_license_challenge) + .ToString()); + encrypted_license_request.set_provider_id(assigned_provider_id); + } + { + const std::string assigned_encrypted_license_request = + encrypted_license_request.encrypted_license_request(); + encrypted_license_request.clear_encrypted_license_request(); + EXPECT_EQ( + "Errors::INVALID_ENCRYPTED_LICENSE_CHALLENGE: " + "missing-encrypted-payload", + DrmServiceCertificate::DecryptLicenseChallenge( + encrypted_license_request, &decrypted_license_challenge) + .ToString()); + encrypted_license_request.set_encrypted_license_request( + assigned_encrypted_license_request); + } + { + const std::string assigned_encrypted_license_request_iv = + encrypted_license_request.encrypted_license_request_iv(); + encrypted_license_request.clear_encrypted_license_request_iv(); + EXPECT_EQ( + "Errors::INVALID_ENCRYPTED_LICENSE_CHALLENGE: " + "missing-iv", + DrmServiceCertificate::DecryptLicenseChallenge( + encrypted_license_request, &decrypted_license_challenge) + .ToString()); + encrypted_license_request.set_encrypted_license_request_iv( + assigned_encrypted_license_request_iv); + } + { + const std::string assigned_encrypted_privacy_key = + encrypted_license_request.encrypted_privacy_key(); + encrypted_license_request.clear_encrypted_privacy_key(); + EXPECT_EQ( + "Errors::INVALID_ENCRYPTED_LICENSE_CHALLENGE: " + "missing-privacy-key", + DrmServiceCertificate::DecryptLicenseChallenge( + encrypted_license_request, &decrypted_license_challenge) + .ToString()); + encrypted_license_request.set_encrypted_privacy_key( + assigned_encrypted_privacy_key); + } + { + const std::string assigned_encrypted_privacy_key = + encrypted_license_request.encrypted_privacy_key(); + encrypted_license_request.mutable_encrypted_privacy_key()->append( + "tampered"); + EXPECT_EQ( + "Errors::INVALID_ENCRYPTED_LICENSE_CHALLENGE: " + "privacy-key-decryption-failed", + DrmServiceCertificate::DecryptLicenseChallenge( + encrypted_license_request, &decrypted_license_challenge) + .ToString()); + encrypted_license_request.set_encrypted_privacy_key( + assigned_encrypted_privacy_key); + } + { + const std::string assigned_provider_id = + encrypted_license_request.provider_id(); + encrypted_license_request.set_provider_id("tamperedProviderId"); + EXPECT_EQ( + "Errors::SERVICE_CERTIFICATE_NOT_FOUND: " + "provider-id-mismatch (" + + assigned_provider_id + " / tamperedProviderId)", + DrmServiceCertificate::DecryptLicenseChallenge( + encrypted_license_request, &decrypted_license_challenge) + .ToString()); + } +} + // TODO(user): Add more unit tests for various fail cases (bad keys having // to do with bad keys and bad certs). diff --git a/common/dual_certificate_client_cert.cc b/common/dual_certificate_client_cert.cc new file mode 100644 index 0000000..ac7d7cd --- /dev/null +++ b/common/dual_certificate_client_cert.cc @@ -0,0 +1,113 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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/dual_certificate_client_cert.h" + +#include "common/error_space.h" +#include "common/status.h" +#include "protos/public/errors.pb.h" + +namespace widevine { + +Status DualCertificateClientCert::Initialize( + const DrmRootCertificate* root_certificate, + const std::string& serialized_signing_certificate, + const std::string& serialized_encryption_certificate) { + Status status = signing_certificate_.Initialize( + root_certificate, serialized_signing_certificate); + if (!status.ok()) { + return status; + } + status = encryption_certificate_.Initialize( + root_certificate, serialized_encryption_certificate); + if (!status.ok()) { + return status; + } + if (encryption_certificate_.signer_serial_number() != + signing_certificate_.signer_serial_number()) { + return Status(error_space, INVALID_DRM_CERTIFICATE, + "certificate_signer_mismatch"); + } + if ((encryption_certificate_.system_id() != + signing_certificate_.system_id()) || + (encryption_certificate_.service_id() != + signing_certificate_.service_id()) || + (encryption_certificate_.signer_creation_time_seconds() != + signing_certificate_.signer_creation_time_seconds()) || + (encryption_certificate_.signed_by_provisioner() != + signing_certificate_.signed_by_provisioner())) { + return Status(error_space, INVALID_DRM_CERTIFICATE, + "invalid_certificate_pair"); + } + return OkStatus(); +} + +Status DualCertificateClientCert::VerifySignature( + const std::string& message, HashAlgorithm hash_algorithm, + const std::string& signature, ProtocolVersion protocol_version) const { + return signing_certificate_.VerifySignature(message, hash_algorithm, + signature, protocol_version); +} + +void DualCertificateClientCert::GenerateSigningKey( + const std::string& message, ProtocolVersion protocol_version) { + encryption_certificate_.GenerateSigningKey(message, protocol_version); +} + +const std::string& DualCertificateClientCert::encrypted_key() const { + return encryption_certificate_.encrypted_key(); +} + +const std::string& DualCertificateClientCert::key() const { + return encryption_certificate_.key(); +} + +SignedMessage::SessionKeyType DualCertificateClientCert::key_type() const { + return encryption_certificate_.key_type(); +} + +// TODO(b/155979840): Support revocation check for the encryption certificate. +const std::string& DualCertificateClientCert::serial_number() const { + return signing_certificate_.serial_number(); +} + +const std::string& DualCertificateClientCert::service_id() const { + return signing_certificate_.service_id(); +} + +const std::string& DualCertificateClientCert::signing_key() const { + return encryption_certificate_.signing_key(); +} + +const std::string& DualCertificateClientCert::signer_serial_number() const { + return signing_certificate_.signer_serial_number(); +} + +uint32_t DualCertificateClientCert::signer_creation_time_seconds() const { + return signing_certificate_.signer_creation_time_seconds(); +} + +bool DualCertificateClientCert::signed_by_provisioner() const { + return signing_certificate_.signed_by_provisioner(); +} + +uint32_t DualCertificateClientCert::system_id() const { + return signing_certificate_.system_id(); +} + +// TODO(b/155979840): Support revocation check for the encryption certificate. +const std::string& DualCertificateClientCert::encrypted_unique_id() const { + return signing_certificate_.encrypted_unique_id(); +} + +// TODO(b/155979840): Support revocation check for the encryption certificate. +const std::string& DualCertificateClientCert::unique_id_hash() const { + return signing_certificate_.unique_id_hash(); +} + +} // namespace widevine diff --git a/common/dual_certificate_client_cert.h b/common/dual_certificate_client_cert.h new file mode 100644 index 0000000..306c2d2 --- /dev/null +++ b/common/dual_certificate_client_cert.h @@ -0,0 +1,57 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef COMMON_DUAL_CERTIFICATE_CLIENT_CERT_H_ +#define COMMON_DUAL_CERTIFICATE_CLIENT_CERT_H_ + +#include "common/certificate_client_cert.h" + +namespace widevine { + +class DualCertificateClientCert : public ClientCert { + public: + DualCertificateClientCert() = default; + ~DualCertificateClientCert() override = default; + DualCertificateClientCert(const DualCertificateClientCert&) = delete; + DualCertificateClientCert& operator=(const DualCertificateClientCert&) = + delete; + + Status Initialize(const DrmRootCertificate* root_certificate, + const std::string& serialized_signing_certificate, + const std::string& serialized_encryption_certificate); + Status VerifySignature(const std::string& message, + HashAlgorithm hash_algorithm, + 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; + const std::string& key() const override; + SignedMessage::SessionKeyType key_type() const override; + bool using_dual_certificate() const override { return true; } + const std::string& serial_number() const override; + const std::string& service_id() const override; + const std::string& signing_key() const override; + const std::string& signer_serial_number() const override; + uint32_t signer_creation_time_seconds() const override; + bool signed_by_provisioner() const override; + uint32_t system_id() const override; + widevine::ClientIdentification::TokenType type() const override { + return ClientIdentification::DRM_DEVICE_CERTIFICATE; + } + const std::string& encrypted_unique_id() const override; + const std::string& unique_id_hash() const override; + + private: + CertificateClientCert signing_certificate_; + CertificateClientCert encryption_certificate_; +}; + +} // namespace widevine +#endif // COMMON_DUAL_CERTIFICATE_CLIENT_CERT_H_ diff --git a/common/ec_key.cc b/common/ec_key.cc new file mode 100644 index 0000000..b5b4684 --- /dev/null +++ b/common/ec_key.cc @@ -0,0 +1,313 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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/hash_algorithm.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; +} + +std::string GetMessageDigest(const std::string& message, + widevine::HashAlgorithm hash_algorithm) { + switch (hash_algorithm) { + case widevine::HashAlgorithm::kUnspecified: + case widevine::HashAlgorithm::kSha256: + return widevine::Sha256_Hash(message); + case widevine::HashAlgorithm::kSha1: + LOG(ERROR) << "Unexpected hash algorithm: " + << static_cast(hash_algorithm); + return ""; + } + LOG(FATAL) << "Unexpected hash algorithm: " + << static_cast(hash_algorithm); + return ""; +} + +} // 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, + HashAlgorithm hash_algorithm, + 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; + } + + // Hash the message using corresponding hash algorithm. + std::string message_digest = GetMessageDigest(message, hash_algorithm); + if (message_digest.empty()) { + LOG(ERROR) << "Empty message digest"; + return false; + } + + 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, + HashAlgorithm hash_algorithm, + 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; + } + + // Hash the message using corresponding hash algorithm. + std::string message_digest = GetMessageDigest(message, hash_algorithm); + if (message_digest.empty()) { + LOG(ERROR) << "Empty message digest"; + return false; + } + + 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..57a74ea --- /dev/null +++ b/common/ec_key.h @@ -0,0 +1,150 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 "absl/base/macros.h" +#include "openssl/ec.h" +#include "common/hash_algorithm.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. + // |hash_algorithm| specifies the hash algorithm. + // |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, + HashAlgorithm hash_algorithm, + 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. + // |hash_algorithm| specifies the hash algorithm. + // |signature| is an ASN.1 DER-encoded signature. + // Returns true on success and false on error. + virtual bool VerifySignature(const std::string& message, + HashAlgorithm hash_algorithm, + 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..e346f75 --- /dev/null +++ b/common/ec_key_test.cc @@ -0,0 +1,320 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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/hash_algorithm.h" +#include "common/random_util.h" + +using ::testing::IsNull; + +namespace { + +const widevine::HashAlgorithm kHashAlgorithm = + widevine::HashAlgorithm::kSha256; +} + +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_; +}; + +// Death test naming convention. See below link for details: +// go/gunitadvanced#death-test-naming +using ECKeyTestKeyPairsDeathTest = ECKeyTestKeyPairs; + +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, SignVerifySha1) { + std::string signature; + EXPECT_FALSE(private_key_->GenerateSignature( + plaintext_message_, HashAlgorithm::kSha1, &signature)); + EXPECT_FALSE(public_key_->VerifySignature(plaintext_message_, + HashAlgorithm::kSha1, signature)); +} + +TEST_P(ECKeyTestKeyPairs, SignVerifySha256) { + std::string signature; + ASSERT_TRUE(private_key_->GenerateSignature( + plaintext_message_, HashAlgorithm::kSha256, &signature)); + ASSERT_TRUE(public_key_->VerifySignature(plaintext_message_, + HashAlgorithm::kSha256, signature)); +} + +TEST_P(ECKeyTestKeyPairs, SignVerifyUnspecified) { + std::string signature; + ASSERT_TRUE(private_key_->GenerateSignature( + plaintext_message_, HashAlgorithm::kUnspecified, &signature)); + ASSERT_TRUE(public_key_->VerifySignature( + plaintext_message_, HashAlgorithm::kUnspecified, signature)); +} + +TEST_P(ECKeyTestKeyPairsDeathTest, SignVerifyUnexpected) { + std::string signature; + HashAlgorithm unexpected_hash_algorithm = static_cast(1234); + EXPECT_DEATH(private_key_->GenerateSignature( + plaintext_message_, unexpected_hash_algorithm, &signature), + "Unexpected hash algorithm: 1234"); + EXPECT_FALSE(public_key_->VerifySignature( + plaintext_message_, unexpected_hash_algorithm, signature)); +} + +TEST_P(ECKeyTestKeyPairs, InvalidSignVerifyParameters) { + std::string signature; + EXPECT_FALSE(private_key_->GenerateSignature("", kHashAlgorithm, &signature)); + EXPECT_FALSE(public_key_->VerifySignature("", kHashAlgorithm, "signature")); + EXPECT_FALSE(private_key_->GenerateSignature(plaintext_message_, + kHashAlgorithm, nullptr)); + EXPECT_FALSE( + public_key_->VerifySignature(plaintext_message_, kHashAlgorithm, "")); +} + +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_, + kHashAlgorithm, &signature)); + EXPECT_FALSE(ephemeral_public_key_->VerifySignature( + plaintext_message_, kHashAlgorithm, 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())); + +INSTANTIATE_TEST_SUITE_P(ECKeyTestKeyPairsDeathTest, ECKeyTestKeyPairsDeathTest, + ::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_, + kHashAlgorithm, &signature)); + EXPECT_FALSE(wrong_curve_public_key_->VerifySignature( + plaintext_message_, kHashAlgorithm, 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..f3e2ff8 --- /dev/null +++ b/common/ecies_crypto_test.cc @@ -0,0 +1,259 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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_METHOD(bool, GetECKey, + (ECPrivateKey::EllipticCurve curve, std::string* private_key, + std::string* public_key), + (override)); +}; + +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..e07669e 100644 --- a/common/file_util_test.cc +++ b/common/file_util_test.cc @@ -7,6 +7,7 @@ //////////////////////////////////////////////////////////////////////////////// #include "common/file_util.h" + #include "testing/gunit.h" #include "absl/strings/str_cat.h" @@ -19,7 +20,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/hash_algorithm.h b/common/hash_algorithm.h new file mode 100644 index 0000000..356b190 --- /dev/null +++ b/common/hash_algorithm.h @@ -0,0 +1,18 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef COMMON_HASH_ALGORITHM_H_ +#define COMMON_HASH_ALGORITHM_H_ + +namespace widevine { + +enum class HashAlgorithm { kUnspecified, kSha1, kSha256 }; + +} // namespace widevine + +#endif // COMMON_HASH_ALGORITHM_H_ diff --git a/common/hash_algorithm_util.cc b/common/hash_algorithm_util.cc new file mode 100644 index 0000000..288badf --- /dev/null +++ b/common/hash_algorithm_util.cc @@ -0,0 +1,55 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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/hash_algorithm_util.h" + +#include "glog/logging.h" +#include "protos/public/hash_algorithm.pb.h" + +namespace widevine { + +HashAlgorithm HashAlgorithmProtoToEnum( + HashAlgorithmProto hash_algorithm_proto) { + switch (hash_algorithm_proto) { + case HASH_ALGORITHM_UNSPECIFIED: + return HashAlgorithm::kUnspecified; + case HASH_ALGORITHM_SHA_1: + return HashAlgorithm::kSha1; + case HASH_ALGORITHM_SHA_256: + return HashAlgorithm::kSha256; + default: + // See below link for using proto3 enum in switch statement: + // http://shortn/_ma9MY7V9wh + if (HashAlgorithmProto_IsValid(hash_algorithm_proto)) { + LOG(ERROR) << "Unsupported value " << hash_algorithm_proto; + } else { + LOG(WARNING) << "Unexpected value " << hash_algorithm_proto; + } + return HashAlgorithm::kUnspecified; + } +} + +HashAlgorithmProto HashAlgorithmEnumToProto(HashAlgorithm hash_algorithm) { + switch (hash_algorithm) { + case HashAlgorithm::kUnspecified: + return HASH_ALGORITHM_UNSPECIFIED; + case HashAlgorithm::kSha1: + return HASH_ALGORITHM_SHA_1; + case HashAlgorithm::kSha256: + return HASH_ALGORITHM_SHA_256; + } + LOG(WARNING) << "Unexpected hash algorithm " + << static_cast(hash_algorithm); + return HASH_ALGORITHM_UNSPECIFIED; +} + +std::string HashAlgorithmEnumToString(HashAlgorithm hash_algorithm) { + return HashAlgorithmProto_Name(HashAlgorithmEnumToProto(hash_algorithm)); +} + +} // namespace widevine diff --git a/common/hash_algorithm_util.h b/common/hash_algorithm_util.h new file mode 100644 index 0000000..25825b3 --- /dev/null +++ b/common/hash_algorithm_util.h @@ -0,0 +1,25 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef COMMON_HASH_ALGORITHM_UTIL_H_ +#define COMMON_HASH_ALGORITHM_UTIL_H_ + +#include "common/hash_algorithm.h" +#include "protos/public/hash_algorithm.pb.h" + +namespace widevine { + +HashAlgorithm HashAlgorithmProtoToEnum(HashAlgorithmProto hash_algorithm_proto); + +HashAlgorithmProto HashAlgorithmEnumToProto(HashAlgorithm hash_algorithm); + +std::string HashAlgorithmEnumToString(HashAlgorithm hash_algorithm); + +} // namespace widevine + +#endif // COMMON_HASH_ALGORITHM_UTIL_H_ diff --git a/common/hash_algorithm_util_test.cc b/common/hash_algorithm_util_test.cc new file mode 100644 index 0000000..f5fbfdd --- /dev/null +++ b/common/hash_algorithm_util_test.cc @@ -0,0 +1,58 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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/hash_algorithm_util.h" + +#include "testing/gmock.h" +#include "testing/gunit.h" +#include "common/hash_algorithm.h" + +namespace widevine { + +TEST(HashAlgorithmTest, ProtoToEnumUnspecified) { + EXPECT_EQ(HashAlgorithm::kUnspecified, + HashAlgorithmProtoToEnum(HASH_ALGORITHM_UNSPECIFIED)); +} + +TEST(HashAlgorithmTest, ProtoToEnumSha1) { + EXPECT_EQ(HashAlgorithm::kSha1, + HashAlgorithmProtoToEnum(HASH_ALGORITHM_SHA_1)); +} + +TEST(HashAlgorithmTest, ProtoToEnumSha256) { + EXPECT_EQ(HashAlgorithm::kSha256, + HashAlgorithmProtoToEnum(HASH_ALGORITHM_SHA_256)); +} + +TEST(HashAlgorithmTest, ProtoToEnumUnsupported) { + EXPECT_EQ(HashAlgorithm::kUnspecified, + HashAlgorithmProtoToEnum(static_cast(1234))); +} + +TEST(HashAlgorithmTest, EnumToProtoUnspecified) { + EXPECT_EQ(HASH_ALGORITHM_UNSPECIFIED, + HashAlgorithmEnumToProto(HashAlgorithm::kUnspecified)); +} + +TEST(HashAlgorithmTest, EnumToProtoSha1) { + EXPECT_EQ(HASH_ALGORITHM_SHA_1, + HashAlgorithmEnumToProto(HashAlgorithm::kSha1)); +} + +TEST(HashAlgorithmTest, EnumToProtoSha256) { + EXPECT_EQ(HASH_ALGORITHM_SHA_256, + HashAlgorithmEnumToProto(HashAlgorithm::kSha256)); +} + +TEST(HashAlgorithmTest, EnumToProtoUnexpected) { + int some_value = 1234; + EXPECT_EQ(HASH_ALGORITHM_UNSPECIFIED, + HashAlgorithmEnumToProto(static_cast(some_value))); +} + +} // namespace widevine diff --git a/common/keybox_client_cert.cc b/common/keybox_client_cert.cc new file mode 100644 index 0000000..bd8e748 --- /dev/null +++ b/common/keybox_client_cert.cc @@ -0,0 +1,58 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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(); +} + +// |hash_algorithm| is needed for function inheritance. +// For KeyBoxClientCert, we always use HMAC-SHA256 in signature verification. +Status KeyboxClientCert::VerifySignature( + const std::string& message, HashAlgorithm hash_algorithm, + 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..3418067 --- /dev/null +++ b/common/keybox_client_cert.h @@ -0,0 +1,86 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 "absl/strings/str_cat.h" +#include "common/client_cert.h" +#include "common/error_space.h" +#include "common/hash_algorithm.h" +#include "protos/public/errors.pb.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, + HashAlgorithm hash_algorithm, + 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_; } + SignedMessage::SessionKeyType key_type() const override { + return SignedMessage::WRAPPED_AES_KEY; + } + bool using_dual_certificate() const override { return false; } + 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; + } + const std::string& encrypted_unique_id() const override { + return unimplemented_; + } + const std::string& unique_id_hash() const override { return unimplemented_; } + + // 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); + Status SystemIdUnknownError() const override { + return Status(error_space, UNSUPPORTED_SYSTEM_ID, + absl::StrCat("keybox-unsupported-system-id: ", system_id())); + } + Status SystemIdRevokedError() const override { + return Status(error_space, DRM_DEVICE_CERTIFICATE_REVOKED, + absl::StrCat("keybox-system-id-revoked: ", system_id())); + } + + 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..feb9eec 100644 --- a/common/mock_rsa_key.h +++ b/common/mock_rsa_key.h @@ -10,7 +10,9 @@ #define COMMON_MOCK_RSA_KEY_H_ #include + #include "testing/gmock.h" +#include "common/hash_algorithm.h" #include "common/rsa_key.h" namespace widevine { @@ -20,12 +22,18 @@ class MockRsaPrivateKey : public RsaPrivateKey { MockRsaPrivateKey() : RsaPrivateKey(RSA_new()) {} ~MockRsaPrivateKey() override {} - MOCK_CONST_METHOD2(Decrypt, bool(const std::string& encrypted_message, - std::string* decrypted_message)); - MOCK_CONST_METHOD2(GenerateSignature, - bool(const std::string& message, std::string* signature)); - MOCK_CONST_METHOD1(MatchesPrivateKey, bool(const RsaPrivateKey& private_key)); - MOCK_CONST_METHOD1(MatchesPublicKey, bool(const RsaPublicKey& public_key)); + MOCK_METHOD(bool, Decrypt, + (const std::string& encrypted_message, + std::string* decrypted_message), + (const, override)); + MOCK_METHOD(bool, GenerateSignature, + (const std::string& message, HashAlgorithm hash_algorithm, + std::string* signature), + (const, override)); + MOCK_METHOD(bool, MatchesPrivateKey, (const RsaPrivateKey& private_key), + (const, override)); + MOCK_METHOD(bool, MatchesPublicKey, (const RsaPublicKey& public_key), + (const, override)); private: MockRsaPrivateKey(const MockRsaPrivateKey&) = delete; @@ -37,12 +45,18 @@ class MockRsaPublicKey : public RsaPublicKey { MockRsaPublicKey() : RsaPublicKey(RSA_new()) {} ~MockRsaPublicKey() override {} - 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_METHOD1(MatchesPrivateKey, bool(const RsaPrivateKey& private_key)); - MOCK_CONST_METHOD1(MatchesPublicKey, bool(const RsaPublicKey& public_key)); + MOCK_METHOD(bool, Encrypt, + (const std::string& clear_message, + std::string* encrypted_message), + (const, override)); + MOCK_METHOD(bool, VerifySignature, + (const std::string& message, HashAlgorithm hash_algorithm, + const std::string& signature), + (const, override)); + MOCK_METHOD(bool, MatchesPrivateKey, (const RsaPrivateKey& private_key), + (const, override)); + MOCK_METHOD(bool, MatchesPublicKey, (const RsaPublicKey& public_key), + (const, override)); private: MockRsaPublicKey(const MockRsaPublicKey&) = delete; @@ -54,14 +68,14 @@ 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_METHOD(std::unique_ptr, CreateFromPkcs1PrivateKey, + (const std::string& private_key), (const, override)); + MOCK_METHOD(std::unique_ptr, CreateFromPkcs8PrivateKey, + (const std::string& private_key, + const std::string& private_key_passphrase), + (const, override)); + MOCK_METHOD(std::unique_ptr, CreateFromPkcs1PublicKey, + (const std::string& public_key), (const, override)); private: MockRsaKeyFactory(const MockRsaKeyFactory&) = delete; diff --git a/common/oemcrypto_core_message/odk/Android.bp b/common/oemcrypto_core_message/odk/Android.bp new file mode 100644 index 0000000..5255afd --- /dev/null +++ b/common/oemcrypto_core_message/odk/Android.bp @@ -0,0 +1,94 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +// ---------------------------------------------------------------- +// Builds libwv_odk.a, The ODK Library (libwv_odk) is used by +// the CDM and by oemcrypto implementations. +cc_library_static { + name: "libwv_odk", + include_dirs: [ + "vendor/widevine/libwvdrmengine/oemcrypto/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/src", + ], + + srcs: [ + "src/odk.c", + "src/odk_overflow.c", + "src/odk_serialize.c", + "src/odk_timer.c", + "src/odk_util.c", + "src/serialization_base.c", + ], + proprietary: true, + + owner: "widevine", +} + +// ---------------------------------------------------------------- +// Builds libwv_kdo.a, The ODK Library companion (libwv_kdo) is used by +// the CDM and by oemcrypto tests, but not by oemcrypto implementations. +cc_library_static { + name: "libwv_kdo", + include_dirs: [ + "vendor/widevine/libwvdrmengine/oemcrypto/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/src", + ], + + srcs: [ + "src/core_message_deserialize.cpp", + "src/core_message_serialize.cpp", + "src/core_message_serialize_proto.cpp", + ], + + static_libs: [ + "libcdm_protos", + "libwv_odk", + ], + + proprietary: true, + + owner: "widevine", +} + +// ---------------------------------------------------------------- +// Builds odk_test executable, which tests the ODK library. +cc_test { + name: "odk_test", + include_dirs: [ + "vendor/widevine/libwvdrmengine/oemcrypto/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/src", + ], + + // WARNING: Module tags are not supported in Soong. + // For native test binaries, use the "cc_test" module type. Some differences: + // - If you don't use gtest, set "gtest: false" + // - Binaries will be installed into /data/nativetest[64]// + // - Both 32 & 64 bit versions will be built (as appropriate) + + owner: "widevine", + proprietary: true, + + static_libs: [ + "libcdm_protos", + "libcdm", + "libwv_odk", + "libwv_kdo", + ], + + srcs: [ + "test/odk_test.cpp", + "test/odk_test_helper.cpp", + "test/odk_timer_test.cpp", + ], + +} diff --git a/common/oemcrypto_core_message/odk/BUILD b/common/oemcrypto_core_message/odk/BUILD new file mode 100644 index 0000000..17c68bf --- /dev/null +++ b/common/oemcrypto_core_message/odk/BUILD @@ -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. +################################################################################ + +package(default_visibility = ["//visibility:public"]) + + +filegroup( + name = "odk_common_hdrs", + srcs = [ + "src/odk_serialize.h", + "src/odk_structs_priv.h", + "src/serialization_base.h", + ], +) + +cc_library( + name = "odk", + srcs = [ + "src/odk.c", + "src/odk_assert.h", + "src/odk_endian.h", + "src/odk_overflow.c", + "src/odk_overflow.h", + "src/odk_serialize.c", + "src/odk_timer.c", + "src/odk_util.c", + "src/odk_util.h", + "src/serialization_base.c", + ":odk_common_hdrs", + ], + hdrs = [ + "include/OEMCryptoCENCCommon.h", + "include/odk.h", + "include/odk_attributes.h", + "include/odk_structs.h", + "include/odk_target.h", + ], + copts = ["-std=c99"], +) + +cc_library( + name = "kdo", + srcs = [ + "src/core_message_deserialize.cpp", + "src/core_message_serialize.cpp", + "src/core_message_serialize_proto.cpp", + ":odk_common_hdrs", + ], + hdrs = [ + "include/core_message_deserialize.h", + "include/core_message_serialize.h", + "include/core_message_serialize_proto.h", + "include/core_message_types.h", + ], + deps = [ + ":odk", + "//protos/public:certificate_provisioning_cc_proto", + "//protos/public:license_protocol_cc_proto", + ], +) + diff --git a/common/oemcrypto_core_message/odk/README b/common/oemcrypto_core_message/odk/README new file mode 100644 index 0000000..ba8c8c7 --- /dev/null +++ b/common/oemcrypto_core_message/odk/README @@ -0,0 +1,10 @@ +This ODK Library is used to generate and parse core OEMCrypto messages for +OEMCrypto v16 and above. + +This library is used by both OEMCrypto on a device, and by Widevine license and +provisioning servers. + +The source of truth for these files is in the server code base on piper. Do not +edit these files in the Android directory tree or in the Widevine Git +repository. If you need to edit these files and are not sure how to procede, +please ask for help from an engineer on the Widevine server or device teams. diff --git a/common/oemcrypto_core_message/odk/include/OEMCryptoCENCCommon.h b/common/oemcrypto_core_message/odk/include/OEMCryptoCENCCommon.h new file mode 100644 index 0000000..1aa106a --- /dev/null +++ b/common/oemcrypto_core_message/odk/include/OEMCryptoCENCCommon.h @@ -0,0 +1,179 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +/********************************************************************* + * OEMCryptoCENCCommon.h + * + * Common structures and error codes between WV servers and OEMCrypto. + * + *********************************************************************/ + +#ifndef COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_OEMCRYPTOCENCCOMMON_H_ +#define COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_OEMCRYPTOCENCCOMMON_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/// @addtogroup common_types +/// @{ + +/* clang-format off */ +/** Error and result codes returned by OEMCrypto functions. */ +typedef enum OEMCryptoResult { + OEMCrypto_SUCCESS = 0, + OEMCrypto_ERROR_INIT_FAILED = 1, + OEMCrypto_ERROR_TERMINATE_FAILED = 2, + OEMCrypto_ERROR_OPEN_FAILURE = 3, + OEMCrypto_ERROR_CLOSE_FAILURE = 4, + OEMCrypto_ERROR_ENTER_SECURE_PLAYBACK_FAILED = 5, /* deprecated */ + OEMCrypto_ERROR_EXIT_SECURE_PLAYBACK_FAILED = 6, /* deprecated */ + OEMCrypto_ERROR_SHORT_BUFFER = 7, + OEMCrypto_ERROR_NO_DEVICE_KEY = 8, /* no keybox device key. */ + OEMCrypto_ERROR_NO_ASSET_KEY = 9, + OEMCrypto_ERROR_KEYBOX_INVALID = 10, + OEMCrypto_ERROR_NO_KEYDATA = 11, + OEMCrypto_ERROR_NO_CW = 12, + OEMCrypto_ERROR_DECRYPT_FAILED = 13, + OEMCrypto_ERROR_WRITE_KEYBOX = 14, + OEMCrypto_ERROR_WRAP_KEYBOX = 15, + OEMCrypto_ERROR_BAD_MAGIC = 16, + OEMCrypto_ERROR_BAD_CRC = 17, + OEMCrypto_ERROR_NO_DEVICEID = 18, + OEMCrypto_ERROR_RNG_FAILED = 19, + OEMCrypto_ERROR_RNG_NOT_SUPPORTED = 20, + OEMCrypto_ERROR_SETUP = 21, + OEMCrypto_ERROR_OPEN_SESSION_FAILED = 22, + OEMCrypto_ERROR_CLOSE_SESSION_FAILED = 23, + OEMCrypto_ERROR_INVALID_SESSION = 24, + OEMCrypto_ERROR_NOT_IMPLEMENTED = 25, + OEMCrypto_ERROR_NO_CONTENT_KEY = 26, + OEMCrypto_ERROR_CONTROL_INVALID = 27, + OEMCrypto_ERROR_UNKNOWN_FAILURE = 28, + OEMCrypto_ERROR_INVALID_CONTEXT = 29, + OEMCrypto_ERROR_SIGNATURE_FAILURE = 30, + OEMCrypto_ERROR_TOO_MANY_SESSIONS = 31, + OEMCrypto_ERROR_INVALID_NONCE = 32, + OEMCrypto_ERROR_TOO_MANY_KEYS = 33, + OEMCrypto_ERROR_DEVICE_NOT_RSA_PROVISIONED = 34, + OEMCrypto_ERROR_INVALID_RSA_KEY = 35, + OEMCrypto_ERROR_KEY_EXPIRED = 36, + OEMCrypto_ERROR_INSUFFICIENT_RESOURCES = 37, + OEMCrypto_ERROR_INSUFFICIENT_HDCP = 38, + OEMCrypto_ERROR_BUFFER_TOO_LARGE = 39, + OEMCrypto_WARNING_GENERATION_SKEW = 40, /* Warning, not error. */ + OEMCrypto_ERROR_GENERATION_SKEW = 41, + OEMCrypto_LOCAL_DISPLAY_ONLY = 42, /* Info, not an error. */ + OEMCrypto_ERROR_ANALOG_OUTPUT = 43, + OEMCrypto_ERROR_WRONG_PST = 44, + OEMCrypto_ERROR_WRONG_KEYS = 45, + OEMCrypto_ERROR_MISSING_MASTER = 46, + OEMCrypto_ERROR_LICENSE_INACTIVE = 47, + OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE = 48, + OEMCrypto_ERROR_ENTRY_IN_USE = 49, + OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE = 50, /* Obsolete. Don't use. */ + /* Use OEMCrypto_ERROR_NO_CONTENT_KEY instead of KEY_NOT_LOADED. */ + OEMCrypto_KEY_NOT_LOADED = 51, /* Obsolete. */ + OEMCrypto_KEY_NOT_ENTITLED = 52, + OEMCrypto_ERROR_BAD_HASH = 53, + OEMCrypto_ERROR_OUTPUT_TOO_LARGE = 54, + OEMCrypto_ERROR_SESSION_LOST_STATE = 55, + OEMCrypto_ERROR_SYSTEM_INVALIDATED = 56, + OEMCrypto_ERROR_LICENSE_RELOAD = 57, + OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES = 58, + OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION = 59, + /* ODK return values */ + ODK_ERROR_BASE = 1000, + ODK_ERROR_CORE_MESSAGE = ODK_ERROR_BASE, + ODK_SET_TIMER = ODK_ERROR_BASE + 1, + ODK_DISABLE_TIMER = ODK_ERROR_BASE + 2, + ODK_TIMER_EXPIRED = ODK_ERROR_BASE + 3, + ODK_UNSUPPORTED_API = ODK_ERROR_BASE + 4, + ODK_STALE_RENEWAL = ODK_ERROR_BASE + 5, +} OEMCryptoResult; +/* clang-format on */ + +/** + * Valid values for status in the usage table. + */ +typedef enum OEMCrypto_Usage_Entry_Status { + kUnused = 0, + kActive = 1, + kInactive = 2, /* Deprecated. Use kInactiveUsed or kInactiveUnused. */ + kInactiveUsed = 3, + kInactiveUnused = 4, +} OEMCrypto_Usage_Entry_Status; + +/** + * OEMCrypto_LicenseType is used in the license message to indicate if the key + * objects are for content keys, or for entitlement keys. + */ +typedef enum OEMCrypto_LicenseType { + OEMCrypto_ContentLicense = 0, + OEMCrypto_EntitlementLicense = 1, + OEMCrypto_LicenstType_MaxValue = OEMCrypto_EntitlementLicense, +} OEMCrypto_LicenseType; + +/* Private key type used in the provisioning response. */ +typedef enum OEMCrypto_PrivateKeyType { + OEMCrypto_RSA_Private_Key = 0, + OEMCrypto_ECC_Private_Key = 1, +} OEMCrypto_PrivateKeyType; + +/** + * Used to indicate a substring of a signed message in OEMCrypto_LoadKeys and + * other functions which must verify that a parameter is contained within a + * signed message. + */ +typedef struct { + size_t offset; + size_t length; +} OEMCrypto_Substring; + +/** + * Points to the relevant fields for a content key. The fields are extracted + * from the License Response message offered to OEMCrypto_LoadKeys(). Each + * field points to one of the components of the key. Key data, key control, + * and both IV fields are 128 bits (16 bytes): + * @param key_id: the unique id of this key. + * @param key_id_length: the size of key_id. OEMCrypto may assume this is at + * most 16. However, OEMCrypto shall correctly handle key id lengths + * from 1 to 16 bytes. + * @param key_data_iv: the IV for performing AES-128-CBC decryption of the + * key_data field. + * @param key_data - the key data. It is encrypted (AES-128-CBC) with the + * session's derived encrypt key and the key_data_iv. + * @param key_control_iv: the IV for performing AES-128-CBC decryption of the + * key_control field. + * @param key_control: the key control block. It is encrypted (AES-128-CBC) with + * the content key from the key_data field. + * + * The memory for the OEMCrypto_KeyObject fields is allocated and freed + * by the caller of OEMCrypto_LoadKeys(). + */ +typedef struct { + OEMCrypto_Substring key_id; + OEMCrypto_Substring key_data_iv; + OEMCrypto_Substring key_data; + OEMCrypto_Substring key_control_iv; + OEMCrypto_Substring key_control; +} OEMCrypto_KeyObject; + +/// @} + +#ifdef __cplusplus +} +#endif + +#endif // COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_OEMCRYPTOCENCCOMMON_H_ diff --git a/common/oemcrypto_core_message/odk/include/core_message_deserialize.h b/common/oemcrypto_core_message/odk/include/core_message_deserialize.h new file mode 100644 index 0000000..bb54e89 --- /dev/null +++ b/common/oemcrypto_core_message/odk/include/core_message_deserialize.h @@ -0,0 +1,75 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +/********************************************************************* + * core_message_deserialize.h + * + * OEMCrypto v16 Core Message Serialization library counterpart (a.k.a. KDO) + * + * This file declares functions to deserialize request messages prepared by + * Widevine clients (OEMCrypto/ODK). + * + * Please refer to core_message_types.h for details. + * + *********************************************************************/ + +#ifndef COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_CORE_MESSAGE_DESERIALIZE_H_ +#define COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_CORE_MESSAGE_DESERIALIZE_H_ + +#include "common/oemcrypto_core_message/odk/include/core_message_types.h" + +namespace oemcrypto_core_message { +namespace deserialize { + +/** + * Counterpart (deserializer) of ODK_PrepareCoreLicenseRequest (serializer) + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_license_request + */ +bool CoreLicenseRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_LicenseRequest* core_license_request); + +/** + * Counterpart (deserializer) of ODK_PrepareCoreRenewalRequest (serializer) + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_renewal_request + */ +bool CoreRenewalRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_RenewalRequest* core_renewal_request); + +/** + * Counterpart (deserializer) of ODK_PrepareCoreProvisioningRequest (serializer) + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_provisioning_request + */ +bool CoreProvisioningRequestFromMessage( + const std::string& oemcrypto_core_message, + ODK_ProvisioningRequest* core_provisioning_request); + +/** + * Serializer counterpart is not used and is therefore not implemented. + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_common_request + */ +bool CoreCommonRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_CommonRequest* core_common_request); + +} // namespace deserialize +} // namespace oemcrypto_core_message + +#endif // COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_CORE_MESSAGE_DESERIALIZE_H_ diff --git a/common/oemcrypto_core_message/odk/include/core_message_serialize.h b/common/oemcrypto_core_message/odk/include/core_message_serialize.h new file mode 100644 index 0000000..f8d6b9e --- /dev/null +++ b/common/oemcrypto_core_message/odk/include/core_message_serialize.h @@ -0,0 +1,74 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +/********************************************************************* + * core_message_serialize.h + * + * OEMCrypto v16 Core Message Serialization library counterpart (a.k.a. KDO) + * + * This file declares functions to serialize response messages that will be + * parsed by Widevine clients (OEMCrypto/ODK). + * + * Please refer to core_message_types.h for details. + * + *********************************************************************/ + +#ifndef COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_H_ +#define COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_H_ + +#include "common/oemcrypto_core_message/odk/include/core_message_types.h" +#include "common/oemcrypto_core_message/odk/include/odk_structs.h" + +namespace oemcrypto_core_message { +namespace serialize { + +/** + * Counterpart (serializer) of ODK_ParseLicense (deserializer) + * struct-input variant + * + * Parameters: + * [in] parsed_lic + * [in] core_request + * [in] core_request_sha256 + * [out] oemcrypto_core_message + */ +bool CreateCoreLicenseResponse(const ODK_ParsedLicense& parsed_lic, + const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, + std::string* oemcrypto_core_message); + +/** + * Counterpart (serializer) of ODK_ParseRenewal (deserializer) + * + * Parameters: + * [in] core_request + * [in] renewal_duration_seconds + * [out] oemcrypto_core_message + */ +bool CreateCoreRenewalResponse(const ODK_RenewalRequest& core_request, + uint64_t renewal_duration_seconds, + std::string* oemcrypto_core_message); + +/** + * Counterpart (serializer) of ODK_ParseProvisioning (deserializer) + * struct-input variant + * + * Parameters: + * [in] parsed_prov + * [in] core_request + * [out] oemcrypto_core_message + */ +bool CreateCoreProvisioningResponse(const ODK_ParsedProvisioning& parsed_prov, + const ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message); +} // namespace serialize +} // namespace oemcrypto_core_message + +#endif // COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_H_ diff --git a/common/oemcrypto_core_message/odk/include/core_message_serialize_proto.h b/common/oemcrypto_core_message/odk/include/core_message_serialize_proto.h new file mode 100644 index 0000000..fd581e2 --- /dev/null +++ b/common/oemcrypto_core_message/odk/include/core_message_serialize_proto.h @@ -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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +/********************************************************************* + * core_message_serialize_proto.h + * + * These functions are an extension of those found in + * core_message_serialize.h. The difference is that these use the + * license and provisioning messages in protobuf format to create the core + * message. + *********************************************************************/ + +#ifndef COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_PROTO_H_ +#define COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_PROTO_H_ + +#include +#include + +#include "common/oemcrypto_core_message/odk/include/core_message_types.h" +#include "protos/public/certificate_provisioning.pb.h" +#include "protos/public/license_protocol.pb.h" + +namespace oemcrypto_core_message { +namespace serialize { + +// @ public create response (serializer) functions accepting proto input + +/** + * Counterpart (serializer) of ODK_ParseLicense (deserializer) + * + * Parameters: + * [in] serialized_license + serialized widevine::License + * [in] core_request oemcrypto core message from request. + * [in] core_request_sha256 - hash of serialized core request. + * [in] nonce_required - if the device should require a nonce match. + * [out] oemcrypto_core_message - the serialized oemcrypto core response. + */ +bool CreateCoreLicenseResponseFromProto(const std::string& serialized_license, + const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, + const bool nonce_required, + std::string* oemcrypto_core_message); + +/** + * Counterpart (serializer) of ODK_ParseProvisioning (deserializer) + * + * Parameters: + * [in] serialized_provisioning_response + * serialized widevine::ProvisioningResponse + * [in] core_request + * [out] oemcrypto_core_message + */ +bool CreateCoreProvisioningResponseFromProto( + const std::string& serialized_provisioning_response, + const ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message); + +} // namespace serialize +} // namespace oemcrypto_core_message + +#endif // COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_PROTO_H_ diff --git a/common/oemcrypto_core_message/odk/include/core_message_types.h b/common/oemcrypto_core_message/odk/include/core_message_types.h new file mode 100644 index 0000000..0786fad --- /dev/null +++ b/common/oemcrypto_core_message/odk/include/core_message_types.h @@ -0,0 +1,118 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +// clang-format off +/********************************************************************* + * core_message_types.h + * + * OEMCrypto v16 Core Message Serialization library counterpart (a.k.a. KDO) + * + * For Widevine Modular DRM, there are six message types between a server and + * a client device: license request and response, provisioning request and + * response, and renewal request and response. + * + * In OEMCrypto v15 and earlier, messages from the server were parsed by the + * CDM layer above OEMCrypto; the CDM in turn gave OEMCrypto a collection of + * pointers to protected data within the message. However, the pointers + * themselves were not signed by the server. + * + * Starting from OEMCrypto v16, all fields used by OEMCrypto in each of these + * messages have been identified in the document "Widevine Core Message + * Serialization". These fields are called the core of the message. Core + * message fields are (de)serialized using the ODK, a C library provided by + * Widevine. OEMCrypto will parse and verify the core of the message with + * help from the ODK. + * + * The KDO library is the counterpart of ODK used in the CDM & Widevine + * servers. For each message type generated by the ODK, KDO provides a + * corresponding parser. For each message type to be parsed by the ODK, + * KDO provides a corresponding writer. + * + * Table: ODK vs KDO (s: serialize; d: deserialize) + * +----------------------------------------+---------------------------------------+ + * | ODK | KDO | + * +---+------------------------------------+---+-----------------------------------+ + * | s | ODK_PrepareCoreLicenseRequest | d | CoreLicenseRequestFromMessage | + * | +------------------------------------+ +-----------------------------------+ + * | | ODK_PrepareCoreRenewalRequest | | CoreRenewalRequestFromMessage | + * | +------------------------------------+ +-----------------------------------+ + * | | ODK_PrepareCoreProvisioningRequest | | CoreProvisioningRequestFromMessage| + * | +------------------------------------+ +-----------------------------------+ + * | | ODK_PrepareCommonRequest | | CoreCommonRequestFromMessage | + * +---+------------------------------------+---+-----------------------------------+ + * | d | ODK_ParseLicense | s | CreateCoreLicenseResponse | + * | +------------------------------------+ +-----------------------------------+ + * | | ODK_ParseRenewal | | CreateCoreRenewalResponse | + * | +------------------------------------+ +-----------------------------------+ + * | | ODK_ParseProvisioning | | CreateCoreProvisioningResponse | + * +---+------------------------------------+---+-----------------------------------+ + * + *********************************************************************/ +// clang-format on + +#ifndef COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_CORE_MESSAGE_TYPES_H_ +#define COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_CORE_MESSAGE_TYPES_H_ + +#include +#include + +namespace oemcrypto_core_message { + +// @ input/output structs + +/** + * Output structure for CommonRequestFromMessage + * Input structure for CreateCommonResponse + */ +struct ODK_CommonRequest { + uint16_t api_minor_version; + uint16_t api_major_version; + uint32_t nonce; + uint32_t session_id; +}; + +/** + * Output structure for CoreLicenseRequestFromMessage + * Input structure for CreateCoreLicenseResponse + */ +struct ODK_LicenseRequest { + uint16_t api_minor_version; + uint16_t api_major_version; + uint32_t nonce; + uint32_t session_id; +}; + +/** + * Output structure for CoreRenewalRequestFromMessage + * Input structure for CreateCoreRenewalResponse + */ +struct ODK_RenewalRequest { + uint16_t api_minor_version; + uint16_t api_major_version; + uint32_t nonce; + uint32_t session_id; + uint64_t playback_time_seconds; +}; + +/** + * Output structure for CoreProvisioningRequestFromMessage + * Input structure for CreateCoreProvisioningResponse + */ +struct ODK_ProvisioningRequest { + uint16_t api_minor_version; + uint16_t api_major_version; + uint32_t nonce; + uint32_t session_id; + std::string device_id; +}; + +} // namespace oemcrypto_core_message + +#endif // COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_CORE_MESSAGE_TYPES_H_ diff --git a/common/oemcrypto_core_message/odk/include/odk.h b/common/oemcrypto_core_message/odk/include/odk.h new file mode 100644 index 0000000..d2fe1bd --- /dev/null +++ b/common/oemcrypto_core_message/odk/include/odk.h @@ -0,0 +1,613 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +/** + * @mainpage OEMCrypto v16 Core Message Serialization library + * + * For Widevine Modular DRM, there are six message types between a server and + * a client device: license request and response, provisioning request and + * response, and renewal request and response. + * + * In OEMCrypto v15 and earlier, messages from the server were parsed by the + * CDM layer above OEMCrypto; the CDM in turn gave OEMCrypto a collection of + * pointers to protected data within the message. However, the pointers + * themselves were not signed by the server. + * + * Starting from OEMCrypto v16, all fields used by OEMCrypto in each of these + * messages have been identified in the document "Widevine Core Message + * Serialization". These fields are called the core of the message. Core + * message fields are (de)serialized using the ODK, a C library provided by + * Widevine. OEMCrypto will parse and verify the core of the message with + * help from the ODK. + * + * The ODK functions that parse code will fill out structs that have similar + * formats to the function parameters of the OEMCrypto v15 functions being + * replaced. The ODK will be provided in source code and it is Widevine's + * intention that partners can build and link ODK with their implementation + * of OEMCrypto with no or few code changes. + * + * OEMCrypto implementers shall build the ODK library as part of the Trusted + * Application (TA) running in the TEE. All memory and buffers used by the + * ODK library shall be sanitized by the OEMCrypto implementer to prevent + * modification by any process running the REE. + * + * See the documents + * Widevine Core Message Serialization + * and + * License Duration and Renewal + * for a detailed description of the ODK API. You can + * find these documents in the widevine repository as + * docs/Widevine_Core_Message_Serialization.pdf and + * docs/License_Duration_and_Renewal.pdf + * + * @defgroup odk_parser Core Message Parsing and Verification + * Functions that parse core messages and verify they are valid. + * TODO(user): add documentation for parsing functions. + * + * @defgroup odk_packer Core Message Creation + * Functions that create core messages. + * TODO(user): add documentation for packing functions. + * + * @defgroup odk_timer Timer and Clock Functions + * Functions related to enforcing timer and duration restrictions. + * TODO(user): add documentation for timers and clocks. + * + * @defgroup common_types Common Types + * Enumerations and structures that are used by several OEMCrypto and ODK + * functions. + *********************************************************************/ + +#ifndef COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_ODK_H_ +#define COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_ODK_H_ + +#include + +#include "common/oemcrypto_core_message/odk/include/OEMCryptoCENCCommon.h" +#include "common/oemcrypto_core_message/odk/include/odk_structs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/// @addtogroup odk_timer +/// @{ + +/** + * This function initializes the session's data structures. It shall be + * called from OEMCrypto_OpenSession. + * + * @param[out] timer_limits: the session's timer limits. + * @param[out] clock_values: the session's clock values. + * @param[out] nonce_values: the session's ODK nonce values. + * @param[in] api_major_version: the API version of OEMCrypto. + * @param[in] session_id: the session id of the newly created session. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, + uint32_t api_major_version, + uint32_t session_id); + +/** + * This function sets the nonce value in the session's nonce structure. It + * shall be called from OEMCrypto_GenerateNonce. + * + * @param[in,out] nonce_values: the session's nonce data. + * @param[in] nonce: the new nonce that was just generated. + * + * @retval true on success + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_SetNonceValues(ODK_NonceValues* nonce_values, + uint32_t nonce); + +/** + * This function initializes the clock values in the session clock_values + * structure. It shall be called from OEMCrypto_PrepAndSignLicenseRequest. + * + * Parameters: + * @param[in,out] clock_values: the session's clock data. + * @param[in] system_time_seconds: the current time on OEMCrypto's monotonic + * clock. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values, + uint64_t system_time_seconds); + +/** + * This function sets the values in the clock_values structure. It shall be + * called from OEMCrypto_LoadUsageEntry. When a usage entry from a v15 or + * earlier license is loaded, the value time_of_license_loaded shall be used + * in place of time_of_license_signed. + * + * @param[in,out] clock_values: the session's clock data. + * @param[in] time_of_license_signed: the value time_license_received from the + * loaded usage entry. + * @param[in] time_of_first_decrypt: the value time_of_first_decrypt from the + * loaded usage entry. + * @param[in] time_of_last_decrypt: the value time_of_last_decrypt from the + * loaded usage entry. + * @param[in] status: the value status from the loaded usage entry. + * @param[in] system_time_seconds: the current time on OEMCrypto's monotonic + * clock. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_ReloadClockValues(ODK_ClockValues* clock_values, + uint64_t time_of_license_signed, + uint64_t time_of_first_decrypt, + uint64_t time_of_last_decrypt, + enum OEMCrypto_Usage_Entry_Status status, + uint64_t system_time_seconds); + +/** + * This updates the clock values, and determines if playback may start based + * on the given system time. It uses the values in clock_values to determine + * if this is the first playback for the license or the first playback for + * just this session. + * + * This shall be called from the first call in a session to any of + * OEMCrypto_DecryptCENC or any of the OEMCrypto_Generic* functions. + * + * If OEMCrypto uses a hardware timer, and this function returns + * ODK_SET_TIMER, then the timer should be set to the value pointed to by + * timer_value. + * + * @param[in] system_time_seconds: the current time on OEMCrypto's monotonic + * clock, in seconds. + * @param[in] timer_limits: timer limits specified in the license. + * @param[in,out] clock_values: the sessions clock values. + * @param[out] timer_value: set to the new timer value. Only used if the return + * value is ODK_SET_TIMER. This must be non-null if OEMCrypto uses a + * hardware timer. + * + * @retval ODK_SET_TIMER: Success. The timer should be reset to the specified + * value and playback is allowed. + * @retval ODK_DISABLE_TIMER: Success, but disable timer. Unlimited playback is + * allowed. + * @retval ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_AttemptFirstPlayback(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value); + +/** + * Vendors that do not implement their own timer should call + * ODK_UpdateLastPlaybackTime regularly during playback. This updates the + * clock values, and determines if playback may continue based on the given + * system time. This shall be called from any of OEMCrypto_DecryptCENC or any + * of the OEMCrypto_Generic* functions. + * + * All Vendors (i.e. those that do or do not implement their own timer) shall + * call ODK_UpdateLastPlaybackTime from the function + * OEMCrypto_UpdateUsageEntry before updating the usage entry so that the + * clock values are accurate. + * + * @param[in] system_time_seconds: the current time on OEMCrypto's monotonic + * clock, in seconds. + * @param[in] timer_limits: timer limits specified in the license. + * @param[in,out] clock_values: the sessions clock values. + * + * @retval OEMCrypto_SUCCESS: Success. Playback is allowed. + * @retval ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_UpdateLastPlaybackTime(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values); + +/** + * This function modifies the session's clock values to indicate that the + * license has been deactivated. It shall be called from + * OEMCrypto_DeactivateUsageEntry + * + * Parameters: + * @param[in,out] clock_values: the sessions clock values. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values); + +/// @} + +/// @addtogroup odk_packer +/// @{ + +/** + * Modifies the message to include a core license request at the beginning of + * the message buffer. The values in nonce_values are used to populate the + * message. + * + * This shall be called by OEMCrypto from OEMCrypto_PrepAndSignLicenseRequest. + * + * NOTE: if the message pointer is null and/or input core_message_size is + * zero, this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * core_message_size to the size needed. + * + * @param[in,out] message: Pointer to memory for the entire message. Modified by + * the ODK library. + * @param[in] message_length: length of the entire message buffer. + * @param[in,out] core_message_size: length of the core message at the beginning + * of the message. (in) size of buffer reserved for the core message, in + * bytes. (out) actual length of the core message, in bytes. + * @param[in] nonce_values: pointer to the session's nonce data. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_SHORT_BUFFER: core_message_size is too small + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_PrepareCoreLicenseRequest( + uint8_t* message, size_t message_length, size_t* core_message_size, + const ODK_NonceValues* nonce_values); + +/** + * Modifies the message to include a core renewal request at the beginning of + * the message buffer. The values in nonce_values, clock_values and + * system_time_seconds are used to populate the message. The nonce_values + * should match those from the license. + * + * This shall be called by OEMCrypto from OEMCrypto_PrepAndSignRenewalRequest. + * + * If status in clock_values indicates that a license has not been loaded, + * then this is a license release. The ODK library will change the value of + * nonce_values.api_major_version to 15. This will make + * OEMCrypto_PrepAndSignRenewalRequest sign just the message body, as it does + * for all legacy licenses. + * + * NOTE: if the message pointer is null and/or input core_message_size is + * zero, this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * core_message_size to the size needed. + * + * @param[in,out] message: Pointer to memory for the entire message. Modified by + * the ODK library. + * @param[in] message_length: length of the entire message buffer. + * @param[in,out] core_message_size: length of the core message at the beginning + * of the message. (in) size of buffer reserved for the core message, in + * bytes. (out) actual length of the core message, in bytes. + * @param[in,out] nonce_values: pointer to the session's nonce data. + * @param[in,out] clock_values: the session's clock values. + * @param[in] system_time_seconds: the current time on OEMCrypto's clock, in + * seconds. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_SHORT_BUFFER: core_message_size is too small + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_PrepareCoreRenewalRequest(uint8_t* message, + size_t message_length, + size_t* core_message_size, + ODK_NonceValues* nonce_values, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds); + +/** + * Modifies the message to include a core provisioning request at the + * beginning of the message buffer. The values in nonce_values are used to + * populate the message. + * + * This shall be called by OEMCrypto from + * OEMCrypto_PrepAndSignProvisioningRequest. + * + * The buffer device_id shall be the same std::string returned by + * OEMCrypto_GetDeviceID. The device ID shall be unique to the device, and + * stable across reboots and factory resets for an L1 device. + * + * NOTE: if the message pointer is null and/or input core_message_size is + * zero, this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * core_message_size to the size needed. + * + * @param[in,out] message: Pointer to memory for the entire message. Modified by + * the ODK library. + * @param[in] message_length: length of the entire message buffer. + * @param[in,out] core_message_size: length of the core message at the beginning + * of the message. (in) size of buffer reserved for the core message, in + * bytes. (out) actual length of the core message, in bytes. + * @param[in] nonce_values: pointer to the session's nonce data. + * @param[in] device_id: For devices with a keybox, this is the device ID from + * the keybox. For devices with an OEM Certificate, this is a device + * unique id string. + * @param[in] device_id_length: length of device_id. The device ID can be at + * most 64 bytes. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_SHORT_BUFFER: core_message_size is too small + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_PrepareCoreProvisioningRequest( + uint8_t* message, size_t message_length, size_t* core_message_size, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length); + +/// @} + +/// @addtogroup odk_timer +/// @{ + +/** + * This function sets all limits in the timer_limits struct to the + * key_duration and initializes the other values. The field + * nonce_values.api_major_version will be set to 15. It shall be called from + * OEMCrypto_LoadKeys when loading a legacy license. + * + * @param[out] timer_limits: The session's timer limits. + * @param[in,out] clock_values: The session's clock values. + * @param[in,out] nonce_values: The session's ODK nonce values. + * @param[in] key_duration: The duration from the first key's key control + * block. In practice, the key duration is the same for all keys and is + * the same as the license duration. + * @param[in] system_time_seconds: The current time on the system clock, as + * described in the document "License Duration and Renewal". + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, + uint32_t key_duration, + uint64_t system_time_seconds); + +/** + * This function updates the clock_values as needed if a v15 renewal is + * accepted. The field nonce_values.api_major_version is verified to be 15. + * + * This is called from OEMCrypto_RefreshKeys for a valid license renewal. + * OEMCrypto shall pass in the current system time, and the key duration from + * the first object in the OEMCrypto_KeyRefreshObject. + * + * @param[in] timer_limits: The session's timer limits. + * @param[in,out] clock_values: The session's clock values. + * @param[in] nonce_values: The session's ODK nonce values. + * @param[in] system_time_seconds: The current time on the system clock, as + * described in the document "License Duration and Renewal". + * @param[in] new_key_duration: The duration from the first + * OEMCrypto_KeyRefreshObject in key_array. + * @param[out] timer_value: set to the new timer value. Only used if the return + * value is ODK_SET_TIMER. This must be non-null if OEMCrypto uses a + * hardware timer. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval ODK_SET_TIMER: Success. The timer should be reset to the specified + * value and playback is allowed. + * @retval ODK_DISABLE_TIMER: Success, but disable timer. Unlimited playback is + * allowed. + * @retval ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + const ODK_NonceValues* nonce_values, + uint64_t system_time_seconds, + uint32_t new_key_duration, + uint64_t* timer_value); + +/// @} + +/// @addtogroup odk_parser +/// @{ + +/** + * The function ODK_ParseLicense will parse the message and verify fields in + * the message. + * + * If the message does not parse correctly, ODK_VerifyAndParseLicense will + * return ODK_ERROR_CORE_MESSAGE that OEMCrypto should return to the CDM + * layer above. + * + * If the API in the message is not 16, then ODK_UNSUPPORTED_API is returned. + * + * If initial_license_load is true, and nonce_required in the license is + * true, then the ODK library shall verify that nonce_values->nonce and + * nonce_values->session_id are the same as those in the message. If + * verification fails, then it shall return OEMCrypto_ERROR_INVALID_NONCE. + * + * If initial_license_load is false, and nonce_required is true, then + * ODK_ParseLicense will set the values in nonce_values from those in the + * message. + * + * The function ODK_ParseLicense will verify that each substring points to a + * location in the message body. The message body is the buffer starting at + * message + core_message_length with size message_length - + * core_message_length. + * + * If initial_license_load is true, then ODK_ParseLicense shall verify that + * the parameter request_hash matches request_hash in the parsed license. If + * verification fails, then it shall return ODK_ERROR_CORE_MESSAGE. This was + * computed by OEMCrypto when the license was requested. + * + * If usage_entry_present is true, then ODK_ParseLicense shall verify that + * the pst in the license has a nonzero length. + * + * @param[in] message: pointer to the message buffer. + * @param[in] message_length: length of the entire message buffer. + * @param[in] core_message_size: length of the core message, at the beginning of + * the message buffer. + * @param[in] initial_license_load: true when called for OEMCrypto_LoadLicense + * and false when called for OEMCrypto_ReloadLicense. + * @param[in] usage_entry_present: true if the session has a new usage entry + * associated with it created via OEMCrypto_CreateNewUsageEntry. + * @param[in] request_hash: the hash of the license request core message. This + * was computed by OEMCrypto when the license request was signed. + * @param[in,out] timer_limits: The session's timer limits. These will be + * updated. + * @param[in,out] clock_values: The session's clock values. These will be + * updated. + * @param[in,out] nonce_values: The session's nonce values. These will be + * updated. + * @param[out] parsed_license: the destination for the data. + * + * @retval OEMCrypto_SUCCESS + * @retval ODK_ERROR_CORE_MESSAGE: if the message did not parse correctly, or + * there were other incorrect values. An error should be returned to the + * CDM layer. + * @retval ODK_UNSUPPORTED_API + * @retval OEMCrypto_ERROR_INVALID_NONCE + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_ParseLicense( + const uint8_t* message, size_t message_length, size_t core_message_length, + bool initial_license_load, bool usage_entry_present, + const uint8_t request_hash[ODK_SHA256_HASH_SIZE], + ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, ODK_ParsedLicense* parsed_license); + +/** + * The function ODK_ParseRenewal will parse the message and verify its + * contents. If the message does not parse correctly, an error of + * ODK_ERROR_CORE_MESSAGE is returned. + * + * ODK_ParseRenewal shall verify that all fields in nonce_values match those + * in the license. Otherwise it shall return OEMCrypto_ERROR_INVALID_NONCE. + * + * After parsing the message, this function updates the clock_values based on + * the timer_limits and the current system time. If playback may not + * continue, then ODK_TIMER_EXPIRED is returned. + * + * If playback may continue, a return value of ODK_SET_TIMER or + * ODK_TIMER_EXPIRED is returned. If the return value is ODK_SET_TIMER, then + * playback may continue until the timer expires. If the return value is + * ODK_DISABLE_TIMER, then playback time is not limited. + * + * If OEMCrypto uses a hardware timer, and this function returns + * ODK_SET_TIMER, then OEMCrypto shall set the timer to the value pointed to + * by timer_value. + * + * @param[in] message: pointer to the message buffer. + * @param[in] message_length: length of the entire message buffer. + * @param[in] core_message_size: length of the core message, at the beginning of + * the message buffer. + * @param[in] nonce_values: pointer to the session's nonce data. + * @param[in] system_time_seconds: the current time on OEMCrypto's clock, in + * seconds. + * @param[in] timer_limits: timer limits specified in the license. + * @param[in,out] clock_values: the sessions clock values. + * @param[out] timer_value: set to the new timer value. Only used if the return + * value is ODK_SET_TIMER. This must be non-null if OEMCrypto uses a + * hardware timer. + * + * @retval ODK_ERROR_CORE_MESSAGE: the message did not parse correctly, or there + * were other incorrect values. An error should be returned to the CDM + * layer. + * @retval ODK_SET_TIMER: Success. The timer should be reset to the specified + * timer value. + * @retval ODK_DISABLE_TIMER: Success, but disable timer. Unlimited playback is + * allowed. + * @retval ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. + * @retval ODK_UNSUPPORTED_API + * @retval ODK_STALE_RENEWAL: This renewal is not the most recently signed. It + * is rejected. + * @retval OEMCrypto_ERROR_INVALID_NONCE + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, + size_t core_message_length, + const ODK_NonceValues* nonce_values, + uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value); + +/** + * The function ODK_ParseProvisioning will parse the message and verify the + * nonce values match those in the license. + * + * If the message does not parse correctly, ODK_ParseProvisioning will return + * an error that OEMCrypto should return to the CDM layer above. + * + * If the API in the message is larger than 16, then ODK_UNSUPPORTED_API is + * returned. + * + * ODK_ParseProvisioning shall verify that nonce_values->nonce and + * nonce_values->session_id are the same as those in the message. Otherwise + * it shall return OEMCrypto_ERROR_INVALID_NONCE. + * + * The function ODK_ParseProvisioning will verify that each substring points + * to a location in the message body. The message body is the buffer starting + * at message + core_message_length with size message_length - + * core_message_length. + * + * @param[in] message: pointer to the message buffer. + * @param[in] message_length: length of the entire message buffer. + * @param[in] core_message_size: length of the core message, at the beginning of + * the message buffer. + * @param[in] nonce_values: pointer to the session's nonce data. + * @param[in] device_id: a pointer to a buffer containing the device ID of the + * device. The ODK function will verify it matches that in the message. + * @param[in] device_id_length: the length of the device ID. + * @param[out] parsed_response: destination for the parse data. + * + * @retval OEMCrypto_SUCCESS + * @retval ODK_ERROR_CORE_MESSAGE: the message did not parse correctly, or there + * were other incorrect values. An error should be returned to the CDM + * layer. + * @retval ODK_UNSUPPORTED_API + * @retval OEMCrypto_ERROR_INVALID_NONCE + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_ParseProvisioning( + const uint8_t* message, size_t message_length, size_t core_message_length, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length, ODK_ParsedProvisioning* parsed_response); + +/// @} + +#ifdef __cplusplus +} +#endif + +#endif // COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_ODK_H_ diff --git a/common/oemcrypto_core_message/odk/include/odk_attributes.h b/common/oemcrypto_core_message/odk/include/odk_attributes.h new file mode 100644 index 0000000..034fced --- /dev/null +++ b/common/oemcrypto_core_message/odk/include/odk_attributes.h @@ -0,0 +1,20 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_ODK_ATTRIBUTES_H_ +#define COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_ODK_ATTRIBUTES_H_ + +#if defined(__GNUC__) || defined(__clang__) +#define UNUSED __attribute__((__unused__)) +#else +#define UNUSED +#endif + +#endif // COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_ODK_ATTRIBUTES_H_ diff --git a/common/oemcrypto_core_message/odk/include/odk_structs.h b/common/oemcrypto_core_message/odk/include/odk_structs.h new file mode 100644 index 0000000..2a7b07b --- /dev/null +++ b/common/oemcrypto_core_message/odk/include/odk_structs.h @@ -0,0 +1,221 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_ODK_STRUCTS_H_ +#define COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_ODK_STRUCTS_H_ + +#include + +#include "common/oemcrypto_core_message/odk/include/OEMCryptoCENCCommon.h" +#include "common/oemcrypto_core_message/odk/include/odk_target.h" + +/* The version of this library. */ +#define ODK_MAJOR_VERSION 16 +#define ODK_MINOR_VERSION 3 + +/* ODK Version string. Date changed automatically on each release. */ +#define ODK_RELEASE_DATE "ODK v16.3 AUTOGEN_RELEASE_DATE" + +/* The lowest version number for an ODK message. */ +#define ODK_FIRST_VERSION 16 + +/* Some useful constants. */ +#define ODK_DEVICE_ID_LEN_MAX 64 +#define ODK_SHA256_HASH_SIZE 32 + +/// @addtogroup odk_timer +/// @{ + +/** + * Timer limits are specified in a license and are used to determine when + * playback is allowed. See the document "License Duration and Renewal" for a + * discussion on the time restrictions that may be placed on a license. The + * fields in this structure are directly related to the fields in the core + * license message. The fields are set when OEMCrypto calls the function + * ODK_ParseLicense or ODK_InitializeV15Values. + * + * @param soft_enforce_rental_duration: A boolean controlling the soft or hard + * enforcement of rental duration. + * @param soft_enforce_playback_duration: A boolean controlling the soft or hard + * enforcement of playback duration. + * @param earliest_playback_start_seconds: The earliest time that the first + * playback is allowed. Measured in seconds since the license request was + * signed. For most use cases, this is zero. + * @param rental_duration_seconds: Window of time for the allowed first + * playback. Measured in seconds since the earliest playback start. If + * soft_enforce_rental_duration is true, this applies only to the first + * playback. If soft_enforce_rental_duration is false, then this + * restricts any playback. A value of zero means no limit. + * @param total_playback_duration_seconds: Window of time for allowed playback. + * Measured in seconds since the first playback start. If + * soft_enforce_playback_duration is true, this applies only to the start + * of playback for any session. If soft_enforce_playback_duration is + * false, then this restricts any playback. A value of zero means no + * limit. + * @param initial_renewal_duration_seconds: Window of time for allowed playback. + * Measured in seconds since the first playback start. This value is only + * used to start the renewal timer. After a renewal message is loaded, + * the timer will be reset. A value of zero means no limit. + * + * @version + * This struct changed in API version 16.2. + */ +typedef struct { + bool soft_enforce_rental_duration; + bool soft_enforce_playback_duration; + uint64_t earliest_playback_start_seconds; + uint64_t rental_duration_seconds; + uint64_t total_playback_duration_seconds; + uint64_t initial_renewal_duration_seconds; +} ODK_TimerLimits; + +/** + * Clock values are modified when decryption occurs or when a renewal is + * processed. They are used to track the current status of the license -- + * i.e. has playback started? When does the timer expire? See the section + * "Complete ODK API" of the document "Widevine Core Message Serialization" + * for a complete list of all fields in this structure. Most of these values + * shall be saved with the usage entry. + * + * All times are in seconds. Most of the fields in this structure are saved + * in the usage entry. This structure should be initialized when a usage + * entry is created or loaded, and should be used to save a usage entry. It + * is updated using the ODK functions listed below. The time values are based + * on OEMCrypto's system clock, as described in the document "License + * Duration and Renewal". + * + * @param time_of_license_signed: Time that the license request was signed, + * based on OEMCrypto's system clock. This value shall be stored and + * reloaded with usage entry as time_of_license_received. + * @param time_of_first_decrypt: Time of the first decrypt or call select key, + * based on OEMCrypto's system clock. This is 0 if the license has not + * been used to decrypt any data. This value shall be stored and reloaded + * with usage entry. + * @param time_of_last_decrypt: Time of the most recent decrypt call, based on + * OEMCrypto's system clock. This value shall be stored and reloaded with + * usage entry. + * @param time_of_renewal_request: Time of the most recent renewal request, + * based on OEMCrypto's system clock. This is used to verify that a + * renewal is not stale. + * @param time_when_timer_expires: Time that the current timer expires, based on + * OEMCrypto's system clock. If the timer is active, this is used by the + * ODK library to determine if it has expired. + * @param timer_status: Used internally by the ODK library to indicate the + * current timer status. + * @param status: The license or usage entry status. This value shall be stored + * and reloaded with usage entry. + * + * @version + * This struct changed in API version 16.2. + */ +typedef struct { + uint64_t time_of_license_signed; + uint64_t time_of_first_decrypt; + uint64_t time_of_last_decrypt; + uint64_t time_of_renewal_request; + uint64_t time_when_timer_expires; + uint32_t timer_status; + enum OEMCrypto_Usage_Entry_Status status; +} ODK_ClockValues; + +/** + * Nonce values are used to match a license or provisioning request to a + * license or provisioning response. They are also used to match a renewal + * request and response to a license. For this reason, the api_version might + * be lower than that supported by OEMCrypto. The api_version matches the + * version of the license. Similarly the nonce and session_id match the + * session that generated the license request. For an offline license, these + * might not match the session that is loading the license. We use the nonce + * to prevent a license from being replayed. By also including a session_id + * in the license request and license response, we prevent an attack using + * the birthday paradox to generate nonce collisions on a single device. + * + * @param api_major_version: the API version of the license. This is initialized + * to the API version of the ODK library, but may be lower. + * @param api_minor_version: the minor version of the ODK library. This is used + * by the server to verify that device is not using an obsolete version + * of the ODK library. + * @param nonce: a randomly generated number used to prevent replay attacks. + * @param session_id: the session id of the session which signed the license or + * provisioning request. It is used to prevent replay attacks from one + * session to another. + * + * @version + * This struct changed in API version 16.2. + */ +typedef struct { + uint16_t api_minor_version; + uint16_t api_major_version; + uint32_t nonce; + uint32_t session_id; +} ODK_NonceValues; + +/// @} + +/// @addtogroup odk_parser +/// @{ + +/** + * The parsed license structure contains information from the license + * message. The function ODK_ParseLicense will fill in the fields of this + * message. All substrings are contained within the message body. + * + * @param enc_mac_keys_iv: IV for decrypting new mac_key. Size is 128 bits. + * @param enc_mac_keys: encrypted mac_keys for generating new mac_keys. Size is + * 512 bits. + * @param pst: the Provider Session Token. + * @param srm_restriction_data: optional data specifying the minimum SRM + * version. + * @param license_type: specifies if the license contains content keys or + * entitlement keys. + * @param nonce_required: indicates if the license requires a nonce. + * @param timer_limits: time limits of the for the license. + * @param key_array_length: number of keys present. + * @param key_array: set of keys to be installed. + * + * @version + * This struct changed in API version 16.2. + */ +typedef struct { + OEMCrypto_Substring enc_mac_keys_iv; + OEMCrypto_Substring enc_mac_keys; + OEMCrypto_Substring pst; + OEMCrypto_Substring srm_restriction_data; + OEMCrypto_LicenseType license_type; + bool nonce_required; + ODK_TimerLimits timer_limits; + uint32_t key_array_length; + OEMCrypto_KeyObject key_array[ODK_MAX_NUM_KEYS]; +} ODK_ParsedLicense; + +/** + * The parsed provisioning structure contains information from the license + * message. The function ODK_ParseProvisioning will fill in the fields of + * this message. All substrings are contained within the message body. + * + * @param key_type: indicates if this key is an RSA or ECC private key. + * @param enc_private_key: encrypted private key for the DRM certificate. + * @param enc_private_key_iv: IV for decrypting new private key. Size is 128 + * bits. + * @param encrypted_message_key: used for provisioning 3.0 to derive keys. + * + * @version + * This struct changed in API version 16.2. + */ +typedef struct { + OEMCrypto_PrivateKeyType key_type; + OEMCrypto_Substring enc_private_key; + OEMCrypto_Substring enc_private_key_iv; + OEMCrypto_Substring encrypted_message_key; /* Used for Prov 3.0 */ +} ODK_ParsedProvisioning; + +/// @} + +#endif // COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_ODK_STRUCTS_H_ diff --git a/common/oemcrypto_core_message/odk/include/odk_target.h b/common/oemcrypto_core_message/odk/include/odk_target.h new file mode 100644 index 0000000..6636a5a --- /dev/null +++ b/common/oemcrypto_core_message/odk/include/odk_target.h @@ -0,0 +1,19 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file is distributed +// under the Widevine Master License Agreement. + +// Partners are expected to edit this file to support target specific code +// and limits. + +#ifndef COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_ODK_TARGET_H_ +#define COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_ODK_TARGET_H_ + +// Maximum number of keys can be modified to suit target's resource tier. +#define ODK_MAX_NUM_KEYS 32 + +#endif // COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_INCLUDE_ODK_TARGET_H_ diff --git a/common/oemcrypto_core_message/odk/src/core_message_deserialize.cpp b/common/oemcrypto_core_message/odk/src/core_message_deserialize.cpp new file mode 100644 index 0000000..c50bcea --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/core_message_deserialize.cpp @@ -0,0 +1,148 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "common/oemcrypto_core_message/odk/include/core_message_deserialize.h" + +#include +#include +#include +#include +#include + +#include "common/oemcrypto_core_message/odk/include/odk_structs.h" +#include "common/oemcrypto_core_message/odk/src/odk_serialize.h" +#include "common/oemcrypto_core_message/odk/src/odk_structs_priv.h" +#include "common/oemcrypto_core_message/odk/src/serialization_base.h" + +namespace oemcrypto_core_message { +namespace deserialize { +namespace { + +/** + * Template for parsing requests + * + * Template arguments: + * S: kdo output struct + * T: struct serialized by odk + * U: auto-generated deserializing function for |T| + */ +template +bool ParseRequest(uint32_t message_type, + const std::string& oemcrypto_core_message, S* core_request, + T* prepared, const U unpacker) { + if (core_request == nullptr || prepared == nullptr) { + return false; + } + + const uint8_t* buf = + reinterpret_cast(oemcrypto_core_message.c_str()); + const size_t buf_length = oemcrypto_core_message.size(); + + uint8_t blk[SIZE_OF_MESSAGE_STRUCT]; + Message* msg = reinterpret_cast(blk); + InitMessage(msg, const_cast(buf), buf_length); + SetSize(msg, buf_length); + + unpacker(msg, prepared); + if (!ValidMessage(msg)) { + return false; + } + + const auto& core_message = prepared->core_message; + core_request->api_major_version = core_message.nonce_values.api_major_version; + core_request->api_minor_version = core_message.nonce_values.api_minor_version; + core_request->nonce = core_message.nonce_values.nonce; + core_request->session_id = core_message.nonce_values.session_id; + // Verify that the minor version matches the released version for the given + // major version. + if (core_request->api_major_version < ODK_FIRST_VERSION) { + // Non existing versions are not supported. + return false; + } else if (core_request->api_major_version == 16) { + // For version 16, we demand a minor version of at least 2. + // We accept 16.2, 16.3, or higher. + if (core_request->api_major_version < 2) return false; + } else { + // Other versions do not (yet) have a restriction on minor number. + // In particular, future versions are accepted for forward compatibility. + } + // For v16, a release and a renewal use the same message structure. + // However, for future API versions, the release might be a separate + // message. Otherwise, we expect an exact match of message types. + if (message_type != ODK_Common_Request_Type && + core_message.message_type != message_type && + !(message_type == ODK_Renewal_Request_Type && + core_message.message_type == ODK_Release_Request_Type)) { + return false; + } + // Verify that the amount of buffer we read, which is GetOffset, is not more + // than the total message size. We allow the total message size to be larger + // for forward compatibility because future messages might have extra fields + // that we can ignore. + if (core_message.message_length < GetOffset(msg)) return false; + return true; +} + +} // namespace + +bool CoreLicenseRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_LicenseRequest* core_license_request) { + const auto unpacker = Unpack_ODK_PreparedLicenseRequest; + ODK_PreparedLicenseRequest prepared_license = {}; + return ParseRequest(ODK_License_Request_Type, oemcrypto_core_message, + core_license_request, &prepared_license, unpacker); +} + +bool CoreRenewalRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_RenewalRequest* core_renewal_request) { + const auto unpacker = Unpack_ODK_PreparedRenewalRequest; + ODK_PreparedRenewalRequest prepared_renewal = {}; + if (!ParseRequest(ODK_Renewal_Request_Type, oemcrypto_core_message, + core_renewal_request, &prepared_renewal, unpacker)) { + return false; + } + core_renewal_request->playback_time_seconds = prepared_renewal.playback_time; + return true; +} + +bool CoreProvisioningRequestFromMessage( + const std::string& oemcrypto_core_message, + ODK_ProvisioningRequest* core_provisioning_request) { + const auto unpacker = Unpack_ODK_PreparedProvisioningRequest; + ODK_PreparedProvisioningRequest prepared_provision = {}; + if (!ParseRequest(ODK_Provisioning_Request_Type, oemcrypto_core_message, + core_provisioning_request, &prepared_provision, unpacker)) { + return false; + } + const uint8_t* device_id = prepared_provision.device_id; + const uint32_t device_id_length = prepared_provision.device_id_length; + if (device_id_length > ODK_DEVICE_ID_LEN_MAX) { + return false; + } + uint8_t zero[ODK_DEVICE_ID_LEN_MAX] = {}; + if (memcmp(zero, device_id + device_id_length, + ODK_DEVICE_ID_LEN_MAX - device_id_length)) { + return false; + } + core_provisioning_request->device_id.assign( + reinterpret_cast(device_id), device_id_length); + return true; +} + +bool CoreCommonRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_CommonRequest* common_request) { + const auto unpacker = Unpack_ODK_PreparedCommonRequest; + ODK_PreparedCommonRequest prepared_common = {}; + return ParseRequest(ODK_Common_Request_Type, oemcrypto_core_message, + common_request, &prepared_common, unpacker); +} + +} // namespace deserialize +} // namespace oemcrypto_core_message diff --git a/common/oemcrypto_core_message/odk/src/core_message_serialize.cpp b/common/oemcrypto_core_message/odk/src/core_message_serialize.cpp new file mode 100644 index 0000000..bdcb2b8 --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/core_message_serialize.cpp @@ -0,0 +1,131 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "common/oemcrypto_core_message/odk/include/core_message_serialize.h" + +#include +#include +#include +#include +#include + +#include "common/oemcrypto_core_message/odk/include/odk_structs.h" +#include "common/oemcrypto_core_message/odk/src/odk_serialize.h" +#include "common/oemcrypto_core_message/odk/src/odk_structs_priv.h" +#include "common/oemcrypto_core_message/odk/src/serialization_base.h" + +namespace oemcrypto_core_message { +namespace serialize { +namespace { + +/** + * Template for parsing requests + * + * Template arguments: + * T: struct to be deserialized by odk + * S: kdo input struct + * P: auto-generated serializing function for |T| + */ +template +bool CreateResponse(uint32_t message_type, const S& core_request, + std::string* oemcrypto_core_message, T& response, + const P& packer) { + if (!oemcrypto_core_message) { + return false; + } + + auto* header = &response.request.core_message; + header->message_type = message_type; + header->nonce_values.api_major_version = core_request.api_major_version; + header->nonce_values.api_minor_version = core_request.api_minor_version; + header->nonce_values.nonce = core_request.nonce; + header->nonce_values.session_id = core_request.session_id; + // The message API version for the response is the minimum of our version and + // the request's version. + if (core_request.api_major_version > ODK_MAJOR_VERSION) { + header->nonce_values.api_major_version = ODK_MAJOR_VERSION; + header->nonce_values.api_minor_version = ODK_MINOR_VERSION; + } + + static constexpr size_t BUF_CAPACITY = 2048; + std::vector buf(BUF_CAPACITY, 0); + uint8_t blk[SIZE_OF_MESSAGE_STRUCT]; + Message* msg = reinterpret_cast(blk); + InitMessage(msg, buf.data(), buf.capacity()); + packer(msg, &response); + if (!ValidMessage(msg)) { + return false; + } + + uint32_t message_length = GetSize(msg); + InitMessage(msg, buf.data() + sizeof(header->message_type), + sizeof(header->message_length)); + Pack_uint32_t(msg, &message_length); + oemcrypto_core_message->assign(reinterpret_cast(buf.data()), + message_length); + return true; +} + +bool CopyDeviceId(const ODK_ProvisioningRequest& src, + ODK_ProvisioningResponse* dest) { + auto& request = dest->request; + const std::string& device_id = src.device_id; + if (request.device_id_length > sizeof(request.device_id)) { + return false; + } + request.device_id_length = device_id.size(); + memset(request.device_id, 0, sizeof(request.device_id)); + memcpy(request.device_id, device_id.data(), request.device_id_length); + return true; +} + +} // namespace + +bool CreateCoreLicenseResponse(const ODK_ParsedLicense& parsed_lic, + const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, + std::string* oemcrypto_core_message) { + ODK_LicenseResponse license_response{ + {}, const_cast(&parsed_lic), {0}}; + if (core_request_sha256.size() != sizeof(license_response.request_hash)) + return false; + memcpy(license_response.request_hash, core_request_sha256.data(), + sizeof(license_response.request_hash)); + return CreateResponse(ODK_License_Response_Type, core_request, + oemcrypto_core_message, license_response, + Pack_ODK_LicenseResponse); +} + +bool CreateCoreRenewalResponse(const ODK_RenewalRequest& core_request, + uint64_t renewal_duration_seconds, + std::string* oemcrypto_core_message) { + ODK_RenewalResponse renewal_response{{}, core_request.playback_time_seconds}; + renewal_response.request.playback_time = core_request.playback_time_seconds; + renewal_response.renewal_duration_seconds = renewal_duration_seconds; + return CreateResponse(ODK_Renewal_Response_Type, core_request, + oemcrypto_core_message, renewal_response, + Pack_ODK_RenewalResponse); +} + +bool CreateCoreProvisioningResponse(const ODK_ParsedProvisioning& parsed_prov, + const ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message) { + ODK_ProvisioningResponse prov_response{ + {}, const_cast(&parsed_prov)}; + if (!CopyDeviceId(core_request, &prov_response)) { + return false; + } + return CreateResponse(ODK_Provisioning_Response_Type, core_request, + oemcrypto_core_message, prov_response, + Pack_ODK_ProvisioningResponse); +} + +} // namespace serialize +} // namespace oemcrypto_core_message diff --git a/common/oemcrypto_core_message/odk/src/core_message_serialize_proto.cpp b/common/oemcrypto_core_message/odk/src/core_message_serialize_proto.cpp new file mode 100644 index 0000000..0fb4463 --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/core_message_serialize_proto.cpp @@ -0,0 +1,190 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "common/oemcrypto_core_message/odk/include/core_message_serialize_proto.h" + +#include +#include +#include +#include +#include + +#include "common/oemcrypto_core_message/odk/include/core_message_serialize.h" +#include "common/oemcrypto_core_message/odk/include/odk_structs.h" +#include "common/oemcrypto_core_message/odk/src/odk_serialize.h" +#include "common/oemcrypto_core_message/odk/src/odk_structs_priv.h" +#include "common/oemcrypto_core_message/odk/src/serialization_base.h" +#include "protos/public/certificate_provisioning.pb.h" +#include "protos/public/license_protocol.pb.h" + +namespace oemcrypto_core_message { +namespace serialize { +namespace { + +/* @ private functions */ + +/** + * Extract OEMCrypto_Substring (offset, length) from serialized protobuf + * + * Parameters: + * message: serialized license protobuf + * field: substring value + */ +OEMCrypto_Substring GetOecSubstring(const std::string& message, + const std::string& field) { + OEMCrypto_Substring substring = {}; + size_t pos = message.find(field); + if (pos != std::string::npos) { + substring = OEMCrypto_Substring{pos, field.length()}; + } + return substring; +} + +OEMCrypto_KeyObject KeyContainerToOecKey( + const std::string& proto, const widevine::License::KeyContainer& k) { + OEMCrypto_KeyObject obj = {}; + obj.key_id = GetOecSubstring(proto, k.id()); + obj.key_data_iv = GetOecSubstring(proto, k.iv()); + // Strip off PKCS#5 padding - since we know the key is 16 or 32 bytes, + // the padding will always be 16 bytes. + const std::string& key_data = k.key(); + const size_t PKCS5_PADDING_SIZE = 16; + obj.key_data = GetOecSubstring( + proto, key_data.substr(0, std::max(PKCS5_PADDING_SIZE, key_data.size()) - + PKCS5_PADDING_SIZE)); + if (k.has_key_control()) { + const auto& key_control = k.key_control(); + obj.key_control_iv = GetOecSubstring(proto, key_control.iv()); + obj.key_control = GetOecSubstring(proto, key_control.key_control_block()); + } + return obj; +} + +} // namespace + +// @ public create response functions + +bool CreateCoreLicenseResponseFromProto(const std::string& serialized_license, + const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, + const bool nonce_required, + std::string* oemcrypto_core_message) { + widevine::License lic; + if (!lic.ParseFromString(serialized_license)) { + return false; + } + + ODK_ParsedLicense parsed_lic{}; + bool any_content = false; + bool any_entitlement = false; + + for (int i = 0; i < lic.key_size(); ++i) { + const auto& k = lic.key(i); + switch (k.type()) { + case widevine::License_KeyContainer::SIGNING: { + if (!k.has_key()) { + continue; + } + parsed_lic.enc_mac_keys_iv = + GetOecSubstring(serialized_license, k.iv()); + parsed_lic.enc_mac_keys = GetOecSubstring(serialized_license, k.key()); + break; + } + case widevine::License_KeyContainer::CONTENT: + case widevine::License_KeyContainer::OPERATOR_SESSION: + case widevine::License_KeyContainer::ENTITLEMENT: { + if (k.type() == widevine::License_KeyContainer::ENTITLEMENT) { + any_entitlement = true; + } else { + any_content = true; + } + if (parsed_lic.key_array_length >= ODK_MAX_NUM_KEYS) { + return false; + } + uint32_t& n = parsed_lic.key_array_length; + parsed_lic.key_array[n++] = KeyContainerToOecKey(serialized_license, k); + break; + } + default: { + continue; + } + } + } + if (any_content && any_entitlement) { + // TODO(b/147513335): this should be logged -- both type of keys. + return false; + } + if (!any_content && !any_entitlement) { + // TODO(b/147513335): this should be logged -- no keys? + return false; + } + parsed_lic.license_type = + any_content ? OEMCrypto_ContentLicense : OEMCrypto_EntitlementLicense; + const auto& lid = lic.id(); + if (lid.has_provider_session_token()) { + parsed_lic.pst = + GetOecSubstring(serialized_license, lid.provider_session_token()); + } + + if (lic.has_srm_requirement()) { + parsed_lic.srm_restriction_data = + GetOecSubstring(serialized_license, lic.srm_requirement()); + } + + parsed_lic.nonce_required = nonce_required; + const auto& policy = lic.policy(); + ODK_TimerLimits& timer_limits = parsed_lic.timer_limits; + timer_limits.soft_enforce_rental_duration = + policy.soft_enforce_rental_duration(); + timer_limits.soft_enforce_playback_duration = + policy.soft_enforce_playback_duration(); + timer_limits.earliest_playback_start_seconds = 0; + timer_limits.rental_duration_seconds = policy.rental_duration_seconds(); + timer_limits.total_playback_duration_seconds = + policy.playback_duration_seconds(); + timer_limits.initial_renewal_duration_seconds = + policy.renewal_delay_seconds() + + policy.renewal_recovery_duration_seconds(); + + return CreateCoreLicenseResponse(parsed_lic, core_request, + core_request_sha256, oemcrypto_core_message); +} + +bool CreateCoreProvisioningResponseFromProto( + const std::string& serialized_provisioning_resp, + const ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message) { + ODK_ParsedProvisioning parsed_prov{}; + widevine::ProvisioningResponse prov; + if (!prov.ParseFromString(serialized_provisioning_resp)) { + return false; + } + + parsed_prov.key_type = + OEMCrypto_RSA_Private_Key; // TODO(b/148404408): ECC or RSA + if (prov.has_device_rsa_key()) { + parsed_prov.enc_private_key = + GetOecSubstring(serialized_provisioning_resp, prov.device_rsa_key()); + } + if (prov.has_device_rsa_key_iv()) { + parsed_prov.enc_private_key_iv = + GetOecSubstring(serialized_provisioning_resp, prov.device_rsa_key_iv()); + } + if (prov.has_wrapping_key()) { + parsed_prov.encrypted_message_key = + GetOecSubstring(serialized_provisioning_resp, prov.wrapping_key()); + } + + return CreateCoreProvisioningResponse(parsed_prov, core_request, + oemcrypto_core_message); +} + +} // namespace serialize +} // namespace oemcrypto_core_message diff --git a/common/oemcrypto_core_message/odk/src/kdo.gypi b/common/oemcrypto_core_message/odk/src/kdo.gypi new file mode 100644 index 0000000..235d766 --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/kdo.gypi @@ -0,0 +1,24 @@ +################################################################################ +# 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. +################################################################################ This file and proprietary +# source code may only be used and distributed under the Widevine Master License +# Agreement. + +# These files are used by the server and by some ODK test code. These files are +# not built into the ODK library on the device. +{ + 'sources': [ + 'core_message_deserialize.cpp', + 'core_message_serialize.cpp', + 'core_message_serialize_proto.cpp', + ], + 'include_dirs': [ + 'src', + '../include', + ], +} + diff --git a/common/oemcrypto_core_message/odk/src/odk.c b/common/oemcrypto_core_message/odk/src/odk.c new file mode 100644 index 0000000..12abd65 --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/odk.c @@ -0,0 +1,425 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "common/oemcrypto_core_message/odk/include/odk.h" + +#include +#include +#include + +#include "common/oemcrypto_core_message/odk/include/odk_structs.h" +#include "common/oemcrypto_core_message/odk/src/odk_overflow.h" +#include "common/oemcrypto_core_message/odk/src/odk_serialize.h" +#include "common/oemcrypto_core_message/odk/src/odk_structs_priv.h" +#include "common/oemcrypto_core_message/odk/src/odk_util.h" +#include "common/oemcrypto_core_message/odk/src/serialization_base.h" + +/* @ private odk functions */ + +static OEMCryptoResult ODK_PrepareRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + ODK_MessageType message_type, const ODK_NonceValues* nonce_values, + void* prepared_request_buffer, size_t prepared_request_buffer_length) { + if (nonce_values == NULL || core_message_length == NULL || + prepared_request_buffer == NULL || + *core_message_length > message_length) { + return ODK_ERROR_CORE_MESSAGE; + } + + uint8_t blk[SIZE_OF_MESSAGE_STRUCT]; + Message* msg = (Message*)blk; + InitMessage(msg, message, *core_message_length); + + /* The core message should be at the beginning of the buffer, and with a + * shorter length. */ + if (sizeof(ODK_CoreMessage) > prepared_request_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + ODK_CoreMessage* core_message = (ODK_CoreMessage*)prepared_request_buffer; + *core_message = (ODK_CoreMessage){ + message_type, + 0, + *nonce_values, + }; + + /* Set core message length, and pack prepared request into message if the + * message buffer has been correctly initialized by the caller. */ + switch (message_type) { + case ODK_License_Request_Type: { + core_message->message_length = ODK_LICENSE_REQUEST_SIZE; + if (sizeof(ODK_PreparedLicenseRequest) > prepared_request_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + Pack_ODK_PreparedLicenseRequest( + msg, (ODK_PreparedLicenseRequest*)prepared_request_buffer); + break; + } + case ODK_Renewal_Request_Type: { + core_message->message_length = ODK_RENEWAL_REQUEST_SIZE; + if (sizeof(ODK_PreparedRenewalRequest) > prepared_request_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + Pack_ODK_PreparedRenewalRequest( + msg, (ODK_PreparedRenewalRequest*)prepared_request_buffer); + break; + } + case ODK_Provisioning_Request_Type: { + core_message->message_length = ODK_PROVISIONING_REQUEST_SIZE; + if (sizeof(ODK_PreparedProvisioningRequest) > + prepared_request_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + Pack_ODK_PreparedProvisioningRequest( + msg, (ODK_PreparedProvisioningRequest*)prepared_request_buffer); + break; + } + default: { + return ODK_ERROR_CORE_MESSAGE; + } + } + + *core_message_length = core_message->message_length; + if (GetStatus(msg) != MESSAGE_STATUS_OK) { + /* This is to indicate the caller that the core_message_length has been + * appropriately set, but the message buffer is either empty or too small, + * which needs to be initialized and filled in the subsequent call. */ + return OEMCrypto_ERROR_SHORT_BUFFER; + } + if (GetSize(msg) != *core_message_length) { + /* This should not happen. Something is wrong. */ + return ODK_ERROR_CORE_MESSAGE; + } + return OEMCrypto_SUCCESS; +} + +static OEMCryptoResult ODK_ParseResponse( + const uint8_t* message, size_t message_length, size_t core_message_length, + ODK_MessageType message_type, const ODK_NonceValues* nonce_values, + void* response_buffer, uint32_t response_buffer_length) { + if (message == NULL || response_buffer == NULL || + core_message_length > message_length) { + return ODK_ERROR_CORE_MESSAGE; + } + + uint8_t blk[SIZE_OF_MESSAGE_STRUCT]; + Message* msg = (Message*)blk; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" + /* We initialize the message buffer with a size of the entire message + * length. */ + /* TODO(b/164486737): Fix the cast-qual warning */ + InitMessage(msg, (uint8_t*)message, message_length); +#pragma GCC diagnostic pop + + /* The core message should be at the beginning of the buffer, and with a + * shorter length. The core message is the part we are parsing. */ + SetSize(msg, core_message_length); + + /* Parse message and unpack it into response buffer. */ + switch (message_type) { + case ODK_License_Response_Type: { + if (sizeof(ODK_LicenseResponse) > response_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + Unpack_ODK_LicenseResponse(msg, (ODK_LicenseResponse*)response_buffer); + break; + } + case ODK_Renewal_Response_Type: { + if (sizeof(ODK_RenewalResponse) > response_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + Unpack_ODK_RenewalResponse(msg, (ODK_RenewalResponse*)response_buffer); + break; + } + case ODK_Provisioning_Response_Type: { + if (sizeof(ODK_ProvisioningResponse) > response_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + Unpack_ODK_ProvisioningResponse( + msg, (ODK_ProvisioningResponse*)response_buffer); + break; + } + default: { + return ODK_ERROR_CORE_MESSAGE; + } + } + + ODK_CoreMessage* core_message = (ODK_CoreMessage*)response_buffer; + if (GetStatus(msg) != MESSAGE_STATUS_OK || + message_type != core_message->message_type || + GetOffset(msg) != core_message->message_length) { + return ODK_ERROR_CORE_MESSAGE; + } + + if (nonce_values) { + /* always verify nonce_values for Renewal and Provisioning responses */ + if (!ODK_NonceValuesEqual(nonce_values, &(core_message->nonce_values))) { + return OEMCrypto_ERROR_INVALID_NONCE; + } + } + + return OEMCrypto_SUCCESS; +} + +/* @ public odk functions */ + +/* @@ prepare request functions */ + +OEMCryptoResult ODK_PrepareCoreLicenseRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + const ODK_NonceValues* nonce_values) { + if (core_message_length == NULL || nonce_values == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + ODK_PreparedLicenseRequest license_request = { + {0, 0, {}}, + }; + return ODK_PrepareRequest( + message, message_length, core_message_length, ODK_License_Request_Type, + nonce_values, &license_request, sizeof(ODK_PreparedLicenseRequest)); +} + +OEMCryptoResult ODK_PrepareCoreRenewalRequest(uint8_t* message, + size_t message_length, + size_t* core_message_size, + ODK_NonceValues* nonce_values, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds) { + if (core_message_size == NULL || nonce_values == NULL || + clock_values == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + + /* If the license has not been loaded, then this is release instead of a + * renewal. All releases use v15. */ + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED || + clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE) { + nonce_values->api_major_version = ODK_FIRST_VERSION - 1; + } + if (nonce_values->api_major_version < ODK_FIRST_VERSION) { + *core_message_size = 0; + return OEMCrypto_SUCCESS; + } + + ODK_PreparedRenewalRequest renewal_request = {{0, 0, {}}, 0}; + /* First, we compute the time this request was made relative to the playback + * clock. */ + if (clock_values->time_of_first_decrypt == 0) { + /* It is OK to preemptively request a renewal before playback starts. + * We'll treat this as asking for a renewal at playback time 0. */ + renewal_request.playback_time = 0; + } else { + /* Otherwise, playback_time is relative to the first decrypt. */ + if (odk_sub_overflow_u64(system_time_seconds, + clock_values->time_of_first_decrypt, + &renewal_request.playback_time)) { + return ODK_ERROR_CORE_MESSAGE; + } + } + + /* Save time for this request so that we can verify the response. This makes + * all earlier requests invalid. If preparing this request fails, then all + * requests will be bad. */ + clock_values->time_of_renewal_request = renewal_request.playback_time; + + return ODK_PrepareRequest( + message, message_length, core_message_size, ODK_Renewal_Request_Type, + nonce_values, &renewal_request, sizeof(ODK_PreparedRenewalRequest)); +} + +OEMCryptoResult ODK_PrepareCoreProvisioningRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length) { + if (core_message_length == NULL || nonce_values == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + ODK_PreparedProvisioningRequest provisioning_request = { + {0, 0, {}}, + 0, + {0}, + }; + if (device_id_length > sizeof(provisioning_request.device_id)) { + return ODK_ERROR_CORE_MESSAGE; + } + provisioning_request.device_id_length = (uint32_t)device_id_length; + if (device_id) { + memcpy(provisioning_request.device_id, device_id, device_id_length); + } + return ODK_PrepareRequest(message, message_length, core_message_length, + ODK_Provisioning_Request_Type, nonce_values, + &provisioning_request, + sizeof(ODK_PreparedProvisioningRequest)); +} + +/* @@ parse response functions */ + +OEMCryptoResult ODK_ParseLicense( + const uint8_t* message, size_t message_length, size_t core_message_length, + bool initial_license_load, bool usage_entry_present, + const uint8_t* request_hash, ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, ODK_NonceValues* nonce_values, + ODK_ParsedLicense* parsed_license) { + if (message == NULL || request_hash == NULL || timer_limits == NULL || + clock_values == NULL || nonce_values == NULL || parsed_license == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + + ODK_LicenseResponse license_response = {{{0, 0, {}}}, NULL, {0}}; + license_response.parsed_license = parsed_license; + + const OEMCryptoResult err = ODK_ParseResponse( + message, message_length, core_message_length, ODK_License_Response_Type, + NULL, &license_response, sizeof(ODK_LicenseResponse)); + + if (err != OEMCrypto_SUCCESS) { + return err; + } + + /* We do not support future API version. Also, this function should not be + * used for legacy licenses. */ + if (license_response.request.core_message.nonce_values.api_major_version > + ODK_MAJOR_VERSION || + license_response.request.core_message.nonce_values.api_major_version < + ODK_FIRST_VERSION) { + return ODK_UNSUPPORTED_API; + } + + /* If the server sent us an older format, record the license's API version. */ + if (nonce_values->api_major_version > + license_response.request.core_message.nonce_values.api_major_version) { + nonce_values->api_major_version = + license_response.request.core_message.nonce_values.api_major_version; + nonce_values->api_minor_version = + license_response.request.core_message.nonce_values.api_minor_version; + } else if (nonce_values->api_minor_version > + license_response.request.core_message.nonce_values + .api_minor_version) { + nonce_values->api_minor_version = + license_response.request.core_message.nonce_values.api_minor_version; + } + /* If the license has a provider session token (pst), then OEMCrypto should + * have a usage entry loaded. The opposite is also an error. */ + if ((usage_entry_present && parsed_license->pst.length == 0) || + (!usage_entry_present && parsed_license->pst.length > 0)) { + return ODK_ERROR_CORE_MESSAGE; + } + + if (parsed_license->nonce_required) { + if (initial_license_load) { + if (nonce_values->nonce != + license_response.request.core_message.nonce_values.nonce || + nonce_values->session_id != + license_response.request.core_message.nonce_values.session_id) { + return OEMCrypto_ERROR_INVALID_NONCE; + } + } else { /* !initial_license_load */ + nonce_values->nonce = + license_response.request.core_message.nonce_values.nonce; + nonce_values->session_id = + license_response.request.core_message.nonce_values.session_id; + } + } + /* For v16, in order to be backwards compatible with a v15 license server, + * OEMCrypto stores a hash of the core license request and only signs the + * message body. Here, when we process the license response, we verify that + * the server has the same hash of the core request. */ + if (initial_license_load && parsed_license->nonce_required && + crypto_memcmp(request_hash, license_response.request_hash, + ODK_SHA256_HASH_SIZE)) { + return ODK_ERROR_CORE_MESSAGE; + } + *timer_limits = parsed_license->timer_limits; + /* And update the clock values state. */ + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, + size_t core_message_length, + const ODK_NonceValues* nonce_values, + uint64_t system_time, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value) { + if (message == NULL || nonce_values == NULL || timer_limits == NULL || + clock_values == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + + ODK_RenewalResponse renewal_response = { + {{0, 0, {}}, 0}, + 0, + }; + const OEMCryptoResult err = ODK_ParseResponse( + message, message_length, core_message_length, ODK_Renewal_Response_Type, + nonce_values, &renewal_response, sizeof(ODK_RenewalResponse)); + + if (err != OEMCrypto_SUCCESS) { + return err; + } + + /* Reference: + * Doc: License Duration and Renewal (Changes for OEMCrypto v16) + * Section: Renewal Message + */ + /* If a renewal request is lost in transit, we should throw it out and create + * a new one. We use the timestamp to make sure we have the latest request. + */ + if (clock_values->time_of_renewal_request < + renewal_response.request.playback_time) { + return ODK_STALE_RENEWAL; + } + return ODK_ComputeRenewalDuration(timer_limits, clock_values, system_time, + renewal_response.renewal_duration_seconds, + timer_value); +} + +OEMCryptoResult ODK_ParseProvisioning( + const uint8_t* message, size_t message_length, size_t core_message_length, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length, ODK_ParsedProvisioning* parsed_response) { + if (message == NULL || nonce_values == NULL || device_id == NULL || + parsed_response == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + + ODK_ProvisioningResponse provisioning_response = {{{0, 0, {}}, 0, {0}}, NULL}; + provisioning_response.parsed_provisioning = parsed_response; + + if (device_id_length > ODK_DEVICE_ID_LEN_MAX) { + return ODK_ERROR_CORE_MESSAGE; + } + + const OEMCryptoResult err = ODK_ParseResponse( + message, message_length, core_message_length, + ODK_Provisioning_Response_Type, nonce_values, &provisioning_response, + sizeof(ODK_ProvisioningResponse)); + + if (err != OEMCrypto_SUCCESS) { + return err; + } + + if (crypto_memcmp(device_id, provisioning_response.request.device_id, + device_id_length) != 0) { + return ODK_ERROR_CORE_MESSAGE; + } + + const uint8_t zero[ODK_DEVICE_ID_LEN_MAX] = {0}; + /* check bytes beyond device_id_length are 0 */ + if (crypto_memcmp(zero, + provisioning_response.request.device_id + device_id_length, + ODK_DEVICE_ID_LEN_MAX - device_id_length) != 0) { + return ODK_ERROR_CORE_MESSAGE; + } + + return OEMCrypto_SUCCESS; +} diff --git a/common/oemcrypto_core_message/odk/src/odk.gyp b/common/oemcrypto_core_message/odk/src/odk.gyp new file mode 100644 index 0000000..a6ba347 --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/odk.gyp @@ -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. +################################################################################ This file and proprietary +# source code may only be used and distributed under the Widevine Master License +# Agreement. + +{ + 'targets': [ + { + 'target_name': 'odk', + 'type': 'static_library', + 'include_dirs': [ + '../include', + '../../include', + ], + 'includes' : [ + 'odk.gypi', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '../include', + ], + } + }, + ], +} diff --git a/common/oemcrypto_core_message/odk/src/odk.gypi b/common/oemcrypto_core_message/odk/src/odk.gypi new file mode 100644 index 0000000..8fb722f --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/odk.gypi @@ -0,0 +1,23 @@ +################################################################################ +# 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. +################################################################################ This file and proprietary +# source code may only be used and distributed under the Widevine Master License +# Agreement. + +# These files are built into the ODK library on the device. They are also used +# by the server and by test cocde. These files should compile on C99 compilers. +{ + 'sources': [ + 'odk.c', + 'odk_overflow.c', + 'odk_serialize.c', + 'odk_timer.c', + 'odk_util.c', + 'serialization_base.c', + ], +} + diff --git a/common/oemcrypto_core_message/odk/src/odk_assert.h b/common/oemcrypto_core_message/odk/src/odk_assert.h new file mode 100644 index 0000000..2abb24f --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/odk_assert.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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_ODK_ASSERT_H_ +#define COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_ODK_ASSERT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if (__STDC_VERSION__ >= 201112L) +#include +#define odk_static_assert static_assert +#else +#define odk_static_assert(msg, e) \ + enum { odk_static_assert = 1 / (!!((msg) && (e))) }; +#endif + +#ifdef __cplusplus +} +#endif + +#endif // COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_ODK_ASSERT_H_ diff --git a/common/oemcrypto_core_message/odk/src/odk_endian.h b/common/oemcrypto_core_message/odk/src/odk_endian.h new file mode 100644 index 0000000..7f716dd --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/odk_endian.h @@ -0,0 +1,35 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_ODK_ENDIAN_H_ +#define COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_ODK_ENDIAN_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__linux__) || defined(__ANDROID__) +#include +#define oemcrypto_htobe32 htobe32 +#define oemcrypto_be32toh be32toh +#define oemcrypto_htobe64 htobe64 +#define oemcrypto_be64toh be64toh +#else /* defined(__linux__) || defined(__ANDROID__) */ +uint32_t oemcrypto_htobe32(uint32_t u32); +uint32_t oemcrypto_be32toh(uint32_t u32); +uint64_t oemcrypto_htobe64(uint64_t u64); +uint64_t oemcrypto_be64toh(uint64_t u64); +#endif /* defined(__linux__) || defined(__ANDROID__) */ + +#ifdef __cplusplus +} +#endif + +#endif // COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_ODK_ENDIAN_H_ diff --git a/common/oemcrypto_core_message/odk/src/odk_overflow.c b/common/oemcrypto_core_message/odk/src/odk_overflow.c new file mode 100644 index 0000000..376983e --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/odk_overflow.c @@ -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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include +#include + +int odk_sub_overflow_u64(uint64_t a, uint64_t b, uint64_t* c) { + if (a >= b) { + if (c) { + *c = a - b; + } + return 0; + } + return 1; +} + +int odk_add_overflow_u64(uint64_t a, uint64_t b, uint64_t* c) { + if (UINT64_MAX - a >= b) { + if (c) { + *c = a + b; + } + return 0; + } + return 1; +} + +int odk_add_overflow_ux(size_t a, size_t b, size_t* c) { + if (SIZE_MAX - a >= b) { + if (c) { + *c = a + b; + } + return 0; + } + return 1; +} diff --git a/common/oemcrypto_core_message/odk/src/odk_overflow.h b/common/oemcrypto_core_message/odk/src/odk_overflow.h new file mode 100644 index 0000000..cf966ef --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/odk_overflow.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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_ODK_OVERFLOW_H_ +#define COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_ODK_OVERFLOW_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int odk_sub_overflow_u64(uint64_t a, uint64_t b, uint64_t* c); +int odk_add_overflow_u64(uint64_t a, uint64_t b, uint64_t* c); +int odk_add_overflow_ux(size_t a, size_t b, size_t* c); + +#ifdef __cplusplus +} +#endif + +#endif // COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_ODK_OVERFLOW_H_ diff --git a/common/oemcrypto_core_message/odk/src/odk_serialize.c b/common/oemcrypto_core_message/odk/src/odk_serialize.c new file mode 100644 index 0000000..ca2ac4c --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/odk_serialize.c @@ -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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +/* + * This code is auto-generated, do not edit + */ + +#include "common/oemcrypto_core_message/odk/src/odk_structs_priv.h" +#include "common/oemcrypto_core_message/odk/src/serialization_base.h" + +/* @ serialize */ + +/* @@ private serialize */ + +static void Pack_ODK_NonceValues(Message* msg, ODK_NonceValues const* obj) { + Pack_uint16_t(msg, &obj->api_minor_version); + Pack_uint16_t(msg, &obj->api_major_version); + Pack_uint32_t(msg, &obj->nonce); + Pack_uint32_t(msg, &obj->session_id); +} + +static void Pack_ODK_CoreMessage(Message* msg, ODK_CoreMessage const* obj) { + Pack_uint32_t(msg, &obj->message_type); + Pack_uint32_t(msg, &obj->message_length); + Pack_ODK_NonceValues(msg, &obj->nonce_values); +} + +static void Pack_OEMCrypto_KeyObject(Message* msg, + OEMCrypto_KeyObject const* obj) { + Pack_OEMCrypto_Substring(msg, &obj->key_id); + Pack_OEMCrypto_Substring(msg, &obj->key_data_iv); + Pack_OEMCrypto_Substring(msg, &obj->key_data); + Pack_OEMCrypto_Substring(msg, &obj->key_control_iv); + Pack_OEMCrypto_Substring(msg, &obj->key_control); +} + +static void Pack_ODK_TimerLimits(Message* msg, ODK_TimerLimits const* obj) { + Pack_bool(msg, &obj->soft_enforce_rental_duration); + Pack_bool(msg, &obj->soft_enforce_playback_duration); + Pack_uint64_t(msg, &obj->earliest_playback_start_seconds); + Pack_uint64_t(msg, &obj->rental_duration_seconds); + Pack_uint64_t(msg, &obj->total_playback_duration_seconds); + Pack_uint64_t(msg, &obj->initial_renewal_duration_seconds); +} + +static void Pack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense const* obj) { + /* hand-coded */ + if (obj->key_array_length > ODK_MAX_NUM_KEYS) { + SetStatus(msg, MESSAGE_STATUS_OVERFLOW_ERROR); + return; + } + Pack_OEMCrypto_Substring(msg, &obj->enc_mac_keys_iv); + Pack_OEMCrypto_Substring(msg, &obj->enc_mac_keys); + Pack_OEMCrypto_Substring(msg, &obj->pst); + Pack_OEMCrypto_Substring(msg, &obj->srm_restriction_data); + Pack_enum(msg, obj->license_type); + Pack_bool(msg, &obj->nonce_required); + Pack_ODK_TimerLimits(msg, &obj->timer_limits); + Pack_uint32_t(msg, &obj->key_array_length); + size_t i; + for (i = 0; i < (size_t)obj->key_array_length; i++) { + Pack_OEMCrypto_KeyObject(msg, &obj->key_array[i]); + } +} + +static void Pack_ODK_ParsedProvisioning(Message* msg, + ODK_ParsedProvisioning const* obj) { + Pack_enum(msg, obj->key_type); + Pack_OEMCrypto_Substring(msg, &obj->enc_private_key); + Pack_OEMCrypto_Substring(msg, &obj->enc_private_key_iv); + Pack_OEMCrypto_Substring(msg, &obj->encrypted_message_key); +} + +/* @@ odk serialize */ + +void Pack_ODK_PreparedLicenseRequest(Message* msg, + ODK_PreparedLicenseRequest const* obj) { + Pack_ODK_CoreMessage(msg, &obj->core_message); +} + +void Pack_ODK_PreparedRenewalRequest(Message* msg, + ODK_PreparedRenewalRequest const* obj) { + Pack_ODK_CoreMessage(msg, &obj->core_message); + Pack_uint64_t(msg, &obj->playback_time); +} + +void Pack_ODK_PreparedProvisioningRequest( + Message* msg, ODK_PreparedProvisioningRequest const* obj) { + Pack_ODK_CoreMessage(msg, &obj->core_message); + Pack_uint32_t(msg, &obj->device_id_length); + PackArray(msg, &obj->device_id[0], sizeof(obj->device_id)); +} + +/* @@ kdo serialize */ + +void Pack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse const* obj) { + Pack_ODK_PreparedLicenseRequest(msg, &obj->request); + Pack_ODK_ParsedLicense(msg, (const ODK_ParsedLicense*)obj->parsed_license); + PackArray(msg, &obj->request_hash[0], sizeof(obj->request_hash)); +} + +void Pack_ODK_RenewalResponse(Message* msg, ODK_RenewalResponse const* obj) { + Pack_ODK_PreparedRenewalRequest(msg, &obj->request); + Pack_uint64_t(msg, &obj->renewal_duration_seconds); +} + +void Pack_ODK_ProvisioningResponse(Message* msg, + ODK_ProvisioningResponse const* obj) { + Pack_ODK_PreparedProvisioningRequest(msg, &obj->request); + Pack_ODK_ParsedProvisioning( + msg, (const ODK_ParsedProvisioning*)obj->parsed_provisioning); +} + +/* @ deserialize */ + +/* @@ private deserialize */ + +static void Unpack_ODK_NonceValues(Message* msg, ODK_NonceValues* obj) { + Unpack_uint16_t(msg, &obj->api_minor_version); + Unpack_uint16_t(msg, &obj->api_major_version); + Unpack_uint32_t(msg, &obj->nonce); + Unpack_uint32_t(msg, &obj->session_id); +} + +static void Unpack_ODK_CoreMessage(Message* msg, ODK_CoreMessage* obj) { + Unpack_uint32_t(msg, &obj->message_type); + Unpack_uint32_t(msg, &obj->message_length); + Unpack_ODK_NonceValues(msg, &obj->nonce_values); +} + +static void Unpack_OEMCrypto_KeyObject(Message* msg, OEMCrypto_KeyObject* obj) { + Unpack_OEMCrypto_Substring(msg, &obj->key_id); + Unpack_OEMCrypto_Substring(msg, &obj->key_data_iv); + Unpack_OEMCrypto_Substring(msg, &obj->key_data); + Unpack_OEMCrypto_Substring(msg, &obj->key_control_iv); + Unpack_OEMCrypto_Substring(msg, &obj->key_control); +} + +static void Unpack_ODK_TimerLimits(Message* msg, ODK_TimerLimits* obj) { + Unpack_bool(msg, &obj->soft_enforce_rental_duration); + Unpack_bool(msg, &obj->soft_enforce_playback_duration); + Unpack_uint64_t(msg, &obj->earliest_playback_start_seconds); + Unpack_uint64_t(msg, &obj->rental_duration_seconds); + Unpack_uint64_t(msg, &obj->total_playback_duration_seconds); + Unpack_uint64_t(msg, &obj->initial_renewal_duration_seconds); +} + +static void Unpack_ODK_ParsedLicense(Message* msg, ODK_ParsedLicense* obj) { + Unpack_OEMCrypto_Substring(msg, &obj->enc_mac_keys_iv); + Unpack_OEMCrypto_Substring(msg, &obj->enc_mac_keys); + Unpack_OEMCrypto_Substring(msg, &obj->pst); + Unpack_OEMCrypto_Substring(msg, &obj->srm_restriction_data); + obj->license_type = (OEMCrypto_LicenseType)Unpack_enum(msg); + Unpack_bool(msg, &obj->nonce_required); + Unpack_ODK_TimerLimits(msg, &obj->timer_limits); + Unpack_uint32_t(msg, &obj->key_array_length); + if (obj->key_array_length > ODK_MAX_NUM_KEYS) { + SetStatus(msg, MESSAGE_STATUS_OVERFLOW_ERROR); + return; + } + uint32_t i; + for (i = 0; i < obj->key_array_length; i++) { + Unpack_OEMCrypto_KeyObject(msg, &obj->key_array[i]); + } +} + +static void Unpack_ODK_ParsedProvisioning(Message* msg, + ODK_ParsedProvisioning* obj) { + obj->key_type = (OEMCrypto_PrivateKeyType)Unpack_enum(msg); + Unpack_OEMCrypto_Substring(msg, &obj->enc_private_key); + Unpack_OEMCrypto_Substring(msg, &obj->enc_private_key_iv); + Unpack_OEMCrypto_Substring(msg, &obj->encrypted_message_key); +} + +/* @ kdo deserialize */ + +void Unpack_ODK_PreparedLicenseRequest(Message* msg, + ODK_PreparedLicenseRequest* obj) { + Unpack_ODK_CoreMessage(msg, &obj->core_message); +} + +void Unpack_ODK_PreparedRenewalRequest(Message* msg, + ODK_PreparedRenewalRequest* obj) { + Unpack_ODK_CoreMessage(msg, &obj->core_message); + Unpack_uint64_t(msg, &obj->playback_time); +} + +void Unpack_ODK_PreparedProvisioningRequest( + Message* msg, ODK_PreparedProvisioningRequest* obj) { + Unpack_ODK_CoreMessage(msg, &obj->core_message); + Unpack_uint32_t(msg, &obj->device_id_length); + UnpackArray(msg, &obj->device_id[0], sizeof(obj->device_id)); +} + +void Unpack_ODK_PreparedCommonRequest(Message* msg, + ODK_PreparedCommonRequest* obj) { + Unpack_ODK_CoreMessage(msg, &obj->core_message); +} +/* @@ odk deserialize */ + +void Unpack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse* obj) { + Unpack_ODK_PreparedLicenseRequest(msg, &obj->request); + Unpack_ODK_ParsedLicense(msg, obj->parsed_license); + UnpackArray(msg, &obj->request_hash[0], sizeof(obj->request_hash)); +} + +void Unpack_ODK_RenewalResponse(Message* msg, ODK_RenewalResponse* obj) { + Unpack_ODK_PreparedRenewalRequest(msg, &obj->request); + Unpack_uint64_t(msg, &obj->renewal_duration_seconds); +} + +void Unpack_ODK_ProvisioningResponse(Message* msg, + ODK_ProvisioningResponse* obj) { + Unpack_ODK_PreparedProvisioningRequest(msg, &obj->request); + Unpack_ODK_ParsedProvisioning(msg, obj->parsed_provisioning); +} diff --git a/common/oemcrypto_core_message/odk/src/odk_serialize.h b/common/oemcrypto_core_message/odk/src/odk_serialize.h new file mode 100644 index 0000000..adefe69 --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/odk_serialize.h @@ -0,0 +1,58 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +/* + * This code is auto-generated, do not edit + */ +#ifndef COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_ODK_SERIALIZE_H_ +#define COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_ODK_SERIALIZE_H_ + +#include "common/oemcrypto_core_message/odk/src/odk_structs_priv.h" +#include "common/oemcrypto_core_message/odk/src/serialization_base.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* odk pack */ +void Pack_ODK_PreparedLicenseRequest(Message* msg, + const ODK_PreparedLicenseRequest* obj); +void Pack_ODK_PreparedRenewalRequest(Message* msg, + const ODK_PreparedRenewalRequest* obj); +void Pack_ODK_PreparedProvisioningRequest( + Message* msg, const ODK_PreparedProvisioningRequest* obj); + +/* odk unpack */ +void Unpack_ODK_LicenseResponse(Message* msg, ODK_LicenseResponse* obj); +void Unpack_ODK_RenewalResponse(Message* msg, ODK_RenewalResponse* obj); +void Unpack_ODK_ProvisioningResponse(Message* msg, + ODK_ProvisioningResponse* obj); + +/* kdo pack */ +void Pack_ODK_LicenseResponse(Message* msg, const ODK_LicenseResponse* obj); +void Pack_ODK_RenewalResponse(Message* msg, const ODK_RenewalResponse* obj); +void Pack_ODK_ProvisioningResponse(Message* msg, + const ODK_ProvisioningResponse* obj); + +/* kdo unpack */ +void Unpack_ODK_PreparedLicenseRequest(Message* msg, + ODK_PreparedLicenseRequest* obj); +void Unpack_ODK_PreparedRenewalRequest(Message* msg, + ODK_PreparedRenewalRequest* obj); +void Unpack_ODK_PreparedProvisioningRequest( + Message* msg, ODK_PreparedProvisioningRequest* obj); + +void Unpack_ODK_PreparedCommonRequest(Message* msg, + ODK_PreparedCommonRequest* obj); + +#ifdef __cplusplus +} // extern "C" +#endif +#endif // COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_ODK_SERIALIZE_H_ diff --git a/common/oemcrypto_core_message/odk/src/odk_structs_priv.h b/common/oemcrypto_core_message/odk/src/odk_structs_priv.h new file mode 100644 index 0000000..e42aa08 --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/odk_structs_priv.h @@ -0,0 +1,115 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_ODK_STRUCTS_PRIV_H_ +#define COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_ODK_STRUCTS_PRIV_H_ + +#include + +#include "common/oemcrypto_core_message/odk/include/OEMCryptoCENCCommon.h" +#include "common/oemcrypto_core_message/odk/include/odk_structs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + ODK_License_Request_Type = 1, + ODK_License_Response_Type = 2, + ODK_Renewal_Request_Type = 3, + ODK_Renewal_Response_Type = 4, + ODK_Provisioning_Request_Type = 5, + ODK_Provisioning_Response_Type = 6, + + // Reserve future message types to support forward compatibility. + ODK_Release_Request_Type = 7, + ODK_Release_Response_Type = 8, + ODK_Common_Request_Type = 9, + ODK_Common_Response_Type = 10, +} ODK_MessageType; + +typedef struct { + uint32_t message_type; + uint32_t message_length; + ODK_NonceValues nonce_values; +} ODK_CoreMessage; + +typedef struct { + ODK_CoreMessage core_message; +} ODK_PreparedLicenseRequest; + +typedef struct { + ODK_CoreMessage core_message; + uint64_t playback_time; +} ODK_PreparedRenewalRequest; + +typedef struct { + ODK_CoreMessage core_message; + uint32_t device_id_length; + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX]; +} ODK_PreparedProvisioningRequest; + +typedef struct { + ODK_CoreMessage core_message; +} ODK_PreparedCommonRequest; + +typedef struct { + ODK_PreparedLicenseRequest request; + ODK_ParsedLicense* parsed_license; + uint8_t request_hash[ODK_SHA256_HASH_SIZE]; +} ODK_LicenseResponse; + +typedef struct { + ODK_PreparedRenewalRequest request; + uint64_t renewal_duration_seconds; +} ODK_RenewalResponse; + +typedef struct { + ODK_PreparedProvisioningRequest request; + ODK_ParsedProvisioning* parsed_provisioning; +} ODK_ProvisioningResponse; + +// These are the sum of sizeof of each individual member of the request structs +// without any padding added by the compiler. Make sure they get updated when +// request structs change. Refer to test suite OdkSizeTest in +// ../test/odk_test.cpp for validations of each of the defined request sizes. +#define ODK_LICENSE_REQUEST_SIZE 20 +#define ODK_RENEWAL_REQUEST_SIZE 28 +#define ODK_PROVISIONING_REQUEST_SIZE 88 + +// These are the possible timer status values. +#define ODK_CLOCK_TIMER_STATUS_UNDEFINED 0 // Should not happen. +// When the structure has been initialized, but no license is loaded. +#define ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED 1 +// After the license is loaded, before a successful decrypt. +#define ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED 2 +// After the license is loaded, if a renewal has also been loaded. +#define ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED 3 +// The first decrypt has occurred and the timer is active. +#define ODK_CLOCK_TIMER_STATUS_ACTIVE 4 +// The first decrypt has occurred and the timer is unlimited. +#define ODK_CLOCK_TIMER_STATUS_UNLIMITED 5 +// The timer has transitioned from active to expired. +#define ODK_CLOCK_TIMER_STATUS_EXPIRED 6 +// The license has been marked as inactive. +#define ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE 7 + +// A helper function for computing timer limits when a renewal is loaded. +OEMCryptoResult ODK_ComputeRenewalDuration(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds, + uint64_t new_renewal_duration, + uint64_t* timer_value); + +#ifdef __cplusplus +} +#endif + +#endif // COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_ODK_STRUCTS_PRIV_H_ diff --git a/common/oemcrypto_core_message/odk/src/odk_timer.c b/common/oemcrypto_core_message/odk/src/odk_timer.c new file mode 100644 index 0000000..b5b893b --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/odk_timer.c @@ -0,0 +1,509 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include +#include + +#include "common/oemcrypto_core_message/odk/include/odk.h" +#include "common/oemcrypto_core_message/odk/include/odk_attributes.h" +#include "common/oemcrypto_core_message/odk/src/odk_overflow.h" +#include "common/oemcrypto_core_message/odk/src/odk_structs_priv.h" + +/* Private function. Checks to see if the license is active. Returns + * ODK_TIMER_EXPIRED if the license is valid but inactive. Returns + * OEMCrypto_SUCCESS if the license is active. Returns + * OEMCrypto_ERROR_UNKNOWN_FAILURE on other errors. */ +static OEMCryptoResult ODK_LicenseActive(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values) { + /* Check some basic errors. */ + if (clock_values == NULL || timer_limits == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* Check if the license has not been loaded yet. */ + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_UNDEFINED || + clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (clock_values->status > kActive) { + return ODK_TIMER_EXPIRED; + } + return OEMCrypto_SUCCESS; +} + +/* Private function. Sets the timer_value to be the min(timer_value, new_value), + * with the convention that 0 means infinite. The convention that 0 means + * infinite is used for all Widevine license and duration values. */ +static void ComputeMinimum(uint64_t* timer_value, uint64_t new_value) { + if (timer_value == NULL) return; + if (new_value > 0) { + if (*timer_value == 0 || *timer_value > new_value) { + *timer_value = new_value; + } + } +} + +/* Private function. Check to see if the rental window restricts playback. If + * the rental enforcement is hard, or if this is the first playback, then we + * verify that system_time_seconds is within the rental window. If the + * enforcement is soft and we have already started playback, then there is no + * restriction. + * Return ODK_TIMER_EXPIRED if out of the window. + * Return ODK_TIMER_ACTIVE if within the window, and there is a hard limit. + * Return ODK_DISABLE_TIMER if no there should be no limit. + * Return other error on error. + * Also, if this function does compute a limit, the timer_value is reduced to + * obey that limit. If the limit is less restrictive than the current + * timer_value, then timer_value is not changed. */ +static OEMCryptoResult ODK_CheckRentalWindow( + const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, + uint64_t system_time_seconds, uint64_t* timer_value) { + if (clock_values == NULL || timer_limits == NULL || timer_value == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* If playback has already started, and rental duration enforcement is soft, + * then there is no restriction. */ + if (clock_values->time_of_first_decrypt > 0 && + timer_limits->soft_enforce_rental_duration) { + return ODK_DISABLE_TIMER; + } + + /* rental_clock = time since license signed. */ + uint64_t rental_clock = 0; + if (odk_sub_overflow_u64(system_time_seconds, + clock_values->time_of_license_signed, + &rental_clock)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* Check if it is before license is valid. This is an unusual case. First + * playback may still work if it occurs after the rental window opens. */ + if (rental_clock < timer_limits->earliest_playback_start_seconds) { + return ODK_TIMER_EXPIRED; + } + /* If the rental duration is 0, there is no limit. */ + if (timer_limits->rental_duration_seconds == 0) { + return ODK_DISABLE_TIMER; + } + /* End of rental window, based on rental clock (not system time). */ + uint64_t end_of_rental_window = 0; + if (odk_add_overflow_u64(timer_limits->earliest_playback_start_seconds, + timer_limits->rental_duration_seconds, + &end_of_rental_window)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (end_of_rental_window <= rental_clock) { + return ODK_TIMER_EXPIRED; + } + /* At this point system_time is within the rental window. */ + if (timer_limits->soft_enforce_rental_duration) { + /* For soft enforcement, we allow playback, and do not adjust the timer. */ + return ODK_DISABLE_TIMER; + } + uint64_t time_left = 0; + if (odk_sub_overflow_u64(end_of_rental_window, rental_clock, &time_left)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + ComputeMinimum(timer_value, time_left); + return ODK_SET_TIMER; +} + +/* Private function. Check to see if the playback window restricts + * playback. This should only be called if playback has started, so that + * clock_values->time_of_first_decrypt is nonzero. + * Return ODK_TIMER_EXPIRED if out of the window. + * Return ODK_SET_TIMER if within the window, and there is a hard limit. + * Return ODK_DISABLE_TIMER if no limit. + * Return other error on error. + * Also, if this function does compute a limit, the timer_value is reduced to + * obey that limit. If the limit is less restrictive than the current + * timer_value, then timer_value is not changed. */ +static OEMCryptoResult ODK_CheckPlaybackWindow( + const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, + uint64_t system_time_seconds, uint64_t* timer_value) { + if (clock_values == NULL || timer_limits == NULL || timer_value == NULL) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + /* if the playback duration is 0, there is no limit. */ + if (timer_limits->total_playback_duration_seconds == 0) { + return ODK_DISABLE_TIMER; + } + uint64_t end_of_playback_window = 0; + if (odk_add_overflow_u64(timer_limits->total_playback_duration_seconds, + clock_values->time_of_first_decrypt, + &end_of_playback_window)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (end_of_playback_window <= system_time_seconds) { + return ODK_TIMER_EXPIRED; + } + /* At this point, system_time is within the total playback window. */ + if (timer_limits->soft_enforce_playback_duration) { + /* For soft enforcement, we allow playback, and do not adjust the timer. */ + return ODK_DISABLE_TIMER; + } + uint64_t time_left = 0; + if (odk_sub_overflow_u64(end_of_playback_window, system_time_seconds, + &time_left)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + ComputeMinimum(timer_value, time_left); + return ODK_SET_TIMER; +} + +/* Update the timer status. If playback has already started, we use the given + * status. However, if playback has not yet started, then we expect a call to + * ODK_AttemptFirstPlayback in the future, and we need to signal to it that we + * have already computed the timer limit. */ +static void ODK_UpdateTimerStatusForRenewal(ODK_ClockValues* clock_values, + uint32_t new_status) { + if (clock_values == NULL) { + return; /* should not happen. */ + } + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED) { + /* Signal that the timer is already set. */ + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED; + } else { + clock_values->timer_status = new_status; + } +} + +/* Private function, but accessed from odk.c so cannot be static. This checks to + * see if a renewal message should restart the playback timer and sets the value + * appropriately. */ +OEMCryptoResult ODK_ComputeRenewalDuration(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds, + uint64_t new_renewal_duration, + uint64_t* timer_value) { + if (timer_limits == NULL || clock_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; /* should not happen. */ + } + /* If this is before the license was signed, something is odd. Return an + * error. */ + if (system_time_seconds < clock_values->time_of_license_signed) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + const OEMCryptoResult license_status = + ODK_LicenseActive(timer_limits, clock_values); + /* If the license is not active, then we cannot renew the license. */ + if (license_status != OEMCrypto_SUCCESS) { + return license_status; + } + + /* We start with the new renewal duration as the new timer limit. */ + uint64_t new_timer_value = new_renewal_duration; + + /* Then we factor in the rental window restrictions. This might decrease + * new_timer_value. */ + const OEMCryptoResult rental_status = ODK_CheckRentalWindow( + timer_limits, clock_values, system_time_seconds, &new_timer_value); + + /* If the rental status forbids playback, then we're done. */ + if ((rental_status != ODK_DISABLE_TIMER) && + (rental_status != ODK_SET_TIMER)) { + return rental_status; + } + + /* If playback has already started and it has hard enforcement, then check + * total playback window. */ + if (clock_values->time_of_first_decrypt > 0 && + !timer_limits->soft_enforce_playback_duration) { + /* This might decrease new_timer_value. */ + const OEMCryptoResult playback_status = ODK_CheckPlaybackWindow( + timer_limits, clock_values, system_time_seconds, &new_timer_value); + /* If the timer limits forbid playback in the playback window, then we're + * done. */ + if ((playback_status != ODK_DISABLE_TIMER) && + (playback_status != ODK_SET_TIMER)) { + return playback_status; + } + } + + /* If new_timer_value is infinite (represented by 0), then there are no + * limits, so we can return now. */ + if (new_timer_value == 0) { + clock_values->time_when_timer_expires = 0; + ODK_UpdateTimerStatusForRenewal(clock_values, + ODK_CLOCK_TIMER_STATUS_UNLIMITED); + return ODK_DISABLE_TIMER; + } + + /* If the caller gave us a pointer to store the new timer value. Fill it. */ + if (timer_value != NULL) { + *timer_value = new_timer_value; + } + if (odk_add_overflow_u64(system_time_seconds, new_timer_value, + &clock_values->time_when_timer_expires)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + ODK_UpdateTimerStatusForRenewal(clock_values, ODK_CLOCK_TIMER_STATUS_ACTIVE); + return ODK_SET_TIMER; +} + +/************************************************************************/ +/************************************************************************/ +/* Public functions, declared in odk.h. */ + +/* This is called when OEMCrypto opens a new session. */ +OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, + uint32_t api_major_version, + uint32_t session_id) { + if (timer_limits == NULL || clock_values == NULL || nonce_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* Check that the API version passed in from OEMCrypto matches the version of + * this ODK library. */ + if (api_major_version != ODK_MAJOR_VERSION) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + timer_limits->soft_enforce_rental_duration = false; + timer_limits->soft_enforce_playback_duration = false; + timer_limits->earliest_playback_start_seconds = 0; + timer_limits->rental_duration_seconds = 0; + timer_limits->total_playback_duration_seconds = 0; + timer_limits->initial_renewal_duration_seconds = 0; + + ODK_InitializeClockValues(clock_values, 0); + + nonce_values->api_major_version = ODK_MAJOR_VERSION; + nonce_values->api_minor_version = ODK_MINOR_VERSION; + nonce_values->nonce = 0; + nonce_values->session_id = session_id; + + return OEMCrypto_SUCCESS; +} + +/* This is called when OEMCrypto generates a new nonce in + * OEMCrypto_GenerateNonce. */ +OEMCryptoResult ODK_SetNonceValues(ODK_NonceValues* nonce_values, + uint32_t nonce) { + if (nonce_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* Setting the nonce should only happen once per session. */ + if (nonce_values->nonce != 0) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + nonce_values->nonce = nonce; + return OEMCrypto_SUCCESS; +} + +/* This is called when OEMCrypto signs a license. */ +OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values, + uint64_t system_time_seconds) { + if (clock_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + clock_values->time_of_license_signed = system_time_seconds; + clock_values->time_of_first_decrypt = 0; + clock_values->time_of_last_decrypt = 0; + clock_values->time_when_timer_expires = 0; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED; + clock_values->status = kUnused; + return OEMCrypto_SUCCESS; +} + +/* This is called when OEMCrypto reloads a usage entry. */ +OEMCryptoResult ODK_ReloadClockValues(ODK_ClockValues* clock_values, + uint64_t time_of_license_signed, + uint64_t time_of_first_decrypt, + uint64_t time_of_last_decrypt, + enum OEMCrypto_Usage_Entry_Status status, + uint64_t system_time_seconds UNUSED) { + if (clock_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + clock_values->time_of_license_signed = time_of_license_signed; + clock_values->time_of_first_decrypt = time_of_first_decrypt; + clock_values->time_of_last_decrypt = time_of_last_decrypt; + clock_values->time_when_timer_expires = 0; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED; + clock_values->status = status; + return OEMCrypto_SUCCESS; +} + +/* This is called on the first playback for a session. */ +OEMCryptoResult ODK_AttemptFirstPlayback(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value) { + if (clock_values == NULL || timer_limits == NULL) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + /* All times are relative to when the license was signed. */ + uint64_t rental_time = 0; + if (odk_sub_overflow_u64(system_time_seconds, + clock_values->time_of_license_signed, + &rental_time)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (rental_time < timer_limits->earliest_playback_start_seconds) { + clock_values->timer_status = ODK_TIMER_EXPIRED; + return ODK_TIMER_EXPIRED; + } + /* If the license is inactive or not loaded, then playback is not allowed. */ + OEMCryptoResult status = ODK_LicenseActive(timer_limits, clock_values); + if (status != OEMCrypto_SUCCESS) { + return status; + } + + /* We start with the initial renewal duration as the timer limit. */ + uint64_t new_timer_value = timer_limits->initial_renewal_duration_seconds; + /* However, if a renewal was loaded before this first playback, use the + * previously computed limit. */ + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED) { + if (clock_values->time_when_timer_expires <= system_time_seconds) { + return ODK_TIMER_EXPIRED; + } + if (odk_sub_overflow_u64(clock_values->time_when_timer_expires, + system_time_seconds, &new_timer_value)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + } + + /* Then we factor in the rental window restrictions. This might decrease + * new_timer_value. */ + status = ODK_CheckRentalWindow(timer_limits, clock_values, + system_time_seconds, &new_timer_value); + if ((status != ODK_DISABLE_TIMER) && (status != ODK_SET_TIMER)) { + return status; + } + + /* If playback has not already started, then this is the first playback. */ + if (clock_values->time_of_first_decrypt == 0) { + clock_values->time_of_first_decrypt = system_time_seconds; + clock_values->status = kActive; + } + + /* Similar to the rental window, we check the playback window + * restrictions. This might decrease new_timer_value. */ + status = ODK_CheckPlaybackWindow(timer_limits, clock_values, + system_time_seconds, &new_timer_value); + if ((status != ODK_DISABLE_TIMER) && (status != ODK_SET_TIMER)) { + return status; + } + + /* We know we are allowed to decrypt. The rest computes the timer duration. */ + clock_values->time_of_last_decrypt = system_time_seconds; + + /* If new_timer_value is infinite (represented by 0), then there are no + * limits, so we can return now. */ + if (new_timer_value == 0) { + clock_values->time_when_timer_expires = 0; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_UNLIMITED; + return ODK_DISABLE_TIMER; + } + /* If the caller gave us a pointer to store the new timer value. Fill it. */ + if (timer_value) { + *timer_value = new_timer_value; + } + if (odk_add_overflow_u64(system_time_seconds, new_timer_value, + &clock_values->time_when_timer_expires)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_ACTIVE; + return ODK_SET_TIMER; +} + +/* This is called regularly during playback if OEMCrypto does not implement its + * own timer. */ +OEMCryptoResult ODK_UpdateLastPlaybackTime(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values) { + OEMCryptoResult status = ODK_LicenseActive(timer_limits, clock_values); + if (status != OEMCrypto_SUCCESS) { + return status; + } + switch (clock_values->timer_status) { + case ODK_CLOCK_TIMER_STATUS_UNLIMITED: + break; + case ODK_CLOCK_TIMER_STATUS_ACTIVE: + /* Note: we allow playback at the time when the timer expires, but not + * after. This is not important for business cases, but it makes it + * easier to write tests. */ + if (clock_values->time_when_timer_expires > 0 && + system_time_seconds > clock_values->time_when_timer_expires) { + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_EXPIRED; + return ODK_TIMER_EXPIRED; + } + break; + default: /* Expired, error state, or never started. */ + return ODK_TIMER_EXPIRED; + } + clock_values->time_of_last_decrypt = system_time_seconds; + return OEMCrypto_SUCCESS; +} + +/* This is called from OEMCrypto_DeactivateUsageEntry. */ +OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values) { + if (clock_values == NULL) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (clock_values->status == kUnused) { + clock_values->status = kInactiveUnused; + } else if (clock_values->status == kActive) { + clock_values->status = kInactiveUsed; + } + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE; + return OEMCrypto_SUCCESS; +} + +/* This is called when OEMCrypto loads a legacy v15 license, from + * OEMCrypto_LoadKeys. */ +OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, + uint32_t key_duration, + uint64_t system_time_seconds) { + if (timer_limits == NULL || clock_values == NULL || nonce_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + timer_limits->soft_enforce_playback_duration = false; + timer_limits->soft_enforce_rental_duration = false; + timer_limits->earliest_playback_start_seconds = 0; + timer_limits->rental_duration_seconds = 0; + timer_limits->total_playback_duration_seconds = 0; + timer_limits->initial_renewal_duration_seconds = key_duration; + + nonce_values->api_major_version = 15; + nonce_values->api_minor_version = 0; + if (key_duration > 0) { + clock_values->time_when_timer_expires = system_time_seconds + key_duration; + } else { + clock_values->time_when_timer_expires = 0; + } + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; + return OEMCrypto_SUCCESS; +} + +/* This is called when OEMCrypto loads a legacy license renewal in + * OEMCrypto_RefreshKeys. */ +OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + const ODK_NonceValues* nonce_values, + uint64_t system_time_seconds, + uint32_t new_key_duration, + uint64_t* timer_value) { + if (timer_limits == NULL || clock_values == NULL || nonce_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (nonce_values->api_major_version != 15) { + return OEMCrypto_ERROR_INVALID_NONCE; + } + if (clock_values->status > kActive) { + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE; + return ODK_TIMER_EXPIRED; + } + return ODK_ComputeRenewalDuration(timer_limits, clock_values, + system_time_seconds, new_key_duration, + timer_value); +} diff --git a/common/oemcrypto_core_message/odk/src/odk_util.c b/common/oemcrypto_core_message/odk/src/odk_util.c new file mode 100644 index 0000000..f1bb05a --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/odk_util.c @@ -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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "common/oemcrypto_core_message/odk/src/odk_util.h" + +int crypto_memcmp(const void* in_a, const void* in_b, size_t len) { + if (len == 0) { + return 0; + } + + /* Only valid pointers are allowed. */ + if (in_a == NULL || in_b == NULL) { + return -1; + } + + const uint8_t* a = (const uint8_t*)in_a; + const uint8_t* b = (const uint8_t*)in_b; + uint8_t x = 0; + + for (size_t i = 0; i < len; i++) { + x |= a[i] ^ b[i]; + } + return x; +} + +bool ODK_NonceValuesEqual(const ODK_NonceValues* a, const ODK_NonceValues* b) { + if (a == NULL || b == NULL) { + return (a == b); + } + return (a->api_major_version == b->api_major_version && + a->api_minor_version == b->api_minor_version && + a->nonce == b->nonce && a->session_id == b->session_id); +} diff --git a/common/oemcrypto_core_message/odk/src/odk_util.h b/common/oemcrypto_core_message/odk/src/odk_util.h new file mode 100644 index 0000000..2dc8d3a --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/odk_util.h @@ -0,0 +1,34 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_ODK_UTIL_H_ +#define COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_ODK_UTIL_H_ + +#include +#include + +#include "common/oemcrypto_core_message/odk/include/odk_structs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* crypto_memcmp returns zero iff the |len| bytes at |a| and |b| are equal. It + * takes an amount of time dependent on |len|, but independent of the contents + * of |a| and |b|. Unlike memcmp, it cannot be used to order elements as the + * return value when a != b is undefined, other than being non-zero. */ +int crypto_memcmp(const void* a, const void* b, size_t len); + +bool ODK_NonceValuesEqual(const ODK_NonceValues* a, const ODK_NonceValues* b); + +#ifdef __cplusplus +} // extern "C" +#endif +#endif // COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_ODK_UTIL_H_ diff --git a/common/oemcrypto_core_message/odk/src/serialization_base.c b/common/oemcrypto_core_message/odk/src/serialization_base.c new file mode 100644 index 0000000..e9121a3 --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/serialization_base.c @@ -0,0 +1,242 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "common/oemcrypto_core_message/odk/src/serialization_base.h" + +#include +#include +#include + +#include "common/oemcrypto_core_message/odk/include/OEMCryptoCENCCommon.h" +#include "common/oemcrypto_core_message/odk/src/odk_overflow.h" + +struct _Message { + uint8_t* base; + size_t capacity; + size_t size; /* bytes written */ + size_t read_offset; /* bytes read */ + MessageStatus status; +}; + +bool ValidMessage(Message* message) { + if (message == NULL) { + return false; + } + if (message->status != MESSAGE_STATUS_OK) { + return false; + } + if (message->base == NULL) { + message->status = MESSAGE_STATUS_NULL_POINTER_ERROR; + return false; + } + if (message->size > message->capacity || + message->read_offset > message->size) { + message->status = MESSAGE_STATUS_OVERFLOW_ERROR; + return false; + } + return true; +} + +static void PackBytes(Message* message, const uint8_t* ptr, size_t count) { + if (count <= message->capacity - message->size) { + memcpy((void*)(message->base + message->size), (void*)ptr, count); + message->size += count; + } else { + message->status = MESSAGE_STATUS_OVERFLOW_ERROR; + } +} + +void Pack_enum(Message* message, int value) { + uint32_t v32 = value; + Pack_uint32_t(message, &v32); +} + +void Pack_bool(Message* message, const bool* value) { + if (!ValidMessage(message)) return; + uint8_t data[4] = {0}; + data[3] = *value ? 1 : 0; + PackBytes(message, data, sizeof(data)); +} + +void Pack_uint16_t(Message* message, const uint16_t* value) { + if (!ValidMessage(message)) return; + uint8_t data[2] = {0}; + data[0] = *value >> 8; + data[1] = *value >> 0; + PackBytes(message, data, sizeof(data)); +} + +void Pack_uint32_t(Message* message, const uint32_t* value) { + if (!ValidMessage(message)) return; + uint8_t data[4] = {0}; + data[0] = *value >> 24; + data[1] = *value >> 16; + data[2] = *value >> 8; + data[3] = *value >> 0; + PackBytes(message, data, sizeof(data)); +} + +void Pack_uint64_t(Message* message, const uint64_t* value) { + if (!ValidMessage(message)) return; + uint32_t hi = *value >> 32; + uint32_t lo = *value; + Pack_uint32_t(message, &hi); + Pack_uint32_t(message, &lo); +} + +void PackArray(Message* message, const uint8_t* base, size_t size) { + if (!ValidMessage(message)) return; + PackBytes(message, base, size); +} + +void Pack_OEMCrypto_Substring(Message* msg, const OEMCrypto_Substring* obj) { + uint32_t offset = (uint32_t)obj->offset; + uint32_t length = (uint32_t)obj->length; + Pack_uint32_t(msg, &offset); + Pack_uint32_t(msg, &length); +} + +static void UnpackBytes(Message* message, uint8_t* ptr, size_t count) { + if (count <= message->size - message->read_offset) { + memcpy((void*)ptr, (void*)(message->base + message->read_offset), count); + message->read_offset += count; + } else { + message->status = MESSAGE_STATUS_UNDERFLOW_ERROR; + } +} + +int Unpack_enum(Message* message) { + uint32_t v32; + Unpack_uint32_t(message, &v32); + return v32; +} + +void Unpack_bool(Message* message, bool* value) { + if (!ValidMessage(message)) return; + uint8_t data[4] = {0}; + UnpackBytes(message, data, sizeof(data)); + *value = (0 != data[3]); +} + +void Unpack_uint16_t(Message* message, uint16_t* value) { + if (!ValidMessage(message)) return; + uint8_t data[2] = {0}; + UnpackBytes(message, data, sizeof(data)); + *value = data[0]; + *value = *value << 8 | data[1]; +} + +void Unpack_uint32_t(Message* message, uint32_t* value) { + if (!ValidMessage(message)) return; + uint8_t data[4] = {0}; + UnpackBytes(message, data, sizeof(data)); + *value = data[0]; + *value = *value << 8 | data[1]; + *value = *value << 8 | data[2]; + *value = *value << 8 | data[3]; +} + +void Unpack_uint64_t(Message* message, uint64_t* value) { + if (!ValidMessage(message)) return; + uint32_t hi = 0; + uint32_t lo = 0; + Unpack_uint32_t(message, &hi); + Unpack_uint32_t(message, &lo); + *value = hi; + *value = *value << 32 | lo; +} + +void Unpack_OEMCrypto_Substring(Message* msg, OEMCrypto_Substring* obj) { + uint32_t offset = 0, length = 0; + Unpack_uint32_t(msg, &offset); + Unpack_uint32_t(msg, &length); + if (!ValidMessage(msg)) return; + /* Each substring should be contained within the message body, which is in the + * total message, just after the core message. The offset of a substring is + * relative to the message body. So we need to verify: + * 0 < offset and offset + length < message->capacity - message->size + * or offset + length + message->size < message->capacity + */ + size_t substring_end = 0; /* = offset + length; */ + size_t end = 0; /* = substring_end + message->size; */ + if (odk_add_overflow_ux(offset, length, &substring_end) || + odk_add_overflow_ux(substring_end, msg->size, &end) || + end > msg->capacity) { + msg->status = MESSAGE_STATUS_OVERFLOW_ERROR; + return; + } + obj->offset = offset; + obj->length = length; +} + +/* copy out */ +void UnpackArray(Message* message, uint8_t* address, size_t size) { + if (!ValidMessage(message)) return; + UnpackBytes(message, address, size); +} + +/* + * The message structure, which is separate from the buffer, + * is initialized to reference the buffer + */ +void InitMessage(Message* message, uint8_t* buffer, size_t capacity) { + if (message == NULL) return; + memset(message, 0, sizeof(Message)); + message->base = buffer; + message->capacity = capacity; + message->size = 0; + message->read_offset = 0; + message->status = MESSAGE_STATUS_OK; +} + +/* + * Set the message to an empty state + */ +void ResetMessage(Message* message) { + message->size = 0; + message->read_offset = 0; + message->status = MESSAGE_STATUS_OK; +} + +uint8_t* GetBase(Message* message) { + if (message == NULL) return NULL; + return message->base; +} + +size_t GetCapacity(Message* message) { + if (message == NULL) return 0; + return message->capacity; +} + +size_t GetSize(Message* message) { + if (message == NULL) return 0; + return message->size; +} + +void SetSize(Message* message, size_t size) { + if (message == NULL) return; + if (size > message->capacity) + message->status = MESSAGE_STATUS_OVERFLOW_ERROR; + else + message->size = size; +} + +MessageStatus GetStatus(Message* message) { return message->status; } + +void SetStatus(Message* message, MessageStatus status) { + message->status = status; +} + +size_t GetOffset(Message* message) { + if (message == NULL) return 0; + return message->read_offset; +} + +size_t SizeOfMessageStruct() { return sizeof(Message); } diff --git a/common/oemcrypto_core_message/odk/src/serialization_base.h b/common/oemcrypto_core_message/odk/src/serialization_base.h new file mode 100644 index 0000000..8b42ba9 --- /dev/null +++ b/common/oemcrypto_core_message/odk/src/serialization_base.h @@ -0,0 +1,95 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#ifndef COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_SERIALIZATION_BASE_H_ +#define COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_SERIALIZATION_BASE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include "common/oemcrypto_core_message/odk/include/OEMCryptoCENCCommon.h" + +#define SIZE_OF_MESSAGE_STRUCT 64 + +/* + * Description: + * Point |msg| to stack-array |blk|. + * |blk| is guaranteed large enough to hold a |Message| struct. + * |blk| cannot be used in the same scope as a variable name. + * |msg| points to valid memory in the same scope |AllocateMessage| is used. + * Parameters: + * msg: pointer to pointer to |Message| struct + * blk: variable name for stack-array + */ +#define AllocateMessage(msg, blk) \ + uint8_t blk[SIZE_OF_MESSAGE_STRUCT]; \ + *(msg) = (Message*)(blk) + +typedef struct _Message Message; + +typedef enum { + MESSAGE_STATUS_OK, + MESSAGE_STATUS_UNKNOWN_ERROR, + MESSAGE_STATUS_OVERFLOW_ERROR, + MESSAGE_STATUS_UNDERFLOW_ERROR, + MESSAGE_STATUS_PARSE_ERROR, + MESSAGE_STATUS_NULL_POINTER_ERROR, + MESSAGE_STATUS_API_VALUE_ERROR +} MessageStatus; + +bool ValidMessage(Message* message); + +void Pack_enum(Message* message, int value); +void Pack_bool(Message* message, const bool* value); +void Pack_uint16_t(Message* message, const uint16_t* value); +void Pack_uint32_t(Message* message, const uint32_t* value); +void Pack_uint64_t(Message* message, const uint64_t* value); +void PackArray(Message* message, const uint8_t* base, size_t size); +void Pack_OEMCrypto_Substring(Message* msg, const OEMCrypto_Substring* obj); + +int Unpack_enum(Message* message); +void Unpack_bool(Message* message, bool* value); +void Unpack_uint16_t(Message* message, uint16_t* value); +void Unpack_uint32_t(Message* message, uint32_t* value); +void Unpack_uint64_t(Message* message, uint64_t* value); +void UnpackArray(Message* message, uint8_t* address, + size_t size); /* copy out */ +void Unpack_OEMCrypto_Substring(Message* msg, OEMCrypto_Substring* obj); + +/* + * Initialize a message structure to reference a separate buffer. The caller + * is responsible for ensuring that the buffer remains allocated for the + * lifetime of the message. + */ +void InitMessage(Message* message, uint8_t* buffer, size_t capacity); + +/* + * Reset an existing the message to an empty state + */ +void ResetMessage(Message* message); +uint8_t* GetBase(Message* message); +size_t GetCapacity(Message* message); +size_t GetSize(Message* message); +void SetSize(Message* message, size_t size); +MessageStatus GetStatus(Message* message); +void SetStatus(Message* message, MessageStatus status); +size_t GetOffset(Message* message); + +size_t SizeOfMessageStruct(); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // COMMON_OEMCRYPTO_CORE_MESSAGE_ODK_SRC_SERIALIZATION_BASE_H_ 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/playready_interface.h b/common/playready_interface.h new file mode 100644 index 0000000..9058411 --- /dev/null +++ b/common/playready_interface.h @@ -0,0 +1,43 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// +#ifndef COMMON_PLAYREADY_INTERFACE_H_ +#define COMMON_PLAYREADY_INTERFACE_H_ + +#include + +#include "util/error_space.h" +#include "protos/public/license_protocol.pb.h" + +namespace widevine { + +class PlayReadyInterface { + public: + PlayReadyInterface() {} + virtual ~PlayReadyInterface() {} + + // Sends to a PlayReady Service running the PlayReady license server on + // Windows . + // Args: + // - |challenge| is a std::string which contains PlayReadyLicenseRequest. + // - |policy| is a std::string which contains the PlayReady Policy Setting. + // - |license| is a std::string of PlayReadyLicenseResponse returned from PlayReady + // Service. + + // Returns: + // - status code from downstream components. + virtual util::Status SendToPlayReady( + const std::string& playready_challenge, const std::string& provider, + const std::string& content_id, + const std::list& keys, + const License::Policy& policy, int64_t license_start_time, + std::string* playready_license) = 0; +}; + +} // namespace widevine + +#endif // COMMON_PLAYREADY_INTERFACE_H_ diff --git a/common/playready_sdk_impl.cc b/common/playready_sdk_impl.cc new file mode 100644 index 0000000..823efa3 --- /dev/null +++ b/common/playready_sdk_impl.cc @@ -0,0 +1,24 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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/playready_sdk_impl.h" + +#include "absl/status/status.h" +#include "absl/strings/escaping.h" +#include "util/task/codes.pb.h" + +namespace widevine { + +// TODO(user): fill in SendToPlayReady function. +util::Status PlayReadySdkImpl::SendToPlayReady( + const std::string& playready_challenge, const std::string& provider, + const std::string& content_id, const std::list& keys, + const License::Policy& policy, int64_t license_start_time, + std::string* playready_license) { + return OkStatus; +} +} // namespace widevine diff --git a/common/playready_sdk_impl.h b/common/playready_sdk_impl.h new file mode 100644 index 0000000..a2f8fae --- /dev/null +++ b/common/playready_sdk_impl.h @@ -0,0 +1,29 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////////////////// +#ifndef COMMON_PLAYREADY_SDK_IMPL_H_ +#define COMMON_PLAYREADY_SDK_IMPL_H_ + +#include "common/playready_interface.h" +#include "protos/public/license_protocol.pb.h" + +namespace widevine { +class PlayReadySdkImpl : public PlayReadyInterface { + public: + PlayReadySdkImpl() : PlayReadyInterface() {} + ~PlayReadySdkImpl() override {} + + util::Status SendToPlayReady(const std::string& playready_challenge, + const std::string& provider, + const std::string& content_id, + const std::list& keys, + const License::Policy& policy, + int64_t license_start_time, + std::string* playready_license) override; +}; +} // namespace widevine +#endif // COMMON_PLAYREADY_SDK_IMPL_H_ 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/python/BUILD b/common/python/BUILD index eb433f2..6ec6b50 100644 --- a/common/python/BUILD +++ b/common/python/BUILD @@ -6,100 +6,10 @@ # widevine-licensing@google.com. ################################################################################ -# -# Description: -# Build file for CLIF wrappers of Widevine common modules. - package(default_visibility = ["//visibility:public"]) -load("//devtools/clif/python:clif_build_rule.bzl", "py_clif_cc") - -py_clif_cc( - name = "aes_cbc_util", - srcs = ["aes_cbc_util.clif"], - deps = [ - "//common:aes_cbc_util", - ], -) - -py_clif_cc( - name = "certificate_type", +filegroup( + name = "provisioning_sdk_binary_release_files", srcs = ["certificate_type.clif"], - deps = [ - "//common:certificate_type", - ], ) -py_clif_cc( - name = "crypto_util", - srcs = ["crypto_util.clif"], - deps = [ - "//common:crypto_util", - ], -) - -py_clif_cc( - name = "drm_root_certificate", - srcs = ["drm_root_certificate.clif"], - clif_deps = [ - ":certificate_type", - ], - pyclif_deps = [ - "//protos/public:drm_certificate_pyclif", - "//protos/public:signed_drm_certificate_pyclif", - ], - deps = [ - "//util/task/python:status_clif", - "//common:drm_root_certificate", - ], -) - -py_clif_cc( - name = "rsa_test_keys", - testonly = 1, - srcs = ["rsa_test_keys.clif"], - deps = [ - "//common:rsa_test_keys", - ], -) - -py_clif_cc( - name = "rsa_key", - srcs = ["rsa_key.clif"], - deps = [ - "//common:rsa_key", - ], -) - -py_clif_cc( - name = "drm_service_certificate", - srcs = ["drm_service_certificate.clif"], - clif_deps = [ - ":certificate_type", - ":drm_root_certificate", - ], - deps = [ - "//util/task/python:status_clif", - "//common:drm_service_certificate", - ], -) - -py_clif_cc( - name = "signing_key_util", - srcs = ["signing_key_util.clif"], - pyclif_deps = [ - "//protos/public:license_protocol_pyclif", - ], - deps = [ - "//common:signing_key_util", - ], -) - -py_clif_cc( - name = "test_drm_certificates", - testonly = 1, - srcs = ["test_drm_certificates.clif"], - deps = [ - "//common:test_drm_certificates", - ], -) diff --git a/common/python/drm_root_certificate.clif b/common/python/drm_root_certificate.clif deleted file mode 100644 index 44038f8..0000000 --- a/common/python/drm_root_certificate.clif +++ /dev/null @@ -1,23 +0,0 @@ -################################################################################ -# Copyright 2018 Google LLC. -# -# This software is licensed under the terms defined in the Widevine Master -# License Agreement. For a copy of this agreement, please contact -# widevine-licensing@google.com. -################################################################################ - -from "util/task/python/clif.h" import * -from "common/python/certificate_type.h" import * -from "protos/public/drm_certificate_pyclif.h" import * -from "protos/public/signed_drm_certificate_pyclif.h" import * - -from "common/drm_root_certificate.h": - namespace `widevine`: - class DrmRootCertificate: - def VerifyCertificate(self, - serialized_certificate: bytes) -> (status: Status, - signed_certificate: SignedDrmCertificate, - certificate: DrmCertificate) - staticmethods from `DrmRootCertificate`: - def CreateByType(cert_type: CertificateType) -> (new_root_cert: DrmRootCertificate, - status: Status) diff --git a/common/python/hash_algorithm.clif b/common/python/hash_algorithm.clif new file mode 100644 index 0000000..0cbb137 --- /dev/null +++ b/common/python/hash_algorithm.clif @@ -0,0 +1,11 @@ +################################################################################ +# 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. +################################################################################ + +from "common/hash_algorithm.h": + namespace `widevine`: + enum HashAlgorithm diff --git a/common/python/hash_algorithm_util.clif b/common/python/hash_algorithm_util.clif new file mode 100644 index 0000000..fe01c09 --- /dev/null +++ b/common/python/hash_algorithm_util.clif @@ -0,0 +1,16 @@ +################################################################################ +# 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. +################################################################################ + +from "common/python/hash_algorithm.h" import * +from "protos/public/hash_algorithm_pyclif.h" import * + +from "common/hash_algorithm_util.h": + namespace `widevine`: + def HashAlgorithmProtoToEnum( + hash_algorithm_proto: HashAlgorithmProto + ) -> HashAlgorithm diff --git a/common/python/rsa_key.clif b/common/python/rsa_key.clif index 14b5bd4..fc5b58f 100644 --- a/common/python/rsa_key.clif +++ b/common/python/rsa_key.clif @@ -6,10 +6,12 @@ # widevine-licensing@google.com. ################################################################################ +from "common/python/hash_algorithm.h" import * + from "common/rsa_key.h": namespace `widevine`: class RsaPublicKey: - def VerifySignature(self, message: bytes, signature: bytes) -> bool + def VerifySignature(self, message: bytes, hash_algorithm: HashAlgorithm, signature: bytes) -> bool staticmethods from `RsaPublicKey`: def Create(serialized_key: bytes) -> RsaPublicKey diff --git a/common/python/test_drm_certificates.clif b/common/python/test_drm_certificates.clif index 7b3bde8..1cebfb2 100644 --- a/common/python/test_drm_certificates.clif +++ b/common/python/test_drm_certificates.clif @@ -12,4 +12,4 @@ from "common/test_drm_certificates.h": def test_root_certificate(self) -> bytes def test_intermediate_certificate(self) -> bytes def test_user_device_certificate(self) -> bytes - def test_service_certificate(self) -> bytes + def test_service_certificate_no_type(self) -> bytes 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..7ae9ed7 100644 --- a/common/remote_attestation_verifier.h +++ b/common/remote_attestation_verifier.h @@ -16,8 +16,7 @@ #include #include -#include "base/macros.h" -#include "base/thread_annotations.h" +#include "absl/base/thread_annotations.h" #include "absl/synchronization/mutex.h" #include "common/status.h" #include "common/x509_cert.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..58c89fe --- /dev/null +++ b/common/rot_id_generator.cc @@ -0,0 +1,130 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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/escaping.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 { + return widevine::GenerateUniqueIdHash(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(); +} + +Status RootOfTrustIdDecryptor::VerifyAndExtractAllValues( + uint32_t system_id, const RootOfTrustId& root_of_trust_id, + std::string* device_unique_id, std::string* device_unique_id_hash) const { + CHECK(device_unique_id != nullptr) << "device_unique_id was null."; + CHECK(device_unique_id_hash != nullptr) << "device_unique_id_hash was null."; + + Status status = DecryptUniqueId( + system_id, root_of_trust_id.encrypted_unique_id(), device_unique_id); + if (!status.ok()) { + return status; + } + *device_unique_id_hash = + widevine::GenerateUniqueIdHash(*device_unique_id, wv_shared_salt_); + std::string revocation_hash = + GenerateRotIdHash(root_of_trust_id.encrypted_unique_id(), system_id, + *device_unique_id_hash); + // This should not happen unless there's a bug in the way we issue root of + // trust ids. + if (revocation_hash != root_of_trust_id.unique_id_hash()) { + return Status(error::INVALID_ARGUMENT, + "The generated revocation hash did not match the one in the " + "root_of_trust_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..b6ae69a --- /dev/null +++ b/common/rot_id_generator.h @@ -0,0 +1,103 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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, + const 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, + const std::string& wv_shared_salt) + : ecies_decryptor_(std::move(ecies_decryptor)), + wv_shared_salt_(wv_shared_salt) {} + + // 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; + + // Decrypts the encrypted id within the |root_of_trust_id|, extacting the + // |device_unique_id|, and generating the |device_unique_id_hash|. It then + // generates the rot id revocation hash and verifies that it matches the + // unique_id_hash from the root_of_trust_id. + Status VerifyAndExtractAllValues(uint32_t system_id, + const RootOfTrustId& root_of_trust_id, + std::string* device_unique_id, + std::string* device_unique_id_hash) const; + + private: + std::unique_ptr ecies_decryptor_; + std::string wv_shared_salt_; +}; + +} // 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..e8b9bc1 --- /dev/null +++ b/common/rot_id_generator_test.cc @@ -0,0 +1,304 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 + +#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_METHOD(bool, Encrypt, + (const std::string&, const std::string&, std::string*), + (const, override)); + + 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(), kTestSharedSalt); + + // 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(), kTestSharedSalt); + + 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(), kTestSharedSalt); + + // 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(), kTestSharedSalt); + + // 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(), kTestSharedSalt); + + // 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"); +} + +TEST_F(RootOfTrustIdGeneratorTest, VerifyAndExtractAllValuesSuccess) { + RootOfTrustIdGenerator generator(CreateEncryptor(), kTestSharedSalt); + RootOfTrustIdDecryptor decryptor(CreateDecryptor(), kTestSharedSalt); + + // 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; + std::string decrypted_unique_id_hash; + EXPECT_OK(decryptor.VerifyAndExtractAllValues(kTestSystemId, root_of_trust_id, + &decrypted_unique_id, + &decrypted_unique_id_hash)); + EXPECT_EQ(kTestUniqueId, decrypted_unique_id); + EXPECT_EQ(generator.GenerateUniqueIdHash(kTestUniqueId), + decrypted_unique_id_hash); +} + +TEST_F(RootOfTrustIdGeneratorTest, VerifyAndExtractAllValuesSystemIdMismatch) { + RootOfTrustIdGenerator generator(CreateEncryptor(), kTestSharedSalt); + RootOfTrustIdDecryptor decryptor(CreateDecryptor(), kTestSharedSalt); + + // 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; + std::string decrypted_unique_id_hash; + EXPECT_EQ(error::INTERNAL, + decryptor + .VerifyAndExtractAllValues(kTestSystemId + 1, root_of_trust_id, + &decrypted_unique_id, + &decrypted_unique_id_hash) + .error_code()); +} + +} // namespace widevine diff --git a/common/rot_id_util.cc b/common/rot_id_util.cc new file mode 100644 index 0000000..a43e942 --- /dev/null +++ b/common/rot_id_util.cc @@ -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. + +#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 { + +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)); +} + +std::string GenerateUniqueIdHash(const std::string& unique_id, + const std::string& salt) { + if (unique_id.empty()) { + LOG(WARNING) << "unique_id should not be empty."; + return ""; + } + if (salt.empty()) { + LOG(WARNING) << "salt should not be empty."; + return ""; + } + return widevine::Sha256_Hash(absl::StrCat(unique_id, salt)); +} + +} // namespace widevine diff --git a/common/rot_id_util.h b/common/rot_id_util.h new file mode 100644 index 0000000..835985e --- /dev/null +++ b/common/rot_id_util.h @@ -0,0 +1,67 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 generates the unique id hash from the |unique_id| and +// the |salt|. |salt| is an internal secret. +// +// Returns the hash value on success. +// If |salt| or |unique_id| are empty, this will return an empty string. +std::string GenerateUniqueIdHash(const std::string& unique_id, + const std::string& salt); + +// 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); + +// 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. +template +bool IsRotIdRevoked(const std::string& encrypted_unique_id, uint32_t system_id, + const std::string& rot_id_hash, const V& 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) { + if (GenerateRotIdHash(encrypted_unique_id, system_id, revoked_id) == + rot_id_hash) { + return true; + } + } + return false; +} + +} // 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..9b89d98 --- /dev/null +++ b/common/rot_id_util_test.cc @@ -0,0 +1,85 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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"; + +constexpr char kFakeUniqueId[] = "fake unique_id"; +constexpr char kFakeSecretSalt[] = "fake secret salt"; + +// This is the ROT ID Hash generated from the fake values. +constexpr char kRotIdHashHex[] = + "0a757dde0f1080b60f34bf8e46af573ce987b5ed1c831b44952e2feed5243a95"; +// This is the unique id hash generated from the fake unique id value. +constexpr char kUniqueIdHashHex[] = + "da20922e84b48e52223496f44b07632a4db19d488cd71cf813de300b9d244e06"; + +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)); +} + +// This test really only ensures the stability of the GenerateUniqueIdHash +// implementation. If the hash ever changes, then it will introduce problems +// into the ecosystem. +TEST(RotIdUtilTest, GenerateUniqueIdHashSuccess) { + ASSERT_EQ(absl::HexStringToBytes(kUniqueIdHashHex), + GenerateUniqueIdHash(kFakeUniqueId, kFakeSecretSalt)); +} + +TEST(RotIdUtilTest, GenerateUniqueIdHashEmptyValues) { + ASSERT_EQ("", GenerateUniqueIdHash(kFakeUniqueId, "")); + ASSERT_EQ("", GenerateUniqueIdHash("", kFakeSecretSalt)); +} + +} // namespace widevine diff --git a/common/rsa_key.cc b/common/rsa_key.cc index 6a14527..d788986 100644 --- a/common/rsa_key.cc +++ b/common/rsa_key.cc @@ -13,7 +13,7 @@ // // RSA signature details: // Algorithm: RSASSA-PSS -// Hash algorithm: SHA1 +// Hash algorithm: |hash_algorithm| // Mask generation function: mgf1SHA1 // Salt length: 20 bytes // Trailer field: 0xbc @@ -21,7 +21,7 @@ // RSA encryption details: // Algorithm: RSA-OAEP // Mask generation function: mgf1SHA1 -// Label (encoding paramter): empty std::string +// Label (encoding parameter): empty std::string #include "common/rsa_key.h" @@ -31,6 +31,7 @@ #include "openssl/evp.h" #include "openssl/rsa.h" #include "openssl/sha.h" +#include "common/hash_algorithm.h" #include "common/rsa_util.h" #include "common/sha_util.h" @@ -51,6 +52,21 @@ std::string OpenSSLErrorString(uint32_t error) { return buf; } +std::string GetMessageDigest(const std::string& message, + widevine::HashAlgorithm hash_algorithm) { + switch (hash_algorithm) { + // The default hash algorithm of RSA signature is SHA1. + case widevine::HashAlgorithm::kUnspecified: + case widevine::HashAlgorithm::kSha1: + return widevine::Sha1_Hash(message); + case widevine::HashAlgorithm::kSha256: + return widevine::Sha256_Hash(message); + } + LOG(FATAL) << "Unexpected hash algorithm: " + << static_cast(hash_algorithm); + return ""; +} + } // namespace namespace widevine { @@ -101,7 +117,9 @@ bool RsaPrivateKey::Decrypt(const std::string& encrypted_message, return true; } + bool RsaPrivateKey::GenerateSignature(const std::string& message, + HashAlgorithm hash_algorithm, std::string* signature) const { DCHECK(signature); @@ -109,8 +127,12 @@ bool RsaPrivateKey::GenerateSignature(const std::string& message, LOG(ERROR) << "Message to be signed is empty"; return false; } - // Hash the message using SHA1. - std::string message_digest = Sha1_Hash(message); + // Hash the message using corresponding hash algorithm. + std::string message_digest = GetMessageDigest(message, hash_algorithm); + if (message_digest.empty()) { + LOG(ERROR) << "Empty message digest"; + return false; + } // Add PSS padding. size_t rsa_size = RSA_size(key_); @@ -172,6 +194,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) { @@ -209,6 +236,7 @@ bool RsaPublicKey::Encrypt(const std::string& clear_message, } bool RsaPublicKey::VerifySignature(const std::string& message, + HashAlgorithm hash_algorithm, const std::string& signature) const { if (message.empty()) { LOG(ERROR) << "Signed message is empty"; @@ -232,8 +260,13 @@ bool RsaPublicKey::VerifySignature(const std::string& message, << OpenSSLErrorString(ERR_get_error()); return false; } - // Hash the message using SHA1. - std::string message_digest = Sha1_Hash(message); + + // Hash the message using SHA1 using corresponding hash algorithm. + std::string message_digest = GetMessageDigest(message, hash_algorithm); + if (message_digest.empty()) { + LOG(ERROR) << "Empty message digest"; + return false; + } // Verify PSS padding. if (RSA_verify_PKCS1_PSS_mgf1( @@ -248,8 +281,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 +324,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..0a8f9ba 100644 --- a/common/rsa_key.h +++ b/common/rsa_key.h @@ -18,8 +18,9 @@ #include #include -#include "base/macros.h" +#include "absl/base/macros.h" #include "openssl/rsa.h" +#include "common/hash_algorithm.h" namespace widevine { @@ -40,9 +41,12 @@ class RsaPrivateKey { virtual bool Decrypt(const std::string& encrypted_message, std::string* decrypted_message) const; + // Generate RSSASSA-PSS signature. Caller retains ownership of all parameters. - // Returns true if successful, false otherwise. + // |hash_algorithm| indicates the hash algorithm used. Returns true if + // successful, false otherwise. virtual bool GenerateSignature(const std::string& message, + HashAlgorithm hash_algorithm, std::string* signature) const; // Generate SHA256 digest, PKCS#7 padded signature. Caller retains ownership @@ -79,7 +83,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. @@ -91,9 +101,12 @@ class RsaPublicKey { virtual bool Encrypt(const std::string& clear_message, std::string* encrypted_message) const; + // Verify RSSASSA-PSS signature. Caller retains ownership of all parameters. - // Returns true if validation succeeds, false otherwise. + // |hash_algorithm| indicates the hash algorithm used. Returns true if + // validation succeeds, false otherwise. virtual bool VerifySignature(const std::string& message, + HashAlgorithm hash_algorithm, const std::string& signature) const; // Verify a signature. This method takes two parameters: |message| which is a @@ -132,6 +145,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 +158,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..395c746 100644 --- a/common/rsa_key_test.cc +++ b/common/rsa_key_test.cc @@ -10,10 +10,12 @@ // Description: // Unit test for rsa_key RSA encryption and signing. +#include "common/rsa_key.h" + #include #include "testing/gunit.h" -#include "common/rsa_key.h" +#include "common/hash_algorithm.h" #include "common/rsa_test_keys.h" #include "common/rsa_util.h" @@ -29,7 +31,8 @@ class RsaKeyTest : public ::testing::Test { std::unique_ptr public_key); void TestSigning(std::unique_ptr private_key, - std::unique_ptr public_key); + std::unique_ptr public_key, + HashAlgorithm hash_algorithm); void TestSigningSha256Pkcs7(std::unique_ptr private_key, std::unique_ptr public_key); @@ -46,8 +49,7 @@ TEST_F(RsaKeyTest, CopyConstructor) { std::unique_ptr private_key_copy( new RsaPrivateKey(*private_key)); - std::unique_ptr public_key_copy( - new RsaPublicKey(*public_key)); + std::unique_ptr public_key_copy(new RsaPublicKey(*public_key)); EXPECT_TRUE(public_key_copy->MatchesPublicKey(*public_key)); EXPECT_TRUE(public_key_copy->MatchesPrivateKey(*private_key)); @@ -79,23 +81,29 @@ void RsaKeyTest::TestEncryption(std::unique_ptr private_key, } void RsaKeyTest::TestSigning(std::unique_ptr private_key, - std::unique_ptr public_key) { + std::unique_ptr public_key, + HashAlgorithm hash_algorithm) { ASSERT_TRUE(private_key); ASSERT_TRUE(public_key); std::string signature; - EXPECT_TRUE(private_key->GenerateSignature(kTestMessage, &signature)); - EXPECT_TRUE(public_key->VerifySignature(kTestMessage, signature)); + EXPECT_TRUE( + private_key->GenerateSignature(kTestMessage, hash_algorithm, &signature)); + EXPECT_TRUE( + public_key->VerifySignature(kTestMessage, hash_algorithm, signature)); // Add a byte to the signature. std::string bad_signature(signature); bad_signature += '\0'; - EXPECT_FALSE(public_key->VerifySignature(kTestMessage, bad_signature)); + EXPECT_FALSE( + public_key->VerifySignature(kTestMessage, hash_algorithm, bad_signature)); // Remove a byte from the signature. bad_signature = signature.substr(0, signature.size() - 1); - EXPECT_FALSE(public_key->VerifySignature(kTestMessage, bad_signature)); + EXPECT_FALSE( + public_key->VerifySignature(kTestMessage, hash_algorithm, bad_signature)); // Change a byte in the signature. bad_signature = signature; bad_signature[32] ^= 0x55; - EXPECT_FALSE(public_key->VerifySignature(kTestMessage, bad_signature)); + EXPECT_FALSE( + public_key->VerifySignature(kTestMessage, hash_algorithm, bad_signature)); } void RsaKeyTest::TestSigningSha256Pkcs7( @@ -161,33 +169,59 @@ 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(); - TestSigning( - std::unique_ptr(RsaPrivateKey::Create(private_key)), - std::unique_ptr(RsaPublicKey::Create(public_key))); - TestSigning(factory_.CreateFromPkcs1PrivateKey(private_key), - factory_.CreateFromPkcs1PublicKey(public_key)); + const HashAlgorithm kHashAlgorithms[] = {HashAlgorithm::kUnspecified, + HashAlgorithm::kSha1, + HashAlgorithm::kSha256}; + for (HashAlgorithm hash_algorithm : kHashAlgorithms) { + TestSigning( + std::unique_ptr(RsaPrivateKey::Create(private_key)), + std::unique_ptr(RsaPublicKey::Create(public_key)), + hash_algorithm); + TestSigning(factory_.CreateFromPkcs1PrivateKey(private_key), + factory_.CreateFromPkcs1PublicKey(public_key), hash_algorithm); + } } TEST_F(RsaKeyTest, SignAndVerify_2048) { const std::string& private_key = test_keys_.private_test_key_2_2048_bits(); const std::string& public_key = test_keys_.public_test_key_2_2048_bits(); - TestSigning( - std::unique_ptr(RsaPrivateKey::Create(private_key)), - std::unique_ptr(RsaPublicKey::Create(public_key))); + const HashAlgorithm kHashAlgorithms[] = {HashAlgorithm::kUnspecified, + HashAlgorithm::kSha1, + HashAlgorithm::kSha256}; + for (HashAlgorithm hash_algorithm : kHashAlgorithms) { + TestSigning( + std::unique_ptr(RsaPrivateKey::Create(private_key)), + std::unique_ptr(RsaPublicKey::Create(public_key)), + hash_algorithm); - std::string pkcs8_key; - std::string passphrase("passphrase"); - ASSERT_TRUE(rsa_util::RsaPrivateKeyToEncryptedPrivateKeyInfo( - private_key, passphrase, &pkcs8_key)); - TestSigning(factory_.CreateFromPkcs8PrivateKey(pkcs8_key, passphrase), - factory_.CreateFromPkcs1PublicKey(public_key)); + std::string pkcs8_key; + std::string passphrase("passphrase"); + ASSERT_TRUE(rsa_util::RsaPrivateKeyToEncryptedPrivateKeyInfo( + private_key, passphrase, &pkcs8_key)); + TestSigning(factory_.CreateFromPkcs8PrivateKey(pkcs8_key, passphrase), + factory_.CreateFromPkcs1PublicKey(public_key), hash_algorithm); - ASSERT_TRUE(rsa_util::RsaPrivateKeyToPrivateKeyInfo(private_key, &pkcs8_key)); - TestSigning(factory_.CreateFromPkcs8PrivateKey(pkcs8_key, std::string()), - factory_.CreateFromPkcs1PublicKey(public_key)); + ASSERT_TRUE( + rsa_util::RsaPrivateKeyToPrivateKeyInfo(private_key, &pkcs8_key)); + TestSigning(factory_.CreateFromPkcs8PrivateKey(pkcs8_key, std::string()), + factory_.CreateFromPkcs1PublicKey(public_key), hash_algorithm); + } } TEST_F(RsaKeyTest, SignAndVerifySha256Pkcs7_3072) { 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..018b4ef --- /dev/null +++ b/common/security_profile_list.cc @@ -0,0 +1,302 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 "glog/logging.h" +#include "google/protobuf/text_format.h" +#include "common/client_id_util.h" +#include "common/device_status_list.h" +#include "protos/public/client_identification.pb.h" +#include "protos/public/device_certificate_status.pb.h" +#include "protos/public/device_common.pb.h" +#include "protos/public/provisioned_device_info.pb.h" +#include "protos/public/security_profile.pb.h" + +namespace widevine { +using ClientCapabilities = ClientIdentification::ClientCapabilities; + +SecurityProfileList::SecurityProfileList(const std::string& profile_namespace) + : profile_namespace_(profile_namespace) {} + +int SecurityProfileList::Init() { return 0; } + +int SecurityProfileList::GetQualifiedProfilesFromSpecifiedProfiles( + const std::vector& profiles_to_check, + const ClientIdentification& client_id, + const ProvisionedDeviceInfo& device_info, + std::vector* qualified_profiles) const { + if (qualified_profiles == nullptr) { + return 0; + } + qualified_profiles->clear(); + absl::ReaderMutexLock lock(&mutex_); + for (auto& profile_name : profiles_to_check) { + SecurityProfile profile; + if (GetProfileByName(profile_name, &profile)) { + if (DoesProfileQualify(profile, client_id, device_info)) { + qualified_profiles->push_back(profile.name()); + } + } + } + return qualified_profiles->size(); +} + +int SecurityProfileList::GetQualifiedProfiles( + const ClientIdentification& client_id, + const ProvisionedDeviceInfo& device_info, + std::vector* qualified_profiles) const { + if (qualified_profiles == nullptr) { + return 0; + } + qualified_profiles->clear(); + absl::ReaderMutexLock lock(&mutex_); + for (auto& profile : security_profiles_) { + if (DoesProfileQualify(profile, client_id, device_info)) { + qualified_profiles->push_back(profile.name()); + } + } + return qualified_profiles->size(); +} + +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_api_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_request_model_info()->set_manufacturer( + GetClientInfo(client_id, kModDrmMake)); + drm_info->mutable_request_model_info()->set_model_name( + GetClientInfo(client_id, kModDrmModel)); + drm_info->mutable_request_model_info()->set_status( + DeviceModel::MODEL_STATUS_UNVERIFIED); + for (const auto& model_info : device_info.model_info()) { + if (model_info.manufacturer() == + drm_info->request_model_info().manufacturer() && + model_info.model_name() == + drm_info->request_model_info().model_name()) { + drm_info->mutable_request_model_info()->set_status(model_info.status()); + drm_info->mutable_request_model_info()->set_model_year( + model_info.model_year()); + break; + } + } + drm_info->set_system_id(device_info.system_id()); + SecurityProfile::ClientInfo* client_info = drm_info->mutable_client_info(); + client_info->set_device_name(GetClientInfo(client_id, kModDrmDeviceName)); + SecurityProfile::ClientInfo::ProductInfo* product_info = + client_info->mutable_product_info(); + product_info->set_product_name(GetClientInfo(client_id, kModDrmProductName)); + product_info->set_build_info(GetClientInfo(client_id, kModDrmBuildInfo)); + product_info->set_oem_crypto_security_patch_level( + GetClientInfo(client_id, kModDrmOemCryptoSecurityPatchLevel)); + // TODO(user): Figure out how to get device platform pushed into SPL API. + DeviceCertificateStatus device_certificate_status; + SecurityProfile::DeviceState device_model_state = + SecurityProfile::DEVICE_STATE_UNKNOWN; + Status status = + DeviceStatusList::Instance()->GetDeviceCertificateStatusBySystemId( + device_info.system_id(), &device_certificate_status); + if (status.ok() && device_certificate_status.has_status()) { + switch (device_certificate_status.status()) { + case DeviceCertificateStatus::STATUS_IN_TESTING: + device_model_state = SecurityProfile::IN_TESTING; + break; + case DeviceCertificateStatus::STATUS_RELEASED: + device_model_state = SecurityProfile::RELEASED; + break; + case DeviceCertificateStatus::STATUS_TEST_ONLY: + device_model_state = SecurityProfile::TEST_ONLY; + break; + case DeviceCertificateStatus::STATUS_REVOKED: + device_model_state = SecurityProfile::REVOKED; + break; + default: + break; + } + } + drm_info->set_device_model_state(device_model_state); + return true; +} + +bool SecurityProfileList::GetProfileByName( + const std::string& name, SecurityProfile* security_profile) const { + absl::ReaderMutexLock lock(&mutex_); + for (auto& profile : security_profiles_) { + if (profile.name() == name) { + 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 (GetProfileByName(profile_to_insert.name(), nullptr)) { + LOG(ERROR) << "Unable to insert profile: " << profile_to_insert.name() + << ". Name already exist."; + return false; + } + if (profile_to_insert.min_security_requirements().security_level() == + ProvisionedDeviceInfo::LEVEL_UNSPECIFIED) { + LOG(ERROR) << "Unable to insert profile: " << profile_to_insert.name() + << ". Security level not specified."; + return false; + } + + absl::WriterMutexLock lock(&mutex_); + security_profiles_.push_back(profile_to_insert); + return true; +} + +int SecurityProfileList::NumProfiles() const { + absl::ReaderMutexLock lock(&mutex_); + return security_profiles_.size(); +} + +void SecurityProfileList::ClearAllProfiles() { + absl::WriterMutexLock lock(&mutex_); + security_profiles_.clear(); +} + +int64_t SecurityProfileList::GetCurrentTimeSeconds() const { + return time(nullptr); +} + +bool SecurityProfileList::DoesProfileQualify( + const SecurityProfile& profile, const ClientIdentification& client_id, + const ProvisionedDeviceInfo& device_info) const { + // First filtering out inactive profiles (expired or future start time + // profiles). + if (!IsProfileActive(profile, GetCurrentTimeSeconds())) { + return false; + } + // Check if the current device could be overridden. + if (profile.device_exceptions_size() > 0) { + uint32_t system_id = device_info.system_id(); + for (const auto& device_exception : profile.device_exceptions()) { + if (device_exception.system_id() == system_id) { + if (device_exception.action() == + DeviceException::DEVICE_EXCEPTION_ALLOW) { + // Include this profile for the system Id regardless of other profile + // requirements. + return true; + } else if (device_exception.action() == + DeviceException::DEVICE_EXCEPTION_BLOCK) { + // Do not use this profile for the system Id regardless of other + // profile requirements. + return false; + } + } + } + } + if (profile.min_security_requirements().security_level() < + device_info.security_level()) { + VLOG(1) << "Profile does not qualify <" << profile.name() + << "> security level: " + << profile.min_security_requirements().security_level() + << ", device: " << device_info.security_level(); + return false; + } + if (profile.min_security_requirements().oemcrypto_api_version() > + client_id.client_capabilities().oem_crypto_api_version()) { + VLOG(1) << "Profile does not qualify <" << profile.name() + << "> oemcrypto version: " + << profile.min_security_requirements().oemcrypto_api_version() + << ", device: " + << client_id.client_capabilities().oem_crypto_api_version(); + return false; + } + if (profile.min_output_requirements().hdcp_version() > + client_id.client_capabilities().max_hdcp_version()) { + VLOG(1) << "profile does not qualify <" << profile.name() + << "> hdcp_version: " + << profile.min_output_requirements().hdcp_version() << ", device: " + << client_id.client_capabilities().max_hdcp_version(); + return false; + } + if (profile.min_output_requirements().analog_output_capabilities() > + client_id.client_capabilities().analog_output_capabilities()) { + VLOG(1) << "Profile idoes not qualify <" << profile.name() + << "> analog output: " + << profile.min_output_requirements().analog_output_capabilities() + << ", device: " + << client_id.client_capabilities().analog_output_capabilities(); + return false; + } + if (profile.min_security_requirements().resource_rating_tier() > + client_id.client_capabilities().resource_rating_tier()) { + VLOG(1) << "Profile does not qualify <" << profile.name() + << "> resource rating tier: " + << profile.min_security_requirements().resource_rating_tier() + << ", device: " + << client_id.client_capabilities().resource_rating_tier(); + return false; + } + return true; +} + +void SecurityProfileList::GetProfileNames( + std::vector* profile_names) const { + if (profile_names == nullptr) { + return; + } + absl::ReaderMutexLock lock(&mutex_); + for (auto& profile : security_profiles_) { + profile_names->push_back(profile.name()); + } +} + +bool SecurityProfileList::IsProfileActive(const SecurityProfile& profile, + int64_t current_time_seconds) const { + if (!profile.has_control_time()) { + return true; + } + // Filter out profile with start time in the future. + if (profile.control_time().start_time_seconds() > current_time_seconds) { + VLOG(1) << "Profile does not qualify <" << profile.name() + << "> start time: " << profile.control_time().start_time_seconds() + << ", current time: " << current_time_seconds + << ". This is an inactive profile with a future start time."; + return false; + } + int64_t end_time_seconds = profile.control_time().end_time_seconds(); + // For never expired profile, it shouldn't be filtered out. + if (end_time_seconds == 0) { + return true; + } + // Filter out expired profile. + if (end_time_seconds < current_time_seconds) { + VLOG(1) << "Profile does not qualify <" << profile.name() + << "> end time: " << end_time_seconds + << ", current time: " << current_time_seconds + << ". This is an inactive profile which has been expired."; + return false; + } + return true; +} + +} // namespace widevine diff --git a/common/security_profile_list.h b/common/security_profile_list.h new file mode 100644 index 0000000..b6cdb96 --- /dev/null +++ b/common/security_profile_list.h @@ -0,0 +1,97 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 device security profiles. Security profiles indicate rules +// to allow using the profile. The rules are based on DRM capabilities of a +// device. + +#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/device_security_profile_data.pb.h" +#include "protos/public/provisioned_device_info.pb.h" +#include "protos/public/security_profile.pb.h" + +namespace widevine { +using ClientCapabilities = ClientIdentification::ClientCapabilities; + +// 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: + explicit SecurityProfileList(const std::string& profile_namespace); + virtual ~SecurityProfileList() {} + + // Initialize the security profile list. The size of the profile list is + // returned. + virtual 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); + + // Populates |profiles_allow| with a list of profiles from the specified + // |profiles_to_check| list that meet the requirements for the this device. + // The number of profiles is returned. + virtual int GetQualifiedProfilesFromSpecifiedProfiles( + const std::vector& profiles_to_check, + const ClientIdentification& client_id, + const ProvisionedDeviceInfo& device_info, + std::vector* qualified_profiles) const; + + // Populates |profiles_to_allow| with a list of profiles that meet the + // requirements for the this device. The number of profiles is returned. + virtual int GetQualifiedProfiles( + const ClientIdentification& client_id, + const ProvisionedDeviceInfo& device_info, + std::vector* qualified_profiles) const; + + // Return true if a profile exist matching the specified |name|. + // |security_profile| is owned by the caller and is populated if a profile + // exist. + bool GetProfileByName(const std::string& name, + SecurityProfile* security_profile) 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; + + // Return the number of profiles in the list. + int NumProfiles() const; + + // Return a list of profile names. + virtual void GetProfileNames(std::vector* profile_names) const; + + protected: + void ClearAllProfiles(); + + private: + bool DoesProfileQualify(const SecurityProfile& profile, + const ClientIdentification& client_id, + const ProvisionedDeviceInfo& device_info) const; + + int64_t GetCurrentTimeSeconds() const; + + bool IsProfileActive(const SecurityProfile& profile, + int64_t current_time_seconds) const; + + mutable absl::Mutex mutex_; + // Security profiles + std::string profile_namespace_; + std::vector security_profiles_ ABSL_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..3ce4027 --- /dev/null +++ b/common/security_profile_list_test.cc @@ -0,0 +1,364 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 "absl/memory/memory.h" +#include "common/client_id_util.h" +#include "protos/public/device_common.pb.h" +#include "protos/public/device_security_profile_data.pb.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 char kDeviceNameValue[] = "TestDeviceName"; +const char kProductNameValue[] = "TestProductName"; +const char kBuildInfoValue[] = "TestBuildInfo"; +const char kOemCryptoSecurityPatchLevelValue[] = + "TestOemCryptoSecurityPatchLevel"; +const char kDefaultContentOwnerName[] = "Widevine"; +const uint32_t kSystemId = 1234; +const uint32_t kSystemIdOverridden = 1567; +const uint32_t kResourceTierLow = 1; +const uint32_t kResourceTierMed = 2; +const uint32_t kResourceTierHigh = 3; +const uint64_t kStartTimeInSeconds = 5000; +const uint64_t kEndTimeInSeconds = 10000; + +class SecurityProfileListTest : public ::testing::Test { + public: + SecurityProfileListTest() {} + ~SecurityProfileListTest() override {} + + void SetUp() override { + const uint32_t oemcrypto_12 = 12; + SecurityProfile profile; + std::string profile_namespace = "widevine"; + profile_list_ = absl::make_unique(profile_namespace); + + AddClientInfo(&client_id_, kMakeName, kMakeValue); + AddClientInfo(&client_id_, kModelName, kModelValue); + AddClientInfo(&client_id_, kModDrmDeviceName, kDeviceNameValue); + AddClientInfo(&client_id_, kModDrmProductName, kProductNameValue); + AddClientInfo(&client_id_, kModDrmBuildInfo, kBuildInfoValue); + AddClientInfo(&client_id_, kModDrmOemCryptoSecurityPatchLevel, + kOemCryptoSecurityPatchLevelValue); + client_id_.mutable_client_capabilities()->set_oem_crypto_api_version( + oemcrypto_12); + client_id_.mutable_client_capabilities()->set_max_hdcp_version( + ClientCapabilities::HDCP_V2_2); + client_id_.mutable_client_capabilities()->set_resource_rating_tier( + kResourceTierHigh); + + device_info_.set_security_level(ProvisionedDeviceInfo::LEVEL_1); + device_info_.set_system_id(kSystemId); + } + std::unique_ptr profile_list_; + ClientIdentification client_id_; + ProvisionedDeviceInfo device_info_; +}; + +TEST_F(SecurityProfileListTest, InsertProfile) { + // Insert test profile1 into the list. + SecurityProfileList profile_list("widevine-test"); + SecurityProfile profile1; + profile1.set_name("profile1"); + profile1.mutable_min_security_requirements()->set_security_level( + ProvisionedDeviceInfo::LEVEL_3); + EXPECT_TRUE(profile_list.InsertProfile(profile1)); + // Verify the list still has one profile. + EXPECT_EQ(1, profile_list.NumProfiles()); + // Should not allow insert if existing profile has the same name. + SecurityProfile profile2; + profile2.set_name(profile1.name()); + profile2.mutable_min_security_requirements()->set_security_level( + ProvisionedDeviceInfo::LEVEL_3); + EXPECT_FALSE(profile_list.InsertProfile(profile2)); + // Verify the list still has one profile. + EXPECT_EQ(1, profile_list.NumProfiles()); + // Should allow insert since this profile has a different name. + profile2.set_name("profile2"); + EXPECT_TRUE(profile_list.InsertProfile(profile2)); + EXPECT_EQ(2, profile_list.NumProfiles()); +} + +TEST_F(SecurityProfileListTest, GetDrmInfo) { + SecurityProfile::DrmInfo drm_info; + DeviceModel* device_model = device_info_.add_model_info(); + device_model->set_manufacturer(GetClientInfo(client_id_, kModDrmMake)); + device_model->set_model_name(GetClientInfo(client_id_, kModDrmModel)); + device_model->set_status(DeviceModel::MODEL_STATUS_VERIFIED); + const uint32_t model_launch_year = 2015; + device_model->set_model_year(model_launch_year); + 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_api_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()); + + EXPECT_EQ(kMakeValue, drm_info.request_model_info().manufacturer()); + EXPECT_EQ(kModelValue, drm_info.request_model_info().model_name()); + EXPECT_EQ(DeviceModel::MODEL_STATUS_VERIFIED, + drm_info.request_model_info().status()); + EXPECT_EQ(model_launch_year, drm_info.request_model_info().model_year()); + EXPECT_EQ(kDeviceNameValue, drm_info.client_info().device_name()); + EXPECT_EQ(kProductNameValue, + drm_info.client_info().product_info().product_name()); + EXPECT_EQ(kBuildInfoValue, + drm_info.client_info().product_info().build_info()); + EXPECT_EQ( + kOemCryptoSecurityPatchLevelValue, + drm_info.client_info().product_info().oem_crypto_security_patch_level()); +} + +TEST_F(SecurityProfileListTest, QualifiedProfiles) { + SecurityProfile profile1; + profile1.set_name("profile1"); + profile1.mutable_min_security_requirements()->set_security_level( + ProvisionedDeviceInfo::LEVEL_3); + profile1.mutable_min_output_requirements()->set_hdcp_version( + ClientCapabilities::HDCP_V1); + profile_list_->InsertProfile(profile1); + EXPECT_EQ(1, profile_list_->NumProfiles()); + + SecurityProfile profile2; + profile2.set_name("profile2"); + profile2.mutable_min_security_requirements()->set_security_level( + ProvisionedDeviceInfo::LEVEL_1); + profile2.mutable_min_output_requirements()->set_hdcp_version( + ClientCapabilities::HDCP_V2); + profile_list_->InsertProfile(profile2); + EXPECT_EQ(2, profile_list_->NumProfiles()); + + // Both profiles should qualify based on client_info and device_info from the + // Setup function. + std::vector qualified_profiles; + EXPECT_EQ(2, profile_list_->GetQualifiedProfiles(client_id_, device_info_, + &qualified_profiles)); + EXPECT_NE(qualified_profiles.end(), + std::find(qualified_profiles.begin(), qualified_profiles.end(), + profile1.name())); + EXPECT_NE(qualified_profiles.end(), + std::find(qualified_profiles.begin(), qualified_profiles.end(), + profile2.name())); + + // Reduce the DRM capabilities of the device so profile2 will not qualify. + client_id_.mutable_client_capabilities()->set_max_hdcp_version( + ClientCapabilities::HDCP_V1); + ASSERT_EQ(1, profile_list_->GetQualifiedProfiles(client_id_, device_info_, + &qualified_profiles)); + EXPECT_NE(qualified_profiles.end(), + std::find(qualified_profiles.begin(), qualified_profiles.end(), + profile1.name())); +} + +TEST_F(SecurityProfileListTest, DeviceExceptionImpactOnQualifiedProfiles) { + std::vector qualified_profiles; + // Add device_exceptions to a profile and check the qualified profile. + SecurityProfile profile1; + profile1.set_name("profile1"); + profile1.mutable_min_security_requirements()->set_security_level( + ProvisionedDeviceInfo::LEVEL_3); + profile1.mutable_min_output_requirements()->set_hdcp_version( + ClientCapabilities::HDCP_V1); + // Add one system_id in device blocked list. + DeviceException* device_exception = profile1.add_device_exceptions(); + device_exception->set_system_id(kSystemId); + device_exception->set_action(DeviceException::DEVICE_EXCEPTION_BLOCK); + device_exception = profile1.add_device_exceptions(); + // Add another system_id in device allowable list. + device_exception->set_system_id(kSystemIdOverridden); + device_exception->set_action(DeviceException::DEVICE_EXCEPTION_ALLOW); + profile_list_->InsertProfile(profile1); + EXPECT_EQ(profile_list_->NumProfiles(), 1); + // System_id of the current device is listed in the device exceptions with + // DEVICE_EXCEPTION_BLOCK label. So this profile can't be used for it. + EXPECT_EQ(profile_list_->GetQualifiedProfiles(client_id_, device_info_, + &qualified_profiles), + 0); + EXPECT_EQ(std::find(qualified_profiles.begin(), qualified_profiles.end(), + profile1.name()), + qualified_profiles.end()); + + // Reset the system id in the device info so that profile1 will be + // auto-qualified for the current device. + device_info_.set_system_id(kSystemIdOverridden); + ASSERT_EQ(profile_list_->GetQualifiedProfiles(client_id_, device_info_, + &qualified_profiles), + 1); + EXPECT_NE(std::find(qualified_profiles.begin(), qualified_profiles.end(), + profile1.name()), + qualified_profiles.end()); +} + +TEST_F(SecurityProfileListTest, GetQualifiedForInactiveDSPs) { + SecurityProfile profile1; + profile1.set_name("profile1"); + profile1.mutable_min_security_requirements()->set_security_level( + ProvisionedDeviceInfo::LEVEL_3); + profile1.mutable_min_output_requirements()->set_hdcp_version( + ClientCapabilities::HDCP_V1); + profile1.set_owner("owner1"); + int64_t current_time_seconds = time(nullptr); + // Set the start time one hour back. + profile1.mutable_control_time()->set_start_time_seconds(current_time_seconds - + 3600); + // Set the end time one hour later. + profile1.mutable_control_time()->set_end_time_seconds(current_time_seconds + + 3600); + + profile_list_->InsertProfile(profile1); + ASSERT_EQ(1, profile_list_->NumProfiles()); + std::vector qualified_profiles; + ASSERT_EQ(1, profile_list_->GetQualifiedProfiles(client_id_, device_info_, + &qualified_profiles)); + + SecurityProfile profile2; + profile2.set_name("profile2"); + profile2.mutable_min_security_requirements()->set_security_level( + ProvisionedDeviceInfo::LEVEL_1); + profile2.mutable_min_output_requirements()->set_hdcp_version( + ClientCapabilities::HDCP_V2); + // Set the start time one hour later. + profile2.mutable_control_time()->set_start_time_seconds(current_time_seconds + + 3600); + // Set the end time two hours later. + profile2.mutable_control_time()->set_end_time_seconds(current_time_seconds + + 7200); + + profile_list_->InsertProfile(profile2); + ASSERT_EQ(2, profile_list_->NumProfiles()); + qualified_profiles.clear(); + ASSERT_EQ(1, profile_list_->GetQualifiedProfiles(client_id_, device_info_, + &qualified_profiles)); + // Profile2 will be filtered out as it is an inactive profile. + EXPECT_NE(qualified_profiles.end(), + std::find(qualified_profiles.begin(), qualified_profiles.end(), + profile1.name())); + + SecurityProfile profile3; + profile3.set_name("profile3"); + profile3.mutable_min_security_requirements()->set_security_level( + ProvisionedDeviceInfo::LEVEL_1); + profile3.mutable_min_output_requirements()->set_hdcp_version( + ClientCapabilities::HDCP_V2); + // Set the start time two hours back. + profile3.mutable_control_time()->set_start_time_seconds(current_time_seconds - + 7200); + // Set the end time one hour back. + profile3.mutable_control_time()->set_end_time_seconds(current_time_seconds - + 3600); + + profile_list_->InsertProfile(profile3); + ASSERT_EQ(3, profile_list_->NumProfiles()); + qualified_profiles.clear(); + ASSERT_EQ(1, profile_list_->GetQualifiedProfiles(client_id_, device_info_, + &qualified_profiles)); + // Profile3 will be filtered out as it is an expired profile. + EXPECT_NE(qualified_profiles.end(), + std::find(qualified_profiles.begin(), qualified_profiles.end(), + profile1.name())); + + SecurityProfile profile4; + profile4.set_name("profile4"); + profile4.mutable_min_security_requirements()->set_security_level( + ProvisionedDeviceInfo::LEVEL_2); + // Set the start time two hours back. + profile4.mutable_control_time()->set_start_time_seconds(current_time_seconds - + 7200); + // Set the end time to 0 (never expired). + profile4.mutable_control_time()->set_end_time_seconds(0); + profile_list_->InsertProfile(profile4); + ASSERT_EQ(4, profile_list_->NumProfiles()); + qualified_profiles.clear(); + ASSERT_EQ(2, profile_list_->GetQualifiedProfiles(client_id_, device_info_, + &qualified_profiles)); + // Profile4 will be listed in the qualified profiles. + EXPECT_NE(qualified_profiles.end(), + std::find(qualified_profiles.begin(), qualified_profiles.end(), + profile1.name())); + EXPECT_NE(qualified_profiles.end(), + std::find(qualified_profiles.begin(), qualified_profiles.end(), + profile4.name())); +} + +TEST_F(SecurityProfileListTest, FindProfile) { + SecurityProfileList profile_list("widevine-test"); + SecurityProfile profile1; + profile1.set_name("profile1"); + profile1.mutable_min_security_requirements()->set_security_level( + ProvisionedDeviceInfo::LEVEL_3); + DeviceException* device_exception = profile1.add_device_exceptions(); + device_exception->set_system_id(kSystemId); + device_exception->set_action(DeviceException::DEVICE_EXCEPTION_BLOCK); + device_exception = profile1.add_device_exceptions(); + device_exception->set_system_id(kSystemIdOverridden); + device_exception->set_action(DeviceException::DEVICE_EXCEPTION_ALLOW); + + EXPECT_EQ(kDefaultContentOwnerName, profile1.owner()); + SecurityProfile profile2; + profile2.set_name("profile2"); + profile2.mutable_min_security_requirements()->set_security_level( + ProvisionedDeviceInfo::LEVEL_3); + // Override the default owner name. + profile2.set_owner("owner2"); + profile2.mutable_control_time()->set_start_time_seconds(kStartTimeInSeconds); + profile2.mutable_control_time()->set_end_time_seconds(kEndTimeInSeconds); + + // Insert profiles 1 & 2. + EXPECT_TRUE(profile_list.InsertProfile(profile1)); + EXPECT_TRUE(profile_list.InsertProfile(profile2)); + EXPECT_EQ(2, profile_list.NumProfiles()); + + // Find the profile by its name. + SecurityProfile profile; + EXPECT_TRUE(profile_list.GetProfileByName(profile1.name(), &profile)); + EXPECT_EQ(profile1.name(), profile.name()); + EXPECT_EQ(profile1.level(), profile.level()); + EXPECT_EQ(profile1.owner(), profile.owner()); + // By default, security profile doesn't contain the control_dates field. + EXPECT_FALSE(profile1.has_control_time()); + EXPECT_EQ(2, profile.device_exceptions().size()); + EXPECT_EQ(kSystemId, profile.device_exceptions(0).system_id()); + EXPECT_EQ(DeviceException::DEVICE_EXCEPTION_BLOCK, + profile.device_exceptions(0).action()); + EXPECT_EQ(kSystemIdOverridden, profile.device_exceptions(1).system_id()); + EXPECT_EQ(DeviceException::DEVICE_EXCEPTION_ALLOW, + profile.device_exceptions(1).action()); + + EXPECT_TRUE(profile_list.GetProfileByName(profile2.name(), &profile)); + EXPECT_EQ(profile2.name(), profile.name()); + EXPECT_EQ(profile2.level(), profile.level()); + EXPECT_EQ(profile2.owner(), profile.owner()); + EXPECT_TRUE(profile2.device_exceptions().empty()); + EXPECT_EQ(profile2.control_time().start_time_seconds(), kStartTimeInSeconds); + EXPECT_EQ(profile2.control_time().end_time_seconds(), kEndTimeInSeconds); + + EXPECT_FALSE( + profile_list.GetProfileByName("you-should-not-find-me", &profile)); +} + +} // namespace security_profile +} // namespace widevine diff --git a/common/sha_util.cc b/common/sha_util.cc index 5f3d5b2..0844855 100644 --- a/common/sha_util.cc +++ b/common/sha_util.cc @@ -37,7 +37,8 @@ std::string Sha512_Hash(const std::string& message) { return digest; } -std::string GenerateSha1Uuid(const std::string& name_space, const std::string& name) { +std::string GenerateSha1Uuid(const std::string& name_space, + const std::string& name) { // X.667 14 Setting the fields of a name-based UUID. // - Allocate a UUID to use as a "name space identifier" for all UUIDs // generated from names in that name space. 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..9d71936 100644 --- a/common/sha_util_test.cc +++ b/common/sha_util_test.cc @@ -7,6 +7,7 @@ //////////////////////////////////////////////////////////////////////////////// #include "common/sha_util.h" + #include "testing/gunit.h" #include "absl/strings/escaping.h" @@ -17,7 +18,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 +28,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..48a4a59 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,9 @@ 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, + HashAlgorithm hash_algorithm, std::string* signature) { if (signature == nullptr) { return Status(error::INVALID_ARGUMENT, "signature is nullptr"); @@ -47,7 +50,7 @@ Status GenerateRsaSignature(const std::string& message, const std::string& priva return Status(error::INTERNAL, "Failed to construct a RsaPrivateKey"); } std::string sig; - if (!rsa_private_key->GenerateSignature(message, &sig)) { + if (!rsa_private_key->GenerateSignature(message, hash_algorithm, &sig)) { return Status(error::INTERNAL, "Failed to generate a RSA signature"); } if (sig.empty()) { diff --git a/common/signature_util.h b/common/signature_util.h index 25238fb..8e6a086 100644 --- a/common/signature_util.h +++ b/common/signature_util.h @@ -11,6 +11,7 @@ #include +#include "common/hash_algorithm.h" #include "common/status.h" namespace widevine { @@ -19,13 +20,17 @@ 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, +// Generates a RSA signature of |message| using |private_key| and +// |hash_algorithm|. 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, + HashAlgorithm hash_algorithm, 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..87dd617 --- /dev/null +++ b/common/signer_public_key.cc @@ -0,0 +1,70 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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, HashAlgorithm hash_algorithm, + const std::string& signature) const override { + if (!signer_public_key_->VerifySignature(message, hash_algorithm, + 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..9670f6d --- /dev/null +++ b/common/signer_public_key.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. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef COMMON_SIGNER_PUBLIC_KEY_H_ +#define COMMON_SIGNER_PUBLIC_KEY_H_ + +#include + +#include "common/hash_algorithm.h" +#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_| and |hash_algorithm|. + virtual bool VerifySignature(const std::string& message, + HashAlgorithm hash_algorithm, + 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..2243690 --- /dev/null +++ b/common/signer_public_key_test.cc @@ -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. +//////////////////////////////////////////////////////////////////////////////// + +#include "common/signer_public_key.h" + +#include + +#include "testing/gunit.h" +#include "common/ec_key.h" +#include "common/ec_test_keys.h" +#include "common/hash_algorithm.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?"; +const HashAlgorithm kHashAlgorithm = HashAlgorithm::kSha256; + +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, kHashAlgorithm, &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, kHashAlgorithm, 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, kHashAlgorithm, &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, kHashAlgorithm, 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..70e448c 100644 --- a/common/signing_key_util_test.cc +++ b/common/signing_key_util_test.cc @@ -7,6 +7,7 @@ //////////////////////////////////////////////////////////////////////////////// #include "common/signing_key_util.h" + #include "testing/gunit.h" #include "absl/strings/escaping.h" #include "common/crypto_util.h" @@ -20,7 +21,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 1d6597b..28d5a86 100644 --- a/common/status.h +++ b/common/status.h @@ -9,6 +9,7 @@ #ifndef COMMON_STATUS_H_ #define COMMON_STATUS_H_ +#include #include #include "util/error_space.h" @@ -36,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, @@ -69,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 84031bb..2a9f9d4 100644 --- a/common/string_util.cc +++ b/common/string_util.cc @@ -9,14 +9,17 @@ #include "common/string_util.h" #include +#include #include #include + #include "common/status.h" 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/string_util_test.cc b/common/string_util_test.cc index 9354571..a28e619 100644 --- a/common/string_util_test.cc +++ b/common/string_util_test.cc @@ -7,6 +7,7 @@ //////////////////////////////////////////////////////////////////////////////// #include "common/string_util.h" + #include "testing/gmock.h" #include "testing/gunit.h" 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/test_utils.cc b/common/test_utils.cc index 5dfce0b..2b4973a 100644 --- a/common/test_utils.cc +++ b/common/test_utils.cc @@ -9,6 +9,7 @@ #include "common/test_utils.h" #include + #include #include "glog/logging.h" diff --git a/common/test_utils.h b/common/test_utils.h index 9252f93..bc8f701 100644 --- a/common/test_utils.h +++ b/common/test_utils.h @@ -22,7 +22,7 @@ namespace widevine { // and PKCS#1 1.5 padding. |pem_private_key| is a PEM-encoded private RSA key, // |message| is the message to be signed, and |signature| is a pointer to a // std::string where the signature will be stored. The caller returns ownership of -// all paramters. +// all parameters. Status GenerateRsaSignatureSha256Pkcs1(const std::string& pem_private_key, const std::string& message, std::string* signature); diff --git a/common/verified_media_pipeline.h b/common/verified_media_pipeline.h index a6372ce..8a95a22 100644 --- a/common/verified_media_pipeline.h +++ b/common/verified_media_pipeline.h @@ -13,6 +13,7 @@ #define COMMON_VERIFIED_MEDIA_PIPELINE_H_ #include + #include "common/status.h" #include "protos/public/license_protocol.pb.h" diff --git a/common/vmp_checker.cc b/common/vmp_checker.cc index a6d15c8..9479582 100644 --- a/common/vmp_checker.cc +++ b/common/vmp_checker.cc @@ -13,12 +13,14 @@ #include "common/vmp_checker.h" #include + #include #include #include "glog/logging.h" #include "common/certificate_type.h" #include "common/error_space.h" +#include "common/hash_algorithm_util.h" #include "common/rsa_key.h" #include "common/x509_cert.h" #include "protos/public/errors.pb.h" @@ -253,11 +255,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())); @@ -334,7 +336,9 @@ Status VmpChecker::VerifyVmpData(const std::string& vmp_data, Result* result) { std::unique_ptr key(cert->GetRsaPublicKey()); std::string message(binary_info.binary_hash()); message += binary_info.flags() & 0xff; - if (!key->VerifySignature(message, binary_info.signature())) { + if (!key->VerifySignature( + message, HashAlgorithmProtoToEnum(binary_info.hash_algorithm()), + binary_info.signature())) { LOG(INFO) << "Code signature verification failed for file \"" << binary_info.file_name() << "\"."; *result = kTampered; diff --git a/common/vmp_checker_test.cc b/common/vmp_checker_test.cc index 005dc86..d10958a 100644 --- a/common/vmp_checker_test.cc +++ b/common/vmp_checker_test.cc @@ -6,14 +6,16 @@ // widevine-licensing@google.com. //////////////////////////////////////////////////////////////////////////////// +#include "common/vmp_checker.h" + #include #include "glog/logging.h" #include "testing/gmock.h" #include "testing/gunit.h" #include "absl/strings/escaping.h" +#include "common/hash_algorithm_util.h" #include "common/rsa_key.h" -#include "common/vmp_checker.h" #include "protos/public/errors.pb.h" #include "protos/public/verified_media_pipeline.pb.h" @@ -167,7 +169,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()); @@ -183,7 +186,9 @@ class VmpCheckerTest : public ::testing::Test { std::string message(binary_hash); message += flags & 0xff; std::string signature; - ASSERT_TRUE(signing_key_->GenerateSignature(message, &signature)); + ASSERT_TRUE(signing_key_->GenerateSignature( + message, HashAlgorithmProtoToEnum(new_binary->hash_algorithm()), + &signature)); new_binary->set_signature(signature); } diff --git a/common/wvm_test_keys.cc b/common/wvm_test_keys.cc index 7c39ad9..c0842a7 100644 --- a/common/wvm_test_keys.cc +++ b/common/wvm_test_keys.cc @@ -6,11 +6,12 @@ // widevine-licensing@google.com. //////////////////////////////////////////////////////////////////////////////// +#include "common/wvm_test_keys.h" + #include #include "absl/strings/escaping.h" #include "absl/strings/string_view.h" -#include "common/wvm_test_keys.h" #include "common/wvm_token_handler.h" namespace widevine { 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/wvm_token_handler_test.cc b/common/wvm_token_handler_test.cc index b3f0bf9..872096a 100644 --- a/common/wvm_token_handler_test.cc +++ b/common/wvm_token_handler_test.cc @@ -7,22 +7,23 @@ //////////////////////////////////////////////////////////////////////////////// #include "common/wvm_token_handler.h" + #include "testing/gmock.h" #include "testing/gunit.h" #include "absl/strings/escaping.h" #include "absl/strings/string_view.h" #include "common/wvm_test_keys.h" -using widevine::wvm_test_keys::kTestSystemId; -using widevine::wvm_test_keys::kTestSystemId3Des; -using widevine::wvm_test_keys::kTestPreprovKeyHex; +using widevine::wvm_test_keys::GetPreprovKeyVector; using widevine::wvm_test_keys::kTestDeviceKey1Hex; using widevine::wvm_test_keys::kTestDeviceKey2Hex; using widevine::wvm_test_keys::kTestDeviceKey3DesHex; +using widevine::wvm_test_keys::kTestPreprovKeyHex; +using widevine::wvm_test_keys::kTestSystemId; +using widevine::wvm_test_keys::kTestSystemId3Des; using widevine::wvm_test_keys::kTestToken1Hex; using widevine::wvm_test_keys::kTestToken2Hex; using widevine::wvm_test_keys::kTestToken3DesHex; -using widevine::wvm_test_keys::GetPreprovKeyVector; 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..20f062e 100644 --- a/common/x509_cert.h +++ b/common/x509_cert.h @@ -13,13 +13,13 @@ #define COMMON_X509_CERT_H_ #include + #include #include #include #include -#include "base/macros.h" -#include "base/thread_annotations.h" +#include "absl/base/thread_annotations.h" #include "absl/synchronization/mutex.h" #include "openssl/pem.h" #include "openssl/x509.h" @@ -39,6 +39,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 +102,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 +140,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 +147,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 +173,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/common/x509_cert_test.cc b/common/x509_cert_test.cc index efc9ee3..0ebe8a1 100644 --- a/common/x509_cert_test.cc +++ b/common/x509_cert_test.cc @@ -6,13 +6,14 @@ // widevine-licensing@google.com. //////////////////////////////////////////////////////////////////////////////// +#include "common/x509_cert.h" + #include #include "testing/gunit.h" #include "absl/strings/escaping.h" #include "common/rsa_key.h" #include "common/test_utils.h" -#include "common/x509_cert.h" namespace widevine { const char kTestRootCaDerCert[] = @@ -352,7 +353,6 @@ const char kTestDevCodeSigningCert[] = const char kDevCertFlagOid[] = "1.3.6.1.4.1.11129.4.1.2"; const bool kTestDevCodeSigningCertFlagValue = true; - TEST(X509CertTest, LoadCert) { X509Cert test_cert; EXPECT_EQ(OkStatus(), diff --git a/example/BUILD b/example/BUILD index 0f14de4..9e17277 100644 --- a/example/BUILD +++ b/example/BUILD @@ -41,9 +41,10 @@ cc_binary( "//base", "//common:aes_cbc_util", "//common:file_util", + "//common:hash_algorithm_util", "//common:random_util", "//common:rsa_key", - "//protos/public:certificate_provisioning_proto", - "//protos/public:client_identification_proto", + "//protos/public:certificate_provisioning_cc_proto", + "//protos/public:client_identification_cc_proto", ], ) diff --git a/example/provisioning_example.cc b/example/provisioning_example.cc index 8559989..cd14e58 100644 --- a/example/provisioning_example.cc +++ b/example/provisioning_example.cc @@ -21,7 +21,6 @@ using widevine::kCertificateTypeTesting; using widevine::OK; using widevine::ProvisioningEngine; using widevine::ProvisioningSession; -using widevine::SignedProvisioningMessage; std::string GetContents(const std::string& file_name) { if (file_name.empty()) { @@ -96,10 +95,9 @@ int main(int argc, char** argv) { // message is processed successfully; if ProcessMessage fails, they can be // reused on another session. std::unique_ptr session; - if (engine.NewProvisioningSession(SignedProvisioningMessage::PROVISIONING_30, - GetContents("example_data/device.public"), - GetContents("example_data/device.private"), - &session) != OK) { + if (engine.NewProvisioningSession( + GetContents("example_data/device.public"), + GetContents("example_data/device.private"), &session) != OK) { std::cout << "Failed to create session." << std::endl; return 1; } diff --git a/example/provisioning_message_generator.cc b/example/provisioning_message_generator.cc index bf84f51..75a0701 100644 --- a/example/provisioning_message_generator.cc +++ b/example/provisioning_message_generator.cc @@ -13,6 +13,7 @@ #include "glog/logging.h" #include "common/aes_cbc_util.h" #include "common/file_util.h" +#include "common/hash_algorithm_util.h" #include "common/random_util.h" #include "common/rsa_key.h" #include "protos/public/certificate_provisioning.pb.h" @@ -36,7 +37,8 @@ namespace widevine { bool GenerateProvisioningMessage(const std::string& service_public_key, const std::string& certificate, - const std::string& private_key, std::string* message) { + const std::string& private_key, + std::string* message) { ClientIdentification client_id; client_id.set_token(certificate); client_id.set_type(ClientIdentification::OEM_DEVICE_CERTIFICATE); @@ -64,8 +66,10 @@ bool GenerateProvisioningMessage(const std::string& service_public_key, SignedProvisioningMessage signed_message; signed_message.set_message(request.SerializeAsString()); std::unique_ptr signer(RsaPrivateKey::Create(private_key)); - CHECK(signer->GenerateSignature(signed_message.message(), - signed_message.mutable_signature())); + CHECK(signer->GenerateSignature( + signed_message.message(), + HashAlgorithmProtoToEnum(signed_message.hash_algorithm()), + signed_message.mutable_signature())); *message = signed_message.SerializeAsString(); return true; } diff --git a/oem_certificate_generator/BUILD b/oem_certificate_generator/BUILD index b2b322b..ce32e5b 100644 --- a/oem_certificate_generator/BUILD +++ b/oem_certificate_generator/BUILD @@ -7,27 +7,42 @@ ################################################################################ # Build file for OEM certificate generation tool. - package(default_visibility = ["//visibility:public"]) + py_binary( name = "oem_certificate", srcs = ["oem_certificate.py"], + python_version = "PY3", + srcs_version = "PY3", + deps = [ + ":oem_certificate_main_lib", + ], +) + +py_library( + name = "oem_certificate_main_lib", + srcs = ["oem_certificate.py"], + srcs_version = "PY3", + deps = [ + ], ) py_library( name = "oem_certificate_test_helper", srcs = ["oem_certificate_test_helper.py"], + srcs_version = "PY3", deps = [ - ":oem_certificate", ], ) py_test( name = "oem_certificate_test", srcs = ["oem_certificate_test.py"], + python_version = "PY3", + srcs_version = "PY3", deps = [ - ":oem_certificate", + ":oem_certificate_main_lib", ":oem_certificate_test_helper", ], ) diff --git a/oem_certificate_generator/oem_certificate.py b/oem_certificate_generator/oem_certificate.py index 1849485..9d00b72 100644 --- a/oem_certificate_generator/oem_certificate.py +++ b/oem_certificate_generator/oem_certificate.py @@ -1,3 +1,4 @@ +# Lint as: python2, python3 ################################################################################ # Copyright 2016 Google LLC. # @@ -5,12 +6,11 @@ # License Agreement. For a copy of this agreement, please contact # widevine-licensing@google.com. ################################################################################ - """OEM certificate generation tool. Supports: - Generating CSR (certificate signing request) - - Generating OEM intermediate certificate + - Generating OEM intermediate certificate (for testing only) - Generating OEM leaf certificate chain - Erasing file securely - Getting CSR/certificate/certificate chain information @@ -31,11 +31,11 @@ example, if file "location.cfg" is, -OU=Some Unit A command of - "python oem_certificate.py generate_csr @location.cfg " + "python oem_certificate.py generate_csr @location.cfg -CN TestDevice1 " "--output_csr_file=csr.pem --output_private_key_file=key.der", is equivalent to - "python oem_certificate.py generate_csr -C=US -ST=CA -L=Kirkland " - "-O='Some Company' -OU='Some Unit' --output_csr_file=csr.pem " + "python oem_certificate.py generate_csr -CN TestDevice1 -C=US -ST=CA " + "-L=Kirkland -O='Some Company' -OU='Some Unit' --output_csr_file=csr.pem " "--output_private_key_file=key.der". Note that (1) the arguments in the config file must be one per line; (2) the @@ -45,6 +45,10 @@ The script uses a default configuration file 'oem_certificate.cfg', which will be loaded automatically if exists. """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + import argparse import datetime import os @@ -63,6 +67,7 @@ from pyasn1.codec.der import decoder from pyasn1.codec.der import encoder from pyasn1.type import univ import six +from six.moves import range class WidevineSystemId(x509.UnrecognizedExtension): @@ -116,7 +121,7 @@ class X509CertificateChain(object): x509_stack = pkcs7.d.sign.cert certificates = [] - for i in xrange(backend._lib.sk_X509_num(x509_stack)): + for i in range(backend._lib.sk_X509_num(x509_stack)): x509_value = backend._ffi.gc( backend._lib.X509_dup(backend._lib.sk_X509_value(x509_stack, i)), backend._lib.X509_free) @@ -188,8 +193,9 @@ def generate_csr(args): six.text_type(args.organizational_unit_name)), ] if args.common_name: - x509_name.append(x509.NameAttribute(oid.NameOID.COMMON_NAME, - six.text_type(args.common_name))) + x509_name.append( + x509.NameAttribute(oid.NameOID.COMMON_NAME, + six.text_type(args.common_name))) csr = x509.CertificateSigningRequestBuilder().subject_name( x509.Name(x509_name)).sign(key, hashes.SHA256(), backends.default_backend()) @@ -243,8 +249,8 @@ def generate_intermediate_certificate(args): csr = x509.load_pem_x509_csr(args.csr_file.read(), backends.default_backend()) certificate = build_certificate(csr.subject, root_cert.subject, - args.system_id, args.not_valid_before, - args.valid_duration, + args.system_id, + args.not_valid_before, args.valid_duration, csr.public_key(), root_private_key, True) args.output_certificate_file.write( certificate.public_bytes(serialization.Encoding.DER)) @@ -305,9 +311,9 @@ def generate_leaf_certificate(args): def secure_erase(args): """Subparser handler for secure erasing of a file.""" length = args.file.tell() - for _ in xrange(args.passes): + for _ in range(args.passes): args.file.seek(0) - for _ in xrange(length): + for _ in range(length): args.file.write(os.urandom(1)) args.file.close() os.remove(args.file.name) @@ -388,7 +394,6 @@ def create_parser(): """Command line parsing.""" parser = argparse.ArgumentParser(fromfile_prefix_chars='@') subparsers = parser.add_subparsers() - # Subparser for certificate signing request generation. parser_csr = subparsers.add_parser( 'generate_csr', help='generate certificate signing request') @@ -412,7 +417,6 @@ def create_parser(): help=('specify an optional passphrase to encrypt the private key. The ' 'private key is not encrypted if omitted.')) parser_csr.set_defaults(func=generate_csr) - # Subparser for intermediate certificate generation. parser_intermediate_cert = subparsers.add_parser( 'generate_intermediate_certificate', @@ -495,7 +499,6 @@ def create_parser(): parser_info.add_argument( '-F', '--file', type=argparse.FileType('rb'), required=True) parser_info.set_defaults(func=get_info) - return parser @@ -503,7 +506,7 @@ def main(): args = sys.argv[1:] config_file_name = 'oem_certificate.cfg' if os.path.isfile(config_file_name): - print 'Load from args default configuration file: ', config_file_name + print('Load from args default configuration file: ', config_file_name) args.append('@' + config_file_name) parser_args = create_parser().parse_args(args) parser_args.func(parser_args) diff --git a/oem_certificate_generator/oem_certificate_test.py b/oem_certificate_generator/oem_certificate_test.py index 85de74b..f4ea095 100644 --- a/oem_certificate_generator/oem_certificate_test.py +++ b/oem_certificate_generator/oem_certificate_test.py @@ -1,3 +1,4 @@ +# Lint as: python2, python3 ################################################################################ # Copyright 2016 Google LLC. # @@ -10,7 +11,7 @@ import base64 import datetime import os import shutil -import StringIO +import sys import tempfile import textwrap import unittest @@ -20,6 +21,7 @@ from cryptography.hazmat import backends from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import padding from cryptography.x509 import oid +import six import oem_certificate import oem_certificate_test_helper as oem_cert_test_helper @@ -49,8 +51,9 @@ class OemCertificateTest(unittest.TestCase): subject.get_attributes_for_oid(oid.NameOID.COUNTRY_NAME)[0].value, args.country_name) self.assertEqual( - subject.get_attributes_for_oid(oid.NameOID.STATE_OR_PROVINCE_NAME)[0] - .value, args.state_or_province_name) + subject.get_attributes_for_oid( + oid.NameOID.STATE_OR_PROVINCE_NAME)[0].value, + args.state_or_province_name) self.assertEqual( subject.get_attributes_for_oid(oid.NameOID.LOCALITY_NAME)[0].value, args.locality_name) @@ -58,8 +61,9 @@ class OemCertificateTest(unittest.TestCase): subject.get_attributes_for_oid(oid.NameOID.ORGANIZATION_NAME)[0].value, args.organization_name) self.assertEqual( - subject.get_attributes_for_oid(oid.NameOID.ORGANIZATIONAL_UNIT_NAME)[0] - .value, args.organizational_unit_name) + subject.get_attributes_for_oid( + oid.NameOID.ORGANIZATIONAL_UNIT_NAME)[0].value, + args.organizational_unit_name) self.assertEqual( len(subject.get_attributes_for_oid(oid.NameOID.COMMON_NAME)), 0) @@ -84,8 +88,9 @@ class OemCertificateTest(unittest.TestCase): subject.get_attributes_for_oid(oid.NameOID.COUNTRY_NAME)[0].value, args.country_name) self.assertEqual( - subject.get_attributes_for_oid(oid.NameOID.STATE_OR_PROVINCE_NAME)[0] - .value, args.state_or_province_name) + subject.get_attributes_for_oid( + oid.NameOID.STATE_OR_PROVINCE_NAME)[0].value, + args.state_or_province_name) self.assertEqual( subject.get_attributes_for_oid(oid.NameOID.LOCALITY_NAME)[0].value, args.locality_name) @@ -93,11 +98,12 @@ class OemCertificateTest(unittest.TestCase): subject.get_attributes_for_oid(oid.NameOID.ORGANIZATION_NAME)[0].value, args.organization_name) self.assertEqual( - subject.get_attributes_for_oid(oid.NameOID.ORGANIZATIONAL_UNIT_NAME)[0] - .value, args.organizational_unit_name) + subject.get_attributes_for_oid( + oid.NameOID.ORGANIZATIONAL_UNIT_NAME)[0].value, + args.organizational_unit_name) self.assertEqual( - subject.get_attributes_for_oid(oid.NameOID.COMMON_NAME)[0] - .value, args.common_name) + subject.get_attributes_for_oid(oid.NameOID.COMMON_NAME)[0].value, + args.common_name) private_key = serialization.load_der_private_key( args.output_private_key_file.getvalue(), @@ -111,11 +117,11 @@ class OemCertificateTest(unittest.TestCase): def test_generate_csr_with_keysize4096_and_passphrase(self): args = oem_cert_test_helper.setup_csr_args( - key_size=4096, passphrase='passphrase_4096') + key_size=4096, passphrase=b'passphrase_4096') oem_certificate.generate_csr(args) private_key = serialization.load_der_private_key( args.output_private_key_file.getvalue(), - 'passphrase_4096', + b'passphrase_4096', backend=backends.default_backend()) csr = x509.load_pem_x509_csr(args.output_csr_file.getvalue(), backends.default_backend()) @@ -156,15 +162,14 @@ class OemCertificateTest(unittest.TestCase): cert.signature_hash_algorithm) def test_generate_intermediate_with_cert_mismatch_root_cert_and_key(self): - root_key1, _ = ( - oem_cert_test_helper.create_root_certificate_and_key()) + root_key1, _ = (oem_cert_test_helper.create_root_certificate_and_key()) _, root_certificate2 = oem_cert_test_helper.create_root_certificate_and_key( ) args = oem_cert_test_helper.setup_intermediate_cert_args( - 'some csr data', root_key1, root_certificate2) + b'some csr data', root_key1, root_certificate2) with self.assertRaises(ValueError) as context: oem_certificate.generate_intermediate_certificate(args) - self.assertTrue('certificate does not match' in str(context.exception)) + self.assertIn('certificate does not match', str(context.exception)) def test_generate_leaf_certificate_from_pem_intermediate_cert(self): intermediate_key_bytes, intermediate_certificate_bytes = ( @@ -195,8 +200,7 @@ class OemCertificateTest(unittest.TestCase): system_id_raw_bytes = leaf_cert.extensions.get_extension_for_oid( oem_certificate.WidevineSystemId.oid).value.value self.assertEqual( - oem_certificate.WidevineSystemId(system_id_raw_bytes).int_value(), - 2001) + oem_certificate.WidevineSystemId(system_id_raw_bytes).int_value(), 2001) leaf_key = serialization.load_der_private_key( args.output_private_key_file.getvalue(), @@ -238,75 +242,81 @@ class OemCertificateTest(unittest.TestCase): intermediate_key_bytes, intermediate_certificate_bytes, key_size=4096, - passphrase='leaf passphrase') + passphrase=b'leaf passphrase') oem_certificate.generate_leaf_certificate(args) leaf_key = serialization.load_der_private_key( args.output_private_key_file.getvalue(), - 'leaf passphrase', + b'leaf passphrase', backend=backends.default_backend()) self.assertEqual(4096, leaf_key.key_size) def test_get_csr_info(self): args = oem_cert_test_helper.setup_csr_args() oem_certificate.generate_csr(args) - args.file = StringIO.StringIO(args.output_csr_file.getvalue()) - output = StringIO.StringIO() + args.file = six.BytesIO(args.output_csr_file.getvalue()) + output = six.StringIO() oem_certificate.get_info(args, output) expected_info = """\ CSR Subject Name: - , value=u'US')> - , value=u'WA')> - , value=u'Kirkland')> - , value=u'CompanyXYZ')> - , value=u'ContentProtection')> + , value='US')> + , value='WA')> + , value='Kirkland')> + , value='CompanyXYZ')> + , value='ContentProtection')> Key Size: 4096""" - self.assertEqual(output.getvalue(), textwrap.dedent(expected_info)) + # Remove unicode marker for Python2 backwards compatibility. + output_value = output.getvalue().replace("u'", "'") + self.assertEqual(output_value, textwrap.dedent(expected_info)) def test_get_pem_certificate_info(self): _, intermediate_certificate_bytes = ( oem_cert_test_helper.create_intermediate_certificate_and_key_bytes( pem_format=True)) args = ArgParseObject() - args.file = StringIO.StringIO(intermediate_certificate_bytes) - output = StringIO.StringIO() + args.file = six.BytesIO(intermediate_certificate_bytes) + output = six.StringIO() oem_certificate.get_info(args, output) expected_info = """\ Certificate Subject Name: - , value=u'US')> - , value=u'WA')> - , value=u'Kirkland')> - , value=u'CompanyXYZ')> - , value=u'ContentProtection')> + , value='US')> + , value='WA')> + , value='Kirkland')> + , value='CompanyXYZ')> + , value='ContentProtection')> Issuer Name: - , value=u'root_cert')> + , value='root_cert')> Key Size: 4096 Widevine System Id: 2001 Not valid before: 2001-08-09 00:00:00 Not valid after: 2001-11-17 00:00:00""" - self.assertEqual(output.getvalue(), textwrap.dedent(expected_info)) + # Remove unicode marker for Python2 backwards compatibility. + output_value = output.getvalue().replace("u'", "'") + self.assertEqual(output_value, textwrap.dedent(expected_info)) def test_get_der_certificate_info(self): _, intermediate_certificate_bytes = ( oem_cert_test_helper.create_intermediate_certificate_and_key_bytes( pem_format=False)) args = ArgParseObject() - args.file = StringIO.StringIO(intermediate_certificate_bytes) - output = StringIO.StringIO() + args.file = six.BytesIO(intermediate_certificate_bytes) + output = six.StringIO() oem_certificate.get_info(args, output) expected_info = """\ Certificate Subject Name: - , value=u'US')> - , value=u'WA')> - , value=u'Kirkland')> - , value=u'CompanyXYZ')> - , value=u'ContentProtection')> + , value='US')> + , value='WA')> + , value='Kirkland')> + , value='CompanyXYZ')> + , value='ContentProtection')> Issuer Name: - , value=u'root_cert')> + , value='root_cert')> Key Size: 4096 Widevine System Id: 2001 Not valid before: 2001-08-09 00:00:00 Not valid after: 2001-11-17 00:00:00""" - self.assertEqual(output.getvalue(), textwrap.dedent(expected_info)) + # Remove unicode marker for Python2 backwards compatibility. + output_value = output.getvalue().replace("u'", "'") + self.assertEqual(output_value, textwrap.dedent(expected_info)) def test_get_certificate_chain_info(self): intermediate_key_bytes, intermediate_certificate_bytes = ( @@ -314,41 +324,43 @@ class OemCertificateTest(unittest.TestCase): args = oem_cert_test_helper.setup_leaf_cert_args( intermediate_key_bytes, intermediate_certificate_bytes) oem_certificate.generate_leaf_certificate(args) - args.file = StringIO.StringIO(args.output_certificate_file.getvalue()) - output = StringIO.StringIO() + args.file = six.BytesIO(args.output_certificate_file.getvalue()) + output = six.StringIO() oem_certificate.get_info(args, output) expected_info = """\ Certificate Subject Name: - , value=u'2001-leaf')> - , value=u'US')> - , value=u'WA')> - , value=u'Kirkland')> - , value=u'CompanyXYZ')> - , value=u'ContentProtection')> + , value='2001-leaf')> + , value='US')> + , value='WA')> + , value='Kirkland')> + , value='CompanyXYZ')> + , value='ContentProtection')> Issuer Name: - , value=u'US')> - , value=u'WA')> - , value=u'Kirkland')> - , value=u'CompanyXYZ')> - , value=u'ContentProtection')> + , value='US')> + , value='WA')> + , value='Kirkland')> + , value='CompanyXYZ')> + , value='ContentProtection')> Key Size: 1024 Widevine System Id: 2001 Not valid before: 2001-08-09 00:00:00 Not valid after: 2023-07-05 00:00:00 Certificate Subject Name: - , value=u'US')> - , value=u'WA')> - , value=u'Kirkland')> - , value=u'CompanyXYZ')> - , value=u'ContentProtection')> + , value='US')> + , value='WA')> + , value='Kirkland')> + , value='CompanyXYZ')> + , value='ContentProtection')> Issuer Name: - , value=u'root_cert')> + , value='root_cert')> Key Size: 4096 Widevine System Id: 2001 Not valid before: 2001-08-09 00:00:00 Not valid after: 2001-11-17 00:00:00""" - self.assertEqual(output.getvalue(), textwrap.dedent(expected_info)) + # Remove unicode marker for Python2 backwards compatibility. + output_value = output.getvalue().replace("u'", "'") + self.assertEqual(output_value, textwrap.dedent(expected_info)) def test_get_certificate_chain_info_fixed_input(self): # This was generated from args.output_certificate_file in the test above. @@ -400,41 +412,43 @@ class OemCertificateTest(unittest.TestCase): 'nm0mTbNTgcC673L5YA8qpQkAzk9vLg4UaslMbPfeKM8rqduJFcjTyVY3C4jBC0qxf6z6' 'vpWbEO7UpHHdfvWe9DEBODFbyXMxAA==') args = oem_cert_test_helper.ArgParseObject() - args.file = StringIO.StringIO(base64.b64decode(data_b64)) - output = StringIO.StringIO() + args.file = six.BytesIO(base64.b64decode(data_b64)) + output = six.StringIO() oem_certificate.get_info(args, output) expected_info = """\ Certificate Subject Name: - , value=u'2001-leaf')> - , value=u'US')> - , value=u'WA')> - , value=u'Kirkland')> - , value=u'CompanyXYZ')> - , value=u'ContentProtection')> + , value='2001-leaf')> + , value='US')> + , value='WA')> + , value='Kirkland')> + , value='CompanyXYZ')> + , value='ContentProtection')> Issuer Name: - , value=u'US')> - , value=u'WA')> - , value=u'Kirkland')> - , value=u'CompanyXYZ')> - , value=u'ContentProtection')> + , value='US')> + , value='WA')> + , value='Kirkland')> + , value='CompanyXYZ')> + , value='ContentProtection')> Key Size: 1024 Widevine System Id: 2001 Not valid before: 2001-08-09 00:00:00 Not valid after: 2023-07-05 00:00:00 Certificate Subject Name: - , value=u'US')> - , value=u'WA')> - , value=u'Kirkland')> - , value=u'CompanyXYZ')> - , value=u'ContentProtection')> + , value='US')> + , value='WA')> + , value='Kirkland')> + , value='CompanyXYZ')> + , value='ContentProtection')> Issuer Name: - , value=u'root_cert')> + , value='root_cert')> Key Size: 4096 Widevine System Id: 2001 Not valid before: 2001-08-09 00:00:00 Not valid after: 2001-11-17 00:00:00""" - self.assertEqual(output.getvalue(), textwrap.dedent(expected_info)) + # Remove unicode marker for Python2 backwards compatibility. + output_value = output.getvalue().replace("u'", "'") + self.assertEqual(output_value, textwrap.dedent(expected_info)) def test_secure_erase(self): args = ArgParseObject() @@ -448,10 +462,12 @@ class OemCertificateTest(unittest.TestCase): class OemCertificateArgParseTest(unittest.TestCase): def setUp(self): + super(OemCertificateArgParseTest, self).setUp() self.parser = oem_certificate.create_parser() self.test_dir = tempfile.mkdtemp() def tearDown(self): + super(OemCertificateArgParseTest, self).tearDown() shutil.rmtree(self.test_dir) def test_generate_csr(self): @@ -506,7 +522,7 @@ class OemCertificateArgParseTest(unittest.TestCase): def _fill_file_with_dummy_contents(self, file_name): with open(file_name, 'wb') as f: - f.write('dummy') + f.write(b'dummy') def test_generate_csr_invalid_key_size(self): cmds = ('generate_csr --key_size unknown -C USA -ST WA ' @@ -601,7 +617,7 @@ class OemCertificateArgParseTest(unittest.TestCase): self.assertEqual(args.func, oem_certificate.generate_leaf_certificate) def test_generate_leaf_cert_invalid_date(self): - cmds = ('generate_leaf_certificate --not_valid_before invaid-date ' + cmds = ('generate_leaf_certificate --not_valid_before invalid-date ' '--valid_duration 10').split() intermediate_certificate_file = os.path.join(self.test_dir, @@ -651,9 +667,12 @@ class OemCertificateArgParseTest(unittest.TestCase): self.assertEqual(context.exception.code, 2) def test_no_argument(self): - with self.assertRaises(SystemExit) as context: - self.parser.parse_args([]) - self.assertEqual(context.exception.code, 2) + # Only valid for python2. + # Python3 does not throw error when missing required + if sys.version_info[0] == 2: + with self.assertRaises(SystemExit) as context: + self.parser.parse_args([]) + self.assertEqual(context.exception.code, 2) if __name__ == '__main__': diff --git a/oem_certificate_generator/oem_certificate_test_helper.py b/oem_certificate_generator/oem_certificate_test_helper.py index 0a7a379..043eda2 100644 --- a/oem_certificate_generator/oem_certificate_test_helper.py +++ b/oem_certificate_generator/oem_certificate_test_helper.py @@ -1,3 +1,4 @@ +# Lint as: python2, python3 ################################################################################ # Copyright 2017 Google LLC. # @@ -5,17 +6,16 @@ # License Agreement. For a copy of this agreement, please contact # widevine-licensing@google.com. ################################################################################ - """Common test utility functions for OEM certificate generation.""" import datetime -import StringIO from cryptography import x509 from cryptography.hazmat import backends from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.x509 import oid +import six import oem_certificate @@ -25,7 +25,6 @@ _LOCALITY_NAME = 'Kirkland' _ORGANIZATION_NAME = 'CompanyXYZ' _ORGANIZATIONAL_UNIT_NAME = 'ContentProtection' - _NOT_VALID_BEFORE = datetime.datetime(2001, 8, 9) _VALID_DURATION = 100 _LEAF_CERT_VALID_DURATION = 8000 @@ -40,14 +39,14 @@ class ArgParseObject(object): def create_root_certificate_and_key(): """Creates a root certificate and key.""" key = rsa.generate_private_key( - public_exponent=65537, - key_size=3072, - backend=backends.default_backend()) + public_exponent=65537, key_size=3072, backend=backends.default_backend()) subject_name = x509.Name( [x509.NameAttribute(oid.NameOID.COMMON_NAME, u'root_cert')]) - certificate = oem_certificate.build_certificate( - subject_name, subject_name, None, - datetime.datetime(2001, 8, 9), 1000, key.public_key(), key, True) + certificate = oem_certificate.build_certificate(subject_name, subject_name, + None, + datetime.datetime(2001, 8, + 9), 1000, + key.public_key(), key, True) return (key, certificate) @@ -73,18 +72,22 @@ def setup_csr_args(country_name=_COUNTRY_NAME, if output_csr_file: args.output_csr_file = output_csr_file else: - args.output_csr_file = StringIO.StringIO() + args.output_csr_file = six.BytesIO() if output_private_key_file: args.output_private_key_file = output_private_key_file else: - args.output_private_key_file = StringIO.StringIO() + args.output_private_key_file = six.BytesIO() args.passphrase = passphrase return args def setup_intermediate_cert_args( - csr_bytes, root_key, root_certificate, not_valid_before=_NOT_VALID_BEFORE, - valid_duration=_VALID_DURATION, system_id=_SYSTEM_ID, + csr_bytes, + root_key, + root_certificate, + not_valid_before=_NOT_VALID_BEFORE, + valid_duration=_VALID_DURATION, + system_id=_SYSTEM_ID, root_private_key_passphrase=_ROOT_PRIVATE_KEY_PASSPHRASE, output_certificate_file=None): """Sets up args to OEM Cert generator for generating intermediate cert.""" @@ -92,12 +95,12 @@ def setup_intermediate_cert_args( args.not_valid_before = not_valid_before args.valid_duration = valid_duration args.system_id = system_id - args.csr_file = StringIO.StringIO(csr_bytes) - args.root_private_key_passphrase = root_private_key_passphrase + args.csr_file = six.BytesIO(csr_bytes) + args.root_private_key_passphrase = root_private_key_passphrase.encode('utf-8') if output_certificate_file: args.output_certificate_file = output_certificate_file else: - args.output_certificate_file = StringIO.StringIO() + args.output_certificate_file = six.BytesIO() serialized_private_key = root_key.private_bytes( serialization.Encoding.DER, @@ -106,8 +109,8 @@ def setup_intermediate_cert_args( args.root_private_key_passphrase)) serialized_certificate = root_certificate.public_bytes( serialization.Encoding.DER) - args.root_certificate_file = StringIO.StringIO(serialized_certificate) - args.root_private_key_file = StringIO.StringIO(serialized_private_key) + args.root_certificate_file = six.BytesIO(serialized_certificate) + args.root_private_key_file = six.BytesIO(serialized_private_key) return args @@ -128,16 +131,15 @@ def setup_leaf_cert_args(intermediate_key_bytes, if output_certificate_file: args.output_certificate_file = output_certificate_file else: - args.output_certificate_file = StringIO.StringIO() + args.output_certificate_file = six.BytesIO() if output_private_key_file: args.output_private_key_file = output_private_key_file else: - args.output_private_key_file = StringIO.StringIO() + args.output_private_key_file = six.BytesIO() args.passphrase = passphrase - args.intermediate_private_key_file = StringIO.StringIO( - intermediate_key_bytes) - args.intermediate_certificate_file = StringIO.StringIO( + args.intermediate_private_key_file = six.BytesIO(intermediate_key_bytes) + args.intermediate_certificate_file = six.BytesIO( intermediate_certificate_bytes) return args diff --git a/protos/public/BUILD b/protos/public/BUILD index 1f5de4e..e78ef2d 100644 --- a/protos/public/BUILD +++ b/protos/public/BUILD @@ -10,7 +10,7 @@ package(default_visibility = ["//visibility:public"]) -load("@protobuf_repo//:protobuf.bzl", "cc_proto_library", "py_proto_library") +load("@com_google_protobuf//:protobuf.bzl", "py_proto_library") # Only a subset of files are included in binary release. filegroup( @@ -18,127 +18,228 @@ filegroup( srcs = [ "certificate_provisioning.proto", "client_identification.proto", + "device_common.proto", + "external_license.proto", + "hash_algorithm.proto", + "license_protocol.proto", "provisioned_device_info.proto", "remote_attestation.proto", "signed_drm_certificate.proto", - ] + ], +) + +proto_library( + name = "certificate_provisioning_proto", + srcs = ["certificate_provisioning.proto"], + deps = [ + ":client_identification_proto", + ":hash_algorithm_proto", + ":remote_attestation_proto", + ], ) cc_proto_library( - name = "certificate_provisioning_proto", - srcs = ["certificate_provisioning.proto"], - default_runtime = "@protobuf_repo//:protobuf", - protoc = "@protobuf_repo//:protoc", - deps = [ - ":client_identification_proto", - ":remote_attestation_proto", - ], + name = "certificate_provisioning_cc_proto", + deps = [":certificate_provisioning_proto"], ) py_proto_library( name = "certificate_provisioning_py_pb2", srcs = ["certificate_provisioning.proto"], - default_runtime = "@protobuf_repo//:protobuf_python", - protoc = "@protobuf_repo//:protoc", + default_runtime = "@com_google_protobuf//:protobuf_python", + protoc = "@com_google_protobuf//:protoc", deps = [ ":client_identification_py_pb2", + ":hash_algorithm_py_pb2", ":remote_attestation_py_pb2", ], ) -cc_proto_library( +proto_library( name = "client_identification_proto", srcs = ["client_identification.proto"], - default_runtime = "@protobuf_repo//:protobuf", - protoc = "@protobuf_repo//:protoc", +) + +cc_proto_library( + name = "client_identification_cc_proto", + deps = [":client_identification_proto"], ) py_proto_library( name = "client_identification_py_pb2", srcs = ["client_identification.proto"], - default_runtime = "@protobuf_repo//:protobuf_python", - protoc = "@protobuf_repo//:protoc", + default_runtime = "@com_google_protobuf//:protobuf_python", + protoc = "@com_google_protobuf//:protoc", +) + +proto_library( + name = "device_common_proto", + srcs = ["device_common.proto"], ) cc_proto_library( + name = "device_common_cc_proto", + deps = [":device_common_proto"], +) + +java_proto_library( + name = "device_common_java_proto", + deps = [":device_common_proto"], +) + +proto_library( name = "device_certificate_status_proto", srcs = ["device_certificate_status.proto"], - default_runtime = "@protobuf_repo//:protobuf", - protoc = "@protobuf_repo//:protoc", - deps = [":provisioned_device_info_proto"], + deps = [ + ":hash_algorithm_proto", + ":provisioned_device_info_proto", + ], +) + +cc_proto_library( + name = "device_certificate_status_cc_proto", + deps = [":device_certificate_status_proto"], ) py_proto_library( name = "device_certificate_status_py_pb2", srcs = ["device_certificate_status.proto"], - default_runtime = "@protobuf_repo//:protobuf_python", - protoc = "@protobuf_repo//:protoc", + default_runtime = "@com_google_protobuf//:protobuf_python", + protoc = "@com_google_protobuf//:protoc", deps = [":provisioned_device_info_py_pb2"], ) -cc_proto_library( +proto_library( name = "drm_certificate_proto", srcs = ["drm_certificate.proto"], - default_runtime = "@protobuf_repo//:protobuf", - protoc = "@protobuf_repo//:protoc", - deps = [":provisioned_device_info_proto"], +) + +cc_proto_library( + name = "drm_certificate_cc_proto", + deps = [":drm_certificate_proto"], ) py_proto_library( name = "drm_certificate_py_pb2", srcs = ["drm_certificate.proto"], - default_runtime = "@protobuf_repo//:protobuf_python", - protoc = "@protobuf_repo//:protoc", + default_runtime = "@com_google_protobuf//:protobuf_python", + protoc = "@com_google_protobuf//:protoc", deps = [":provisioned_device_info_py_pb2"], ) -cc_proto_library( +proto_library( name = "errors_proto", srcs = ["errors.proto"], - default_runtime = "@protobuf_repo//:protobuf", - protoc = "@protobuf_repo//:protoc", ) cc_proto_library( + name = "errors_cc_proto", + deps = [":errors_proto"], +) + +proto_library( + name = "external_license_proto", + srcs = ["external_license.proto"], + deps = [ + ":client_identification_proto", + ":license_protocol_proto", + ], +) + +cc_proto_library( + name = "external_license_cc_proto", + deps = [":external_license_proto"], +) + +proto_library( name = "remote_attestation_proto", srcs = ["remote_attestation.proto"], - default_runtime = "@protobuf_repo//:protobuf", - protoc = "@protobuf_repo//:protoc", deps = [":client_identification_proto"], ) +cc_proto_library( + name = "remote_attestation_cc_proto", + deps = [":remote_attestation_proto"], +) + py_proto_library( name = "remote_attestation_py_pb2", srcs = ["remote_attestation.proto"], - default_runtime = "@protobuf_repo//:protobuf_python", - protoc = "@protobuf_repo//:protoc", + default_runtime = "@com_google_protobuf//:protobuf_python", + protoc = "@com_google_protobuf//:protoc", deps = [":client_identification_py_pb2"], ) -cc_proto_library( +proto_library( name = "signed_drm_certificate_proto", srcs = ["signed_drm_certificate.proto"], - default_runtime = "@protobuf_repo//:protobuf", - protoc = "@protobuf_repo//:protoc", + deps = [":hash_algorithm_proto"], +) + +cc_proto_library( + name = "signed_drm_certificate_cc_proto", + deps = [":signed_drm_certificate_proto"], ) py_proto_library( name = "signed_drm_certificate_py_pb2", srcs = ["signed_drm_certificate.proto"], - default_runtime = "@protobuf_repo//:protobuf_python", - protoc = "@protobuf_repo//:protoc", + default_runtime = "@com_google_protobuf//:protobuf_python", + protoc = "@com_google_protobuf//:protoc", + deps = [":hash_algorithm_py_pb2"], +) + +proto_library( + name = "provisioned_device_info_proto", + srcs = ["provisioned_device_info.proto"], + deps = [":device_common_proto"], ) cc_proto_library( - name = "provisioned_device_info_proto", - srcs = ["provisioned_device_info.proto"], - default_runtime = "@protobuf_repo//:protobuf", - protoc = "@protobuf_repo//:protoc", + name = "provisioned_device_info_cc_proto", + deps = [":provisioned_device_info_proto"], ) py_proto_library( name = "provisioned_device_info_py_pb2", srcs = ["provisioned_device_info.proto"], - default_runtime = "@protobuf_repo//:protobuf_python", - protoc = "@protobuf_repo//:protoc", + default_runtime = "@com_google_protobuf//:protobuf_python", + protoc = "@com_google_protobuf//:protoc", +) + +proto_library( + name = "license_protocol_proto", + srcs = ["license_protocol.proto"], + deps = [ + ":client_identification_proto", + ":hash_algorithm_proto", + ":remote_attestation_proto", + ], +) + +cc_proto_library( + name = "license_protocol_cc_proto", + deps = [":license_protocol_proto"], +) + +proto_library( + name = "hash_algorithm_proto", + srcs = ["hash_algorithm.proto"], +) + +cc_proto_library( + name = "hash_algorithm_cc_proto", + deps = [":hash_algorithm_proto"], +) + +java_proto_library( + name = "hash_algorithm_java_proto", + deps = [":hash_algorithm_proto"], +) + +py_proto_library( + name = "hash_algorithm_py_pb2", + srcs = ["hash_algorithm.proto"], + default_runtime = "@com_google_protobuf//:protobuf_python", + protoc = "@com_google_protobuf//:protoc", ) diff --git a/protos/public/certificate_provisioning.proto b/protos/public/certificate_provisioning.proto index d6b3b60..20332e0 100644 --- a/protos/public/certificate_provisioning.proto +++ b/protos/public/certificate_provisioning.proto @@ -14,11 +14,14 @@ syntax = "proto2"; package widevine; -option java_package = "com.google.video.widevine.protos"; import "protos/public/client_identification.proto"; +import "protos/public/hash_algorithm.proto"; import "protos/public/remote_attestation.proto"; +option java_package = "com.google.video.widevine.protos"; + + // ProvisioningOptions specifies the type of certificate to specify and // in the case of X509 certificates, the certificate authority to use. message ProvisioningOptions { @@ -127,8 +130,16 @@ message ProvisioningContext { // Protocol-dependent context data, encrypted with key and IV in key_data. // Required. optional bytes context_data = 2; - // HMAC-SHA256 MAC of |context_data|, generated using MAC key in key_data. - optional bytes mac = 3; +} + +message SignedProvisioningContext { + // ProvisioningContext in bytes. + optional bytes provisioning_context = 1; + // RSASSA-PSS signature of provisioning_context. Signed with service private + // key. + optional bytes signature = 2; + // Optional field that indicates the hash algorithm used in signature scheme. + optional HashAlgorithmProto hash_algorithm = 3; } // Cryptographic tokens to be used for ProvisioningContext. @@ -137,8 +148,6 @@ message ProvisioningContextKeyData { optional bytes encryption_key = 1; // Encryption IV, 16 bytes. Required. optional bytes encryption_iv = 2; - // MAC key, usually 32 bytes used with HMAC-SHA256. Required. - optional bytes mac_key = 3; } // Serialized ProvisioningRequest or ProvisioningResponse signed with @@ -147,9 +156,7 @@ message SignedProvisioningMessage { enum ProtocolVersion { SERVICE_CERTIFICATE_REQUEST = 1; // Service certificate request. PROVISIONING_20 = 2; // Keybox factory-provisioned devices. - PROVISIONING_30 = 3; // OEM certificate factory-provisioned devices. - ARCPP_PROVISIONING = 4; // ChromeOS/Arc++ devices. - INTEL_SIGMA_101 = 101; // Intel Sigma 1.0.1 protocol. + PROVISIONING_30 = 3; // OEM certificate factory-provisioned devices. } // Serialized protobuf message for the corresponding protocol and stage of @@ -164,10 +171,18 @@ message SignedProvisioningMessage { optional ProtocolVersion protocol_version = 3 [default = PROVISIONING_20]; // Protocol-specific context / state information for multiple-exchange, // stateful provisioing protocols. Optional. - optional ProvisioningContext provisioning_context = 4; + optional SignedProvisioningContext signed_provisioning_context = 4; // Remote attestation data to authenticate that the ChromeOS client device // is operating in verified mode. Remote attestation challenge data is // |message| field above. Required for ARCPP_PROVISIONING request. // It contains signature of |message|. optional RemoteAttestation remote_attestation = 5; + // The core message is the simple serialization of fields used by OEMCrypto. + // This field was introduced in OEMCrypto API v16. The core message format is + // documented in the "Widevine Core Message Serialization", found internally + // at + // https://docs.google.com/document/d/1M5f0OA8zrIFufpZiny_M9WkvJkCUs9DpRpeDmk9QKKY/edit + optional bytes oemcrypto_core_message = 6; + // Optional field that indicates the hash algorithm used in signature scheme. + optional HashAlgorithmProto hash_algorithm = 7; } diff --git a/protos/public/client_identification.proto b/protos/public/client_identification.proto index 79c83aa..0454f8f 100644 --- a/protos/public/client_identification.proto +++ b/protos/public/client_identification.proto @@ -13,10 +13,11 @@ syntax = "proto2"; package widevine; -option java_package = "com.google.video.widevine.protos"; +option java_package = "com.google.video.widevine.protos"; option java_outer_classname = "ClientIdentificationProtos"; + // ClientIdentification message used to authenticate the client device. message ClientIdentification { enum TokenType { @@ -47,6 +48,9 @@ message ClientIdentification { enum CertificateKeyType { RSA_2048 = 0; RSA_3072 = 1; + ECC_SECP256R1 = 2; + ECC_SECP384R1 = 3; + ECC_SECP521R1 = 4; } enum AnalogOutputCapabilities { @@ -84,6 +88,11 @@ message ClientIdentification { optional uint32 resource_rating_tier = 12 [default = 0]; } + message ClientCredentials { + optional TokenType type = 1 [default = KEYBOX]; + optional bytes token = 2; + } + // Type of factory-provisioned device root of trust. Optional. optional TokenType type = 1 [default = KEYBOX]; // Factory-provisioned device root of trust. Required. @@ -99,6 +108,8 @@ message ClientIdentification { optional ClientCapabilities client_capabilities = 6; // Serialized VmpData message. Optional. optional bytes vmp_data = 7; + // Optional field that may contain additional provisioning credentials. + optional ClientCredentials device_credentials = 8; } // EncryptedClientIdentification message used to hold ClientIdentification @@ -110,8 +121,8 @@ message EncryptedClientIdentification { // Serial number for the service certificate for which ClientIdentification is // encrypted. optional bytes service_certificate_serial_number = 2; - // Serialized ClientIdentification message, encrypted with the privacy key using - // AES-128-CBC with PKCS#5 padding. + // Serialized ClientIdentification message, encrypted with the privacy key + // using AES-128-CBC with PKCS#5 padding. optional bytes encrypted_client_id = 3; // Initialization vector needed to decrypt encrypted_client_id. optional bytes encrypted_client_id_iv = 4; diff --git a/protos/public/device_certificate_status.proto b/protos/public/device_certificate_status.proto index 75c9f62..9fc67b3 100644 --- a/protos/public/device_certificate_status.proto +++ b/protos/public/device_certificate_status.proto @@ -14,11 +14,12 @@ syntax = "proto2"; package widevine; +import "protos/public/hash_algorithm.proto"; +import "protos/public/provisioned_device_info.proto"; + option java_outer_classname = "DeviceCertificateStatusProtos"; option java_package = "com.google.video.widevine.protos"; -import "protos/public/provisioned_device_info.proto"; - // Contains DRM and OEM certificate status and device information for a // specific system ID. // TODO(user): Move this to its own file. @@ -35,6 +36,18 @@ message DeviceCertificateStatus { STATUS_REVOKED = 40; // Revoked device. } + message RevokedIdentifiers { + // Contains a sorted list of DRM serial numbers that are revoked. + repeated bytes revoked_certificate_serial_numbers = 1; + // Contains a sorted list of revoked_unique_id_hashes that are revoked. + // These identifiers are hash values of the root of trust identifier. + // For a keybox, hash = SHA256(KeyboxUniqueID || SecretSauce) where + // SecretSauce is a Widevine owned secret. For Provisioning 3.0, hash = + // SHA256(X509SerialNumber|| SecretSauce) where SecretSauce is a Widevine + // owned secret. + repeated bytes revoked_unique_id_hashes = 2; + } + // Serial number of the intermediate DrmCertificate to which this // message refers. Required. optional bytes drm_serial_number = 1; @@ -49,6 +62,10 @@ message DeviceCertificateStatus { optional bytes oem_serial_number = 5; // Status of the device. Optional. optional Status status = 6 [default = STATUS_UNKNOWN]; + + // RevokedIdentifiers collect all the serial_numbers or unique_id_hashes used + // for individual drm certificate revocation. + optional RevokedIdentifiers revoked_identifiers = 7; } // List of DeviceCertificateStatus. Used to propagate certificate revocation @@ -58,10 +75,15 @@ message DeviceCertificateStatusList { optional uint32 creation_time_seconds = 1; // DeviceCertificateStatus for each system ID. repeated DeviceCertificateStatus certificate_status = 2; - // The duration for this device certificate status list in seconds. Within - // this grace period, content provider can set device certificate status list - // in the SDK. The default time is 7 days. - optional uint32 duration_time_seconds = 3; +} + +// List of DeviceCertificateStatus. Used to propagate certificate revocation +// status and device information. (Used in published devices library) +message PublishedDevicesList { + // POSIX time, in seconds, when the list was created. Required. + optional uint32 creation_time_seconds = 1; + // DeviceCertificateStatus for each system ID. + repeated DeviceCertificateStatus device_certificate_status = 2; } // Signed CertificateStatusList @@ -71,43 +93,6 @@ message SignedDeviceCertificateStatusList { // Signature of certificate_status_list. Signed with root certificate private // key using RSASSA-PSS. Required. optional bytes signature = 2; -} - -// A signed request sent to Widevine Provisioning Server (keysmith) to retrieve -// 'DeviceCertificateStatusList'. -message SignedDeviceCertificateStatusListRequest { - // Serialized DeviceCertificateStatusListRequest. Required. - optional bytes device_certificate_status_list_request = 1; - // Signature of device_certificate_status_list_request. Signed with root - // certificate private key using RSASSA-PSS. Required. - optional bytes signature = 2; -} - -// A request sent to Widevine Provisioning Server (keysmith) to retrieve -// 'DeviceCertificateStatusList'. -message DeviceCertificateStatusListRequest { - // The version of sdk. Required. - optional string sdk_version = 1; - // POSIX time, in seconds, when this request was created. Required. - optional uint64 sdk_time_seconds = 2; -} - -// Contains response from Widevine Provisioning Server with status and -// DeviceCertificateStatusList information. -message DeviceCertificateStatusListResponse { - enum Status { - UNKNOWN = 0; - OK = 1; - SIGNATURE_FAILED = 2; - NOT_AUTHORIZED = 3; - AUTHORIZATION_EXPIRED = 4; - PROVIDER_ID_MISSING = 5; - INTERNAL_ERROR = 6; - } - // Status returned by the Widevine Provisioning Server. Required. - optional Status status = 1; - // String message returned by the Widevine Provisioning Server. - optional string status_message = 2; - // Serialized SignedDeviceCertificateStatusList. Required. - optional bytes signed_device_certificate_status_list = 3; + // Optional field that indicates the hash algorithm used in signature scheme. + optional HashAlgorithmProto hash_algorithm = 3; } diff --git a/protos/public/device_common.proto b/protos/public/device_common.proto new file mode 100644 index 0000000..f3c5676 --- /dev/null +++ b/protos/public/device_common.proto @@ -0,0 +1,172 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2020 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// This file contains device-related definitions that are common to both the +// legacy device management service and the new devices service. Eventually, +// we may merge the contents of this file into other files. + +syntax = "proto3"; + +package widevine; + +option java_package = "com.google.video.widevine.protos"; + + +// Allows additional make/models to be associated with a system_id. +message DeviceModel { + // ModelStatus is used to specify how confident we are that this + // make/model/year combination is allowed by the device manufacturer. + // VERIFIED indicates that the manufacturer confirmed that it is correct. + // UNVERIFIED means that we have sufficient data to believe it is correct, + // but the manufacturer has not confirmed. + // UNKNOWN indicates that we do not have sufficient information to indicate + // whether or not the device is allowed by the manufacturer. + // REJECTED indicates that the manufacturer explicitly disallowed the use + // of the make/model/year combination. + enum ModelStatus { + MODEL_STATUS_UNSPECIFIED = 0; + MODEL_STATUS_VERIFIED = 1; + MODEL_STATUS_UNVERIFIED = 2; + MODEL_STATUS_UNKNOWN = 4; + MODEL_STATUS_REJECTED = 3; + } + // Represents the device manufacturer. Typically, this will be Philips, LG, + // Sharp, etc. + string manufacturer = 1; + // Model of the device. + string model_name = 2; + // The expected release year of the make/model combination. Optional. + uint32 model_year = 3; + // The model status of this make and model. + ModelStatus status = 4; +} + +// DeviceState defines the current state of the device. It is used in +// licensing to determine if a (classic or MDRM/CENC) license should be +// issued. The status affects if and how a device record is shown in +// keysmith's CertificateStatusList. +// +// States: +// DEVICE_STATE_UNKNOWN: This should not be used. +// It only indicates that a state has not been set. +// IN_TESTING: The first valid state of a device record. A newly created +// device should be in this state until the device is considered +// "released". In this state a device should only be supported on test +// services (e.g. UAT license service). +// PRE_RELEASE: The state of a device when it's ready to be used with +// production services. In this state a device can receive production +// classic and MDRM/CENC licenses. The device will also be listed in +// keysmith's certificate status list. The device data will be +// available for sharing with internal partners only. +// RELEASED: Indicates that the device is available on the store shelves. +// The device data will be available for sharing with external partners. +// DELETED: Indicates that the device was manually disabled and should +// not be used for any test or production services. The device should +// not appear in the device certificate status list. Customers will +// not be able to see or utilize this state when managing their devices. +// TEST_ONLY: Indicates that this device was never intended for production +// but can be used for test purposes. The device will be listed in the +// certificate status list as a test device. +// REVOKED: Indicates that the device was revoked. No test or production +// service should honor requests (classic nor MDRM/CENC) from one of +// these devices. The device serial number and its REVOKED status will +// appear in keysmith's certificate status list. +// +// Devices in the above states have the following behaviors in widevince +// services: +// +// Licensing | Certificate | Cert | Cert | Test | +// State Prod | UAT | Provisioning | Listed | status | device | redact +// -- -- -- -- -- -- -- -- +// IN_TESTING No Yes Yes Yes VALID true yes +// TEST_ONLY No Yes Yes Yes VALID true no +// PRE_RELEASE Yes Yes Yes Yes VALID false yes +// RELEASED Yes Yes Yes Yes VALID false no +// REVOKED No No No Yes REVOKED false no +// DELETED No No No No n/a n/a n/a +enum DeviceState { + DEVICE_STATE_UNKNOWN = 0; + IN_TESTING = 1; + RELEASED = 2; + DELETED = 3; + TEST_ONLY = 4; + REVOKED = 5; + PRE_RELEASE = 6; +} + +// Specifies the device type, or form factor of a device. +enum DeviceType { + DEVICE_TYPE_UNSPECIFIED = 0; + DEVICE_TYPE_PHONE = 1; + DEVICE_TYPE_TV = 2; + DEVICE_TYPE_TABLET = 3; + DEVICE_TYPE_GAMING_CONSOLE = 4; + DEVICE_TYPE_SET_TOP_BOX = 5; + DEVICE_TYPE_VIDEO_DONGLE = 6; + DEVICE_TYPE_PC = 7; + DEVICE_TYPE_AUTO = 8; + DEVICE_TYPE_WEARABLE = 9; + DEVICE_TYPE_CONNECTED_AUDIO_DEVICE = 10; + DEVICE_TYPE_SMART_DISPLAY = 11; + // Legacy identifier for records that were created for SoC integration. + DEVICE_TYPE_SOC = 12; +} + +// Specifies the platform and OS of the device. +enum Platform { + PLATFORM_UNSPECIFIED = 0; + PLATFORM_CHROMECAST = 1; + PLATFORM_FUCHSIA = 2; + PLATFORM_IOS = 3; + PLATFORM_IPAD_OS = 4; + PLATFORM_TV_OS = 5; + PLATFORM_ANDROID = 6; + PLATFORM_WINDOWS = 7; + PLATFORM_CHROME_OS = 8; + PLATFORM_MAC_OS = 9; + PLATFORM_LINUX = 10; + PLATFORM_WEB_OS = 11; + PLATFORM_TIZEN = 12; + PLATFORM_FIRE_OS = 13; + PLATFORM_ROKU = 14; + PLATFORM_PLAYSTATION = 15; + PLATFORM_XBOX = 16; + PLATFORM_KAIOS = 17; + PLATFORM_RDK = 18; + PLATFORM_OTHER = 19; +} + +// This is used for tri-state answers. Yes-TEE, Yes-REE, No. +// This has to be in device_common to avoid import conflicts between security +// profiles and device security profiles. +enum OsOptionalSupport { + OS_OPTIONAL_SUPPORT_UNSPECIFIED = 0; + YES_TEE = 1; + YES_REE = 2; + NO_SUPPORT = 3; +} + +// Version of High-bandwidth Digital Content Protection (HDCP). +// This has to be in device_common to avoid import conflicts between security +// profiles and device security profiles. +enum HdcpVersion { + HDCP_VERSION_UNSPECIFIED = 0; + HDCP_V1 = 1; + HDCP_V2 = 2; + HDCP_V2_1 = 3; + HDCP_V2_2 = 4; + HDCP_V2_3 = 5; +} + +// Widevine device security level. +enum DeviceSecurityLevel { + SECURITY_LEVEL_UNSPECIFIED = 0; + LEVEL_1 = 1; + LEVEL_2 = 2; + LEVEL_3 = 3; +} diff --git a/protos/public/drm_certificate.proto b/protos/public/drm_certificate.proto index e03a74b..550d644 100644 --- a/protos/public/drm_certificate.proto +++ b/protos/public/drm_certificate.proto @@ -17,8 +17,47 @@ package widevine; option java_outer_classname = "DrmCertificateProtos"; option java_package = "com.google.video.widevine.protos"; + +// Definition of the root of trust identifier proto. The proto message contains +// the EC-IES encrypted identifier (e.g. keybox unique id) for a device and +// an associated hash. These can be used by Widevine to identify the root of +// trust that was used to acquire a DRM certificate. +// +// In addition to the encrypted part and the hash, the proto contains the +// version of the root of trust id which implies the EC key algorithm that was +// used. +// Next id: 5 +message RootOfTrustId { + // The version specifies the EC algorithm that was used to generate the + // root of trust id. + enum RootOfTrustIdVersion { + // Should not be used. + ROOT_OF_TRUST_ID_VERSION_UNSPECIFIED = 0; + // Version 1 of the ID uses EC-IES with SECP256R1 curve. + ROOT_OF_TRUST_ID_VERSION_1 = 1; + } + optional RootOfTrustIdVersion version = 1; + // The key_id is used for key rotation. It indicates which key was used to + // generate the root of trust id. + optional uint32 key_id = 2; + + // The EC-IES encrypted message containing the unique_id. The bytes are + // a concatenation of + // 1) The ephemeral public key. Uncompressed keypoint format per X9.62. + // 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. + optional bytes encrypted_unique_id = 3; + + // The hash of encrypted unique id and other values. + // unique_id_hash = SHA256( + // encrypted_unique_id || system_id || SHA256(unique_id || secret_sauce)). + optional bytes unique_id_hash = 4; +} + // DRM certificate definition for user devices, intermediate, service, and root // certificates. +// Next id: 11 message DrmCertificate { enum Type { ROOT = 0; // ProtoBestPractices: ignore. @@ -28,7 +67,18 @@ message DrmCertificate { PROVISIONER = 4; } enum ServiceType { - UNKNOWN = 0; LICENSE_SERVER_SDK = 1; LICENSE_SERVER_PROXY_SDK = 2; + UNKNOWN_SERVICE_TYPE = 0; + LICENSE_SERVER_SDK = 1; + LICENSE_SERVER_PROXY_SDK = 2; + PROVISIONING_SDK = 3; + CAS_PROXY_SDK = 4; + } + enum Algorithm { + UNKNOWN_ALGORITHM = 0; + RSA = 1; + ECC_SECP256R1 = 2; + ECC_SECP384R1 = 3; + ECC_SECP521R1 = 4; } // Type of certificate. Required. optional Type type = 1; @@ -50,6 +100,19 @@ message DrmCertificate { // certificate. Required for service and provisioner certificates. optional string provider_id = 7; // This field is used only when type = SERVICE to specify which SDK uses - // service certificate. - optional ServiceType service_type = 8 [default = UNKNOWN]; + // service certificate. This repeated field is treated as a set. A certificate + // may be used for the specified service SDK if the appropriate ServiceType + // is specified in this field. + repeated ServiceType service_types = 8; + // Required. The algorithm field contains the curve used to create the + // |public_key| if algorithm is one of the ECC types. + // The |algorithm| is used for both to determine the if the certificate is ECC + // or RSA. The |algorithm| also specifies the parameters that were used to + // create |public_key| and are used to create an ephemeral session key. + optional Algorithm algorithm = 9 [default = RSA]; + // Optional. May be present in DEVICE certificate types. This is the root + // of trust identifier that holds an encrypted value that identifies the + // keybox or other root of trust that was used to provision a DEVICE drm + // certificate. + optional RootOfTrustId rot_id = 10; } diff --git a/protos/public/errors.proto b/protos/public/errors.proto index cb3c10b..d73ef57 100644 --- a/protos/public/errors.proto +++ b/protos/public/errors.proto @@ -12,6 +12,7 @@ syntax = "proto2"; package widevine; + option java_package = "com.google.video.widevine.protos"; @@ -239,4 +240,76 @@ enum Errors { // Invalid method parameter. INVALID_PARAMETER = 170; + // Even KeyID not specified, CasEncryptionResponse.KeyInfo.KeySlot + MISSING_EVEN_KEY_ID = 171; + + // Even Key not specified, CasEncryptionResponse.KeyInfo.KeySlot + MISSING_EVEN_KEY = 172; + + // VMP verification required for this platform, however VMP data is missing. + VMP_ERROR_PLATFORM_NOT_VERIFIED = 173; + + // VMP verification failed this platform, perhaps was tampered with. + VMP_ERROR_PLATFORM_TAMPERED = 174; + + // Device Certificate SN is revoked. + DRM_DEVICE_CERTIFICATE_SERIAL_NUMBER_REVOKED = 175; + + // Signing key expired. + SIGNING_KEY_EXPIRED = 176; + + // Signature verification failed. + SIGNATURE_VERIFICATION_FAILED = 177; + + // License request provider and the signer provider do not match. + SIGNER_PROVIDER_ID_MISMATCH = 178; + + // Invalid session key. + INVALID_SESSION_KEY = 179; + + // Device not supported. + DEVICE_NOT_SUPPORTED = 180; + + // Empty content info entry + CONTENT_INFO_ENTRY_EMPTY = 181; + + // Drm device certificate serial number is empty. + DRM_DEVICE_CERTIFICATE_EMPTY_SERIAL_NUMBER = 182; + + // Make/Model reported in license request is unknown. + UNKNOWN_MAKE_MODEL = 183; + + // Generating ECC ephemeral keys failed. + DRM_DEVICE_CERTIFICATE_ECC_KEYGEN_FAILED = 184; + + // Certificate Algorithm. + INVALID_CERT_ALGORITHM = 185; + + // RSA Public key not found. + MISSING_RSA_PUBLIC_KEY = 186; + + // An error in creating a RSA Public Key. + CREATE_RSA_PUBLIC_KEY_FAILED = 187; + + // An error was found in the ATSC profile. + ATSC_PROFILE_ERROR = 188; + + // Attempt to parse the external license request message failed. + EXTERNAL_LICENSE_REQUEST_PARSE_ERROR = 189; + + // Unexpected key type for the type of message. + INVALID_KEY_TYPE = 190; + + // Unable to find provider key. + MISSING_PROVIDER_KEY = 191; + + // Unable to find provider iv. + MISSING_PROVIDER_IV = 192; + + // Group id is empty. + EMPTY_GROUP_ID = 193; + + // Encrypted license challenge not decrypted. + INVALID_ENCRYPTED_LICENSE_CHALLENGE = 194; + } diff --git a/protos/public/external_license.proto b/protos/public/external_license.proto new file mode 100644 index 0000000..6fe74b3 --- /dev/null +++ b/protos/public/external_license.proto @@ -0,0 +1,81 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2019 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Definitions of the protocol buffer messages used in the Widevine license +// exchange protocol to support DRM systems native to a device other than +// Widevine. + +syntax = "proto2"; + +package widevine; + +import "protos/public/client_identification.proto"; +import "protos/public/license_protocol.proto"; + +option java_package = "com.google.video.widevine.externallicense"; +option java_multiple_files = true; + + +enum ExternalLicenseType { + EXTERNAL_LICENSE_TYPE_UNDEFINED = 0; + PLAYREADY_LICENSE_NEW = 1; + PLAYREADY_LICENSE_RENEWAL = 2; + PLAYREADY_LICENSE_RELEASE = 3; +} + +message EncryptedLicenseRequest { + // Provider ID for which the license request is encrypted (owner of + // service certificate). + optional string provider_id = 1; + // Serial number for the service certificate for which license_request is + // encrypted. + optional bytes service_certificate_serial_number = 2; + // Serialized license request message, encrypted with the privacy key + // using AES-128-CBC with PKCS#5 padding. + optional bytes encrypted_license_request = 3; + // Initialization vector needed to decrypt encrypted_license_request. + optional bytes encrypted_license_request_iv = 4; + // AES-128 privacy key, encrypted with the service public key using RSA-OAEP. + optional bytes encrypted_privacy_key = 5; +} + +message ExternalLicenseRequest { + optional ExternalLicenseType request_type = 1; + // The license request. + oneof clear_or_encrypted_request { + // License request from the client. + bytes request = 2; + // Encrypted request from the client. + EncryptedLicenseRequest encrypted_request = 7; + } + oneof clear_or_encrypted_client_id { + // Information about the device. + ClientIdentification client_id = 3; + // Encrypted ClientIdentification message, used for privacy purposes. + EncryptedClientIdentification encrypted_client_id = 4; + } + // Information about the content, including the PSSH data. + optional LicenseRequest.ContentIdentification content_id = 5; + // Time of the request in seconds (UTC) as set by the client. + optional int64 request_time = 6; +} + +message ExternalLicense { + optional ExternalLicenseType license_type = 1; + // The license representing the license type. + optional bytes license = 2; + // Widevine specific policy for renewals and expiry. + optional License.Policy policy = 3; + // Time of the request in seconds (UTC) as set in + // LicenseRequest.request_time. If this time is not set in the request, + // the local time at the license service is used in this field. + optional int64 license_start_time = 4; + // List of key Identifiers associated with this license. + repeated bytes key_id = 5; +} diff --git a/protos/public/hash_algorithm.proto b/protos/public/hash_algorithm.proto new file mode 100644 index 0000000..bcb0751 --- /dev/null +++ b/protos/public/hash_algorithm.proto @@ -0,0 +1,20 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2020 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// + +syntax = "proto3"; + +package widevine; + +// LINT.IfChange +enum HashAlgorithmProto { + // Unspecified hash algorithm: SHA_256 shall be used for ECC based algorithms + // and SHA_1 shall be used otherwise. + HASH_ALGORITHM_UNSPECIFIED = 0; + HASH_ALGORITHM_SHA_1 = 1; + HASH_ALGORITHM_SHA_256 = 2; +} diff --git a/protos/public/license_protocol.proto b/protos/public/license_protocol.proto new file mode 100644 index 0000000..388adaf --- /dev/null +++ b/protos/public/license_protocol.proto @@ -0,0 +1,522 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright 2016 Google LLC. +// +// This software is licensed under the terms defined in the Widevine Master +// License Agreement. For a copy of this agreement, please contact +// widevine-licensing@google.com. +//////////////////////////////////////////////////////////////////////////////// +// +// Description: +// Definitions of the protocol buffer messages used in the Widevine license +// exchange protocol, described in Widevine license exchange protocol document + +syntax = "proto2"; + +package widevine; + +import "protos/public/client_identification.proto"; +import "protos/public/hash_algorithm.proto"; +import "protos/public/remote_attestation.proto"; + +option java_package = "com.google.video.widevine.protos"; + + +// option optimize_for = LITE_RUNTIME; +enum LicenseType { + STREAMING = 1; + OFFLINE = 2; + // License type decision is left to provider. + AUTOMATIC = 3; +} + +enum PlatformVerificationStatus { + // The platform is not verified. + PLATFORM_UNVERIFIED = 0; + // Tampering detected on the platform. + PLATFORM_TAMPERED = 1; + // The platform has been verified by means of software. + PLATFORM_SOFTWARE_VERIFIED = 2; + // The platform has been verified by means of hardware (e.g. secure boot). + PLATFORM_HARDWARE_VERIFIED = 3; + // Platform verification was not performed. + PLATFORM_NO_VERIFICATION = 4; + // Platform and secure storage capability have been verified by means of + // software. + PLATFORM_SECURE_STORAGE_SOFTWARE_VERIFIED = 5; +} + +// LicenseIdentification is propagated from LicenseRequest to License, +// incrementing version with each iteration. +message LicenseIdentification { + optional bytes request_id = 1; + optional bytes session_id = 2; + optional bytes purchase_id = 3; + optional LicenseType type = 4; + optional int32 version = 5; + optional bytes provider_session_token = 6; +} + +// This message is used to indicate the license cateogry spec for a license as +// a part of initial license issuance. +// LINT.IfChange +message LicenseCategorySpec { + // Possible license categories. + enum LicenseCategory { + // By default, License is used for single content. + SINGLE_CONTENT_LICENSE_DEFAULT = 0; + // License is used for multiple contents (could be a combination of + // single contents and groups of contents). + MULTI_CONTENT_LICENSE = 1; + // License is used for contents logically grouped. + GROUP_LICENSE = 2; + } + // Optional. License category indicates if license is used for single + // content, multiple contents (could be a combination of + // single contents and groups of contents) or a group of contents. + optional LicenseCategory license_category = 1; + // Optional. Content or group ID covered by the license. + oneof content_or_group_id { + // Content_id would be present if it is a license for single content. + bytes content_id = 2; + // Group_id would be present if the license is a multi_content_license or + // group_license. Group Id could be the name of a group of contents, + // defined by licensor. + bytes group_id = 3; + } +} + +message License { + // LINT.IfChange + message Policy { + // Indicates that playback of the content is allowed. + optional bool can_play = 1 [default = false]; + + // Indicates that the license may be persisted to non-volatile + // storage for offline use. + optional bool can_persist = 2 [default = false]; + + // Indicates that renewal of this license is allowed. + optional bool can_renew = 3 [default = false]; + + // For the |*duration*| fields, playback must halt when + // license_start_time (seconds since the epoch (UTC)) + + // license_duration_seconds is exceeded. A value of 0 + // indicates that there is no limit to the duration. + + // Indicates the rental window. + optional int64 rental_duration_seconds = 4 [default = 0]; + + // Indicates the viewing window, once playback has begun. + optional int64 playback_duration_seconds = 5 [default = 0]; + + // Indicates the time window for this specific license. + optional int64 license_duration_seconds = 6 [default = 0]; + + // The |renewal*| fields only apply if |can_renew| is true. + + // The window of time, in which playback is allowed to continue while + // renewal is attempted, yet unsuccessful due to backend problems with + // the license server. + optional int64 renewal_recovery_duration_seconds = 7 [default = 0]; + + // All renewal requests for this license shall be directed to the + // specified URL. + optional string renewal_server_url = 8; + + // How many seconds after license_start_time, before renewal is first + // attempted. + optional int64 renewal_delay_seconds = 9 [default = 0]; + + // Specifies the delay in seconds between subsequent license + // renewal requests, in case of failure. + optional int64 renewal_retry_interval_seconds = 10 [default = 0]; + + // Indicates that the license shall be sent for renewal when usage is + // started. + optional bool renew_with_usage = 11 [default = false]; + + // Indicates to client that license renewal and release requests ought to + // include ClientIdentification (client_id). + optional bool always_include_client_id = 12 [default = false]; + + // Duration of grace period before playback_duration_seconds (short window) + // goes into effect. Optional. + optional int64 play_start_grace_period_seconds = 13 [default = 0]; + + // Enables "soft enforcement" of playback_duration_seconds, letting the user + // finish playback even if short window expires. Optional. + optional bool soft_enforce_playback_duration = 14 [default = false]; + + // Enables "soft enforcement" of rental_duration_seconds. Initial playback + // must always start before rental duration expires. In order to allow + // subsequent playbacks to start after the rental duration expires, + // soft_enforce_playback_duration must be true. Otherwise, subsequent + // playbacks will not be allowed once rental duration expires. Optional. + optional bool soft_enforce_rental_duration = 15 [default = true]; + } + + message KeyContainer { + enum KeyType { + SIGNING = 1; // No more than one signing key may appear. + CONTENT = 2; // Content key. + KEY_CONTROL = 3; // Key control block for license renewals. No key. + OPERATOR_SESSION = 4; // wrapped keys for auxiliary crypto operations. + ENTITLEMENT = 5; // Entitlement keys. + OEM_CONTENT = 6; // Partner-specific content key. + } + + // The SecurityLevel enumeration allows the server to communicate the level + // of robustness required by the client, in order to use the key. + enum SecurityLevel { + // Software-based whitebox crypto is required. + SW_SECURE_CRYPTO = 1; + + // Software crypto and an obfuscated decoder is required. + SW_SECURE_DECODE = 2; + + // The key material and crypto operations must be performed within a + // hardware backed trusted execution environment. + HW_SECURE_CRYPTO = 3; + + // The crypto and decoding of content must be performed within a hardware + // backed trusted execution environment. + HW_SECURE_DECODE = 4; + + // The crypto, decoding and all handling of the media (compressed and + // uncompressed) must be handled within a hardware backed trusted + // execution environment. + HW_SECURE_ALL = 5; + } + + message KeyControl { + // If present, the key control must be communicated to the secure + // environment prior to any usage. This message is automatically generated + // by the Widevine License Server SDK. + optional bytes key_control_block = 1; + optional bytes iv = 2; + } + + message OutputProtection { + // Indicates whether HDCP is required on digital outputs, and which + // version should be used. + enum HDCP { + HDCP_NONE = 0; + HDCP_V1 = 1; + HDCP_V2 = 2; + HDCP_V2_1 = 3; + HDCP_V2_2 = 4; + HDCP_V2_3 = 5; + HDCP_NO_DIGITAL_OUTPUT = 0xff; + } + optional HDCP hdcp = 1 [default = HDCP_NONE]; + + // Indicate the CGMS setting to be inserted on analog output. + enum CGMS { + CGMS_NONE = 42; + COPY_FREE = 0; + COPY_ONCE = 2; + COPY_NEVER = 3; + } + optional CGMS cgms_flags = 2 [default = CGMS_NONE]; + + enum HdcpSrmRule { + HDCP_SRM_RULE_NONE = 0; + // In 'required_protection', this means most current SRM is required. + // Update the SRM on the device. If update cannot happen, + // do not allow the key. + // In 'requested_protection', this means most current SRM is requested. + // Update the SRM on the device. If update cannot happen, + // allow use of the key anyway. + CURRENT_SRM = 1; + } + optional HdcpSrmRule hdcp_srm_rule = 3 [default = HDCP_SRM_RULE_NONE]; + // Optional requirement to indicate analog output is not allowed. + optional bool disable_analog_output = 4 [default = false]; + // Optional requirement to indicate digital output is not allowed. + optional bool disable_digital_output = 5 [default = false]; + // Optional. If set, it indicates digital video recording (DVR) is + // allowed. + optional bool allow_record = 6 [default = false]; + } + + message VideoResolutionConstraint { + // Minimum and maximum video resolutions in the range (height x width). + optional uint32 min_resolution_pixels = 1; + optional uint32 max_resolution_pixels = 2; + // Optional output protection requirements for this range. If not + // specified, the OutputProtection in the KeyContainer applies. + optional OutputProtection required_protection = 3; + } + + message OperatorSessionKeyPermissions { + // Permissions/key usage flags for operator service keys + // (type = OPERATOR_SESSION). + optional bool allow_encrypt = 1 [default = false]; + optional bool allow_decrypt = 2 [default = false]; + optional bool allow_sign = 3 [default = false]; + optional bool allow_signature_verify = 4 [default = false]; + } + + // KeyCategorySpec message is used to identify if current key is generated + // for a single content or a group of contents. Currently it is only used in + // CAS request. + message KeyCategorySpec { + // Represents what kind of content a key is used for. + enum KeyCategory { + // By default, key is created for single content. + SINGLE_CONTENT_KEY_DEFAULT = 0; + // Key is created for a group of contents. + GROUP_KEY = 1; + } + // Indicate if the current key is created for single content or for group + // use. + optional KeyCategory key_category = 1; + // Id for key category. If it is a key for single content, this id + // represents the content_id. Otherwise, it represents a group_id. + oneof content_or_group_id { + bytes content_id = 2; + bytes group_id = 3; + } + } + + optional bytes id = 1; + optional bytes iv = 2; + optional bytes key = 3; + optional KeyType type = 4; + optional SecurityLevel level = 5 [default = SW_SECURE_CRYPTO]; + optional OutputProtection required_protection = 6; + // NOTE: Use of requested_protection is not recommended as it is only + // supported on a small number of platforms. + optional OutputProtection requested_protection = 7; + optional KeyControl key_control = 8; + optional OperatorSessionKeyPermissions operator_session_key_permissions = 9; + // Optional video resolution constraints. If the video resolution of the + // content being decrypted/decoded falls within one of the specified ranges, + // the optional required_protections may be applied. Otherwise an error will + // be reported. + // NOTE: Use of this feature is not recommended, as it is only supported on + // a small number of platforms. + repeated VideoResolutionConstraint video_resolution_constraints = 10; + // Optional flag to indicate the key must only be used if the client + // supports anti rollback of the user table. Content provider can query the + // client capabilities to determine if the client support this feature. + optional bool anti_rollback_usage_table = 11 [default = false]; + // Optional not limited to commonly known track types such as SD, HD. + // It can be some provider defined label to identify the track. + optional string track_label = 12; + // Optional. It is used to identify if current key is generated for a + // single content or a group of contents. Currently it is only used in CAS + // request. + optional KeyCategorySpec key_category_spec = 13; + } + + optional LicenseIdentification id = 1; + optional Policy policy = 2; + repeated KeyContainer key = 3; + // Time of the request in seconds (UTC) as set in + // LicenseRequest.request_time. If this time is not set in the request, + // the local time at the license service is used in this field. + optional int64 license_start_time = 4; + // TODO(b/65054419): Deprecate remote_attestation_verified in favor of + // platform_verification_status, below. + optional bool remote_attestation_verified = 5 [default = false]; + // Client token generated by the content provider. Optional. + optional bytes provider_client_token = 6; + // 4cc code specifying the CENC protection scheme as defined in the CENC 3.0 + // specification. Propagated from Widevine PSSH box. Optional. + optional uint32 protection_scheme = 7; + // 8 byte verification field "HDCPDATA" followed by unsigned 32 bit minimum + // HDCP SRM version (whether the version is for HDCP1 SRM or HDCP2 SRM + // depends on client max_hdcp_version). + optional bytes srm_requirement = 8; + // If present this contains a signed SRM file (either HDCP1 SRM or HDCP2 SRM + // depending on client max_hdcp_version) that should be installed on the + // client device. + optional bytes srm_update = 9; + // Indicates the status of any type of platform verification performed by the + // server. + optional PlatformVerificationStatus platform_verification_status = 10 + [default = PLATFORM_NO_VERIFICATION]; + // IDs of the groups for which keys are delivered in this license, if any. + repeated bytes group_ids = 11; + // Optional. LicenseCategorySpec is used to indicate the license cateogry for + // a license. It could be used as a part of initial license issuance or shown + // as a part of license in license response. + optional LicenseCategorySpec license_category_spec = 12; +} + +enum ProtocolVersion { + VERSION_2_0 = 20; + VERSION_2_1 = 21; + VERSION_2_2 = 22; +} + +message LicenseRequest { + message ContentIdentification { + message WidevinePsshData { + repeated bytes pssh_data = 1; + optional LicenseType license_type = 2; + optional bytes request_id = 3; // Opaque, client-specified. + } + + message WebmKeyId { + optional bytes header = 1; + optional LicenseType license_type = 2; + optional bytes request_id = 3; // Opaque, client-specified. + } + + message ExistingLicense { + optional LicenseIdentification license_id = 1; + optional int64 seconds_since_started = 2; + optional int64 seconds_since_last_played = 3; + optional bytes session_usage_table_entry = 4; + } + + message InitData { + enum InitDataType { + CENC = 1; + WEBM = 2; + } + + optional InitDataType init_data_type = 1 [default = CENC]; + optional bytes init_data = 2; + optional LicenseType license_type = 3; + optional bytes request_id = 4; + } + + oneof content_id_variant { + // Exactly one of these must be present. + WidevinePsshData widevine_pssh_data = 1; + WebmKeyId webm_key_id = 2; + ExistingLicense existing_license = 3; + InitData init_data = 4; + } + } + + enum RequestType { + NEW = 1; + RENEWAL = 2; + RELEASE = 3; + } + + // The client_id provides information authenticating the calling device. It + // contains the Widevine keybox token that was installed on the device at the + // factory. This field or encrypted_client_id below is required for a valid + // license request, but both should never be present in the same request. + optional ClientIdentification client_id = 1; + optional ContentIdentification content_id = 2; + optional RequestType type = 3; + // Time of the request in seconds (UTC) as set by the client. + optional int64 request_time = 4; + // Old-style decimal-encoded string key control nonce. + optional bytes key_control_nonce_deprecated = 5; + optional ProtocolVersion protocol_version = 6 [default = VERSION_2_0]; + // New-style uint32 key control nonce, please use instead of + // key_control_nonce_deprecated. + optional uint32 key_control_nonce = 7; + // Encrypted ClientIdentification message, used for privacy purposes. + optional EncryptedClientIdentification encrypted_client_id = 8; +} + +message LicenseError { + enum Error { + // The device credentials are invalid. The device must re-provision. + INVALID_DRM_DEVICE_CERTIFICATE = 1; + // The device credentials have been revoked. Re-provisioning is not + // possible. + REVOKED_DRM_DEVICE_CERTIFICATE = 2; + // The service is currently unavailable due to the backend being down + // or similar circumstances. + SERVICE_UNAVAILABLE = 3; + } + optional Error error_code = 1; +} + +message MetricData { + enum MetricType { + // The time spent in the 'stage', specified in microseconds. + LATENCY = 1; + // The UNIX epoch timestamp at which the 'stage' was first accessed in + // microseconds. + TIMESTAMP = 2; + } + + message TypeValue { + optional MetricType type = 1; + // The value associated with 'type'. For example if type == LATENCY, the + // value would be the time in microseconds spent in this 'stage'. + optional int64 value = 2 [default = 0]; + } + + // 'stage' that is currently processing the SignedMessage. Required. + optional string stage_name = 1; + // metric and associated value. + repeated TypeValue metric_data = 2; +} + +message VersionInfo { + // License SDK version reported by the Widevine License SDK. This field + // is populated automatically by the SDK. + optional string license_sdk_version = 1; + // Version of the service hosting the license SDK. This field is optional. + // It may be provided by the hosting service. + optional string license_service_version = 2; +} + +message SignedMessage { + enum MessageType { + LICENSE_REQUEST = 1; + LICENSE = 2; + ERROR_RESPONSE = 3; + SERVICE_CERTIFICATE_REQUEST = 4; + SERVICE_CERTIFICATE = 5; + SUB_LICENSE = 6; + CAS_LICENSE_REQUEST = 7; + CAS_LICENSE = 8; + EXTERNAL_LICENSE_REQUEST = 9; + EXTERNAL_LICENSE = 10; + } + + enum SessionKeyType { + UNDEFINED = 0; + WRAPPED_AES_KEY = 1; + EPHEMERAL_ECC_PUBLIC_KEY = 2; + } + + optional MessageType type = 1; + optional bytes msg = 2; + // Required field that contains the signature of the bytes of msg. + // For license requests, the signing algorithm is determined by the + // certificate contained in the request. + // For license responses, the signing algorithm is HMAC with signing key based + // on |session_key|. + optional bytes signature = 3; + // If populated, the contents of this field will be signaled by the + // |session_key_type| type. If the |session_key_type| is WRAPPED_AES_KEY the + // key is the bytes of an encrypted AES key. If the |session_key_type| is + // EPHERMERAL_ECC_PUBLIC_KEY the field contains the bytes of an RFC5208 ASN1 + // serialized ECC public key. + optional bytes session_key = 4; + // Remote attestation data which will be present in the initial license + // request for ChromeOS client devices operating in verified mode. Remote + // attestation challenge data is |msg| field above. Optional. + optional RemoteAttestation remote_attestation = 5; + + repeated MetricData metric_data = 6; + // Version information from the SDK and license service. This information is + // provided in the license response. + optional VersionInfo service_version_info = 7; + // Optional field that contains the algorithm type used to generate the + // session_key and signature in a LICENSE message. + optional SessionKeyType session_key_type = 8 [default = WRAPPED_AES_KEY]; + // The core message is the simple serialization of fields used by OEMCrypto. + // This field was introduced in OEMCrypto API v16. + optional bytes oemcrypto_core_message = 9; + // Optional field that indicates the hash algorithm used in signature scheme. + optional HashAlgorithmProto hash_algorithm = 10; + // If true it indicates that a LICENSE message session key was based on a key + // provided in a secondary encryption certificate. The secondary encryption + // certificate was provided by the client in a previous LICENSE_REQUEST + // message. + optional bool using_dual_certificate = 11; +} diff --git a/protos/public/provisioned_device_info.proto b/protos/public/provisioned_device_info.proto index f6d4104..d73d68b 100644 --- a/protos/public/provisioned_device_info.proto +++ b/protos/public/provisioned_device_info.proto @@ -13,9 +13,12 @@ syntax = "proto2"; package widevine; +import "protos/public/device_common.proto"; + option java_package = "com.google.video.widevine.protos"; option java_outer_classname = "ProvisionedDeviceInfoProto"; + // Contains device model information for a provisioned device. message ProvisionedDeviceInfo { enum WvSecurityLevel { @@ -55,13 +58,14 @@ message ProvisionedDeviceInfo { optional uint32 system_id = 1; // Name of system-on-a-chip. Optional. optional string soc = 2; - // Name of manufacturer. Optional. + // First registered manufacturer. Optional. optional string manufacturer = 3; - // Manufacturer's model name. Matches "brand" in device metadata. Optional. + // First registered manufacturer's model name. Matches "brand" in device + // metadata. Optional. optional string model = 4; - // Type of device (Phone, Tablet, TV, etc). + // First registered type of device (Phone, Tablet, TV, etc). optional string device_type = 5; - // Device model year. Optional. + // First registered device model year. Optional. optional uint32 model_year = 6; // Widevine-defined security level. Optional. optional WvSecurityLevel security_level = 7 [default = LEVEL_UNSPECIFIED]; @@ -70,4 +74,6 @@ message ProvisionedDeviceInfo { optional bool test_device = 8 [default = false]; // Indicates the type of device root of trust which was factory provisioned. optional ProvisioningMethod provisioning_method = 9; + // A list of ModelInfo using the same system_id. + repeated DeviceModel model_info = 10; } diff --git a/protos/public/remote_attestation.proto b/protos/public/remote_attestation.proto index d8a8551..6a487ac 100644 --- a/protos/public/remote_attestation.proto +++ b/protos/public/remote_attestation.proto @@ -13,10 +13,12 @@ syntax = "proto2"; package widevine; -option java_package = "com.google.video.widevine.protos"; import "protos/public/client_identification.proto"; +option java_package = "com.google.video.widevine.protos"; + + message RemoteAttestation { // Encrypted ClientIdentification message containing the device remote // attestation certificate. Required. @@ -27,4 +29,3 @@ message RemoteAttestation { // Signed remote attestation challenge + salt. Required. optional bytes signature = 3; } - diff --git a/protos/public/signed_drm_certificate.proto b/protos/public/signed_drm_certificate.proto index bbd0835..6022fbf 100644 --- a/protos/public/signed_drm_certificate.proto +++ b/protos/public/signed_drm_certificate.proto @@ -12,9 +12,12 @@ syntax = "proto2"; package widevine; +import "protos/public/hash_algorithm.proto"; + option java_outer_classname = "SignedDrmCertificateProtos"; option java_package = "com.google.video.widevine.protos"; + // DrmCertificate signed by a higher (CA) DRM certificate. message SignedDrmCertificate { // Serialized certificate. Required. @@ -24,4 +27,6 @@ message SignedDrmCertificate { optional bytes signature = 2; // SignedDrmCertificate used to sign this certificate. optional SignedDrmCertificate signer = 3; + // Optional field that indicates the hash algorithm used in signature scheme. + optional HashAlgorithmProto hash_algorithm = 4; } diff --git a/provisioning_sdk/internal/BUILD b/provisioning_sdk/internal/BUILD index 4dbdf93..925d958 100644 --- a/provisioning_sdk/internal/BUILD +++ b/provisioning_sdk/internal/BUILD @@ -15,6 +15,7 @@ package_group( "//arcpp_provisioning/...", "//provisioning_sdk/...", "//sigma101_provisioning/...", + "//sigma210_provisioning/...", ], ) @@ -29,21 +30,23 @@ cc_library( deps = [ ":oem_device_cert", "//base", + "@abseil_repo//absl/base:core_headers", "@abseil_repo//absl/synchronization", "//common:aes_cbc_util", "//common:certificate_type", "//common:crypto_util", "//common:drm_root_certificate", "//common:drm_service_certificate", + "//common:hash_algorithm_util", "//common:random_util", "//common:rsa_key", "//provisioning_sdk/internal/certificates:root_oem_certificates", "//provisioning_sdk/public:provisioning_status", - "//protos/public:certificate_provisioning_proto", - "//protos/public:device_certificate_status_proto", - "//protos/public:drm_certificate_proto", - "//protos/public:provisioned_device_info_proto", - "//protos/public:signed_drm_certificate_proto", + "//protos/public:certificate_provisioning_cc_proto", + "//protos/public:device_certificate_status_cc_proto", + "//protos/public:drm_certificate_cc_proto", + "//protos/public:provisioned_device_info_cc_proto", + "//protos/public:signed_drm_certificate_cc_proto", ], ) @@ -62,7 +65,7 @@ cc_test( "//common:rsa_util", "//common:status", "//common:test_drm_certificates", - "//protos/public:certificate_provisioning_proto", + "//protos/public:certificate_provisioning_cc_proto", ], ) @@ -99,15 +102,18 @@ cc_library( ":provisioning_session_impl", "//base", "//common:aes_cbc_util", + "//common:core_message_util", "//common:drm_service_certificate", + "//common:hash_algorithm", + "//common:hash_algorithm_util", "//common:random_util", "//common:rsa_key", "//common:sha_util", "//provisioning_sdk/public:provisioning_status", - "//protos/public:certificate_provisioning_proto", - "//protos/public:client_identification_proto", - "//protos/public:drm_certificate_proto", - "//protos/public:provisioned_device_info_proto", + "//protos/public:certificate_provisioning_cc_proto", + "//protos/public:client_identification_cc_proto", + "//protos/public:drm_certificate_cc_proto", + "//protos/public:provisioned_device_info_cc_proto", ], ) @@ -121,9 +127,13 @@ cc_test( ":provisioning_engine_impl", ":provisioning_session_impl", "//testing:gunit_main", + "@abseil_repo//absl/strings", "//common:aes_cbc_util", + "//common:hash_algorithm", + "//common:hash_algorithm_util", "//common:mock_rsa_key", "//common:sha_util", + "//protos/public:hash_algorithm_cc_proto", ], ) diff --git a/provisioning_sdk/internal/provisioning30_session_impl.cc b/provisioning_sdk/internal/provisioning30_session_impl.cc index fed7f87..5b27207 100644 --- a/provisioning_sdk/internal/provisioning30_session_impl.cc +++ b/provisioning_sdk/internal/provisioning30_session_impl.cc @@ -12,6 +12,9 @@ #include "glog/logging.h" #include "common/aes_cbc_util.h" +#include "common/core_message_util.h" +#include "common/hash_algorithm.h" +#include "common/hash_algorithm_util.h" #include "common/random_util.h" #include "common/rsa_key.h" #include "common/sha_util.h" @@ -21,7 +24,8 @@ #define LOG_EVERY_N_WITH_PROTO(message, proto) \ LOG_EVERY_N(WARNING, FLAGS_prov_sdk_log_every_n) \ << (message) << " [proto: " << (proto).ShortDebugString() << "]" - +// TODO(user): Use instantiate_test_suite_p to reorg the test case to cover +// with or withoutcore_message. namespace widevine { Provisioning30SessionImpl::Provisioning30SessionImpl( @@ -66,12 +70,14 @@ ProvisioningStatus Provisioning30SessionImpl::ProcessMessage( LOG_EVERY_N_WITH_PROTO("Invalid token", client_id); return INVALID_REQUEST_MESSAGE; } - if (!cert_public_key->VerifySignature(signed_request.message(), - signed_request.signature())) { + const HashAlgorithm hash_algorithm = + HashAlgorithmProtoToEnum(signed_request.hash_algorithm()); + if (!cert_public_key->VerifySignature( + signed_request.oemcrypto_core_message() + signed_request.message(), + hash_algorithm, signed_request.signature())) { LOG_EVERY_N_WITH_PROTO("Signature verification failed", client_id); return INVALID_REQUEST_MESSAGE; } - // Save device_info for query later. device_info_ = engine_.GetDeviceInfo(system_id); @@ -81,8 +87,8 @@ ProvisioningStatus Provisioning30SessionImpl::ProcessMessage( } else { // Generate stable serial number. const std::string stable_data(client_id.token() + request.stable_id() + - request.provider_id() + - engine_.secret_spoid_sauce()); + request.provider_id() + + engine_.secret_spoid_sauce()); const std::string hash = Sha256_Hash(stable_data); const size_t RootCertificateSerialNumberSize = 16; certificate_serial_number = hash.substr(0, RootCertificateSerialNumberSize); @@ -102,8 +108,25 @@ ProvisioningStatus Provisioning30SessionImpl::ProcessMessage( LOG(WARNING) << "Error serializing ProvisioningResponse."; return INTERNAL_ERROR; } + if (signed_request.has_oemcrypto_core_message() && + !signed_request.oemcrypto_core_message().empty()) { + if (!core_message_util::GetCoreProvisioningResponse( + signed_message.message(), signed_request.oemcrypto_core_message(), + signed_message.mutable_oemcrypto_core_message())) { + LOG(WARNING) << "Failed to get signed core message, response: " + << signed_message.ShortDebugString(); + return INTERNAL_ERROR; + } + if (signed_message.oemcrypto_core_message().empty()) { + LOG(WARNING) << "Failed to get signed core message, response: " + << signed_message.ShortDebugString(); + return INTERNAL_ERROR; + } + } + signed_message.set_hash_algorithm(HashAlgorithmEnumToProto(hash_algorithm)); if (!service_private_key_.GenerateSignature( - signed_message.message(), signed_message.mutable_signature())) { + signed_message.oemcrypto_core_message() + signed_message.message(), + hash_algorithm, signed_message.mutable_signature())) { LOG(WARNING) << "Failed to sign ProvisioningResponse."; return INTERNAL_ERROR; } @@ -111,7 +134,6 @@ ProvisioningStatus Provisioning30SessionImpl::ProcessMessage( LOG(WARNING) << "Error serializing SignedProvisioningMessage."; return INTERNAL_ERROR; } - *done = true; return OK; } @@ -197,7 +219,8 @@ bool Provisioning30SessionImpl::DecryptClientIdentification( ProvisioningStatus Provisioning30SessionImpl::GenerateProvisioningResponse( uint32_t system_id, const std::string& oem_ca_serial_number, - const std::string& provider_id, const std::string& certificate_serial_number, + const std::string& provider_id, + const std::string& certificate_serial_number, const RsaPublicKey& cert_public_key, ProvisioningResponse* response) { ProvisioningStatus status = engine_.GenerateProviderDeviceDrmCertificate( system_id, oem_ca_serial_number, provider_id, device_drm_public_key_, diff --git a/provisioning_sdk/internal/provisioning30_session_impl.h b/provisioning_sdk/internal/provisioning30_session_impl.h index 8cc5e34..15d5a06 100644 --- a/provisioning_sdk/internal/provisioning30_session_impl.h +++ b/provisioning_sdk/internal/provisioning30_session_impl.h @@ -43,8 +43,7 @@ class Provisioning30SessionImpl : public ProvisioningSessionImpl { // exchange is complete. // Returns OK if successful, or an appropriate error status code otherwise. ProvisioningStatus ProcessMessage(const std::string& message, - std::string* response, - bool* done) override; + std::string* response, bool* done) override; // * Returns a ProvisioneddeviceInfo message containing information about the // type of device being provisioned. May return nullptr. @@ -65,7 +64,8 @@ class Provisioning30SessionImpl : public ProvisioningSessionImpl { ClientIdentification* client_id); ProvisioningStatus GenerateProvisioningResponse( uint32_t system_id, const std::string& oem_ca_serial_number, - const std::string& provider_id, const std::string& certificate_serial_number, + const std::string& provider_id, + const std::string& certificate_serial_number, const RsaPublicKey& cert_public_key, ProvisioningResponse* response); const OemDeviceCert& oem_device_cert_; diff --git a/provisioning_sdk/internal/provisioning30_session_impl_test.cc b/provisioning_sdk/internal/provisioning30_session_impl_test.cc index 1bbc771..ba2baf9 100644 --- a/provisioning_sdk/internal/provisioning30_session_impl_test.cc +++ b/provisioning_sdk/internal/provisioning30_session_impl_test.cc @@ -8,13 +8,20 @@ #include "provisioning_sdk/internal/provisioning30_session_impl.h" +#include + #include "testing/gmock.h" #include "testing/gunit.h" +#include "absl/strings/escaping.h" +#include "absl/strings/str_cat.h" #include "common/aes_cbc_util.h" +#include "common/hash_algorithm.h" +#include "common/hash_algorithm_util.h" #include "common/mock_rsa_key.h" #include "common/sha_util.h" #include "provisioning_sdk/internal/oem_device_cert.h" #include "provisioning_sdk/internal/provisioning_engine_impl.h" +#include "protos/public/hash_algorithm.pb.h" using ::testing::_; using ::testing::ByMove; @@ -23,8 +30,16 @@ using ::testing::IsEmpty; using ::testing::Return; using ::testing::SaveArg; using ::testing::SetArgPointee; - +// TODO(user): use a real test key instead of mock_key to testcore message +// code change. namespace { +const char kCoreMessage[] = + "00000005000000580000001000000010000000000000004000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000"; +const char kInvalidCoreMessage[] = + "0000000500000058000000100000001000000000000000400000000000000000000000000"; +const char kEmptyCoreMessage[] = ""; const char kEncryptedClientIdIv[] = "sixteen_bytes_iv"; const char kPrivacyKey[] = "privacy_key_16B_"; const char kProviderId[] = "testing_provider"; @@ -38,8 +53,9 @@ const char kNonce[] = "testing_nonce"; const char kSignature[] = "generated_signature"; // Derives Stable Per-Origin IDentifiers. -std::string DeriveSpoid(const std::string& client_token, const std::string& provider_id, - const std::string& secret_sauce) { +std::string DeriveSpoid(const std::string& client_token, + const std::string& provider_id, + const std::string& secret_sauce) { return widevine::Sha256_Hash(client_token + provider_id + secret_sauce) .substr(0, 16); } @@ -50,27 +66,27 @@ namespace widevine { class MockProvisioningEngineImpl : public ProvisioningEngineImpl { public: - MOCK_CONST_METHOD6(GenerateProviderDeviceDrmCertificate, - ProvisioningStatus(uint32_t system_id, - const std::string& oem_ca_serial_number, - const std::string& provider_id, - const std::string& public_key, - const std::string& certificate_serial_number, - std::string* certificate)); + MOCK_METHOD(ProvisioningStatus, GenerateProviderDeviceDrmCertificate, + (uint32_t system_id, const std::string& oem_ca_serial_number, + const std::string& provider_id, const std::string& public_key, + const std::string& certificate_serial_number, + std::string* certificate), + (const, override)); }; class MockOemDeviceCert : public OemDeviceCert { public: // gmock does not support SetArgPointee on std::unique_ptr, so we have to // workaround it with a trick. - MOCK_CONST_METHOD4(DoVerifyCertificateChain, - bool(const std::string& certificate_chain, - RsaPublicKey** leaf_public_key, uint32_t* system_id, - std::string* oem_ca_serial_number)); - bool VerifyCertificateChain(const std::string& certificate_chain, - std::unique_ptr* leaf_public_key, - uint32_t* system_id, - std::string* oem_ca_serial_number) const override { + MOCK_METHOD(bool, DoVerifyCertificateChain, + (const std::string& certificate_chain, + RsaPublicKey** leaf_public_key, uint32_t* system_id, + std::string* oem_ca_serial_number), + (const)); + bool VerifyCertificateChain( + const std::string& certificate_chain, + std::unique_ptr* leaf_public_key, uint32_t* system_id, + std::string* oem_ca_serial_number) const override { RsaPublicKey* raw_leaf_public_key = nullptr; if (!DoVerifyCertificateChain(certificate_chain, &raw_leaf_public_key, system_id, oem_ca_serial_number)) { @@ -133,9 +149,61 @@ class Provisioning30SessionImplProcessTest signed_prov_message_.set_signature("testing_signature"); } + void ProcessMessage(std::string core_message) { + signed_prov_message_.set_oemcrypto_core_message( + absl::HexStringToBytes(core_message)); + const uint32_t kSystemId = 1234; + EXPECT_CALL(mock_service_private_key_, Decrypt(kEncryptedPrivacyKey, _)) + .WillOnce(DoAll(SetArgPointee<1>(kPrivacyKey), Return(true))); + MockRsaPublicKey* mock_cert_public_key = new MockRsaPublicKey; + EXPECT_CALL(mock_oem_device_cert_, + DoVerifyCertificateChain(kClientToken, _, _, _)) + .WillOnce(DoAll(SetArgPointee<1>(mock_cert_public_key), + SetArgPointee<2>(kSystemId), Return(true))); + const HashAlgorithm hash_algorithm = + HashAlgorithmProtoToEnum(signed_prov_message_.hash_algorithm()); + EXPECT_CALL( + *mock_cert_public_key, + VerifySignature(signed_prov_message_.oemcrypto_core_message() + + signed_prov_message_.message(), + hash_algorithm, signed_prov_message_.signature())) + .WillOnce(Return(true)); + + EXPECT_CALL(mock_engine_impl_, GenerateProviderDeviceDrmCertificate( + kSystemId, _, _, kDevicePublicKey, _, _)) + .WillOnce(DoAll(SetArgPointee<5>(kDeviceCertificate), Return(OK))); + EXPECT_CALL(*mock_cert_public_key, Encrypt(_, _)) + .WillOnce(DoAll(SaveArg<0>(&message_key_), + SetArgPointee<1>(kWrappingKey), Return(true))); + } + + void ValidateMessage(std::string core_message) { + ProvisioningResponse prov_response; + // Verify the response. + ASSERT_TRUE(signed_prov_message_.ParseFromString(response_)); + EXPECT_EQ(kSignature, signed_prov_message_.signature()); + ASSERT_TRUE(prov_response.ParseFromString(signed_prov_message_.message())); + if (!core_message.empty()) { + EXPECT_TRUE(signed_prov_message_.has_oemcrypto_core_message()); + EXPECT_EQ(signature_input_, + absl::StrCat(signed_prov_message_.oemcrypto_core_message(), + signed_prov_message_.message())); + } + EXPECT_EQ(kDevicePrivateKey, + crypto_util::DecryptAesCbc(message_key_, + prov_response.device_rsa_key_iv(), + prov_response.device_rsa_key())); + EXPECT_EQ(kDeviceCertificate, prov_response.device_certificate()); + EXPECT_EQ(kNonce, prov_response.nonce()); + EXPECT_EQ(kWrappingKey, prov_response.wrapping_key()); + } + ClientIdentification client_id_; ProvisioningRequest prov_request_; SignedProvisioningMessage signed_prov_message_; + std::string signature_input_; + std::string message_key_; + std::string response_; }; TEST_F(Provisioning30SessionImplProcessTest, InvalidMessage) { @@ -310,8 +378,10 @@ TEST_F(Provisioning30SessionImplProcessTest, VerifySignatureFailed) { EXPECT_CALL(mock_oem_device_cert_, DoVerifyCertificateChain(kClientToken, _, _, _)) .WillOnce(DoAll(SetArgPointee<1>(mock_cert_public_key), Return(true))); + const HashAlgorithm hash_algorithm = + HashAlgorithmProtoToEnum(signed_prov_message_.hash_algorithm()); EXPECT_CALL(*mock_cert_public_key, - VerifySignature(signed_prov_message_.message(), + VerifySignature(signed_prov_message_.message(), hash_algorithm, signed_prov_message_.signature())) .WillOnce(Return(false)); @@ -334,8 +404,10 @@ TEST_F(Provisioning30SessionImplProcessTest, GenerateDeviceCertificateFailed) { .WillOnce(DoAll( SetArgPointee<1>(mock_cert_public_key), SetArgPointee<2>(kSystemId), SetArgPointee<3>(kExpectedOemSerialNumber), Return(true))); + const HashAlgorithm hash_algorithm = + HashAlgorithmProtoToEnum(signed_prov_message_.hash_algorithm()); EXPECT_CALL(*mock_cert_public_key, - VerifySignature(signed_prov_message_.message(), + VerifySignature(signed_prov_message_.message(), hash_algorithm, signed_prov_message_.signature())) .WillOnce(Return(true)); @@ -353,54 +425,57 @@ TEST_F(Provisioning30SessionImplProcessTest, GenerateDeviceCertificateFailed) { signed_prov_message_.SerializeAsString(), &response, &done)); } -TEST_F(Provisioning30SessionImplProcessTest, Success) { - const uint32_t kSystemId = 1234; - EXPECT_CALL(mock_service_private_key_, Decrypt(kEncryptedPrivacyKey, _)) - .WillOnce(DoAll(SetArgPointee<1>(kPrivacyKey), Return(true))); - MockRsaPublicKey* mock_cert_public_key = new MockRsaPublicKey; - EXPECT_CALL(mock_oem_device_cert_, - DoVerifyCertificateChain(kClientToken, _, _, _)) - .WillOnce(DoAll(SetArgPointee<1>(mock_cert_public_key), - SetArgPointee<2>(kSystemId), Return(true))); - EXPECT_CALL(*mock_cert_public_key, - VerifySignature(signed_prov_message_.message(), - signed_prov_message_.signature())) - .WillOnce(Return(true)); - - EXPECT_CALL(mock_engine_impl_, GenerateProviderDeviceDrmCertificate( - kSystemId, _, _, kDevicePublicKey, _, _)) - .WillOnce(DoAll(SetArgPointee<5>(kDeviceCertificate), Return(OK))); - - std::string message_key; - EXPECT_CALL(*mock_cert_public_key, Encrypt(_, _)) - .WillOnce(DoAll(SaveArg<0>(&message_key), SetArgPointee<1>(kWrappingKey), - Return(true))); - std::string message; - EXPECT_CALL(mock_service_private_key_, GenerateSignature(_, _)) - .WillOnce(DoAll(SaveArg<0>(&message), SetArgPointee<1>(kSignature), - Return(true))); - - std::string response; +TEST_F(Provisioning30SessionImplProcessTest, SuccessWithCoreMessage) { + ProcessMessage(kCoreMessage); bool done; - ASSERT_EQ(OK, session_impl_.ProcessMessage( - signed_prov_message_.SerializeAsString(), &response, &done)); - - // Verify the response. - EXPECT_TRUE(done); - SignedProvisioningMessage signed_prov_message; - ASSERT_TRUE(signed_prov_message.ParseFromString(response)); - EXPECT_EQ(message, signed_prov_message.message()); - EXPECT_EQ(kSignature, signed_prov_message.signature()); - - ProvisioningResponse prov_response; - ASSERT_TRUE(prov_response.ParseFromString(message)); - EXPECT_EQ( - kDevicePrivateKey, - crypto_util::DecryptAesCbc(message_key, prov_response.device_rsa_key_iv(), - prov_response.device_rsa_key())); - EXPECT_EQ(kDeviceCertificate, prov_response.device_certificate()); - EXPECT_EQ(kNonce, prov_response.nonce()); - EXPECT_EQ(kWrappingKey, prov_response.wrapping_key()); + EXPECT_CALL(mock_service_private_key_, GenerateSignature(_, _, _)) + .WillOnce(DoAll(SaveArg<0>(&signature_input_), + SetArgPointee<2>(kSignature), Return(true))); + ASSERT_EQ( + OK, session_impl_.ProcessMessage(signed_prov_message_.SerializeAsString(), + &response_, &done)); + ASSERT_TRUE(done); + ValidateMessage(kCoreMessage); } +TEST_F(Provisioning30SessionImplProcessTest, SuccessWithEmptyCoreMessage) { + ProcessMessage(kEmptyCoreMessage); + bool done; + EXPECT_CALL(mock_service_private_key_, GenerateSignature(_, _, _)) + .WillOnce(DoAll(SaveArg<0>(&signature_input_), + SetArgPointee<2>(kSignature), Return(true))); + ASSERT_EQ( + OK, session_impl_.ProcessMessage(signed_prov_message_.SerializeAsString(), + &response_, &done)); + ASSERT_TRUE(done); + ValidateMessage(kEmptyCoreMessage); +} + +TEST_F(Provisioning30SessionImplProcessTest, FailedWithInvalidCoreMessage) { + ProcessMessage(kInvalidCoreMessage); + bool done; + ASSERT_EQ(INTERNAL_ERROR, + session_impl_.ProcessMessage( + signed_prov_message_.SerializeAsString(), &response_, &done)); + ASSERT_FALSE(done); +} + +TEST_F(Provisioning30SessionImplProcessTest, VerifyHashAlgorithmInResponse) { + const HashAlgorithm hash_algorithm = HashAlgorithm::kSha256; + signed_prov_message_.set_hash_algorithm( + HashAlgorithmEnumToProto(hash_algorithm)); + ProcessMessage(kCoreMessage); + bool done; + EXPECT_CALL(mock_service_private_key_, GenerateSignature(_, _, _)) + .WillOnce(DoAll(SaveArg<0>(&signature_input_), + SetArgPointee<2>(kSignature), Return(true))); + ASSERT_EQ( + OK, session_impl_.ProcessMessage(signed_prov_message_.SerializeAsString(), + &response_, &done)); + ASSERT_TRUE(done); + SignedProvisioningMessage signed_response; + signed_response.ParseFromString(response_); + EXPECT_EQ(signed_response.hash_algorithm(), + signed_prov_message_.hash_algorithm()); +} } // namespace widevine diff --git a/provisioning_sdk/internal/provisioning_engine_impl.cc b/provisioning_sdk/internal/provisioning_engine_impl.cc index 07dfccf..e0fa2f8 100644 --- a/provisioning_sdk/internal/provisioning_engine_impl.cc +++ b/provisioning_sdk/internal/provisioning_engine_impl.cc @@ -10,6 +10,7 @@ #include #include + #include #include #include @@ -22,6 +23,7 @@ #include "common/crypto_util.h" #include "common/drm_root_certificate.h" #include "common/drm_service_certificate.h" +#include "common/hash_algorithm_util.h" #include "common/random_util.h" #include "common/rsa_key.h" #include "provisioning_sdk/internal/certificates/root_oem_certificates.h" @@ -38,7 +40,8 @@ const size_t kContextEncryptionIvSize(16); const size_t kContextMacKeySize(32); bool GenerateCertificate(DrmCertificate::Type type, uint32_t system_id, - const std::string& provider_id, const std::string& serial_number, + const std::string& provider_id, + const std::string& serial_number, const std::string& public_key, const RsaPrivateKey& signing_key, const SignedDrmCertificate& signer, @@ -63,8 +66,10 @@ bool GenerateCertificate(DrmCertificate::Type type, uint32_t system_id, LOG(WARNING) << "Error serializing DrmCertificate."; return false; } - if (!signing_key.GenerateSignature(signed_cert.drm_certificate(), - signed_cert.mutable_signature())) { + if (!signing_key.GenerateSignature( + signed_cert.drm_certificate(), + HashAlgorithmProtoToEnum(signed_cert.hash_algorithm()), + signed_cert.mutable_signature())) { LOG(WARNING) << "Failed to generate signature for DrmCertificate."; return false; } @@ -105,7 +110,8 @@ ProvisioningEngineImpl::ProvisioningEngineImpl() ProvisioningEngineImpl::~ProvisioningEngineImpl() {} ProvisioningStatus ProvisioningEngineImpl::Initialize( - CertificateType certificate_type, const std::string& drm_service_certificate, + CertificateType certificate_type, + const std::string& drm_service_certificate, const std::string& service_private_key, const std::string& service_private_key_passphrase, const std::string& provisioning_drm_certificate, @@ -173,7 +179,8 @@ ProvisioningStatus ProvisioningEngineImpl::Initialize( } ProvisioningStatus ProvisioningEngineImpl::SetCertificateStatusList( - const std::string& certificate_status_list, uint32_t expiration_period_seconds) { + const std::string& certificate_status_list, + uint32_t expiration_period_seconds) { if (certificate_status_list.empty()) { LOG(WARNING) << "Empty certificate_status_list."; return INVALID_STATUS_LIST; @@ -187,6 +194,7 @@ ProvisioningStatus ProvisioningEngineImpl::SetCertificateStatusList( if (!drm_root_public_key_->VerifySignature( signed_cert_status_list.certificate_status_list(), + HashAlgorithmProtoToEnum(signed_cert_status_list.hash_algorithm()), signed_cert_status_list.signature())) { LOG_WITH_PROTO("Signature verification failed", signed_cert_status_list); return INVALID_STATUS_LIST; @@ -236,7 +244,8 @@ ProvisioningStatus ProvisioningEngineImpl::SetCertificateStatusList( } ProvisioningStatus ProvisioningEngineImpl::GenerateDrmIntermediateCertificate( - uint32_t system_id, const std::string& public_key, std::string* certificate) const { + uint32_t system_id, const std::string& public_key, + std::string* certificate) const { auto intermediate_public_key = rsa_key_factory_->CreateFromPkcs1PublicKey(public_key); if (!intermediate_public_key) return INVALID_INTERMEDIATE_PUBLIC_KEY; @@ -247,8 +256,8 @@ ProvisioningStatus ProvisioningEngineImpl::GenerateDrmIntermediateCertificate( LOG(WARNING) << "Failed to generate serial_number."; return INTERNAL_ERROR; } - if (!GenerateCertificate(DrmCertificate::DEVICE_MODEL, system_id, std::string(), - serial_number, public_key, + if (!GenerateCertificate(DrmCertificate::DEVICE_MODEL, system_id, + std::string(), serial_number, public_key, *provisioning_private_key_, signed_provisioning_cert_, certificate)) { return INTERNAL_ERROR; @@ -318,7 +327,8 @@ ProvisioningStatus ProvisioningEngineImpl::GenerateDeviceDrmCertificate( ProvisioningStatus ProvisioningEngineImpl::GenerateProviderDeviceDrmCertificate( uint32_t system_id, const std::string& oem_ca_serial_number, const std::string& provider_id, const std::string& public_key, - const std::string& certificate_serial_number, std::string* certificate) const { + const std::string& certificate_serial_number, + std::string* certificate) const { // |oem_ca_serial_number| could be empty if it is called directly from // ProvisioningEngine::GenerateDeviceDrmCertificate. DCHECK(!certificate_serial_number.empty()); @@ -365,40 +375,67 @@ std::shared_ptr ProvisioningEngineImpl::GetDeviceInfo( } ProvisioningStatus ProvisioningEngineImpl::StoreContext( - const std::string& context_data, ProvisioningContext* context) const { - DCHECK(context); + const std::string& context_data, + SignedProvisioningContext* signed_context) const { + DCHECK(signed_context); ProvisioningContextKeyData key_data; if (!RandomBytes(kContextEncryptionKeySize, key_data.mutable_encryption_key()) || !RandomBytes(kContextEncryptionIvSize, - key_data.mutable_encryption_iv()) || - !RandomBytes(kContextMacKeySize, key_data.mutable_mac_key())) { + key_data.mutable_encryption_iv())) { LOG(ERROR) << "Failed to generate random context key data."; return INTERNAL_ERROR; } - context->set_context_data(crypto_util::EncryptAesCbc( + + const DrmServiceCertificate* service_cert = + DrmServiceCertificate::GetDefaultDrmServiceCertificateOrDie(); + + ProvisioningContext context; + context.set_context_data(crypto_util::EncryptAesCbc( key_data.encryption_key(), key_data.encryption_iv(), context_data)); - context->set_mac(crypto_util::CreateSignatureHmacSha256( - key_data.mac_key(), context->context_data())); - if (!DrmServiceCertificate::GetDefaultDrmServiceCertificateOrDie() - ->public_key() - ->Encrypt(key_data.SerializeAsString(), - context->mutable_key_data())) { + if (!service_cert->public_key()->Encrypt(key_data.SerializeAsString(), + context.mutable_key_data())) { LOG(WARNING) << "Context key data encryption failed"; return INTERNAL_ERROR; } + + signed_context->set_provisioning_context(context.SerializeAsString()); + if (!service_cert->private_key()->GenerateSignature( + signed_context->provisioning_context(), + HashAlgorithmProtoToEnum(signed_context->hash_algorithm()), + signed_context->mutable_signature())) { + LOG(WARNING) << "Failed to generate signature for ProvisioningContext."; + return INTERNAL_ERROR; + } return OK; } ProvisioningStatus ProvisioningEngineImpl::RetrieveContext( - const ProvisioningContext& context, std::string* context_data) const { + const SignedProvisioningContext& signed_context, + std::string* context_data) const { DCHECK(context_data); + const DrmServiceCertificate* service_cert = + DrmServiceCertificate::GetDefaultDrmServiceCertificateOrDie(); + + if (!service_cert->public_key()->VerifySignature( + signed_context.provisioning_context(), + HashAlgorithmProtoToEnum(signed_context.hash_algorithm()), + signed_context.signature())) { + LOG(WARNING) << "ProvisioningContext signature verification failed."; + return INVALID_CONTEXT; + } + + ProvisioningContext context; + if (!context.ParseFromString(signed_context.provisioning_context())) { + LOG(WARNING) << "Invalid context."; + return INVALID_CONTEXT; + } + std::string serialized_key_data; - if (!DrmServiceCertificate::GetDefaultDrmServiceCertificateOrDie() - ->private_key() - ->Decrypt(context.key_data(), &serialized_key_data)) { + if (!service_cert->private_key()->Decrypt(context.key_data(), + &serialized_key_data)) { LOG(WARNING) << "Could not decrypt context key data"; return INVALID_CONTEXT_KEY_DATA; } @@ -411,11 +448,6 @@ ProvisioningStatus ProvisioningEngineImpl::RetrieveContext( LOG(WARNING) << "Invalid context key data."; return INVALID_CONTEXT_KEY_DATA; } - if (!crypto_util::VerifySignatureHmacSha256(key_data.mac_key(), context.mac(), - context.context_data())) { - LOG(WARNING) << "Provisioning context MAC verification failed."; - return INVALID_CONTEXT; - } *context_data = crypto_util::DecryptAesCbc(key_data.encryption_key(), key_data.encryption_iv(), context.context_data()); diff --git a/provisioning_sdk/internal/provisioning_engine_impl.h b/provisioning_sdk/internal/provisioning_engine_impl.h index c3f30bb..5612717 100644 --- a/provisioning_sdk/internal/provisioning_engine_impl.h +++ b/provisioning_sdk/internal/provisioning_engine_impl.h @@ -12,13 +12,14 @@ #define PROVISIONING_SDK_INTERNAL_PROVISIONING_ENGINE_IMPL_H_ #include + #include #include #include #include #include -#include "base/thread_annotations.h" +#include "absl/base/thread_annotations.h" #include "absl/synchronization/mutex.h" #include "common/certificate_type.h" #include "common/drm_root_certificate.h" @@ -61,7 +62,8 @@ class ProvisioningEngineImpl { // derivation of Stable Per-Origin IDentifiers. // * Returns OK on success, or an appropriate error status code otherwise. ProvisioningStatus Initialize( - CertificateType certificate_type, const std::string& drm_service_certificate, + CertificateType certificate_type, + const std::string& drm_service_certificate, const std::string& service_private_key, const std::string& service_private_key_passphrase, const std::string& provisioning_drm_certificate, @@ -77,7 +79,8 @@ class ProvisioningEngineImpl { // (creation_time_seconds). Zero means it will never expire. // * Returns OK on success, or an appropriate error status code otherwise. ProvisioningStatus SetCertificateStatusList( - const std::string& certificate_status_list, uint32_t expiration_period_seconds); + const std::string& certificate_status_list, + uint32_t expiration_period_seconds); // Generate an intermediate DRM certificate. // * |system_id| is the Widevine system ID for the type of device. @@ -91,7 +94,8 @@ class ProvisioningEngineImpl { // engines, including this one, by invoking // |AddIntermediatedrmcertificate| on all active ProvisioningEngine(s). ProvisioningStatus GenerateDrmIntermediateCertificate( - uint32_t system_id, const std::string& public_key, std::string* certificate) const; + uint32_t system_id, const std::string& public_key, + std::string* certificate) const; // Add an intermediate DRM certificate to the provisioning engine. This is // usually done once for each supported device type. @@ -123,26 +127,30 @@ class ProvisioningEngineImpl { // Virtual for mocking. virtual ProvisioningStatus GenerateDeviceDrmCertificate( uint32_t system_id, const std::string& oem_ca_serial_number, - const std::string& public_key, const std::string& certificate_serial_number, + const std::string& public_key, + const std::string& certificate_serial_number, std::string* certificate) const; // Internal version of the method above. Allows specifying |provider_id|. virtual ProvisioningStatus GenerateProviderDeviceDrmCertificate( uint32_t system_id, const std::string& oem_ca_serial_number, const std::string& provider_id, const std::string& public_key, - const std::string& certificate_serial_number, std::string* certificate) const; + const std::string& certificate_serial_number, + std::string* certificate) const; // Get the device info for the given |system_id|. virtual std::shared_ptr GetDeviceInfo( uint32_t system_id) const; // Encrypt, store, and sign context/state data. - virtual ProvisioningStatus StoreContext(const std::string& context_data, - ProvisioningContext* context) const; + virtual ProvisioningStatus StoreContext( + const std::string& context_data, + SignedProvisioningContext* context) const; // Verify, decrypt, and retrieve context/state data. - virtual ProvisioningStatus RetrieveContext(const ProvisioningContext& context, - std::string* context_data) const; + virtual ProvisioningStatus RetrieveContext( + const SignedProvisioningContext& context, + std::string* context_data) const; const DrmRootCertificate* drm_root_certificate() const { return drm_root_certificate_.get(); @@ -154,6 +162,7 @@ class ProvisioningEngineImpl { friend class ProvisioningEngineImplTest; friend class ProvisioningEngineImplProvTest; friend class Sigma101ProvisioningSessionImplTest; + friend class Sigma210ProvisioningSessionImplTest; ProvisioningEngineImpl(const ProvisioningEngineImpl&) = delete; ProvisioningEngineImpl& operator=(const ProvisioningEngineImpl&) = delete; @@ -189,10 +198,11 @@ class ProvisioningEngineImpl { mutable absl::Mutex cert_status_mutex_; // POSIX time, in seconds, when the list would be expired. - uint32_t certificate_expiration_seconds_utc_ GUARDED_BY(cert_status_mutex_); + uint32_t certificate_expiration_seconds_utc_ + ABSL_GUARDED_BY(cert_status_mutex_); // Maps with system_id as the key. std::map certificate_status_map_ - GUARDED_BY(cert_status_mutex_); + ABSL_GUARDED_BY(cert_status_mutex_); struct IntermediateCertificateInfo { SignedDrmCertificate signed_drm_certificate; std::shared_ptr device_info; diff --git a/provisioning_sdk/internal/provisioning_engine_impl_test.cc b/provisioning_sdk/internal/provisioning_engine_impl_test.cc index 00eb510..c3e1b3f 100644 --- a/provisioning_sdk/internal/provisioning_engine_impl_test.cc +++ b/provisioning_sdk/internal/provisioning_engine_impl_test.cc @@ -8,7 +8,7 @@ #include "provisioning_sdk/internal/provisioning_engine_impl.h" -#include +#include #include #include "glog/logging.h" @@ -69,14 +69,14 @@ namespace widevine { class MockDrmRootCertificate : public DrmRootCertificate { public: MockDrmRootCertificate() - : DrmRootCertificate(kCertificateTypeTesting, std::string(), std::string(), - kDrmRootPublicKey, + : DrmRootCertificate(kCertificateTypeTesting, std::string(), + std::string(), kDrmRootPublicKey, std::unique_ptr()) {} - MOCK_CONST_METHOD3(VerifyCertificate, - Status(const std::string& serialized_cert, - SignedDrmCertificate* signed_drm_cert, - DrmCertificate* drm_cert)); + MOCK_METHOD(Status, VerifyCertificate, + (const std::string& serialized_cert, + SignedDrmCertificate* signed_drm_cert, DrmCertificate* drm_cert), + (const, override)); }; class ProvisioningEngineImplTest : public ::testing::Test { @@ -87,11 +87,11 @@ class ProvisioningEngineImplTest : public ::testing::Test { test_keys.private_test_key_2_2048_bits(), kServicePrivateKeyPassphrase, &service_private_key_); TestDrmCertificates test_certificates; - service_certificate_ = test_certificates.test_service_certificate(); + service_certificate_ = test_certificates.test_service_certificate_no_type(); } - ProvisioningStatus CheckDeviceStatus(uint32_t system_id, - const std::string& oem_ca_serial_number) { + ProvisioningStatus CheckDeviceStatus( + uint32_t system_id, const std::string& oem_ca_serial_number) { return engine_impl_.CheckDeviceStatus(system_id, oem_ca_serial_number); } @@ -177,7 +177,8 @@ class ProvisioningEngineImplProvTest : public ProvisioningEngineImplTest { ByMove(std::unique_ptr(mock_root_public_key_)))); } - ProvisioningStatus Initialize(const std::string& provisioning_drm_certificate) { + ProvisioningStatus Initialize( + const std::string& provisioning_drm_certificate) { return engine_impl_.Initialize( kCertificateTypeTesting, service_certificate_, service_private_key_, kServicePrivateKeyPassphrase, provisioning_drm_certificate, @@ -268,44 +269,33 @@ class ProvisioningEngineImplContextTest TEST_F(ProvisioningEngineImplContextTest, ContextStoreAndRetrieveSuccess) { const char kContextData[] = "I dislike tacky orange things"; - ProvisioningContext context; - ASSERT_EQ(OK, engine_impl_.StoreContext(kContextData, &context)); - EXPECT_NE(kContextData, context.context_data()); - EXPECT_FALSE(context.mac().empty()); + SignedProvisioningContext signed_context; + ASSERT_EQ(OK, engine_impl_.StoreContext(kContextData, &signed_context)); + EXPECT_FALSE(signed_context.signature().empty()); std::string context_data; - ASSERT_EQ(OK, engine_impl_.RetrieveContext(context, &context_data)); + ASSERT_EQ(OK, engine_impl_.RetrieveContext(signed_context, &context_data)); EXPECT_EQ(kContextData, context_data); } TEST_F(ProvisioningEngineImplContextTest, ContextStoreAndRetrieveFailBadData) { const char kContextData[] = "Climate change is not a hoax"; - ProvisioningContext context; - ASSERT_EQ(OK, engine_impl_.StoreContext(kContextData, &context)); - ++(*context.mutable_context_data())[5]; + SignedProvisioningContext signed_context; + ASSERT_EQ(OK, engine_impl_.StoreContext(kContextData, &signed_context)); + ++(*signed_context.mutable_provisioning_context())[5]; std::string context_data; ASSERT_EQ(INVALID_CONTEXT, - engine_impl_.RetrieveContext(context, &context_data)); -} - -TEST_F(ProvisioningEngineImplContextTest, ContextStoreAndRetrieveFailBadMac) { - const char kContextData[] = "No one wants coal anymore"; - ProvisioningContext context; - ASSERT_EQ(OK, engine_impl_.StoreContext(kContextData, &context)); - ++(*context.mutable_mac())[5]; - std::string context_data; - ASSERT_EQ(INVALID_CONTEXT, - engine_impl_.RetrieveContext(context, &context_data)); + engine_impl_.RetrieveContext(signed_context, &context_data)); } TEST_F(ProvisioningEngineImplContextTest, - ContextStoreAndRetrieveFailBadKeyData) { + ContextStoreAndRetrieveFailBadSignature) { const char kContextData[] = "No one wants coal anymore"; - ProvisioningContext context; - ASSERT_EQ(OK, engine_impl_.StoreContext(kContextData, &context)); - ++(*context.mutable_key_data())[5]; + SignedProvisioningContext signed_context; + ASSERT_EQ(OK, engine_impl_.StoreContext(kContextData, &signed_context)); + ++(*signed_context.mutable_signature())[5]; std::string context_data; - ASSERT_EQ(INVALID_CONTEXT_KEY_DATA, - engine_impl_.RetrieveContext(context, &context_data)); + ASSERT_EQ(INVALID_CONTEXT, + engine_impl_.RetrieveContext(signed_context, &context_data)); } class ProvisioningEngineImplGeneralTest @@ -365,7 +355,7 @@ class ProvisioningEngineImplGeneralTest cert_status_list_.SerializeAsString()); EXPECT_CALL(*mock_root_public_key_, VerifySignature( - StrEq(signed_cert_status_list.certificate_status_list()), + StrEq(signed_cert_status_list.certificate_status_list()), _, "cert_status_list_signature")) .WillOnce(Return(true)); signed_cert_status_list.set_signature("cert_status_list_signature"); @@ -395,7 +385,7 @@ TEST_F(ProvisioningEngineImplGeneralTest, signed_cert_status_list.set_certificate_status_list( cert_status_list_.SerializeAsString()); EXPECT_CALL(*mock_root_public_key_, - VerifySignature(_, "cert_status_list_signature")) + VerifySignature(_, _, "cert_status_list_signature")) .WillOnce(Return(false)); signed_cert_status_list.set_signature("cert_status_list_signature"); ASSERT_EQ(INVALID_STATUS_LIST, @@ -440,7 +430,7 @@ TEST_F(ProvisioningEngineImplGeneralTest, UpdateCertificateStatusList) { signed_cert_status_list.set_certificate_status_list( cert_status_list_.SerializeAsString()); EXPECT_CALL(*mock_root_public_key_, - VerifySignature(_, "cert_status_list_signature")) + VerifySignature(_, _, "cert_status_list_signature")) .WillOnce(Return(true)); signed_cert_status_list.set_signature("cert_status_list_signature"); ASSERT_EQ(OK, engine_impl_.SetCertificateStatusList( @@ -476,9 +466,9 @@ TEST_F(ProvisioningEngineImplGeneralTest, GenerateDrmIntermediateCertificate) { CreateFromPkcs1PublicKey(kIntermediatePublicKey)) .WillOnce( Return(ByMove(std::unique_ptr(new MockRsaPublicKey)))); - EXPECT_CALL(*mock_prov_private_key_, GenerateSignature(_, _)) + EXPECT_CALL(*mock_prov_private_key_, GenerateSignature(_, _, _)) .WillOnce(DoAll(SaveArg<0>(&drm_certificate), - SetArgPointee<1>(kSignature), Return(true))); + SetArgPointee<2>(kSignature), Return(true))); std::string certificate; ASSERT_EQ(OK, engine_impl_.GenerateDrmIntermediateCertificate( kSystemId, kIntermediatePublicKey, &certificate)); @@ -634,7 +624,7 @@ TEST_F(ProvisioningEngineImplGeneralTest, ExpiredCertificateStatusList) { signed_cert_status_list.set_certificate_status_list( cert_status_list_.SerializeAsString()); EXPECT_CALL(*mock_root_public_key_, - VerifySignature(_, "cert_status_list_signature")) + VerifySignature(_, _, "cert_status_list_signature")) .WillOnce(Return(true)); signed_cert_status_list.set_signature("cert_status_list_signature"); ASSERT_EQ(OK, engine_impl_.SetCertificateStatusList( @@ -706,9 +696,9 @@ TEST_F(ProvisioningEngineImplGeneralTest, GenerateDeviceDrmCertificate) { // Intermediate private key expectation. std::string drm_certificate; - EXPECT_CALL(*mock_intermediate_private_key, GenerateSignature(_, _)) + EXPECT_CALL(*mock_intermediate_private_key, GenerateSignature(_, _, _)) .WillOnce(DoAll(SaveArg<0>(&drm_certificate), - SetArgPointee<1>(kSignature), Return(true))); + SetArgPointee<2>(kSignature), Return(true))); std::string certificate; EXPECT_EQ(OK, engine_impl_.GenerateDeviceDrmCertificate( kSystemId, kOemSerialNumber0, kDevicePublicKey, diff --git a/provisioning_sdk/internal/provisioning_session_impl.cc b/provisioning_sdk/internal/provisioning_session_impl.cc index 5d870de..1d61e24 100644 --- a/provisioning_sdk/internal/provisioning_session_impl.cc +++ b/provisioning_sdk/internal/provisioning_session_impl.cc @@ -26,7 +26,8 @@ ProvisioningSessionImpl::ProvisioningSessionImpl( ProvisioningSessionImpl::~ProvisioningSessionImpl() {} ProvisioningStatus ProvisioningSessionImpl::Initialize( - const std::string& device_drm_public_key, const std::string& device_drm_private_key) { + const std::string& device_drm_public_key, + const std::string& device_drm_private_key) { auto rsa_public_key = rsa_key_factory_->CreateFromPkcs1PublicKey(device_drm_public_key); if (!rsa_public_key) return INVALID_DRM_DEVICE_PUBLIC_KEY; diff --git a/provisioning_sdk/public/BUILD b/provisioning_sdk/public/BUILD index e316059..dde8206 100644 --- a/provisioning_sdk/public/BUILD +++ b/provisioning_sdk/public/BUILD @@ -36,7 +36,7 @@ cc_library( deps = [ "//base", "//common:certificate_type", - "//protos/public:certificate_provisioning_proto", + "//protos/public:certificate_provisioning_cc_proto", ], ) @@ -56,8 +56,10 @@ cc_library( "//provisioning_sdk/internal:provisioning30_session_impl", "//provisioning_sdk/internal:provisioning_engine_impl", "//provisioning_sdk/internal:provisioning_session_impl", - "//protos/public:certificate_provisioning_proto", + "//protos/public:certificate_provisioning_cc_proto", ], + # Make sure libprovisioning_sdk links in symbols defined in this target. + alwayslink = 1, ) cc_test( @@ -79,7 +81,7 @@ cc_library( ":provisioning_status", "//base", "//provisioning_sdk/internal:provisioning_session_impl", - "//protos/public:drm_certificate_proto", + "//protos/public:drm_certificate_cc_proto", ], ) diff --git a/provisioning_sdk/public/provisioning_engine.cc b/provisioning_sdk/public/provisioning_engine.cc index 0def6bd..d66304d 100644 --- a/provisioning_sdk/public/provisioning_engine.cc +++ b/provisioning_sdk/public/provisioning_engine.cc @@ -27,7 +27,8 @@ ProvisioningEngine::ProvisioningEngine() {} ProvisioningEngine::~ProvisioningEngine() {} ProvisioningStatus ProvisioningEngine::Initialize( - CertificateType certificate_type, const std::string& service_drm_certificate, + CertificateType certificate_type, + const std::string& service_drm_certificate, const std::string& service_private_key, const std::string& service_private_key_passphrase, const std::string& provisioning_drm_certificate, @@ -64,20 +65,22 @@ ProvisioningStatus ProvisioningEngine::Initialize( } void ProvisioningEngine::RegisterProtocol( - SignedProvisioningMessage::ProtocolVersion protocol, + int protocol, SessionFactory session_factory) { protocol_registry_[protocol] = std::move(session_factory); } ProvisioningStatus ProvisioningEngine::SetCertificateStatusList( - const std::string& certificate_status_list, uint32_t expiration_period_seconds) { + const std::string& certificate_status_list, + uint32_t expiration_period_seconds) { if (!impl_) return PROVISIONING_ENGINE_UNINITIALIZED; return impl_->SetCertificateStatusList(certificate_status_list, expiration_period_seconds); } ProvisioningStatus ProvisioningEngine::GenerateDrmIntermediateCertificate( - uint32_t system_id, const std::string& public_key, std::string* certificate) const { + uint32_t system_id, const std::string& public_key, + std::string* certificate) const { if (!impl_) return PROVISIONING_ENGINE_UNINITIALIZED; if (!certificate) { LOG(WARNING) << "|certificate| should not be a nullptr."; @@ -97,7 +100,6 @@ ProvisioningStatus ProvisioningEngine::AddDrmIntermediateCertificate( } ProvisioningStatus ProvisioningEngine::NewProvisioningSession( - SignedProvisioningMessage::ProtocolVersion protocol, const std::string& device_public_key, const std::string& device_private_key, std::unique_ptr* new_session) const { if (!impl_) return PROVISIONING_ENGINE_UNINITIALIZED; @@ -106,6 +108,7 @@ ProvisioningStatus ProvisioningEngine::NewProvisioningSession( return INTERNAL_ERROR; } + const int protocol = 3; // Provisioning 3.0 auto factory = protocol_registry_.find(protocol); if (factory == protocol_registry_.end()) { LOG(WARNING) << "Provisioning protocol not supported (" << protocol << ")"; @@ -124,45 +127,17 @@ ProvisioningStatus ProvisioningEngine::NewProvisioningSession( } std::unique_ptr ProvisioningEngine::NewProvisioningSession( - SignedProvisioningMessage::ProtocolVersion protocol, const std::string& device_public_key, const std::string& device_private_key, ProvisioningStatus* status) const { std::unique_ptr new_session; - *status = NewProvisioningSession(protocol, device_public_key, - device_private_key, &new_session); + *status = NewProvisioningSession(device_public_key, + device_private_key, &new_session); return new_session; } -ProvisioningStatus ProvisioningEngine::NewKeyboxProvisioningSession( - const std::string& keybox_device_key, - std::unique_ptr* new_session) const { - if (!impl_) return PROVISIONING_ENGINE_UNINITIALIZED; - if (!new_session) { - LOG(WARNING) << "|new_session| should not be a nullptr."; - return INTERNAL_ERROR; - } - - SignedProvisioningMessage::ProtocolVersion protocol = - SignedProvisioningMessage::ARCPP_PROVISIONING; - auto factory = protocol_registry_.find(protocol); - if (factory == protocol_registry_.end()) { - LOG(WARNING) << "Provisioning protocol not supported (" << protocol << ")"; - return INVALID_PROTOCOL; - } - std::unique_ptr session_impl; - ProvisioningStatus status = (factory->second)(*impl_, &session_impl); - if (status != OK) return status; - - status = session_impl->Initialize(keybox_device_key); - if (status != OK) return status; - - new_session->reset(new ProvisioningSession(std::move(session_impl))); - return OK; -} - ProvisioningStatus ProvisioningEngine::GenerateDeviceDrmCertificate( - uint32_t system_id, const std::string& public_key, const std::string& serial_number, - std::string* certificate) const { + uint32_t system_id, const std::string& public_key, + const std::string& serial_number, std::string* certificate) const { if (!impl_) return PROVISIONING_ENGINE_UNINITIALIZED; if (!certificate) { LOG(WARNING) << "|certificate| should not be a nullptr."; diff --git a/provisioning_sdk/public/provisioning_engine.h b/provisioning_sdk/public/provisioning_engine.h index 34c9fa0..6f1444c 100644 --- a/provisioning_sdk/public/provisioning_engine.h +++ b/provisioning_sdk/public/provisioning_engine.h @@ -17,7 +17,6 @@ #include "common/certificate_type.h" #include "provisioning_sdk/public/provisioning_status.h" -#include "protos/public/certificate_provisioning.pb.h" namespace widevine { @@ -65,7 +64,8 @@ class ProvisioningEngine { // derivation of Stable Per-Origin IDentifiers. // * Returns OK on success, or an appropriate error status code otherwise. ProvisioningStatus Initialize( - CertificateType certificate_type, const std::string& service_drm_certificate, + CertificateType certificate_type, + const std::string& service_drm_certificate, const std::string& service_private_key, const std::string& service_private_key_passphrase, const std::string& provisioning_drm_certificate, @@ -78,7 +78,7 @@ class ProvisioningEngine { // SignedProvisioningMessage message. // * |session_factory| is the function which instantiates the appropriate // ProvisioningSessionImpl object for the specified protocol. - void RegisterProtocol(SignedProvisioningMessage::ProtocolVersion protocol, + void RegisterProtocol(int protocol, SessionFactory session_factory); // Set the certificate status list for this engine. @@ -89,7 +89,8 @@ class ProvisioningEngine { // (creation_time_seconds). Zero means it will never expire. // * Returns OK on success, or an appropriate error status code otherwise. virtual ProvisioningStatus SetCertificateStatusList( - const std::string& certificate_status_list, uint32_t expiration_period_seconds); + const std::string& certificate_status_list, + uint32_t expiration_period_seconds); // Generate an intermediate DRM certificate. // * |system_id| is the Widevine system ID for the type of device. @@ -103,7 +104,8 @@ class ProvisioningEngine { // engines, including this one, by invoking // |AddIntermediatedrmcertificate| on all active ProvisioningEngine(s). ProvisioningStatus GenerateDrmIntermediateCertificate( - uint32_t system_id, const std::string& public_key, std::string* certificate) const; + uint32_t system_id, const std::string& public_key, + std::string* certificate) const; // Add an intermediate DRM certificate to the provisioning engine. This is // usually done once for each supported device type. @@ -134,29 +136,15 @@ class ProvisioningEngine { // NOTE: All ProvisioningSession objects must be deleted before the // ProvisioningEngine which created them. virtual ProvisioningStatus NewProvisioningSession( - SignedProvisioningMessage::ProtocolVersion protocol, - const std::string& device_public_key, const std::string& device_private_key, + const std::string& device_public_key, + const std::string& device_private_key, std::unique_ptr* new_session) const; // This is the same as NewProvisioningSession above, but with outputs reversed // To get around CLIF bug https://github.com/google/clif/issues/30. std::unique_ptr NewProvisioningSession( - SignedProvisioningMessage::ProtocolVersion protocol, - const std::string& device_public_key, const std::string& device_private_key, - ProvisioningStatus* status) const; - - // Create a session to handle a keybox provisioning exchange between - // a client device (e.g., ChromeOS) and the provisioning server. - // It would use ARCPP_PROVISIONING protocol. - // * |keybox_device_key| is the secret device key in the keybox. - // * |new_session| will point, on successful return, to the newly created - // ProvisioningSession. - // * Returns OK if successful, or an appropriate error status code otherwise. - // NOTE: All ProvisioningSession objects must be deleted before the - // ProvisioningEngine which created them. - virtual ProvisioningStatus NewKeyboxProvisioningSession( - const std::string& keybox_device_key, - std::unique_ptr* new_session) const; + const std::string& device_public_key, + const std::string& device_private_key, ProvisioningStatus* status) const; // Generate a new device DRM certificate to be provisioned by means other than // the Widevine provisioning protocol. @@ -173,13 +161,12 @@ class ProvisioningEngine { // * |certificate| will contain, upon successful return the generated // certificate. // * Returns OK on success, or an appropriate error status code otherwise. - ProvisioningStatus GenerateDeviceDrmCertificate(uint32_t system_id, - const std::string& public_key, - const std::string& serial_number, - std::string* certificate) const; + ProvisioningStatus GenerateDeviceDrmCertificate( + uint32_t system_id, const std::string& public_key, + const std::string& serial_number, std::string* certificate) const; private: - std::map + std::map protocol_registry_; #ifndef SWIGPYTHON diff --git a/provisioning_sdk/public/provisioning_session.cc b/provisioning_sdk/public/provisioning_session.cc index 46ba78f..4e2c4eb 100644 --- a/provisioning_sdk/public/provisioning_session.cc +++ b/provisioning_sdk/public/provisioning_session.cc @@ -26,9 +26,8 @@ ProvisioningSession::ProvisioningSession() {} ProvisioningSession::~ProvisioningSession() {} -ProvisioningStatus ProvisioningSession::ProcessMessage(const std::string& message, - std::string* response, - bool* done) { +ProvisioningStatus ProvisioningSession::ProcessMessage( + const std::string& message, std::string* response, bool* done) { if (!response) { LOG(WARNING) << "|response| should not be a nullptr."; return INTERNAL_ERROR; diff --git a/provisioning_sdk/public/provisioning_session.h b/provisioning_sdk/public/provisioning_session.h index 6ea2190..1495925 100644 --- a/provisioning_sdk/public/provisioning_session.h +++ b/provisioning_sdk/public/provisioning_session.h @@ -32,8 +32,7 @@ class ProvisioningSession { // exchange is complete. // Returns OK if successful, or an appropriate error status code otherwise. virtual ProvisioningStatus ProcessMessage(const std::string& message, - std::string* response, - bool* done); + std::string* response, bool* done); // * Returns a ProvisioneddeviceInfo message containing information about the // type of device being provisioned. May return nullptr. diff --git a/provisioning_sdk/public/python/BUILD b/provisioning_sdk/public/python/BUILD index 1c56549..278e4dd 100644 --- a/provisioning_sdk/public/python/BUILD +++ b/provisioning_sdk/public/python/BUILD @@ -11,76 +11,7 @@ package(default_visibility = ["//visibility:public"]) filegroup( name = "binary_release_files", srcs = glob([ + "*.clif", "*.py", - "*.i", ]), ) - -py_library( - name = "test_data_utility", - srcs = [ - "test_data_provider.py", - "test_data_utility.py", - ], - data = [ - "//example:example_data", - ], - deps = [ - "//protos/public:certificate_provisioning_py_pb2", - ], -) - -py_library( - name = "crypto_utility", - srcs = ["crypto_utility.py"], -) - -py_test( - name = "init_engine_test", - size = "small", - srcs = ["init_engine_test.py"], - deps = [ - ":test_data_utility", - ], -) - -py_test( - name = "set_certificate_status_list_test", - size = "small", - srcs = ["set_certificate_status_list_test.py"], - deps = [ - ":test_data_utility", - ], -) - -py_test( - name = "drm_intermediate_certificate_test", - size = "small", - srcs = ["drm_intermediate_certificate_test.py"], - deps = [ - ":test_data_utility", - ], -) - -py_test( - name = "engine_generate_certificate_test", - size = "small", - srcs = ["engine_generate_certificate_test.py"], - deps = [ - ":crypto_utility", - ":test_data_utility", - "//protos/public:signed_drm_certificate_py_pb2", - ], -) - -py_test( - name = "new_session_test", - size = "small", - srcs = ["new_session_test.py"], - deps = [ - ":crypto_utility", - ":test_data_utility", - "//protos/public:certificate_provisioning_py_pb2", - "//protos/public:signed_drm_certificate_py_pb2", - ], -) diff --git a/provisioning_sdk/public/python/init_engine_test.py b/provisioning_sdk/public/python/init_engine_test.py index cb1e352..664ef47 100644 --- a/provisioning_sdk/public/python/init_engine_test.py +++ b/provisioning_sdk/public/python/init_engine_test.py @@ -13,7 +13,6 @@ import test_data_provider import test_data_utility from provisioning_engine import ProvisioningEngine from provisioning_status import ProvisioningStatus -from protos.public import certificate_provisioning_pb2 class InitEngineTest(unittest.TestCase): @@ -22,8 +21,6 @@ class InitEngineTest(unittest.TestCase): self._engine = ProvisioningEngine() self._data_provider = test_data_provider.TestDataProvider( CertificateType.kCertificateTypeTesting) - self._prov30 = ( - certificate_provisioning_pb2.SignedProvisioningMessage.PROVISIONING_30) def testInitEngineSucceed(self): status = test_data_utility.InitProvisionEngineWithTestData( @@ -57,7 +54,8 @@ class InitEngineTest(unittest.TestCase): def testNewProvisioningSessionWithoutInit(self): session, status = self._engine.NewProvisioningSession( - self._prov30, 'DEVICE_PUBLIC_KEY', 'DEVICE_PRIVATE_KEY') + 'DEVICE_PUBLIC_KEY', + 'DEVICE_PRIVATE_KEY') self.assertEqual(ProvisioningStatus.PROVISIONING_ENGINE_UNINITIALIZED, status) self.assertIsNone(session) diff --git a/provisioning_sdk/public/python/new_session_test.py b/provisioning_sdk/public/python/new_session_test.py index 4c6182d..62fde7c 100644 --- a/provisioning_sdk/public/python/new_session_test.py +++ b/provisioning_sdk/public/python/new_session_test.py @@ -28,8 +28,6 @@ class NewSessionTest(unittest.TestCase): self._engine, 0, verify_success=True) self._data_provider = test_data_provider.TestDataProvider( CertificateType.kCertificateTypeTesting) - self._prov30 = ( - certificate_provisioning_pb2.SignedProvisioningMessage.PROVISIONING_30) def testNewSessionSuccess(self): test_data_utility.AddDrmIntermediateCertificateWithTestData( @@ -77,7 +75,7 @@ class NewSessionTest(unittest.TestCase): test_data_utility.AddDrmIntermediateCertificateWithTestData( self._engine, 2001, verify_success=True) (_, session_status) = self._engine.NewProvisioningSession( - self._prov30, 'INVALID_PUBLIC_KEY', + 'INVALID_PUBLIC_KEY', self._data_provider.device_private_key) self.assertEqual(ProvisioningStatus.INVALID_DRM_DEVICE_PUBLIC_KEY, session_status) @@ -86,19 +84,11 @@ class NewSessionTest(unittest.TestCase): test_data_utility.AddDrmIntermediateCertificateWithTestData( self._engine, 2001, verify_success=True) (_, session_status) = self._engine.NewProvisioningSession( - self._prov30, self._data_provider.device_public_key, + self._data_provider.device_public_key, 'INVALID_PRIVATE_KEY') self.assertEqual(ProvisioningStatus.INVALID_DRM_DEVICE_PRIVATE_KEY, session_status) - def testNewSessionInvalidProtocol(self): - test_data_utility.AddDrmIntermediateCertificateWithTestData( - self._engine, 2001, verify_success=True) - (_, session_status) = self._engine.NewProvisioningSession( - 1234, self._data_provider.device_public_key, - self._data_provider.device_private_key) - self.assertEqual(ProvisioningStatus.INVALID_PROTOCOL, session_status) - def _VerifyMessageSignature(self, public_key, signed_response): crypto_utility.VerifySignature(public_key, signed_response.signature, signed_response.message) diff --git a/provisioning_sdk/public/python/provisioning_engine.clif b/provisioning_sdk/public/python/provisioning_engine.clif index 72339b5..23d0f58 100644 --- a/provisioning_sdk/public/python/provisioning_engine.clif +++ b/provisioning_sdk/public/python/provisioning_engine.clif @@ -9,7 +9,6 @@ from "common/python/certificate_type.h" import * from "provisioning_sdk/public/python/provisioning_status.h" import * from "provisioning_sdk/public/python/provisioning_session.h" import * -from "protos/public/certificate_provisioning_pyclif.h" import * from "provisioning_sdk/public/provisioning_engine.h": namespace `widevine`: @@ -35,7 +34,6 @@ from "provisioning_sdk/public/provisioning_engine.h": cert_private_key: bytes, cert_private_key_passhprase: bytes) -> ProvisioningStatus def NewProvisioningSession(self, - protocol: SignedProvisioningMessage.ProtocolVersion, device_public_key: bytes, device_private_key: bytes) -> (new_session: ProvisioningSession, status: ProvisioningStatus) diff --git a/provisioning_sdk/public/python/provisioning_engine_setup.py b/provisioning_sdk/public/python/provisioning_engine_setup.py index db4084e..e2b377c 100644 --- a/provisioning_sdk/public/python/provisioning_engine_setup.py +++ b/provisioning_sdk/public/python/provisioning_engine_setup.py @@ -26,8 +26,6 @@ if __name__ == '__main__': '%s/clif/python/runtime.cc' % common.CLIF_PREFIX, '%s/clif/python/slots.cc' % common.CLIF_PREFIX, '%s/clif/python/types.cc' % common.CLIF_PREFIX, - '%s/certificate_provisioning_pyclif.cc' % - common.WVPROTO_SRC_DIR, ], include_dirs=[ common.SDK_ROOT_DIR, common.GEN_DIR, common.CLIF_PREFIX, '/' diff --git a/provisioning_sdk/public/python/provisioning_status.clif b/provisioning_sdk/public/python/provisioning_status.clif index fea6cd9..f7b69f2 100644 --- a/provisioning_sdk/public/python/provisioning_status.clif +++ b/provisioning_sdk/public/python/provisioning_status.clif @@ -9,4 +9,3 @@ from "provisioning_sdk/public/provisioning_status.h": namespace `widevine`: enum ProvisioningStatus - def GetProvisioningStatusMessage(status: ProvisioningStatus) -> str diff --git a/provisioning_sdk/public/python/setup_common.py b/provisioning_sdk/public/python/setup_common.py index eddea82..a9b08e7 100644 --- a/provisioning_sdk/public/python/setup_common.py +++ b/provisioning_sdk/public/python/setup_common.py @@ -24,8 +24,12 @@ def _GetSdkRootDir(): SDK_ROOT_DIR = _GetSdkRootDir() GEN_DIR = '%s/%s' % (SDK_ROOT_DIR, GEN_DIRNAME) + SDK_LIBRARY_DIR = os.path.join(SDK_ROOT_DIR, 'bazel-bin', 'provisioning_sdk', 'public') +if not os.path.exists(SDK_LIBRARY_DIR): + SDK_LIBRARY_DIR = SDK_ROOT_DIR + CLIF_PREFIX = os.environ['CLIF_PREFIX'] BUILD_DIR = os.environ['PYEXT_BUILD_DIR'] diff --git a/provisioning_sdk/public/python/test_data_provider.py b/provisioning_sdk/public/python/test_data_provider.py index 385f531..4710c25 100644 --- a/provisioning_sdk/public/python/test_data_provider.py +++ b/provisioning_sdk/public/python/test_data_provider.py @@ -1,3 +1,4 @@ +# Lint as: python2, python3 ################################################################################ # Copyright 2017 Google LLC. # @@ -7,6 +8,10 @@ ################################################################################ """Class that provides test data for Provisioning SDK testing.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + import os from certificate_type import CertificateType @@ -34,11 +39,11 @@ class TestDataProvider(object): current_dir = os.path.dirname(current_dir) filename = os.path.join(current_dir, subfolder_path, filename) try: - with open(filename, 'r') as data_file: + with open(filename, 'rb') as data_file: data = data_file.read() return data except IOError: - print 'TestDataProvider: Failed to read \'%s\'' % filename + print('TestDataProvider: Failed to read \'%s\'' % filename) return None @property diff --git a/provisioning_sdk/public/python/test_data_utility.py b/provisioning_sdk/public/python/test_data_utility.py index 7656f7c..a6884d2 100644 --- a/provisioning_sdk/public/python/test_data_utility.py +++ b/provisioning_sdk/public/python/test_data_utility.py @@ -170,8 +170,8 @@ def NewProvisioningSessionWithTestData( 'sample device public and private keys.') data_provider = test_data_provider.TestDataProvider(cert_type) new_session, status = engine.NewProvisioningSession( - certificate_provisioning_pb2.SignedProvisioningMessage.PROVISIONING_30, - data_provider.device_public_key, data_provider.device_private_key) + data_provider.device_public_key, + data_provider.device_private_key) if verify_success: assert (ProvisioningStatus.OK == status), 'status = %r' % status diff --git a/run_tests.sh b/run_tests.sh index 9759b1e..5989b43 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -74,14 +74,13 @@ protoc -I="$SRC_DIR" --cpp_out="$GEN_DIR" "$SRC_DIR/protos/public/certificate_pr # Generate py outputs. protoc -I="$SRC_DIR" --python_out="$GEN_DIR" "$SRC_DIR/protos/public/client_identification.proto" +protoc -I="$SRC_DIR" --python_out="$GEN_DIR" "$SRC_DIR/protos/public/hash_algorithm.proto" protoc -I="$SRC_DIR" --python_out="$GEN_DIR" "$SRC_DIR/protos/public/remote_attestation.proto" protoc -I="$SRC_DIR" --python_out="$GEN_DIR" "$SRC_DIR/protos/public/provisioned_device_info.proto" protoc -I="$SRC_DIR" --python_out="$GEN_DIR" "$SRC_DIR/protos/public/certificate_provisioning.proto" protoc -I="$SRC_DIR" --python_out="$GEN_DIR" "$SRC_DIR/protos/public/signed_drm_certificate.proto" # Generate CLIF Python wrappers. -"$PYCLIF_PROTO" --source_dir="$SRC_DIR/protos/public" --ccdeps_out="$PROTO_GEN_DIR/certificate_provisioning_pyclif.cc" --header_out="$PROTO_GEN_DIR/certificate_provisioning_pyclif.h" certificate_provisioning.proto - "$PYCLIF" --modname certificate_type --ccdeps_out="$COMMON_GEN_DIR/certificate_type.cc" --ccinit_out="$COMMON_GEN_DIR/initcertificate_type.cc" --header_out="$COMMON_GEN_DIR/certificate_type.h" --cc_flags="-std=c++11 -I$SRC_DIR $PYTHON_INC $CLIF_INC" "$SRC_DIR/common/python/certificate_type.clif" "$PYCLIF" --modname provisioning_status --ccdeps_out="$SDK_PYTHON_GEN_DIR/provisioning_status.cc" --ccinit_out="$SDK_PYTHON_GEN_DIR/initprovisioning_status.cc" --header_out="$SDK_PYTHON_GEN_DIR/provisioning_status.h" --cc_flags="-std=c++11 -I$SRC_DIR $PYTHON_INC $CLIF_INC" "$SRC_DIR/provisioning_sdk/public/python/provisioning_status.clif" diff --git a/util/BUILD b/util/BUILD index c14212a..a7d19e9 100644 --- a/util/BUILD +++ b/util/BUILD @@ -27,7 +27,7 @@ cc_library( name = "proto_status", hdrs = ["proto_status.h"], deps = [ - "@protobuf_repo//:protobuf", + "@com_google_protobuf//:protobuf", "//util:error_space", ], ) diff --git a/util/endian/BUILD b/util/endian/BUILD index fbe70d4..0b0ad15 100644 --- a/util/endian/BUILD +++ b/util/endian/BUILD @@ -17,3 +17,13 @@ cc_library( "endian.h", ], ) + +cc_test( + name = "endian_test", + srcs = ["endian_test.cc"], + deps = [ + ":endian", + "//testing:gunit_main", + "@abseil_repo//absl/strings", + ], +) diff --git a/util/endian/endian.h b/util/endian/endian.h index df04c32..031c701 100644 --- a/util/endian/endian.h +++ b/util/endian/endian.h @@ -10,6 +10,7 @@ #define UTIL_ENDIAN_ENDIAN_H_ #include + #include @@ -19,7 +20,8 @@ namespace widevine { // order and big-endian byte order (same as network byte order) class BigEndian { public: - static uint32_t Load32(const char* data) { + static uint32_t Load32(const char* indata) { + const uint8_t* data = reinterpret_cast(indata); return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; } diff --git a/util/endian/endian_test.cc b/util/endian/endian_test.cc new file mode 100644 index 0000000..60ca0f8 --- /dev/null +++ b/util/endian/endian_test.cc @@ -0,0 +1,21 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 "util/endian/endian.h" + +#include "testing/gmock.h" +#include "testing/gunit.h" +#include "absl/strings/escaping.h" + +TEST(BigEndian, Load32) { + const char value[] = "000000c8"; + EXPECT_EQ(static_cast(0xC8), + widevine::BigEndian::Load32( + absl::HexStringToBytes(std::string(value)).data())); +} + +namespace widevine {} // namespace widevine diff --git a/util/error_space.h b/util/error_space.h index 2e66a39..ce63880 100644 --- a/util/error_space.h +++ b/util/error_space.h @@ -17,7 +17,9 @@ namespace util { class ErrorSpace { public: std::string SpaceName() const { return space_name_func_(this); } - std::string String(int code) const { return code_to_string_func_(this, code); } + std::string String(int code) const { + return code_to_string_func_(this, code); + } protected: // typedef instead of using statements for SWIG compatibility.