From 5387878a5b80e6572fb07cc3d74574589e95996c Mon Sep 17 00:00:00 2001 From: Googler Date: Tue, 13 May 2025 21:44:08 +0000 Subject: [PATCH] Automated update of OPK code Included changes: - 676ac7be8548d80c420591fc0b4fb9a11723ef34 Backwards compatibility script for CDM v18 and OPK v19 by Vicky Min - 3cd4f71fda91245ac0b61c4c847950952f3021c0 Change BuildInformation ree fields to optional by Matt Feddersen - a2259e95dea40c27a4be02ad479aec8f1fc84737 Created a DICE CBOR Cert parser/serializer. by Alex Dale - b8f2c364afeb6279e5aee6488d4527e189ac42ff Don't create invalid enum value by John "Juce" Bruce - b0aed212a3b2dd8f752d8fc43982848c1aa6c152 Created an HLS Key type. by Alex Dale - f8cfc54b41f124ba849596dbe6438b7f271a72b7 Specify C/C++ standard when running clang-tidy on OPK by John "Juce" Bruce GitOrigin-RevId: 676ac7be8548d80c420591fc0b4fb9a11723ef34 --- linux/src/log.cpp | 4 +- .../oem_certificate_test_helper.py | 159 ---- oemcrypto/include/OEMCryptoCENC.h | 4 +- oemcrypto/include/level3.h | 700 -------------- oemcrypto/include/level3_file_system.h | 32 - .../opk/oemcrypto_ta/oemcrypto_api_macros.h | 2 +- oemcrypto/opk/ports/linux/rules.mk | 5 +- .../opk/ports/linux/ta/common/opk_config.h | 2 +- oemcrypto/opk/ports/optee/host/rules.mk | 4 +- oemcrypto/opk/ports/optee/ta/rules.mk | 2 + .../trusty/ta/reference/clang-tidy-inc.mk | 4 +- .../ports/trusty/ta/reference/rustfmt.toml | 5 - .../wtpi_lib/impl/crypto/ref/rules.mk | 75 ++ .../wtpi_lib/impl/device_key/ref/rules.mk | 71 ++ .../wtpi_lib/impl/storage/ref/rules.mk | 95 ++ oemcrypto/test/GEN_api_lock_file.c | 8 - oemcrypto/test/oemcrypto_basic_test.cpp | 29 +- oemcrypto/util/build.gyp | 1 + oemcrypto/util/include/dice_cert_utils.h | 346 +++++++ oemcrypto/util/oec_ref_util_unittests.gypi | 1 + oemcrypto/util/src/dice_cert_utils.cpp | 421 +++++++++ .../util/test/dice_cert_utils_unittest.cpp | 561 ++++++++++++ util/include/hls_key.h | 210 +++++ util/src/hls_key.cpp | 469 ++++++++++ util/test/hls_key_unittest.cpp | 866 ++++++++++++++++++ 25 files changed, 3139 insertions(+), 937 deletions(-) delete mode 100644 oem_certificate_generator/oem_certificate_test_helper.py delete mode 100644 oemcrypto/include/level3.h delete mode 100644 oemcrypto/include/level3_file_system.h delete mode 100644 oemcrypto/opk/ports/trusty/ta/reference/rustfmt.toml create mode 100644 oemcrypto/opk/ports/trusty/ta/reference/wtpi_lib/impl/crypto/ref/rules.mk create mode 100644 oemcrypto/opk/ports/trusty/ta/reference/wtpi_lib/impl/device_key/ref/rules.mk create mode 100644 oemcrypto/opk/ports/trusty/ta/reference/wtpi_lib/impl/storage/ref/rules.mk create mode 100644 oemcrypto/util/include/dice_cert_utils.h create mode 100644 oemcrypto/util/src/dice_cert_utils.cpp create mode 100644 oemcrypto/util/test/dice_cert_utils_unittest.cpp create mode 100644 util/include/hls_key.h create mode 100644 util/src/hls_key.cpp create mode 100644 util/test/hls_key_unittest.cpp diff --git a/linux/src/log.cpp b/linux/src/log.cpp index 5b0a9e9..5faa65d 100644 --- a/linux/src/log.cpp +++ b/linux/src/log.cpp @@ -33,8 +33,8 @@ void InitLogging() { void Log(const char* file, const char* function, int line, LogPriority level, const char* fmt, ...) { const char* severities[] = {"ERROR", "WARN", "INFO", "DEBUG", "VERBOSE"}; - if (level < 0 || level >= static_cast(sizeof(severities) / - sizeof(severities[0]))) { + if (level < 0 || static_cast(level) >= + sizeof(severities) / sizeof(severities[0])) { fprintf(kOutputFile, "[FATAL:%s(%d):%s] Invalid log priority level: %d\n", file, line, function, level); return; diff --git a/oem_certificate_generator/oem_certificate_test_helper.py b/oem_certificate_generator/oem_certificate_test_helper.py deleted file mode 100644 index 75de4ae..0000000 --- a/oem_certificate_generator/oem_certificate_test_helper.py +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/python3 -# Copyright 2017 Google LLC. All Rights Reserved. - -"""Common test utility functions for OEM certificate generation.""" - -import datetime -import io - -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 oem_certificate - -_COUNTRY_NAME = 'US' -_STATE_OR_PROVINCE_NAME = 'WA' -_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 -_SYSTEM_ID = 2001 -_ROOT_PRIVATE_KEY_PASSPHRASE = b'root_passphrase' - - -class ArgParseObject(object): - """A convenient object to allow adding arbitrary attribute to it.""" - - -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()) - 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) - return (key, certificate) - - -def setup_csr_args(country_name=_COUNTRY_NAME, - state_or_province_name=_STATE_OR_PROVINCE_NAME, - locality_name=_LOCALITY_NAME, - organization_name=_ORGANIZATION_NAME, - organizational_unit_name=_ORGANIZATIONAL_UNIT_NAME, - key_size=4096, - output_csr_file=None, - output_private_key_file=None, - passphrase=None, - common_name=None): - """Sets up arguments to OEM Certificate generator for generating csr.""" - args = ArgParseObject() - args.key_size = key_size - args.country_name = country_name - args.state_or_province_name = state_or_province_name - args.locality_name = locality_name - args.organization_name = organization_name - args.organizational_unit_name = organizational_unit_name - args.common_name = common_name - if output_csr_file: - args.output_csr_file = output_csr_file - else: - args.output_csr_file = io.BytesIO() - if output_private_key_file: - args.output_private_key_file = output_private_key_file - else: - args.output_private_key_file = io.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, - root_private_key_passphrase=_ROOT_PRIVATE_KEY_PASSPHRASE, - output_certificate_file=None): - """Sets up args to OEM Cert generator for generating intermediate cert.""" - args = ArgParseObject() - args.not_valid_before = not_valid_before - args.valid_duration = valid_duration - args.system_id = system_id - args.csr_file = io.BytesIO(csr_bytes) - args.root_private_key_passphrase = root_private_key_passphrase - if output_certificate_file: - args.output_certificate_file = output_certificate_file - else: - args.output_certificate_file = io.BytesIO() - - serialized_private_key = root_key.private_bytes( - serialization.Encoding.DER, - format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.BestAvailableEncryption( - args.root_private_key_passphrase)) - serialized_certificate = root_certificate.public_bytes( - serialization.Encoding.DER) - args.root_certificate_file = io.BytesIO(serialized_certificate) - args.root_private_key_file = io.BytesIO(serialized_private_key) - return args - - -def setup_leaf_cert_args(intermediate_key_bytes, - intermediate_certificate_bytes, - key_size=1024, - passphrase=None, - not_valid_before=_NOT_VALID_BEFORE, - valid_duration=_LEAF_CERT_VALID_DURATION, - output_certificate_file=None, - output_private_key_file=None): - """Sets up args to OEM Certificate generator for generating leaf cert.""" - args = ArgParseObject() - args.key_size = key_size - args.not_valid_before = not_valid_before - args.valid_duration = valid_duration - args.intermediate_private_key_passphrase = None - if output_certificate_file: - args.output_certificate_file = output_certificate_file - else: - args.output_certificate_file = io.BytesIO() - if output_private_key_file: - args.output_private_key_file = output_private_key_file - else: - args.output_private_key_file = io.BytesIO() - args.passphrase = passphrase - - args.intermediate_private_key_file = io.BytesIO( - intermediate_key_bytes) - args.intermediate_certificate_file = io.BytesIO( - intermediate_certificate_bytes) - return args - - -def create_intermediate_certificate_and_key_bytes(key_size=4096, - passphrase=None, - pem_format=True): - """Creates an intermediate certificate and key.""" - csr_args = setup_csr_args(key_size=key_size, passphrase=passphrase) - oem_certificate.generate_csr(csr_args) - csr_bytes = csr_args.output_csr_file.getvalue() - - root_key, root_certificate = create_root_certificate_and_key() - args = setup_intermediate_cert_args(csr_bytes, root_key, root_certificate) - - oem_certificate.generate_intermediate_certificate(args) - - cert_bytes = args.output_certificate_file.getvalue() - if pem_format: - cert = x509.load_der_x509_certificate(cert_bytes, - backends.default_backend()) - cert_bytes = cert.public_bytes(serialization.Encoding.PEM) - - return (csr_args.output_private_key_file.getvalue(), cert_bytes) diff --git a/oemcrypto/include/OEMCryptoCENC.h b/oemcrypto/include/OEMCryptoCENC.h index fc4fc2e..2a4933d 100644 --- a/oemcrypto/include/OEMCryptoCENC.h +++ b/oemcrypto/include/OEMCryptoCENC.h @@ -3629,7 +3629,9 @@ uint32_t OEMCrypto_MinorAPIVersion(void); * defined * * While not required, another optional top level struct can be added to the - * build information string to provide information about liboemcrypto.so: + * build information string to provide information about liboemcrypto.so. The + * fields within this struct are not required, but if they are included they + * must match the listed data type: * - "ree" { * - "liboemcrypto_ver" [string]: liboemcrypto.so version in string format * eg "2.15.0+tag". Note that this is separate from the "ta_ver" field diff --git a/oemcrypto/include/level3.h b/oemcrypto/include/level3.h deleted file mode 100644 index 9be64ca..0000000 --- a/oemcrypto/include/level3.h +++ /dev/null @@ -1,700 +0,0 @@ -// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine -// License Agreement. - -/********************************************************************* - * level3.h - * - * Reference APIs needed to support Widevine's crypto algorithms. - *********************************************************************/ - -#ifndef LEVEL3_OEMCRYPTO_H_ -#define LEVEL3_OEMCRYPTO_H_ - -#include -#include - -#include "OEMCryptoCENC.h" -#include "level3_file_system.h" - -// clang-format off -#ifdef DYNAMIC_ADAPTER -#define Level3_IsInApp _lcc00 -#define Level3_Initialize _lcc01 -#define Level3_Terminate _lcc02 -#define Level3_InstallKeyboxOrOEMCert _lcc03 -#define Level3_GetKeyData _lcc04 -#define Level3_IsKeyboxOrOEMCertValid _lcc05 -#define Level3_GetRandom _lcc06 -#define Level3_GetDeviceID _lcc07 -#define Level3_WrapKeyboxOrOEMCert _lcc08 -#define Level3_OpenSession _lcc09 -#define Level3_CloseSession _lcc10 -#define Level3_GenerateSignature _lcc13 -#define Level3_GenerateNonce _lcc14 -#define Level3_RewrapDeviceRSAKey _lcc18 -#define Level3_LoadDeviceRSAKey _lcc19 -#define Level3_DeriveKeysFromSessionKey _lcc21 -#define Level3_APIVersion _lcc22 -#define Level3_Generic_Encrypt_V17 _lcc24 -#define Level3_Generic_Decrypt_V17 _lcc25 -#define Level3_Generic_Sign_V17 _lcc26 -#define Level3_Generic_Verify_V17 _lcc27 -#define Level3_SupportsUsageTable _lcc29 -#define Level3_ReportUsage _lcc32 -#define Level3_GetMaxNumberOfSessions _lcc37 -#define Level3_GetNumberOfOpenSessions _lcc38 -#define Level3_IsAntiRollbackHwPresent _lcc39 -#define Level3_QueryKeyControl _lcc41 -#define Level3_GetHDCPCapability _lcc44 -#define Level3_LoadTestRSAKey _lcc45 -#define Level3_SecurityPatchLevel _lcc46 -#define Level3_GetProvisioningMethod _lcc49 -#define Level3_RewrapDeviceRSAKey30 _lcc51 -#define Level3_SupportedCertificates _lcc52 -#define Level3_IsSRMUpdateSupported _lcc53 -#define Level3_GetCurrentSRMVersion _lcc54 -#define Level3_LoadSRM _lcc55 -#define Level3_RemoveSRM _lcc57 -#define Level3_CreateUsageTableHeader _lcc61 -#define Level3_LoadUsageTableHeader _lcc62 -#define Level3_CreateNewUsageEntry _lcc63 -#define Level3_LoadUsageEntry _lcc64 -#define Level3_UpdateUsageEntry _lcc65 -#define Level3_ShrinkUsageTableHeader _lcc67 -#define Level3_MoveEntry _lcc68 -#define Level3_GetAnalogOutputFlags _lcc71 -#define Level3_LoadTestKeybox _lcc78 -#define Level3_SelectKey _lcc81 -#define Level3_LoadKeys _lcc83 -#define Level3_SetSandbox _lcc84 -#define Level3_ResourceRatingTier _lcc85 -#define Level3_SupportsDecryptHash _lcc86 -#define Level3_SetDecryptHash_V18 _lcc88 -#define Level3_GetHashErrorCode _lcc89 -#define Level3_RefreshKeys _lcc91 -#define Level3_LoadEntitledContentKeys_V16 _lcc92 -#define Level3_CopyBuffer _lcc93 -#define Level3_MaximumUsageTableHeaderSize _lcc94 -#define Level3_GenerateDerivedKeys _lcc95 -#define Level3_PrepAndSignLicenseRequest _lcc96 -#define Level3_PrepAndSignRenewalRequest _lcc97 -#define Level3_PrepAndSignProvisioningRequest _lcc98 -#define Level3_LoadLicense_V18 _lcc99 -#define Level3_LoadRenewal _lcc101 -#define Level3_LoadProvisioning_V18 _lcc102 -#define Level3_LoadOEMPrivateKey _lcc103 -#define Level3_GetOEMPublicCertificate _lcc104 -#define Level3_DecryptCENC_V17 _lcc105 -#define Level3_LoadDRMPrivateKey _lcc107 -#define Level3_MinorAPIVersion _lcc108 -#define Level3_AllocateSecureBuffer _lcc109 -#define Level3_FreeSecureBuffer _lcc110 -#define Level3_CreateEntitledKeySession _lcc111 -#define Level3_RemoveEntitledKeySession _lcc112 -#define Level3_GetBootCertificateChain _lcc116 -#define Level3_GenerateCertificateKeyPair _lcc117 -#define Level3_InstallOemPrivateKey _lcc118 -#define Level3_ReassociateEntitledKeySession _lcc119 -#define Level3_LoadCasECMKeys _lcc120 -#define Level3_LoadEntitledContentKeys _lcc121 // place holder for v17. -#define Level3_ProductionReady _lcc122 -#define Level3_Idle _lcc123 -#define Level3_Wake _lcc124 -#define Level3_BuildInformation _lcc125 -#define Level3_SecurityLevel _lcc126 -#define Level3_ReuseUsageEntry _lcc127 -#define Level3_GetDTCP2Capability _lcc128 -#define Level3_GetWatermarkingSupport _lcc129 -#define Level3_GetOEMKeyToken _lcc130 -#define Level3_GetDeviceInformation _lcc131 -#define Level3_SetMaxAPIVersion _lcc132 -#define Level3_GetKeyHandle _lcc133 -#define Level3_DecryptCENC _lcc134 -#define Level3_Generic_Encrypt _lcc135 -#define Level3_Generic_Decrypt _lcc136 -#define Level3_Generic_Sign _lcc137 -#define Level3_Generic_Verify _lcc138 -#define Level3_GetSignatureHashAlgorithm _lcc139 -#define Level3_EnterTestMode _lcc140 -#define Level3_GetDeviceSignedCsrPayload _lcc141 -#define Level3_SetDecryptHash _lcc143 -#define Level3_LoadLicense _lcc144 -#define Level3_LoadProvisioning _lcc145 -#define Level3_LoadProvisioningCast _lcc146 -#define Level3_PrepAndSignReleaseRequest _lcc147 -#define Level3_GetUsageEntryInfo _lcc148 -#define Level3_GetBCCType _lcc149 -#define Level3_LoadRelease _lcc150 -#define Level3_GetEmbeddedDrmCertificate _lcc151 -#define Level3_UseSecondaryKey _lcc152 -#define Level3_MarkOfflineSession _lcc153 -#define Level3_SetSessionUsage _lcc155 -#define Level3_GetBCCSignatureType _lcc156 -#define Level3_GetPVRKey _lcc157 -#define Level3_LoadPVRKey _lcc158 -#define Level3_LoadLicenseData _lcc159 -#define Level3_SaveLicenseData _lcc160 -#else -#define Level3_Initialize _oecc01 -#define Level3_Terminate _oecc02 -#define Level3_InstallKeyboxOrOEMCert _oecc03 -#define Level3_GetKeyData _oecc04 -#define Level3_IsKeyboxOrOEMCertValid _oecc05 -#define Level3_GetRandom _oecc06 -#define Level3_GetDeviceID _oecc07 -#define Level3_WrapKeyboxOrOEMCert _oecc08 -#define Level3_OpenSession _oecc09 -#define Level3_CloseSession _oecc10 -#define Level3_GenerateSignature _oecc13 -#define Level3_GenerateNonce _oecc14 -#define Level3_RewrapDeviceRSAKey _oecc18 -#define Level3_LoadDeviceRSAKey _oecc19 -#define Level3_DeriveKeysFromSessionKey _oecc21 -#define Level3_APIVersion _oecc22 -#define Level3_Generic_Encrypt_V17 _oecc24 -#define Level3_Generic_Decrypt_V17 _oecc25 -#define Level3_Generic_Sign_V17 _oecc26 -#define Level3_Generic_Verify_V17 _oecc27 -#define Level3_SupportsUsageTable _oecc29 -#define Level3_ReportUsage _oecc32 -#define Level3_GenerateRSASignature _oecc36 -#define Level3_GetMaxNumberOfSessions _oecc37 -#define Level3_GetNumberOfOpenSessions _oecc38 -#define Level3_IsAntiRollbackHwPresent _oecc39 -#define Level3_QueryKeyControl _oecc41 -#define Level3_GetHDCPCapability _oecc44 -#define Level3_LoadTestRSAKey _oecc45 -#define Level3_SecurityPatchLevel _oecc46 -#define Level3_GetProvisioningMethod _oecc49 -#define Level3_RewrapDeviceRSAKey30 _oecc51 -#define Level3_SupportedCertificates _oecc52 -#define Level3_IsSRMUpdateSupported _oecc53 -#define Level3_GetCurrentSRMVersion _oecc54 -#define Level3_LoadSRM _oecc55 -#define Level3_RemoveSRM _oecc57 -#define Level3_CreateUsageTableHeader _oecc61 -#define Level3_LoadUsageTableHeader _oecc62 -#define Level3_CreateNewUsageEntry _oecc63 -#define Level3_LoadUsageEntry _oecc64 -#define Level3_UpdateUsageEntry _oecc65 -#define Level3_DeactivateUsageEntry _oecc66 -#define Level3_ShrinkUsageTableHeader _oecc67 -#define Level3_MoveEntry _oecc68 -#define Level3_GetAnalogOutputFlags _oecc71 -#define Level3_LoadTestKeybox _oecc78 -#define Level3_SelectKey _oecc81 -#define Level3_LoadKeys _oecc83 -#define Level3_SetSandbox _oecc84 -#define Level3_ResourceRatingTier _oecc85 -#define Level3_SupportsDecryptHash _oecc86 -#define Level3_SetDecryptHash_V18 _oecc88 -#define Level3_GetHashErrorCode _oecc89 -#define Level3_RefreshKeys _oecc91 -#define Level3_LoadEntitledContentKeys_V16 _oecc92 -#define Level3_CopyBuffer _oecc93 -#define Level3_MaximumUsageTableHeaderSize _oecc94 -#define Level3_GenerateDerivedKeys _oecc95 -#define Level3_PrepAndSignLicenseRequest _oecc96 -#define Level3_PrepAndSignRenewalRequest _oecc97 -#define Level3_PrepAndSignProvisioningRequest _oecc98 -#define Level3_LoadLicense_V18 _oecc99 -#define Level3_LoadRenewal _oecc101 -#define Level3_LoadProvisioning_V18 _oecc102 -#define Level3_LoadOEMPrivateKey _oecc103 -#define Level3_GetOEMPublicCertificate _oecc104 -#define Level3_DecryptCENC_V17 _oecc105 -#define Level3_LoadDRMPrivateKey _oecc107 -#define Level3_MinorAPIVersion _oecc108 -#define Level3_AllocateSecureBuffer _oecc109 -#define Level3_FreeSecureBuffer _oecc110 -#define Level3_CreateEntitledKeySession _oecc111 -#define Level3_RemoveEntitledKeySession _oecc112 -#define Level3_GetBootCertificateChain _oecc116 -#define Level3_GenerateCertificateKeyPair _oecc117 -#define Level3_InstallOemPrivateKey _oecc118 -#define Level3_ReassociateEntitledKeySession _oecc119 -#define Level3_LoadCasECMKeys _oecc120 -#define Level3_LoadEntitledContentKeys _oecc121 // place holder for v17. -#define Level3_ProductionReady _oecc122 -#define Level3_Idle _oecc123 -#define Level3_Wake _oecc124 -#define Level3_BuildInformation _oecc125 -#define Level3_SecurityLevel _oecc126 -#define Level3_ReuseUsageEntry _oecc127 -#define Level3_GetDTCP2Capability _oecc128 -#define Level3_GetWatermarkingSupport _oecc129 -#define Level3_GetOEMKeyToken _oecc130 -#define Level3_GetDeviceInformation _oecc131 -#define Level3_SetMaxAPIVersion _oecc132 -#define Level3_GetKeyHandle _oecc133 -#define Level3_DecryptCENC _oecc134 -#define Level3_Generic_Encrypt _oecc135 -#define Level3_Generic_Decrypt _oecc136 -#define Level3_Generic_Sign _oecc137 -#define Level3_Generic_Verify _oecc138 -#define Level3_GetSignatureHashAlgorithm _oecc139 -#define Level3_EnterTestMode _oecc140 -#define Level3_GetDeviceSignedCsrPayload _oecc141 -#define Level3_SetDecryptHash _oecc143 -#define Level3_LoadLicense _oecc144 -#define Level3_LoadProvisioning _oecc145 -#define Level3_LoadProvisioningCast _oecc146 -#define Level3_PrepAndSignReleaseRequest _oecc147 -#define Level3_GetUsageEntryInfo _oecc148 -#define Level3_GetBCCType _oecc149 -#define Level3_LoadRelease _oecc150 -// Internal-only. -#define Level3_GetEmbeddedDrmCertificate _oecc151 -#define Level3_UseSecondaryKey _oecc152 -#define Level3_MarkOfflineSession _oecc153 -#define Level3_SetSessionUsage _oecc155 -#define Level3_GetBCCSignatureType _oecc156 -#define Level3_GetPVRKey _oecc157 -#define Level3_LoadPVRKey _oecc158 -#define Level3_LoadLicenseData _oecc159 -#define Level3_SaveLicenseData _oecc160 -#endif - -#define Level3_GetInitializationState _oecl3o01 -// clang-format on - -extern "C" { - -bool Level3_IsInApp(); -OEMCryptoResult Level3_Initialize(void); -OEMCryptoResult Level3_Terminate(void); -OEMCryptoResult Level3_OpenSession(OEMCrypto_SESSION* session); -OEMCryptoResult Level3_CloseSession(OEMCrypto_SESSION session); -OEMCryptoResult Level3_GenerateDerivedKeys(OEMCrypto_SESSION session, - const uint8_t* mac_key_context, - size_t mac_key_context_length, - const uint8_t* enc_key_context, - size_t enc_key_context_length); -OEMCryptoResult Level3_GenerateNonce(OEMCrypto_SESSION session, - uint32_t* nonce); -OEMCryptoResult Level3_QueryKeyControl(OEMCrypto_SESSION session, - const uint8_t* key_id, - size_t key_id_length, - uint8_t* key_control_block, - size_t* key_control_block_length); -OEMCryptoResult Level3_DecryptCENC_V17( - OEMCrypto_SESSION session, const OEMCrypto_SampleDescription* samples, - size_t samples_length, const OEMCrypto_CENCEncryptPatternDesc* pattern); -OEMCryptoResult Level3_InstallKeyboxOrOEMCert(const uint8_t* rot, - size_t rotLength); -OEMCryptoResult Level3_IsKeyboxOrOEMCertValid(void); -OEMCryptoResult Level3_WrapKeyboxOrOEMCert(const uint8_t* rot, size_t rotLength, - uint8_t* wrappedRot, - size_t* wrappedRotLength, - const uint8_t* transportKey, - size_t transportKeyLength); -OEMCrypto_ProvisioningMethod Level3_GetProvisioningMethod(); -OEMCryptoResult Level3_GetOEMPublicCertificate(uint8_t* public_cert, - size_t* public_cert_length); -OEMCryptoResult Level3_GetDeviceID(uint8_t* deviceID, size_t* idLength); -OEMCryptoResult Level3_GetKeyData(uint8_t* keyData, size_t* keyDataLength); -OEMCryptoResult Level3_GetRandom(OEMCrypto_SharedMemory* randomData, - size_t randomDataLength); -OEMCryptoResult Level3_LoadOEMPrivateKey(OEMCrypto_SESSION session); -OEMCryptoResult Level3_LoadDRMPrivateKey(OEMCrypto_SESSION session, - OEMCrypto_PrivateKeyType key_type, - const uint8_t* wrapped_rsa_key, - size_t wrapped_rsa_key_length); -OEMCryptoResult Level3_LoadProvisioning_V18( - OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, - size_t core_message_length, const uint8_t* signature, - size_t signature_length, uint8_t* wrapped_private_key, - size_t* wrapped_private_key_length); -OEMCryptoResult Level3_RewrapDeviceRSAKey( - OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, - const uint8_t* signature, size_t signature_length, const uint32_t* nonce, - const uint8_t* enc_rsa_key, size_t enc_rsa_key_length, - const uint8_t* enc_rsa_key_iv, uint8_t* wrapped_rsa_key, - size_t* wrapped_rsa_key_length); -OEMCryptoResult Level3_LoadTestRSAKey(); -OEMCryptoResult Level3_GenerateRSASignature(OEMCrypto_SESSION session, - const uint8_t* message, - size_t message_length, - uint8_t* signature, - size_t* signature_length, - RSA_Padding_Scheme padding_scheme); -OEMCryptoResult Level3_DeriveKeysFromSessionKey(OEMCrypto_SESSION session, - const uint8_t* enc_session_key, - size_t enc_session_key_length, - const uint8_t* mac_key_context, - size_t mac_key_context_length, - const uint8_t* enc_key_context, - size_t enc_key_context_length); -uint32_t Level3_APIVersion(); -uint32_t Level3_MinorAPIVersion(); -uint8_t Level3_SecurityPatchLevel(); -OEMCrypto_Security_Level Level3_SecurityLevel(); -OEMCryptoResult Level3_GetHDCPCapability(OEMCrypto_HDCP_Capability* current, - OEMCrypto_HDCP_Capability* maximum); -bool Level3_SupportsUsageTable(); -bool Level3_IsAntiRollbackHwPresent(); -OEMCryptoResult Level3_GetNumberOfOpenSessions(size_t* count); -OEMCryptoResult Level3_GetMaxNumberOfSessions(size_t* maximum); -uint32_t Level3_SupportedCertificates(); -OEMCryptoResult Level3_Generic_Encrypt_V17( - OEMCrypto_SESSION session, const uint8_t* in_buffer, size_t buffer_length, - const uint8_t* iv, OEMCrypto_Algorithm algorithm, uint8_t* out_buffer); -OEMCryptoResult Level3_Generic_Decrypt_V17( - OEMCrypto_SESSION session, const uint8_t* in_buffer, size_t buffer_length, - const uint8_t* iv, OEMCrypto_Algorithm algorithm, uint8_t* out_buffer); -OEMCryptoResult Level3_Generic_Sign_V17(OEMCrypto_SESSION session, - const uint8_t* in_buffer, - size_t buffer_length, - OEMCrypto_Algorithm algorithm, - uint8_t* signature, - size_t* signature_length); -OEMCryptoResult Level3_Generic_Verify_V17(OEMCrypto_SESSION session, - const uint8_t* in_buffer, - size_t buffer_length, - OEMCrypto_Algorithm algorithm, - const uint8_t* signature, - size_t signature_length); -OEMCryptoResult Level3_DeactivateUsageEntry(OEMCrypto_SESSION session, - const uint8_t* pst, - size_t pst_length); -OEMCryptoResult Level3_ReportUsage(OEMCrypto_SESSION session, - const uint8_t* pst, size_t pst_length, - uint8_t* buffer, size_t* buffer_length); -OEMCryptoResult Level3_GetUsageEntryInfo( - OEMCrypto_SESSION session, OEMCrypto_UsageEntryStatus* status, - int64_t* seconds_since_license_received, - int64_t* seconds_since_first_decrypt); -bool Level3_IsSRMUpdateSupported(); -OEMCryptoResult Level3_GetCurrentSRMVersion(uint16_t* version); -OEMCryptoResult Level3_LoadSRM(const uint8_t* buffer, size_t buffer_length); -OEMCryptoResult Level3_RemoveSRM(); -OEMCryptoResult Level3_CreateUsageTableHeader(uint8_t* header_buffer, - size_t* header_buffer_length); -OEMCryptoResult Level3_LoadUsageTableHeader(const uint8_t* buffer, - size_t buffer_length); -OEMCryptoResult Level3_CreateNewUsageEntry(OEMCrypto_SESSION session, - uint32_t* usage_entry_number); -OEMCryptoResult Level3_LoadUsageEntry(OEMCrypto_SESSION session, uint32_t index, - const uint8_t* buffer, - size_t buffer_size); -OEMCryptoResult Level3_UpdateUsageEntry(OEMCrypto_SESSION session, - uint8_t* header_buffer, - size_t* header_buffer_length, - uint8_t* entry_buffer, - size_t* entry_buffer_length); -OEMCryptoResult Level3_ShrinkUsageTableHeader(uint32_t new_table_size, - uint8_t* header_buffer, - size_t* header_buffer_length); -OEMCryptoResult Level3_MoveEntry(OEMCrypto_SESSION session, uint32_t new_index); -uint32_t Level3_GetAnalogOutputFlags(); -OEMCryptoResult Level3_LoadTestKeybox(const uint8_t* buffer, size_t length); -OEMCryptoResult Level3_SelectKey(const OEMCrypto_SESSION session, - const uint8_t* key_id, size_t key_id_length, - OEMCryptoCipherMode cipher_mode); -OEMCryptoResult Level3_LoadLicense_V18(OEMCrypto_SESSION session, - const uint8_t* message, - size_t message_length, - size_t core_message_length, - const uint8_t* signature, - size_t signature_length); -OEMCryptoResult Level3_LoadKeys( - OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, - const uint8_t* signature, size_t signature_length, - OEMCrypto_Substring enc_mac_keys_iv, OEMCrypto_Substring enc_mac_keys, - size_t num_keys, const OEMCrypto_KeyObject* key_array, - OEMCrypto_Substring pst, OEMCrypto_Substring srm_restriction_data, - OEMCrypto_LicenseType license_type); -OEMCryptoResult Level3_SetSandbox(const uint8_t* sandbox_id, - size_t sandbox_id_length); -uint32_t Level3_ResourceRatingTier(); -uint32_t Level3_SupportsDecryptHash(); - -OEMCryptoResult Level3_SetDecryptHash(OEMCrypto_SESSION session, - uint32_t frame_number, uint32_t crc32); -OEMCryptoResult Level3_SetDecryptHash_V18(OEMCrypto_SESSION session, - uint32_t frame_number, - const uint8_t* hash, - size_t hash_length); -OEMCryptoResult Level3_GetHashErrorCode(OEMCrypto_SESSION session, - uint32_t* failed_frame_number); -OEMCryptoResult Level3_BuildInformation(char* buffer, size_t* buffer_length); -OEMCryptoResult Level3_LoadRenewal(OEMCrypto_SESSION session, - const uint8_t* message, - size_t message_length, - size_t core_message_length, - const uint8_t* signature, - size_t signature_length); -OEMCryptoResult Level3_LoadRelease(OEMCrypto_SESSION session, - const uint8_t* message, - size_t message_length, - size_t core_message_length, - const uint8_t* signature, - size_t signature_length); -OEMCryptoResult Level3_RefreshKeys(OEMCrypto_SESSION session, - const uint8_t* message, - size_t message_length, - const uint8_t* signature, - size_t signature_length, size_t num_keys, - const OEMCrypto_KeyRefreshObject* key_array); -OEMCryptoResult Level3_LoadEntitledContentKeys( - OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, - size_t num_keys, const OEMCrypto_EntitledContentKeyObject* key_array); -OEMCryptoResult Level3_CopyBuffer( - OEMCrypto_SESSION session, const uint8_t* data_addr, size_t data_length, - const OEMCrypto_DestBufferDesc* out_buffer_descriptor, - uint8_t subsample_flags); -OEMCryptoResult Level3_PrepAndSignProvisioningRequest( - OEMCrypto_SESSION session, uint8_t* message, size_t message_length, - size_t* core_message_length, uint8_t* signature, size_t* signature_length); -OEMCryptoResult Level3_PrepAndSignLicenseRequest( - OEMCrypto_SESSION session, uint8_t* message, size_t message_length, - size_t* core_message_length, uint8_t* signature, size_t* signature_length); -OEMCryptoResult Level3_PrepAndSignRenewalRequest( - OEMCrypto_SESSION session, uint8_t* message, size_t message_length, - size_t* core_message_length, uint8_t* signature, size_t* signature_length); -OEMCryptoResult Level3_PrepAndSignReleaseRequest( - OEMCrypto_SESSION session, uint8_t* message, size_t message_length, - size_t* core_message_size, uint8_t* signature, size_t* signature_length); -size_t Level3_MaximumUsageTableHeaderSize(); -OEMCryptoResult Level3_AllocateSecureBuffer( - OEMCrypto_SESSION session, size_t buffer_size, - OEMCrypto_DestBufferDesc* output_descriptor, int* secure_fd); -OEMCryptoResult Level3_FreeSecureBuffer( - OEMCrypto_SESSION session, OEMCrypto_DestBufferDesc* output_descriptor, - int secure_fd); -OEMCryptoResult Level3_CreateEntitledKeySession(OEMCrypto_SESSION oec_session, - OEMCrypto_SESSION* key_session); -OEMCryptoResult Level3_RemoveEntitledKeySession(OEMCrypto_SESSION key_session); -OEMCryptoResult Level3_GetBootCertificateChain( - uint8_t* bcc, size_t* bcc_size, uint8_t* additional_signature, - size_t* additional_signature_size); -OEMCryptoResult Level3_GenerateCertificateKeyPair( - OEMCrypto_SESSION session, uint8_t* public_key, size_t* public_key_size, - uint8_t* public_key_signature, size_t* public_key_signature_size, - uint8_t* wrapped_private_key, size_t* wrapped_private_key_size, - OEMCrypto_PrivateKeyType* key_type); -OEMCryptoResult Level3_InstallOemPrivateKey(OEMCrypto_SESSION session, - OEMCrypto_PrivateKeyType key_type, - const uint8_t* wrapped_private_key, - size_t wrapped_private_key_length); -OEMCryptoResult Level3_ReassociateEntitledKeySession( - OEMCrypto_SESSION key_session, OEMCrypto_SESSION oec_session); -OEMCryptoResult Level3_LoadCasECMKeys( - OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, - const OEMCrypto_EntitledContentKeyObject* even_key, - const OEMCrypto_EntitledContentKeyObject* odd_key); -OEMCryptoResult Level3_ProductionReady(); -OEMCryptoResult Level3_Idle(OEMCrypto_IdleState state, - uint32_t os_specific_code); -OEMCryptoResult Level3_Wake(); -OEMCryptoResult Level3_ReuseUsageEntry(OEMCrypto_SESSION session, - uint32_t usage_entry_number); -OEMCryptoResult Level3_GetDTCP2Capability( - OEMCrypto_DTCP2_Capability* capability); -OEMCrypto_WatermarkingSupport Level3_GetWatermarkingSupport(); -OEMCryptoResult Level3_GetOEMKeyToken(OEMCrypto_SESSION key_session, - uint8_t* key_token, - size_t* key_token_length); -OEMCryptoResult Level3_SetSessionUsage(OEMCrypto_SESSION session, - uint32_t intent, uint32_t mode); -OEMCryptoResult Level3_GetPVRKey(OEMCrypto_SESSION session, - uint8_t* wrapped_pvr_key, - size_t* wrapped_pvr_key_length); -OEMCryptoResult Level3_LoadPVRKey(OEMCrypto_SESSION session, - const uint8_t* wrapped_pvr_key, - size_t wrapped_pvr_key_length); -OEMCryptoResult Level3_GetDeviceInformation(uint8_t* device_info, - size_t* device_info_length); -OEMCryptoResult Level3_GetDeviceSignedCsrPayload( - const uint8_t* challenge, size_t challenge_length, - const uint8_t* encoded_device_info, size_t encoded_device_info_length, - uint8_t* signed_csr_payload, size_t* signed_csr_payload_length); -OEMCryptoResult Level3_SetMaxAPIVersion(uint32_t max_version); -OEMCryptoResult Level3_GetKeyHandle(OEMCrypto_SESSION session, - const uint8_t* content_key_id, - size_t content_key_id_length, - OEMCryptoCipherMode cipher_mode, - uint8_t* key_handle, - size_t* key_handle_length); -OEMCryptoResult Level3_DecryptCENC( - const uint8_t* key_handle, size_t key_handle_length, - const OEMCrypto_SampleDescription* samples, size_t samples_length, - const OEMCrypto_CENCEncryptPatternDesc* pattern); -OEMCryptoResult Level3_Generic_Encrypt(const uint8_t* key_handle, - size_t key_handle_length, - const OEMCrypto_SharedMemory* in_buffer, - size_t in_buffer_length, - const uint8_t* iv, - OEMCrypto_Algorithm algorithm, - OEMCrypto_SharedMemory* out_buffer); -OEMCryptoResult Level3_Generic_Decrypt(const uint8_t* key_handle, - size_t key_handle_length, - const OEMCrypto_SharedMemory* in_buffer, - size_t in_buffer_length, - const uint8_t* iv, - OEMCrypto_Algorithm algorithm, - OEMCrypto_SharedMemory* out_buffer); -OEMCryptoResult Level3_Generic_Sign(const uint8_t* key_handle, - size_t key_handle_length, - const OEMCrypto_SharedMemory* buffer, - size_t buffer_length, - OEMCrypto_Algorithm algorithm, - OEMCrypto_SharedMemory* signature, - size_t* signature_length); -OEMCryptoResult Level3_Generic_Verify(const uint8_t* key_handle, - size_t key_handle_length, - const OEMCrypto_SharedMemory* buffer, - size_t buffer_length, - OEMCrypto_Algorithm algorithm, - const OEMCrypto_SharedMemory* signature, - size_t signature_length); -OEMCryptoResult Level3_GetSignatureHashAlgorithm( - OEMCrypto_SESSION session, OEMCrypto_SignatureHashAlgorithm* algorithm); -OEMCryptoResult Level3_EnterTestMode(void); -OEMCryptoResult Level3_LoadLicense( - OEMCrypto_SESSION session, const uint8_t* context, size_t context_length, - const uint8_t* derivation_key, size_t derivation_key_length, - const uint8_t* message, size_t message_length, size_t core_message_length, - const uint8_t* signature, size_t signature_length); -OEMCryptoResult Level3_LoadProvisioning( - OEMCrypto_SESSION session, const uint8_t* provision_request, - size_t provision_request_length, const uint8_t* message, - size_t message_length, size_t core_message_length, const uint8_t* signature, - size_t signature_length, uint8_t* wrapped_private_key, - size_t* wrapped_private_key_length); -OEMCryptoResult Level3_LoadProvisioningCast( - OEMCrypto_SESSION session, const uint8_t* derivation_key, - size_t derivation_key_length, const uint8_t* provision_request, - size_t provision_request_length, const uint8_t* message, - size_t message_length, size_t core_message_length, const uint8_t* signature, - size_t signature_length, uint8_t* wrapped_private_key, - size_t* wrapped_private_key_length); -OEMCryptoResult Level3_GetBCCType(OEMCrypto_BCCType* bcc_type); -OEMCryptoResult Level3_GetEmbeddedDrmCertificate(uint8_t* public_cert, - size_t* public_cert_length); -OEMCryptoResult Level3_UseSecondaryKey(OEMCrypto_SESSION session_id, - bool dual_key); -OEMCryptoResult Level3_MarkOfflineSession(OEMCrypto_SESSION session_id); -OEMCryptoResult Level3_LoadLicenseData(OEMCrypto_SESSION session, - const uint8_t* data, size_t data_length); -OEMCryptoResult Level3_SaveLicenseData(OEMCrypto_SESSION session, uint8_t* data, - size_t* data_length); -OEMCryptoResult Level3_GetBCCSignatureType( - OEMCrypto_BCCSignatureType* bcc_signature_type); - -// The following are specific to Google's Level 3 implementation and are not -// required. - -enum Level3InitializationState { - LEVEL3_INITIALIZATION_SUCCESS = 0, - LEVEL3_INITIALIZATION_UNKNOWN_FAILURE = 1, - LEVEL3_SEED_FAILURE = 2, - LEVEL3_SAVE_DEVICE_KEYS_FAILURE = 3, - LEVEL3_READ_DEVICE_KEYS_FAILURE = 4, - LEVEL3_VERIFY_DEVICE_KEYS_FAILURE = 5, -}; - -enum Level3RunningMode { - LEVEL3_MODE_HAYSTACK_ONLY = 0, - LEVEL3_MODE_RIKERS_DEFAULT = 1, - LEVEL3_MODE_RIKERS_ONLY = 2, -}; - -/* - * Level3_GetRunningMode - * - * Description: - * Returns the current mode the Level3 is running in. This shouldn't change - * while the processes is running. - * - * Parameters: - * N/A - * - * Threading: - * No other function calls will be made while this function is running. - * - * Version: - * This method is new in API version 19. - */ -Level3RunningMode Level3_GetRunningMode(void); - -/* - * Level3_GetInitializationState - * - * Description: - * Return any warning or error condition which occurred during - * initialization. On some platforms, this value will be logged and metrics - * will be gathered on production devices. This is an optional feature, and - * OEMCrypto may always return 0, even if Level3_Initialize failed. This - * function may be called whether Level3_Initialize succeeded or not. - * - * Parameters: - * N/A - * - * Threading: - * No other function calls will be made while this function is running. - * - * Returns: - * LEVEL3_INITIALIZATION_SUCCESS - no warnings or errors during initialization - * LEVEL3_SEED_FAILURE - error in seeding the software RNG - * LEVEL3_SAVE_DEVICE_KEYS_FAILURE - failed to save device keys to file system - * LEVEL3_READ_DEVICE_KEYS_FAILURE - failed to read device keys from file - * system - * LEVEL3_VERIFY_DEVICE_KEYS_FAILURE - failed to verify decrypted device keys - * - * Version: - * This method is new in API version 14. - */ -Level3InitializationState Level3_GetInitializationState(void); - -/* - * Level3_OutputErrorLogs - * - * Description: - * Call to output any errors in the Level 3 execution if the Level 3 has - * failed. This method should only be called if the Level 3 has failed in - * an unrecoverable state, and needs to be reinitialized. - * - * Parameters: - * N/A - * - * Threading: - * No other function calls will be made while this function is running. - * - * Returns: - * N/A - * - * Version: - * This method is new in API version 15. - */ -void Level3_OutputErrorLogs(); - -} // extern "C" - -namespace wvoec3 { - -// The following are interfaces needed for Google's Level 3 OEMCrypto -// specifically, which partners are expected to implement. - -// Returns a stable, unique identifier for the device. This could be a -// serial number or any other character sequence representing that device. -// The parameter |len| needs to be changed to reflect the length of the -// unique identifier. -const char* getUniqueID(size_t* len); - -// Returns a 64-bit unsigned integer to be used as a random seed for RNG. -// If the operation is unsuccessful, this function returns 0. -// We provide a sample implementation under the name generate_entropy_linux.cpp -// which partners should use if they can. -uint64_t generate_entropy(); - -// Creates and returns an OEMCrypto_Level3FileSystem implementation. -OEMCrypto_Level3FileSystem* createLevel3FileSystem(); - -// Deletes the pointer retrieved by the function above. -void deleteLevel3FileSystem(OEMCrypto_Level3FileSystem* file_system); - -} // namespace wvoec3 - -#endif // LEVEL3_OEMCRYPTO_H_ diff --git a/oemcrypto/include/level3_file_system.h b/oemcrypto/include/level3_file_system.h deleted file mode 100644 index 8f8fbc3..0000000 --- a/oemcrypto/include/level3_file_system.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine -// License Agreement. - -/********************************************************************* - * level3_file_system.h - * - * File system for OEMCrypto Level3 file operations. - *********************************************************************/ - -#ifndef LEVEL3_FILE_SYSTEM_H_ -#define LEVEL3_FILE_SYSTEM_H_ - -#include -#include "platform.h" - -namespace wvoec3 { - -class OEMCrypto_Level3FileSystem { - public: - virtual ~OEMCrypto_Level3FileSystem() {} - virtual ssize_t Read(const char *filename, void *buffer, size_t size) = 0; - virtual ssize_t Write(const char *filename, const void *buffer, - size_t size) = 0; - virtual bool Exists(const char *filename) = 0; - virtual ssize_t FileSize(const char *filename) = 0; - virtual bool Remove(const char *filename) = 0; -}; - -} // namespace wvoec3 - -#endif diff --git a/oemcrypto/opk/oemcrypto_ta/oemcrypto_api_macros.h b/oemcrypto/opk/oemcrypto_ta/oemcrypto_api_macros.h index 597ed6d..de28fa6 100644 --- a/oemcrypto/opk/oemcrypto_ta/oemcrypto_api_macros.h +++ b/oemcrypto/opk/oemcrypto_ta/oemcrypto_api_macros.h @@ -36,6 +36,6 @@ #define API_MAJOR_VERSION 20 #define API_MINOR_VERSION 0 #define OPK_PATCH_VERSION 0 -#define OPK_BUILD_ID "MAIN" +#define OPK_BUILD_ID "2025-05-30" #endif /* OEMCRYPTO_TA_OEMCRYPTO_API_MACROS_H_ */ diff --git a/oemcrypto/opk/ports/linux/rules.mk b/oemcrypto/opk/ports/linux/rules.mk index e218fdc..4e263d4 100644 --- a/oemcrypto/opk/ports/linux/rules.mk +++ b/oemcrypto/opk/ports/linux/rules.mk @@ -52,6 +52,7 @@ cflags_c += \ cppflags += \ $(cflags) \ + -std=c++17 \ $(CPPFLAGS) # Filter out files and directories in third_party. @@ -105,7 +106,9 @@ define clang-tidy-rule .PHONY: clang-tidy-$(1) clang-tidy-$(1): @$(cmd-echo-silent) ' CLANG-TIDY $(1)' - ${q}clang-tidy $(clang-tidy-flags) $(1) -- $(cflags) + ${q}clang-tidy $(clang-tidy-flags) $(1) -- $(cflags) \ + $(if $(filter .c,$(suffix $(1))),-std=c11 -D_POSIX_C_SOURCE=200809L) \ + $(if $(filter .cpp,$(suffix $(1))),-std=c++17) endef define clang-tidy-rule-cpp diff --git a/oemcrypto/opk/ports/linux/ta/common/opk_config.h b/oemcrypto/opk/ports/linux/ta/common/opk_config.h index 7e78d44..91d1649 100644 --- a/oemcrypto/opk/ports/linux/ta/common/opk_config.h +++ b/oemcrypto/opk/ports/linux/ta/common/opk_config.h @@ -8,7 +8,7 @@ #define OPK_CONFIG_H_ #ifndef OPK_CONFIG_SECURITY_LEVEL -# define OPK_CONFIG_SECURITY_LEVEL OEMCrypto_Level3 +# define OPK_CONFIG_SECURITY_LEVEL OEMCrypto_Level1 #endif #ifndef OPK_CONFIG_RESOURCE_RATING_TIER diff --git a/oemcrypto/opk/ports/optee/host/rules.mk b/oemcrypto/opk/ports/optee/host/rules.mk index 80e0600..cff924a 100644 --- a/oemcrypto/opk/ports/optee/host/rules.mk +++ b/oemcrypto/opk/ports/optee/host/rules.mk @@ -106,7 +106,9 @@ define clang-tidy-rule .PHONY: clang-tidy-$(1) clang-tidy-$(1): @$(cmd-echo-silent) ' CLANG-TIDY $(1)' - ${q}clang-tidy $(clang-tidy-flags) $(1) -- $(cflags) + ${q}clang-tidy $(clang-tidy-flags) $(1) -- $(cflags) \ + $(if $(filter .c,$(suffix $(1))),-std=c11 -D_POSIX_C_SOURCE=200809L) \ + $(if $(filter .cpp,$(suffix $(1))),-std=c++17) endef define clang-tidy-rule-cpp diff --git a/oemcrypto/opk/ports/optee/ta/rules.mk b/oemcrypto/opk/ports/optee/ta/rules.mk index b5062c9..a3a0002 100644 --- a/oemcrypto/opk/ports/optee/ta/rules.mk +++ b/oemcrypto/opk/ports/optee/ta/rules.mk @@ -28,6 +28,8 @@ clang-tidy-flags = \ clang-tidy-cflags = \ -m$(ARCH) \ + $(if $(filter .c,$(suffix $(1))),-std=c11 -D_POSIX_C_SOURCE=200809L) \ + $(if $(filter .cpp,$(suffix $(1))),-std=c++17) \ $(comp-cppflags-$(call oname,$(1))) # Define a rule template to run clang-tidy with a single source file. diff --git a/oemcrypto/opk/ports/trusty/ta/reference/clang-tidy-inc.mk b/oemcrypto/opk/ports/trusty/ta/reference/clang-tidy-inc.mk index b4f22cb..f26cf9d 100644 --- a/oemcrypto/opk/ports/trusty/ta/reference/clang-tidy-inc.mk +++ b/oemcrypto/opk/ports/trusty/ta/reference/clang-tidy-inc.mk @@ -26,6 +26,8 @@ clang-tidy-flags := \ clang-tidy-cflags := \ $(MODULE_CFLAGS) \ + $(if $(filter .c,$(suffix $(1))),-std=c11 -D_POSIX_C_SOURCE=200809L) \ + $(if $(filter .cpp,$(suffix $(1))),-std=c++17) \ $(addprefix -D,$(MODULE_DEFINES) TRUSTY_USERSPACE) \ $(addprefix -I,\ $(MODULE_INCLUDES) \ @@ -37,7 +39,7 @@ clang-tidy-cflags := \ define clang-tidy-rule .PHONY: clang-tidy-$(1) clang-tidy-$(1): clang-tidy-flags := $(clang-tidy-flags) -clang-tidy-$(1): clang-tidy-cflags := $(clang-tidy-cflags) +clang-tidy-$(1): clang-tidy-cflags := $(call clang-tidy-cflags,$(1)) clang-tidy-$(1): @echo running clang-tidy: $(1) $(NOECHO)clang-tidy $(clang-tidy-flags) $(1) -- $(clang-tidy-cflags) diff --git a/oemcrypto/opk/ports/trusty/ta/reference/rustfmt.toml b/oemcrypto/opk/ports/trusty/ta/reference/rustfmt.toml deleted file mode 100644 index cefaa42..0000000 --- a/oemcrypto/opk/ports/trusty/ta/reference/rustfmt.toml +++ /dev/null @@ -1,5 +0,0 @@ -# Android Format Style - -edition = "2021" -use_small_heuristics = "Max" -newline_style = "Unix" diff --git a/oemcrypto/opk/ports/trusty/ta/reference/wtpi_lib/impl/crypto/ref/rules.mk b/oemcrypto/opk/ports/trusty/ta/reference/wtpi_lib/impl/crypto/ref/rules.mk new file mode 100644 index 0000000..f7d7734 --- /dev/null +++ b/oemcrypto/opk/ports/trusty/ta/reference/wtpi_lib/impl/crypto/ref/rules.mk @@ -0,0 +1,75 @@ +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_DIR := $(GET_LOCAL_DIR) + +MODULE := $(LOCAL_DIR) + +WV_TA_TOP_LEVEL := $(LOCAL_DIR)/../../../../ +SHARED_DIR := $(WV_TA_TOP_LEVEL)/shared +IMPL_DIR := $(WV_TA_TOP_LEVEL)/interface_impls + +CDM_DIR := trusty/vendor/widevine/cdm +APP_DIR := $(CDM_DIR)/oemcrypto/opk/oemcrypto_ta +SERIALIZATION_DIR := $(CDM_DIR)/oemcrypto/opk/serialization +ODK_DIR := $(CDM_DIR)/oemcrypto/odk + +ifndef WIDEVINE_PROVISION_METHOD +$(error WIDEVINE_PROVISION_METHOD is not set. \ + Please set it in the [target_name]-inc.mk file of your project) +endif + +MODULE_DEFINES += \ + WIDEVINE_PROVISION_METHOD=$(WIDEVINE_PROVISION_METHOD) \ + +# Default value if WIDEVINE_PROVISION_BCC_SIGNATURE_TYPE is not set by *-inc.mk. +WIDEVINE_PROVISION_BCC_SIGNATURE_TYPE ?= 0 +MODULE_DEFINES += \ + WIDEVINE_PROVISION_BCC_SIGNATURE_TYPE=$(WIDEVINE_PROVISION_BCC_SIGNATURE_TYPE) + +MODULE_DEFINES += \ + OPK_CONFIG_SOC_VENDOR_NAME=Google \ + OPK_CONFIG_SOC_MODEL_NAME=$(PLATFORM) \ + OPK_CONFIG_TEE_OS_NAME=Trusty \ + OPK_CONFIG_TEE_OS_VERSION=0.0 \ + OPK_CONFIG_DEVICE_FORM_FACTOR=phone+tablet \ + OPK_CONFIG_IMPLEMENTER_NAME=Widevine \ + WTPI_BUILD_INFO=TRUSTY \ + +# Widevine vendor code assumes trusty defines __linux__. We need to get this +# fixed upstream. TODO(b/232255239) +MODULE_CFLAGS += -D__linux__ + +MODULE_SRCS += \ + $(APP_DIR)/wtpi_reference/wtpi_decrypt_sample.c \ + +MODULE_INCLUDES += \ + $(LOCAL_DIR)/include \ + $(SHARED_DIR)/include \ + +MODULE_INCLUDES += \ + $(CDM_DIR)/oemcrypto/include \ + $(ODK_DIR)/include \ + $(CDM_DIR)/oemcrypto/odk/src \ + $(IMPL_DIR) \ + $(APP_DIR) \ + $(WV_TA_TOP_LEVEL)/include \ + $(APP_DIR)/wtpi \ + $(APP_DIR)/wtpi_reference \ + +MODULE_LIBRARY_DEPS += \ + trusty/user/base/lib/libc-trusty \ + +include make/library.mk diff --git a/oemcrypto/opk/ports/trusty/ta/reference/wtpi_lib/impl/device_key/ref/rules.mk b/oemcrypto/opk/ports/trusty/ta/reference/wtpi_lib/impl/device_key/ref/rules.mk new file mode 100644 index 0000000..cbe5024 --- /dev/null +++ b/oemcrypto/opk/ports/trusty/ta/reference/wtpi_lib/impl/device_key/ref/rules.mk @@ -0,0 +1,71 @@ +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_DIR := $(GET_LOCAL_DIR) + +MODULE := $(LOCAL_DIR) + +TOP_LEVEL := $(LOCAL_DIR)/../../../../ +SHARED_DIR := $(TOP_LEVEL)/shared +IMPL_DIR := $(TOP_LEVEL)/interface_impls + +CDM_DIR := trusty/vendor/widevine/cdm +APP_DIR := $(CDM_DIR)/oemcrypto/opk/oemcrypto_ta +SERIALIZATION_DIR := $(CDM_DIR)/oemcrypto/opk/serialization +ODK_DIR := $(CDM_DIR)/oemcrypto/odk + +ifndef WIDEVINE_PROVISION_METHOD +$(error WIDEVINE_PROVISION_METHOD is not set. \ + Please set it in the [target_name]-inc.mk file of your project) +endif + +MODULE_DEFINES += \ + WIDEVINE_PROVISION_METHOD=$(WIDEVINE_PROVISION_METHOD) \ + +# Default value if WIDEVINE_PROVISION_BCC_SIGNATURE_TYPE is not set by *-inc.mk. +WIDEVINE_PROVISION_BCC_SIGNATURE_TYPE ?= 0 +MODULE_DEFINES += \ + WIDEVINE_PROVISION_BCC_SIGNATURE_TYPE=$(WIDEVINE_PROVISION_BCC_SIGNATURE_TYPE) + +MODULE_DEFINES += \ + OPK_CONFIG_SOC_VENDOR_NAME=Google \ + OPK_CONFIG_SOC_MODEL_NAME=$(PLATFORM) \ + OPK_CONFIG_TEE_OS_NAME=Trusty \ + OPK_CONFIG_TEE_OS_VERSION=0.0 \ + OPK_CONFIG_DEVICE_FORM_FACTOR=phone+tablet \ + OPK_CONFIG_IMPLEMENTER_NAME=Widevine \ + WTPI_BUILD_INFO=TRUSTY \ + +MODULE_SRCS += \ + $(IMPL_DIR)/derive_key.c \ + +MODULE_INCLUDES += \ + $(LOCAL_DIR)/include \ + $(SHARED_DIR)/include \ + +MODULE_INCLUDES += \ + $(CDM_DIR)/oemcrypto/include \ + $(ODK_DIR)/include \ + $(CDM_DIR)/oemcrypto/odk/src \ + $(IMPL_DIR) \ + $(APP_DIR) \ + $(TOP_LEVEL)/include \ + $(APP_DIR)/wtpi \ + $(APP_DIR)/wtpi_reference \ + +MODULE_LIBRARY_DEPS += \ + trusty/user/base/lib/hwkey \ + +include make/library.mk diff --git a/oemcrypto/opk/ports/trusty/ta/reference/wtpi_lib/impl/storage/ref/rules.mk b/oemcrypto/opk/ports/trusty/ta/reference/wtpi_lib/impl/storage/ref/rules.mk new file mode 100644 index 0000000..df5fa14 --- /dev/null +++ b/oemcrypto/opk/ports/trusty/ta/reference/wtpi_lib/impl/storage/ref/rules.mk @@ -0,0 +1,95 @@ +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_DIR := $(GET_LOCAL_DIR) + +MODULE := $(LOCAL_DIR) + +WV_TA_TOP_LEVEL := $(LOCAL_DIR)/../../../../ +SHARED_DIR := $(WV_TA_TOP_LEVEL)/shared +IMPL_DIR := $(WV_TA_TOP_LEVEL)/interface_impls + +CDM_DIR := trusty/vendor/widevine/cdm +APP_DIR := $(CDM_DIR)/oemcrypto/opk/oemcrypto_ta +SERIALIZATION_DIR := $(CDM_DIR)/oemcrypto/opk/serialization +ODK_DIR := $(CDM_DIR)/oemcrypto/odk + +ifndef WIDEVINE_PROVISION_METHOD +$(error WIDEVINE_PROVISION_METHOD is not set. \ + Please set it in the [target_name]-inc.mk file of your project) +endif + +# oemcrypto.h will declare constants as C++-style statics. +# C sources do not like this, so change the behavior. +MODULE_CFLAGS += -DNO_OEMCRYPTO_VARIABLE_DEFINITIONS + +# Ensure API entry points are renamed in a predictable manner. +# TODO: can we remove this? +MODULE_CFLAGS += -DOEMCRYPTO_TA_TEST_ONLY + +# TODO: remove when the prototypes in oemcrypto.h are strict. +MODULE_CFLAGS += -Wno-strict-prototypes + +# TODO: remove when unused variable in dump_ssl_error() is fixed +MODULE_CFLAGS += -Wno-error=unused-but-set-variable + +MODULE_DEFINES += \ + WIDEVINE_PROVISION_METHOD=$(WIDEVINE_PROVISION_METHOD) \ + +# Default value if WIDEVINE_PROVISION_BCC_SIGNATURE_TYPE is not set by *-inc.mk. +WIDEVINE_PROVISION_BCC_SIGNATURE_TYPE ?= 0 +MODULE_DEFINES += \ + WIDEVINE_PROVISION_BCC_SIGNATURE_TYPE=$(WIDEVINE_PROVISION_BCC_SIGNATURE_TYPE) + +MODULE_DEFINES += \ + OPK_CONFIG_SOC_VENDOR_NAME=Google \ + OPK_CONFIG_SOC_MODEL_NAME=$(PLATFORM) \ + OPK_CONFIG_TEE_OS_NAME=Trusty \ + OPK_CONFIG_TEE_OS_VERSION=0.0 \ + OPK_CONFIG_DEVICE_FORM_FACTOR=phone+tablet \ + OPK_CONFIG_IMPLEMENTER_NAME=Widevine \ + WTPI_BUILD_INFO=TRUSTY \ + +# Widevine vendor code assumes trusty defines __linux__. We need to get this +# fixed upstream. TODO(b/232255239) +MODULE_CFLAGS += -D__linux__ + +# The base Trusty app. +MODULE_SRCS += \ + $(IMPL_DIR)/wtpi_persistent_storage_layer1.c \ + $(IMPL_DIR)/wtpi_initialize_terminate.c \ + $(IMPL_DIR)/wtpi_root_of_trust_layer2.c \ + $(WV_TA_TOP_LEVEL)/tee_context.c \ + +MODULE_INCLUDES += \ + $(WV_TA_TOP_LEVEL)/include \ + $(CDM_DIR)/oemcrypto/include \ + $(ODK_DIR)/include \ + $(SERIALIZATION_DIR)/common \ + $(SERIALIZATION_DIR)/common/include \ + $(CDM_DIR)/oemcrypto/odk/src \ + $(SERIALIZATION_DIR)/os_interfaces \ + $(IMPL_DIR) \ + +MODULE_INCLUDES += \ + $(APP_DIR) \ + $(APP_DIR)/wtpi \ + $(APP_DIR)/wtpi_reference \ + +MODULE_LIBRARY_DEPS += \ + trusty/user/base/lib/libc-trusty \ + trusty/user/base/lib/storage \ + +include make/library.mk diff --git a/oemcrypto/test/GEN_api_lock_file.c b/oemcrypto/test/GEN_api_lock_file.c index 91b18b7..eee336e 100644 --- a/oemcrypto/test/GEN_api_lock_file.c +++ b/oemcrypto/test/GEN_api_lock_file.c @@ -450,11 +450,3 @@ OEMCryptoResult _oecc157(OEMCrypto_SESSION session, uint8_t* wrapped_pvr_key, OEMCryptoResult _oecc158(OEMCrypto_SESSION session, const uint8_t* wrapped_pvr_key, size_t wrapped_pvr_key_length); - -// OEMCrypto_LoadLicenseData defined in v20.0 -OEMCryptoResult _oecc159(OEMCrypto_SESSION session, const uint8_t* data, - size_t data_length); - -// OEMCrypto_SaveLicenseData defined in v20.0 -OEMCryptoResult _oecc160(OEMCrypto_SESSION session, uint8_t* data, - size_t* data_length); diff --git a/oemcrypto/test/oemcrypto_basic_test.cpp b/oemcrypto/test/oemcrypto_basic_test.cpp index eaac56a..37e9454 100644 --- a/oemcrypto/test/oemcrypto_basic_test.cpp +++ b/oemcrypto/test/oemcrypto_basic_test.cpp @@ -693,7 +693,7 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) { // Whether this was built with FACTORY_MODE_ONLY defined {"is_factory_mode", JSMN_PRIMITIVE}, // ... provide information about liboemcrypto.so - // Special case, see kOptionalReeFields for details. + // Special case, see kReeOptionalFields for details. {kSpecialCaseReeKey, JSMN_OBJECT}, // Technically required, but several implementations // do not implement this fields. @@ -791,7 +791,7 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) { // The optional field "ree", if present, must follow the required // format. - const std::map kReeRequiredFields = { + const std::map kReeOptionalFields = { // liboemcrypto.so version in string format eg "2.15.0+tag" {"liboemcrypto_ver", JSMN_STRING}, // git hash of code that compiled liboemcrypto.so @@ -799,7 +799,6 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) { // ISO 8601 timestamp for when liboemcrypto.so was built {"build_timestamp", JSMN_STRING}}; - found_required_fields.clear(); for (int32_t i = 0; (i + 1) < static_cast(ree_tokens.size()); i += 2) { const jsmntok_t& key_token = ree_tokens[i]; @@ -809,11 +808,10 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) { const std::string key = build_info.substr(key_token.start, key_token.end - key_token.start); - if (kReeRequiredFields.find(key) != kReeRequiredFields.end()) { - ASSERT_EQ(value_token.type, kReeRequiredFields.at(key)) + if (kReeOptionalFields.find(key) != kReeOptionalFields.end()) { + ASSERT_EQ(value_token.type, kReeOptionalFields.at(key)) << "Unexpected optional REE field type: ree_field = " << key << ", build_info = " << build_info; - found_required_fields.insert(key); RecordWvProperty(kReeBuildInfoRecordPrefix + key, build_info.substr(value_token.start, value_token.end - value_token.start)); @@ -823,25 +821,6 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) { i += JsmnAncestorCount(ree_tokens, i + 1); } - // Step 4b: Ensure all required fields of the "ree" object were found. - if (found_required_fields.size() == kReeRequiredFields.size()) return; - // Generate a list of all the missing REE fields. - std::string missing_ree_fields; - for (const auto& required_field : kReeRequiredFields) { - if (found_required_fields.find(required_field.first) != - found_required_fields.end()) - continue; - if (!missing_ree_fields.empty()) { - missing_ree_fields.append(", "); - } - missing_ree_fields.push_back('"'); - missing_ree_fields.append(required_field.first); - missing_ree_fields.push_back('"'); - } - - FAIL() << "REE info JSON object does not contain all required keys; " - << "missing_ree_fields = [" << missing_ree_fields - << "], build_info = " << build_info; } TEST_F(OEMCryptoClientTest, CheckMaxNumberOfSessionsAPI10) { diff --git a/oemcrypto/util/build.gyp b/oemcrypto/util/build.gyp index 58fbfee..69cb088 100644 --- a/oemcrypto/util/build.gyp +++ b/oemcrypto/util/build.gyp @@ -39,6 +39,7 @@ 'sources': [ '<(wvcrypto_dir)/src/cmac.cpp', '<(wvcrypto_dir)/src/cose_utils.cpp', + '<(wvcrypto_dir)/src/dice_cert_utils.cpp', '<(wvcrypto_dir)/src/hmac.cpp', '<(wvcrypto_dir)/src/oemcrypto_cose_key.cpp', '<(wvcrypto_dir)/src/oemcrypto_drm_key.cpp', diff --git a/oemcrypto/util/include/dice_cert_utils.h b/oemcrypto/util/include/dice_cert_utils.h new file mode 100644 index 0000000..2f03bf3 --- /dev/null +++ b/oemcrypto/util/include/dice_cert_utils.h @@ -0,0 +1,346 @@ +// Copyright 2025 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Reference implementation utilities of OEMCrypto APIs +// +#ifndef WVOEC_UTIL_DICE_CERT_UTILS_H_ +#define WVOEC_UTIL_DICE_CERT_UTILS_H_ + +#include + +#include +#include +#include + +#include "wv_class_utils.h" + +namespace wvoec { +namespace util { + +// ==== DICE UDS/CDI CBOR Certificates ==== +// DICE UDS/CDI CBOR Certificates are based on CBOR Web Token (CWT). + +// For CWT, RFC 8392 +// Token issuer ("iss") => tstr +// Identifies the issuer of the CWT/Cert. +// For DICE, this is a lower-hex encoded UDS_ID/CDI_ID. +constexpr int64_t kDiceIssuerLabel = 1; +// Token subject ("sub") => tstr +// Identifies the subject of the CWT/Cert. +// For DICE, this is a lower-hex encoded CDI_ID. +constexpr int64_t kDiceSubjectLabel = 2; +// Token audience ("aud") => tstr +// Identifies the audience of the CWT/Cert. +// Not used for DICE. +constexpr int64_t kDiceAudienceLabel = 3; +// Expiration time ("exp") => int / float (CWT NumericDate) +// Time of expiry of certificate. +// For DICE, this is only available if a reliable time source is available. +constexpr int64_t kDiceExpirationTimeLabel = 4; +// Not before time ("nbf") => int / float (CWT NumericDate) +// Time after which the certificate may be used. +// For DICE, this is only available if a reliable time source is available. +constexpr int64_t kDiceNotBeforeLabel = 5; +// Issued at time ("iat") => int / float (CWT NumericDate) +// Time which the certificate was issued. +// For DICE, this is only available if a reliable time source is available. +constexpr int64_t kDiceIssuedAtLabel = 6; +// CWT ID ("cti") => bstr +// Used to identify the token claim. +// Not used for DICE. +constexpr int64_t kDiceCwtIdLabel = 7; + +// From Open Profile for DICE +// Code hash ("codeHash") => bstr +// Exact code input value used to compute CDI values. +// For Android/Widevine DICE, this is unpadded +// For Open DICE, this is always 64 bytes. +constexpr int64_t kDiceCodeHashLabel = -4670545; +// Code Descriptor ("codeDescriptor") => bstr +// Contains additional information about the code input value. +// Exact format is implementation specific. +constexpr int64_t kDiceCodeDescriptorLabel = -4670546; +// Config Input Hash ("configurationHash") => bstr +// If the configation input is hashed, this is that hash; if +// config input is not hashed, than this field is omitted. +// For Android/Widevine DICE, this is unpadded and required. +// For Open DICE, this is always 64 bytes. +constexpr int64_t kDiceConfigHashLabel = -4670547; +// Configuration Descriptor ("configurationDescriptor") => bstr +// If configuration input is hashed, this field contains the original +// configuration data that was hash, if not hash, this field contains +// the exact 64-byte configuration input value used to compute +// the CDI values. +// For Android/Widevine DICE, this is a CBOR encoded map. +constexpr int64_t kDiceConfigDescriptorLabel = -4670548; +// Authority Input Hash ("authorityHash") => bstr +// Exact authority input used to compute the CDI values. +// For Android/Widevine DICE, this is unpadded and required. +// For Open DICE, this is always 64 bytes. +constexpr int64_t kDiceAuthorityHashLabel = -4670549; +// Authority Descriptor ("authorityDescriptor") => bstr +// If configuration input is hashed, this field contains the original +// configuration data that was hash, if not hash, this field is +// omitted. +constexpr int64_t kDiceAuthorityDescriptorLabel = -4670550; +// DICE Mode ("mode") => bstr .size 1 +// DICE config mode. Encoded as a bstr of size 1; which represents +// one of the enumerated DICE modes (0-3). +constexpr int64_t kDiceModeLabel = -4670551; +// Subject Public Key ("subjectPublicKey") => bstr +// Contains the public key of this certificate's subject. Should +// be a CBOR encoded COSE_Key (public key only). +constexpr int64_t kDiceSubjectPublicKeyLabel = -4670552; +// Key usage ("keyUsage") => bstr +// Contains the key usage of the subject public key. Contains +// a little-endian encoded integer representing an X.509 KeyUsage +// bit field. Should only be set to "keyCertSign" bit. +constexpr int64_t kDiceKeyUsageLabel = -4670553; +// Profile Name ("profileName") => tstr +// This is the name of the DICE profile that defines the contents +// of this certificate. +// For Android DICE, required and format "android." +// For Widevine DICE, required and format "widevine." +// For Open DICE, optional. +constexpr int64_t kDiceProfileNameLabel = -4670554; + +constexpr uint8_t kDiceModeNotConfigured = 0; +constexpr uint8_t kDiceModeNormal = 1; +constexpr uint8_t kDiceModeDebug = 2; +constexpr uint8_t kDiceModeRecovery = 3; + +// Decoded form of the X.509 key usage bits. +// DICE only recognizes "keyCertSign". +// See RFC 5280 section 4.2.1.3 for details of defined bits. +using DiceKeyUsageFlags = uint16_t; +// Subject public key is used for certificate signature +// verification. +constexpr DiceKeyUsageFlags kKeyCertSignBit = (1 << 5); + +class DiceCertData { + public: + // Decodes the bstr format "mode" field into a raw DICE mode byte. + // Returns: + // - Raw DICE mode byte on success + // - nullopt if |mode_bstr| does not represent a valid bstr encoded + // mode. + static std::optional DecodeModeBytes( + const std::vector& mode_bstr); + // Encodes the raw DICE mode into the bstr format of "mode". + static std::vector EncodeModeBytes(uint8_t mode) { + return std::vector(1, mode); + } + + // Decodes the bstr format "keyUsage" field into a raw X.509 + // key usage bit field. + // Returns: + // - Raw X.506 bit field. + // - nullopt if |key_usage_bstr| cannot represent a valid bstr + // encoded key usage bit field. + static std::optional DecodeKeyUsageBytes( + const std::vector& key_usage_bstr); + static std::vector EncodeKeyUsageBytes( + const DiceKeyUsageFlags& key_usage); + + DiceCertData() = default; + WVCDM_DEFAULT_COPY_AND_MOVE(DiceCertData); + + // == CWT Fields == + + const std::optional& issuer() const { return issuer_; } + bool has_issuer() const { return issuer_.has_value(); } + void set_issuer(const std::string& issuer) { issuer_ = issuer; } + void clear_issuer() { issuer_.reset(); } + + const std::optional& subject() const { return subject_; } + bool has_subject() const { return subject_.has_value(); } + void set_subject(const std::string& subject) { subject_ = subject; } + void clear_subject() { subject_.reset(); } + + const std::optional& audience() const { return audience_; } + bool has_audience() const { return audience_.has_value(); } + void set_audience(const std::string& audience) { audience_ = audience; } + void clear_audience() { audience_.reset(); } + + const std::optional& expiration_time() const { + return expiration_time_; + } + bool has_expiration_time() const { return expiration_time_.has_value(); } + void set_expiration_time(int64_t time) { expiration_time_ = time; } + void clear_expiration_time() { expiration_time_.reset(); } + + const std::optional& not_before_time() const { + return not_before_time_; + } + bool has_not_before_time() const { return not_before_time_.has_value(); } + void set_not_before_time(int64_t time) { not_before_time_ = time; } + void clear_not_before_time() { not_before_time_.reset(); } + + const std::optional& issued_time() const { return issued_time_; } + bool has_issued_time() const { return issued_time_.has_value(); } + void set_issued_time(int64_t time) { issued_time_ = time; } + void clear_issued_time() { issued_time_.reset(); } + + const std::optional>& cwt_id() const { return cwt_id_; } + bool has_cwt_id() const { return cwt_id_.has_value(); } + void set_cwt_id(const std::vector& id) { cwt_id_ = id; } + void clear_cwt_id() { cwt_id_.reset(); } + + // == DICE CDI Fields == + + const std::optional>& code_hash() const { + return code_hash_; + } + bool has_code_hash() const { return code_hash_.has_value(); } + void set_code_hash(const std::vector& hash) { code_hash_ = hash; } + void clear_code_hash() { code_hash_.reset(); } + + const std::optional>& code_descriptor() const { + return code_descriptor_; + } + bool has_code_descriptor() const { return code_descriptor_.has_value(); } + void set_code_descriptor(const std::vector& descriptor) { + code_descriptor_ = descriptor; + } + void clear_code_descriptor() { code_descriptor_.reset(); } + + const std::optional>& config_hash() const { + return config_hash_; + } + bool has_config_hash() const { return config_hash_.has_value(); } + void set_config_hash(const std::vector& hash) { + config_hash_ = hash; + } + void clear_config_hash() { config_hash_.reset(); } + + const std::optional>& config_descriptor() const { + return config_descriptor_; + } + bool has_config_descriptor() const { return config_descriptor_.has_value(); } + void set_config_descriptor(const std::vector& descriptor) { + config_descriptor_ = descriptor; + } + void clear_config_descriptor() { config_descriptor_.reset(); } + + const std::optional>& authority_hash() const { + return authority_hash_; + } + bool has_authority_hash() const { return authority_hash_.has_value(); } + void set_authority_hash(const std::vector& hash) { + authority_hash_ = hash; + } + void clear_authority_hash() { authority_hash_.reset(); } + + const std::optional>& authority_descriptor() const { + return authority_descriptor_; + } + bool has_authority_descriptor() const { + return authority_descriptor_.has_value(); + } + void set_authority_descriptor(const std::vector& descriptor) { + authority_descriptor_ = descriptor; + } + void clear_authority_descriptor() { authority_descriptor_.reset(); } + + const std::optional& mode() const { return mode_; } + bool has_mode() const { return mode_.has_value(); } + void set_mode(uint8_t mode) { mode_ = mode; } + void clear_mode() { mode_.reset(); } + + const std::optional>& subject_public_key() const { + return subject_public_key_; + } + bool has_subject_public_key() const { + return subject_public_key_.has_value(); + } + void set_subject_public_key(const std::vector& key) { + subject_public_key_ = key; + } + void clear_subject_public_key() { subject_public_key_.reset(); } + + const std::optional& key_usage() const { + return key_usage_; + } + bool has_key_usage() const { return key_usage_.has_value(); } + void set_key_usage(DiceKeyUsageFlags key_usage) { key_usage_ = key_usage; } + void clear_key_usage() { key_usage_.reset(); } + + const std::optional& profile_name() const { + return profile_name_; + } + bool has_profile_name() const { return profile_name_.has_value(); } + void set_profile_name(const std::string& name) { profile_name_ = name; } + void clear_profile_name() { profile_name_.reset(); } + + void Clear() { + // CWT fields. + clear_issuer(); + clear_subject(); + clear_audience(); + clear_expiration_time(); + clear_not_before_time(); + clear_issued_time(); + clear_cwt_id(); + // DICE CDI fields. + clear_code_hash(); + clear_code_descriptor(); + clear_config_hash(); + clear_config_descriptor(); + clear_authority_hash(); + clear_authority_descriptor(); + clear_mode(); + clear_subject_public_key(); + clear_key_usage(); + clear_profile_name(); + } + + // Parses the CBOR CWT/DICE Cert. + // Input should be a CBOR serializes map. + // Limited validation: + // - All defined fields are treated as optional (even + // if standard says they are required) + // - Defined fields, if present, must be the expected type. + // - "mode", if present must be a bstr containing a single byte, + // raw value will be stored, even if out of range of defined + // modes. + // - "keyUsage", if present, must decode as a little-endian + // integer that can fit into a uint16_t + // - Additional fields are dropped + // Returns true if successfully parsed, false otherwise. + bool ParseCbor(const uint8_t* buffer, size_t buffer_size); + bool ParseCbor(const std::vector& buffer) { + return ParseCbor(buffer.data(), buffer.size()); + } + + // Serializes the DICE Cert. + // - Any set field will be encoded as the type defined. + // - "mode" will be encoded as a bstr containing a single byte + // - "keyUsage" will be encoded as a little-endian integer, + // using the 1 or 2 bytes (0 is encoded as 1 byte). + std::vector SerializeCbor() const; + + private: + // CWT fields. + std::optional issuer_ = std::nullopt; + std::optional subject_ = std::nullopt; + std::optional audience_ = std::nullopt; + std::optional expiration_time_ = std::nullopt; + std::optional not_before_time_ = std::nullopt; + std::optional issued_time_ = std::nullopt; + std::optional> cwt_id_ = std::nullopt; + // Open DICE fields. + std::optional> code_hash_ = std::nullopt; + std::optional> code_descriptor_ = std::nullopt; + std::optional> config_hash_ = std::nullopt; + std::optional> config_descriptor_ = std::nullopt; + std::optional> authority_hash_ = std::nullopt; + std::optional> authority_descriptor_ = std::nullopt; + std::optional mode_ = std::nullopt; + std::optional> subject_public_key_ = std::nullopt; + std::optional key_usage_ = std::nullopt; + std::optional profile_name_ = std::nullopt; +}; // class DiceCertData +} // namespace util +} // namespace wvoec +#endif // WVOEC_UTIL_DICE_CERT_UTILS_H_ diff --git a/oemcrypto/util/oec_ref_util_unittests.gypi b/oemcrypto/util/oec_ref_util_unittests.gypi index 67a5673..607d726 100644 --- a/oemcrypto/util/oec_ref_util_unittests.gypi +++ b/oemcrypto/util/oec_ref_util_unittests.gypi @@ -19,6 +19,7 @@ '<(oemcrypto_dir)/util/test/cmac_unittest.cpp', '<(oemcrypto_dir)/util/test/cose_utils_unittest.cpp', '<(oemcrypto_dir)/util/test/device_info_validator_unittest.cpp', + '<(oemcrypto_dir)/util/test/dice_cert_utils_unittest.cpp', '<(oemcrypto_dir)/util/test/hmac_unittest.cpp', '<(oemcrypto_dir)/util/test/oem_cert_test.cpp', '<(oemcrypto_dir)/util/test/oemcrypto_cose_key_unittest.cpp', diff --git a/oemcrypto/util/src/dice_cert_utils.cpp b/oemcrypto/util/src/dice_cert_utils.cpp new file mode 100644 index 0000000..a4e368c --- /dev/null +++ b/oemcrypto/util/src/dice_cert_utils.cpp @@ -0,0 +1,421 @@ +// Copyright 2025 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Reference implementation utilities of OEMCrypto APIs +// +#include "dice_cert_utils.h" + +#include + +#include +#include +#include + +#include +#include + +#include "log.h" +#include "string_conversions.h" + +namespace wvoec { +namespace util { +namespace { +// == cppbor Utils == + +bool IsItemMapType(const cppbor::Item& item) { + return item.type() == cppbor::MAP; +} + +bool IsItemIntType(const cppbor::Item& item) { + return item.type() == cppbor::UINT || item.type() == cppbor::NINT; +} + +bool IsItemBstrType(const cppbor::Item& item) { + return item.type() == cppbor::BSTR; +} + +bool IsItemTstrType(const cppbor::Item& item) { + return item.type() == cppbor::TSTR; +} + +const char* SimpleItemTypeToString(const cppbor::Simple& item) { + switch (item.simpleType()) { + case cppbor::BOOLEAN: + return "bool"; + case cppbor::NULL_T: + return "nil"; + // case cppbor::FLOAT: + // case cppbor::DOUBLE: + // return "float"; + // CDM's version of cppbor is out of date and does not support + // the simple types FLOAT and DOUBLE; however, Android's version does. + // Adding "default" to prevent compilation errors if/when CDM's cppbor + // is updated. + default: + break; + } + return "unknownSimple"; +} + +const char* ItemTypeToString(const cppbor::Item& item) { + switch (item.type()) { + case cppbor::UINT: + case cppbor::NINT: + return "int"; + case cppbor::BSTR: + return "bstr"; + case cppbor::TSTR: + return "tstr"; + case cppbor::ARRAY: + return "array"; + case cppbor::MAP: + return "map"; + case cppbor::SEMANTIC: + // Could be other things, but most common is tag. + return "tag"; + case cppbor::SIMPLE: + return SimpleItemTypeToString(*item.asSimple()); + } + return "unknown"; +} +} // namespace + +// static +std::optional DiceCertData::DecodeModeBytes( + const std::vector& mode_bstr) { + if (mode_bstr.size() != 1) { + LOGE("Invalid length of mode bstr: length = %zu, expected = 1", + mode_bstr.size()); + return std::nullopt; + } + return mode_bstr.front(); +} + +// static +std::optional DiceCertData::DecodeKeyUsageBytes( + const std::vector& key_usage_bstr) { + if (key_usage_bstr.empty() || key_usage_bstr.size() > 2) { + LOGE("Invalid length of key usage bstr: length = %zu, min = 1, max = 2", + key_usage_bstr.size()); + return std::nullopt; + } + // Decode as little-endian (reverse iterator). + DiceKeyUsageFlags key_usage = 0; + for (auto it = key_usage_bstr.rbegin(); it != key_usage_bstr.rend(); ++it) { + key_usage = (key_usage << 8) | static_cast(*it); + } + return key_usage; +} + +// static +std::vector DiceCertData::EncodeKeyUsageBytes( + const DiceKeyUsageFlags& key_usage) { + if (key_usage == 0) { + // Special case, always encode at least 1 byte. + return std::vector(1, 0); + } + std::vector key_usage_bstr; + key_usage_bstr.reserve(sizeof(DiceKeyUsageFlags)); + DiceKeyUsageFlags temp = key_usage; + // Encode as little-endian. + // Note: Must be encoded using the fewest number of bytes + // necessary with a minimum of 1. + while (temp > 0) { + key_usage_bstr.push_back(static_cast(temp & 0xff)); + temp >>= 8; + } + return key_usage_bstr; +} + +bool DiceCertData::ParseCbor(const uint8_t* buffer, size_t buffer_size) { + if (buffer == nullptr) { + LOGE("Input |buffer| is null"); + return false; + } + if (buffer_size == 0) { + LOGE("Input |buffer| is empty"); + return false; + } + Clear(); // Clear existing data. + + const auto [cert_item, end_pos, error_message] = + cppbor::parse(buffer, buffer_size); + if (!cert_item) { + LOGE("Failed to parse CBOR DICE Cert: %s", error_message.c_str()); + return false; + } + if (!IsItemMapType(*cert_item)) { + LOGE("CBOR item is not a map: type = %s", ItemTypeToString(*cert_item)); + return false; + } + const cppbor::Map* cert_map = cert_item->asMap(); + + // Issuer "iss" => tstr + const auto& issuer_item = cert_map->get(kDiceIssuerLabel); + if (issuer_item) { + if (!IsItemTstrType(*issuer_item)) { + LOGE("Expected 'iss' field to be a tstr: type = %s", + ItemTypeToString(*issuer_item)); + return false; + } + issuer_ = issuer_item->asTstr()->value(); + } + + // Subject "sub" => tstr + const auto& subject_item = cert_map->get(kDiceSubjectLabel); + if (subject_item) { + if (!IsItemTstrType(*subject_item)) { + LOGE("Expected 'sub' field to be a tstr: type = %s", + ItemTypeToString(*subject_item)); + return false; + } + subject_ = subject_item->asTstr()->value(); + } + + // Audience "aud" => tstr + const auto& audience_item = cert_map->get(kDiceAudienceLabel); + if (audience_item) { + if (!IsItemTstrType(*audience_item)) { + LOGE("Expected 'aud' field to be a tstr: type = %s", + ItemTypeToString(*audience_item)); + return false; + } + audience_ = audience_item->asTstr()->value(); + } + + // Note: Version of cppbor does not support float/double. + // Expiration time "exp" -> int / float + const auto& expiration_time_item = cert_map->get(kDiceExpirationTimeLabel); + if (expiration_time_item) { + if (!IsItemIntType(*expiration_time_item)) { + LOGE("Expected 'exp' field to be a int: type = %s", + ItemTypeToString(*expiration_time_item)); + return false; + } + expiration_time_ = expiration_time_item->asInt()->value(); + } + + // Not before time "nbf" -> int / float + const auto& not_before_time_item = cert_map->get(kDiceNotBeforeLabel); + if (not_before_time_item) { + if (!IsItemIntType(*not_before_time_item)) { + LOGE("Expected 'nbf' field to be a int: type = %s", + ItemTypeToString(*not_before_time_item)); + return false; + } + not_before_time_ = not_before_time_item->asInt()->value(); + } + + // Issued at time "iat" -> int / float + const auto& issued_time_item = cert_map->get(kDiceIssuedAtLabel); + if (issued_time_item) { + if (!IsItemIntType(*issued_time_item)) { + LOGE("Expected 'iat' field to be a int: type = %s", + ItemTypeToString(*issued_time_item)); + return false; + } + issued_time_ = issued_time_item->asInt()->value(); + } + + // CWT ID "cti" => bstr + const auto& cwt_id_item = cert_map->get(kDiceCwtIdLabel); + if (cwt_id_item) { + if (!IsItemBstrType(*cwt_id_item)) { + LOGE("Expected 'cti' field to be a bstr: type = %s", + ItemTypeToString(*cwt_id_item)); + return false; + } + cwt_id_ = cwt_id_item->asBstr()->value(); + } + + // Code Hash "codeHash" => bstr + const auto& code_hash_item = cert_map->get(kDiceCodeHashLabel); + if (code_hash_item) { + if (!IsItemBstrType(*code_hash_item)) { + LOGE("Expected 'codeHash' field to be a bstr: type = %s", + ItemTypeToString(*code_hash_item)); + return false; + } + code_hash_ = code_hash_item->asBstr()->value(); + } + + // Code descriptor "codeDescriptor" => bstr + const auto& code_descriptor_item = cert_map->get(kDiceCodeDescriptorLabel); + if (code_descriptor_item) { + if (!IsItemBstrType(*code_descriptor_item)) { + LOGE("Expected 'codeDescriptor' field to be a bstr: type = %s", + ItemTypeToString(*code_descriptor_item)); + return false; + } + code_descriptor_ = code_descriptor_item->asBstr()->value(); + } + + // Config Hash "configHash" => bstr + const auto& config_hash_item = cert_map->get(kDiceConfigHashLabel); + if (config_hash_item) { + if (!IsItemBstrType(*config_hash_item)) { + LOGE("Expected 'configHash' field to be a bstr: type = %s", + ItemTypeToString(*config_hash_item)); + return false; + } + config_hash_ = config_hash_item->asBstr()->value(); + } + + // Config descriptor "configDescriptor" => bstr + const auto& config_descriptor_item = + cert_map->get(kDiceConfigDescriptorLabel); + if (config_descriptor_item) { + if (!IsItemBstrType(*config_descriptor_item)) { + LOGE("Expected 'configDescriptor' field to be a bstr: type = %s", + ItemTypeToString(*config_descriptor_item)); + return false; + } + config_descriptor_ = config_descriptor_item->asBstr()->value(); + } + + // Authority Hash "configHash" => bstr + const auto& authority_hash_item = cert_map->get(kDiceAuthorityHashLabel); + if (authority_hash_item) { + if (!IsItemBstrType(*authority_hash_item)) { + LOGE("Expected 'authorityHash' field to be a bstr: type = %s", + ItemTypeToString(*authority_hash_item)); + return false; + } + authority_hash_ = authority_hash_item->asBstr()->value(); + } + + // Authority descriptor "authorityDescriptor" => bstr + const auto& authority_descriptor_item = + cert_map->get(kDiceAuthorityDescriptorLabel); + if (authority_descriptor_item) { + if (!IsItemBstrType(*authority_descriptor_item)) { + LOGE("Expected 'authorityDescriptor' field to be a bstr: type = %s", + ItemTypeToString(*authority_descriptor_item)); + return false; + } + authority_descriptor_ = authority_descriptor_item->asBstr()->value(); + } + + // Mode "mode" => bstr .size 1 + const auto& mode_item = cert_map->get(kDiceModeLabel); + if (mode_item) { + if (!IsItemBstrType(*mode_item)) { + LOGE("Expected 'mode' field to be a bstr: type = %s", + ItemTypeToString(*mode_item)); + return false; + } + const std::vector mode_bstr = mode_item->asBstr()->value(); + mode_ = DecodeModeBytes(mode_bstr); + if (!mode_.has_value()) { + LOGE("Invalid 'mode' format: length = %zu, mode_bstr = %s", + mode_bstr.size(), wvutil::b2a_hex(mode_bstr).c_str()); + return false; + } + } + + // Subject public key "subjectPublicKey" => bstr + const auto& subject_public_key_item = + cert_map->get(kDiceSubjectPublicKeyLabel); + if (subject_public_key_item) { + if (!IsItemBstrType(*subject_public_key_item)) { + LOGE("Expected 'subjectPublicKey' field to be a bstr: type = %s", + ItemTypeToString(*subject_public_key_item)); + return false; + } + subject_public_key_ = subject_public_key_item->asBstr()->value(); + } + + // Key usage "keyUsage" => bstr .size 1..2 + const auto& key_usage_item = cert_map->get(kDiceKeyUsageLabel); + if (key_usage_item) { + if (!IsItemBstrType(*key_usage_item)) { + LOGE("Expected 'keyUsage' field to be a bstr: type = %s", + ItemTypeToString(*key_usage_item)); + return false; + } + const std::vector key_usage_bstr = + key_usage_item->asBstr()->value(); + key_usage_ = DecodeKeyUsageBytes(key_usage_bstr); + if (!key_usage_.has_value()) { + LOGE("Invalid 'keyUsage' format: length = %zu, key_usage_bstr = %s", + key_usage_bstr.size(), wvutil::b2a_hex(key_usage_bstr).c_str()); + return false; + } + } + + // Profile name "profileName" => tstr + const auto& profile_name_item = cert_map->get(kDiceProfileNameLabel); + if (profile_name_item) { + if (!IsItemTstrType(*profile_name_item)) { + LOGE("Expected 'profileName' field to be a tstr: type = %s", + ItemTypeToString(*profile_name_item)); + return false; + } + profile_name_ = profile_name_item->asTstr()->value(); + } + return true; +} + +std::vector DiceCertData::SerializeCbor() const { + auto cert = cppbor::Map(); + if (issuer_.has_value()) { + cert.add(kDiceIssuerLabel, cppbor::Tstr(issuer_.value())); + } + if (subject_.has_value()) { + cert.add(kDiceSubjectLabel, cppbor::Tstr(subject_.value())); + } + if (audience_.has_value()) { + cert.add(kDiceAudienceLabel, cppbor::Tstr(audience_.value())); + } + if (expiration_time_.has_value()) { + cert.add(kDiceExpirationTimeLabel, expiration_time_.value()); + } + if (not_before_time_.has_value()) { + cert.add(kDiceNotBeforeLabel, not_before_time_.value()); + } + if (issued_time_.has_value()) { + cert.add(kDiceIssuedAtLabel, issued_time_.value()); + } + if (cwt_id_.has_value()) { + cert.add(kDiceCwtIdLabel, cppbor::Bstr(cwt_id_.value())); + } + if (code_hash_.has_value()) { + cert.add(kDiceCodeHashLabel, cppbor::Bstr(code_hash_.value())); + } + if (code_descriptor_.has_value()) { + cert.add(kDiceCodeDescriptorLabel, cppbor::Bstr(code_descriptor_.value())); + } + if (config_hash_.has_value()) { + cert.add(kDiceConfigHashLabel, cppbor::Bstr(config_hash_.value())); + } + if (config_descriptor_.has_value()) { + cert.add(kDiceConfigDescriptorLabel, + cppbor::Bstr(config_descriptor_.value())); + } + if (authority_hash_.has_value()) { + cert.add(kDiceAuthorityHashLabel, cppbor::Bstr(authority_hash_.value())); + } + if (authority_descriptor_.has_value()) { + cert.add(kDiceAuthorityDescriptorLabel, + cppbor::Bstr(authority_descriptor_.value())); + } + if (mode_.has_value()) { + cert.add(kDiceModeLabel, cppbor::Bstr(EncodeModeBytes(mode_.value()))); + } + if (subject_public_key_.has_value()) { + cert.add(kDiceSubjectPublicKeyLabel, + cppbor::Bstr(subject_public_key_.value())); + } + if (key_usage_.has_value()) { + cert.add(kDiceKeyUsageLabel, + cppbor::Bstr(EncodeKeyUsageBytes(key_usage_.value()))); + } + if (profile_name_.has_value()) { + cert.add(kDiceProfileNameLabel, cppbor::Tstr(profile_name_.value())); + } + // All CBOR DICE certs must be canonicalized. + return cert.canonicalize().encode(); +} +} // namespace util +} // namespace wvoec diff --git a/oemcrypto/util/test/dice_cert_utils_unittest.cpp b/oemcrypto/util/test/dice_cert_utils_unittest.cpp new file mode 100644 index 0000000..c547e77 --- /dev/null +++ b/oemcrypto/util/test/dice_cert_utils_unittest.cpp @@ -0,0 +1,561 @@ +// Copyright 2025 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Reference implementation utilities of OEMCrypto APIs +// +#include "dice_cert_utils.h" + +#include + +#include +#include +#include + +#include +#include + +#include "oemcrypto_ref_test_utils.h" + +namespace wvoec { +namespace util { + +TEST(DiceCertDataTest, Empty) { + const DiceCertData cert_data; + + EXPECT_FALSE(cert_data.has_issuer()); + EXPECT_FALSE(cert_data.has_subject()); + EXPECT_FALSE(cert_data.has_audience()); + EXPECT_FALSE(cert_data.has_expiration_time()); + EXPECT_FALSE(cert_data.has_not_before_time()); + EXPECT_FALSE(cert_data.has_issued_time()); + EXPECT_FALSE(cert_data.has_cwt_id()); + EXPECT_FALSE(cert_data.has_code_hash()); + EXPECT_FALSE(cert_data.has_code_descriptor()); + EXPECT_FALSE(cert_data.has_config_hash()); + EXPECT_FALSE(cert_data.has_config_descriptor()); + EXPECT_FALSE(cert_data.has_authority_hash()); + EXPECT_FALSE(cert_data.has_authority_descriptor()); + EXPECT_FALSE(cert_data.has_mode()); + EXPECT_FALSE(cert_data.has_subject_public_key()); + EXPECT_FALSE(cert_data.has_key_usage()); + EXPECT_FALSE(cert_data.has_profile_name()); + + EXPECT_EQ(cert_data.issuer(), std::nullopt); + EXPECT_EQ(cert_data.subject(), std::nullopt); + EXPECT_EQ(cert_data.audience(), std::nullopt); + EXPECT_EQ(cert_data.expiration_time(), std::nullopt); + EXPECT_EQ(cert_data.not_before_time(), std::nullopt); + EXPECT_EQ(cert_data.issued_time(), std::nullopt); + EXPECT_EQ(cert_data.cwt_id(), std::nullopt); + EXPECT_EQ(cert_data.code_hash(), std::nullopt); + EXPECT_EQ(cert_data.code_descriptor(), std::nullopt); + EXPECT_EQ(cert_data.config_hash(), std::nullopt); + EXPECT_EQ(cert_data.config_descriptor(), std::nullopt); + EXPECT_EQ(cert_data.authority_hash(), std::nullopt); + EXPECT_EQ(cert_data.authority_descriptor(), std::nullopt); + EXPECT_EQ(cert_data.mode(), std::nullopt); + EXPECT_EQ(cert_data.subject_public_key(), std::nullopt); + EXPECT_EQ(cert_data.key_usage(), std::nullopt); + EXPECT_EQ(cert_data.profile_name(), std::nullopt); +} + +TEST(DiceCertDataTest, Parse_IssuerOnly) { + const std::string kIssuer = "issuer"; + cppbor::Map cert_map; + cert_map.add(kDiceIssuerLabel, cppbor::Tstr(kIssuer)); + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + ASSERT_TRUE(cert_data.ParseCbor(cbor_cert)); + + EXPECT_TRUE(cert_data.has_issuer()); + EXPECT_FALSE(cert_data.has_subject()); + EXPECT_FALSE(cert_data.has_audience()); + + EXPECT_EQ(cert_data.issuer(), kIssuer); + EXPECT_EQ(cert_data.subject(), std::nullopt); + EXPECT_EQ(cert_data.audience(), std::nullopt); +} + +TEST(DiceCertDataTest, Parse_SubjectOnly) { + const std::string kSubject = "subject"; + cppbor::Map cert_map; + cert_map.add(kDiceSubjectLabel, cppbor::Tstr(kSubject)); + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + ASSERT_TRUE(cert_data.ParseCbor(cbor_cert)); + + EXPECT_FALSE(cert_data.has_issuer()); + EXPECT_TRUE(cert_data.has_subject()); + EXPECT_FALSE(cert_data.has_audience()); + + EXPECT_EQ(cert_data.issuer(), std::nullopt); + EXPECT_EQ(cert_data.subject(), kSubject); + EXPECT_EQ(cert_data.audience(), std::nullopt); +} + +TEST(DiceCertDataTest, Parse_AudienceOnly) { + const std::string kAudience = "audience"; + cppbor::Map cert_map; + cert_map.add(kDiceAudienceLabel, cppbor::Tstr(kAudience)); + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + ASSERT_TRUE(cert_data.ParseCbor(cbor_cert)); + + EXPECT_FALSE(cert_data.has_issuer()); + EXPECT_FALSE(cert_data.has_subject()); + EXPECT_TRUE(cert_data.has_audience()); + + EXPECT_EQ(cert_data.issuer(), std::nullopt); + EXPECT_EQ(cert_data.subject(), std::nullopt); + EXPECT_EQ(cert_data.audience(), kAudience); +} + +TEST(DiceCertDataTest, Parse_ExpirationTimeOnly) { + const int64_t kTime = 12345; + cppbor::Map cert_map; + cert_map.add(kDiceExpirationTimeLabel, kTime); + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + ASSERT_TRUE(cert_data.ParseCbor(cbor_cert)); + + EXPECT_TRUE(cert_data.has_expiration_time()); + EXPECT_FALSE(cert_data.has_not_before_time()); + EXPECT_FALSE(cert_data.has_issued_time()); + + EXPECT_EQ(cert_data.expiration_time(), kTime); + EXPECT_EQ(cert_data.not_before_time(), std::nullopt); + EXPECT_EQ(cert_data.issued_time(), std::nullopt); +} + +TEST(DiceCertDataTest, Parse_NotBeforeTimeOnly) { + const int64_t kTime = 12345; + cppbor::Map cert_map; + cert_map.add(kDiceNotBeforeLabel, kTime); + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + ASSERT_TRUE(cert_data.ParseCbor(cbor_cert)); + + EXPECT_FALSE(cert_data.has_expiration_time()); + EXPECT_TRUE(cert_data.has_not_before_time()); + EXPECT_FALSE(cert_data.has_issued_time()); + + EXPECT_EQ(cert_data.expiration_time(), std::nullopt); + EXPECT_EQ(cert_data.not_before_time(), kTime); + EXPECT_EQ(cert_data.issued_time(), std::nullopt); +} + +TEST(DiceCertDataTest, Parse_IssuedAtTimeOnly) { + const int64_t kTime = 12345; + cppbor::Map cert_map; + cert_map.add(kDiceIssuedAtLabel, kTime); + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + ASSERT_TRUE(cert_data.ParseCbor(cbor_cert)); + + EXPECT_FALSE(cert_data.has_expiration_time()); + EXPECT_FALSE(cert_data.has_not_before_time()); + EXPECT_TRUE(cert_data.has_issued_time()); + + EXPECT_EQ(cert_data.expiration_time(), std::nullopt); + EXPECT_EQ(cert_data.not_before_time(), std::nullopt); + EXPECT_EQ(cert_data.issued_time(), kTime); +} + +TEST(DiceCertDataTest, Parse_ModeOnly) { + const uint8_t kMode = kDiceModeDebug; + const std::vector kModeBytes = {kMode}; + + cppbor::Map cert_map; + cert_map.add(kDiceModeLabel, kModeBytes); + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + ASSERT_TRUE(cert_data.ParseCbor(cbor_cert)); + + EXPECT_TRUE(cert_data.has_mode()); + EXPECT_EQ(cert_data.mode(), kMode); +} + +TEST(DiceCertDataTest, Parse_KeyUsageOnly_Zero) { + const DiceKeyUsageFlags kKeyUsage = 0; + // little-endian + const std::vector kKeyUsageBytes = {0}; + + cppbor::Map cert_map; + cert_map.add(kDiceKeyUsageLabel, kKeyUsageBytes); + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + ASSERT_TRUE(cert_data.ParseCbor(cbor_cert)); + + EXPECT_TRUE(cert_data.has_key_usage()); + EXPECT_EQ(cert_data.key_usage(), kKeyUsage); +} + +TEST(DiceCertDataTest, Parse_KeyUsageOnly_SingleByte) { + const DiceKeyUsageFlags kKeyUsage = 0x40; + // little-endian + const std::vector kKeyUsageBytes = {0x40}; + + cppbor::Map cert_map; + cert_map.add(kDiceKeyUsageLabel, kKeyUsageBytes); + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + ASSERT_TRUE(cert_data.ParseCbor(cbor_cert)); + + EXPECT_TRUE(cert_data.has_key_usage()); + EXPECT_EQ(cert_data.key_usage(), kKeyUsage); +} + +TEST(DiceCertDataTest, Parse_KeyUsageOnly_TwoBytes) { + const DiceKeyUsageFlags kKeyUsage = 0x0245; + // little-endian + const std::vector kKeyUsageBytes = {0x45, 0x02}; + + cppbor::Map cert_map; + cert_map.add(kDiceKeyUsageLabel, kKeyUsageBytes); + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + ASSERT_TRUE(cert_data.ParseCbor(cbor_cert)); + + EXPECT_TRUE(cert_data.has_key_usage()); + EXPECT_EQ(cert_data.key_usage(), kKeyUsage); +} + +TEST(DiceCertDataTest, Parse_WidevineCert) { + const std::string kIssuer = "Issuer"; + const std::string kSubject = "Subject"; + const int64_t kExpiryTime = 3003016585; + const int64_t kNotBeforeTime = 1740798985; + const int64_t kIssuedAtTime = 1740712585; + + const std::vector kCodeHash = RandomData(64); + const std::vector kConfigHash = RandomData(64); + const std::vector kConfigDescriptor = RandomData(256); + const std::vector kAuthorityHash = RandomData(64); + const uint8_t kMode = kDiceModeNormal; + const std::vector kModeBytes = {kMode}; + const std::vector kSubjectPublicKey = RandomData(512); + const DiceKeyUsageFlags kKeyUsage = kKeyCertSignBit; + const std::vector kKeyUsageBytes = + DiceCertData::EncodeKeyUsageBytes(kKeyUsage); + const std::string kProfileName = "Widevine"; + + cppbor::Map cert_map; + cert_map.add(kDiceIssuerLabel, kIssuer); + cert_map.add(kDiceSubjectLabel, kSubject); + // No audience + cert_map.add(kDiceExpirationTimeLabel, kExpiryTime); + cert_map.add(kDiceNotBeforeLabel, kNotBeforeTime); + cert_map.add(kDiceIssuedAtLabel, kIssuedAtTime); + // No CWT ID. + cert_map.add(kDiceCodeHashLabel, kCodeHash); + // No code descriptor. + cert_map.add(kDiceConfigHashLabel, kConfigHash); + cert_map.add(kDiceConfigDescriptorLabel, kConfigDescriptor); + cert_map.add(kDiceAuthorityHashLabel, kAuthorityHash); + // No authority descriptor. + cert_map.add(kDiceModeLabel, kModeBytes); + cert_map.add(kDiceSubjectPublicKeyLabel, kSubjectPublicKey); + cert_map.add(kDiceKeyUsageLabel, kKeyUsageBytes); + cert_map.add(kDiceProfileNameLabel, kProfileName); + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + ASSERT_TRUE(cert_data.ParseCbor(cbor_cert)); + + EXPECT_TRUE(cert_data.has_issuer()); + EXPECT_TRUE(cert_data.has_subject()); + EXPECT_FALSE(cert_data.has_audience()); + EXPECT_TRUE(cert_data.has_expiration_time()); + EXPECT_TRUE(cert_data.has_not_before_time()); + EXPECT_TRUE(cert_data.has_issued_time()); + EXPECT_FALSE(cert_data.has_cwt_id()); + EXPECT_TRUE(cert_data.has_code_hash()); + EXPECT_FALSE(cert_data.has_code_descriptor()); + EXPECT_TRUE(cert_data.has_config_hash()); + EXPECT_TRUE(cert_data.has_config_descriptor()); + EXPECT_TRUE(cert_data.has_authority_hash()); + EXPECT_FALSE(cert_data.has_authority_descriptor()); + EXPECT_TRUE(cert_data.has_mode()); + EXPECT_TRUE(cert_data.has_subject_public_key()); + EXPECT_TRUE(cert_data.has_key_usage()); + EXPECT_TRUE(cert_data.has_profile_name()); + + EXPECT_EQ(cert_data.issuer(), kIssuer); + EXPECT_EQ(cert_data.subject(), kSubject); + EXPECT_EQ(cert_data.audience(), std::nullopt); + EXPECT_EQ(cert_data.expiration_time(), kExpiryTime); + EXPECT_EQ(cert_data.not_before_time(), kNotBeforeTime); + EXPECT_EQ(cert_data.issued_time(), kIssuedAtTime); + EXPECT_EQ(cert_data.cwt_id(), std::nullopt); + EXPECT_EQ(cert_data.code_hash(), kCodeHash); + EXPECT_EQ(cert_data.code_descriptor(), std::nullopt); + EXPECT_EQ(cert_data.config_hash(), kConfigHash); + EXPECT_EQ(cert_data.config_descriptor(), kConfigDescriptor); + EXPECT_EQ(cert_data.authority_hash(), kAuthorityHash); + EXPECT_EQ(cert_data.authority_descriptor(), std::nullopt); + EXPECT_EQ(cert_data.mode(), kMode); + EXPECT_EQ(cert_data.subject_public_key(), kSubjectPublicKey); + EXPECT_EQ(cert_data.key_usage(), kKeyUsage); + EXPECT_EQ(cert_data.profile_name(), kProfileName); + + const std::vector repackaged_cbor_cert = cert_data.SerializeCbor(); + ASSERT_FALSE(repackaged_cbor_cert.empty()); + EXPECT_EQ(cbor_cert, repackaged_cbor_cert); +} + +TEST(DiceCertDataTest, ParseBad_BadIssuerType) { + const int64_t kBadIssuer = 64; + cppbor::Map cert_map; + cert_map.add(kDiceIssuerLabel, kBadIssuer); // Must be tstr + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadSubjectType) { + const std::vector kBadSubject = {'b', 's', 't', 'r'}; + cppbor::Map cert_map; + cert_map.add(kDiceSubjectLabel, kBadSubject); // Must be tstr + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadAudienceType) { + const bool kBadAudience = true; + cppbor::Map cert_map; + cert_map.add(kDiceAudienceLabel, cppbor::Bool(kBadAudience)); // Must be tstr + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadExpirationTimeType) { + cppbor::Map cert_map; + cert_map.add(kDiceExpirationTimeLabel, cppbor::Null()); // Must be int + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadNotBeforeTimeType) { + const std::string kBadNotBeforeTime = "02/27/25"; + cppbor::Map cert_map; + cert_map.add(kDiceNotBeforeLabel, kBadNotBeforeTime); // Must be int + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadIssuedAtTimeType) { + const std::vector kBadIssuedAtTime = {1, 2, 3, 4, 5}; + cppbor::Map cert_map; + cert_map.add(kDiceIssuedAtLabel, kBadIssuedAtTime); // Must be int + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadCwtIdType) { + const std::string kBadCwtId = "CWT ID as string"; + cppbor::Map cert_map; + cert_map.add(kDiceCwtIdLabel, kBadCwtId); // Must be bstr + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadCodeHashType) { + const int64_t kBadCodeHash = 1234; + cppbor::Map cert_map; + cert_map.add(kDiceCodeHashLabel, kBadCodeHash); // Must be bstr + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadCodeDescriptorType) { + cppbor::Map cert_map; + cert_map.add(kDiceCodeDescriptorLabel, cppbor::Null()); // Must be bstr + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadConfigHashType) { + const int64_t kBadConfigHash = 1234; + cppbor::Map cert_map; + cert_map.add(kDiceConfigHashLabel, kBadConfigHash); // Must be bstr + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadConfigDescriptorType) { + cppbor::Map cert_map; + cert_map.add(kDiceConfigDescriptorLabel, cppbor::Null()); // Must be bstr + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadAuthorityHashType) { + const int64_t kBadAuthorityHash = 1234; + cppbor::Map cert_map; + cert_map.add(kDiceAuthorityHashLabel, kBadAuthorityHash); // Must be bstr + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadAuthorityDescriptorType) { + cppbor::Map cert_map; + cert_map.add(kDiceAuthorityDescriptorLabel, cppbor::Null()); // Must be bstr + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadModeType) { + const std::string kBadMode = "Normal"; + + cppbor::Map cert_map; + cert_map.add(kDiceModeLabel, kBadMode); // Must be bstr + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadModeLength_TooLong) { + const std::vector kBadMode = {kDiceModeRecovery, 0}; + + cppbor::Map cert_map; + cert_map.add(kDiceModeLabel, kBadMode); + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadModeLength_TooShort) { + const std::vector kBadMode; + + cppbor::Map cert_map; + cert_map.add(kDiceModeLabel, kBadMode); + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadSubjectPublicKeyType) { + const std::string kBadSubjectPublicKey = "I'm a key, not!"; + + cppbor::Map cert_map; + cert_map.add(kDiceSubjectPublicKeyLabel, kBadSubjectPublicKey); + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadKeyUsageType) { + const int64_t kBadKeyUsage = 1234; + + cppbor::Map cert_map; + cert_map.add(kDiceKeyUsageLabel, kBadKeyUsage); // Must be bstr + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadKeyUsageLength_TooLong) { + const std::vector kBadKeyUsage = {0, 0, 1}; + + cppbor::Map cert_map; + cert_map.add(kDiceKeyUsageLabel, kBadKeyUsage); + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadKeyUsageLength_TooShort) { + const std::vector kBadKeyUsage; + + cppbor::Map cert_map; + cert_map.add(kDiceKeyUsageLabel, kBadKeyUsage); + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} + +TEST(DiceCertDataTest, ParseBad_BadProfileNameType) { + cppbor::Map cert_map; + cert_map.add(kDiceProfileNameLabel, cppbor::Bool(false)); // Must be tstr + const std::vector cbor_cert = cert_map.canonicalize().encode(); + ASSERT_FALSE(cbor_cert.empty()); + + DiceCertData cert_data; + EXPECT_FALSE(cert_data.ParseCbor(cbor_cert)); +} +} // namespace util +} // namespace wvoec diff --git a/util/include/hls_key.h b/util/include/hls_key.h new file mode 100644 index 0000000..b6cc6ec --- /dev/null +++ b/util/include/hls_key.h @@ -0,0 +1,210 @@ +// Copyright 2025 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WVCDM_UTIL_HLS_KEY_H_ +#define WVCDM_UTIL_HLS_KEY_H_ + +#include + +#include +#include +#include +#include + +#include "wv_class_utils.h" + +namespace wvutil { +class HlsAttributeList; + +// Data wrapper types for HLS Key fields. +// +// Ensures that the values of each attribute of an +// HLS key is valid. The class does not enforce contextual +// requirements between attributes; however, IsWellFormed() +// can optionally be called to ensure some fields are set +// appropriately based on the values of other fields. +// +// This class is based on RFC 8216 section 4.3.2.4. +class HlsKey { + public: + // HLS Tag for key data. + static constexpr char kTag[] = "EXT-X-KEY"; + + // Attribute names for HLS keys. + // METHOD is an enumerated string with three known values + // of NONE, AES-128, or SAMPLE-AES. + // METHOD may be a different value so long as it is a + // valid enum string. + // METHOD is required, if set to NONE, then the other + // fields SHOULD NOT be set. + static constexpr char kMethodName[] = "METHOD"; + // URI is a quoted string containing be a valid URI which + // identifies where to find the key(s) used for content + // decryption. + // Must be set if METHOD is not NONE. + // Should not be set if METHOD is NONE. + static constexpr char kUriName[] = "URI"; + // IV is a hex sequence for a 128-bit initialization vector + // used for decryption of media content. + // Must be set if METHOD is not NONE. + // Should not be set if METHOD is NONE. + static constexpr char kIvName[] = "IV"; + // The key format is a quoted string which identifies the + // format of the key acquired by the URI. + // Optionally set if METHOD is not NONE, if not set, then + // it is assumed the URI will directly acquire the key. + // Should not be set if METHOD is NONE. + static constexpr char kKeyFormatName[] = "KEYFORMAT"; + // The key format versions is a quoted string containing one + // or more positive integers (of HLS integer format) separated + // by a '/', which indicates the vendor-specific versions of the + // key acquired via the URI. + // Optionally set if METHOD is not NONE, assumed to contain + // a value of 1 if not set but a version is required. + // Should not be set if METHOD is NONE. + static constexpr char kKeyFormatVersionsName[] = "KEYFORMATVERSIONS"; + + // List of known METHOD values. + static constexpr char kMethodNone[] = "NONE"; + static constexpr char kMethodAes128[] = "AES-128"; + static constexpr char kMethodSampleAes[] = "SAMPLE-AES"; + + // HLS keys require an IV length of 128-bits / 16 octets. + static constexpr size_t kIvLength = 16; + + // Checks if the provided |method| is one of the known + // values for the enumerated string METHOD. + static bool IsKnownMethod(const std::string& method); + // Checks if the provided |method| is a valid HLS enum + // string value. + static bool IsValidMethod(const std::string& method); + // Simply checks that the provided URI is a valid HLS quoted + // string value. Does not valid URI format. + static bool IsValidUriValue(const std::string& uri); + // Checks that the specified IV length (in octets) is a supported + // length. + static constexpr bool IsValidIvLength(size_t iv_length) { + return iv_length == kIvLength; + } + static bool IsValidIv(const std::string& iv) { + return IsValidIvLength(iv.size()); + } + static bool IsValidIv(const std::vector& iv) { + return IsValidIvLength(iv.size()); + } + // Checks that the provided key format conforms to the quoted + // string value requirements of HLS. + static bool IsValidKeyFormat(const std::string& key_format); + // Checks if the specified |version| is a valid key format + // version (must be positive). + static constexpr bool IsValidKeyFormatVersion(uint64_t version) { + return version > 0; + } + // Checks that all the values in the set of |versions| is a valid + // versions. + // The set of versions cannot be empty, and all versions must be a + // positive value. + static bool IsValidKeyFormatVersions(const std::set& versions); + // Checks that the provided formatted version list is a valid + // representation of key format versions. + // Note: There is no requirement that the versions are unique. + static bool IsValidKeyFormatVersionListRep( + const std::string& version_list_rep); + + HlsKey() = default; + WVCDM_DEFAULT_COPY_AND_MOVE(HlsKey); + + const std::string& method() const { return method_; } + bool HasMethod() const { return !method_.empty(); } + bool SetMethod(const std::string& method); + void ClearMethod() { method_.clear(); } + + // URIs are only validated that they are valid quoted string + // values; not that they are well formed URIs. + const std::string& uri() const { return uri_; } + bool HasUri() const { return !uri_.empty(); } + bool SetUri(const std::string& uri); + void ClearUri() { uri_.clear(); } + + const std::vector& iv() const { return iv_; } + bool HasIv() const { return !iv_.empty(); } + bool SetIv(const std::vector& iv); + bool SetIv(const std::string& iv); + bool SetIvHex(const std::string& iv_hex); + void ClearIv() { iv_.clear(); } + + const std::string& key_format() const { return key_format_; } + bool HasKeyFormat() const { return !key_format_.empty(); } + bool SetKeyFormat(const std::string& key_format); + void ClearKeyFormat() { key_format_.clear(); } + + const std::set& key_format_versions() const { + return key_format_versions_; + } + bool HasKeyFormatVersions() const { return !key_format_versions_.empty(); } + bool HasKeyFormatVersion(uint64_t version) const; + bool SetKeyFormatVersions(const std::set& versions); + bool SetKeyFormatVersions(const std::vector& versions); + bool AddKeyFormatVersion(uint64_t version); + void ClearKeyFormatVersions() { key_format_versions_.clear(); } + + void Clear() { + ClearMethod(); + ClearUri(); + ClearIv(); + ClearKeyFormat(); + ClearKeyFormatVersions(); + } + + // Checks that the HLS key is well-formed based on the requirements + // of an HLS key. Relies mainly on the requirements of METHOD. + bool IsWellFormed() const; + + // == Parsing / Serialization == + // Initializes the HLS key from a populated instance of + // HlsAttributeList. + // + // Requirements: + // - METHOD must be set, must be one of the well-known methods. + // - URI is optional, must be quoted string if set. + // - IV is optional, must be a 16-byte / 128-bit hex sequence if set. + // - KEYFORMAT is optional, must be a quoted string if set, defaults + // to "identify" is not set. + // - KEYFORMATVERSIONS is optional, must be a quoted string containing + // a valid sequence of '/' separated version if set, defaults to + // "1" if not set. + bool FromAttributeList(const HlsAttributeList& attr_list); + // Initializes the HLS key from the HLS serialized HLS Attribute List. + // Internally uses FromAttributeList(), and follows the same requirements. + bool ParseAttributeList(const std::string& attr_list_rep); + + // Packages the HLS key into the HlsAttributeList instance. + // Clears existing data in |attr_list|. + // + // Serializing does not enforce any field requirements other than + // types. + // + // Follows the following rules: + // - METHOD packaged as enum string if set + // - URI packaged as quoted string if set + // - IV packaged as hex sequence if set + // - KEYFORMAT packaged as quoted string if set + // - KEYFORMATVERSIONS packaged as quoted string containing slash + // separated version if any version is set. + bool ToAttributeList(HlsAttributeList* attr_list) const; + // Serializes the HLS key into an HLS attribute string format. + // Internally uses ToAttributeList(), and follows the same packaging + // rules. + std::string Serialize() const; + bool SerializeToStream(std::ostream* out) const; + + private: + // Values are either empty or set to valid values. + std::string method_; + std::string uri_; + std::vector iv_; + std::string key_format_; + std::set key_format_versions_; +}; // class HlsKey +} // namespace wvutil +#endif // WVCDM_UTIL_HLS_KEY_H_ diff --git a/util/src/hls_key.cpp b/util/src/hls_key.cpp new file mode 100644 index 0000000..19a3b43 --- /dev/null +++ b/util/src/hls_key.cpp @@ -0,0 +1,469 @@ +// Copyright 2025 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#include "hls_key.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hls_attribute_list.h" +#include "log.h" +#include "string_conversions.h" +#include "string_utils.h" + +namespace wvutil { +namespace { +// If KEYFORMATVERSIONS specifies multiple versions, then each +// version is separated by a '/' character. +constexpr char kVersionSeparatorChar = '/'; +constexpr char kZeroChar = '0'; + +// RFC 8216 specifies that KEYFORMAT is optional, but when +// METHOD is not NONE and KEYFORMAT absent, then KEYFORMAT is assumed +// to be "identity". +constexpr char kDefaultKeyFormat[] = "identity"; +// RFC 8216 specifies that KEYFORMATVERSIONS is optional, but when +// METHOD is not NONE, and KEYFORMATVERSIONS absent, then +// KEYFORMATVERSIONS is assumed to be "1". +constexpr uint64_t kDefaultKeyFormatVersion = 1; + +// Checks each version in the |versions| container are valid +// values. +template +bool IsValidKeyFormatVersionsInternal(const Container& versions) { + // Container should be of uint64_t. + static_assert(std::is_same_v, + "Container must have values of uint64_t"); + if (versions.empty()) return false; + // Note: Duplicate values are technically not invalid. + return std::all_of(versions.begin(), versions.end(), + HlsKey::IsValidKeyFormatVersion); +} + +bool IsValidKeyFormatVersionRep(const std::string& version_rep) { + // Must be a valid HLS integer. + if (!HlsAttributeList::IsValidIntegerRep(version_rep)) return false; + // Version cannot be zero. + // Note: HLS integer strings may contain leading zeros, so long + // as not all the digits are zero, the |version_rep| is valid. + const size_t zero_count = + std::count(version_rep.begin(), version_rep.end(), kZeroChar); + return zero_count < version_rep.size(); +} + +bool ParseKeyFormatVersionList(const std::string& version_list_rep, + std::set* versions) { + versions->clear(); + const std::vector version_reps = + StringSplit(version_list_rep, kVersionSeparatorChar); + if (version_reps.empty()) { + LOGV("Invalid key format version list rep"); + return false; + } + + for (const auto& version_rep : version_reps) { + uint64_t version = 0; + if (!HlsAttributeList::ParseInteger(version_rep, &version)) { + LOGV("Failed to parse key format version: %s", version_rep.c_str()); + versions->clear(); + return false; + } + if (!HlsKey::IsValidKeyFormatVersion(version)) { + LOGV("Found invalid key format version: %" PRIu64, version); + versions->clear(); + return false; + } + versions->insert(version); + } + return true; +} + +std::string KeyFormatVersionsToString(const std::vector& versions) { + std::string result; + for (const uint64_t& version : versions) { + // Ensure each version is separated by a '/' character. + if (!result.empty()) result.push_back(kVersionSeparatorChar); + result.append(std::to_string(version)); + } + return result; +} + +std::string KeyFormatVersionsToString(const std::set& versions) { + // std::set are not necessarily sorted. Converting to a vector + // to allow formatting in ascending order. + std::vector versions_vec(versions.begin(), versions.end()); + std::sort(versions_vec.begin(), versions_vec.end()); + return KeyFormatVersionsToString(versions_vec); +} +} // namespace + +// static +bool HlsKey::IsKnownMethod(const std::string& method) { + return method == kMethodNone || method == kMethodAes128 || + method == kMethodSampleAes; +} + +// static +bool HlsKey::IsValidMethod(const std::string& method) { + return !method.empty() && HlsAttributeList::IsValidEnumStringValue(method); +} + +// static +bool HlsKey::IsValidUriValue(const std::string& uri) { + return !uri.empty() && HlsAttributeList::IsValidQuotedStringValue(uri); +} + +// static +bool HlsKey::IsValidKeyFormat(const std::string& key_format) { + return !key_format.empty() && + HlsAttributeList::IsValidQuotedStringValue(key_format); +} + +// static +bool HlsKey::IsValidKeyFormatVersions(const std::set& versions) { + return IsValidKeyFormatVersionsInternal(versions); +} + +// static +bool HlsKey::IsValidKeyFormatVersionListRep( + const std::string& version_list_rep) { + const std::vector version_reps = + StringSplit(version_list_rep, kVersionSeparatorChar); + if (version_reps.empty()) return false; + return std::all_of(version_reps.begin(), version_reps.end(), + IsValidKeyFormatVersionRep); +} + +bool HlsKey::SetMethod(const std::string& method) { + if (!IsValidMethod(method)) { + LOGV("Invalid method: %s", SafeByteIdToString(method).c_str()); + return false; + } + method_ = method; + return true; +} + +bool HlsKey::SetUri(const std::string& uri) { + if (!IsValidUriValue(uri)) { + LOGV("Invalid URI: %s", SafeByteIdToString(uri).c_str()); + return false; + } + uri_ = uri; + return true; +} + +bool HlsKey::SetIv(const std::vector& iv) { + if (!IsValidIv(iv)) { + LOGV("Invalid IV: 0x%s (length = %zu)", b2a_hex(iv).c_str(), iv.size()); + return false; + } + iv_ = iv; + return true; +} + +bool HlsKey::SetIv(const std::string& iv) { + if (!IsValidIv(iv)) { + LOGV("Invalid IV: 0x%s (length = %zu)", b2a_hex(iv).c_str(), iv.size()); + return false; + } + iv_.assign(iv.begin(), iv.end()); + return true; +} + +bool HlsKey::SetIvHex(const std::string& iv_hex) { + std::vector iv = a2b_hex(iv_hex); + if (iv.empty() && !iv_hex.empty()) { + LOGV("Invalid IV hex: %s", SafeByteIdToString(iv_hex).c_str()); + return false; + } + if (!IsValidIv(iv)) { + LOGV("Invalid IV: 0x%s (length = %zu)", b2a_hex(iv).c_str(), iv.size()); + return false; + } + iv_ = std::move(iv); + return true; +} + +bool HlsKey::SetKeyFormat(const std::string& key_format) { + if (!IsValidKeyFormat(key_format)) { + LOGV("Invalid key format: %s", SafeByteIdToString(key_format).c_str()); + return false; + } + key_format_ = key_format; + return true; +} + +bool HlsKey::HasKeyFormatVersion(uint64_t version) const { + return key_format_versions_.find(version) != key_format_versions_.end(); +} + +bool HlsKey::SetKeyFormatVersions(const std::set& versions) { + if (!IsValidKeyFormatVersionsInternal(versions)) { + LOGV("Invalid key format versions: %s", + KeyFormatVersionsToString(versions).c_str()); + return false; + } + key_format_versions_ = versions; + return true; +} + +bool HlsKey::SetKeyFormatVersions(const std::vector& versions) { + if (!IsValidKeyFormatVersionsInternal(versions)) { + LOGV("Invalid key format versions: %s", + KeyFormatVersionsToString(versions).c_str()); + return false; + } + key_format_versions_.clear(); + key_format_versions_.insert(versions.begin(), versions.end()); + return true; +} + +bool HlsKey::AddKeyFormatVersion(uint64_t version) { + if (!IsValidKeyFormatVersion(version)) { + LOGV("Invalid key format version: %" PRIu64, version); + return false; + } + key_format_versions_.insert(version); + return true; +} + +bool HlsKey::IsWellFormed() const { + // METHOD is always required. + if (!HasMethod()) { + LOGV("Missing method"); + return false; + } + if (method() == kMethodNone) { + // From RFC 8216, section 4.3.2.4: + // If the encryption method is NONE, other attributes + // MUST NOT be present. + if (HasUri()) { + LOGV("URI not allowed if method is NONE: uri = %s", uri_.c_str()); + return false; + } + if (HasIv()) { + LOGV("IV not allowed if method is NONE: iv = 0x%s", b2a_hex(iv_).c_str()); + return false; + } + if (HasKeyFormat()) { + LOGV("Key format not allowed if method is NONE: key_format = %s", + key_format_.c_str()); + return false; + } + if (HasKeyFormatVersions()) { + LOGV( + "Key format versions are not allowed if method is NONE: " + "key_format_versions = %s", + KeyFormatVersionsToString(key_format_versions_).c_str()); + return false; + } + return true; + } + // METHOD != NONE + + // URI is the only required field if METHOD is not NONE. + if (!HasUri()) { + LOGV("Missing URI"); + return false; + } + // IV, key format and key format versions are optional fields. + return true; +} + +bool HlsKey::FromAttributeList(const HlsAttributeList& list) { + if (list.IsEmpty()) { + LOGV("HLS attribute list is empty"); + return false; + } + if (!list.IsType(kMethodName, HlsAttributeList::kEnumStringType)) { + LOGV("HLS attribute list is missing METHOD"); + return false; + } + std::string method; + if (!list.GetEnumString(kMethodName, &method)) { + // Definitely an error as the check above should + // ensure method is available. + LOGE("Failed to get METHOD"); + return false; + } + if (!IsValidMethod(method)) { + LOGV("Invalid method: %s", method.c_str()); + return false; + } + if (method == kMethodNone) { + // Ignore all other fields. + Clear(); + method_ = std::move(method); + return true; + } + + // URI is the only required field. + if (!list.IsType(kUriName, HlsAttributeList::kQuotedStringType)) { + LOGV("HLS attribute list is missing URI"); + return false; + } + std::string uri; + if (!list.GetQuotedString(kUriName, &uri)) { + LOGE("Failed to get URI"); + return false; + } + if (!IsValidUriValue(uri)) { + LOGV("Invalid URI: \"%s\"", uri.c_str()); + return false; + } + + std::vector iv; + if (list.IsType(kIvName, HlsAttributeList::kHexSequenceType)) { + if (!list.GetHexSequence(kIvName, &iv)) { + LOGE("Failed to get IV"); + return false; + } + if (!IsValidIv(iv)) { + // Note: hex sequences are never empty. + LOGV("Invalid IV: 0x%s", b2a_hex(iv).c_str()); + return false; + } + } else if (list.Contains(kIvName)) { + LOGV("HLS attribute list contains an invalid IV"); + return false; + } + + std::string key_format = kDefaultKeyFormat; + if (list.IsType(kKeyFormatName, HlsAttributeList::kQuotedStringType)) { + if (!list.GetQuotedString(kKeyFormatName, &key_format)) { + LOGE("Failed to get KEYFORMAT"); + return false; + } + if (!IsValidKeyFormat(key_format)) { + LOGV("Invalid key format: \"%s\"", key_format.c_str()); + return false; + } + } else if (list.Contains(kKeyFormatName)) { + LOGV("HLS attribute list contains an invalid KEYFORMAT"); + return false; + } + + std::set versions = {kDefaultKeyFormatVersion}; + if (list.IsType(kKeyFormatVersionsName, + HlsAttributeList::kQuotedStringType)) { + versions.clear(); + std::string version_list_rep; + if (!list.GetQuotedString(kKeyFormatVersionsName, &version_list_rep)) { + LOGE("Failed to get KEYFORMATVERSIONS"); + return false; + } + if (!IsValidKeyFormatVersionListRep(version_list_rep)) { + LOGV("Invalid key format versions: \"%s\"", version_list_rep.c_str()); + return false; + } + if (!ParseKeyFormatVersionList(version_list_rep, &versions)) { + LOGE("Failed to parse key format versions: \"%s\"", + version_list_rep.c_str()); + return false; + } + } else if (list.Contains(kKeyFormatVersionsName)) { + LOGV("HLS attribute list contains an invalid KEYFORMATVERSIONS"); + return false; + } + + // Ignore all other fields as they might be from a future HLS version. + method_ = std::move(method); + uri_ = std::move(uri); + iv_ = std::move(iv); + key_format_ = std::move(key_format); + key_format_versions_ = std::move(versions); + return true; +} + +bool HlsKey::ParseAttributeList(const std::string& list_rep) { + if (list_rep.empty()) { + LOGV("HLS attribute list rep is empty"); + return false; + } + HlsAttributeList list; + if (!list.Parse(list_rep)) { + LOGV("Failed to parse HLS attribute list"); + return false; + } + return FromAttributeList(list); +} + +// Note: Any value that is set is assumed to able to +// produce a valid HLS attribute value, and therefore, setting +// the attribute should be successful. Failure to do so is +// considered an internal error. +bool HlsKey::ToAttributeList(HlsAttributeList* list) const { + if (list == nullptr) { + LOGE("Output |list| is null"); + return false; + } + if (!IsWellFormed()) { + // Serialize anyways. + LOGW("Current HLS key is incomplete"); + } + + list->Clear(); + if (HasMethod() && !list->SetEnumString(kMethodName, method_)) { + LOGE("Failed to set method: %s=%s", kMethodName, method_.c_str()); + list->Clear(); + return false; + } + if (HasUri() && !list->SetQuotedString(kUriName, uri_)) { + LOGE("Failed to set URI: %s=\"%s\"", kUriName, uri_.c_str()); + list->Clear(); + return false; + } + if (HasIv() && !list->SetHexSequence(kIvName, iv_)) { + LOGE("Failed to set IV: %s=0x%s", kIvName, b2a_hex(iv_).c_str()); + list->Clear(); + return false; + } + if (HasKeyFormat() && !list->SetQuotedString(kKeyFormatName, key_format_)) { + LOGE("Failed to set key format: %s=\"%s\"", kKeyFormatName, + key_format_.c_str()); + list->Clear(); + return false; + } + if (HasKeyFormatVersions()) { + const std::string formatted_versions = + KeyFormatVersionsToString(key_format_versions_); + if (!list->SetQuotedString(kKeyFormatVersionsName, formatted_versions)) { + LOGE("Failed to set key format versions: %s=\"%s\"", + kKeyFormatVersionsName, formatted_versions.c_str()); + list->Clear(); + return false; + } + } + return true; +} + +std::string HlsKey::Serialize() const { + HlsAttributeList list; + if (!ToAttributeList(&list)) { + LOGV("Failed to convert HLS key to HLS attribute list"); + return std::string(); + } + return list.Serialize(); +} + +bool HlsKey::SerializeToStream(std::ostream* out) const { + if (out == nullptr) { + LOGE("Output stream is null"); + return false; + } + HlsAttributeList list; + if (!ToAttributeList(&list)) { + LOGV("Failed to convert HLS key to HLS attribute list"); + return false; + } + return list.SerializeToStream(out); +} +} // namespace wvutil diff --git a/util/test/hls_key_unittest.cpp b/util/test/hls_key_unittest.cpp new file mode 100644 index 0000000..42f3d1e --- /dev/null +++ b/util/test/hls_key_unittest.cpp @@ -0,0 +1,866 @@ +// Copyright 2025 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#include "hls_key.h" + +#include + +#include +#include +#include +#include + +#include + +#include "hls_attribute_list.h" +#include "string_conversions.h" + +namespace wvutil { +namespace test { +TEST(HlsKeyTest, ClassConstants) { + // Validity checks on the class constants. + // Verify names. + EXPECT_TRUE(HlsAttributeList::IsValidName(HlsKey::kMethodName)) + << "METHOD key invalid: " << HlsKey::kMethodName; + EXPECT_TRUE(HlsAttributeList::IsValidName(HlsKey::kUriName)) + << "URI key invalid: " << HlsKey::kUriName; + EXPECT_TRUE(HlsAttributeList::IsValidName(HlsKey::kIvName)) + << "IV key invalid: " << HlsKey::kIvName; + EXPECT_TRUE(HlsAttributeList::IsValidName(HlsKey::kKeyFormatName)) + << "KEYFORMAT key invalid: " << HlsKey::kKeyFormatName; + EXPECT_TRUE(HlsAttributeList::IsValidName(HlsKey::kKeyFormatVersionsName)) + << "KEYFORMATVERSIONS key invalid: " << HlsKey::kKeyFormatVersionsName; + + // Well-known methods. + EXPECT_TRUE(HlsAttributeList::IsValidEnumStringValue(HlsKey::kMethodNone)) + << "METHOD=NONE value invalid: " << HlsKey::kMethodNone; + EXPECT_TRUE(HlsAttributeList::IsValidEnumStringValue(HlsKey::kMethodAes128)) + << "METHOD=AES-128 value invalid: " << HlsKey::kMethodAes128; + EXPECT_TRUE( + HlsAttributeList::IsValidEnumStringValue(HlsKey::kMethodSampleAes)) + << "METHOD=SAMPLE-AES value invalid: " << HlsKey::kMethodSampleAes; +} + +TEST(HlsKeyTest, IsValidKeyFormatVersion) { + EXPECT_TRUE(HlsKey::IsValidKeyFormatVersion(1)); + EXPECT_TRUE(HlsKey::IsValidKeyFormatVersion(2)); + EXPECT_TRUE(HlsKey::IsValidKeyFormatVersion(1337)); + EXPECT_TRUE( + HlsKey::IsValidKeyFormatVersion(std::numeric_limits::max())); +} + +TEST(HlsKeyTest, IsValidKeyFormatVersion_Invalid) { + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersion(0)); +} + +TEST(HlsKeyTest, IsValidKeyFormatVersions) { + const std::set kSingleVersion = {2}; + EXPECT_TRUE(HlsKey::IsValidKeyFormatVersions(kSingleVersion)); + + const std::set kFewVersions = {1, 2, 5}; + EXPECT_TRUE(HlsKey::IsValidKeyFormatVersions(kFewVersions)); + + const std::set kLargeVersion = { + 1, 2, std::numeric_limits::max()}; + EXPECT_TRUE(HlsKey::IsValidKeyFormatVersions(kLargeVersion)); +} + +TEST(HlsKeyTest, IsValidKeyFormatVersions_Invalid) { + const std::set kEmptySet; + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersions(kEmptySet)); + + const std::set kZeroOnly = {0}; + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersions(kZeroOnly)); + + const std::set kWithZero = {0, 1, 2, 5}; + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersions(kWithZero)); +} + +TEST(HlsKeyTest, IsValidKeyFormatVersionListRep) { + EXPECT_TRUE(HlsKey::IsValidKeyFormatVersionListRep("1")); + EXPECT_TRUE(HlsKey::IsValidKeyFormatVersionListRep("1/2")); + EXPECT_TRUE(HlsKey::IsValidKeyFormatVersionListRep("1/2/5")); + EXPECT_TRUE(HlsKey::IsValidKeyFormatVersionListRep("1337")); +} + +TEST(HlsKeyTest, IsValidKeyFormatVersionListRep_Invalid) { + // Empty not allowed. + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("")); + // Zero is not allowed. + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("0")); + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("0/2/5")); + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("1/0/5")); + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("1/2/0")); + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("1/2/000000")); + + // All must integers. + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("NONE")); + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("1/NONE/4")); + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("1/1.2/2")); + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("1/0xDEADBEAF")); + + // Cannot have empty values. + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("/2/5")); + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("1//5")); + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("1/2/")); + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("/")); + + // Must be a slash separated list. + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("1,0,5")); + + // Cannot have spaces. + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep(" 1/2/5")); + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("1 /2/5")); + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("1/ 2/5")); + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("1/2 /5")); + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("1/2/ 5")); + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("1/2/5 ")); + + // Must fit in the 64-bit integer range. + EXPECT_FALSE(HlsKey::IsValidKeyFormatVersionListRep("100000000000000000000")); +} + +TEST(HlsKeyTest, Empty) { + const HlsKey key; + + EXPECT_FALSE(key.HasMethod()); + EXPECT_FALSE(key.HasUri()); + EXPECT_FALSE(key.HasIv()); + EXPECT_FALSE(key.HasKeyFormat()); + EXPECT_FALSE(key.HasKeyFormatVersions()); + EXPECT_FALSE(key.HasKeyFormatVersion(1)); +} + +TEST(HlsKeyTest, SetMethod) { + HlsKey key; + EXPECT_TRUE(key.SetMethod("NONE")); + EXPECT_TRUE(key.HasMethod()); + EXPECT_EQ(key.method(), "NONE"); + key.ClearMethod(); + EXPECT_FALSE(key.HasMethod()); +} + +TEST(HlsKeyTest, SetMethod_Invalid) { + HlsKey key; + // Check that an invalid cannot be set. + EXPECT_FALSE(key.SetMethod("NOT VALID")); + EXPECT_FALSE(key.HasMethod()); + + EXPECT_TRUE(key.SetMethod("SAMPLE-AES")); + + // Check that setting an invalid method does not + // overwrite the existing method. + EXPECT_FALSE(key.SetMethod("NOT VALID")); + EXPECT_EQ(key.method(), "SAMPLE-AES"); +} + +TEST(HlsKeyTest, SetUri) { + const std::string kSampleUri = "http://media.src/some-key"; + HlsKey key; + EXPECT_TRUE(key.SetUri(kSampleUri)); + EXPECT_TRUE(key.HasUri()); + EXPECT_EQ(key.uri(), kSampleUri); + key.ClearUri(); + EXPECT_FALSE(key.HasUri()); +} + +TEST(HlsKeyTest, SetUri_Invalid) { + const std::string kBadUri = "No\"quotes"; + const std::string kGoodUri = "http://media.src/some-key"; + + HlsKey key; + EXPECT_FALSE(key.SetUri(kBadUri)); + EXPECT_FALSE(key.HasUri()); + EXPECT_FALSE(key.SetUri("")); + EXPECT_FALSE(key.HasUri()); + + EXPECT_TRUE(key.SetUri(kGoodUri)); + + EXPECT_FALSE(key.SetUri(kBadUri)); + EXPECT_EQ(key.uri(), kGoodUri); +} + +TEST(HlsKeyTest, SetIv_Vec) { + const std::vector kGoodIv(HlsKey::kIvLength, 42); + HlsKey key; + EXPECT_TRUE(key.SetIv(kGoodIv)); + EXPECT_TRUE(key.HasIv()); + EXPECT_EQ(key.iv(), kGoodIv); + key.ClearIv(); + EXPECT_FALSE(key.HasIv()); +} + +TEST(HlsKeyTest, SetIv_Vec_Invalid) { + const std::vector kGoodIv(HlsKey::kIvLength, 42); + const std::vector kEmptyIv; + const std::vector kShortIv(HlsKey::kIvLength / 2, 22); + const std::vector kLargeIv(HlsKey::kIvLength * 2, 22); + HlsKey key; + EXPECT_FALSE(key.SetIv(kEmptyIv)); + EXPECT_FALSE(key.SetIv(kShortIv)); + EXPECT_FALSE(key.SetIv(kLargeIv)); + EXPECT_FALSE(key.HasIv()); + + EXPECT_TRUE(key.SetIv(kGoodIv)); + + EXPECT_FALSE(key.SetIv(kShortIv)); + EXPECT_EQ(key.iv(), kGoodIv); +} + +TEST(HlsKeyTest, SetIv_String) { + const std::string kGoodIv(HlsKey::kIvLength, 'x'); + HlsKey key; + EXPECT_TRUE(key.SetIv(kGoodIv)); + EXPECT_TRUE(key.HasIv()); + const std::vector kGoodIvVec(kGoodIv.begin(), kGoodIv.end()); + EXPECT_EQ(key.iv(), kGoodIvVec); + key.ClearIv(); + EXPECT_FALSE(key.HasIv()); +} + +TEST(HlsKeyTest, SetIv_String_Invalid) { + const std::vector kGoodIv(HlsKey::kIvLength, 42); + const std::string kEmptyIv; + const std::string kShortIv(HlsKey::kIvLength / 2, 'y'); + const std::string kLargeIv(HlsKey::kIvLength * 2, 'z'); + HlsKey key; + EXPECT_FALSE(key.SetIv(kEmptyIv)); + EXPECT_FALSE(key.SetIv(kShortIv)); + EXPECT_FALSE(key.SetIv(kLargeIv)); + EXPECT_FALSE(key.HasIv()); + + EXPECT_TRUE(key.SetIv(kGoodIv)); + + EXPECT_FALSE(key.SetIv(kShortIv)); + EXPECT_EQ(key.iv(), kGoodIv); +} + +TEST(HlsKeyTest, SetIvHex) { + const std::vector kGoodIv(HlsKey::kIvLength, 42); + const std::string kGoodIvHex = b2a_hex(kGoodIv); + HlsKey key; + EXPECT_TRUE(key.SetIvHex(kGoodIvHex)); + EXPECT_TRUE(key.HasIv()); + EXPECT_EQ(key.iv(), kGoodIv); + key.ClearIv(); + EXPECT_FALSE(key.HasIv()); +} + +TEST(HlsKeyTest, SetIvHex_Invalid) { + const std::vector kGoodIv(HlsKey::kIvLength, 42); + const std::string kEmptyIvHex; + const std::string kShortIvHex(HlsKey::kIvLength, 'e'); + const std::string kLargeIvHex(HlsKey::kIvLength * 4, 'e'); + const std::string kNonHexIv(HlsKey::kIvLength * 2, 'x'); + + HlsKey key; + EXPECT_FALSE(key.SetIvHex(kEmptyIvHex)); + EXPECT_FALSE(key.SetIvHex(kShortIvHex)); + EXPECT_FALSE(key.SetIvHex(kLargeIvHex)); + EXPECT_FALSE(key.SetIvHex(kNonHexIv)); + + EXPECT_TRUE(key.SetIv(kGoodIv)); + + EXPECT_FALSE(key.SetIvHex(kShortIvHex)); + EXPECT_EQ(key.iv(), kGoodIv); +} + +TEST(HlsKeyTest, SetKeyFormat) { + const std::string kGoodKeyFormat = "com.widevine"; + HlsKey key; + EXPECT_TRUE(key.SetKeyFormat(kGoodKeyFormat)); + EXPECT_TRUE(key.HasKeyFormat()); + EXPECT_EQ(key.key_format(), kGoodKeyFormat); + key.ClearKeyFormat(); + EXPECT_FALSE(key.HasKeyFormat()); +} + +TEST(HlsKeyTest, SetKeyFormat_Invalid) { + const std::string kGoodKeyFormat = "com.widevine"; + const std::string kEmptyKeyFormat; + const std::string kInvalidKeyFormat = "com\"widevine"; + + HlsKey key; + EXPECT_FALSE(key.SetKeyFormat(kEmptyKeyFormat)); + EXPECT_FALSE(key.SetKeyFormat(kInvalidKeyFormat)); + + EXPECT_TRUE(key.SetKeyFormat(kGoodKeyFormat)); + + EXPECT_FALSE(key.SetKeyFormat(kInvalidKeyFormat)); + EXPECT_EQ(key.key_format(), kGoodKeyFormat); +} + +TEST(HlsKeyTest, SetKeyFormatVersions_Set) { + const std::set kSingleVersion = {3}; + const std::set kFewVersions = {1, 2, 5}; + + HlsKey key; + EXPECT_TRUE(key.SetKeyFormatVersions(kSingleVersion)); + EXPECT_TRUE(key.HasKeyFormatVersions()); + EXPECT_EQ(key.key_format_versions(), kSingleVersion); + + key.ClearKeyFormatVersions(); + EXPECT_FALSE(key.HasKeyFormatVersions()); + + EXPECT_TRUE(key.SetKeyFormatVersions(kFewVersions)); + EXPECT_TRUE(key.HasKeyFormatVersions()); + EXPECT_EQ(key.key_format_versions(), kFewVersions); + + // Ensure that set is not merging. + EXPECT_TRUE(key.SetKeyFormatVersions(kSingleVersion)); + EXPECT_EQ(key.key_format_versions(), kSingleVersion); +} + +TEST(HlsKeyTest, SetKeyFormatVersions_Set_Invalid) { + const std::set kSingleVersion = {3}; + const std::set kBadVersions = {0, 2, 5}; + + HlsKey key; + EXPECT_FALSE(key.SetKeyFormatVersions(kBadVersions)); + EXPECT_FALSE(key.HasKeyFormatVersions()); + + EXPECT_TRUE(key.SetKeyFormatVersions(kSingleVersion)); + + EXPECT_FALSE(key.SetKeyFormatVersions(kBadVersions)); + EXPECT_EQ(key.key_format_versions(), kSingleVersion); +} + +TEST(HlsKeyTest, SetKeyFormatVersions_Vec) { + const std::vector kSingleVersionVec = {3}; + const std::vector kFewVersionsVec = {1, 2, 5}; + const std::vector kRepeatedVersionsVec = {1, 1, 2, 5, 5, 5, 5, 5}; + + const std::set kSingleVersion = {3}; + const std::set kFewVersions = {1, 2, 5}; + + HlsKey key; + EXPECT_TRUE(key.SetKeyFormatVersions(kSingleVersionVec)); + EXPECT_TRUE(key.HasKeyFormatVersions()); + EXPECT_EQ(key.key_format_versions(), kSingleVersion); + + key.ClearKeyFormatVersions(); + EXPECT_FALSE(key.HasKeyFormatVersions()); + + EXPECT_TRUE(key.SetKeyFormatVersions(kFewVersionsVec)); + EXPECT_TRUE(key.HasKeyFormatVersions()); + EXPECT_EQ(key.key_format_versions(), kFewVersions); + + // Ensure that set is not merging. + EXPECT_TRUE(key.SetKeyFormatVersions(kSingleVersionVec)); + EXPECT_EQ(key.key_format_versions(), kSingleVersion); + + // Ensure that repeated values are not stored. + EXPECT_TRUE(key.SetKeyFormatVersions(kRepeatedVersionsVec)); + EXPECT_EQ(key.key_format_versions(), kFewVersions); +} + +TEST(HlsKeyTest, SetKeyFormatVersions_Vec_Invalid) { + const std::set kSingleVersion = {3}; + const std::vector kBadVersionsVec = {1, 2, 0, 5}; + + HlsKey key; + EXPECT_FALSE(key.SetKeyFormatVersions(kBadVersionsVec)); + EXPECT_FALSE(key.HasKeyFormatVersions()); + + EXPECT_TRUE(key.SetKeyFormatVersions(kSingleVersion)); + + EXPECT_FALSE(key.SetKeyFormatVersions(kBadVersionsVec)); + EXPECT_EQ(key.key_format_versions(), kSingleVersion); +} + +TEST(HlsKeyTest, HasKeyFormatVersion) { + const std::set kFewVersions = {1, 2, 5}; + + HlsKey key; + EXPECT_FALSE(key.HasKeyFormatVersion(1)); + EXPECT_FALSE(key.HasKeyFormatVersion(std::numeric_limits::max())); + + EXPECT_TRUE(key.SetKeyFormatVersions(kFewVersions)); + + EXPECT_FALSE(key.HasKeyFormatVersion(0)); + EXPECT_TRUE(key.HasKeyFormatVersion(1)); + EXPECT_TRUE(key.HasKeyFormatVersion(2)); + EXPECT_FALSE(key.HasKeyFormatVersion(3)); + EXPECT_FALSE(key.HasKeyFormatVersion(4)); + EXPECT_TRUE(key.HasKeyFormatVersion(5)); + EXPECT_FALSE(key.HasKeyFormatVersion(std::numeric_limits::max())); +} + +TEST(HlsKeyTest, AddKeyFormatVersion) { + const std::set kFewVersions = {1, 2, 5}; + HlsKey key; + EXPECT_TRUE(key.AddKeyFormatVersion(1)); + EXPECT_TRUE(key.AddKeyFormatVersion(2)); + EXPECT_TRUE(key.AddKeyFormatVersion(5)); + + EXPECT_EQ(key.key_format_versions(), kFewVersions); + + // Repeated adds doesn't count as error. + EXPECT_TRUE(key.AddKeyFormatVersion(5)); + // Cannot add invalid. + EXPECT_FALSE(key.AddKeyFormatVersion(0)); + + EXPECT_EQ(key.key_format_versions(), kFewVersions); +} + +TEST(HlsKeyTest, IsWellFormed_Empty) { + const HlsKey key; + EXPECT_FALSE(key.IsWellFormed()); +} + +TEST(HlsKeyTest, IsWellFormed_MethodNone) { + HlsKey key; + EXPECT_TRUE(key.SetMethod("NONE")); + EXPECT_TRUE(key.IsWellFormed()); +} + +// When method is none, all other fields must be empty. +TEST(HlsKeyTest, IsWellFormed_MethodNone_Invalid) { + HlsKey key; + EXPECT_TRUE(key.SetMethod("NONE")); + + // With URI. + EXPECT_TRUE(key.SetUri("http://domain.tld/key")); + EXPECT_FALSE(key.IsWellFormed()); + key.ClearUri(); + + // With IV. + EXPECT_TRUE(key.SetIv(std::vector(HlsKey::kIvLength, 42))); + EXPECT_FALSE(key.IsWellFormed()); + key.ClearIv(); + + // With key format. + EXPECT_TRUE(key.SetKeyFormat("com.widevine")); + EXPECT_FALSE(key.IsWellFormed()); + key.ClearKeyFormat(); + + // With key format versions. + EXPECT_TRUE(key.AddKeyFormatVersion(1)); + EXPECT_FALSE(key.IsWellFormed()); + key.ClearKeyFormatVersions(); + + // Should be well-formed again. + EXPECT_TRUE(key.IsWellFormed()); +} + +TEST(HlsKeyTest, IsWellFormed_MethodNonNone) { + HlsKey key; + EXPECT_TRUE(key.SetMethod("AES-128")); + // Required fields. + EXPECT_TRUE(key.SetUri("http://domain.tld/key")); + EXPECT_TRUE(key.SetIv(std::vector(HlsKey::kIvLength, 42))); + EXPECT_TRUE(key.IsWellFormed()); + + // Optional fields. + EXPECT_TRUE(key.SetKeyFormat("com.widevine")); + EXPECT_TRUE(key.IsWellFormed()); + + EXPECT_TRUE(key.AddKeyFormatVersion(1)); + EXPECT_TRUE(key.IsWellFormed()); +} + +TEST(HlsKeyTest, IsWellFormed_MethodNonNone_Invalid) { + HlsKey key; + EXPECT_TRUE(key.SetMethod("AES-128")); + // Missing required all fields. + EXPECT_FALSE(key.IsWellFormed()); + + // URI is the only required field. + EXPECT_TRUE(key.SetUri("http://domain.tld/key")); + EXPECT_TRUE(key.IsWellFormed()); + + // Missing URI. + key.ClearUri(); + EXPECT_FALSE(key.IsWellFormed()); +} + +TEST(HlsKeyTest, FromAttributeList_MethodNone) { + HlsAttributeList list; + EXPECT_TRUE(list.SetEnumString("METHOD", "NONE")); + + HlsKey key; + EXPECT_TRUE(key.FromAttributeList(list)); + + EXPECT_TRUE(key.IsWellFormed()); + EXPECT_EQ(key.method(), "NONE"); + EXPECT_FALSE(key.HasUri()); + EXPECT_FALSE(key.HasIv()); + EXPECT_FALSE(key.HasKeyFormat()); + EXPECT_FALSE(key.HasKeyFormatVersions()); +} + +TEST(HlsKeyTest, FromAttributeList_MethodNone_IgnoreOtherFields) { + // The HLS specification states that if METHOD is NONE, then + // there should not be any other fields. However, this parser + // is less strict, and will simply ignore the other fields + // when reading from an attribute list. + HlsAttributeList list; + EXPECT_TRUE(list.SetEnumString("METHOD", "NONE")); + EXPECT_TRUE( + list.SetHexSequence("IV", std::vector(HlsKey::kIvLength, 42))); + EXPECT_TRUE(list.SetQuotedString("URI", "http://example.com/key-data")); + EXPECT_TRUE(list.SetQuotedString("KEYFORMAT", "com.example")); + EXPECT_TRUE(list.SetQuotedString("KEYFORMATVERSIONS", "1337")); + + HlsKey key; + EXPECT_TRUE(key.FromAttributeList(list)); + + EXPECT_TRUE(key.IsWellFormed()); + EXPECT_EQ(key.method(), "NONE"); + EXPECT_FALSE(key.HasUri()); + EXPECT_FALSE(key.HasIv()); + EXPECT_FALSE(key.HasKeyFormat()); + EXPECT_FALSE(key.HasKeyFormatVersions()); +} + +TEST(HlsKeyTest, FromAttributeList_MethodNonNone_OnlyRequiredFields) { + const std::vector kIv(HlsKey::kIvLength, 42); + const std::string kUri = "http://domain.tld/key"; + // HLS standard says to use the following defaults if not + // present in the HLS list. + const std::string kDefaultKeyFormat = "identity"; + const std::set kDefaultKeyFormatVersions = {1}; + HlsAttributeList list; + EXPECT_TRUE(list.SetEnumString("METHOD", "AES-128")); + EXPECT_TRUE(list.SetHexSequence("IV", kIv)); + EXPECT_TRUE(list.SetQuotedString("URI", kUri)); + + HlsKey key; + EXPECT_TRUE(key.FromAttributeList(list)); + + EXPECT_TRUE(key.IsWellFormed()); + EXPECT_EQ(key.method(), "AES-128"); + EXPECT_EQ(key.uri(), kUri); + EXPECT_EQ(key.iv(), kIv); + EXPECT_EQ(key.key_format(), kDefaultKeyFormat); + EXPECT_EQ(key.key_format_versions(), kDefaultKeyFormatVersions); +} + +TEST(HlsKeyTest, FromAttributeList_MethodNonNone_AllFields) { + const std::vector kIv(HlsKey::kIvLength, 42); + const std::string kUri = "http://domain.tld/key"; + const std::string kKeyFormat = "com.widevine"; + const std::set kKeyFormatVersion = {1, 2, 5}; + + HlsAttributeList list; + EXPECT_TRUE(list.SetEnumString("METHOD", "SAMPLE-AES")); + EXPECT_TRUE(list.SetHexSequence("IV", kIv)); + EXPECT_TRUE(list.SetQuotedString("URI", kUri)); + EXPECT_TRUE(list.SetQuotedString("KEYFORMAT", kKeyFormat)); + EXPECT_TRUE(list.SetQuotedString("KEYFORMATVERSIONS", "1/2/5")); + + HlsKey key; + EXPECT_TRUE(key.FromAttributeList(list)); + + EXPECT_TRUE(key.IsWellFormed()); + EXPECT_EQ(key.method(), "SAMPLE-AES"); + EXPECT_EQ(key.uri(), kUri); + EXPECT_EQ(key.iv(), kIv); + EXPECT_EQ(key.key_format(), kKeyFormat); + EXPECT_EQ(key.key_format_versions(), kKeyFormatVersion); +} + +TEST(HlsKeyTest, FromAttributeList_MissingFields_All) { + HlsAttributeList list; + HlsKey key; + EXPECT_FALSE(key.FromAttributeList(list)); +} + +TEST(HlsKeyTest, FromAttributeList_MissingFields_Method) { + const std::string kUri = "http://domain.tld/key"; + const std::vector kIv(HlsKey::kIvLength, 42); + HlsAttributeList list; + EXPECT_TRUE(list.SetQuotedString("URI", kUri)); + EXPECT_TRUE(list.SetHexSequence("IV", kIv)); + + HlsKey key; + EXPECT_FALSE(key.FromAttributeList(list)); + // Nothing should be set. + EXPECT_FALSE(key.HasUri()); + EXPECT_FALSE(key.HasIv()); +} + +TEST(HlsKeyTest, FromAttributeList_MissingFields_MissingUri) { + const std::vector kIv(HlsKey::kIvLength, 42); + HlsAttributeList list; + EXPECT_TRUE(list.SetEnumString("METHOD", "AES-128")); + EXPECT_TRUE(list.SetHexSequence("IV", kIv)); + + HlsKey key; + EXPECT_FALSE(key.FromAttributeList(list)); + // Nothing should be set. + EXPECT_FALSE(key.HasMethod()); + EXPECT_FALSE(key.HasIv()); +} + +TEST(HlsKeyTest, FromAttributeList_InvalidFields_BadMethodType) { + HlsAttributeList list; + // Must be enum string not quoted string. + EXPECT_TRUE(list.SetQuotedString("METHOD", "NONE")); + + HlsKey key; + EXPECT_FALSE(key.FromAttributeList(list)); + EXPECT_FALSE(key.HasMethod()); +} + +TEST(HlsKeyTest, FromAttributeList_InvalidFields_EmptyUri) { + const std::vector kIv(HlsKey::kIvLength, 42); + HlsAttributeList list; + EXPECT_TRUE(list.SetEnumString("METHOD", "AES-128")); + EXPECT_TRUE(list.SetQuotedString("URI", "")); + EXPECT_TRUE(list.SetHexSequence("IV", kIv)); + + HlsKey key; + EXPECT_FALSE(key.FromAttributeList(list)); + // Nothing should be set. + EXPECT_FALSE(key.HasMethod()); + EXPECT_FALSE(key.HasUri()); + EXPECT_FALSE(key.HasIv()); +} + +TEST(HlsKeyTest, FromAttributeList_InvalidFields_BadUriType) { + const std::vector kIv(HlsKey::kIvLength, 42); + HlsAttributeList list; + EXPECT_TRUE(list.SetEnumString("METHOD", "AES-128")); + EXPECT_TRUE(list.SetEnumString("URI", "DATA")); + EXPECT_TRUE(list.SetHexSequence("IV", kIv)); + + HlsKey key; + EXPECT_FALSE(key.FromAttributeList(list)); + // Nothing should be set. + EXPECT_FALSE(key.HasMethod()); + EXPECT_FALSE(key.HasUri()); + EXPECT_FALSE(key.HasIv()); +} + +TEST(HlsKeyTest, FromAttributeList_InvalidFields_ShortIv) { + const std::vector kShortIv(HlsKey::kIvLength / 2, 42); + HlsAttributeList list; + EXPECT_TRUE(list.SetEnumString("METHOD", "AES-128")); + EXPECT_TRUE(list.SetQuotedString("URI", "http://example.com/key-data")); + EXPECT_TRUE(list.SetHexSequence("IV", kShortIv)); + + HlsKey key; + EXPECT_FALSE(key.FromAttributeList(list)); + EXPECT_FALSE(key.HasMethod()); + EXPECT_FALSE(key.HasUri()); + EXPECT_FALSE(key.HasIv()); +} + +TEST(HlsKeyTest, FromAttributeList_InvalidFields_LargeIv) { + const std::vector kLargeIv(HlsKey::kIvLength * 2, 42); + HlsAttributeList list; + EXPECT_TRUE(list.SetEnumString("METHOD", "AES-128")); + EXPECT_TRUE(list.SetQuotedString("URI", "http://example.com/key-data")); + EXPECT_TRUE(list.SetHexSequence("IV", kLargeIv)); + + HlsKey key; + EXPECT_FALSE(key.FromAttributeList(list)); + EXPECT_FALSE(key.HasMethod()); + EXPECT_FALSE(key.HasUri()); + EXPECT_FALSE(key.HasIv()); +} + +TEST(HlsKeyTest, FromAttributeList_InvalidFields_BadIvType) { + HlsAttributeList list; + EXPECT_TRUE(list.SetEnumString("METHOD", "AES-128")); + EXPECT_TRUE(list.SetQuotedString("URI", "http://example.com/key-data")); + EXPECT_TRUE(list.SetQuotedString("IV", "42 repeated 16 times")); + + HlsKey key; + EXPECT_FALSE(key.FromAttributeList(list)); + EXPECT_FALSE(key.HasMethod()); + EXPECT_FALSE(key.HasUri()); + EXPECT_FALSE(key.HasIv()); +} + +TEST(HlsKeyTest, FromAttributeList_InvalidFields_EmptyKeyFormat) { + const std::vector kIv(HlsKey::kIvLength, 42); + HlsAttributeList list; + EXPECT_TRUE(list.SetEnumString("METHOD", "AES-128")); + EXPECT_TRUE(list.SetQuotedString("URI", "http://example.com/key-data")); + EXPECT_TRUE(list.SetHexSequence("IV", kIv)); + EXPECT_TRUE(list.SetQuotedString("KEYFORMAT", "")); + + HlsKey key; + EXPECT_FALSE(key.FromAttributeList(list)); + // Nothing should be set. + EXPECT_FALSE(key.HasMethod()); + EXPECT_FALSE(key.HasUri()); + EXPECT_FALSE(key.HasIv()); +} + +TEST(HlsKeyTest, FromAttributeList_InvalidFields_BadKeyFormatType) { + const std::vector kIv(HlsKey::kIvLength, 42); + HlsAttributeList list; + EXPECT_TRUE(list.SetEnumString("METHOD", "AES-128")); + EXPECT_TRUE(list.SetQuotedString("URI", "http://example.com/key-data")); + EXPECT_TRUE(list.SetHexSequence("IV", kIv)); + EXPECT_TRUE(list.SetEnumString("KEYFORMAT", "COM-EXAMPLE")); + + HlsKey key; + EXPECT_FALSE(key.FromAttributeList(list)); + // Nothing should be set. + EXPECT_FALSE(key.HasMethod()); + EXPECT_FALSE(key.HasUri()); + EXPECT_FALSE(key.HasIv()); + EXPECT_FALSE(key.HasKeyFormat()); +} + +TEST(HlsKeyTest, FromAttributeList_InvalidFields_EmptyKeyFormatVersions) { + const std::vector kIv(HlsKey::kIvLength, 42); + HlsAttributeList list; + EXPECT_TRUE(list.SetEnumString("METHOD", "AES-128")); + EXPECT_TRUE(list.SetQuotedString("URI", "http://example.com/key-data")); + EXPECT_TRUE(list.SetHexSequence("IV", kIv)); + EXPECT_TRUE(list.SetQuotedString("KEYFORMAT", "com.example")); + EXPECT_TRUE(list.SetQuotedString("KEYFORMATVERSIONS", "")); + + HlsKey key; + EXPECT_FALSE(key.FromAttributeList(list)); + // Nothing should be set. + EXPECT_FALSE(key.HasMethod()); + EXPECT_FALSE(key.HasUri()); + EXPECT_FALSE(key.HasIv()); + EXPECT_FALSE(key.HasKeyFormat()); +} + +TEST(HlsKeyTest, FromAttributeList_InvalidFields_ZeroKeyFormatVersions) { + const std::vector kIv(HlsKey::kIvLength, 42); + HlsAttributeList list; + EXPECT_TRUE(list.SetEnumString("METHOD", "AES-128")); + EXPECT_TRUE(list.SetQuotedString("URI", "http://example.com/key-data")); + EXPECT_TRUE(list.SetHexSequence("IV", kIv)); + EXPECT_TRUE(list.SetQuotedString("KEYFORMAT", "com.example")); + EXPECT_TRUE(list.SetQuotedString("KEYFORMATVERSIONS", "0/1/5")); + + HlsKey key; + EXPECT_FALSE(key.FromAttributeList(list)); + // Nothing should be set. + EXPECT_FALSE(key.HasMethod()); + EXPECT_FALSE(key.HasUri()); + EXPECT_FALSE(key.HasIv()); + EXPECT_FALSE(key.HasKeyFormat()); + EXPECT_FALSE(key.HasKeyFormatVersions()); +} + +TEST(HlsKeyTest, FromAttributeList_InvalidFields_BadKeyFormatVersionsFormat) { + const std::vector kIv(HlsKey::kIvLength, 42); + HlsAttributeList list; + EXPECT_TRUE(list.SetEnumString("METHOD", "AES-128")); + EXPECT_TRUE(list.SetQuotedString("URI", "http://example.com/key-data")); + EXPECT_TRUE(list.SetHexSequence("IV", kIv)); + EXPECT_TRUE(list.SetQuotedString("KEYFORMAT", "com.example")); + EXPECT_TRUE(list.SetQuotedString("KEYFORMATVERSIONS", "1,2,5")); + + HlsKey key; + EXPECT_FALSE(key.FromAttributeList(list)); + // Nothing should be set. + EXPECT_FALSE(key.HasMethod()); + EXPECT_FALSE(key.HasUri()); + EXPECT_FALSE(key.HasIv()); + EXPECT_FALSE(key.HasKeyFormat()); + EXPECT_FALSE(key.HasKeyFormatVersions()); +} + +TEST(HlsKeyTest, FromAttributeList_InvalidFields_BadKeyFormatVersionsType) { + const std::vector kIv(HlsKey::kIvLength, 42); + HlsAttributeList list; + EXPECT_TRUE(list.SetEnumString("METHOD", "AES-128")); + EXPECT_TRUE(list.SetQuotedString("URI", "http://example.com/key-data")); + EXPECT_TRUE(list.SetHexSequence("IV", kIv)); + EXPECT_TRUE(list.SetQuotedString("KEYFORMAT", "com.example")); + EXPECT_TRUE(list.SetInteger("KEYFORMATVERSIONS", 1)); + + HlsKey key; + EXPECT_FALSE(key.FromAttributeList(list)); + // Nothing should be set. + EXPECT_FALSE(key.HasMethod()); + EXPECT_FALSE(key.HasUri()); + EXPECT_FALSE(key.HasIv()); + EXPECT_FALSE(key.HasKeyFormat()); + EXPECT_FALSE(key.HasKeyFormatVersions()); +} + +TEST(HlsKeyTest, FromAttributeList_InvalidList) { + // Populate HLS Attribute List as if it is a different message. + // Using EXT-X-MEDIA. + HlsAttributeList list; + EXPECT_TRUE(list.SetEnumString("TYPE", "AUDIO")); + EXPECT_TRUE(list.SetQuotedString("URI", "http://example.com/video-data")); + EXPECT_TRUE(list.SetQuotedString("GROUP-ID", "Video Group")); + EXPECT_TRUE(list.SetQuotedString("LANGUAGE", "en")); + EXPECT_TRUE(list.SetQuotedString("NAME", "A nice video")); + EXPECT_TRUE(list.SetEnumString("DEFAULT", "YES")); + + HlsKey key; + EXPECT_FALSE(key.FromAttributeList(list)); +} + +TEST(HlsKeyTest, ParseAttributeList_MethodNone) { + HlsKey key; + EXPECT_TRUE(key.ParseAttributeList("METHOD=NONE")); + + EXPECT_TRUE(key.IsWellFormed()); + EXPECT_EQ(key.method(), "NONE"); +} + +TEST(HlsKeyTest, ParseAttributeList_MethodNonNone_OnlyRequiredFields) { + const std::string kUri = "http://domain.tld/key"; + const std::vector kEmptyIv; + const std::string kDefaultKeyFormat = "identity"; + const std::set kDefaultKeyFormatVersions = {1}; + + std::ostringstream hls_stream; + hls_stream << "METHOD=AES-128,"; + hls_stream << "URI=\"" << kUri << "\""; + + const std::string key_rep = hls_stream.str(); + HlsKey key; + EXPECT_TRUE(key.ParseAttributeList(key_rep)); + + EXPECT_TRUE(key.IsWellFormed()); + EXPECT_EQ(key.method(), "AES-128"); + EXPECT_EQ(key.uri(), kUri); + EXPECT_EQ(key.iv(), kEmptyIv); + EXPECT_EQ(key.key_format(), kDefaultKeyFormat); + EXPECT_EQ(key.key_format_versions(), kDefaultKeyFormatVersions); +} + +TEST(HlsKeyTest, ParseAttributeList_MethodNonNone_AllFields) { + const std::string kIvHex(HlsKey::kIvLength * 2, 'A'); + const std::vector kIv(HlsKey::kIvLength, 0xaa); + const std::string kUri = "http://domain.tld/key"; + const std::string kKeyFormat = "com.widevine"; + const std::set kKeyFormatVersion = {1, 2, 5}; + + std::ostringstream hls_stream; + hls_stream << "METHOD=SAMPLE-AES,"; + hls_stream << "URI=\"" << kUri << "\","; + hls_stream << "IV=0x" << kIvHex << ','; + hls_stream << "KEYFORMAT=\"" << kKeyFormat << "\","; + hls_stream << "KEYFORMATVERSIONS=\"1/2/5\""; + + const std::string key_rep = hls_stream.str(); + HlsKey key; + EXPECT_TRUE(key.ParseAttributeList(key_rep)); + + EXPECT_TRUE(key.IsWellFormed()); + EXPECT_EQ(key.method(), "SAMPLE-AES"); + EXPECT_EQ(key.uri(), kUri); + EXPECT_EQ(key.iv(), kIv); + EXPECT_EQ(key.key_format(), kKeyFormat); + EXPECT_EQ(key.key_format_versions(), kKeyFormatVersion); +} + +TEST(HlsKeyTest, ParseAttributeList_Empty) { + HlsKey key; + EXPECT_FALSE(key.ParseAttributeList("")); +} + +TEST(HlsKeyTest, ParseAttributeList_InvalidAttributeList) { + HlsKey key; + EXPECT_FALSE(key.ParseAttributeList("Not an Attribute List")); +} +} // namespace test +} // namespace wvutil