diff --git a/CHANGELOG.md b/CHANGELOG.md index 579e4af..c5e318f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,46 @@ [TOC] +## [Version 19.2][v19.2] + +This patch provides provisioning 3.0 for OEMs using OPK. Also includes bug +fixes and test improvements for ODK and OPK. + +**It is strongly recommended** for partners to upgrade ODK from 19.0 / 19.1 +to 19.2 due to the backwards compatibility fixes for devices using License +Protocol v2.2 interacting with pre v2.2 servers. + +General + +- Clarified signing algorithm for OEMCrypto_PrepAndSign*() functions in + OEMCryptoCENC.h header. +- New L3 API function OEMCrypto_WrapClearPrivateKey() for factory builds. +- New L3 API function OEMCrypto_MarkOfflineSession() for informing OEMCrypto + that a session is used for a reloaded offline license. +- Clarified HDCP level enforcement for downstream devices (see OEMCrypto + documentation). + +ODK Library + +- Fixed backwards compatibility issues found in License Protocol v2.2 + - Issue was found where license keys were missing in core message + due to server error, but are recoverable by the device with ODK patch. + - This was the root case of Android GTS test failure + `testWidevineLicenseService16_4_2`. +- Fixed timer status updating when attempting first playback before + start of playback window. + +OPK serialization layer + +- Remove unused serialization code. + +OPK TA + +- Added provisioning 3.0 support. +- Added tests for provisioning 3.0 support. +- Improved backwards compatibility for DRM key wrapping. +- Added secure clearing of buffers contain sensitive data. + ## [Version 19.1][v19.1] This is a minor release that includes a few security fixes and bug fixes. @@ -582,7 +622,6 @@ for more details. Public release for OEMCrypto API and ODK library version 16.4. - [v16.4]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v16.4 [v16.4+extra-test]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v16.4+extra-tests [v16.4+doc-updates]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v16.4+doc-updates @@ -599,3 +638,4 @@ Public release for OEMCrypto API and ODK library version 16.4. [v18.4]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v18.4 [v19.0]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v19.0 [v19.1]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v19.1 +[v19.2]: https://widevine-partner.googlesource.com/oemcrypto/+/refs/tags/v19.2 diff --git a/oemcrypto/include/OEMCryptoCENC.h b/oemcrypto/include/OEMCryptoCENC.h index 0131355..c710f09 100644 --- a/oemcrypto/include/OEMCryptoCENC.h +++ b/oemcrypto/include/OEMCryptoCENC.h @@ -3,7 +3,7 @@ // License Agreement. /** - * @mainpage OEMCrypto API v19.1 + * @mainpage OEMCrypto API v19.2 * * OEMCrypto is the low level library implemented by the OEM to provide key and * content protection, usually in a separate secure memory or process space. The @@ -744,6 +744,8 @@ typedef enum OEMCrypto_SignatureHashAlgorithm { #define OEMCrypto_LoadRelease _oecc150 #define OEMCrypto_GetEmbeddedDrmCertificate _oecc151 #define OEMCrypto_UseSecondaryKey _oecc152 +#define OEMCrypto_MarkOfflineSession _oecc153 +#define OEMCrypto_WrapClearPrivateKey _oecc154 // clang-format on /// @addtogroup initcontrol @@ -1030,9 +1032,12 @@ OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session, /** * OEMCrypto will use ODK_PrepareCoreLicenseRequest to prepare the core - * message. If it returns OEMCrypto_SUCCESS, then OEMCrypto shall sign the - * message body using the DRM certificate's private key. If it returns an - * error, the error should be returned by OEMCrypto to the CDM layer. + * message. If it returns OEMCrypto_SUCCESS, then OEMCrypto shall generate + * a SHA-512 hash over the whole message buffer (includes both the prepared + * core message and message body), then sign the resulting hash using the + * DRM certificate's private key. If ODK returns an error, the error should + * be returned by OEMCrypto to the CDM layer. + * * ODK_PrepareCoreLicenseRequest is described in the document "Widevine Core * Message Serialization". * @@ -1113,11 +1118,16 @@ OEMCryptoResult OEMCrypto_PrepAndSignLicenseRequest( * corresponding generation number in the usage table header is also incremented * so that it matches the one in the entry. * - * OEMCrypto will use ODK_PrepareCoreLicenseRelease to prepare the core - * message. If it returns OEMCrypto_SUCCESS, then OEMCrypto shall sign the - * message body using the DRM certificate's private key. If it returns an - * error, the error should be returned by OEMCrypto to the CDM layer. - * ODK_PrepareCoreLicenseRelease is described in the document "Widevine Core + * OEMCrypto will use ODK_PrepareCoreReleaseRequest to prepare the core + * message. If it returns OEMCrypto_SUCCESS, then OEMCrypto shall generate + * the signature using the whole message buffer (includes both the prepared + * core message and message body) using the session's client MAC key client + * which should be the client renewal MAC key if available; otherwise, the + * signing is performed using the session's Usage Entry's client MAC key. + * If ODK returns an error, the error should be returned by OEMCrypto to the + * CDM layer. + * + * ODK_PrepareCoreReleaseRequest is described in the document "Widevine Core * Message Serialization". * * This function generates a HMAC-SHA256 signature using the mac_key[client] @@ -1180,22 +1190,10 @@ OEMCryptoResult OEMCrypto_PrepAndSignReleaseRequest( * document "Widevine Core Message Serialization", to prepare the core * message. * - * If it returns an error, the error should be returned by OEMCrypto to the - * CDM layer. If it returns OEMCrypto_SUCCESS, then OEMCrypto computes the - * signature using the renewal mac key which was delivered in the license via - * LoadLicense. - * - * If nonce_values.api_level is 16, then OEMCrypto shall compute the - * signature of the entire message using the session's client renewal mac - * key. The entire message is the buffer starting at message with length - * message_length. - * - * If nonce_values.api_major_version is 15, then OEMCrypto shall compute the - * signature of the message body using the session's client renewal mac key. - * The message body is the buffer starting at message+core_message_size with - * length message_length - core_message_size. If the session has not had a - * license loaded, it will use the usage entries client mac key to sign the - * message body. + * If ODK returns an error, the error should be returned by OEMCrypto to the + * CDM layer. If it returns OEMCrypto_SUCCESS, then OEMCrypto shall generate + * the signature using the whole message buffer (includes both the prepared + * core message and message body) using the session's client MAC key client. * * This function generates a HMAC-SHA256 signature using the mac_key[client] * for license request signing under the license server protocol for CENC. @@ -3178,6 +3176,51 @@ OEMCryptoResult OEMCrypto_IsKeyboxOrOEMCertValid(void); OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* device_id, size_t* device_id_length); +/** + * Encrypts a clear device RSA/ECC key with an internal key (such as the OEM + * key or Widevine Keybox key) and a generated IV using AES-128-CBC with PKCS#5 + * padding. + * + * Copies the wrapped key to the buffer specified by |wrapped_private_key| and + * sets the size of the wrapped key to |wrapped_private_key_length|. + * + * The clear private key is encoded in PKCS#8 binary DER format. The OEMCrypto + * library shall verify that this RSA key is valid. + * + * The clear key should be encrypted using the same device specific key used in + * OEMCrypto_LoadProvisioning. The wrapped private key will be unwrapped in the + * function OEMCrypto_LoadDRMPrivateKey. + * + * This function should only be implemented for factory builds. + * + * @param[in] clear_private_key_bytes: pointer to memory containing the + * unencrypted private key data. + * @param[in] clear_private_key_length: the length of the private key data. + * @param[out] wrapped_private_key: pointer to buffer in which the encrypted + * private key should be stored. May be null on the first call in order to + * find required buffer size. + * @param[in,out] wrapped_private_key_length: (in) length of the encrypted + * private key, in bytes. (out) actual length of the encrypted private key, + * or required length if provided length is too small. + * + * @retval OEMCrypto_SUCCESS on success + * @retval OEMCrypto_ERROR_INVALID_CONTEXT clear_private_key_bytes is NULL, or + * clear private key fails to parse as PKCS#8 + * @retval OEMCrypto_ERROR_SHORT_BUFFER wrapped_private_key_length is too small, + * or wrapped_private_key is NULL + * + * @threading + * This is an "Initialization and Termination Function" and will not be + * called simultaneously with any other function, as if the CDM holds a write + * lock on the OEMCrypto system. + * + * @version + * This method is new in API version 19.2. + */ +OEMCryptoResult OEMCrypto_WrapClearPrivateKey( + const uint8_t* clear_private_key_bytes, size_t clear_private_key_length, + uint8_t* wrapped_private_key, size_t* wrapped_private_key_length); + /// @} /// @addtogroup keybox @@ -6138,10 +6181,28 @@ OEMCryptoResult OEMCrypto_GetEmbeddedDrmCertificate(uint8_t* public_cert, * * @ignore * @retval OEMCrypto_SUCCESS on success + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_SESSION_STATE_LOST + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED */ OEMCryptoResult OEMCrypto_UseSecondaryKey(OEMCrypto_SESSION session_id, bool dual_key); +/** + * Marks the given session as being used for existing offline licenses. + * + * @param[in] session: session id for operation. + * + * @ignore + * @retval OEMCrypto_SUCCESS on success + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_SESSION_STATE_LOST + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + */ +OEMCryptoResult OEMCrypto_MarkOfflineSession(OEMCrypto_SESSION session); + #ifdef __cplusplus } #endif diff --git a/oemcrypto/include/level3.h b/oemcrypto/include/level3.h index 677d29a..f3e225d 100644 --- a/oemcrypto/include/level3.h +++ b/oemcrypto/include/level3.h @@ -127,6 +127,7 @@ #define Level3_LoadRelease _lcc150 #define Level3_GetEmbeddedDrmCertificate _lcc151 #define Level3_UseSecondaryKey _lcc152 +#define Level3_MarkOfflineSession _lcc153 #else #define Level3_Initialize _oecc01 #define Level3_Terminate _oecc02 @@ -238,6 +239,7 @@ // Internal-only. #define Level3_GetEmbeddedDrmCertificate _oecc151 #define Level3_UseSecondaryKey _oecc152 +#define Level3_MarkOfflineSession _oecc153 #endif #define Level3_GetInitializationState _oecl3o01 @@ -536,6 +538,7 @@ 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); // The following are specific to Google's Level 3 implementation and are not // required. diff --git a/oemcrypto/odk/include/OEMCryptoCENCCommon.h b/oemcrypto/odk/include/OEMCryptoCENCCommon.h index f5317a5..ffc70ab 100644 --- a/oemcrypto/odk/include/OEMCryptoCENCCommon.h +++ b/oemcrypto/odk/include/OEMCryptoCENCCommon.h @@ -93,6 +93,7 @@ typedef enum OEMCryptoResult { OEMCrypto_ERROR_DVR_FORBIDDEN = 63, OEMCrypto_ERROR_INSUFFICIENT_PRIVILEGE = 64, OEMCrypto_ERROR_INVALID_KEY = 65, + OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE = 66, /* ODK return values */ ODK_ERROR_BASE = 1000, ODK_ERROR_CORE_MESSAGE = ODK_ERROR_BASE, diff --git a/oemcrypto/odk/include/core_message_features.h b/oemcrypto/odk/include/core_message_features.h index b2dda6a..36d9d83 100644 --- a/oemcrypto/odk/include/core_message_features.h +++ b/oemcrypto/odk/include/core_message_features.h @@ -26,9 +26,9 @@ struct CoreMessageFeatures { // This is the published version of the ODK Core Message library. The default // behavior is for the server to restrict messages to at most this version - // number. The default is 19.1. + // number. The default is 19.2. uint32_t maximum_major_version = 19; - uint32_t maximum_minor_version = 1; + uint32_t maximum_minor_version = 2; bool operator==(const CoreMessageFeatures &other) const; bool operator!=(const CoreMessageFeatures &other) const { diff --git a/oemcrypto/odk/include/odk_structs.h b/oemcrypto/odk/include/odk_structs.h index 3335f95..55713f4 100644 --- a/oemcrypto/odk/include/odk_structs.h +++ b/oemcrypto/odk/include/odk_structs.h @@ -16,10 +16,10 @@ extern "C" { /* The version of this library. */ #define ODK_MAJOR_VERSION 19 -#define ODK_MINOR_VERSION 1 +#define ODK_MINOR_VERSION 2 /* ODK Version string. Date changed automatically on each release. */ -#define ODK_RELEASE_DATE "ODK v19.1 2024-03-25" +#define ODK_RELEASE_DATE "ODK v19.2 2024-06-11" /* The lowest version number for an ODK message. */ #define ODK_FIRST_VERSION 16 diff --git a/oemcrypto/odk/src/core_message_features.cpp b/oemcrypto/odk/src/core_message_features.cpp index 4143d14..249bfdf 100644 --- a/oemcrypto/odk/src/core_message_features.cpp +++ b/oemcrypto/odk/src/core_message_features.cpp @@ -33,7 +33,7 @@ CoreMessageFeatures CoreMessageFeatures::DefaultFeatures( features.maximum_minor_version = 4; // 18.4 break; case 19: - features.maximum_minor_version = 1; // 19.1 + features.maximum_minor_version = 2; // 19.2 break; default: features.maximum_minor_version = 0; diff --git a/oemcrypto/odk/src/core_message_serialize_proto.cpp b/oemcrypto/odk/src/core_message_serialize_proto.cpp index 2559012..edb9453 100644 --- a/oemcrypto/odk/src/core_message_serialize_proto.cpp +++ b/oemcrypto/odk/src/core_message_serialize_proto.cpp @@ -45,7 +45,7 @@ OEMCrypto_Substring GetOecSubstring(std::string_view message, } OEMCrypto_KeyObject KeyContainerToOecKey( - const std::string& proto, const video_widevine::License::KeyContainer& k, + std::string_view proto, const video_widevine::License::KeyContainer& k, const bool uses_padding) { OEMCrypto_KeyObject obj = {}; obj.key_id = GetOecSubstring(proto, k.id()); diff --git a/oemcrypto/odk/src/odk_serialize.c b/oemcrypto/odk/src/odk_serialize.c index 04982fa..1f2e48e 100644 --- a/oemcrypto/odk/src/odk_serialize.c +++ b/oemcrypto/odk/src/odk_serialize.c @@ -8,6 +8,8 @@ #include "odk_serialize.h" +#include "odk_message.h" +#include "odk_overflow.h" #include "odk_structs_priv.h" #include "serialization_base.h" @@ -237,6 +239,36 @@ static void Unpack_OEMCrypto_KeyObject(ODK_Message* msg, Unpack_OEMCrypto_Substring(msg, &obj->key_data); Unpack_OEMCrypto_Substring(msg, &obj->key_control_iv); Unpack_OEMCrypto_Substring(msg, &obj->key_control); + + /* + Edge case for servers that incorrectly process protocol VERSION_2_2 padding. + Key data in proto is present, but each key's position in the core + message is missing. + + Use the key_data_iv offset to determine if the key_data is present. + This assumes that the serialized protobuf is deterministically ordered, and + that the content key is always 16 bytes. These assumptions should hold true + for v16 and older servers. + */ + if (ODK_Message_GetStatus(msg) == MESSAGE_STATUS_OK && + obj->key_data.offset == 0 && obj->key_data.length == 0) { + const size_t kKeyDataProtoHeaderSize = 2; + obj->key_data.offset = obj->key_data_iv.offset + obj->key_data_iv.length + + kKeyDataProtoHeaderSize; + obj->key_data.length = 16u; // assume 16-byte key + + // Check for overflow. The offset is relative to the end of the core + // message, so add that length to the calculation. + size_t substring_end = 0; // offset + length + size_t end = 0; // offset + length + message_length + if (odk_add_overflow_ux(obj->key_data.offset, obj->key_data.length, + &substring_end) || + odk_add_overflow_ux(substring_end, ODK_Message_GetSize(msg), &end) || + end > ODK_Message_GetCapacity(msg)) { + ODK_Message_SetStatus(msg, MESSAGE_STATUS_OVERFLOW_ERROR); + return; + } + } } static void Unpack_ODK_TimerLimits(ODK_Message* msg, ODK_TimerLimits* obj) { diff --git a/oemcrypto/odk/src/odk_timer.c b/oemcrypto/odk/src/odk_timer.c index c76fc5a..d5523ec 100644 --- a/oemcrypto/odk/src/odk_timer.c +++ b/oemcrypto/odk/src/odk_timer.c @@ -277,7 +277,7 @@ OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits, nonce_values->api_minor_version = 4; break; case 19: - nonce_values->api_minor_version = 1; + nonce_values->api_minor_version = 2; break; default: nonce_values->api_minor_version = 0; @@ -355,7 +355,7 @@ OEMCryptoResult ODK_AttemptFirstPlayback(uint64_t system_time_seconds, return OEMCrypto_ERROR_INVALID_CONTEXT; } if (rental_time < timer_limits->earliest_playback_start_seconds) { - clock_values->timer_status = ODK_TIMER_EXPIRED; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_EXPIRED; return ODK_TIMER_EXPIRED; } /* If the license is inactive or not loaded, then playback is not allowed. */ diff --git a/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.cpp b/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.cpp index fbc00dc..b7797fd 100644 --- a/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.cpp +++ b/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.cpp @@ -10,6 +10,7 @@ #include "odk.h" #include "odk_attributes.h" #include "odk_structs.h" +#include "odk_target.h" namespace oemcrypto_core_message { using features::CoreMessageFeatures; @@ -148,7 +149,7 @@ bool kdo_serialize_LicenseResponse(const ODK_ParseLicense_Args* args, parsed_license.key_array_length = parsed_lic.key_array_length; std::vector key_array; size_t i; - for (i = 0; i < parsed_lic.key_array_length; i++) { + for (i = 0; i < parsed_lic.key_array_length && i < ODK_MAX_NUM_KEYS; i++) { key_array.push_back(parsed_lic.key_array[i]); } parsed_license.key_array = key_array.data(); diff --git a/oemcrypto/odk/test/odk_test.cpp b/oemcrypto/odk/test/odk_test.cpp index 5201ff8..1cc2f6c 100644 --- a/oemcrypto/odk/test/odk_test.cpp +++ b/oemcrypto/odk/test/odk_test.cpp @@ -1275,7 +1275,7 @@ std::vector TestCases() { {16, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 16, 5}, {17, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 17, 2}, {18, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 18, 4}, - {19, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 19, 1}, + {19, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 19, 2}, // Here are some known good versions. Make extra sure they work. {ODK_MAJOR_VERSION, 16, 3, 16, 3}, {ODK_MAJOR_VERSION, 16, 4, 16, 4}, @@ -1287,6 +1287,7 @@ std::vector TestCases() { {ODK_MAJOR_VERSION, 18, 3, 18, 3}, {ODK_MAJOR_VERSION, 19, 0, 19, 0}, {ODK_MAJOR_VERSION, 19, 1, 19, 1}, + {ODK_MAJOR_VERSION, 19, 2, 19, 2}, {0, 16, 3, 16, 3}, {0, 16, 4, 16, 4}, {0, 16, 5, 16, 5}, @@ -1296,6 +1297,7 @@ std::vector TestCases() { {0, 18, 4, 18, 4}, {0, 19, 0, 19, 0}, {0, 19, 1, 19, 1}, + {0, 19, 2, 19, 2}, }; return test_cases; } diff --git a/oemcrypto/opk/build/ree-sources.mk b/oemcrypto/opk/build/ree-sources.mk index 931138c..3a72ce5 100644 --- a/oemcrypto/opk/build/ree-sources.mk +++ b/oemcrypto/opk/build/ree-sources.mk @@ -279,6 +279,7 @@ wtpi_unittests_sources += \ $(wtpi_unittests_dir)/ssl_util.cpp \ $(wtpi_unittests_dir)/cose_util.cpp \ $(wtpi_unittests_dir)/test_rsa_key.cpp \ + $(wtpi_unittests_dir)/legacy_keywrap_test.cpp \ $(log_dir)/log.cpp \ $(util_dir)/src/string_conversions.cpp \ $(odk_sources) \ diff --git a/oemcrypto/opk/oemcrypto_ta/oemcrypto.c b/oemcrypto/opk/oemcrypto_ta/oemcrypto.c index f01523a..58660af 100644 --- a/oemcrypto/opk/oemcrypto_ta/oemcrypto.c +++ b/oemcrypto/opk/oemcrypto_ta/oemcrypto.c @@ -260,26 +260,152 @@ cleanup: return result; } +static OEMCryptoResult DecryptMessageKeyWithOEMPrivateKey( + const uint8_t* encrypted_message_key, size_t encrypted_message_key_length, + uint8_t* message_key, size_t* message_key_length) { + ABORT_IF_NULL(encrypted_message_key); + ABORT_IF_ZERO(encrypted_message_key_length); + ABORT_IF_NULL(message_key); + ABORT_IF_NULL(message_key_length); + WTPI_AsymmetricKey_Handle oem_key_handle; + OEMCryptoResult result = + WTPI_CreateAsymmetricKeyHandleFromOEMKey(&oem_key_handle); + if (result != OEMCrypto_SUCCESS) return result; + result = WTPI_RSADecrypt(oem_key_handle, encrypted_message_key, + encrypted_message_key_length, message_key, + message_key_length); + WTPI_FreeAsymmetricKeyHandle(oem_key_handle); + return result; +} + +static OEMCryptoResult RewrapDeviceDRMKeyOEMCert( + OEMCryptoSession* session_context, const uint8_t* encrypted_message_key, + size_t encrypted_message_key_length, const uint8_t* enc_drm_key, + size_t enc_drm_key_length, const uint8_t* enc_drm_key_iv, + AsymmetricKeyType drm_key_type, uint8_t* wrapped_drm_key, + size_t wrapped_drm_key_length) { + ABORT_IF(g_opk_system_state != SYSTEM_INITIALIZED, + "OEMCrypto is not yet initialized"); + ABORT_IF(WTPI_GetProvisioningMethod() != OEMCrypto_OEMCertificate, + "This function is only valid on Provisioning 3.0 devices"); + ABORT_IF_NULL(session_context); + ABORT_IF_NULL(encrypted_message_key); + ABORT_IF_ZERO(encrypted_message_key_length); + ABORT_IF_NULL(enc_drm_key); + ABORT_IF_ZERO(enc_drm_key_length); + ABORT_IF(enc_drm_key_length > PKCS8_DRM_KEY_MAX_SIZE, + "enc_drm_key_length of %zu is too large", enc_drm_key_length); + ABORT_IF_NULL(enc_drm_key_iv); + ABORT_IF(!IsSupportedAsymmetricKeyType(drm_key_type), + "drm_key_type %d is invalid", drm_key_type); + ABORT_IF_NULL(wrapped_drm_key); + ABORT_IF_ZERO(wrapped_drm_key_length); + + /* Decrypt message key with OEM private key. */ + uint8_t message_key[KEY_SIZE_3072]; + size_t message_key_length = sizeof(message_key); + OEMCryptoResult result = DecryptMessageKeyWithOEMPrivateKey( + encrypted_message_key, encrypted_message_key_length, message_key, + &message_key_length); + if (result != OEMCrypto_SUCCESS) { + session_context->state = SESSION_INVALID; + WTPI_SecureZeroMemory(message_key, sizeof(message_key)); + return result; + } + + if (message_key_length != KEY_SIZE_128) { + /* Encryption key is expected to be an AES 128-bit key. */ + session_context->state = SESSION_INVALID; + WTPI_SecureZeroMemory(message_key, sizeof(message_key)); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + /* Install message key and decrypt DRM private key with the message key. */ + uint8_t clear_drm_key[PKCS8_DRM_KEY_MAX_SIZE]; + result = OPKI_CreateKey(&session_context->encryption_key, ENCRYPTION_KEY, + KEY_SIZE_128); + if (result != OEMCrypto_SUCCESS) goto cleanup; + result = + WTPI_K1_CreateKeyHandle(message_key, message_key_length, ENCRYPTION_KEY, + &(session_context->encryption_key->key_handle)); + if (result != OEMCrypto_SUCCESS) goto cleanup; + result = WTPI_C1_AESCBCDecrypt( + session_context->encryption_key->key_handle, (size_t)KEY_SIZE_128, + enc_drm_key, enc_drm_key_length, enc_drm_key_iv, clear_drm_key); + if (result != OEMCrypto_SUCCESS) { + LOGE("Failed to AES CBC decrypt DRM private key with result: %u", result); + goto cleanup; + } + /* Create asymmetric key handle for DRM private key in order to get the + * signature size. */ + WTPI_AsymmetricKey_Handle private_key_handle; + result = WTPI_CreateAsymmetricKeyHandle(clear_drm_key, enc_drm_key_length, + drm_key_type, &private_key_handle); + if (result != OEMCrypto_SUCCESS) { + LOGE( + "Failed to create asymmetric key handle for DRM private key with " + "result: %u", + result); + goto cleanup; + } + size_t signature_size; + result = WTPI_GetSignatureSize(private_key_handle, &signature_size); + WTPI_FreeAsymmetricKeyHandle(private_key_handle); + if (result != OEMCrypto_SUCCESS) { + LOGE("Failed to get DRM key signature size with result: %u", result); + goto cleanup; + } + /* Wrap DRM private key with device key. */ + result = WTPI_WrapAsymmetricKey(DEVICE_KEY_WRAP_DRM_CERT, wrapped_drm_key, + wrapped_drm_key_length, drm_key_type, + clear_drm_key, enc_drm_key_length); + if (result != OEMCrypto_SUCCESS) { + LOGE("Failed to re-wrap DRM private key with result: %u", result); + goto cleanup; + } + /* Check that it's a valid DRM key. */ + result = + OPKI_LoadDRMKey(session_context, drm_key_type, wrapped_drm_key, + wrapped_drm_key_length, signature_size, kSign_RSASSA_PSS); + if (result != OEMCrypto_SUCCESS) { + LOGE("Failed to load DRM key with result: %u", result); + } + +cleanup: + WTPI_SecureZeroMemory(message_key, sizeof(message_key)); + WTPI_SecureZeroMemory(clear_drm_key, sizeof(clear_drm_key)); + + OEMCryptoResult free_key_result = + OPKI_FreeAsymmetricKeyFromTable(&session_context->drm_private_key); + if (result == OEMCrypto_SUCCESS) result = free_key_result; + + free_key_result = FreeMacAndEncryptionKeys(session_context); + if (result == OEMCrypto_SUCCESS) result = free_key_result; + + if (result != OEMCrypto_SUCCESS) session_context->state = SESSION_INVALID; + return result; +} + static OEMCryptoResult GetDeviceID(uint8_t* device_id, size_t* device_id_length) { if (device_id_length == NULL) { return OEMCrypto_ERROR_INVALID_CONTEXT; } - if (WTPI_GetProvisioningMethod() != OEMCrypto_Keybox && - WTPI_GetProvisioningMethod() != OEMCrypto_BootCertificateChain) { + const OEMCrypto_ProvisioningMethod provisioning_method = + WTPI_GetProvisioningMethod(); + if (provisioning_method != OEMCrypto_Keybox && + provisioning_method != OEMCrypto_OEMCertificate && + provisioning_method != OEMCrypto_BootCertificateChain) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } size_t actual_device_id_size = 0; if (WTPI_GetDeviceIDLength(&actual_device_id_size) != OEMCrypto_SUCCESS) { return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - if (*device_id_length < actual_device_id_size) { + if (device_id == NULL || *device_id_length < actual_device_id_size) { *device_id_length = actual_device_id_size; return OEMCrypto_ERROR_SHORT_BUFFER; } - if (device_id == NULL) { - return OEMCrypto_ERROR_INVALID_CONTEXT; - } *device_id_length = actual_device_id_size; return WTPI_GetDeviceID(device_id, *device_id_length); } @@ -376,26 +502,17 @@ OEMCryptoResult OEMCrypto_Initialize(void) { } result = WTPI_InitializeKeybox(); - if (result != OEMCrypto_SUCCESS) { - LOGD("Initializing keybox error %u. System not ready for production.", - result); + if (result != OEMCrypto_SUCCESS && + WTPI_GetProvisioningMethod() == OEMCrypto_Keybox) { // This means we don't have a keybox installed. This is fine for a test // device or for a device that is still in the factory before it has been // provisioned with a keybox. In that case, we log the error and continue. // A production system will fail later on when it tries to validate the // keybox. + LOGD("Initializing keybox error %u. System not ready for production.", + result); } -#ifdef USE_OPK_FEATURE_SETHDCP - OEMCrypto_HDCP_Capability max_hdcp_level = WTPI_MaxHDCPCapability(); - result = WTPI_SetHDCPLevel(max_hdcp_level); - if (result == OEMCrypto_ERROR_NOT_IMPLEMENTED) { - LOGI("WTPI_SetHDCPLevel() not implemented."); - } else if (result != OEMCrypto_SUCCESS) { - LOGE("WTPI_SetHDCPLevel() failed for target level %d", max_hdcp_level); - } -#endif // USE_OPK_FEATURE_SETHDCP - OPKI_InitializeSessionTable(); OPKI_InitializeEntitledKeySessionTable(); OPKI_InitializeKeyTable(); @@ -451,15 +568,6 @@ OEMCryptoResult OEMCrypto_Terminate(void) { OEMCryptoResult asymmetric_key_terminate_result = OPKI_TerminateAsymmetricKeyTable(); -#ifdef USE_OPK_FEATURE_SETHDCP - OEMCryptoResult hdcp_result = WTPI_SetHDCPLevel(HDCP_NONE); - if (hdcp_result != OEMCrypto_SUCCESS || - hdcp_result != OEMCrypto_ERROR_NOT_IMPLEMENTED) { - // Log failure for now, allow to continue - LOGE("WTPI_SetHDCPLevel() failed for target level %d", max_hdcp_level); - } -#endif // USE_OPK_FEATURE_SETHDCP - OEMCryptoResult keybox_result = WTPI_TerminateKeybox(); OEMCryptoResult clock_result = WTPI_TerminateClock(); OEMCryptoResult key_management_result = WTPI_K1_TerminateKeyManagement(); @@ -522,9 +630,27 @@ OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session) { } ABORT_IF(session_context == NULL, "OPKI_GetSession() provided invalid output."); + result = OPKI_CheckStatePreCall(session_context, API_OPENSESSION); if (result != OEMCrypto_SUCCESS) goto cleanup; result = OPKI_SetStatePostCall(session_context, API_OPENSESSION); + if (result != OEMCrypto_SUCCESS) goto cleanup; + +#ifdef USE_OPK_FEATURE_SETHDCP + size_t count; + result = OEMCrypto_GetNumberOfOpenSessions(&count); + if (result != OEMCrypto_SUCCESS) goto cleanup; + + if (count == 1) { // the session was the first session to be opened. + OEMCrypto_HDCP_Capability max_hdcp_level = WTPI_MaxHDCPCapability(); + result = WTPI_SetHDCPLevel(max_hdcp_level); + if (result == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + LOGD("WTPI_SetHDCPLevel() not implemented."); + } else if (result != OEMCrypto_SUCCESS) { + LOGE("WTPI_SetHDCPLevel() failed for target level %d", max_hdcp_level); + } + } +#endif // USE_OPK_FEATURE_SETHDCP cleanup: if (result != OEMCrypto_SUCCESS) { @@ -552,11 +678,28 @@ OEMCryptoResult OEMCrypto_CloseSession(OEMCrypto_SESSION session) { } ABORT_IF(session_context == NULL, "OPKI_GetSession() provided invalid output."); + result = OPKI_CheckStatePreCall(session_context, API_CLOSESESSION); if (result != OEMCrypto_SUCCESS) return result; result = OPKI_FreeEntitledKeySessions(session); - if (result != OEMCrypto_SUCCESS) return result; - return OPKI_FreeSession(session); + OEMCryptoResult free_session_result = OPKI_FreeSession(session); + if (result == OEMCrypto_SUCCESS) result = free_session_result; + +#ifdef USE_OPK_FEATURE_SETHDCP + size_t count; + OEMCryptoResult hdcp_result = OEMCrypto_GetNumberOfOpenSessions(&count); + if (hdcp_result != OEMCrypto_SUCCESS) return result; + + if (count == 0) { // the session was the last one opened. + hdcp_result = WTPI_SetHDCPLevel(HDCP_NONE); + if (hdcp_result == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + LOGD("WTPI_SetHDCPLevel() not implemented."); + } else if (hdcp_result != OEMCrypto_SUCCESS) { + LOGE("WTPI_SetHDCPLevel() failed for target level %d", max_hdcp_level); + } + } +#endif // USE_OPK_FEATURE_SETHDCP + return result; } static OEMCryptoResult DeriveKeysFromSessionKey( @@ -773,8 +916,12 @@ OEMCryptoResult OEMCrypto_PrepAndSignProvisioningRequest( &session_context->nonce_values, device_info, device_info_length, &g_counter_info); } else { - result = GetROTSignatureLength(&required_signature_size); - if (result != OEMCrypto_SUCCESS) return result; + if (provisioning_method == OEMCrypto_OEMCertificate) { + required_signature_size = MAX_ASYMMETRIC_SIGNATURE_SIZE; + } else { + result = GetROTSignatureLength(&required_signature_size); + if (result != OEMCrypto_SUCCESS) return result; + } result = ODK_PrepareCoreProvisioningRequest( message, message_length, core_message_length, &session_context->nonce_values, &g_counter_info); @@ -812,7 +959,6 @@ OEMCryptoResult OEMCrypto_PrepAndSignProvisioningRequest( result); return result; } - result = OPKI_GenerateSignatureWithMacKeyClient( session_context, message, message_length, signature, signature_length); } else if (provisioning_method == OEMCrypto_BootCertificateChain) { @@ -1231,7 +1377,7 @@ static OEMCryptoResult LoadKeysNoSignature( OEMCryptoResult result = OEMCrypto_SUCCESS; session->license_type = license_type; - session->refresh_valid = + const bool refresh_valid = IsSubstrInRange(message_length, enc_mac_keys, false) && IsSubstrInRange(message_length, enc_mac_keys_iv, false); @@ -1297,7 +1443,7 @@ static OEMCryptoResult LoadKeysNoSignature( } } if (result != OEMCrypto_SUCCESS) goto cleanup; - if (session->refresh_valid) { + if (refresh_valid) { result = OPKI_UpdateMacKeys(session, message + enc_mac_keys.offset, message + enc_mac_keys_iv.offset); if (result != OEMCrypto_SUCCESS) { @@ -1376,7 +1522,7 @@ cleanup:; OEMCryptoResult free_key_result = OPKI_FreeKeyFromTable(&session->encryption_key); if (result == OEMCrypto_SUCCESS) result = free_key_result; - if (result != OEMCrypto_SUCCESS || !session->refresh_valid) { + if (result != OEMCrypto_SUCCESS || !refresh_valid) { /* Mac keys must be freed in every case except when the call is successful AND we are given new mac keys. In that case, the new keys should be preserved in the session for future RefreshKeys calls. */ @@ -2384,59 +2530,75 @@ OEMCryptoResult OEMCrypto_Generic_Verify( } OEMCryptoResult OEMCrypto_WrapKeyboxOrOEMCert( - const uint8_t* rot UNUSED, size_t rotLength UNUSED, - uint8_t* wrappedRot UNUSED, size_t* wrappedRotLength UNUSED, - const uint8_t* transportKey UNUSED, size_t transportKeyLength UNUSED) { - // TODO(fredgc, ncbray, marcone): One installation option is that there an - // installation tool that uses a system key to unwrap the keybox and then - // re-wraps it using a device key. This wrapped keybox can be stored on the - // file system. I think we don't plan to use this. If we do, then Widevine - // needs to implement this function, and we have to agree on an porting - // layer interface for: decrypt w/system key -> encrypt w/device key -> - // return buffer. - // - return OEMCrypto_ERROR_NOT_IMPLEMENTED; -} - -OEMCryptoResult OEMCrypto_InstallKeyboxOrOEMCert(const uint8_t* keybox, - size_t length) { + const uint8_t* rot, size_t rot_length, uint8_t* wrapped_rot, + size_t* wrapped_rot_length, const uint8_t* transport_key UNUSED, + size_t transport_key_length UNUSED) { // This API is only allowed in factory mode. #ifdef FACTORY_BUILD_ONLY if (g_opk_system_state != SYSTEM_INITIALIZED) { LOGE("OEMCrypto is not yet initialized"); - RETURN_INVALID_CONTEXT_IF_NULL(keybox); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - - if (WTPI_GetBCCSignatureType() == BCC_SIG_KEYBOX) { - // Install keybox, which is needed to generate BCC sig - OEMCryptoResult res = WTPI_UnwrapValidateAndInstallKeybox(keybox, length); - if (res != OEMCrypto_SUCCESS) return res; - - // Generate BCC signature - uint8_t sig[256] = {0}; - size_t sig_size = 0; - res = WTPI_GenerateBCCSignature(sig, &sig_size); - if (res != OEMCrypto_ERROR_SHORT_BUFFER) return res; - if (sig_size > sizeof(sig)) { - return OEMCrypto_ERROR_INVALID_CONTEXT; - } - res = WTPI_GenerateBCCSignature(sig, &sig_size); - if (res != OEMCrypto_SUCCESS) return res; - - // Save BCC signature - res = OEMCrypto_FactoryInstallBCCSignature(sig, sig_size); - if (res != OEMCrypto_SUCCESS) return res; - - // Strip device key from keybox and re-install - return WTPI_StripKeyboxAndReinstall(); + const OEMCrypto_ProvisioningMethod provisioning_method = + WTPI_GetProvisioningMethod(); + if (provisioning_method != OEMCrypto_OEMCertificate) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; } - - return WTPI_UnwrapValidateAndInstallKeybox(keybox, length); - + return WTPI_WrapOEMCert(rot, rot_length, wrapped_rot, wrapped_rot_length); #else - (void)keybox; - (void)length; + (void)rot; + (void)rot_length; + (void)wrapped_rot; + (void)wrapped_rot_length; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +#endif +} + +OEMCryptoResult OEMCrypto_InstallKeyboxOrOEMCert(const uint8_t* rot, + size_t rot_length) { +// This API is only allowed in factory mode. +#ifdef FACTORY_BUILD_ONLY + if (g_opk_system_state != SYSTEM_INITIALIZED) { + LOGE("OEMCrypto is not yet initialized"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + RETURN_INVALID_CONTEXT_IF_NULL(rot); + const OEMCrypto_ProvisioningMethod provisioning_method = + WTPI_GetProvisioningMethod(); + if (provisioning_method == OEMCrypto_Keybox) { + if (WTPI_GetBCCSignatureType() == BCC_SIG_KEYBOX) { + // Install keybox, which is needed to generate BCC sig + OEMCryptoResult res = + WTPI_UnwrapValidateAndInstallKeybox(rot, rot_length); + if (res != OEMCrypto_SUCCESS) return res; + + // Generate BCC signature + uint8_t sig[256] = {0}; + size_t sig_size = 0; + res = WTPI_GenerateBCCSignature(sig, &sig_size); + if (res != OEMCrypto_ERROR_SHORT_BUFFER) return res; + if (sig_size > sizeof(sig)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + res = WTPI_GenerateBCCSignature(sig, &sig_size); + if (res != OEMCrypto_SUCCESS) return res; + + // Save BCC signature + res = OEMCrypto_FactoryInstallBCCSignature(sig, sig_size); + if (res != OEMCrypto_SUCCESS) return res; + + // Strip device key from keybox and re-install + return WTPI_StripKeyboxAndReinstall(); + } + return WTPI_UnwrapValidateAndInstallKeybox(rot, rot_length); + } else if (provisioning_method == OEMCrypto_OEMCertificate) { + return WTPI_UnwrapValidateAndInstallOEMCert(rot, rot_length); + } else { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } +#else + (void)rot; + (void)rot_length; return OEMCrypto_ERROR_NOT_IMPLEMENTED; #endif } @@ -2513,7 +2675,7 @@ OEMCryptoResult OEMCrypto_IsKeyboxOrOEMCertValid(void) { const OEMCrypto_ProvisioningMethod provisioning_method = WTPI_GetProvisioningMethod(); if (provisioning_method == OEMCrypto_OEMCertificate) { - return OEMCrypto_ERROR_NOT_IMPLEMENTED; + return WTPI_ValidateOEMCertAndKey(); } else if (provisioning_method == OEMCrypto_Keybox) { return WTPI_ValidateKeybox(); } else if (provisioning_method == OEMCrypto_BootCertificateChain) { @@ -2579,13 +2741,49 @@ OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t* buffer, size_t length) { return WTPI_LoadTestKeybox(buffer, length); } -OEMCryptoResult OEMCrypto_GetOEMPublicCertificate( - uint8_t* public_cert UNUSED, size_t* public_cert_length UNUSED) { - return OEMCrypto_ERROR_NOT_IMPLEMENTED; +OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(uint8_t* public_cert, + size_t* public_cert_length) { + if (g_opk_system_state != SYSTEM_INITIALIZED) { + LOGE("OEMCrypto is not yet initialized"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (WTPI_GetProvisioningMethod() != OEMCrypto_OEMCertificate) { + LOGD("This function is only valid on Provisioning 3.0 devices"); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + RETURN_INVALID_CONTEXT_IF_NULL(public_cert_length); + return WTPI_LoadOEMPublicCertificate(public_cert, public_cert_length); } -OEMCryptoResult OEMCrypto_LoadOEMPrivateKey(OEMCrypto_SESSION session UNUSED) { - return OEMCrypto_ERROR_NOT_IMPLEMENTED; +OEMCryptoResult OEMCrypto_LoadOEMPrivateKey(OEMCrypto_SESSION session) { + if (g_opk_system_state != SYSTEM_INITIALIZED) { + LOGE("OEMCrypto is not yet initialized"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (WTPI_GetProvisioningMethod() != OEMCrypto_OEMCertificate) { + LOGD("This function is only valid on Provisioning 3.0 devices"); + return OEMCrypto_ERROR_NOT_IMPLEMENTED; + } + if (OPKI_GetSessionType(session) != SESSION_TYPE_OEMCRYPTO) { + LOGE("Unexpected session type."); + return OEMCrypto_ERROR_INVALID_SESSION; + } + OEMCryptoSession* session_context = NULL; + OEMCryptoResult result = OPKI_GetSession(session, &session_context); + if (result != OEMCrypto_SUCCESS) { + LOGE("Failed to get session with result: %u, session = %u", result, + session); + return result; + } + ABORT_IF(session_context == NULL, + "OPKI_GetSession() provided invalid output."); + result = OPKI_CheckStatePreCall(session_context, API_LOADOEMPRIVATEKEY); + if (result != OEMCrypto_SUCCESS) return result; + /* This API sets the session state to indicate that the OEM private key is + * ready for use. It doesn't actually load it. It is best to load the OEM + * private key in memory only when being used. */ + session_context->prov30_oem_key_loaded = true; + return OPKI_SetStatePostCall(session_context, API_LOADOEMPRIVATEKEY); } OEMCryptoResult OEMCrypto_GetRandom(uint8_t* randomData, size_t dataLength) { @@ -2983,7 +3181,11 @@ static OEMCryptoResult LoadProvisioningCommon( RETURN_INVALID_CONTEXT_IF_ZERO(message_length); RETURN_INVALID_CONTEXT_IF_NULL(signature); RETURN_INVALID_CONTEXT_IF_ZERO(signature_length); - if (signature_length != SHA256_DIGEST_LENGTH) { + const OEMCrypto_ProvisioningMethod provisioning_method = + WTPI_GetProvisioningMethod(); + if ((provisioning_method == OEMCrypto_Keybox || + provisioning_method == OEMCrypto_BootCertificateChain) && + signature_length != SHA256_DIGEST_LENGTH) { LOGE("signature_length is not the length of a SHA256 digest"); return OEMCrypto_ERROR_INVALID_CONTEXT; } @@ -3036,8 +3238,6 @@ static OEMCryptoResult LoadProvisioningCommon( *wrapped_private_key_length = buffer_size; const uint8_t* message_body = message + core_message_length; - const OEMCrypto_ProvisioningMethod provisioning_method = - WTPI_GetProvisioningMethod(); // RewrapDeviceDRMKey only intended for Prov 4 with cast, or Prov 2. if (provisioning_method == OEMCrypto_Keybox || @@ -3049,7 +3249,14 @@ static OEMCryptoResult LoadProvisioningCommon( message_body + parsed_response.enc_private_key_iv.offset, drm_key_type, wrapped_private_key, *wrapped_private_key_length); } else if (provisioning_method == OEMCrypto_OEMCertificate) { - return OEMCrypto_ERROR_NOT_IMPLEMENTED; + result = RewrapDeviceDRMKeyOEMCert( + session_context, + message_body + parsed_response.encrypted_message_key.offset, + parsed_response.encrypted_message_key.length, + message_body + parsed_response.enc_private_key.offset, + parsed_response.enc_private_key.length, + message_body + parsed_response.enc_private_key_iv.offset, drm_key_type, + wrapped_private_key, *wrapped_private_key_length); } else { return OEMCrypto_ERROR_UNKNOWN_FAILURE; } @@ -3698,7 +3905,8 @@ OEMCryptoResult OEMCrypto_GenerateCertificateKeyPair( const bool is_stage1 = (session_context->prov40_oem_private_key == NULL); const CertSignatureType requested_cert_type = is_stage1 ? CERT_SIGNATURE_OEM : CERT_SIGNATURE_DRM; - const uint32_t wrapping_context = is_stage1 ? DEVICE_KEY_WRAP_OEM_CERT : DEVICE_KEY_WRAP_DRM_CERT; + const uint32_t wrapping_context = + is_stage1 ? DEVICE_KEY_WRAP_OEM_CERT : DEVICE_KEY_WRAP_DRM_CERT; AsymmetricKeyType generated_key_type; result = WTPI_GenerateRandomCertificateKeyPair( @@ -4435,3 +4643,64 @@ OEMCryptoResult OEMCrypto_GetEmbeddedDrmCertificate( uint8_t* public_cert UNUSED, size_t* public_cert_length UNUSED) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } + +OEMCryptoResult OEMCrypto_WrapClearPrivateKey( + const uint8_t* clear_private_key_bytes, size_t clear_private_key_length, + uint8_t* wrapped_private_key, size_t* wrapped_private_key_length) { +#ifdef FACTORY_BUILD_ONLY + if (g_opk_system_state != SYSTEM_INITIALIZED) { + LOGE("OEMCrypto is not yet initialized"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + OEMCryptoResult res = OEMCrypto_ERROR_UNKNOWN_FAILURE; + + RETURN_INVALID_CONTEXT_IF_NULL(clear_private_key_bytes); + RETURN_INVALID_CONTEXT_IF_NULL(wrapped_private_key_length); + + size_t required_size = 0; + AsymmetricKeyType key_type = DRM_RSA_PRIVATE_KEY; + + // verify clear private key format by trying to convert it into a key handle + WTPI_AsymmetricKey_Handle out_handle; + res = WTPI_CreateAsymmetricKeyHandle( + clear_private_key_bytes, clear_private_key_length, key_type, &out_handle); + WTPI_FreeAsymmetricKeyHandle(out_handle); + if (res != OEMCrypto_SUCCESS) { + // try again with ECC type + key_type = DRM_ECC_PRIVATE_KEY; + res = WTPI_CreateAsymmetricKeyHandle(clear_private_key_bytes, + clear_private_key_length, key_type, + &out_handle); + WTPI_FreeAsymmetricKeyHandle(out_handle); + if (res != OEMCrypto_SUCCESS) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + } + + res = WTPI_GetWrappedAsymmetricKeySize(clear_private_key_length, key_type, + &required_size); + if (res != OEMCrypto_SUCCESS) { + return res; + } + + if (wrapped_private_key == NULL || + *wrapped_private_key_length < required_size) { + *wrapped_private_key_length = required_size; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + + res = + WTPI_WrapAsymmetricKey(DEVICE_KEY_WRAP_DRM_CERT, wrapped_private_key, + *wrapped_private_key_length, key_type, + clear_private_key_bytes, clear_private_key_length); + + return res; +#else + (void)clear_private_key_bytes; + (void)clear_private_key_length; + (void)wrapped_private_key; + (void)wrapped_private_key_length; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +#endif +} diff --git a/oemcrypto/opk/oemcrypto_ta/oemcrypto_api_macros.h b/oemcrypto/opk/oemcrypto_ta/oemcrypto_api_macros.h index cef2e79..79f40c6 100644 --- a/oemcrypto/opk/oemcrypto_ta/oemcrypto_api_macros.h +++ b/oemcrypto/opk/oemcrypto_ta/oemcrypto_api_macros.h @@ -34,7 +34,7 @@ // version bumps to v17.1, the first released OPK implementation would be // v17.1.0 #define API_MAJOR_VERSION 19 -#define API_MINOR_VERSION 1 +#define API_MINOR_VERSION 2 #define OPK_PATCH_VERSION 0 #endif /* OEMCRYPTO_TA_OEMCRYPTO_API_MACROS_H_ */ diff --git a/oemcrypto/opk/oemcrypto_ta/oemcrypto_entitled_key_session.c b/oemcrypto/opk/oemcrypto_ta/oemcrypto_entitled_key_session.c index e451801..40dd2cd 100644 --- a/oemcrypto/opk/oemcrypto_ta/oemcrypto_entitled_key_session.c +++ b/oemcrypto/opk/oemcrypto_ta/oemcrypto_entitled_key_session.c @@ -12,6 +12,7 @@ #ifdef SUPPORT_CAS # include "wtpi_cas_interface.h" #endif +#include "wtpi_memory_interface.h" OEMCryptoResult OPKI_InitializeEntitledKeySession( OEMCryptoEntitledKeySession* session, OEMCrypto_SESSION key_session_id, @@ -53,7 +54,7 @@ OEMCryptoResult OPKI_TerminateEntitledKeySession( WTPI_FreeKeySlotDescriptor(session->key_slot_descriptor); if (result == OEMCrypto_SUCCESS) result = free_key_slot_result; #endif - *session = (OEMCryptoEntitledKeySession){0}; + WTPI_SecureZeroMemory(session, sizeof(*session)); return result; } diff --git a/oemcrypto/opk/oemcrypto_ta/oemcrypto_session.c b/oemcrypto/opk/oemcrypto_ta/oemcrypto_session.c index a497974..81d4284 100644 --- a/oemcrypto/opk/oemcrypto_ta/oemcrypto_session.c +++ b/oemcrypto/opk/oemcrypto_ta/oemcrypto_session.c @@ -24,10 +24,11 @@ #include "wtpi_clock_interface_layer1.h" #include "wtpi_crc32_interface.h" #include "wtpi_decrypt_sample_interface.h" +#include "wtpi_device_key_interface.h" #include "wtpi_hdcp_interface.h" #include "wtpi_logging_interface.h" +#include "wtpi_memory_interface.h" #include "wtpi_root_of_trust_interface_layer1.h" -#include "wtpi_device_key_interface.h" NO_IGNORE_RESULT static bool IsSupportedDrmKeyType(AsymmetricKeyType key_type) { return key_type == DRM_RSA_PRIVATE_KEY || key_type == DRM_ECC_PRIVATE_KEY; @@ -99,7 +100,7 @@ OEMCryptoResult OPKI_TerminateSession(OEMCryptoSession* session) { } OPKI_ReleaseEntry(session->session_id); - memset(session, 0, sizeof(OEMCryptoSession)); + WTPI_SecureZeroMemory(session, sizeof(*session)); return result; } @@ -120,6 +121,7 @@ OEMCryptoResult OPKI_CheckStatePreCall(OEMCryptoSession* session, case API_GENERATENONCE: switch (session->state) { case (SESSION_OPENED): + case (SESSION_LOAD_OEM_RSA_KEY): case (SESSION_PROV4_OEM_KEY_LOADED): case (SESSION_DRM_KEY_LOADED): case (SESSION_CAST_RSA_KEY_LOADED): @@ -552,36 +554,32 @@ NO_IGNORE_RESULT static OEMCryptoResult DeriveKey( OEMCryptoResult OPKI_DeriveMacAndEncryptionKeys( OEMCryptoSession* session, WTPI_K1_SymmetricKey_Handle master_key, const uint8_t* context, size_t context_length) { - const uint8_t kMacKeyLabel[] = "AUTHENTICATION"; - const uint8_t kMacContextSuffix[] = {0x00, 0x00, 0x02, 0x00}; - const uint8_t kEncKeyLabel[] = "ENCRYPTION"; - const uint8_t kEncContextSuffix[] = {0x00, 0x00, 0x00, 0x80}; - if (session == NULL || context == NULL || context_length == 0 || master_key == NULL) { return OEMCrypto_ERROR_INVALID_CONTEXT; } uint8_t counter = 1; - OEMCryptoResult result = DeriveKey( - master_key, counter, kMacKeyLabel, sizeof(kMacKeyLabel), context, - context_length, kMacContextSuffix, sizeof(kMacContextSuffix), - MAC_KEY_SERVER, MAC_KEY_SIZE, &session->mac_key_server); + OEMCryptoResult result = + DeriveKey(master_key, counter, ODK_MacKeyLabelWithZero, + ODK_MacKeyLabelWithZeroLength, context, context_length, + ODK_MacKeySuffix, ODK_MacKeySuffixLength, MAC_KEY_SERVER, + MAC_KEY_SIZE, &session->mac_key_server); if (result != OEMCrypto_SUCCESS) return result; counter += 2; // increment counter by 2 because 2 blocks used for server key. - result = DeriveKey(master_key, counter, kMacKeyLabel, sizeof(kMacKeyLabel), - context, context_length, kMacContextSuffix, - sizeof(kMacContextSuffix), MAC_KEY_CLIENT, MAC_KEY_SIZE, - &session->mac_key_client); + result = DeriveKey(master_key, counter, ODK_MacKeyLabelWithZero, + ODK_MacKeyLabelWithZeroLength, context, context_length, + ODK_MacKeySuffix, ODK_MacKeySuffixLength, + MAC_KEY_CLIENT, MAC_KEY_SIZE, &session->mac_key_client); if (result != OEMCrypto_SUCCESS) { OPKI_FreeKeyFromTable(&session->mac_key_server); return result; } counter = 1; // Start over with encryption context. - result = DeriveKey(master_key, counter, kEncKeyLabel, sizeof(kEncKeyLabel), - context, context_length, kEncContextSuffix, - sizeof(kEncContextSuffix), ENCRYPTION_KEY, KEY_SIZE_128, - &session->encryption_key); + result = DeriveKey(master_key, counter, ODK_EncKeyLabelWithZero, + ODK_EncKeyLabelWithZeroLength, context, context_length, + ODK_EncKeySuffix, ODK_EncKeySuffixLength, + ENCRYPTION_KEY, KEY_SIZE_128, &session->encryption_key); if (result != OEMCrypto_SUCCESS) { OPKI_FreeKeyFromTable(&session->mac_key_server); OPKI_FreeKeyFromTable(&session->mac_key_client); @@ -651,37 +649,52 @@ OEMCryptoResult OPKI_GenerateCertSignature(OEMCryptoSession* session, signature == NULL || signature_length == NULL) { return OEMCrypto_ERROR_INVALID_CONTEXT; } - if (signature_type == CERT_SIGNATURE_OEM) { - return OEMCrypto_ERROR_NOT_IMPLEMENTED; - } - + AsymmetricKeyType key_type; AsymmetricKey* signing_key; uint32_t wrapping_context; if (signature_type == CERT_SIGNATURE_DRM) { + // DRM cert (prov20, prov30, or prov40) if (!DRMKeyMaySign(session->drm_private_key)) return OEMCrypto_ERROR_INVALID_KEY; signing_key = session->drm_private_key; + key_type = signing_key->key_type; wrapping_context = DEVICE_KEY_WRAP_DRM_CERT; } else if (signature_type == CERT_SIGNATURE_CSR) { + // Prov40 OEM cert, RSA or ECC if (!DRMKeyMaySign(session->prov40_csr_signing_key)) return OEMCrypto_ERROR_INVALID_KEY; signing_key = session->prov40_csr_signing_key; + key_type = signing_key->key_type; wrapping_context = session->prov40_csr_keywrap_context; + } else if (signature_type == CERT_SIGNATURE_OEM) { + // Prov30 OEM cert, RSA only + // For Prov30, signing key handle is directly loaded from the secure + // storage. The oemcrypto session doesn't hold the key. + key_type = DRM_RSA_PRIVATE_KEY; + wrapping_context = DEVICE_KEY_WRAP_OEM_CERT; } else { return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - WTPI_AsymmetricKey_Handle signing_key_handle; - uint32_t allowed_schemes; - OEMCryptoResult result = WTPI_UnwrapIntoAsymmetricKeyHandle( - wrapping_context, signing_key->wrapped_key, signing_key->wrapped_key_length, - signing_key->key_type, &signing_key_handle, &allowed_schemes); - if (result != OEMCrypto_SUCCESS) { - LOGE("Failed to unwrap signing key into key handle with result: %u", - result); - return result; + OEMCryptoResult result = OEMCrypto_SUCCESS; + if (signature_type == CERT_SIGNATURE_OEM) { + result = WTPI_CreateAsymmetricKeyHandleFromOEMKey(&signing_key_handle); + if (result != OEMCrypto_SUCCESS) return result; + } else { + // For prov20 or prov40, signing key handle is created from |signing_key|, + // which has already been loaded in the session. + uint32_t allowed_schemes; + result = WTPI_UnwrapIntoAsymmetricKeyHandle( + wrapping_context, signing_key->wrapped_key, + signing_key->wrapped_key_length, signing_key->key_type, + &signing_key_handle, &allowed_schemes); + if (result != OEMCrypto_SUCCESS) { + LOGE("Failed to unwrap signing key into key handle with result: %u", + result); + return result; + } } - if (signing_key->key_type == DRM_RSA_PRIVATE_KEY) { + if (key_type == DRM_RSA_PRIVATE_KEY) { result = WTPI_RSASign(signing_key_handle, message, message_length, signature, signature_length, kSign_RSASSA_PSS); } else { @@ -708,6 +721,17 @@ NO_IGNORE_RESULT OEMCryptoResult OPKI_GetSessionSignatureHashAlgorithm( } else if (session->prov40_csr_signing_key != NULL) { key = session->prov40_csr_signing_key; context = session->prov40_csr_keywrap_context; + } else if (session->prov30_oem_key_loaded) { + // Create OEM key handle on the fly for prov30 since it is not held by + // the session. + WTPI_AsymmetricKey_Handle key_handle; + OEMCryptoResult result = + WTPI_CreateAsymmetricKeyHandleFromOEMKey(&key_handle); + if (result != OEMCrypto_SUCCESS) return result; + result = WTPI_GetSignatureHashAlgorithm(key_handle, DRM_RSA_PRIVATE_KEY, + hash_algorithm); + WTPI_FreeAsymmetricKeyHandle(key_handle); + return result; } else { LOGE("No asymmetric keys loaded."); return OEMCrypto_ERROR_UNKNOWN_FAILURE; diff --git a/oemcrypto/opk/oemcrypto_ta/oemcrypto_session.h b/oemcrypto/opk/oemcrypto_ta/oemcrypto_session.h index cebdbb3..ea7d57a 100644 --- a/oemcrypto/opk/oemcrypto_ta/oemcrypto_session.h +++ b/oemcrypto/opk/oemcrypto_ta/oemcrypto_session.h @@ -94,7 +94,6 @@ typedef struct OEMCryptoSession { SymmetricKey* mac_key_server; SymmetricKey* mac_key_client; SymmetricKey* encryption_key; - bool refresh_valid; OEMCrypto_LicenseType license_type; uint32_t current_content_key_index; SymmetricKey* content_keys[CONTENT_KEYS_PER_SESSION]; @@ -112,16 +111,13 @@ typedef struct OEMCryptoSession { uint8_t license_request_hash[ODK_SHA256_HASH_SIZE]; /* |decrypt_hash| is only used by hash validation for content key session. */ DecryptHash decrypt_hash; - /* If |recent_decrypt| is true, then a usage report cannot be generated - * without first updating the usage entry. It should be set to true whenever a - * key is used. */ - bool recent_decrypt; /* The bare minimum state check to double-confirm that at most one call of * each of these function categories can be made during the lifetime of a * session. This is also enforced by the OPK session state machine. */ bool nonce_created; bool request_signed; bool response_loaded; + bool prov30_oem_key_loaded; } OEMCryptoSession; /* Initializes session context. diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi/wtpi_root_of_trust_interface_layer1.h b/oemcrypto/opk/oemcrypto_ta/wtpi/wtpi_root_of_trust_interface_layer1.h index 138e0a3..9d1ce70 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi/wtpi_root_of_trust_interface_layer1.h +++ b/oemcrypto/opk/oemcrypto_ta/wtpi/wtpi_root_of_trust_interface_layer1.h @@ -10,6 +10,7 @@ #include "OEMCryptoCENC.h" #include "wtpi_crypto_and_key_management_interface_layer1.h" +#include "wtpi_crypto_asymmetric_interface.h" #ifdef __cplusplus extern "C" { @@ -150,6 +151,106 @@ OEMCryptoResult WTPI_GetDeviceIDLength(size_t* device_id_length); OEMCryptoResult WTPI_StripKeyboxAndReinstall(void); #endif +/* Provisioning 3.0 */ + +/** + * Wrap the root of trust of Provisioning 3.0, which + * contains the OEM certificate chain (a leaf cert followed by an intermediate + * cert), and the key of the leaf cert. It may be wise to make this function a + * no-op when the device is not in factory mode. + * Caller retains ownership of all parameters. + * + * @param[in] input: OEM certificate chain and private key to be wrapped + * @param[in] input_length: size of the input + * @param[out] wrapped_cert: buffer where wrapped OEM cert and key will be + * written. + * @param[in,out] wrapped_cert_length: on input, it's the buffer size, on output + * it's how much was wrapped. If wrapped_cert_length is too short for the entire + * cert chain and key, the error code OEMCrypto_ERROR_SHORT_BUFFER shall be + * returned. Otherwise, set wrapped_cert_length to the size of the OEM cert + * chain and key wrapped. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INVALID_CONTEXT if |input| or |wrapped_cert_length| + * is NULL + * @retval OEMCrypto_ERROR_SHORT_BUFFER if |wrapped_cert| is NULL, or + * |wrapped_cert_length| is too small + */ +OEMCryptoResult WTPI_WrapOEMCert(const uint8_t* input, size_t input_length, + uint8_t* wrapped_cert, + size_t* wrapped_cert_length); + +/** + * Unwrap and store the root of trust of Provisioning 3.0. It may be wise to + * make this function a no-op when the device is not in factory mode. + * Caller retains ownership of all parameters. + * + * @param[in] wrapped_cert: OEM certificate chain and private key to be + * installed. It is a result of WTPI_WrapOEMCert(). + * @param[in] wrapped_cert_length: size of |wrapped_cert| + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INVALID_CONTEXT if |wrapped_cert| is NULL, or if + * |wrapped_cert| can not be parsed + * @retal OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE if the OEM cert is not + * valid + * @retal OEMCrypto_ERROR_INVALID_KEY if the OEM private key is not + * valid + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE otherwise + */ +OEMCryptoResult WTPI_UnwrapValidateAndInstallOEMCert( + const uint8_t* wrapped_cert, size_t wrapped_cert_length); + +/** + * Load Provisioning 3.0 OEM public certificate to output buffer. + * Caller retains ownership of all parameters. + * + * @param[out] output: buffer of the OEM public certificate. + * @param[in,out] output_length: on input, it's the buffer size, on output it's + * the actual size loaded into the buffer. If output_length is too short for the + * OEM public certificate, the error code OEMCrypto_ERROR_SHORT_BUFFER shall be + * returned. Otherwise, set output length to the size of the OEM public + * certificate loaded. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INVALID_CONTEXT if |output_length| is NULL + * @retval OEMCrypto_ERROR_SHORT_BUFFER if |output| is NULL, or |output_length| + * is too small + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE otherwise + */ +OEMCryptoResult WTPI_LoadOEMPublicCertificate(uint8_t* output, + size_t* output_length); + +/** + * Attempt to validate the Provisioning 3.0 OEM certificate chain and key. + * Caller retains ownership of all parameters. + * + * @retval OEMCrypto_SUCCESS success + * @retal OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE if the OEM cert is not + * valid + * @retal OEMCrypto_ERROR_INVALID_KEY if the OEM private key is not + * valid + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED if Provisioning 3.0 is not enabled + */ +OEMCryptoResult WTPI_ValidateOEMCertAndKey(void); + +/** + * Provisioning 3.0 only. Creates a key handle from the Prov30 key material in + * secure storage, and places the result in |key_handle|. Key type is + * DRM_RSA_PRIVATE_KEY. + * Caller retains ownership of all parameters. + * + * @param[out] key_handle: output key handle + * + * @retval OEMCrypto_ERROR_INVALID_CONTEXT if any of the parameters are NULL + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE if there are any other failures + * @retval OEMCrypto_SUCCESS otherwise + */ +OEMCryptoResult WTPI_CreateAsymmetricKeyHandleFromOEMKey( + WTPI_AsymmetricKey_Handle* key_handle); + +/* End of Provisioning 3.0 */ + /// @} #ifdef __cplusplus } diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi/wtpi_root_of_trust_interface_layer2.h b/oemcrypto/opk/oemcrypto_ta/wtpi/wtpi_root_of_trust_interface_layer2.h index 15ecbe9..eccbbd3 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi/wtpi_root_of_trust_interface_layer2.h +++ b/oemcrypto/opk/oemcrypto_ta/wtpi/wtpi_root_of_trust_interface_layer2.h @@ -46,24 +46,103 @@ OEMCryptoResult WTPI_UnwrapRootOfTrust(const uint8_t* input, * return an error code if this function is called when the device is not in * "factory mode". * - *@param[in] keybox: buffer of the keybox. - *@param[in] length: on input, it's the buffer size, on output it's the actual - * size loaded into the buffer. If length is too short for the entire root - * of trust, the error code OEMCrypto_ERROR_SHORT_BUFFER shall be returned. - * Otherwise, set length to the size of the root of trust loaded. + * @param[in] keybox: buffer of the keybox. + * @param[in] length: keybox buffer size. */ OEMCryptoResult WTPI_SaveRootOfTrust(const uint8_t* keybox, size_t length); /** Read the buffer from secure permanent storage. * - *@param[in] keybox: buffer of the keybox. - *@param[in,out] length: on input, it's the buffer size, on output it's the + * @param[out] keybox: buffer of the keybox. + * @param[in,out] length: on input, it's the buffer size, on output it's the * actual size loaded into the buffer. If length is too short for the entire * root of trust, the error code OEMCrypto_ERROR_SHORT_BUFFER shall be * returned. Otherwise, set length to the size of the root of trust loaded. */ OEMCryptoResult WTPI_LoadRootOfTrust(uint8_t* keybox, size_t* length); +/* Provisioning 3.0 */ + +/** Use the system specific key to wrap the Provisioning 3.0 root of trust, + * which contains the OEM certificate chain and key. This function is used in + * the factory. + * + * @param[in] input: buffer with OEM cert chain and key. + * @param[in] input_length: the length of the input buffer. + * @param[out] output: buffer where encrypted OEM cert chain and key will be + * written. + * @param[in,out] output_length: on input, it's the buffer size, on output it's + * how much was wrapped. If output_length is too short for the entire root + * of trust, the error code OEMCrypto_ERROR_SHORT_BUFFER shall be returned. + */ +OEMCryptoResult WTPI_WrapRootOfTrust30(const uint8_t* input, + size_t input_length, uint8_t* output, + size_t* output_length); + +/** Use the system specific key to unwrap the Provisioning 3.0 root of trust, + * which contains the OEM certificate chain and key. This function is used in + * the factory. + * + * @param[in] input: buffer with encrypted OEM cert chain and key. + * @param[in] input_length: the length of the input buffer. + * @param[out] output: buffer where clear OEM cert chain and key will be + * written. + * @param[in,out] output_length: on input, it's the buffer size, on output it's + * how much was unwrapped. If output_length is too short for the entire root + * of trust, the error code OEMCrypto_ERROR_SHORT_BUFFER shall be returned. + */ +OEMCryptoResult WTPI_UnwrapRootOfTrust30(const uint8_t* input, + size_t input_length, uint8_t* output, + size_t* output_length); + +/** Save the buffer to secure permanent storage. + * To prevent accidentally destroying the OEM private key, production devices + * should return an error code if this function is called when the device is + * not in "factory mode". + * + * @param[in] input: buffer of the OEM cert private key. + * @param[in] input_length: input buffer size. + */ +OEMCryptoResult WTPI_SaveOEMPrivateKey30(const uint8_t* input, + size_t input_length); + +/** Read the buffer from secure permanent storage. + * + * @param[out] output: buffer of the OEM cert private key. + * @param[in,out] output_length: on input, it's the buffer size, on output it's + * the actual size loaded into the buffer. If output_length is too short + * for the entire root of trust, the error code OEMCrypto_ERROR_SHORT_BUFFER + * shall be returned. Otherwise, set length to the size of the root of + * trust loaded. + */ +OEMCryptoResult WTPI_LoadOEMPrivateKey30(uint8_t* output, + size_t* output_length); + +/** Save the buffer to secure permanent storage. + * To prevent accidentally destroying the OEM public certificate, production + * devices should return an error code if this function is called when the + * device is not in "factory mode". + * + * @param[in] input: buffer of the OEM public certificate. + * @param[in] input_length: input buffer size. + */ +OEMCryptoResult WTPI_SaveOEMPublicCertificate30(const uint8_t* input, + size_t input_length); + +/** Read the buffer from secure permanent storage. + * + * @param[out] output: buffer of the OEM public certificate. + * @param[in,out] output_length: on input, it's the buffer size, on output it's + * the actual size loaded into the buffer. If output_length is too short + * for the entire root of trust, the error code OEMCrypto_ERROR_SHORT_BUFFER + * shall be returned. Otherwise, set length to the size of the OEM public + * certificate loaded. + */ +OEMCryptoResult WTPI_LoadOEMPublicCertificate30(uint8_t* output, + size_t* output_length); + +/* End of Provisioning 3.0 */ + /// @} #ifdef __cplusplus diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/config/default.h b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/config/default.h index 5811bfa..64bb351 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/config/default.h +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/config/default.h @@ -336,6 +336,35 @@ # define MAX_ASYMMETRIC_SIGNATURE_SIZE 1024 #endif +/** + * Maximum size of OEM certificate chain and private key in Provisioning 3.0 + */ +#ifndef MAX_PROV30_ROT_SIZE +# define MAX_PROV30_ROT_SIZE 8192 +#endif + +/** + * Maximum size of OEM X509 certificate chain in Provisioning 3.0 + */ +#ifndef MAX_PROV30_OEM_CERT_CHAIN_SIZE +# define MAX_PROV30_OEM_CERT_CHAIN_SIZE 4096 +#endif + +/** + * Maximum size of OEM certificate chain in PKCS#7 in Provisioning 3.0 + */ +#ifndef MAX_PROV30_OEM_CERT_CHAIN_PKCS7_SIZE +# define MAX_PROV30_OEM_CERT_CHAIN_PKCS7_SIZE \ + (MAX_PROV30_OEM_CERT_CHAIN_SIZE + 2048) +#endif + +/** + * Maximum size of OEM private key in Provisioning 3.0 + */ +#ifndef MAX_PROV30_OEM_KEY_SIZE +# define MAX_PROV30_OEM_KEY_SIZE 2048 +#endif + /* * * Config values that must be defined by the build process. These are wrapped by diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/prov30_factory_util.c b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/prov30_factory_util.c new file mode 100644 index 0000000..c42975f --- /dev/null +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/prov30_factory_util.c @@ -0,0 +1,135 @@ +/* Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary + source code may only be used and distributed under the Widevine + License Agreement. */ + +#include "prov30_factory_util.h" + +#include + +#include "openssl/evp.h" +#include "openssl/pkcs7.h" +#include "openssl/x509.h" + +/* +This code assumes the certs_and_key always contains a certificate chain (one +leaf and one intermediate cert) followed by a private key. + 1. Leaf certificate + 2. Intermediate certificate + 3. Private leaf key +Input format for |certs_and_key|: ++-----------------------+----------------------+--------------------------+ +| Cert Chain Length | Certificate Chain | Key Length | ++-----------------------+----------------------+--------------------------+ +| (4 bytes, big-endian) | (DER-encoded PKCS#7) | (4 bytes, big-endian) | ++-----------------------+----------------------+--------------------------+ +| Private Key | ++-----------------------+ +*/ +OEMCryptoResult ParseCertificateChainAndKey(const uint8_t* certs_and_key, + size_t certs_and_key_length, + uint8_t* cert_chain_out, + size_t* cert_chain_out_length, + uint8_t* key_out, + size_t* key_out_length) { + if (certs_and_key == NULL || certs_and_key_length == 0 || + cert_chain_out == NULL || cert_chain_out_length == NULL || + key_out == NULL || key_out_length == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + // Read cert chain length from the first 4 bytes of the input buffer + if (certs_and_key_length < sizeof(uint32_t)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + const uint32_t cert_chain_length = (certs_and_key[0] << 24) | + (certs_and_key[1] << 16) | + (certs_and_key[2] << 8) | certs_and_key[3]; + // Read key length from the next 4 bytes after the cert chain + if (certs_and_key_length < + sizeof(uint32_t) + cert_chain_length + sizeof(uint32_t)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + const uint8_t* key_data = + certs_and_key + sizeof(uint32_t) + cert_chain_length; + const uint32_t key_length = (key_data[0] << 24) | (key_data[1] << 16) | + (key_data[2] << 8) | key_data[3]; + if (certs_and_key_length < + sizeof(uint32_t) + cert_chain_length + sizeof(uint32_t) + key_length) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + // Check if output buffers are large enough + if (*cert_chain_out_length < cert_chain_length || + *key_out_length < key_length) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + // Copy certificate chain + memcpy(cert_chain_out, certs_and_key + sizeof(uint32_t), cert_chain_length); + *cert_chain_out_length = (size_t)cert_chain_length; + // Copy private key + memcpy(key_out, key_data + sizeof(uint32_t), key_length); + *key_out_length = (size_t)key_length; + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult ValidateCertificateChainAndKey(const uint8_t* cert_chain, + size_t cert_chain_length, + const uint8_t* key, + size_t key_length) { + // 1. PKCS#7 Parsing + BIO* bio_cert_chain = BIO_new_mem_buf(cert_chain, cert_chain_length); + if (!bio_cert_chain) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + PKCS7* pkcs7 = d2i_PKCS7_bio(bio_cert_chain, NULL); + if (!pkcs7) { + BIO_free(bio_cert_chain); + return OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE; + } + STACK_OF(X509)* certs = pkcs7->d.sign->cert; // Assuming signed PKCS#7 + if (sk_X509_num(certs) < 2) { + PKCS7_free(pkcs7); + BIO_free(bio_cert_chain); + return OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE; + } + // 2. Certificate Chain Verification (without root trust) + X509* leaf_cert = sk_X509_value(certs, 0); + X509* intermediate_cert = sk_X509_value(certs, 1); + if (!leaf_cert || !intermediate_cert) { + PKCS7_free(pkcs7); + BIO_free(bio_cert_chain); + return OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE; + } + EVP_PKEY* issuer_pubkey = X509_get_pubkey(intermediate_cert); + if (!issuer_pubkey) { + return OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE; + } + int status = X509_verify(leaf_cert, issuer_pubkey); + EVP_PKEY_free(issuer_pubkey); + if (status != 1) { + PKCS7_free(pkcs7); + BIO_free(bio_cert_chain); + return OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE; + } + // 3. Key Matching Verification (DER-encoded key) + BIO* bio_key = BIO_new_mem_buf(key, key_length); + if (!bio_key) { + PKCS7_free(pkcs7); + BIO_free(bio_cert_chain); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + EVP_PKEY* pkey = d2i_PrivateKey_bio(bio_key, NULL); + if (!pkey) { + BIO_free(bio_key); + PKCS7_free(pkcs7); + BIO_free(bio_cert_chain); + return OEMCrypto_ERROR_INVALID_KEY; + } + int key_match_result = X509_check_private_key(leaf_cert, pkey); + EVP_PKEY_free(pkey); + BIO_free(bio_key); + PKCS7_free(pkcs7); + BIO_free(bio_cert_chain); + if (key_match_result != 1) { + return OEMCrypto_ERROR_INVALID_KEY; + } + return OEMCrypto_SUCCESS; +} diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/prov30_factory_util.h b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/prov30_factory_util.h new file mode 100644 index 0000000..6339b11 --- /dev/null +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/prov30_factory_util.h @@ -0,0 +1,31 @@ +/* Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary + source code may only be used and distributed under the Widevine + License Agreement. */ + +#ifndef OEMCRYPTO_TA_PROV30_FACTORY_UTIL_H_ +#define OEMCRYPTO_TA_PROV30_FACTORY_UTIL_H_ + +#include "OEMCryptoCENC.h" + +/* Parse |certs_and_key|, which includes an X509 certificate chain (one leaf + certificate and one intermediate certificate) and leaf private key, outputs + DER encoded PKCS#7 certificate chain and DER encoded private key. The input + format of |certs_and_key| is implementation specific. It could be PEM encoded + bytes, or any self-defined format. Caller retains ownership of all pointers + and they must not be NULL. */ +OEMCryptoResult ParseCertificateChainAndKey(const uint8_t* certs_and_key, + size_t certs_and_key_length, + uint8_t* cert_chain_out, + size_t* cert_chain_out_length, + uint8_t* key_out, + size_t* key_out_length); + +/* Validate DER encoded PKCS#7 certificate chain and DER encoded private key + corresponding to the leaf certificate. Caller retains ownership of all + pointers and they must not be NULL. */ +OEMCryptoResult ValidateCertificateChainAndKey(const uint8_t* cert_chain, + size_t cert_chain_length, + const uint8_t* key, + size_t key_length); + +#endif /* OEMCRYPTO_TA_PROV30_FACTORY_UTIL_H_ */ diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_device_key.c b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_device_key.c index 98de618..cff68e3 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_device_key.c +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_device_key.c @@ -262,6 +262,14 @@ OEMCryptoResult WTPI_VerifyAndDecrypt(uint32_t context, const uint8_t* data, switch (version) { case 1: + // If the header type is V1, use the legacy unwrapping format. + // Check the incoming context. DRM_CERT or OEM_CERT means this is a + // legacy DRM/OEM wrap, which used the same DRM_CERT_V1 context to wrap. + // Otherwise use the context provided. + if (context == DEVICE_KEY_WRAP_DRM_CERT || + context == DEVICE_KEY_WRAP_OEM_CERT) { + context = DEVICE_KEY_WRAP_DRM_CERT_V1; + } return VerifyAndDecrypt_V1(context, data, data_size, out, out_size); case 2: return VerifyAndDecrypt_V2(context, data, data_size, out, out_size); diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_provisioning_4.c b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_provisioning_4.c index ad9fa95..7e398a3 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_provisioning_4.c +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_provisioning_4.c @@ -107,6 +107,9 @@ static OEMCryptoResult BuildPhase2BootCertificateChain( uint8_t entry_public_keys[BCC_ENTRY_COUNT][ED25519_PUBLIC_KEY_LEN]; memset(entry_public_keys, 0, sizeof(entry_public_keys)); size_t entry_public_key_lengths[BCC_ENTRY_COUNT]; + for (int i = 0; i < BCC_ENTRY_COUNT; ++i) { + entry_public_key_lengths[i] = ED25519_PUBLIC_KEY_LEN; + } uint8_t context[DEVICE_KEY_DERIVATION_CONTEXT_SIZE]; CreateDeviceKeyDerivationContext(context); OEMCryptoResult result; diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_reference.gyp b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_reference.gyp index bffd8a3..58bd06b 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_reference.gyp +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_reference.gyp @@ -7,6 +7,7 @@ # TODO(b/207176111): add test scripts to cover both reference crypto impl 'reference_crypto_impl%': 'software', 'prov4_bcc_signature_type%': 'none', + 'use_provisioning_30%': 'none', 'test_renewal%': 0, }, 'includes': [ @@ -31,6 +32,17 @@ 'include_dirs': [ '.', ], + # The reference implementation of the root of trust interface uses + # BoringSSL/OpenSSL, which requires C11. These flags effectively do the + # opposite of the default flags, filtering out the C99 flag and + # un-filtering-out the C11 flag. + 'cflags_c': [ + '-std=c11', + ], + 'cflags_c/': [ + ['exclude', '-std=*'], + ['include', '-std=c11'], + ], 'dependencies': [ '../../../odk/src/odk.gyp:odk', 'oemcrypto_ta_reference_crypto', @@ -132,6 +144,11 @@ 'OPK_CONFIG_BCC_SIGNATURE_TYPE=BCC_SIG_KEYBOX', ], }], + ['use_provisioning_30', { + 'sources': [ + 'prov30_factory_util.c', + ], + }], ], 'variables': { # Needed for BoringSSL dependency build files. These SHOULD already be diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_root_of_trust_layer1.c b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_root_of_trust_layer1.c index 2cec1d8..da47497 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_root_of_trust_layer1.c +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_reference/wtpi_root_of_trust_layer1.c @@ -11,15 +11,27 @@ #include #include +#include "openssl/bio.h" +#include "openssl/err.h" +#include "openssl/pem.h" +#include "openssl/rsa.h" +#include "openssl/x509.h" + +#include "config/default.h" #include "cose_util.h" #include "odk_endian.h" +#include "oemcrypto_compiler_attributes.h" #include "oemcrypto_key_types.h" +#ifdef USE_PROVISIONING_30 +# include "prov30_factory_util.h" +#endif #include "renewal_util.h" #include "wtpi_abort_interface.h" #include "wtpi_config_interface.h" #include "wtpi_crc32_interface.h" #include "wtpi_crypto_and_key_management_interface_layer1.h" #include "wtpi_crypto_asymmetric_interface.h" +#include "wtpi_device_key_interface.h" #include "wtpi_device_renewal_interface_layer1.h" #include "wtpi_logging_interface.h" #include "wtpi_memory_interface.h" @@ -233,14 +245,40 @@ static OEMCryptoResult GetProv4DeviceID(uint8_t* device_id, return result; } +#ifdef USE_PROVISIONING_30 +static OEMCryptoResult GetProv3DeviceID(uint8_t* device_id, + size_t device_id_length) { + if (device_id == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; + if (device_id_length < SHA256_DIGEST_LENGTH) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + uint8_t public_cert_buffer[MAX_PROV30_OEM_CERT_CHAIN_PKCS7_SIZE]; + size_t public_cert_buffer_size = sizeof(public_cert_buffer); + OEMCryptoResult result = WTPI_LoadOEMPublicCertificate( + public_cert_buffer, &public_cert_buffer_size); + if (result != OEMCrypto_SUCCESS) return result; + // Device ID with provisioning 3 in this reference implementation is hash of + // (PKCS#7 DER encoded) OEM public certificate. + result = + WTPI_C1_SHA256(public_cert_buffer, public_cert_buffer_size, device_id); + return result; +} +#endif + OEMCryptoResult WTPI_GetDeviceID(uint8_t* device_id, size_t device_id_length) { if (device_id == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; - if (WTPI_GetProvisioningMethod() == OEMCrypto_BootCertificateChain) { + const OEMCrypto_ProvisioningMethod provisioning_method = + WTPI_GetProvisioningMethod(); + if (provisioning_method == OEMCrypto_BootCertificateChain) { return GetProv4DeviceID(device_id, device_id_length); } - - if (WTPI_GetProvisioningMethod() != OEMCrypto_Keybox) { +#ifdef USE_PROVISIONING_30 + if (provisioning_method == OEMCrypto_OEMCertificate) { + return GetProv3DeviceID(device_id, device_id_length); + } +#endif + if (provisioning_method != OEMCrypto_Keybox) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } @@ -255,7 +293,8 @@ OEMCryptoResult WTPI_GetDeviceIDLength(size_t* device_id_length) { if (device_id_length == NULL) { return OEMCrypto_ERROR_INVALID_CONTEXT; } - if (WTPI_GetProvisioningMethod() == OEMCrypto_BootCertificateChain) { + if (WTPI_GetProvisioningMethod() == OEMCrypto_OEMCertificate || + WTPI_GetProvisioningMethod() == OEMCrypto_BootCertificateChain) { *device_id_length = SHA256_DIGEST_LENGTH; } else if (WTPI_GetProvisioningMethod() == OEMCrypto_Keybox) { *device_id_length = KEYBOX_DEVICE_ID_SIZE; @@ -335,3 +374,165 @@ OEMCryptoResult WTPI_StripKeyboxAndReinstall(void) { return WTPI_SaveRootOfTrust((uint8_t*)&gKeybox, sizeof(gKeybox)); } #endif + +#ifdef USE_PROVISIONING_30 +// Load Provisioning 3.0 OEM private key to output buffer +static OEMCryptoResult LoadOEMPrivateKey(uint8_t* output, + size_t* output_length) { + if (output_length == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (output == NULL || *output_length < MAX_PROV30_OEM_KEY_SIZE) { + *output_length = MAX_PROV30_OEM_KEY_SIZE; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + return WTPI_LoadOEMPrivateKey30(output, output_length); +} +#endif + +OEMCryptoResult WTPI_WrapOEMCert(const uint8_t* input, size_t input_length, + uint8_t* wrapped_cert, + size_t* wrapped_cert_length) { +#ifdef USE_PROVISIONING_30 + if (input == NULL || input_length == 0 || wrapped_cert_length == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + size_t required_size = 0; + OEMCryptoResult result = WTPI_GetEncryptAndSignSize( + DEVICE_KEY_WRAP_OEM_CERT, input_length, &required_size); + if (result != OEMCrypto_SUCCESS) { + LOGE("Failed to get wrapped OEM cert and key size with result: %u", result); + return result; + } + if (wrapped_cert == NULL || *wrapped_cert_length < required_size) { + *wrapped_cert_length = required_size; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + /* Tell caller how much space we used. */ + *wrapped_cert_length = required_size; + return WTPI_WrapRootOfTrust30(input, input_length, wrapped_cert, + wrapped_cert_length); +#else + (void)input; + (void)input_length; + (void)wrapped_cert; + (void)wrapped_cert_length; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +#endif +} + +OEMCryptoResult WTPI_UnwrapValidateAndInstallOEMCert(const uint8_t* input, + size_t input_length) { +#ifdef USE_PROVISIONING_30 + if (input == NULL || input_length == 0) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + uint8_t certs_and_key[MAX_PROV30_ROT_SIZE] = {0}; + size_t certs_and_key_length = MAX_PROV30_ROT_SIZE; + OEMCryptoResult result = WTPI_UnwrapRootOfTrust30( + input, input_length, certs_and_key, &certs_and_key_length); + if (result != OEMCrypto_SUCCESS) { + goto cleanup; + } + + uint8_t cert_chain[MAX_PROV30_OEM_CERT_CHAIN_PKCS7_SIZE] = {0}; + size_t cert_chain_length = MAX_PROV30_OEM_CERT_CHAIN_PKCS7_SIZE; + uint8_t private_key[MAX_PROV30_OEM_KEY_SIZE] = {0}; + size_t private_key_length = MAX_PROV30_OEM_KEY_SIZE; + result = ParseCertificateChainAndKey(certs_and_key, certs_and_key_length, + cert_chain, &cert_chain_length, + private_key, &private_key_length); + if (result != OEMCrypto_SUCCESS) { + goto cleanup; + } + result = ValidateCertificateChainAndKey(cert_chain, cert_chain_length, + private_key, private_key_length); + if (result != OEMCrypto_SUCCESS) { + LOGE("OEM certificate chain or key is not valid"); + goto cleanup; + } + result = WTPI_SaveOEMPrivateKey30(private_key, private_key_length); + if (result != OEMCrypto_SUCCESS) { + goto cleanup; + } + result = WTPI_SaveOEMPublicCertificate30(cert_chain, cert_chain_length); +cleanup: + WTPI_SecureZeroMemory(certs_and_key, sizeof(certs_and_key)); + WTPI_SecureZeroMemory(private_key, sizeof(private_key)); + return result; +#else + (void)input; + (void)input_length; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +#endif +} + +OEMCryptoResult WTPI_LoadOEMPublicCertificate(uint8_t* output, + size_t* output_length) { +#ifdef USE_PROVISIONING_30 + if (output_length == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (output == NULL || *output_length < MAX_PROV30_OEM_CERT_CHAIN_PKCS7_SIZE) { + *output_length = MAX_PROV30_OEM_CERT_CHAIN_PKCS7_SIZE; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + return WTPI_LoadOEMPublicCertificate30(output, output_length); +#else + (void)output; + (void)output_length; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +#endif +} + +OEMCryptoResult WTPI_ValidateOEMCertAndKey(void) { +#ifdef USE_PROVISIONING_30 + uint8_t cert_chain[MAX_PROV30_OEM_CERT_CHAIN_PKCS7_SIZE] = {0}; + size_t cert_chain_length = MAX_PROV30_OEM_CERT_CHAIN_PKCS7_SIZE; + OEMCryptoResult result = + WTPI_LoadOEMPublicCertificate(cert_chain, &cert_chain_length); + if (result != OEMCrypto_SUCCESS) return result; + uint8_t private_key[MAX_PROV30_OEM_KEY_SIZE] = {0}; + size_t private_key_length = MAX_PROV30_OEM_KEY_SIZE; + result = LoadOEMPrivateKey(private_key, &private_key_length); + if (result != OEMCrypto_SUCCESS) { + WTPI_SecureZeroMemory(private_key, sizeof(private_key)); + return result; + } + result = ValidateCertificateChainAndKey(cert_chain, cert_chain_length, + private_key, private_key_length); + WTPI_SecureZeroMemory(private_key, sizeof(private_key)); + return result; +#else + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +#endif +} + +OEMCryptoResult WTPI_CreateAsymmetricKeyHandleFromOEMKey( + WTPI_AsymmetricKey_Handle* key_handle) { +#ifdef USE_PROVISIONING_30 + uint8_t oem_key[MAX_PROV30_OEM_KEY_SIZE]; + size_t oem_key_length = MAX_PROV30_OEM_KEY_SIZE; + OEMCryptoResult result = LoadOEMPrivateKey(oem_key, &oem_key_length); + if (result != OEMCrypto_SUCCESS) { + WTPI_SecureZeroMemory(oem_key, sizeof(oem_key)); + LOGE("Failed to load OEM private key with result: %u", result); + return result; + } + result = WTPI_CreateAsymmetricKeyHandle(oem_key, oem_key_length, + DRM_RSA_PRIVATE_KEY, key_handle); + if (result != OEMCrypto_SUCCESS) { + WTPI_SecureZeroMemory(oem_key, sizeof(oem_key)); + LOGE( + "Failed to create asymmetric key handle for OEM private key with " + "result: %u", + result); + return result; + } + WTPI_SecureZeroMemory(oem_key, sizeof(oem_key)); + return OEMCrypto_SUCCESS; +#else + (void)key_handle; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +#endif +} diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_test/common/GEN_common_serializer.c b/oemcrypto/opk/oemcrypto_ta/wtpi_test/common/GEN_common_serializer.c index 5318fdb..976dc73 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_test/common/GEN_common_serializer.c +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_test/common/GEN_common_serializer.c @@ -103,6 +103,7 @@ bool Is_Valid_OEMCryptoResult(uint32_t value) { case 63: /* OEMCrypto_ERROR_DVR_FORBIDDEN */ case 64: /* OEMCrypto_ERROR_INSUFFICIENT_PRIVILEGE */ case 65: /* OEMCrypto_ERROR_INVALID_KEY */ + case 66: /* OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE */ case 1000: /* ODK_ERROR_BASE */ case 1001: /* ODK_SET_TIMER */ case 1002: /* ODK_DISABLE_TIMER */ diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_test/crypto_test.cpp b/oemcrypto/opk/oemcrypto_ta/wtpi_test/crypto_test.cpp index ca3c2da..7903616 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_test/crypto_test.cpp +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_test/crypto_test.cpp @@ -20,11 +20,6 @@ #include "wtpi_crypto_asymmetric_interface.h" #include "wtpi_device_key_interface.h" -#define TEST_RSA_KEY_DER_LEN 1216 -#define HELLO_WORLD_ENC_LEN 256 -extern uint8_t test_rsa_key_der[TEST_RSA_KEY_DER_LEN]; -extern uint8_t hello_world_encrypted[HELLO_WORLD_ENC_LEN]; - const uint8_t kNoBuffer[1] = {0}; const size_t kNoBufferLength = 0; diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_test/legacy_keywrap_test.cpp b/oemcrypto/opk/oemcrypto_ta/wtpi_test/legacy_keywrap_test.cpp new file mode 100644 index 0000000..96ae90c --- /dev/null +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_test/legacy_keywrap_test.cpp @@ -0,0 +1,165 @@ +// Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +// This file is a direct code copy of keywrap code v1, followed by a GTEST test +// to compare it to the v2 implementation for backards compatibility + +#include +#include + +#include "log.h" +#include "opk_init.h" +#include "test_common.h" +#include "wtpi_crypto_and_key_management_interface_layer1.h" +#include "wtpi_crypto_asymmetric_interface.h" +#include "wtpi_device_key_interface.h" + +#define UUID_LENGTH 16 +// This struct represents a wrapped blob that we do not yet know how to +// interpret. It contains only the fields that we expect every versioned blob to +// have. +typedef struct WrappedData { + uint8_t signature[SHA256_DIGEST_LENGTH]; + uint8_t magic[UUID_LENGTH]; + uint8_t version[sizeof(uint32_t)]; + uint8_t data[]; +} WrappedData; +// The randomly-generated UUID that identifies a blob as a WrappedData struct, +// in network byte order. +static const uint8_t kMagicUuid[UUID_LENGTH] = { + 0xb5, 0x76, 0x3b, 0xad, 0x84, 0x05, 0x40, 0xfd, + 0xa0, 0x88, 0x3b, 0x6c, 0x69, 0x97, 0xfc, 0x74}; +// 1 in network byte order +static const uint8_t kVersionOne[sizeof(uint32_t)] = {0x00, 0x00, 0x00, 0x01}; +// This is the layout of the |data| field of a WrappedData structure when its +// |version| field is 1. +typedef struct WrappedData_V1 { + uint8_t iv[KEY_IV_SIZE]; + uint8_t enc_data[]; +} WrappedData_V1; + +static OEMCryptoResult GetEncryptAndSignSize(uint32_t context, size_t in_size, + size_t* wrapped_size) { + *wrapped_size = in_size + sizeof(WrappedData) + sizeof(WrappedData_V1); + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult EncryptAndSign_V1(uint32_t context, const uint8_t* data, + size_t data_size, uint8_t* out, + size_t* out_size) { + if (!out_size) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + size_t needed_size; + OEMCryptoResult result = + GetEncryptAndSignSize(context, data_size, &needed_size); + if (result != OEMCrypto_SUCCESS) return result; + if (*out_size < needed_size) { + printf("need %zu", needed_size); + *out_size = needed_size; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + // Allow querying size without these buffers. + if (!data || !out) return OEMCrypto_ERROR_INVALID_CONTEXT; + *out_size = needed_size; + WrappedData* const wrapped_header = (WrappedData*)out; + memcpy(wrapped_header->magic, kMagicUuid, sizeof(wrapped_header->magic)); + memcpy(wrapped_header->version, kVersionOne, sizeof(wrapped_header->version)); + WrappedData_V1* const wrapped_data = (WrappedData_V1*)wrapped_header->data; + /* Pick a random IV for generating keys. */ + result = WTPI_C1_RandomBytes(wrapped_data->iv, sizeof(wrapped_data->iv)); + if (result != OEMCrypto_SUCCESS) return result; + // Encrypt the buffer. + WTPI_K1_SymmetricKey_Handle encryption_key = NULL; + result = WTPI_K1_DeriveDeviceKeyIntoHandle(context, ENCRYPTION_KEY, + &encryption_key, KEY_SIZE_128); + if (result != OEMCrypto_SUCCESS) return result; + result = WTPI_C1_AESCBCEncrypt(encryption_key, data, data_size, + wrapped_data->iv, wrapped_data->enc_data); + WTPI_K1_FreeKeyHandle(encryption_key); + if (result != OEMCrypto_SUCCESS) return result; + // Compute the signature of the data past the signature block and store it + // at the start of the output buffer. + WTPI_K1_SymmetricKey_Handle signing_key = NULL; + result = WTPI_K1_DeriveDeviceKeyIntoHandle(context, MAC_KEY_CLIENT, + &signing_key, KEY_SIZE_256); + if (result != OEMCrypto_SUCCESS) return result; + const size_t offset = sizeof(wrapped_header->signature); + result = WTPI_C1_HMAC_SHA256(signing_key, out + offset, needed_size - offset, + wrapped_header->signature); + WTPI_K1_FreeKeyHandle(signing_key); + return result; +} + +class LegacyKeywrapTest : public ::testing::Test { + protected: + LegacyKeywrapTest() {} + + void SetUp() override { + ::testing::Test::SetUp(); + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + LOGD("Running test %s.%s", test_info->test_case_name(), test_info->name()); + ASSERT_EQ(true, OPK_Initialize()); + ASSERT_EQ(OEMCrypto_SUCCESS, WTPI_K1_InitializeKeyManagement()); + } + + void TearDown() override { + ASSERT_EQ(OEMCrypto_SUCCESS, WTPI_K1_TerminateKeyManagement()); + OPK_Terminate(); + ::testing::Test::TearDown(); + } +}; + +TEST_F(LegacyKeywrapTest, UnwrapDrmLegacyKey) { + OEMCryptoResult res = OEMCrypto_ERROR_INVALID_CONTEXT; + std::vector wrapped_key; + size_t required_size = wrapped_key.size(); + uint8_t* clear_key = test_rsa_key_der; + size_t clear_key_len = TEST_RSA_KEY_DER_LEN; + AsymmetricKeyType key_type = DRM_RSA_PRIVATE_KEY; + + // Get required max size (most recent impl, larger than v1 wrapping size) + res = + WTPI_GetWrappedAsymmetricKeySize(clear_key_len, key_type, &required_size); + ASSERT_EQ(res, OEMCrypto_SUCCESS); + wrapped_key.resize(required_size); + + // Will be changed to reflect the actual size of the wrapped key + size_t v1_wrapped_key_size = wrapped_key.size(); + + // Wrap and unwrap using v1 scheme + res = EncryptAndSign_V1(DEVICE_KEY_WRAP_DRM_CERT_V1, clear_key, + clear_key_len, wrapped_key.data(), + &v1_wrapped_key_size); + ASSERT_EQ(res, OEMCrypto_SUCCESS); + + WTPI_AsymmetricKey_Handle unwrapped_key_handle; + uint32_t allowed_schemes = 0; + res = WTPI_UnwrapIntoAsymmetricKeyHandle( + DEVICE_KEY_WRAP_DRM_CERT, wrapped_key.data(), v1_wrapped_key_size, + key_type, &unwrapped_key_handle, &allowed_schemes); + ASSERT_EQ(res, OEMCrypto_SUCCESS); + + // Create a key handle directly + WTPI_AsymmetricKey_Handle created_key_handle; + res = WTPI_CreateAsymmetricKeyHandle(clear_key, clear_key_len, key_type, + &created_key_handle); + ASSERT_EQ(res, OEMCrypto_SUCCESS); + + // Compare the two key handles to ensure they are the same + for (WTPI_AsymmetricKey_Handle handle : + {unwrapped_key_handle, created_key_handle}) { + std::vector output(256, 0); + size_t output_len = 256; + ASSERT_EQ(OEMCrypto_SUCCESS, + WTPI_RSADecrypt(handle, hello_world_encrypted, + HELLO_WORLD_ENC_LEN, output.data(), &output_len)); + + output.resize(output_len); + std::string message = "Hello world!\n"; + std::vector expected(message.begin(), message.end()); + ASSERT_EQ(expected, output); + } +} diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_test/test_common.h b/oemcrypto/opk/oemcrypto_ta/wtpi_test/test_common.h index 6fdd918..8c7a91f 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_test/test_common.h +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_test/test_common.h @@ -6,6 +6,11 @@ #include "wtpi_crypto_and_key_management_interface_layer1.h" #include "wtpi_crypto_asymmetric_interface.h" +#define TEST_RSA_KEY_DER_LEN 1216 +#define HELLO_WORLD_ENC_LEN 256 +extern uint8_t test_rsa_key_der[TEST_RSA_KEY_DER_LEN]; +extern uint8_t hello_world_encrypted[HELLO_WORLD_ENC_LEN]; + using WtpiSymmetricKeyType = std::remove_pointer::type; const auto wtpi_symmetric_key_free = [](WtpiSymmetricKeyType* key) { diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_test/wtpi_test.gyp b/oemcrypto/opk/oemcrypto_ta/wtpi_test/wtpi_test.gyp index 5f94f5f..27febc0 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_test/wtpi_test.gyp +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_test/wtpi_test.gyp @@ -57,6 +57,7 @@ 'ssl_util.cpp', 'cose_util.cpp', 'test_rsa_key.cpp', + 'legacy_keywrap_test.cpp', '<(DEPTH)/linux/src/log.cpp', '<(DEPTH)/util/src/string_conversions.cpp', ], diff --git a/oemcrypto/opk/oemcrypto_ta/wtpi_useless/wtpi_root_of_trust_layer2.c b/oemcrypto/opk/oemcrypto_ta/wtpi_useless/wtpi_root_of_trust_layer2.c index fb211a3..8a29a3f 100644 --- a/oemcrypto/opk/oemcrypto_ta/wtpi_useless/wtpi_root_of_trust_layer2.c +++ b/oemcrypto/opk/oemcrypto_ta/wtpi_useless/wtpi_root_of_trust_layer2.c @@ -7,8 +7,11 @@ #include #include +#include "config/default.h" +#include "oemcrypto_compiler_attributes.h" #include "wtpi_config_interface.h" #include "wtpi_crypto_and_key_management_interface_layer1.h" +#include "wtpi_device_key_interface.h" #include "wtpi_logging_interface.h" // In practice, ROT_SIZE is the size of a keybox. @@ -16,6 +19,13 @@ static size_t gBufferSize = 0; static uint8_t gBuffer[MAX_ROT_SIZE]; +#ifdef USE_PROVISIONING_30 +static size_t gOEMCertBufferSize = 0; +static uint8_t gOEMCertBuffer[MAX_PROV30_OEM_CERT_CHAIN_PKCS7_SIZE]; +static size_t gOEMPrivateKeyBufferSize = 0; +static uint8_t gOEMPrivateKeyBuffer[MAX_PROV30_OEM_KEY_SIZE]; +#endif + OEMCryptoResult WTPI_UnwrapRootOfTrust(const uint8_t* input, size_t input_length, uint8_t* output, size_t* output_length) { @@ -44,3 +54,97 @@ OEMCryptoResult WTPI_LoadRootOfTrust(uint8_t* output, size_t* length) { memcpy(output, gBuffer, gBufferSize); return OEMCrypto_SUCCESS; } + +OEMCryptoResult WTPI_WrapRootOfTrust30(const uint8_t* input, + size_t input_length, uint8_t* output, + size_t* output_length) { +#ifdef USE_PROVISIONING_30 + return WTPI_EncryptAndSign(DEVICE_KEY_WRAP_OEM_CERT, input, input_length, + output, output_length); +#else + (void)input; + (void)input_length; + (void)output; + (void)output_length; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +#endif +} + +OEMCryptoResult WTPI_UnwrapRootOfTrust30(const uint8_t* input, + size_t input_length, uint8_t* output, + size_t* output_length) { +#ifdef USE_PROVISIONING_30 + return WTPI_VerifyAndDecrypt(DEVICE_KEY_WRAP_OEM_CERT, input, input_length, + output, output_length); +#else + (void)input; + (void)input_length; + (void)output; + (void)output_length; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +#endif +} + +OEMCryptoResult WTPI_SaveOEMPrivateKey30(const uint8_t* input, + size_t input_length) { +#ifdef USE_PROVISIONING_30 + if (input == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; + if (input_length > MAX_PROV30_OEM_KEY_SIZE) + return OEMCrypto_ERROR_INVALID_CONTEXT; + gOEMPrivateKeyBufferSize = input_length; + memcpy(&gOEMPrivateKeyBuffer, input, input_length); + return OEMCrypto_SUCCESS; +#else + (void)input; + (void)input_length; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +#endif +} + +OEMCryptoResult WTPI_LoadOEMPrivateKey30(uint8_t* output, + size_t* output_length) { +#ifdef USE_PROVISIONING_30 + if (output_length == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; + if (output == NULL || *output_length < gOEMPrivateKeyBufferSize) + return OEMCrypto_ERROR_SHORT_BUFFER; + *output_length = gOEMPrivateKeyBufferSize; + memcpy(output, gOEMPrivateKeyBuffer, gOEMPrivateKeyBufferSize); + return OEMCrypto_SUCCESS; +#else + (void)output; + (void)output_length; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +#endif +} + +OEMCryptoResult WTPI_SaveOEMPublicCertificate30(const uint8_t* input, + size_t input_length) { +#ifdef USE_PROVISIONING_30 + if (input == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; + if (input_length > MAX_PROV30_OEM_CERT_CHAIN_PKCS7_SIZE) + return OEMCrypto_ERROR_INVALID_CONTEXT; + gOEMCertBufferSize = input_length; + memcpy(&gOEMCertBuffer, input, input_length); + return OEMCrypto_SUCCESS; +#else + (void)input; + (void)input_length; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +#endif +} + +OEMCryptoResult WTPI_LoadOEMPublicCertificate30(uint8_t* output, + size_t* output_length) { +#ifdef USE_PROVISIONING_30 + if (output_length == NULL) return OEMCrypto_ERROR_INVALID_CONTEXT; + if (output == NULL || *output_length < gOEMCertBufferSize) + return OEMCrypto_ERROR_SHORT_BUFFER; + *output_length = gOEMCertBufferSize; + memcpy(output, gOEMCertBuffer, gOEMCertBufferSize); + return OEMCrypto_SUCCESS; +#else + (void)output; + (void)output_length; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +#endif +} diff --git a/oemcrypto/opk/ports/linux/ta/common/wtpi_impl/sources.mk b/oemcrypto/opk/ports/linux/ta/common/wtpi_impl/sources.mk index a8851f1..eaa0666 100644 --- a/oemcrypto/opk/ports/linux/ta/common/wtpi_impl/sources.mk +++ b/oemcrypto/opk/ports/linux/ta/common/wtpi_impl/sources.mk @@ -51,6 +51,10 @@ wtpi_impl_sources += \ $(tos_impl_dir)/tos_secure_buffers.c \ $(tos_impl_dir)/tos_transport.cpp \ +ifdef USE_PROVISIONING_30 + wtpi_impl_sources += $(wtpi_ref_dir)/prov30_factory_util.c +endif + wtpi_impl_includes += \ $(wtpi_impl_dir) \ $(wtpi_ref_dir) \ diff --git a/oemcrypto/opk/ports/linux/ta/common/wtpi_impl/wtpi_test_impl.gyp b/oemcrypto/opk/ports/linux/ta/common/wtpi_impl/wtpi_test_impl.gyp index cc59c25..2e83f4f 100644 --- a/oemcrypto/opk/ports/linux/ta/common/wtpi_impl/wtpi_test_impl.gyp +++ b/oemcrypto/opk/ports/linux/ta/common/wtpi_impl/wtpi_test_impl.gyp @@ -15,6 +15,7 @@ 'use_provisioning_40%': 0, 'use_dice_bcc%': 0, 'prov4_bcc_signature_type%': 'none', + 'use_random_device_key%': 0, }, 'target_defaults': { 'toolsets' : [ 'target' ], @@ -35,7 +36,6 @@ 'test-only/wtpi_persistent_storage_layer1.c', 'test-only/file_store_interface.c', 'test-only/wtpi_cas.c', - 'test-only/wtpi_device_key_access.c', '<(wtpi_stub_dir)/wtpi_root_of_trust_layer2.c', '<(wtpi_stub_dir)/wtpi_secure_buffer_access.c', '<(wtpi_stub_dir)/wtpi_fused.c', @@ -62,6 +62,11 @@ '<(wtpi_stub_dir)/wtpi_bcc_signature_keybox.c', ], }], + ['use_random_device_key', { + 'sources': ['<(wtpi_stub_dir)/wtpi_device_key_access.c',], + }, { + 'sources': ['test-only/wtpi_device_key_access.c',], + }], ], 'dependencies': [ '<(odk_dir)/src/odk.gyp:odk', diff --git a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_crypto_asymmetric.c b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_crypto_asymmetric.c index 39e88c3..4143877 100644 --- a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_crypto_asymmetric.c +++ b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_crypto_asymmetric.c @@ -663,6 +663,6 @@ OEMCryptoResult WTPI_DeriveNewAsymmetricKeyHandle( const uint8_t* deriving_key, size_t deriving_key_length, const uint8_t* context, size_t context_length, AsymmetricKeyType key_type, WTPI_AsymmetricKey_Handle* private_key_handle, uint8_t* public_key, - size_t* public_key_length){ + size_t* public_key_length) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } diff --git a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_root_of_trust_layer1.c b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_root_of_trust_layer1.c index 9394a18..be9ab93 100644 --- a/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_root_of_trust_layer1.c +++ b/oemcrypto/opk/ports/optee/ta/common/wtpi_impl/wtpi_root_of_trust_layer1.c @@ -304,3 +304,29 @@ OEMCryptoResult WTPI_K1_CreateKeyHandleFromKeybox( DERIVING_KEY, out); } } + +OEMCryptoResult WTPI_WrapOEMCert(const uint8_t* input UNUSED, + size_t input_length UNUSED, + uint8_t* wrapped_cert UNUSED, + size_t* wrapped_cert_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult WTPI_UnwrapValidateAndInstallOEMCert( + const uint8_t* input UNUSED, size_t input_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult WTPI_LoadOEMPublicCertificate(uint8_t* output UNUSED, + size_t* output_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult WTPI_ValidateOEMCertAndKey(void) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult WTPI_CreateAsymmetricKeyHandleFromOEMKey( + WTPI_AsymmetricKey_Handle* key_handle UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} diff --git a/oemcrypto/opk/ports/trusty/ta/reference/interface_impls/wtpi_clock_layer2.c b/oemcrypto/opk/ports/trusty/ta/reference/interface_impls/wtpi_clock_layer2.c index a9a7ac4..7ea83fe 100644 --- a/oemcrypto/opk/ports/trusty/ta/reference/interface_impls/wtpi_clock_layer2.c +++ b/oemcrypto/opk/ports/trusty/ta/reference/interface_impls/wtpi_clock_layer2.c @@ -19,12 +19,16 @@ #include #include +#include #include OEMCryptoResult WTPI_GetSecureTimer(uint64_t* time_in_s) { int64_t now; - trusty_gettime(0, &now); + int rc = trusty_gettime(0, &now); + if (rc != NO_ERROR) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } *time_in_s = now / 1000000000ll; return OEMCrypto_SUCCESS; } diff --git a/oemcrypto/opk/ports/trusty/ta/reference/interface_impls/wtpi_root_of_trust_layer2.c b/oemcrypto/opk/ports/trusty/ta/reference/interface_impls/wtpi_root_of_trust_layer2.c index fa13341..26cb2a4 100644 --- a/oemcrypto/opk/ports/trusty/ta/reference/interface_impls/wtpi_root_of_trust_layer2.c +++ b/oemcrypto/opk/ports/trusty/ta/reference/interface_impls/wtpi_root_of_trust_layer2.c @@ -34,135 +34,187 @@ static const char* kKeyboxPath = "widevine_keybox"; OEMCryptoResult WTPI_UnwrapRootOfTrust(const uint8_t* input, - size_t input_length, - uint8_t* output, + size_t input_length, uint8_t* output, size_t* output_length) { - TLOGD("WTPI_UnwrapRootOfTrust\n"); + TLOGD("WTPI_UnwrapRootOfTrust\n"); - if (input == NULL || output == NULL || output_length == NULL) { - TLOGE("WTPI_UnwrapRootOfTrust: invalid parameters\n"); - return OEMCrypto_ERROR_INVALID_CONTEXT; - } + if (input == NULL || output == NULL || output_length == NULL) { + TLOGE("WTPI_UnwrapRootOfTrust: invalid parameters\n"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } - /* Try to unwrap the keybox. */ - int rc = keybox_unwrap(input, input_length, output, *output_length, - output_length); - if (rc == KEYBOX_STATUS_SUCCESS) { - return OEMCrypto_SUCCESS; - } - - /* If unwrapping failed, assume the keybox is cleartext. */ - TLOGW("WTPI_UnwrapRootOfTrust: decryption failed (%d), assuming cleartext\n", - rc); - - /* The output buffer needs to be at least the size of the input buffer. */ - size_t min_output_length = input_length; - if (min_output_length > *output_length) { - *output_length = min_output_length; - TLOGE("WTPI_UnwrapRootOfTrust: output buffer too short, should be at least %zu bytes\n", - min_output_length); - return OEMCrypto_ERROR_SHORT_BUFFER; - } - - *output_length = min_output_length; - memcpy(output, input, *output_length); + /* Try to unwrap the keybox. */ + int rc = + keybox_unwrap(input, input_length, output, *output_length, output_length); + if (rc == KEYBOX_STATUS_SUCCESS) { return OEMCrypto_SUCCESS; + } + + /* If unwrapping failed, assume the keybox is cleartext. */ + TLOGW("WTPI_UnwrapRootOfTrust: decryption failed (%d), assuming cleartext\n", + rc); + + /* The output buffer needs to be at least the size of the input buffer. */ + size_t min_output_length = input_length; + if (min_output_length > *output_length) { + *output_length = min_output_length; + TLOGE( + "WTPI_UnwrapRootOfTrust: output buffer too short, should be at least " + "%zu bytes\n", + min_output_length); + return OEMCrypto_ERROR_SHORT_BUFFER; + } + + *output_length = min_output_length; + memcpy(output, input, *output_length); + return OEMCrypto_SUCCESS; } OEMCryptoResult WTPI_SaveRootOfTrust(const uint8_t* keybox, size_t length) { - TLOGD("WTPI_SaveRootOfTrust\n"); - tee_context* ctx = get_tee_context(); - assert(ctx->new_provisioning_session != STORAGE_INVALID_SESSION); + TLOGD("WTPI_SaveRootOfTrust\n"); + tee_context* ctx = get_tee_context(); + assert(ctx->new_provisioning_session != STORAGE_INVALID_SESSION); - if (!system_state_provisioning_allowed()) { - TLOGE("provisioning not allowed\n"); - return OEMCrypto_ERROR_INVALID_CONTEXT; - } + if (!system_state_provisioning_allowed()) { + TLOGE("provisioning not allowed\n"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } - if (keybox == NULL) { - TLOGE("WTPI_SaveRootOfTrust: no keybox provided\n"); - return OEMCrypto_ERROR_INVALID_CONTEXT; - } + if (keybox == NULL) { + TLOGE("WTPI_SaveRootOfTrust: no keybox provided\n"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } - file_handle_t handle; - int rc = storage_open_file( - ctx->new_provisioning_session, &handle, kKeyboxPath, - STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, 0); - if (rc < 0) { - TLOGE("WTPI_SaveRootOfTrust: failed to create keybox: %d\n", rc); - return OEMCrypto_ERROR_OPEN_FAILURE; - } - rc = storage_write(handle, 0, keybox, length, STORAGE_OP_COMPLETE); - storage_close_file(handle); - if (rc < 0) { - TLOGE("WTPI_SaveRootOfTrust: failed to write keybox: %d\n", rc); - return OEMCrypto_ERROR_CLOSE_FAILURE; - } + file_handle_t handle; + int rc = storage_open_file( + ctx->new_provisioning_session, &handle, kKeyboxPath, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, 0); + if (rc < 0) { + TLOGE("WTPI_SaveRootOfTrust: failed to create keybox: %d\n", rc); + return OEMCrypto_ERROR_OPEN_FAILURE; + } + rc = storage_write(handle, 0, keybox, length, STORAGE_OP_COMPLETE); + storage_close_file(handle); + if (rc < 0) { + TLOGE("WTPI_SaveRootOfTrust: failed to write keybox: %d\n", rc); + return OEMCrypto_ERROR_CLOSE_FAILURE; + } - return OEMCrypto_SUCCESS; + return OEMCrypto_SUCCESS; } OEMCryptoResult WTPI_LoadRootOfTrust(uint8_t* keybox, size_t* length) { - TLOGD("WTPI_LoadRootOfTrust\n"); - tee_context* ctx = get_tee_context(); - assert(ctx->new_provisioning_session != STORAGE_INVALID_SESSION); + TLOGD("WTPI_LoadRootOfTrust\n"); + tee_context* ctx = get_tee_context(); + assert(ctx->new_provisioning_session != STORAGE_INVALID_SESSION); - if (keybox == NULL || length == NULL) { - TLOGE("WTPI_LoadRootOfTrust: invalid parameters\n"); - return OEMCrypto_ERROR_INVALID_CONTEXT; - } + if (keybox == NULL || length == NULL) { + TLOGE("WTPI_LoadRootOfTrust: invalid parameters\n"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } - file_handle_t handle; - int rc = storage_open_file(ctx->new_provisioning_session, &handle, - kKeyboxPath, 0, 0); - bool loading_from_backup = false; + file_handle_t handle; + int rc = storage_open_file(ctx->new_provisioning_session, &handle, + kKeyboxPath, 0, 0); + bool loading_from_backup = false; + if (rc < 0) { + TLOGE( + "WTPI_LoadRootOfTrust: error opening keybox on TP. Attempting TDP.\n"); + assert(ctx->old_provisioning_session != STORAGE_INVALID_SESSION); + rc = storage_open_file(ctx->old_provisioning_session, &handle, kKeyboxPath, + 0, 0); if (rc < 0) { - TLOGE("WTPI_LoadRootOfTrust: error opening keybox on TP. Attempting TDP.\n"); - assert(ctx->old_provisioning_session != STORAGE_INVALID_SESSION); - rc = storage_open_file(ctx->old_provisioning_session, &handle, - kKeyboxPath, 0, 0); - if (rc < 0) { - TLOGE("WTPI_LoadRootOfTrust: error opening keybox: %d\n", rc); - return OEMCrypto_ERROR_OPEN_FAILURE; - } - - loading_from_backup = true; + TLOGE("WTPI_LoadRootOfTrust: error opening keybox: %d\n", rc); + return OEMCrypto_ERROR_OPEN_FAILURE; } - storage_off_t keysize; - rc = storage_get_file_size(handle, &keysize); - if (rc < 0) { - TLOGE("WTPI_LoadRootOfTrust: couldn't get file size: %d\n", rc); - storage_close_file(handle); - return OEMCrypto_ERROR_OPEN_FAILURE; - } + loading_from_backup = true; + } - if (*length < keysize) { - TLOGE("WTPI_LoadRootOfTrust: output buffer too small, should be at least %zu bytes\n", - (size_t)keysize); - storage_close_file(handle); - *length = keysize; - return OEMCrypto_ERROR_SHORT_BUFFER; - } - - rc = storage_read(handle, 0, keybox, keysize); + storage_off_t keysize; + rc = storage_get_file_size(handle, &keysize); + if (rc < 0) { + TLOGE("WTPI_LoadRootOfTrust: couldn't get file size: %d\n", rc); storage_close_file(handle); - if (rc < 0) { - TLOGE("WTPI_LoadRootOfTrust: error reading keybox: %d\n", rc); - return OEMCrypto_ERROR_KEYBOX_INVALID; - } - assert((size_t)rc == keysize); - - if (loading_from_backup) { - TLOGD("WTPI_LoadRootOfTrust: attempting to copy keybox from TDP to TP\n"); - rc = WTPI_SaveRootOfTrust(keybox, keysize); - if (rc != OEMCrypto_SUCCESS) { - TLOGE("WTPI_LoadRootOfTrust: failed to copy keybox from TDP to TP\n"); - } - } - - TLOGD("WTPI_LoadRootOfTrust: read %lu bytes for keybox\n", keysize); + return OEMCrypto_ERROR_OPEN_FAILURE; + } + if (*length < keysize) { + TLOGE( + "WTPI_LoadRootOfTrust: output buffer too small, should be at least %zu " + "bytes\n", + (size_t)keysize); + storage_close_file(handle); *length = keysize; - return OEMCrypto_SUCCESS; + return OEMCrypto_ERROR_SHORT_BUFFER; + } + + rc = storage_read(handle, 0, keybox, keysize); + storage_close_file(handle); + if (rc < 0) { + TLOGE("WTPI_LoadRootOfTrust: error reading keybox: %d\n", rc); + return OEMCrypto_ERROR_KEYBOX_INVALID; + } + assert((size_t)rc == keysize); + + if (loading_from_backup) { + TLOGD("WTPI_LoadRootOfTrust: attempting to copy keybox from TDP to TP\n"); + rc = WTPI_SaveRootOfTrust(keybox, keysize); + if (rc != OEMCrypto_SUCCESS) { + TLOGE("WTPI_LoadRootOfTrust: failed to copy keybox from TDP to TP\n"); + } + } + + TLOGD("WTPI_LoadRootOfTrust: read %lu bytes for keybox\n", keysize); + + *length = keysize; + return OEMCrypto_SUCCESS; } + +OEMCryptoResult WTPI_WrapRootOfTrust30(const uint8_t* input, + size_t input_length, uint8_t* output, + size_t* output_length) { + (void)input; + (void)input_length; + (void)output; + (void)output_length; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult WTPI_UnwrapRootOfTrust30(const uint8_t* input, + size_t input_length, uint8_t* output, + size_t* output_length) { + (void)input; + (void)input_length; + (void)output; + (void)output_length; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult WTPI_SaveOEMPrivateKey30(const uint8_t* input, + size_t input_length) { + (void)input; + (void)input_length; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult WTPI_LoadOEMPrivateKey30(uint8_t* output, + size_t* output_length) { + (void)output; + (void)output_length; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult WTPI_SaveOEMPublicCertificate30(const uint8_t* input, + size_t input_length) { + (void)input; + (void)input_length; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult WTPI_LoadOEMPublicCertificate30(uint8_t* output, + size_t* output_length) { + (void)output; + (void)output_length; + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} \ No newline at end of file diff --git a/oemcrypto/opk/serialization/common/GEN_common_serializer.c b/oemcrypto/opk/serialization/common/GEN_common_serializer.c index a5107b6..76e9e60 100644 --- a/oemcrypto/opk/serialization/common/GEN_common_serializer.c +++ b/oemcrypto/opk/serialization/common/GEN_common_serializer.c @@ -98,6 +98,7 @@ bool Is_Valid_OEMCryptoResult(uint32_t value) { case 63: /* OEMCrypto_ERROR_DVR_FORBIDDEN */ case 64: /* OEMCrypto_ERROR_INSUFFICIENT_PRIVILEGE */ case 65: /* OEMCrypto_ERROR_INVALID_KEY */ + case 66: /* OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE */ case 1000: /* ODK_ERROR_BASE */ case 1001: /* ODK_SET_TIMER */ case 1002: /* ODK_DISABLE_TIMER */ @@ -372,16 +373,6 @@ void OPK_Pack_OEMCrypto_KeyObject(ODK_Message* msg, (const OEMCrypto_Substring*)&obj->key_control); } -void OPK_Pack_OEMCrypto_InputOutputPair(ODK_Message* msg, - OEMCrypto_InputOutputPair const* obj) { - OPK_Pack_size_t(msg, (const size_t*)&obj->input_data_length); - OPK_PackSharedBuffer(msg, obj->input_data, - OPK_ToLengthType(obj->input_data_length), /* map */ true, - /* copy_in */ true, /* is_output */ false); - OPK_Pack_OEMCrypto_DestBufferDesc( - msg, (const OEMCrypto_DestBufferDesc*)&obj->output_descriptor); -} - void OPK_Pack_OEMCrypto_SubSampleDescription( ODK_Message* msg, OEMCrypto_SubSampleDescription const* obj) { OPK_Pack_size_t(msg, (const size_t*)&obj->num_bytes_clear); @@ -533,21 +524,6 @@ void OPK_Unpack_OEMCrypto_KeyObject(ODK_Message* msg, OPK_Unpack_OEMCrypto_Substring(msg, &obj->key_control); } -void OPK_Unpack_OEMCrypto_InputOutputPair(ODK_Message* msg, - OEMCrypto_InputOutputPair* obj) { - OEMCrypto_InputOutputPair tmp_obj; - if (obj == NULL) { - obj = &tmp_obj; - } - OPK_Unpack_size_t(msg, &obj->input_data_length); - OEMCrypto_SharedMemory* input_data = NULL; - OPK_UnpackSharedBuffer(msg, &input_data, - OPK_ToLengthType(obj->input_data_length), - /* map */ true, /* is_output */ false); - OPK_Unpack_OEMCrypto_DestBufferDesc(msg, &obj->output_descriptor); - memcpy(&obj->input_data, &input_data, sizeof(input_data)); -} - void OPK_Unpack_OEMCrypto_SubSampleDescription( ODK_Message* msg, OEMCrypto_SubSampleDescription* obj) { OEMCrypto_SubSampleDescription tmp_obj; @@ -755,6 +731,21 @@ void OPK_UnpackNullable_OEMCrypto_DestBufferDesc( OPK_Unpack_OEMCrypto_DestBufferDesc(msg, NULL); } } +void OPK_PackNullable_uint8_t(ODK_Message* msg, const uint8_t* value) { + OPK_PackBoolValue(msg, value == NULL); + if (value) { + OPK_Pack_uint8_t(msg, value); + } +} +void OPK_UnpackNullable_uint8_t(ODK_Message* msg, uint8_t** value) { + if (OPK_UnpackIsNull(msg)) { + if (value) *value = NULL; + } else if (value) { + OPK_Unpack_uint8_t(msg, *value); + } else { + OPK_Unpack_uint8_t(msg, NULL); + } +} void OPK_PackNullable_uint16_t(ODK_Message* msg, const uint16_t* value) { OPK_PackBoolValue(msg, value == NULL); if (value) { diff --git a/oemcrypto/opk/serialization/common/GEN_common_serializer.h b/oemcrypto/opk/serialization/common/GEN_common_serializer.h index 1c1b825..cf9b281 100644 --- a/oemcrypto/opk/serialization/common/GEN_common_serializer.h +++ b/oemcrypto/opk/serialization/common/GEN_common_serializer.h @@ -48,8 +48,6 @@ void OPK_Pack_OEMCrypto_DTCP2_CMI_Packet(ODK_Message* msg, OEMCrypto_DTCP2_CMI_Packet const* obj); void OPK_Pack_OEMCrypto_KeyObject(ODK_Message* msg, OEMCrypto_KeyObject const* obj); -void OPK_Pack_OEMCrypto_InputOutputPair(ODK_Message* msg, - OEMCrypto_InputOutputPair const* obj); void OPK_Pack_OEMCrypto_SubSampleDescription( ODK_Message* msg, OEMCrypto_SubSampleDescription const* obj); void OPK_Pack_OEMCrypto_SampleDescription( @@ -74,8 +72,6 @@ void OPK_Unpack_OEMCrypto_DTCP2_CMI_Descriptor_2( void OPK_Unpack_OEMCrypto_DTCP2_CMI_Packet(ODK_Message* msg, OEMCrypto_DTCP2_CMI_Packet* obj); void OPK_Unpack_OEMCrypto_KeyObject(ODK_Message* msg, OEMCrypto_KeyObject* obj); -void OPK_Unpack_OEMCrypto_InputOutputPair(ODK_Message* msg, - OEMCrypto_InputOutputPair* obj); void OPK_Unpack_OEMCrypto_SubSampleDescription( ODK_Message* msg, OEMCrypto_SubSampleDescription* obj); void OPK_Unpack_OEMCrypto_SampleDescription(ODK_Message* msg, @@ -108,6 +104,8 @@ void OPK_PackNullable_OEMCrypto_DestBufferDesc( ODK_Message* msg, const OEMCrypto_DestBufferDesc* value); void OPK_UnpackNullable_OEMCrypto_DestBufferDesc( ODK_Message* msg, OEMCrypto_DestBufferDesc** value); +void OPK_PackNullable_uint8_t(ODK_Message* msg, const uint8_t* value); +void OPK_UnpackNullable_uint8_t(ODK_Message* msg, uint8_t** value); void OPK_PackNullable_uint16_t(ODK_Message* msg, const uint16_t* value); void OPK_UnpackNullable_uint16_t(ODK_Message* msg, uint16_t** value); void OPK_UnpackAlloc_uint16_t(ODK_Message* msg, uint16_t** value); diff --git a/oemcrypto/opk/serialization/common/common_special_cases.c b/oemcrypto/opk/serialization/common/common_special_cases.c index 19d60f2..035bba1 100644 --- a/oemcrypto/opk/serialization/common/common_special_cases.c +++ b/oemcrypto/opk/serialization/common/common_special_cases.c @@ -95,3 +95,28 @@ void OPK_Unpack_OEMCrypto_DestBufferDesc(ODK_Message* message, break; } } + +void OPK_Pack_OEMCrypto_InputOutputPair(ODK_Message* msg, + OEMCrypto_InputOutputPair const* obj) { + OPK_Pack_size_t(msg, (const size_t*)&obj->input_data_length); + OPK_PackSharedBuffer(msg, obj->input_data, + OPK_ToLengthType(obj->input_data_length), /* map */ true, + /* copy_in */ true, /* is_output */ false); + OPK_Pack_OEMCrypto_DestBufferDesc( + msg, (const OEMCrypto_DestBufferDesc*)&obj->output_descriptor); +} + +void OPK_Unpack_OEMCrypto_InputOutputPair(ODK_Message* msg, + OEMCrypto_InputOutputPair* obj) { + OEMCrypto_InputOutputPair tmp_obj; + if (obj == NULL) { + obj = &tmp_obj; + } + OPK_Unpack_size_t(msg, &obj->input_data_length); + OEMCrypto_SharedMemory* input_data = NULL; + OPK_UnpackSharedBuffer(msg, &input_data, + OPK_ToLengthType(obj->input_data_length), + /* map */ true, /* is_output */ false); + OPK_Unpack_OEMCrypto_DestBufferDesc(msg, &obj->output_descriptor); + memcpy(&obj->input_data, &input_data, sizeof(input_data)); +} diff --git a/oemcrypto/opk/serialization/common/common_special_cases.h b/oemcrypto/opk/serialization/common/common_special_cases.h index cb6d352..a3a8a2d 100644 --- a/oemcrypto/opk/serialization/common/common_special_cases.h +++ b/oemcrypto/opk/serialization/common/common_special_cases.h @@ -25,6 +25,11 @@ void OPK_Pack_OEMCrypto_DestBufferDesc(ODK_Message* msg, void OPK_Unpack_OEMCrypto_DestBufferDesc(ODK_Message* msg, OEMCrypto_DestBufferDesc* obj); +void OPK_Pack_OEMCrypto_InputOutputPair(ODK_Message* msg, + OEMCrypto_InputOutputPair const* obj); +void OPK_Unpack_OEMCrypto_InputOutputPair(ODK_Message* msg, + OEMCrypto_InputOutputPair* obj); + #ifdef __cplusplus } // extern "C" #endif diff --git a/oemcrypto/opk/serialization/common/include/marshaller_base.h b/oemcrypto/opk/serialization/common/include/marshaller_base.h index b3eb9c0..b0ca99d 100644 --- a/oemcrypto/opk/serialization/common/include/marshaller_base.h +++ b/oemcrypto/opk/serialization/common/include/marshaller_base.h @@ -47,6 +47,8 @@ void* OPK_VarAlloc(size_t size); */ void OPK_Init_OEMCrypto_DestBufferDesc(OEMCrypto_DestBufferDesc* desc); +void OPK_Init_OEMCrypto_InputOutputPair(OEMCrypto_InputOutputPair* obj); + #ifdef __cplusplus } // extern "C" #endif diff --git a/oemcrypto/opk/serialization/common/marshaller_base.c b/oemcrypto/opk/serialization/common/marshaller_base.c index 45109aa..b7b9810 100644 --- a/oemcrypto/opk/serialization/common/marshaller_base.c +++ b/oemcrypto/opk/serialization/common/marshaller_base.c @@ -92,3 +92,10 @@ void OPK_Init_OEMCrypto_DestBufferDesc(OEMCrypto_DestBufferDesc* d) { d->type = OEMCrypto_BufferType_Clear; } } + +void OPK_Init_OEMCrypto_InputOutputPair(OEMCrypto_InputOutputPair* obj) { + OPK_Init_size_t((size_t*)&obj->input_data_length); + obj->input_data = NULL; + OPK_Init_OEMCrypto_DestBufferDesc( + (OEMCrypto_DestBufferDesc*)&obj->output_descriptor); +} diff --git a/oemcrypto/opk/serialization/ree/GEN_oemcrypto_api.c b/oemcrypto/opk/serialization/ree/GEN_oemcrypto_api.c index 48db966..878bae5 100644 --- a/oemcrypto/opk/serialization/ree/GEN_oemcrypto_api.c +++ b/oemcrypto/opk/serialization/ree/GEN_oemcrypto_api.c @@ -1356,6 +1356,43 @@ cleanup_and_return: return result; } +OEMCRYPTO_API OEMCryptoResult OEMCrypto_WrapClearPrivateKey( + const uint8_t* clear_private_key_bytes, size_t clear_private_key_length, + uint8_t* wrapped_private_key, size_t* wrapped_private_key_length) { + pthread_mutex_lock(&api_lock); + OEMCryptoResult result = OEMCrypto_ERROR_UNKNOWN_FAILURE; + ODK_Message request = ODK_Message_Create(NULL, 0); + ODK_Message response = ODK_Message_Create(NULL, 0); + + API_Initialize(); + request = OPK_Pack_WrapClearPrivateKey_Request( + clear_private_key_bytes, clear_private_key_length, wrapped_private_key, + wrapped_private_key_length); + if (ODK_Message_GetStatus(&request) != MESSAGE_STATUS_OK) { + if (ODK_Message_GetStatus(&request) == MESSAGE_STATUS_BUFFER_TOO_LARGE) { + api_result = OEMCrypto_ERROR_BUFFER_TOO_LARGE; + } else { + api_result = OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + goto cleanup_and_return; + } + response = API_Transact(&request); + if (api_result != OEMCrypto_SUCCESS) goto cleanup_and_return; + OPK_Unpack_WrapClearPrivateKey_Response( + &response, &result, &wrapped_private_key, &wrapped_private_key_length); + + if (ODK_Message_GetStatus(&response) != MESSAGE_STATUS_OK) { + api_result = OEMCrypto_ERROR_UNKNOWN_FAILURE; + } +cleanup_and_return: + TOS_Transport_ReleaseMessage(&request); + TOS_Transport_ReleaseMessage(&response); + + result = API_CheckResult(result); + pthread_mutex_unlock(&api_lock); + return result; +} + OEMCRYPTO_API OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* key_data, size_t* key_data_length) { pthread_mutex_lock(&api_lock); diff --git a/oemcrypto/opk/serialization/ree/GEN_ree_serializer.c b/oemcrypto/opk/serialization/ree/GEN_ree_serializer.c index aa90b54..596324a 100644 --- a/oemcrypto/opk/serialization/ree/GEN_ree_serializer.c +++ b/oemcrypto/opk/serialization/ree/GEN_ree_serializer.c @@ -1415,6 +1415,51 @@ void OPK_Unpack_GetDeviceID_Response(ODK_Message* msg, OEMCryptoResult* result, } } +ODK_Message OPK_Pack_WrapClearPrivateKey_Request( + const uint8_t* clear_private_key_bytes, size_t clear_private_key_length, + const uint8_t* wrapped_private_key, + const size_t* wrapped_private_key_length) { + uint32_t api_value = 154; /* from _oecc154 */ + ODK_Message msg = TOS_Transport_GetRequest(); + OPK_Pack_uint32_t(&msg, &api_value); + uint64_t timestamp = time(0); + OPK_Pack_uint64_t(&msg, ×tamp); + OPK_PackNullable_size_t(&msg, wrapped_private_key_length); + OPK_PackNullable_uint8_t(&msg, clear_private_key_bytes); + OPK_Pack_size_t(&msg, &clear_private_key_length); + OPK_PackAlloc(&msg, wrapped_private_key); + OPK_PackEOM(&msg); + OPK_SharedBuffer_FinalizePacking(); + return msg; +} + +void OPK_Unpack_WrapClearPrivateKey_Response( + ODK_Message* msg, OEMCryptoResult* result, uint8_t** wrapped_private_key, + size_t** wrapped_private_key_length) { + uint32_t api_value = UINT32_MAX; + OPK_Unpack_uint32_t(msg, &api_value); + if (api_value != 154) + ODK_MESSAGE_SETSTATUS(msg, MESSAGE_STATUS_API_VALUE_ERROR); + OPK_UnpackNullable_size_t(msg, wrapped_private_key_length); + OPK_Unpack_uint32_t(msg, result); + if (!Is_Valid_OEMCryptoResult(*result)) { + ODK_MESSAGE_SETSTATUS(msg, MESSAGE_STATUS_INVALID_ENUM_VALUE); + } + if (SuccessResult(*result)) { + uint8_t* p; + OPK_UnpackInPlace(msg, &p, OPK_FromSizeTPtrPtr(wrapped_private_key_length)); + if (p && *wrapped_private_key) { + memcpy(*wrapped_private_key, p, + OPK_SafeDerefSizeTPtrPtr(wrapped_private_key_length)); + } + } + OPK_UnpackEOM(msg); + + if (SuccessResult(*result)) { + OPK_SharedBuffer_FinalizeUnpacking(); + } +} + ODK_Message OPK_Pack_GetKeyData_Request(const uint8_t* key_data, const size_t* key_data_length) { uint32_t api_value = 4; /* from _oecc4 */ diff --git a/oemcrypto/opk/serialization/ree/GEN_ree_serializer.h b/oemcrypto/opk/serialization/ree/GEN_ree_serializer.h index d763086..1e66a34 100644 --- a/oemcrypto/opk/serialization/ree/GEN_ree_serializer.h +++ b/oemcrypto/opk/serialization/ree/GEN_ree_serializer.h @@ -203,6 +203,13 @@ ODK_Message OPK_Pack_GetDeviceID_Request(const uint8_t* device_id, void OPK_Unpack_GetDeviceID_Response(ODK_Message* msg, OEMCryptoResult* result, uint8_t** device_id, size_t** device_id_length); +ODK_Message OPK_Pack_WrapClearPrivateKey_Request( + const uint8_t* clear_private_key_bytes, size_t clear_private_key_length, + const uint8_t* wrapped_private_key, + const size_t* wrapped_private_key_length); +void OPK_Unpack_WrapClearPrivateKey_Response( + ODK_Message* msg, OEMCryptoResult* result, uint8_t** wrapped_private_key, + size_t** wrapped_private_key_length); ODK_Message OPK_Pack_GetKeyData_Request(const uint8_t* key_data, const size_t* key_data_length); void OPK_Unpack_GetKeyData_Response(ODK_Message* msg, OEMCryptoResult* result, diff --git a/oemcrypto/opk/serialization/tee/GEN_dispatcher.c b/oemcrypto/opk/serialization/tee/GEN_dispatcher.c index 14b58e7..d12658f 100644 --- a/oemcrypto/opk/serialization/tee/GEN_dispatcher.c +++ b/oemcrypto/opk/serialization/tee/GEN_dispatcher.c @@ -81,16 +81,6 @@ void OPK_Init_OEMCrypto_KeyObject(OEMCrypto_KeyObject* obj) { OPK_Init_OEMCrypto_Substring((OEMCrypto_Substring*)&obj->key_control); } -void OPK_Init_OEMCrypto_InputOutputPair(OEMCrypto_InputOutputPair* obj) { - OPK_Init_size_t((size_t*)&obj->input_data_length); - OEMCrypto_SharedMemory* input_data = NULL; - input_data = (OEMCrypto_SharedMemory*)OPK_VarAlloc(obj->input_data_length); - OPK_InitMemory((uint8_t*)input_data, obj->input_data_length); - OPK_Init_OEMCrypto_DestBufferDesc( - (OEMCrypto_DestBufferDesc*)&obj->output_descriptor); - memcpy(&obj->input_data, &input_data, sizeof(input_data)); -} - void OPK_Init_OEMCrypto_SubSampleDescription( OEMCrypto_SubSampleDescription* obj) { OPK_Init_size_t((size_t*)&obj->num_bytes_clear); @@ -898,6 +888,32 @@ ODK_MessageStatus OPK_DispatchMessage(ODK_Message* request, OPK_Pack_GetDeviceID_Response(result, device_id, device_id_length); break; } + case 154: /* OEMCrypto_WrapClearPrivateKey */ + { + size_t* wrapped_private_key_length = + (size_t*)OPK_VarAlloc(sizeof(size_t)); + OPK_Init_size_t(wrapped_private_key_length); + uint8_t* clear_private_key_bytes = + (uint8_t*)OPK_VarAlloc(sizeof(uint8_t)); + OPK_Init_uint8_t((uint8_t*)clear_private_key_bytes); + size_t clear_private_key_length; + OPK_Init_size_t((size_t*)&clear_private_key_length); + uint8_t* wrapped_private_key; + OPK_InitPointer((uint8_t**)&wrapped_private_key); + OPK_Unpack_WrapClearPrivateKey_Request( + request, &clear_private_key_bytes, &clear_private_key_length, + &wrapped_private_key, &wrapped_private_key_length); + if (!ODK_Message_IsValid(request)) goto handle_invalid_request; + OEMCryptoResult result; + OPK_Init_uint32_t((uint32_t*)&result); + LOGD("WrapClearPrivateKey"); + result = OEMCrypto_WrapClearPrivateKey( + clear_private_key_bytes, clear_private_key_length, + wrapped_private_key, wrapped_private_key_length); + *response = OPK_Pack_WrapClearPrivateKey_Response( + result, wrapped_private_key, wrapped_private_key_length); + break; + } case 4: /* OEMCrypto_GetKeyData */ { size_t* key_data_length = (size_t*)OPK_VarAlloc(sizeof(size_t)); diff --git a/oemcrypto/opk/serialization/tee/GEN_tee_serializer.c b/oemcrypto/opk/serialization/tee/GEN_tee_serializer.c index 82eba68..16962e8 100644 --- a/oemcrypto/opk/serialization/tee/GEN_tee_serializer.c +++ b/oemcrypto/opk/serialization/tee/GEN_tee_serializer.c @@ -1189,6 +1189,42 @@ ODK_Message OPK_Pack_GetDeviceID_Response(OEMCryptoResult result, return msg; } +void OPK_Unpack_WrapClearPrivateKey_Request( + ODK_Message* msg, uint8_t** clear_private_key_bytes, + size_t* clear_private_key_length, uint8_t** wrapped_private_key, + size_t** wrapped_private_key_length) { + uint32_t api_value = UINT32_MAX; + OPK_Unpack_uint32_t(msg, &api_value); + if (api_value != 154) + ODK_MESSAGE_SETSTATUS(msg, MESSAGE_STATUS_API_VALUE_ERROR); + uint64_t timestamp; + OPK_Unpack_uint64_t(msg, ×tamp); + OPK_UnpackNullable_size_t(msg, wrapped_private_key_length); + OPK_UnpackNullable_uint8_t(msg, clear_private_key_bytes); + OPK_Unpack_size_t(msg, clear_private_key_length); + *wrapped_private_key = (uint8_t*)OPK_UnpackAllocBuffer( + msg, OPK_FromSizeTPtrPtr(wrapped_private_key_length), sizeof(uint8_t)); + OPK_UnpackEOM(msg); + OPK_SharedBuffer_FinalizeUnpacking(); +} + +ODK_Message OPK_Pack_WrapClearPrivateKey_Response( + OEMCryptoResult result, const uint8_t* wrapped_private_key, + const size_t* wrapped_private_key_length) { + uint32_t api_value = 154; /* from _oecc154 */ + ODK_Message msg = TOS_Transport_GetResponse(); + OPK_Pack_uint32_t(&msg, &api_value); + OPK_PackNullable_size_t(&msg, wrapped_private_key_length); + OPK_Pack_uint32_t(&msg, &result); + if (SuccessResult(result)) { + OPK_PackMemory(&msg, (const uint8_t*)wrapped_private_key, + OPK_FromSizeTPtr(wrapped_private_key_length)); + } + OPK_PackEOM(&msg); + OPK_SharedBuffer_FinalizePacking(); + return msg; +} + void OPK_Unpack_GetKeyData_Request(ODK_Message* msg, uint8_t** key_data, size_t** key_data_length) { uint32_t api_value = UINT32_MAX; diff --git a/oemcrypto/opk/serialization/tee/GEN_tee_serializer.h b/oemcrypto/opk/serialization/tee/GEN_tee_serializer.h index 2cdd351..a376ddd 100644 --- a/oemcrypto/opk/serialization/tee/GEN_tee_serializer.h +++ b/oemcrypto/opk/serialization/tee/GEN_tee_serializer.h @@ -206,6 +206,13 @@ void OPK_Unpack_GetDeviceID_Request(ODK_Message* msg, uint8_t** device_id, ODK_Message OPK_Pack_GetDeviceID_Response(OEMCryptoResult result, const uint8_t* device_id, const size_t* device_id_length); +void OPK_Unpack_WrapClearPrivateKey_Request( + ODK_Message* msg, uint8_t** clear_private_key_bytes, + size_t* clear_private_key_length, uint8_t** wrapped_private_key, + size_t** wrapped_private_key_length); +ODK_Message OPK_Pack_WrapClearPrivateKey_Response( + OEMCryptoResult result, const uint8_t* wrapped_private_key, + const size_t* wrapped_private_key_length); void OPK_Unpack_GetKeyData_Request(ODK_Message* msg, uint8_t** key_data, size_t** key_data_length); ODK_Message OPK_Pack_GetKeyData_Response(OEMCryptoResult result, diff --git a/oemcrypto/test/GEN_api_lock_file.c b/oemcrypto/test/GEN_api_lock_file.c index 1822df9..af60124 100644 --- a/oemcrypto/test/GEN_api_lock_file.c +++ b/oemcrypto/test/GEN_api_lock_file.c @@ -425,3 +425,12 @@ OEMCryptoResult _oecc151(uint8_t* public_cert, size_t* public_cert_length); // OEMCrypto_UseSecondaryKey defined in v19.1 OEMCryptoResult _oecc152(OEMCrypto_SESSION session_id, bool dual_key); + +// OEMCrypto_WrapClearPrivateKey defined in v19.2 +OEMCryptoResult _oecc154(const uint8_t* clear_private_key_bytes, + size_t clear_private_key_length, + uint8_t* wrapped_private_key, + size_t* wrapped_private_key_length); + +// OEMCrypto_MarkOfflineSession defined in v19.2 +OEMCryptoResult _oecc153(OEMCrypto_SESSION session); diff --git a/oemcrypto/test/common.mk b/oemcrypto/test/common.mk index 054127c..9c0b351 100644 --- a/oemcrypto/test/common.mk +++ b/oemcrypto/test/common.mk @@ -62,7 +62,7 @@ LOCAL_STATIC_LIBRARIES := \ LOCAL_SHARED_LIBRARIES := \ libbase \ - libcppbor_external \ + libcppbor \ libcrypto \ libdl \ libbinder_ndk \ diff --git a/oemcrypto/test/extract_bcc_tool.cpp b/oemcrypto/test/extract_bcc_tool.cpp new file mode 100644 index 0000000..a469ea6 --- /dev/null +++ b/oemcrypto/test/extract_bcc_tool.cpp @@ -0,0 +1,173 @@ +// Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. +// This tool extracts BCC by calling OEMCrypto APIs and generates a CSR file in +// JSON format, which can be handled by CE CDM wv_upload_tool.py. +#include +#include +#include +#include +#include +#include + +#include "OEMCryptoCENC.h" +#include "string_conversions.h" + +namespace { +// Make and Model for system ID resolution. +const std::string kDeviceMake = "widevine_test"; +const std::string kDeviceModel = "prov4"; + +// Informative fields. +const std::string kDeviceArchitecture = "x86_64"; +const std::string kDeviceName = "prov40 test client"; +const std::string kDeviceProduct = "prov40 test"; +const std::string kDeviceBuildInfo = "prov40 test build"; + +// == Utils == + +std::string StringMapToJson( + const std::map& string_map) { + std::string json = "{"; + for (const auto& value_pair : string_map) { + std::string escaped_value = + std::regex_replace(value_pair.second, std::regex("\""), "\\\""); + json.append("\"" + value_pair.first + "\": " + "\"" + escaped_value + + "\","); + } + json.resize(json.size() - 1); // Remove the last comma. + json.append("}"); + return json; +} + +// == Primary == + +bool GetBccAndBuildInfo(std::vector* bcc, + std::string* oemcrypto_build_info) { + // Step 1: Initialize. + OEMCryptoResult result = OEMCrypto_Initialize(); + if (result != OEMCrypto_SUCCESS) { + std::cerr << "Failed to initialize: result = " << result << std::endl; + return false; + } + + // Step 2: Get BCC. + const OEMCrypto_ProvisioningMethod method = OEMCrypto_GetProvisioningMethod(); + if (method != OEMCrypto_BootCertificateChain) { + std::cerr << "ProvisioningMethod is not BCC type: method = "; + std::cerr << method << std::endl; + OEMCrypto_Terminate(); + return false; + } + + bcc->resize(0); + size_t bcc_size = 0; + std::vector additional_signature; // It should be empty. + size_t additional_signature_size = 0; + result = OEMCrypto_GetBootCertificateChain(bcc->data(), &bcc_size, + additional_signature.data(), + &additional_signature_size); + if (additional_signature_size != 0) { + std::cerr << "The additional_signature_size required by OEMCrypto is " + << additional_signature_size + << ", while it is expected to be zero." << std::endl; + OEMCrypto_Terminate(); + return false; + } + if (result == OEMCrypto_ERROR_SHORT_BUFFER) { + bcc->resize(bcc_size); + additional_signature.resize(additional_signature_size); + result = OEMCrypto_GetBootCertificateChain(bcc->data(), &bcc_size, + additional_signature.data(), + &additional_signature_size); + } + if (result != OEMCrypto_SUCCESS) { + std::cerr << "Failed to get BCC: result = " << result << std::endl; + OEMCrypto_Terminate(); + return false; + } + bcc->resize(bcc_size); + + // Step 3: Get oemcrypto build info. + oemcrypto_build_info->resize(0); + size_t oemcrypto_build_info_size = 0; + result = OEMCrypto_BuildInformation(oemcrypto_build_info->data(), + &oemcrypto_build_info_size); + if (result == OEMCrypto_ERROR_SHORT_BUFFER) { + oemcrypto_build_info->resize(oemcrypto_build_info_size); + result = OEMCrypto_BuildInformation(oemcrypto_build_info->data(), + &oemcrypto_build_info_size); + } + if (result != OEMCrypto_SUCCESS) { + std::cerr << "Failed to get build information: result = " << result + << std::endl; + OEMCrypto_Terminate(); + return false; + } + oemcrypto_build_info->resize(oemcrypto_build_info_size); + + // Step 4: Cleanup. + result = OEMCrypto_Terminate(); + if (result != OEMCrypto_SUCCESS) { + std::cerr << "Failed to terminate: result = " << result << std::endl; + return false; + } + return true; +} + +bool GenerateBccRecord(const std::vector& bcc, + const std::string& oemcrypto_build_info, + std::string* bcc_record) { + std::map record; + record["company"] = kDeviceMake; + record["model"] = kDeviceModel; + + record["architecture"] = kDeviceArchitecture; + record["name"] = kDeviceName; + record["product"] = kDeviceProduct; + record["build_info"] = kDeviceBuildInfo; + record["bcc"] = wvutil::Base64Encode(bcc); + record["oemcrypto_build_info"] = oemcrypto_build_info; + + const std::string record_json = StringMapToJson(record); + bcc_record->assign(record_json.begin(), record_json.end()); + return true; +} + +bool OutputBccRecord(const std::string& path, const std::string& record) { + std::cout << "Writing BCC record to file " << path << std::endl; + std::cout << record << std::endl; + std::ofstream out(path); + if (out) out << record; + if (out.bad()) { + std::cerr << "Failed to write BCC record to file " << path << std::endl; + return false; + } + return true; +} +} // namespace + +int main(int argc, char** argv) { + if (argc != 2) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + const std::string bcc_path = argv[1]; + + std::vector bcc; + std::string oemcrypto_build_info; + if (!GetBccAndBuildInfo(&bcc, &oemcrypto_build_info)) { + std::cerr << "Failed to get BCC or OEMCrypto build info" << std::endl; + return 1; + } + std::string bcc_record; + if (!GenerateBccRecord(bcc, oemcrypto_build_info, &bcc_record)) { + std::cerr << "Failed to generate BCC record" << std::endl; + return 1; + } + if (!OutputBccRecord(bcc_path, bcc_record)) { + std::cerr << "Failed to output BCC record" << std::endl; + return 1; + } + return 0; +} diff --git a/oemcrypto/test/fuzz_tests/corpus/oemcrypto_load_release_fuzz_seed_corpus/a1ec51f4d4967eb63e55376bec82655c2aaad465 b/oemcrypto/test/fuzz_tests/corpus/oemcrypto_load_release_fuzz_seed_corpus/a1ec51f4d4967eb63e55376bec82655c2aaad465 new file mode 100644 index 0000000..207403d Binary files /dev/null and b/oemcrypto/test/fuzz_tests/corpus/oemcrypto_load_release_fuzz_seed_corpus/a1ec51f4d4967eb63e55376bec82655c2aaad465 differ diff --git a/oemcrypto/test/fuzz_tests/corpus/oemcrypto_release_request_fuzz_seed_corpus/c3a45eee8e3336c6bedd071a8b4dce07a7bc088f b/oemcrypto/test/fuzz_tests/corpus/oemcrypto_release_request_fuzz_seed_corpus/c3a45eee8e3336c6bedd071a8b4dce07a7bc088f new file mode 100644 index 0000000..0cf9002 Binary files /dev/null and b/oemcrypto/test/fuzz_tests/corpus/oemcrypto_release_request_fuzz_seed_corpus/c3a45eee8e3336c6bedd071a8b4dce07a7bc088f differ diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.h b/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.h index a322ebc..c5e6c1f 100644 --- a/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.h +++ b/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.h @@ -122,6 +122,8 @@ class OEMCryptoRenewalAPIFuzz { void Initialize() { license_api_fuzz_.Initialize(); } + void LoadLicense() { license_api_fuzz_.LoadLicense(); } + void Terminate() { license_api_fuzz_.Terminate(); } LicenseRoundTrip& license_messages() { diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_structs.h b/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_structs.h index 42d077c..f925b03 100644 --- a/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_structs.h +++ b/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_structs.h @@ -43,6 +43,14 @@ struct OEMCrypto_Renewal_Response_Fuzz { // structure. }; +struct OEMCrypto_Release_Response_Fuzz { + oemcrypto_core_message::ODK_ReleaseRequest core_request; + int64_t seconds_since_license_received; + int64_t seconds_since_first_decrypt; + // license_release_response is of variable length and not included in this + // structure. +}; + struct OEMCrypto_Request_Fuzz { // We would like to fuzz computed signature_length, input core_message_length // that ODK parses and actual message buffer to the request APIs. diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_load_release_fuzz.cc b/oemcrypto/test/fuzz_tests/oemcrypto_load_release_fuzz.cc new file mode 100644 index 0000000..bd1820a --- /dev/null +++ b/oemcrypto/test/fuzz_tests/oemcrypto_load_release_fuzz.cc @@ -0,0 +1,38 @@ +// Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#include "FuzzedDataProvider.h" +#include "oec_session_util.h" +#include "oemcrypto_fuzz_helper.h" +#include "oemcrypto_fuzz_structs.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + wvoec::RedirectStdoutToFile(); + + // Copy input data to OEMCrypto_Release_Response_Fuzz and rest of message + // into encrypted license_release_response. + wvoec::OEMCrypto_Release_Response_Fuzz fuzzed_structure; + if (size < sizeof(fuzzed_structure)) { + return 0; + } + FuzzedDataProvider fuzzed_data(data, size); + fuzzed_data.ConsumeData(&fuzzed_structure, sizeof(fuzzed_structure)); + const std::vector release_response = + fuzzed_data.ConsumeRemainingBytes(); + + wvoec::OEMCryptoLicenseAPIFuzz license_api_fuzz; + license_api_fuzz.Initialize(); + license_api_fuzz.LoadLicense(); + + // Call release response API using fuzzed data. + wvoec::ReleaseRoundTrip release_messages( + &license_api_fuzz.license_messages()); + release_messages.SignAndVerifyRequest(); + release_messages.InjectFuzzedResponseData( + fuzzed_structure, release_response.data(), release_response.size()); + release_messages.LoadResponse(); + + license_api_fuzz.Terminate(); + return 0; +} diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_load_renewal_fuzz.cc b/oemcrypto/test/fuzz_tests/oemcrypto_load_renewal_fuzz.cc index 8957ee0..1403376 100644 --- a/oemcrypto/test/fuzz_tests/oemcrypto_load_renewal_fuzz.cc +++ b/oemcrypto/test/fuzz_tests/oemcrypto_load_renewal_fuzz.cc @@ -37,6 +37,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { renewal_response_fuzz.renewal_messages().InjectFuzzedResponseData( fuzzed_structure, renewal_response.data(), renewal_response.size()); renewal_response_fuzz.renewal_messages().LoadResponse(); + renewal_response_fuzz.Terminate(); return 0; } diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_opk_fuzztests.gyp b/oemcrypto/test/fuzz_tests/oemcrypto_opk_fuzztests.gyp index fe60be9..be0ab62 100644 --- a/oemcrypto/test/fuzz_tests/oemcrypto_opk_fuzztests.gyp +++ b/oemcrypto/test/fuzz_tests/oemcrypto_opk_fuzztests.gyp @@ -171,6 +171,12 @@ 'oemcrypto_load_provisioning_fuzz.cc', ], }, + { + 'target_name': 'oemcrypto_opk_load_release_fuzz', + 'sources': [ + 'oemcrypto_load_release_fuzz.cc', + ], + }, { 'target_name': 'oemcrypto_opk_load_renewal_fuzz', 'sources': [ @@ -207,6 +213,12 @@ 'oemcrypto_query_key_control_fuzz.cc', ], }, + { + 'target_name': 'oemcrypto_opk_release_request_fuzz', + 'sources': [ + 'oemcrypto_release_request_fuzz.cc', + ], + }, { 'target_name': 'oemcrypto_opk_renewal_request_fuzz', 'sources': [ diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_release_request_fuzz.cc b/oemcrypto/test/fuzz_tests/oemcrypto_release_request_fuzz.cc new file mode 100644 index 0000000..11df453 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/oemcrypto_release_request_fuzz.cc @@ -0,0 +1,32 @@ +// Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#include + +#include "oec_session_util.h" +#include "oemcrypto_fuzz_helper.h" +#include "oemcrypto_fuzz_structs.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + wvoec::RedirectStdoutToFile(); + + // If input size is less than fuzz data structure, reject the input. + if (size < sizeof(wvoec::OEMCrypto_Request_Fuzz)) { + return 0; + } + wvoec::LicenseWithUsageEntryFuzz entry; + entry.Initialize(); + entry.CreateUsageTableHeader(); + entry.InstallTestDrmKey(); + entry.session().CreateNewUsageEntry(); + entry.session().GenerateNonce(); + std::vector encrypted_usage_header; + entry.session().UpdateUsageEntry(&encrypted_usage_header); + entry.LoadLicense(); + wvoec::ReleaseRoundTrip release_messages(&entry.license_messages()); + std::vector input(data, data + size); + release_messages.InjectFuzzedRequestData(input.data(), input.size()); + entry.Terminate(); + return 0; +} diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_renewal_request_fuzz.cc b/oemcrypto/test/fuzz_tests/oemcrypto_renewal_request_fuzz.cc index 658bdc1..03cf860 100644 --- a/oemcrypto/test/fuzz_tests/oemcrypto_renewal_request_fuzz.cc +++ b/oemcrypto/test/fuzz_tests/oemcrypto_renewal_request_fuzz.cc @@ -20,6 +20,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { std::vector input(data, data + size); wvoec::OEMCryptoRenewalAPIFuzz renewal_api_fuzz; renewal_api_fuzz.Initialize(); + renewal_api_fuzz.LoadLicense(); renewal_api_fuzz.renewal_messages().InjectFuzzedRequestData(input.data(), input.size()); renewal_api_fuzz.Terminate(); diff --git a/oemcrypto/test/fuzz_tests/partner_oemcrypto_fuzztests.gyp b/oemcrypto/test/fuzz_tests/partner_oemcrypto_fuzztests.gyp index 4ebf783..29826b4 100644 --- a/oemcrypto/test/fuzz_tests/partner_oemcrypto_fuzztests.gyp +++ b/oemcrypto/test/fuzz_tests/partner_oemcrypto_fuzztests.gyp @@ -148,6 +148,12 @@ 'oemcrypto_load_provisioning_fuzz.cc', ], }, + { + 'target_name': 'oemcrypto_load_release_fuzz', + 'sources': [ + 'oemcrypto_load_release_fuzz.cc', + ], + }, { 'target_name': 'oemcrypto_load_renewal_fuzz', 'sources': [ @@ -184,6 +190,12 @@ 'oemcrypto_query_key_control_fuzz.cc', ], }, + { + 'target_name': 'oemcrypto_release_request_fuzz', + 'sources': [ + 'oemcrypto_release_request_fuzz.cc', + ], + }, { 'target_name': 'oemcrypto_renewal_request_fuzz', 'sources': [ diff --git a/oemcrypto/test/install_prov30_oem_cert_tool.cpp b/oemcrypto/test/install_prov30_oem_cert_tool.cpp new file mode 100644 index 0000000..25eac6d --- /dev/null +++ b/oemcrypto/test/install_prov30_oem_cert_tool.cpp @@ -0,0 +1,154 @@ +// Copyright 2024 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#include +#include +#include + +#include "OEMCryptoCENC.h" +#include "oec_test_data.h" +#include "platform.h" +#include "string_conversions.h" + +namespace { + +constexpr size_t kAesBlockSize = 16; + +/* +This function concatenates the test Prov30 OEM certificate chain and key to the +format below: + ++-----------------------+----------------------+--------------------------+ +| Cert Chain Length | Certificate Chain | Key Length | ++-----------------------+----------------------+--------------------------+ +| (4 bytes, big-endian) | (DER-encoded PKCS#7) | (4 bytes, big-endian) | ++-----------------------+----------------------+--------------------------+ +| Private Key | ++-----------------------+ + +|oem_private_key| should be a RSA key in PKCS#8 PrivateKeyInfo format. +|oem_public_cert| should be a DER-encoded PKCS#7 certificate chain. + +The output will be consumed by OEMCrypto Prov30 factory functions: +1. It is wrapped by OEMCrypto_WrapKeyboxOrOEMCert(), and +2. The wrapped root of trust will be installed by +OEMCrypto_InstallKeyboxOrOEMCert(). Therefore, the OEMCrypto implementation of +the factory functions and the tool must have an agreement on the format above. +*/ +std::vector PrepareProv30OEMCertAndKey( + const uint8_t* oem_public_cert, const size_t oem_public_cert_size, + const uint8_t* oem_private_key, const size_t oem_private_key_size) { + std::vector oem_cert_and_key; + // Calculate total size + size_t total_size = sizeof(uint32_t) + oem_public_cert_size + + sizeof(uint32_t) + oem_private_key_size; + oem_cert_and_key.resize(total_size); + + // Offset to track where to write in the output vector + size_t offset = 0; + // 1. Store public cert size (big-endian) + uint32_t networkOrderCertSize = htonl((uint32_t)oem_public_cert_size); + std::copy(reinterpret_cast(&networkOrderCertSize), + reinterpret_cast(&networkOrderCertSize) + + sizeof(uint32_t), + oem_cert_and_key.begin()); + offset += sizeof(uint32_t); + + // 2. Store public cert content + std::copy(oem_public_cert, oem_public_cert + oem_public_cert_size, + oem_cert_and_key.begin() + offset); + offset += oem_public_cert_size; + + // 3. Store private key size (big-endian) + uint32_t networkOrderKeySize = htonl((uint32_t)oem_private_key_size); + std::copy( + reinterpret_cast(&networkOrderKeySize), + reinterpret_cast(&networkOrderKeySize) + sizeof(uint32_t), + oem_cert_and_key.begin() + offset); + offset += sizeof(uint32_t); + + // 4. Store private key content + std::copy(oem_private_key, oem_private_key + oem_private_key_size, + oem_cert_and_key.begin() + offset); + return oem_cert_and_key; +} + +OEMCryptoResult InstallTestProv30RootOfTrust( + const uint8_t* oem_public_cert, const size_t oem_public_cert_size, + const uint8_t* oem_private_key, const size_t oem_private_key_size) { + if (oem_public_cert == nullptr || oem_private_key == nullptr || + oem_public_cert_size == 0 || oem_private_key_size == 0) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + // 1. Prepare OEM cert and key. + std::vector oem_cert_and_key = + PrepareProv30OEMCertAndKey(oem_public_cert, oem_public_cert_size, + oem_private_key, oem_private_key_size); + if (oem_cert_and_key.empty()) { + std::cerr << "Failed to prepare OEM cert and key" << std::endl; + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + // Add padding. + const uint8_t padding = + kAesBlockSize - (oem_cert_and_key.size() % kAesBlockSize); + for (size_t i = 0; i < padding; i++) { + oem_cert_and_key.push_back(padding); + } + + // 2: Initialize OEMCrypto. + OEMCryptoResult sts = OEMCrypto_Initialize(); + if (sts != OEMCrypto_SUCCESS) { + std::cerr << "Failed to initialize: result = " << sts << std::endl; + return sts; + } + + // 3: Wrap OEM cert and key before calling install function. + const OEMCrypto_ProvisioningMethod method = OEMCrypto_GetProvisioningMethod(); + if (method != OEMCrypto_OEMCertificate) { + std::cerr << "OEMCrypto is not OEMCrypto_OEMCertificate: method = "; + std::cerr << method << std::endl; + OEMCrypto_Terminate(); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + std::vector wrapped_oem_cert_and_key; + size_t wrapped_oem_cert_and_key_size = 0; + sts = OEMCrypto_WrapKeyboxOrOEMCert( + oem_cert_and_key.data(), oem_cert_and_key.size(), + wrapped_oem_cert_and_key.data(), &wrapped_oem_cert_and_key_size, nullptr, + 0); + if (sts != OEMCrypto_ERROR_SHORT_BUFFER) { + OEMCrypto_Terminate(); + return sts; + } + wrapped_oem_cert_and_key.resize(wrapped_oem_cert_and_key_size); + sts = OEMCrypto_WrapKeyboxOrOEMCert( + oem_cert_and_key.data(), oem_cert_and_key.size(), + wrapped_oem_cert_and_key.data(), &wrapped_oem_cert_and_key_size, nullptr, + 0); + if (sts != OEMCrypto_SUCCESS) { + OEMCrypto_Terminate(); + return sts; + } + + // 4: Install the wrapped OEM cert and key. + sts = OEMCrypto_InstallKeyboxOrOEMCert(wrapped_oem_cert_and_key.data(), + wrapped_oem_cert_and_key_size); + OEMCrypto_Terminate(); + return sts; +} +} // namespace + +int main() { + const OEMCryptoResult result = InstallTestProv30RootOfTrust( + wvoec::kTestOEMPublicCertInfo2, sizeof(wvoec::kTestOEMPublicCertInfo2), + wvoec::kTestRSAPKCS8PrivateKeyInfo2_2048, + sizeof(wvoec::kTestRSAPKCS8PrivateKeyInfo2_2048)); + if (result != OEMCrypto_SUCCESS) { + std::cerr << "Failed to install OEM cert and key with result: " << result + << std::endl; + return 1; + } + return 0; +} diff --git a/oemcrypto/test/oec_device_features.cpp b/oemcrypto/test/oec_device_features.cpp index 0bd759f..b70b3c6 100644 --- a/oemcrypto/test/oec_device_features.cpp +++ b/oemcrypto/test/oec_device_features.cpp @@ -65,7 +65,8 @@ void DeviceFeatures::Initialize() { // baked in certificate. loads_certificate = provisioning_method == OEMCrypto_Keybox || provisioning_method == OEMCrypto_OEMCertificate || - provisioning_method == OEMCrypto_BootCertificateChain; + provisioning_method == OEMCrypto_BootCertificateChain || + provisioning_method == OEMCrypto_DrmReprovisioning; printf("loads_certificate = %s.\n", loads_certificate ? "true" : "false"); generic_crypto = (OEMCrypto_ERROR_NOT_IMPLEMENTED != diff --git a/oemcrypto/test/oec_session_util.cpp b/oemcrypto/test/oec_session_util.cpp index 519ae37..c23bd7d 100644 --- a/oemcrypto/test/oec_session_util.cpp +++ b/oemcrypto/test/oec_session_util.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -359,6 +360,11 @@ void ProvisioningRoundTrip::PrepareSession( session_->LoadOEMCert(true); session_->GenerateRsaSessionKey(); encryptor_.set_enc_key(session_->session_key()); + } else if (global_features.provisioning_method == + OEMCrypto_DrmReprovisioning) { + session_->SetTestRsaPublicKey(); + session_->GenerateRsaSessionKey(); + encryptor_.set_enc_key(session_->session_key()); } else { EXPECT_EQ(global_features.provisioning_method, OEMCrypto_OEMCertificate); session_->LoadOEMCert(true); @@ -371,7 +377,16 @@ void ProvisioningRoundTrip::VerifyRequestSignature( const vector& data, const vector& generated_signature, size_t core_message_length) { if (keybox_ == nullptr) { - session()->VerifyRsaSignature(data, generated_signature.data(), + std::vector signature_source; + if (global_features.provisioning_method == OEMCrypto_DrmReprovisioning) { + // DRM Reprovisioning uses protocol 2.2 which computes signatures for the + // sha512 hash of the message and not the full message. + signature_source.resize(SHA512_DIGEST_LENGTH); + SHA512(data.data(), data.size(), signature_source.data()); + } else { + signature_source = data; + } + session()->VerifyRsaSignature(signature_source, generated_signature.data(), generated_signature.size(), kSign_RSASSA_PSS); } else { // Setup the derived keys using the proto message (ignoring the core @@ -1575,7 +1590,7 @@ void RenewalRoundTrip::EncryptAndSignResponse() { } void RenewalRoundTrip::InjectFuzzedResponseData( - OEMCrypto_Renewal_Response_Fuzz& fuzzed_data, + const OEMCrypto_Renewal_Response_Fuzz& fuzzed_data, const uint8_t* renewal_response, const size_t renewal_response_size) { // TODO(b/191724203): Test renewal server has different version from license // server. @@ -1692,8 +1707,53 @@ void ReleaseRoundTrip::EncryptAndSignResponse() { SetEncryptAndSignResponseLengths(); } +void ReleaseRoundTrip::InjectFuzzedResponseData( + const OEMCrypto_Release_Response_Fuzz& fuzzed_data, + const uint8_t* release_response, const size_t release_response_size) { + ASSERT_NE(license_messages_, nullptr); + CoreMessageFeatures features = + CoreMessageFeatures::DefaultFeatures(license_messages_->api_version()); + // Serializing core message. + // This call also sets nonce in core response to match with session nonce. + oemcrypto_core_message::serialize::CreateCoreReleaseResponse( + features, fuzzed_data.core_request, + fuzzed_data.seconds_since_license_received, + fuzzed_data.seconds_since_first_decrypt, &serialized_core_message_); + + // Copy serialized core message and encrypted response from data and + // calculate signature. Now we will have a valid signature for data + // generated by fuzzer. + encrypted_response_.assign(serialized_core_message_.begin(), + serialized_core_message_.end()); + encrypted_response_.insert(encrypted_response_.end(), release_response, + release_response + release_response_size); + session()->key_deriver().ServerSignBuffer(encrypted_response_.data(), + encrypted_response_.size(), + &response_signature_); +} + OEMCryptoResult ReleaseRoundTrip::LoadResponse(Session* session) { - // TODO(vickymin): Write corpus for oemcrypto_load_release_fuzz. + // Write corpus for oemcrypto_load_renewal_fuzz. Fuzz script expects + // encrypted response from Renewal server as input corpus data. + // Data will be signed again explicitly by fuzzer script after mutations. + if (ShouldGenerateCorpus()) { + const std::string file_name = + GetFileName("oemcrypto_load_release_fuzz_seed_corpus"); + // Corpus for release response fuzzer should be in the format: + // OEMCrypto_Release_Response_Fuzz + license_release_response. + OEMCrypto_Release_Response_Fuzz release_response_fuzz; + release_response_fuzz.core_request = core_request_; + release_response_fuzz.seconds_since_license_received = + seconds_since_license_received_; + release_response_fuzz.seconds_since_first_decrypt = + seconds_since_first_decrypt_; + AppendToFile(file_name, + reinterpret_cast(&release_response_fuzz), + sizeof(release_response_fuzz)); + AppendToFile(file_name, + reinterpret_cast(&encrypted_response_data_), + sizeof(encrypted_response_data_)); + } VerifyEncryptAndSignResponseLengths(); return OEMCrypto_LoadRelease( session->session_id(), encrypted_response_.data(), @@ -2309,6 +2369,9 @@ void WriteRequestApiCorpus(size_t signature_length, size_t core_message_length, } else if (std::is_same::value) { file_name = GetFileName("oemcrypto_renewal_request_fuzz_seed_corpus"); + } else if (std::is_same::value) { + file_name = GetFileName("oemcrypto_release_request_fuzz_seed_corpus"); } else { LOGE("Invalid CoreRequest type while writing request api corups."); } diff --git a/oemcrypto/test/oec_session_util.h b/oemcrypto/test/oec_session_util.h index 75204d5..4141942 100644 --- a/oemcrypto/test/oec_session_util.h +++ b/oemcrypto/test/oec_session_util.h @@ -583,9 +583,9 @@ class RenewalRoundTrip is_release_(false) {} void CreateDefaultResponse() override; void EncryptAndSignResponse() override; - void InjectFuzzedResponseData(OEMCrypto_Renewal_Response_Fuzz& fuzzed_data, - const uint8_t* renewal_response, - size_t renewal_response_size); + void InjectFuzzedResponseData( + const OEMCrypto_Renewal_Response_Fuzz& fuzzed_data, + const uint8_t* renewal_response, size_t renewal_response_size); OEMCryptoResult LoadResponse() override { return LoadResponse(session_); } OEMCryptoResult LoadResponse(Session* session) override; uint64_t renewal_duration_seconds() const { @@ -623,6 +623,9 @@ class ReleaseRoundTrip license_messages_(license_messages) {} void CreateDefaultResponse() override; void EncryptAndSignResponse() override; + void InjectFuzzedResponseData( + const OEMCrypto_Release_Response_Fuzz& fuzzed_data, + const uint8_t* release_response, size_t release_response_size); OEMCryptoResult LoadResponse() override { return LoadResponse(session_); } OEMCryptoResult LoadResponse(Session* session) override; int64_t seconds_since_license_received() const { diff --git a/oemcrypto/test/oec_test_data.h b/oemcrypto/test/oec_test_data.h index d279217..eec25c2 100644 --- a/oemcrypto/test/oec_test_data.h +++ b/oemcrypto/test/oec_test_data.h @@ -8,6 +8,7 @@ #define CDM_OEC_TEST_DATA_H_ #include +#include #include "OEMCryptoCENC.h" #include "oemcrypto_types.h" @@ -193,6 +194,206 @@ static const uint8_t kTestRSAPKCS8PrivateKeyInfo2_2048[] = { 0x72, 0x2c, 0xf7, 0xc1, 0x22, 0x36, 0xd9, 0x18, 0x56, 0xfe, 0x39, 0x28, 0x33, 0xe0, 0xdb, 0x03}; +// Counterpart of kTestRSAPKCS8PrivateKeyInfo2_2048[] +static const uint8_t kTestOEMPublicCertInfo2[] = { + 0x30, 0x82, 0x09, 0x2d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x07, 0x02, 0xa0, 0x82, 0x09, 0x1e, 0x30, 0x82, 0x09, 0x1a, 0x02, + 0x01, 0x01, 0x31, 0x00, 0x30, 0x0f, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x02, 0x04, 0x00, 0xa0, 0x82, 0x08, + 0xfe, 0x30, 0x82, 0x03, 0x71, 0x30, 0x82, 0x02, 0x59, 0xa0, 0x03, 0x02, + 0x01, 0x02, 0x02, 0x11, 0x00, 0xc2, 0x8d, 0x20, 0x22, 0x82, 0x8b, 0x9e, + 0x63, 0x9d, 0x15, 0x89, 0x2c, 0xa9, 0x8f, 0xd9, 0x5d, 0x30, 0x0d, 0x06, + 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, + 0x30, 0x6b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, + 0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, + 0x0c, 0x02, 0x57, 0x41, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, + 0x07, 0x0c, 0x08, 0x4b, 0x69, 0x72, 0x6b, 0x6c, 0x61, 0x6e, 0x64, 0x31, + 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, + 0x0b, 0x0c, 0x08, 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x31, + 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, 0x73, 0x79, + 0x73, 0x74, 0x65, 0x6d, 0x20, 0x69, 0x64, 0x3a, 0x20, 0x37, 0x39, 0x31, + 0x33, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x31, 0x31, 0x31, 0x31, + 0x33, 0x32, 0x36, 0x32, 0x32, 0x5a, 0x17, 0x0d, 0x33, 0x38, 0x30, 0x31, + 0x30, 0x36, 0x31, 0x33, 0x32, 0x36, 0x32, 0x32, 0x5a, 0x30, 0x65, 0x31, + 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x09, 0x37, 0x39, + 0x31, 0x33, 0x2d, 0x6c, 0x65, 0x61, 0x66, 0x31, 0x0b, 0x30, 0x09, 0x06, + 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, + 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x02, 0x57, 0x41, 0x31, 0x11, 0x30, + 0x0f, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x08, 0x4b, 0x69, 0x72, 0x6b, + 0x6c, 0x61, 0x6e, 0x64, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, + 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x31, 0x11, 0x30, + 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x08, 0x57, 0x69, 0x64, 0x65, + 0x76, 0x69, 0x6e, 0x65, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, + 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, + 0x00, 0xa7, 0x00, 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, 0x5a, 0x2a, 0x40, + 0xb4, 0xe1, 0x15, 0x94, 0x58, 0x11, 0x4f, 0x94, 0x58, 0xdd, 0xde, 0xa7, + 0x1f, 0x3c, 0x2c, 0xe0, 0x88, 0x09, 0x29, 0x61, 0x57, 0x67, 0x5e, 0x56, + 0x7e, 0xee, 0x27, 0x8f, 0x59, 0x34, 0x9a, 0x2a, 0xaa, 0x9d, 0xb4, 0x4e, + 0xfa, 0xa7, 0x6a, 0xd4, 0xc9, 0x7a, 0x53, 0xc1, 0x4e, 0x9f, 0xe3, 0x34, + 0xf7, 0x3d, 0xb7, 0xc9, 0x10, 0x47, 0x4f, 0x28, 0xda, 0x3f, 0xce, 0x31, + 0x7b, 0xfd, 0x06, 0x10, 0xeb, 0xf7, 0xbe, 0x92, 0xf9, 0xaf, 0xfb, 0x3e, + 0x68, 0xda, 0xee, 0x1a, 0x64, 0x4c, 0xf3, 0x29, 0xf2, 0x73, 0x9e, 0x39, + 0xd8, 0xf6, 0x6f, 0xd8, 0xb2, 0x80, 0x82, 0x71, 0x8e, 0xb5, 0xa4, 0xf2, + 0xc2, 0x3e, 0xcd, 0x0a, 0xca, 0xb6, 0x04, 0xcd, 0x9a, 0x13, 0x8b, 0x54, + 0x73, 0x54, 0x25, 0x54, 0x8c, 0xbe, 0x98, 0x7a, 0x67, 0xad, 0xda, 0xb3, + 0x4e, 0xb3, 0xfa, 0x82, 0xa8, 0x4a, 0x67, 0x98, 0x56, 0x57, 0x54, 0x71, + 0xcd, 0x12, 0x7f, 0xed, 0xa3, 0x01, 0xc0, 0x6a, 0x8b, 0x24, 0x03, 0x96, + 0x88, 0xbe, 0x97, 0x66, 0x2a, 0xbc, 0x53, 0xc9, 0x83, 0x06, 0x51, 0x5a, + 0x88, 0x65, 0x13, 0x18, 0xe4, 0x3a, 0xed, 0x6b, 0xf1, 0x61, 0x5b, 0x4c, + 0xc8, 0x1e, 0xf4, 0xc2, 0xae, 0x08, 0x5e, 0x2d, 0x5f, 0xf8, 0x12, 0x7f, + 0xa2, 0xfc, 0xbb, 0x21, 0x18, 0x30, 0xda, 0xfe, 0x40, 0xfb, 0x01, 0xca, + 0x2e, 0x37, 0x0e, 0xce, 0xdd, 0x76, 0x87, 0x82, 0x46, 0x0b, 0x3a, 0x77, + 0x8f, 0xc0, 0x72, 0x07, 0x2c, 0x7f, 0x9d, 0x1e, 0x86, 0x5b, 0xed, 0x27, + 0x29, 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, 0xd3, 0x5b, 0x3d, 0xdb, 0x9c, + 0x5e, 0x1b, 0x7b, 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, 0xbb, 0xbb, 0x2c, + 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x16, + 0x30, 0x14, 0x30, 0x12, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, + 0x79, 0x04, 0x01, 0x01, 0x04, 0x04, 0x02, 0x02, 0x1e, 0xe9, 0x30, 0x0d, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, + 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x88, 0x95, 0xec, 0xcd, 0x8b, 0xa7, + 0x51, 0xda, 0x74, 0x81, 0xa5, 0x39, 0x62, 0x1a, 0x0e, 0x2e, 0xde, 0x3c, + 0x37, 0xea, 0xad, 0x7c, 0xee, 0x9b, 0x26, 0x8e, 0xe2, 0xd6, 0x34, 0xcd, + 0xb7, 0x70, 0xba, 0xbf, 0xa0, 0xa3, 0xfe, 0xb3, 0x4b, 0xbc, 0xf4, 0x1c, + 0x72, 0x66, 0x81, 0xd5, 0x09, 0x33, 0x78, 0x0c, 0x61, 0x21, 0xa8, 0xf1, + 0xe2, 0xc9, 0xe2, 0x83, 0xc2, 0x19, 0x02, 0xf2, 0xe8, 0xab, 0x17, 0x36, + 0x3a, 0x0b, 0x20, 0xaf, 0x0f, 0xae, 0x2e, 0x73, 0x68, 0xac, 0x15, 0xee, + 0x9c, 0xc0, 0x92, 0x03, 0x7e, 0x95, 0x63, 0xaa, 0xad, 0x15, 0x96, 0x43, + 0x20, 0x3b, 0xe5, 0x9b, 0x1f, 0xca, 0x02, 0xba, 0xf0, 0x07, 0x76, 0x80, + 0xd7, 0xa3, 0x1a, 0xeb, 0xc8, 0xdb, 0x03, 0x7b, 0x43, 0x56, 0xe5, 0x96, + 0x6b, 0x86, 0xfe, 0x08, 0x58, 0x8a, 0x84, 0xbd, 0xe9, 0x47, 0x18, 0xee, + 0xb2, 0xa8, 0x05, 0x7b, 0xf0, 0xfd, 0xaa, 0xb9, 0x85, 0xcd, 0x7a, 0x0e, + 0x6b, 0x6c, 0x9f, 0xc6, 0x75, 0xd2, 0x2a, 0xfe, 0x5b, 0xf3, 0xb7, 0x31, + 0x6c, 0xac, 0xe3, 0x00, 0x9f, 0xe7, 0xdd, 0xe3, 0x81, 0xc1, 0x36, 0xc3, + 0x1c, 0x5f, 0xdf, 0xf2, 0xc3, 0x5e, 0xfa, 0x55, 0x32, 0xd8, 0x5c, 0xa8, + 0xe5, 0xcc, 0xb6, 0x4a, 0xe9, 0xe2, 0xcc, 0x38, 0x44, 0x07, 0x46, 0x59, + 0x34, 0x84, 0x79, 0xf9, 0xee, 0x3c, 0x4b, 0x48, 0x90, 0xab, 0x73, 0xb0, + 0xa1, 0x92, 0xc3, 0xd6, 0x83, 0x87, 0x81, 0xca, 0x12, 0x81, 0xd6, 0x5d, + 0xf7, 0x6f, 0x7a, 0x35, 0x5e, 0x4f, 0x02, 0x66, 0x8a, 0x47, 0x88, 0x82, + 0xab, 0xf0, 0x12, 0x1d, 0xb9, 0x75, 0x3b, 0x7b, 0xa8, 0x36, 0x15, 0xef, + 0xa8, 0x12, 0x0e, 0x53, 0xb4, 0x83, 0x78, 0x53, 0xc0, 0x52, 0xae, 0xa6, + 0x0a, 0xa0, 0x53, 0xdc, 0x1c, 0x15, 0x22, 0xdd, 0x17, 0x98, 0x30, 0x82, + 0x05, 0x85, 0x30, 0x82, 0x03, 0x6d, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, + 0x10, 0x03, 0xb1, 0xf7, 0x58, 0xdf, 0x1d, 0xe3, 0x25, 0x00, 0x0b, 0x10, + 0x3d, 0xd5, 0xe6, 0xe4, 0x64, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x7e, 0x31, 0x0b, + 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, + 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x57, 0x61, + 0x73, 0x68, 0x69, 0x6e, 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x11, 0x30, 0x0f, + 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x08, 0x4b, 0x69, 0x72, 0x6b, 0x6c, + 0x61, 0x6e, 0x64, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, + 0x0c, 0x06, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x31, 0x11, 0x30, 0x0f, + 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x08, 0x57, 0x69, 0x64, 0x65, 0x76, + 0x69, 0x6e, 0x65, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, + 0x0c, 0x1a, 0x77, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x6f, 0x65, 0x6d, 0x2d, 0x72, 0x6f, 0x6f, 0x74, 0x2d, + 0x70, 0x72, 0x6f, 0x64, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x37, 0x31, 0x31, + 0x31, 0x38, 0x30, 0x31, 0x31, 0x33, 0x33, 0x35, 0x5a, 0x17, 0x0d, 0x32, + 0x37, 0x31, 0x31, 0x31, 0x38, 0x30, 0x31, 0x31, 0x33, 0x31, 0x33, 0x5a, + 0x30, 0x6b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, + 0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, + 0x0c, 0x02, 0x57, 0x41, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, + 0x07, 0x0c, 0x08, 0x4b, 0x69, 0x72, 0x6b, 0x6c, 0x61, 0x6e, 0x64, 0x31, + 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, + 0x0b, 0x0c, 0x08, 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x31, + 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, 0x73, 0x79, + 0x73, 0x74, 0x65, 0x6d, 0x20, 0x69, 0x64, 0x3a, 0x20, 0x37, 0x39, 0x31, + 0x33, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, + 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xae, 0xc8, + 0x71, 0xae, 0x08, 0x0c, 0x06, 0x06, 0x2d, 0x81, 0x7c, 0xa9, 0x8b, 0xb3, + 0xd6, 0x66, 0xe4, 0xf6, 0x08, 0x5e, 0x5a, 0x75, 0xe8, 0x74, 0x61, 0x7a, + 0x88, 0xca, 0x85, 0x14, 0x0d, 0x58, 0xa4, 0x09, 0x19, 0x6c, 0x60, 0xc9, + 0xad, 0x91, 0x1c, 0xbf, 0x04, 0xb3, 0x47, 0x10, 0x63, 0x7f, 0x02, 0x58, + 0xc2, 0x1e, 0xbd, 0xcc, 0x07, 0x77, 0xaa, 0x7e, 0x14, 0xa8, 0xc2, 0x01, + 0xcd, 0xe8, 0x46, 0x60, 0x53, 0x6f, 0x2f, 0xda, 0x17, 0x2d, 0x4d, 0x9d, + 0x0e, 0x5d, 0xb5, 0x50, 0x95, 0xae, 0xab, 0x6e, 0x43, 0xe3, 0xb0, 0x00, + 0x12, 0xb4, 0x05, 0x82, 0x4a, 0x2b, 0x14, 0x63, 0x0d, 0x1f, 0x06, 0x12, + 0xaa, 0xe1, 0x9d, 0xe7, 0xba, 0xda, 0xe3, 0xfc, 0x7c, 0x6c, 0x73, 0xae, + 0x56, 0xf8, 0xab, 0xf7, 0x51, 0x93, 0x31, 0xef, 0x8f, 0xe4, 0xb6, 0x01, + 0x2c, 0xeb, 0x7b, 0xe4, 0xd8, 0xb3, 0xea, 0x70, 0x37, 0x89, 0x05, 0xa9, + 0x51, 0x57, 0x72, 0x98, 0x9e, 0xa8, 0x46, 0xdb, 0xeb, 0x7a, 0x38, 0x2b, + 0x2f, 0xc0, 0x27, 0xb7, 0xc2, 0xe1, 0x9a, 0x17, 0xdf, 0xf5, 0xd6, 0x9c, + 0xd5, 0x8c, 0xb8, 0x66, 0x42, 0xd5, 0x04, 0x1e, 0x7c, 0x36, 0x4c, 0x1e, + 0x3e, 0x45, 0x51, 0x4d, 0x41, 0x72, 0x22, 0x53, 0x3d, 0xf4, 0x57, 0x7c, + 0x6c, 0x33, 0x34, 0x24, 0x45, 0xdf, 0x84, 0x87, 0x4a, 0xa6, 0xcb, 0x7c, + 0x03, 0xa3, 0xaa, 0x8e, 0x2d, 0x82, 0x01, 0x27, 0x87, 0x74, 0x82, 0x1a, + 0xbc, 0x0f, 0x76, 0x69, 0xab, 0xe0, 0x4e, 0x70, 0xbe, 0x37, 0xfc, 0xc8, + 0x2c, 0x91, 0x17, 0x4f, 0xd5, 0x26, 0x3b, 0x7b, 0x90, 0xb5, 0x2d, 0x64, + 0xba, 0xf7, 0xd2, 0x8a, 0xb4, 0x8f, 0x38, 0x9d, 0x8e, 0xba, 0xe7, 0x5c, + 0x52, 0xf1, 0x0a, 0xb8, 0xc0, 0x1b, 0xb6, 0xb1, 0x70, 0x7e, 0x47, 0x59, + 0x94, 0x59, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x10, 0x30, + 0x82, 0x01, 0x0c, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, + 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, + 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, + 0x02, 0x02, 0x04, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, + 0x04, 0x14, 0x4b, 0xcb, 0xdf, 0xaa, 0x02, 0xde, 0x8d, 0xc3, 0xe7, 0xe5, + 0x85, 0xdb, 0x2e, 0x8a, 0xbe, 0x75, 0x6b, 0x8a, 0x67, 0x58, 0x30, 0x81, + 0xb2, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81, 0xaa, 0x30, 0x81, 0xa7, + 0x80, 0x14, 0x04, 0x94, 0x66, 0xaa, 0xf9, 0x61, 0x89, 0xb6, 0xdb, 0xb5, + 0xf7, 0x13, 0x38, 0x3d, 0x62, 0x84, 0xb8, 0x18, 0x0a, 0x8f, 0xa1, 0x81, + 0x83, 0xa4, 0x81, 0x80, 0x30, 0x7e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, + 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, + 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, + 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, + 0x07, 0x0c, 0x08, 0x4b, 0x69, 0x72, 0x6b, 0x6c, 0x61, 0x6e, 0x64, 0x31, + 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, + 0x0b, 0x0c, 0x08, 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x31, + 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x1a, 0x77, 0x69, + 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, + 0x65, 0x6d, 0x2d, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x70, 0x72, 0x6f, 0x64, + 0x82, 0x09, 0x00, 0xdf, 0x86, 0x05, 0x31, 0x01, 0xbe, 0x9a, 0x9a, 0x30, + 0x12, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x04, 0x01, + 0x01, 0x04, 0x04, 0x02, 0x02, 0x1e, 0xe9, 0x30, 0x0d, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, + 0x02, 0x01, 0x00, 0x61, 0x3f, 0x2f, 0x43, 0xe4, 0xbe, 0x66, 0x34, 0xef, + 0x92, 0x06, 0xe9, 0x88, 0xba, 0x6a, 0x1d, 0x4f, 0x54, 0x5a, 0x97, 0xb1, + 0x75, 0xd7, 0x93, 0xf8, 0x45, 0xc6, 0x83, 0x92, 0x36, 0xfd, 0x55, 0xa9, + 0x21, 0x0b, 0xdc, 0xf6, 0xae, 0x11, 0xdc, 0x62, 0x21, 0x44, 0xbd, 0x04, + 0x1d, 0x58, 0x2c, 0x03, 0xf8, 0xe4, 0xe2, 0x1e, 0xba, 0xe6, 0xdd, 0x19, + 0xdd, 0x56, 0xfd, 0xce, 0x06, 0x73, 0x5f, 0x94, 0x1e, 0xb6, 0x03, 0xdb, + 0x3d, 0x7b, 0xab, 0xab, 0x72, 0x64, 0x7b, 0xde, 0x7d, 0x4d, 0xcf, 0x7e, + 0xf0, 0x91, 0x29, 0xc1, 0x77, 0x13, 0xc2, 0x6f, 0x80, 0xab, 0x7a, 0xa8, + 0xce, 0xb0, 0x1c, 0x2a, 0xc5, 0x9c, 0xfb, 0x0b, 0xe5, 0x9f, 0x9c, 0x1b, + 0xc9, 0x4b, 0x58, 0xdf, 0x96, 0x18, 0xf7, 0x67, 0x67, 0x89, 0xa4, 0xe9, + 0x14, 0x48, 0xac, 0xfa, 0x9d, 0x86, 0x2a, 0xeb, 0x75, 0x2c, 0x2b, 0xbf, + 0x63, 0x7d, 0xc7, 0x4e, 0x7e, 0xad, 0x39, 0x2d, 0xb4, 0x7c, 0x07, 0xa5, + 0x5a, 0xe8, 0x3a, 0xd4, 0xf5, 0x0c, 0x4f, 0xf3, 0xa2, 0x9c, 0x3c, 0x32, + 0xed, 0x9d, 0x4b, 0x49, 0x05, 0xbc, 0x1f, 0xa0, 0x13, 0xe6, 0xdd, 0x82, + 0x79, 0x06, 0x31, 0x3b, 0xc6, 0x97, 0xec, 0x8d, 0xaa, 0x4f, 0xef, 0x14, + 0x3c, 0x21, 0xf6, 0x72, 0xb2, 0x09, 0x42, 0xc7, 0x74, 0xfe, 0xef, 0x70, + 0xbd, 0xe9, 0x85, 0x41, 0x30, 0x0b, 0xb3, 0x6b, 0x59, 0x0c, 0x0f, 0x11, + 0x75, 0xd4, 0xbb, 0xb1, 0xdf, 0xb1, 0xdf, 0xb3, 0xfa, 0xb3, 0x3a, 0x43, + 0x17, 0x7d, 0x8a, 0x82, 0xae, 0xa2, 0x07, 0xf8, 0x83, 0x51, 0xfb, 0x16, + 0xfb, 0x64, 0xb6, 0x46, 0xda, 0xbe, 0x32, 0x2b, 0xc0, 0xee, 0x78, 0x2a, + 0x84, 0xa9, 0x54, 0x0a, 0xf9, 0x2d, 0x61, 0x65, 0xde, 0xa5, 0x97, 0x66, + 0x79, 0x02, 0xf8, 0x97, 0x17, 0xe2, 0xd4, 0x9f, 0x9e, 0xac, 0xcc, 0xae, + 0x99, 0x9a, 0x03, 0x04, 0xbb, 0x45, 0xfe, 0xb2, 0xf5, 0x80, 0xba, 0xbf, + 0xdd, 0x24, 0xe5, 0xe6, 0x1e, 0x5d, 0x36, 0xa5, 0x87, 0x0c, 0xdf, 0x60, + 0x81, 0x6f, 0xb7, 0x5f, 0xb9, 0x1f, 0xca, 0x75, 0x3c, 0x1a, 0x63, 0xb0, + 0xeb, 0xe6, 0x95, 0x86, 0x0d, 0xae, 0xa6, 0xc9, 0x2a, 0x94, 0xf1, 0xd0, + 0xbe, 0x75, 0xc8, 0xf8, 0x07, 0xd7, 0x88, 0xff, 0xec, 0xf9, 0xcd, 0x49, + 0xc6, 0xfe, 0x4d, 0x7f, 0x44, 0x1e, 0xd8, 0xaf, 0xa9, 0x72, 0x27, 0x98, + 0xe2, 0x5a, 0x08, 0xea, 0x55, 0xd3, 0xb3, 0xea, 0xdc, 0x76, 0x69, 0x51, + 0x10, 0x01, 0x46, 0x7d, 0x33, 0x94, 0x9c, 0x94, 0xef, 0xfe, 0x76, 0x1c, + 0xc6, 0xd7, 0x15, 0x53, 0x3e, 0x8d, 0x3d, 0x29, 0x9a, 0x58, 0x6a, 0xf1, + 0x75, 0x9e, 0xea, 0x1b, 0x4c, 0xf0, 0x47, 0x76, 0xac, 0xc6, 0xa2, 0x32, + 0x44, 0x40, 0xdf, 0xfe, 0xff, 0x9d, 0xf4, 0xe2, 0xc2, 0xfa, 0xa1, 0x5f, + 0x2e, 0x66, 0xe9, 0x97, 0xcb, 0x27, 0x26, 0x6e, 0x53, 0xe4, 0xe8, 0x86, + 0x2c, 0xea, 0xd3, 0x69, 0x6c, 0x61, 0x4f, 0xfe, 0xc1, 0xc9, 0x8b, 0x05, + 0x92, 0x6f, 0x47, 0x96, 0xce, 0xf0, 0x33, 0xfa, 0x7c, 0x78, 0x24, 0x9b, + 0xd7, 0x8d, 0x36, 0x56, 0x37, 0x86, 0xbc, 0x72, 0x5a, 0xf9, 0xb9, 0xb0, + 0x93, 0xf0, 0x81, 0x78, 0x10, 0xf2, 0xb0, 0xc2, 0x79, 0x91, 0x5e, 0xcf, + 0xbc, 0x8c, 0xf2, 0x32, 0x0f, 0xf7, 0x2d, 0x30, 0xd8, 0x13, 0x77, 0x4f, + 0x78, 0x9e, 0x40, 0x8d, 0xe6, 0x3a, 0x98, 0xb2, 0xaa, 0x13, 0x4d, 0x25, + 0x49, 0x34, 0x6c, 0x80, 0x9e, 0x19, 0x03, 0xdb, 0xcd, 0xf5, 0xb1, 0x54, + 0x74, 0x1b, 0x67, 0x3c, 0x46, 0xac, 0x3e, 0x5d, 0xa2, 0xd9, 0x13, 0x83, + 0x30, 0xeb, 0x82, 0x3b, 0x06, 0xab, 0x3c, 0x39, 0x7d, 0xd0, 0x68, 0x31, + 0x00}; + // A 3072 bit RSA key in PKCS#8 PrivateKeyInfo format // Used to verify the functions that manipulate RSA keys. static const uint8_t kTestRSAPKCS8PrivateKeyInfo3_3072[] = { diff --git a/oemcrypto/test/oemcrypto_basic_test.cpp b/oemcrypto/test/oemcrypto_basic_test.cpp index 0d66926..ef43884 100644 --- a/oemcrypto/test/oemcrypto_basic_test.cpp +++ b/oemcrypto/test/oemcrypto_basic_test.cpp @@ -180,7 +180,7 @@ TEST_F(OEMCryptoClientTest, FreeUnallocatedSecureBufferNoFailure) { */ TEST_F(OEMCryptoClientTest, VersionNumber) { const std::string log_message = - "OEMCrypto unit tests for API 19.1. Tests last updated 2024-03-25"; + "OEMCrypto unit tests for API 19.2. Tests last updated 2024-06-24"; cout << " " << log_message << "\n"; cout << " " << "These tests are part of Android U." @@ -189,7 +189,7 @@ TEST_F(OEMCryptoClientTest, VersionNumber) { // If any of the following fail, then it is time to update the log message // above. EXPECT_EQ(ODK_MAJOR_VERSION, 19); - EXPECT_EQ(ODK_MINOR_VERSION, 1); + EXPECT_EQ(ODK_MINOR_VERSION, 2); EXPECT_EQ(kCurrentAPI, static_cast(ODK_MAJOR_VERSION)); OEMCrypto_Security_Level level = OEMCrypto_SecurityLevel(); EXPECT_GT(level, OEMCrypto_Level_Unknown); diff --git a/oemcrypto/test/oemcrypto_decrypt_test.cpp b/oemcrypto/test/oemcrypto_decrypt_test.cpp index 6e7a49a..b9868d0 100644 --- a/oemcrypto/test/oemcrypto_decrypt_test.cpp +++ b/oemcrypto/test/oemcrypto_decrypt_test.cpp @@ -544,7 +544,7 @@ TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptZeroSizeSubSample) { ASSERT_NO_FATAL_FAILURE(LoadLicense()); ASSERT_NO_FATAL_FAILURE(MakeBuffers()); ASSERT_NO_FATAL_FAILURE(EncryptData()); - ASSERT_NO_FATAL_FAILURE(TestDecryptCENC()); + ASSERT_NO_FATAL_FAILURE(DecryptCENC()); } // There are probably no frames this small, but we should handle them anyway. diff --git a/oemcrypto/test/oemcrypto_license_test.cpp b/oemcrypto/test/oemcrypto_license_test.cpp index f4dbf79..18031c4 100644 --- a/oemcrypto/test/oemcrypto_license_test.cpp +++ b/oemcrypto/test/oemcrypto_license_test.cpp @@ -213,7 +213,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyNoNonceTwiceAPI16) { ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); // A second load, should NOT succeed. - ASSERT_EQ(OEMCrypto_ERROR_LICENSE_RELOAD, license_messages_.LoadResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } // Verify that a second license may not be loaded in a session. @@ -223,7 +223,7 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithNonceTwiceAPI16) { ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); // A second load, should NOT succeed. - ASSERT_EQ(OEMCrypto_ERROR_LICENSE_RELOAD, license_messages_.LoadResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } // This tests load license with an 8k license response. @@ -496,7 +496,7 @@ TEST_P(OEMCryptoLicenseTest, LoadLicenseAgainFailureAPI16) { ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); - ASSERT_EQ(OEMCrypto_ERROR_LICENSE_RELOAD, license_messages_.LoadResponse()); + ASSERT_NE(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } TEST_P(OEMCryptoLicenseTest, LoadKeysBadSignatureAPI16) { diff --git a/oemcrypto/test/oemcrypto_provisioning_test.cpp b/oemcrypto/test/oemcrypto_provisioning_test.cpp index fbb7a83..37b2777 100644 --- a/oemcrypto/test/oemcrypto_provisioning_test.cpp +++ b/oemcrypto/test/oemcrypto_provisioning_test.cpp @@ -8,6 +8,7 @@ #include "bcc_validator.h" #include "device_info_validator.h" #include "log.h" +#include "oec_device_features.h" #include "platform.h" #include "signed_csr_payload_validator.h" #include "test_sleep.h" @@ -786,6 +787,12 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange1_API16) { if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) { GTEST_SKIP() << "Test for non Prov 4.0 devices only."; } + // DRM Reprovisioning CDMs have embedded certificates and do not support + // key rewrapping. + if (global_features.provisioning_method == OEMCrypto_DrmReprovisioning) { + GTEST_SKIP() << "Test for non DRM Reprovisioning devices."; + } + Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); provisioning_messages.PrepareSession(keybox_); @@ -808,6 +815,11 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange2_API16) { if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) { GTEST_SKIP() << "Test for non Prov 4.0 devices only."; } + // DRM Reprovisioning CDMs have embedded certificates and do not support + // key rewrapping. + if (global_features.provisioning_method == OEMCrypto_DrmReprovisioning) { + GTEST_SKIP() << "Test for non DRM Reprovisioning devices."; + } Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); provisioning_messages.PrepareSession(keybox_); @@ -830,6 +842,11 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange3_API16) { if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) { GTEST_SKIP() << "Test for non Prov 4.0 devices only."; } + // DRM Reprovisioning CDMs have embedded certificates and do not support + // key rewrapping. + if (global_features.provisioning_method == OEMCrypto_DrmReprovisioning) { + GTEST_SKIP() << "Test for non DRM Reprovisioning devices."; + } Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); provisioning_messages.PrepareSession(keybox_); @@ -854,6 +871,11 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange4_API16) { if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) { GTEST_SKIP() << "Test for non Prov 4.0 devices only."; } + // DRM Reprovisioning CDMs have embedded certificates and do not support + // key rewrapping. + if (global_features.provisioning_method == OEMCrypto_DrmReprovisioning) { + GTEST_SKIP() << "Test for non DRM Reprovisioning devices."; + } Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); provisioning_messages.PrepareSession(keybox_); @@ -881,6 +903,11 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange5Prov30_API16) { if (global_features.provisioning_method != OEMCrypto_OEMCertificate) { GTEST_SKIP() << "Test for Prov 3.0 devices only."; } + // DRM Reprovisioning CDMs have embedded certificates and do not support + // key rewrapping. + if (global_features.provisioning_method == OEMCrypto_DrmReprovisioning) { + GTEST_SKIP() << "Test for non DRM Reprovisioning devices."; + } Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); provisioning_messages.PrepareSession(keybox_); @@ -931,6 +958,11 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadNonce_API16) { if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) { GTEST_SKIP() << "Test for non Prov 4.0 devices only."; } + // DRM Reprovisioning CDMs have embedded certificates and do not support + // key rewrapping. + if (global_features.provisioning_method == OEMCrypto_DrmReprovisioning) { + GTEST_SKIP() << "Test for non DRM Reprovisioning devices."; + } Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); provisioning_messages.PrepareSession(keybox_); @@ -950,6 +982,11 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRSAKey) { if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) { GTEST_SKIP() << "Test for non Prov 4.0 devices only."; } + // DRM Reprovisioning CDMs have embedded certificates and do not support + // key rewrapping. + if (global_features.provisioning_method == OEMCrypto_DrmReprovisioning) { + GTEST_SKIP() << "Test for non DRM Reprovisioning devices."; + } Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); provisioning_messages.PrepareSession(keybox_); @@ -1119,6 +1156,10 @@ TEST_F(OEMCryptoLoadsCertificate, TestMultipleRSAKeys) { if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) { GTEST_SKIP() << "Test for non Prov 4.0 devices only."; } + // DRM Reprovisioning CDMs have embedded certificates. + if (global_features.provisioning_method == OEMCrypto_DrmReprovisioning) { + GTEST_SKIP() << "Test for non DRM Reprovisioning devices."; + } ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey()); Session s1; // Session s1 loads the default rsa key, but doesn't use it // until after s2 uses its key. @@ -1160,6 +1201,10 @@ TEST_F(OEMCryptoLoadsCertificate, TestMaxDRMKeys) { if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) { GTEST_SKIP() << "Test for non Prov 4.0 devices only."; } + // DRM Reprovisioning CDMs have embedded certificates. + if (global_features.provisioning_method == OEMCrypto_DrmReprovisioning) { + GTEST_SKIP() << "Test for non DRM Reprovisioning devices."; + } const size_t max_total_keys = GetResourceValue(kMaxTotalDRMPrivateKeys); std::vector> sessions; std::vector> licenses; diff --git a/oemcrypto/test/oemcrypto_session_tests_helper.h b/oemcrypto/test/oemcrypto_session_tests_helper.h index 1b35239..eba241b 100644 --- a/oemcrypto/test/oemcrypto_session_tests_helper.h +++ b/oemcrypto/test/oemcrypto_session_tests_helper.h @@ -44,7 +44,7 @@ class SessionUtil { // Create a new DRM Cert. Only for provisioning 4.0 void CreateProv4DRMKey(); - void CreateProv4CastKey(Session *s, bool load_drm_before_prov_req); + void CreateProv4CastKey(Session* s, bool load_drm_before_prov_req); // Used by prov2.0, prov3.0, and prov 4.0 std::vector encoded_rsa_key_; diff --git a/oemcrypto/test/oemcrypto_test.cpp b/oemcrypto/test/oemcrypto_test.cpp index 051fb05..ab245ce 100644 --- a/oemcrypto/test/oemcrypto_test.cpp +++ b/oemcrypto/test/oemcrypto_test.cpp @@ -635,6 +635,9 @@ TEST_P(OEMCryptoLicenseTest, EntitledKeySessionsAPI17) { if (wvoec::global_features.api_version < 17) { GTEST_SKIP() << "Test for versions 17 and up only."; } + if (!global_features.supports_cas) { + GTEST_SKIP() << "OEMCrypto does not support CAS"; + } license_messages_.set_license_type(OEMCrypto_EntitlementLicense); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); diff --git a/oemcrypto/util/README.md b/oemcrypto/util/README.md new file mode 100644 index 0000000..82e9dd5 --- /dev/null +++ b/oemcrypto/util/README.md @@ -0,0 +1,96 @@ +# OEMCrypto Reference Utils + +Small collection of OEMCrypto utilities which perform general operations +which hold true for **any** standard implementation of OEMCrypto. + +The goal of the utils is to provide high-level components which can perform +certain complex operations found in OEMCrypto, with the intention to be used +in the testbed code and unittests. The most important aspect of this is to +avoid providing any implementation-specific utilities here. + +Practical exceptions to goal + +* The use of C++ + * Techincally specific to a specific implementation + * Our testbed and test code both use C++, and this is intended to be + used by these systems +* Raw data formats + * Generally, we use `std::string`, `std::vector` and `uint8_t*` to + store and manipulate bytes of data +* Reasonable resource limitations + * Theoretical algorithms to usually take into consideration computer + resources, but real implementations are still subjected to them + * Ex. max buffer size which component operate on +* Use of Widevine's logging system + +Example of goal-aligned components + +* Standard cryptographic operations wrappers + * Schemes, protocols and algorithms which are publicly published + * Mechanically similar to other cryptographical software libraries +* OEMCrypto-specific RSA and ECC cryptographic operations + * OEMCrypto has a small set of protocols based on RSA and ECC cryptography + * These protocols are well-defined by the OEMCrypto specification, and + any implementation would need to include an implementation of these exact + protocols +* DRM key cryptographic operations + * Built upon RSA and ECC operations + * Only performs DRM key operations which ANY implementation would also + have to perform in the same way + +Example of hypothetical anti-goal components + +* DRM key wrapping algorithms + * The OEMCrypto specification (as of v19) does not specify exactly how + wrapping a DRM key is to be performed. + * Although many OEMs likely use the same implementaion as the reference, + it is still an undefined operation. + * Note: Standard parts of a wrapping algorithm (such as AES encryption) + may be provided. +* Usage Table Header/Entry encryptors + * Similar to DRM key wrapping, the OEMCrypto specification (as of v19) does + not specify exactly how the Usage Table's header and entries are to be + encrypted and signed +* Key handle to session ID serialization + * This is NOT defined by the OEMCrypto specification + +## Current Components + +This is a non-exhausted list of components found within this library which +provided general OEMCrypto operations. + +* `EccPublicKey` and `EccPrivateKey` + * High-level wrappers around OEMCrypto-specific protocols of Elliptic + Curve Cryptography + * Generally aimed for how ECC-based DRM keys are used, as well as for + a small set of BCC operations +* `RsaPublicKey` and `RsaPrivateKey` + * High-level wrappers around OEMCrypto-specific protocols of RSA Cryptography + * Generally aimed for how RSA-based DRM and OEM Cert keys are used, as + well as a small set of Cast-specific operations +* `DrmPrivateKey` + * A high-level wrapper around OEMCrypto's DRM key + * Provides a generic DRM key interface, which internally handles the + different cases of using either a RSA or ECC based DRM key +* `wvcrc32.h` + * Set of functions for OEMCrypto's CRC-32 algorithm + * CRC-32 is a general term for 32-bit Cyclic Redundancy Checks + * OEMCrypto has a well-defined set of parameters which all implementers + must use to work with Widevine's software stack +* `hmac.h` and `cmac.h` + * HMAC and CMAC C++ wrappers, restricted to only the algorithm parameters + which are used within OEMCrypto +* `KeyDeriver` + * High-level wrapper around OEMCrypto's key derivation algorithm + * Intended to be used only for messaging key derivation, which is + well-defined within the OEMCrypto standard + * Note: Although the testbed uses this for other types of keys, the + methods provided by `KeyDeriver` are only designed with messaging keys + in mind. +* `CborValidator` and specialized validators + * High-level validator for CBOR messages found within the Provisioning 4.0 + protocol. + * Note: Although the error messages provided by this library are not defined + the primary function of these components do follow the specification, and + the error messages are used to inform a human user, not another software + system. diff --git a/oemcrypto/util/include/oemcrypto_ecc_key.h b/oemcrypto/util/include/oemcrypto_ecc_key.h index 079da6e..3542eb2 100644 --- a/oemcrypto/util/include/oemcrypto_ecc_key.h +++ b/oemcrypto/util/include/oemcrypto_ecc_key.h @@ -38,7 +38,8 @@ class EccPublicKey { // Creates a new public key equivalent of the provided private key. static std::unique_ptr New(const EccPrivateKey& private_key); - // Loads a serialized EC public key. + // Loads a ASN.1 DER serialized EC public key. + // // The provided |buffer| must contain a valid ASN.1 DER encoded // SubjectPublicKey. Only supported curves by this API are those // enumerated by EccCurve. @@ -50,7 +51,7 @@ class EccPublicKey { // namedCurve: OID = secp256r1 | secp384r1 | secp521r1 // } // }, - // subjectPublicKey: BIT STRING = ... -- SEC1 encoded ECPoint + // subjectPublicKey: BIT STRING = ... -- SEC 1 encoded ECPoint // } // // Failure will occur if the provided |buffer| does not contain a @@ -60,13 +61,39 @@ class EccPublicKey { size_t length); static std::unique_ptr Load(const std::string& buffer); static std::unique_ptr Load(const std::vector& buffer); - // Loads EC public key from the |curve| and |buffer|. - // The provided |buffer| must contain an EC point serialized from raw X9.62 - // format. For uncompressed form, it is a 1-byte prefix plus two 32-byte - // integers representing X, Y coordinates. + + // Loads a SEC 1 serialized EC public key. + // + // The provided |buffer| must contain a valid SEC 1 encoded EC point + // belonging to the provided |curve|. + // + // SEC 1 section 2.3.3 specifies two supported formats, compressed or + // uncompressed. + // + // Case uncompressed: + // buffer: 0x04 || X || Y + // + // Where X and Y are fixed-width byte encodings of the public keys + // x and y component. + // + // Case compressed: + // buffer: (0x02 or 0x03) || X + // + // Where X is a fixed-width byte encoding of the public keys x + // component; and the y component is derived using the curve + // equation, and the sign of y is positive if lead byte is 0x02, + // or negative if lead byte is 0x03. + // + // Note: The EC point encoding in SEC 1 is derived from the X9.62 + // format; however, the "evenness" version of the X9.62 compressed + // point is NOT supported and is generally not recommended. static std::unique_ptr LoadKeyPoint(EccCurve curve, const uint8_t* buffer, size_t length); + static std::unique_ptr LoadKeyPoint(EccCurve curve, + const std::string& buffer); + static std::unique_ptr LoadKeyPoint( + EccCurve curve, const std::vector& buffer); // Loads a serialized ECC private key, but only converting the public key. static std::unique_ptr LoadPrivateKeyInfo(const uint8_t* buffer, @@ -93,6 +120,17 @@ class EccPublicKey { // Same as above, except directly returns the serialized key. // Returns an empty vector on error. std::vector Serialize() const; + // Serializes the public key into a SEC 1 encoded public key point. + // To restore a key from the returned key point, the caller must + // keep track of the specific curve of the key. + // + // If |compressed| is false (default), the key point will be + // serialized in its uncompressed form; otherwise, the key will be + // serialized in its compressed form. + // + // Directly returns the serialized public key. + // Returns an empty vector on error. + std::vector SerializeAsSec1KeyPoint(bool compressed = false) const; // Verifies the |signature| matches the provided |message| by the // private equivalent of this public key. @@ -114,15 +152,26 @@ class EccPublicKey { const std::string& signature) const; OEMCryptoResult VerifySignature(const std::vector& message, const std::vector& signature) const; - // Verifies the raw |signature| matches the provided |message| by the - // private equivalent of this public key. - // A raw ECDSA signature consists of a pair of integers (r,s). The |signature| - // is a concatenation of two octet strings resulting from the integer-to-octet - // encoding of the values of r and s, in the order of (r||s). + // Verifies the raw |signature| matches the provided |message| by + // the private equivalent of this public key. + // A raw ECDSA signature consists of a pair of integers (r, s). + // The |signature| is a concatenation of the unsigned integers r and + // s as two equal length octet strings using big-endian encoding. + // + // The |message| is digested using the same ECDSA algorithm as + // VerifySignature(). + // + // Returns: + // OEMCrypto_SUCCESS if signature is valid + // OEMCrypto_ERROR_SIGNATURE_FAILURE if the |signature| is invalid + // Any other result indicates an unexpected error OEMCryptoResult VerifyRawSignature(const uint8_t* message, size_t message_length, const uint8_t* signature, size_t signature_length) const; + OEMCryptoResult VerifyRawSignature( + const std::vector& message, + const std::vector& signature) const; ~EccPublicKey(); @@ -141,11 +190,12 @@ class EccPublicKey { bool InitFromPrivateKeyInfo(const uint8_t* buffer, size_t length); // Initializes the public key object from a private. bool InitFromPrivateKey(const EccPrivateKey& private_key); - // Initializes the public key object from the provided curve and key point - // |buffer|. - bool InitFromKeyPoint(EccCurve curve, const uint8_t* buffer, size_t length); - // Digests the |message| and verifies signature against the provided signature - // point. + // Initializes the public key object from the provided |curve| and + // SEC 1 encoded EC key point |buffer|. + bool InitFromSec1KeyPoint(EccCurve curve, const uint8_t* buffer, + size_t length); + // Digests the |message| and verifies signature against the provided + // ECDSA signature point |sig_point|. OEMCryptoResult DigestAndVerify(const uint8_t* message, size_t message_length, const ECDSA_SIG* sig_point) const; @@ -181,7 +231,7 @@ class EccPrivateKey { // version: INTEGER = ecPrivateKeyVer1(1), // privateKey: OCTET STRING = ..., -- I2OSP of private key point // -- |parameters| are obtained from PrivateKeyInfo - // publicKey: BIT STRING OPTIONAL = ... -- SEC1 encoded ECPoint + // publicKey: BIT STRING OPTIONAL = ... -- SEC 1 encoded ECPoint // } // Note: If the public key is not included, then it is computed from // the private key. @@ -237,6 +287,12 @@ class EccPrivateKey { // Returns an empty vector on error. std::vector SerializeAsPublicKey() const; + // Serializes the public component of the private key into an SEC 1 + // public key point. + // See EccPublicKey::SerializeAsSec1KeyPoint() for details. + std::vector SerializeAsPublicSec1KeyPoint( + bool compressed = false) const; + // Signs the provided |message| and serializes the signature // point to |signature| as a ASN.1 DER encoded ECDSA-Sig-Value. // This implementation uses ECDSA with the following digest @@ -261,6 +317,12 @@ class EccPrivateKey { // the actual signature generated by GenerateSignature(). size_t SignatureSize() const; + // Special test method used to generate a raw ECDSA signature. + // A raw ECDSA signature is a concatination of a same-width-big-endian + // encoding of the ECDSA signature point components r and s. + std::vector GenerateRawSignature( + const std::vector& message) const; + // Derives the OEMCrypto session key used for deriving other keys. // The provided public key must be of the same curve. // On success, |session_key_size| is populated with the number of diff --git a/oemcrypto/util/include/oemcrypto_key_handle.h b/oemcrypto/util/include/oemcrypto_key_handle.h deleted file mode 100644 index 9db9043..0000000 --- a/oemcrypto/util/include/oemcrypto_key_handle.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine License -// Agreement. -// -// Implements utility functions for serializing and deserializing the fake key -// handles used by the Ref and Testbed. -// -#ifndef WVOEC_UTIL_KEY_HANDLE_H_ -#define WVOEC_UTIL_KEY_HANDLE_H_ - -#include -#include -#include - -#include "OEMCryptoCENC.h" -#include "log.h" - -namespace wvoec { -namespace util { -// Size of a key handle, which for this implementation is just a session ID. -constexpr size_t kKeyHandleSize = sizeof(OEMCrypto_SESSION); - -OEMCryptoResult SerializeSessionToKeyHandle(OEMCrypto_SESSION session, - uint8_t* key_handle, - size_t* key_handle_length) { - if (key_handle_length == nullptr) { - LOGE("Null key handle length"); - return OEMCrypto_ERROR_INVALID_CONTEXT; - } - - if (key_handle == nullptr || *key_handle_length < kKeyHandleSize) { - *key_handle_length = kKeyHandleSize; - return OEMCrypto_ERROR_SHORT_BUFFER; - } - - *key_handle_length = kKeyHandleSize; - memcpy(key_handle, &session, kKeyHandleSize); - return OEMCrypto_SUCCESS; -} - -OEMCryptoResult DeserializeKeyHandleToSession(const uint8_t* key_handle, - size_t key_handle_length, - OEMCrypto_SESSION* session) { - if (key_handle == nullptr) { - LOGE("Null key handle"); - return OEMCrypto_ERROR_INVALID_CONTEXT; - } - if (session == nullptr) { - LOGE("Null session"); - return OEMCrypto_ERROR_INVALID_CONTEXT; - } - - if (key_handle_length != kKeyHandleSize) { - LOGE("Invalid key handle length"); - return OEMCrypto_ERROR_INVALID_CONTEXT; - } - - memcpy(session, key_handle, kKeyHandleSize); - return OEMCrypto_SUCCESS; -} -} // namespace util -} // namespace wvoec -#endif // WVOEC_UTIL_KEY_HANDLE_H_ diff --git a/oemcrypto/util/include/scoped_object.h b/oemcrypto/util/include/scoped_object.h index 115b617..d9cdc6f 100644 --- a/oemcrypto/util/include/scoped_object.h +++ b/oemcrypto/util/include/scoped_object.h @@ -41,7 +41,8 @@ class ScopedObject { return *this; } - explicit operator bool() const { return ptr_ != nullptr; } + bool ok() const { return ptr_ != nullptr; } + explicit operator bool() const { return ok(); } Type& operator*() { return *ptr_; } Type* get() const { return ptr_; } diff --git a/oemcrypto/util/src/oemcrypto_ecc_key.cpp b/oemcrypto/util/src/oemcrypto_ecc_key.cpp index 47a4655..a311614 100644 --- a/oemcrypto/util/src/oemcrypto_ecc_key.cpp +++ b/oemcrypto/util/src/oemcrypto_ecc_key.cpp @@ -31,10 +31,31 @@ namespace { // larger needed. constexpr size_t kPrivateKeySize = 250; constexpr size_t kPublicKeySize = 164; +// Estimated max size (in bytes) of a SEC 1 serialized ECC public key +// point. Similar to above, this is based on a rough calculation for +// secp521r1 and is padded by a few bytes to ensure it is larger than +// needed. +constexpr size_t kSec1PublicKeyPointMaxSize = 150; // 256 bit key, intended to be used with CMAC-AES-256. constexpr size_t kEccSessionKeySize = 32; +// SEC 1 EC public key point lead byte values. +constexpr uint8_t kSec1LeadCompressedPositive = 0x02; +constexpr uint8_t kSec1LeadCompressedNegative = 0x03; +constexpr uint8_t kSec1LeadUncompressed = 0x04; + +// Checks that the first byte of a SEC 1 encoded EC public key point. +// Only supported values are compressed w/ sign and uncompressed. +// Other X9.62 values are not supported. +constexpr bool IsSupportedSec1LeadByte(uint8_t lead_byte) { + return lead_byte == kSec1LeadCompressedPositive || + lead_byte == kSec1LeadCompressedNegative || + lead_byte == kSec1LeadUncompressed; +} + +const char* BoolToStatus(bool value) { return value ? "Ok" : "Bad"; } + using ScopedBigNum = ScopedObject; using ScopedBigNumCtx = ScopedObject; using ScopedBio = ScopedObject; @@ -46,6 +67,11 @@ using ScopedPrivateKeyInfo = using ScopedSigPoint = ScopedObject; using ScopedEcPoint = ScopedObject; +void OpensslFreeU8(uint8_t* ptr) { OPENSSL_free(ptr); } +using ScopedBuffer = ScopedObject; + +// == EC Group Utilts == + const EC_GROUP* GetEcGroup(EccCurve curve) { // Creating a named EC_GROUP is an expensive operation, and they // are always used in a manner which does not transfer ownership. @@ -141,63 +167,7 @@ EccCurve GetCurveFromKeyGroup(const EC_KEY* key) { return kEccCurveUnknown; } -// Creates EC public key from |curve| and |key_point|, and sets the result in -// *|public_key|. -bool GetPublicKeyFromKeyPoint(EccCurve curve, const uint8_t* key_point, - size_t key_point_length, EC_KEY** public_key) { - if (key_point == nullptr || key_point_length == 0) { - return false; - } - const EC_GROUP* group = GetEcGroup(curve); - if (!group) { - LOGE("Failed to get ECC group for curve %d", curve); - return false; - } - ScopedEcPoint point(EC_POINT_new(group)); - if (!point) { - LOGE("Failed to new EC_POINT"); - return false; - } - if (!EC_POINT_oct2point(group, point.get(), key_point, key_point_length, - nullptr)) { - LOGE("Failed to convert the serialized point to EC_POINT"); - return false; - } - ScopedEcKey key(EC_KEY_new()); - if (!key) { - LOGE("Failed to allocate key"); - return false; - } - if (!EC_KEY_set_group(key.get(), group)) { - LOGE("Failed to set group"); - return false; - } - if (EC_KEY_set_public_key(key.get(), point.get()) == 0) { - LOGE("Failed to convert the EC_POINT to EC_KEY"); - return false; - } - *public_key = key.release(); - return true; -} - -// Compares the public EC points of both keys to see if they are the -// equal. -// Both |public_key| and |private_key| must be of the same group. -bool IsMatchingKeyPair(const EC_KEY* public_key, const EC_KEY* private_key) { - ScopedBigNumCtx ctx(BN_CTX_new()); - if (!ctx) { - LOGE("Failed to allocate BN ctx"); - return false; - } - // Returns: 1 if not equal, 0 if equal, -1 if error. - const int res = EC_POINT_cmp(EC_KEY_get0_group(public_key), - EC_KEY_get0_public_key(public_key), - EC_KEY_get0_public_key(private_key), ctx.get()); - if (res == -1) { - LOGE("Error occurred comparing keys"); - } - return res == 0; -} +// == EC Key Operation Utilities == // Performs a SHA2 digest on the provided |message| and outputs the // computed hash to |digest|. @@ -284,7 +254,57 @@ void* WidevineEccKdf(const void* secret, size_t secret_length, void* key, return key; } -void OpensslFreeU8(uint8_t* ptr) { OPENSSL_free(ptr); } +// Generates an EC signature point using ECDSA. +// Outputs the signature to |sig_point|. +// Returns true on success, false otherwise. +bool DigestAndSign(EccCurve curve, const EC_KEY* private_key, + const uint8_t* message, size_t message_length, + ScopedSigPoint* sig_point) { + if (private_key == nullptr || (message == nullptr && message_length > 0) || + sig_point == nullptr) { + // These are expected to be caught before calling this function. + return false; + } + // Digest message based on |curve|. + std::vector digest; + if (!DigestMessage(curve, message, message_length, &digest)) { + LOGE("Failed to digest message"); + return false; + } + // Generate signature point. + // Note: OpenSSL and BoringSSL have slightly different APIs. + // For OpenSSL, EC_KEY is not marked consts, however, it does not + // modify the key. + sig_point->reset( + ECDSA_do_sign(digest.data(), static_cast(digest.size()), + const_cast(private_key) /* Does not modify */)); + if (!*sig_point) { + LOGE("Failed to perform ECDSA"); + return false; + } + return true; +} + +// == EC Key Utilities == + +// Compares the public EC points of both keys to see if they are the +// equal. +// Both |public_key| and |private_key| must be of the same group. +bool IsMatchingKeyPair(const EC_KEY* public_key, const EC_KEY* private_key) { + ScopedBigNumCtx ctx(BN_CTX_new()); + if (!ctx) { + LOGE("Failed to allocate BN ctx"); + return false; + } + // Returns: 1 if not equal, 0 if equal, -1 if error. + const int res = EC_POINT_cmp(EC_KEY_get0_group(public_key), + EC_KEY_get0_public_key(public_key), + EC_KEY_get0_public_key(private_key), ctx.get()); + if (res == -1) { + LOGE("Error occurred comparing keys"); + } + return res == 0; +} // Internal ECC public key serialization. OEMCryptoResult SerializeEccPublicKey(const EC_KEY* key, uint8_t* buffer, @@ -305,7 +325,7 @@ OEMCryptoResult SerializeEccPublicKey(const EC_KEY* key, uint8_t* buffer, LOGE("Public key serialization failed"); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - ScopedObject der_key(der_key_raw); + ScopedBuffer der_key(der_key_raw); der_key_raw = nullptr; if (!der_key) { LOGE("Encoded key is unexpectedly null"); @@ -339,6 +359,139 @@ std::vector SerializeEccPublicKey(const EC_KEY* key) { return key_data; } +// Internal ECC public key serialization for SEC 1 key points. +std::vector SerializeEccPublicKeyAsSec1KeyPoint(const EC_KEY* key, + bool compressed) { + if (key == nullptr) { + // Programmer error, internal to this module. + LOGE("Input |key| is null"); + return {}; + } + // Obtain key point and group. + const EC_POINT* key_point = EC_KEY_get0_public_key(key); + if (key_point == nullptr) { + LOGE("Failed to obtain key point"); + return {}; + } + const EC_GROUP* group = EC_KEY_get0_group(key); + if (group == nullptr) { + LOGE("Failed to obtain curve group"); + return {}; + } + ScopedBigNumCtx ctx(BN_CTX_new()); + if (!ctx) { + LOGE("Failed to allocate BN CTX"); + return {}; + } + std::vector sec1_key_point(kSec1PublicKeyPointMaxSize, 0); + // EC_POINT_point2oct() follows SEC 1 section 2.3.3 protocol for + // serialization of a public key point. + const size_t res = EC_POINT_point2oct( + group, key_point, + compressed ? POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED, + sec1_key_point.data(), sec1_key_point.size(), ctx.get()); + if (res == 0) { + LOGE( + "Failed to serialize public key point in SEC 1 format: " + "compressed = %s", + compressed ? "true" : "false"); + return {}; + } + if (res > kSec1PublicKeyPointMaxSize) { + // This shouldn't happen. Could be a bug in OpenSSL/BoringSSL, + // or a result of future support of larger curve groups without + // updating the related size estimates. + LOGE( + "Key point is unexpectedly large: " + "actual_size = %zu, expected_max_size = %zu", + res, kSec1PublicKeyPointMaxSize); + return {}; + } + sec1_key_point.resize(res); + return sec1_key_point; +} + +void SetIetfComplianceFlags(EC_KEY* key) { + // Required flags for IETF compliance. + EC_KEY_set_asn1_flag(key, OPENSSL_EC_NAMED_CURVE); + EC_KEY_set_conv_form(key, POINT_CONVERSION_UNCOMPRESSED); +} + +// Parses the EC key from a SEC 1 section 2.3.4 encoded public key. +// Uses |curve| to determine the group of the key, and then converts +// the encoded points from |buffer| into an EC point. +// If successful, which is then the group and public key point will +// be assigned to the provided |public_key|. +// +// Returns true on success, false otherwise. +bool ParseEccSec1PublicKey(EccCurve curve, const uint8_t* buffer, size_t length, + ScopedEcKey* public_key) { + if (buffer == nullptr || length <= 1) { + LOGE("Invalid SEC 1 encoded key point buffer"); + return false; + } + if (public_key == nullptr) { + LOGE("Provided public key buffer is null"); + return false; + } + const uint8_t lead_byte = buffer[0]; + if (!IsSupportedSec1LeadByte(lead_byte)) { + LOGE("Unsupported SEC 1 encoding: lead_byte = 0x%02x", + static_cast(lead_byte)); + return false; + } + // Step 1: Validate curve is supported. + const EC_GROUP* group = GetEcGroup(curve); + if (group == nullptr) { + LOGE("Failed to get ECC group"); + return false; + } + // Step 2: Deserialize from SEC 1 to EC_POINT. + ScopedEcPoint point(EC_POINT_new(group)); + if (!point) { + LOGE("Failed to allocate EC_POINT"); + return false; + } + ScopedBigNumCtx ctx(BN_CTX_new()); + if (!ctx) { + LOGE("Failed to allocate BN CTX"); + return false; + } + // Note: EC_POINT_oct2point() follows SEC 1 section 2.3.4 rules for + // deserializing. It should be able to handle uncompressed and + // compressed points. + if (!EC_POINT_oct2point(group, point.get(), buffer, length, ctx.get())) { + LOGE("Failed to convert SEC 1 encoded key point to EC_POINT: type = %s", + lead_byte == kSec1LeadUncompressed ? "Uncompressed" : "Compressed"); + return false; + } + // Step 3: Construct public key from EC_POINT. + public_key->reset(EC_KEY_new()); + if (!*public_key) { + LOGE("Failed to allocate key"); + return false; + } + if (!EC_KEY_set_group(public_key->get(), group)) { + LOGE("Failed to set group"); + return false; + } + if (!EC_KEY_set_public_key(public_key->get(), point.get())) { + LOGE("Failed to set public key point"); + return false; + } + // Step 4: Validate and set compliance flags. + const int check = EC_KEY_check_key(public_key->get()); + if (check == 0) { + LOGE("ECC key parameters are invalid"); + return false; + } else if (check == -1) { + LOGE("Failed to check ECC key"); + return false; + } + SetIetfComplianceFlags(public_key->get()); + return true; +} + bool ParseEccPrivateKeyInfo(const uint8_t* buffer, size_t length, ScopedEcKey* key, EccCurve* curve) { if (length == 0) { @@ -387,9 +540,7 @@ bool ParseEccPrivateKeyInfo(const uint8_t* buffer, size_t length, LOGE("Failed to determine key group"); return false; } - // Required flags for IETF compliance. - EC_KEY_set_asn1_flag(key->get(), OPENSSL_EC_NAMED_CURVE); - EC_KEY_set_conv_form(key->get(), POINT_CONVERSION_UNCOMPRESSED); + SetIetfComplianceFlags(key->get()); return true; } } // namespace @@ -458,14 +609,58 @@ std::unique_ptr EccPublicKey::Load( } // static -std::unique_ptr EccPublicKey::LoadPrivateKeyInfo( - const uint8_t* buffer, size_t length) { +std::unique_ptr EccPublicKey::LoadKeyPoint(EccCurve curve, + const uint8_t* buffer, + size_t length) { if (buffer == nullptr) { - LOGE("Provided public key buffer is null"); + LOGE("Provided key point buffer is null"); return nullptr; } if (length == 0) { - LOGE("Provided public key buffer is zero length"); + LOGE("Provided key point buffer is zero length"); + return nullptr; + } + std::unique_ptr key(new EccPublicKey()); + if (!key->InitFromSec1KeyPoint(curve, buffer, length)) { + LOGE( + "Failed to initialize public key from SEC 1 key point: " + "curve = %s, length = %zu", + EccCurveToString(curve).c_str(), length); + key.reset(); + } + return key; +} + +// static +std::unique_ptr EccPublicKey::LoadKeyPoint( + EccCurve curve, const std::string& buffer) { + if (buffer.empty()) { + LOGE("Provided public key point buffer is empty"); + return std::unique_ptr(); + } + return LoadKeyPoint(curve, reinterpret_cast(buffer.data()), + buffer.size()); +} + +// static +std::unique_ptr EccPublicKey::LoadKeyPoint( + EccCurve curve, const std::vector& buffer) { + if (buffer.empty()) { + LOGE("Provided public key point buffer is empty"); + return std::unique_ptr(); + } + return LoadKeyPoint(curve, buffer.data(), buffer.size()); +} + +// static +std::unique_ptr EccPublicKey::LoadPrivateKeyInfo( + const uint8_t* buffer, size_t length) { + if (buffer == nullptr) { + LOGE("Provided private key buffer is null"); + return nullptr; + } + if (length == 0) { + LOGE("Provided private key buffer is zero length"); return nullptr; } std::unique_ptr key(new EccPublicKey()); @@ -480,7 +675,7 @@ std::unique_ptr EccPublicKey::LoadPrivateKeyInfo( std::unique_ptr EccPublicKey::LoadPrivateKeyInfo( const std::string& buffer) { if (buffer.empty()) { - LOGE("Provided public key buffer is empty"); + LOGE("Provided private key buffer is empty"); return std::unique_ptr(); } return LoadPrivateKeyInfo(reinterpret_cast(buffer.data()), @@ -491,32 +686,12 @@ std::unique_ptr EccPublicKey::LoadPrivateKeyInfo( std::unique_ptr EccPublicKey::LoadPrivateKeyInfo( const std::vector& buffer) { if (buffer.empty()) { - LOGE("Provided public key buffer is empty"); + LOGE("Provided private key buffer is empty"); return std::unique_ptr(); } return LoadPrivateKeyInfo(buffer.data(), buffer.size()); } -// static -std::unique_ptr EccPublicKey::LoadKeyPoint(EccCurve curve, - const uint8_t* buffer, - size_t length) { - if (buffer == nullptr) { - LOGE("Provided key point buffer is null"); - return nullptr; - } - if (length == 0) { - LOGE("Provided key point buffer is zero length"); - return nullptr; - } - std::unique_ptr key(new EccPublicKey()); - if (!key->InitFromKeyPoint(curve, buffer, length)) { - LOGE("Failed to initialize public key from KeyPoint"); - key.reset(); - } - return key; -} - bool EccPublicKey::IsMatchingPrivateKey( const EccPrivateKey& private_key) const { if (private_key.curve() != curve_) { @@ -534,6 +709,11 @@ std::vector EccPublicKey::Serialize() const { return SerializeEccPublicKey(key_); } +std::vector EccPublicKey::SerializeAsSec1KeyPoint( + bool compressed) const { + return SerializeEccPublicKeyAsSec1KeyPoint(key_, compressed); +} + OEMCryptoResult EccPublicKey::VerifySignature(const uint8_t* message, size_t message_length, const uint8_t* signature, @@ -590,26 +770,48 @@ OEMCryptoResult EccPublicKey::VerifyRawSignature( LOGE("Bad message data"); return OEMCrypto_ERROR_INVALID_CONTEXT; } - if (signature_length % 2 == 1) { + if ((signature_length % 2) == 1) { + // Raw ECDSA signatures should alawys be even length. LOGE("Bad signature size: %zu", signature_length); - return OEMCrypto_ERROR_INVALID_CONTEXT; + return OEMCrypto_ERROR_SIGNATURE_FAILURE; } // Parse signature. + const int r_s_size = static_cast(signature_length) / 2; + ScopedBigNum r(BN_bin2bn(signature, r_s_size, nullptr)); + ScopedBigNum s(BN_bin2bn(&signature[r_s_size], r_s_size, nullptr)); + // Note: Must ensure both |r| and |s| are not null. If one is + // null, then the other might not get freed when calling + // ECDSA_SIG_set0(). + if (!s || !r) { + LOGE("Failed to parse R/S values: r = %s, s = %s, r_s_size = %d", + BoolToStatus(r.ok()), BoolToStatus(s.ok()), r_s_size); + // Technically, any byte string should be convertable to an + // integer. This must be an OpenSSL/BoringSSL error. + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } ScopedSigPoint sig_point(ECDSA_SIG_new()); if (!sig_point) { LOGE("Error occurred in ECDSA_SIG_new()"); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - const int r_s_size = static_cast(signature_length) / 2; - if (!ECDSA_SIG_set0(sig_point.get(), BN_bin2bn(signature, r_s_size, nullptr), - BN_bin2bn(signature + r_s_size, r_s_size, nullptr))) { - LOGE("Failed to parse signature"); - // Most likely an invalid signature than an OpenSSL error. - return OEMCrypto_ERROR_SIGNATURE_FAILURE; + if (!ECDSA_SIG_set0(sig_point.get(), r.release(), s.release())) { + LOGE("Failed to set ECDSA_SIG components"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; } return DigestAndVerify(message, message_length, sig_point.get()); } +OEMCryptoResult EccPublicKey::VerifyRawSignature( + const std::vector& message, + const std::vector& signature) const { + if (signature.empty()) { + LOGE("Signature should not be empty"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + return VerifyRawSignature(message.data(), message.size(), signature.data(), + signature.size()); +} + EccPublicKey::~EccPublicKey() { if (key_ != nullptr) { EC_KEY_free(key_); @@ -641,21 +843,21 @@ bool EccPublicKey::InitFromSubjectPublicKeyInfo(const uint8_t* buffer, LOGE("Failed to determine key group"); return false; } - // Required flags for IETF compliance. - EC_KEY_set_asn1_flag(key.get(), OPENSSL_EC_NAMED_CURVE); - EC_KEY_set_conv_form(key.get(), POINT_CONVERSION_UNCOMPRESSED); + SetIetfComplianceFlags(key.get()); key_ = key.release(); return true; } bool EccPublicKey::InitFromPrivateKeyInfo(const uint8_t* buffer, size_t length) { - ScopedEcKey private_key; - if (!ParseEccPrivateKeyInfo(buffer, length, &private_key, &curve_)) { + ScopedEcKey key; + if (!ParseEccPrivateKeyInfo(buffer, length, &key, &curve_)) { return false; } - // TODO(sigquit): Strip private information. - key_ = private_key.release(); + // OpenSSL allows for removing the private component of the + // EC key; however, BoringSSL does not support it, and will + // crash if attempted. + key_ = key.release(); return true; } @@ -674,35 +876,21 @@ bool EccPublicKey::InitFromPrivateKey(const EccPrivateKey& private_key) { LOGE("Failed to set public point"); return false; } + SetIetfComplianceFlags(key.get()); curve_ = private_key.curve(); - // Required flags for IETF compliance. - EC_KEY_set_asn1_flag(key.get(), OPENSSL_EC_NAMED_CURVE); - EC_KEY_set_conv_form(key.get(), POINT_CONVERSION_UNCOMPRESSED); key_ = key.release(); return true; } -bool EccPublicKey::InitFromKeyPoint(EccCurve curve, const uint8_t* buffer, - size_t length) { - if (buffer == nullptr || length == 0) { - LOGE("Provided key point buffer is empty"); +bool EccPublicKey::InitFromSec1KeyPoint(EccCurve curve, const uint8_t* buffer, + size_t length) { + ScopedEcKey key; + if (!ParseEccSec1PublicKey(curve, buffer, length, &key)) { + LOGE("Failed to parse SEC 1 encoded public key point"); return false; } - EC_KEY* ec_key; - if (!GetPublicKeyFromKeyPoint(curve, buffer, length, &ec_key)) { - return false; - } - ScopedEcKey key(ec_key); - if (EC_KEY_check_key(key.get()) != 1) { - LOGE("Invalid public EC key"); - return false; - } - - // Required flags for IETF compliance. - EC_KEY_set_asn1_flag(key.get(), OPENSSL_EC_NAMED_CURVE); - EC_KEY_set_conv_form(key.get(), POINT_CONVERSION_UNCOMPRESSED); - key_ = key.release(); curve_ = curve; + key_ = key.release(); return true; } @@ -871,6 +1059,11 @@ std::vector EccPrivateKey::SerializeAsPublicKey() const { return SerializeEccPublicKey(key_); } +std::vector EccPrivateKey::SerializeAsPublicSec1KeyPoint( + bool compressed) const { + return SerializeEccPublicKeyAsSec1KeyPoint(key_, compressed); +} + OEMCryptoResult EccPrivateKey::GenerateSignature( const uint8_t* message, size_t message_length, uint8_t* signature, size_t* signature_length) const { @@ -891,21 +1084,13 @@ OEMCryptoResult EccPrivateKey::GenerateSignature( *signature_length = expected_signature_length; return OEMCrypto_ERROR_SHORT_BUFFER; } - - // Step 1: Hash message. - std::vector digest; - if (!DigestMessage(curve_, message, message_length, &digest)) { - LOGE("Failed to digest message"); + // Generate sig point. + ScopedSigPoint sig_point; + if (!DigestAndSign(curve_, key_, message, message_length, &sig_point)) { + LOGE("Failed to digest and sign"); return OEMCrypto_ERROR_UNKNOWN_FAILURE; } - // Step 2: Generate signature point. - ScopedSigPoint sig_point( - ECDSA_do_sign(digest.data(), static_cast(digest.size()), key_)); - if (!sig_point) { - LOGE("Failed to perform ECDSA"); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - // Step 3: Serialize + // Serialize to DER encoded ECDSA-Sig-Value. std::vector temp(expected_signature_length); uint8_t* sig_ptr = temp.data(); const int res = i2d_ECDSA_SIG(sig_point.get(), &sig_ptr); @@ -915,6 +1100,8 @@ OEMCryptoResult EccPrivateKey::GenerateSignature( } const size_t required_size = static_cast(res); if (signature == nullptr || *signature_length < required_size) { + // Unlikely, but possible that ECDSA_size() returned too small + // a value. *signature_length = required_size; return OEMCrypto_ERROR_SHORT_BUFFER; } @@ -956,6 +1143,52 @@ std::vector EccPrivateKey::GenerateSignature( size_t EccPrivateKey::SignatureSize() const { return ECDSA_size(key_); } +std::vector EccPrivateKey::GenerateRawSignature( + const std::vector& message) const { + // Generate sig point. + ScopedSigPoint sig_point; + if (!DigestAndSign(curve_, key_, message.data(), message.size(), + &sig_point)) { + LOGE("Failed to digest and sign"); + return {}; + } + // Serialize to same-width-big-endian of both sig point components + // r and s. + const BIGNUM* r = ECDSA_SIG_get0_r(sig_point.get()); + const BIGNUM* s = ECDSA_SIG_get0_s(sig_point.get()); + if (r == nullptr || s == nullptr) { + LOGE("Failed to extract R/S: r = %s, s = %s", BoolToStatus(r != nullptr), + BoolToStatus(s != nullptr)); + return {}; + } + // Determine the size, must use the larger of the two. + const int r_size = BN_num_bytes(r); + const int s_size = BN_num_bytes(s); + if (r_size <= 0 || s_size <= 0) { + LOGE("Failed to determine R/S size: r_size = %d, s_size = %d", r_size, + s_size); + return {}; + } + const int component_size = (r_size >= s_size ? r_size : s_size); + const size_t total_size = static_cast(component_size) * 2; + // Serialize R. + std::vector raw_signature(total_size, 0); + int res = BN_bn2binpad(r, raw_signature.data(), component_size); + if (res != component_size) { + LOGE("Failed to serialize R component: result = %d, component_size = %d", + res, component_size); + return {}; + } + // Serialize S. + res = BN_bn2binpad(s, &raw_signature[component_size], component_size); + if (res != component_size) { + LOGE("Failed to serialize S component: result = %d, component_size = %d", + res, component_size); + return {}; + } + return raw_signature; +} + OEMCryptoResult EccPrivateKey::DeriveSessionKey( const EccPublicKey& public_key, uint8_t* session_key, size_t* session_key_size) const { @@ -1046,9 +1279,7 @@ bool EccPrivateKey::InitFromCurve(EccCurve curve) { return false; } curve_ = curve; - // Required flags for IETF compliance. - EC_KEY_set_asn1_flag(key.get(), OPENSSL_EC_NAMED_CURVE); - EC_KEY_set_conv_form(key.get(), POINT_CONVERSION_UNCOMPRESSED); + SetIetfComplianceFlags(key.get()); key_ = key.release(); return true; } diff --git a/oemcrypto/util/src/oemcrypto_oem_cert.cpp b/oemcrypto/util/src/oemcrypto_oem_cert.cpp index 3f0bb69..619ad68 100644 --- a/oemcrypto/util/src/oemcrypto_oem_cert.cpp +++ b/oemcrypto/util/src/oemcrypto_oem_cert.cpp @@ -202,14 +202,12 @@ OEMCryptoResult OemCertificate::GetPublicCertificate( return OEMCrypto_ERROR_INVALID_CONTEXT; } const std::vector& cert_data = public_cert_->cert_data(); - if (*public_cert_length < cert_data.size()) { + if (public_cert == nullptr || *public_cert_length < cert_data.size()) { *public_cert_length = cert_data.size(); return OEMCrypto_ERROR_SHORT_BUFFER; } *public_cert_length = cert_data.size(); - if (public_cert != nullptr) { - memcpy(public_cert, cert_data.data(), cert_data.size()); - } + memcpy(public_cert, cert_data.data(), cert_data.size()); return OEMCrypto_SUCCESS; } diff --git a/oemcrypto/util/src/oemcrypto_rsa_key.cpp b/oemcrypto/util/src/oemcrypto_rsa_key.cpp index b700a68..bd8ba7b 100644 --- a/oemcrypto/util/src/oemcrypto_rsa_key.cpp +++ b/oemcrypto/util/src/oemcrypto_rsa_key.cpp @@ -806,14 +806,12 @@ OEMCryptoResult RsaPublicKey::EncryptOaep(const uint8_t* message, } // Step 2: Copy encrypted data key. const size_t enc_size = static_cast(enc_res); - if (*enc_message_length < enc_size) { + if (enc_message == nullptr || *enc_message_length < enc_size) { *enc_message_length = enc_size; return OEMCrypto_ERROR_SHORT_BUFFER; } *enc_message_length = enc_size; - if (enc_message != nullptr) { - memcpy(enc_message, encrypt_buffer.data(), enc_size); - } + memcpy(enc_message, encrypt_buffer.data(), enc_size); return OEMCrypto_SUCCESS; } diff --git a/oemcrypto/util/test/oemcrypto_ecc_key_unittest.cpp b/oemcrypto/util/test/oemcrypto_ecc_key_unittest.cpp index 12838e5..827d755 100644 --- a/oemcrypto/util/test/oemcrypto_ecc_key_unittest.cpp +++ b/oemcrypto/util/test/oemcrypto_ecc_key_unittest.cpp @@ -171,6 +171,104 @@ TEST_P(OEMCryptoEccKeyTest, SerializeAndReloadPublicKey) { } } +// Checks that a public and private key can be serialized as a SEC 1 +// key point, and that a public key can be reloaded from the key point. +// Uses the default option for point compression. +TEST_P(OEMCryptoEccKeyTest, SerializeAndReloadAsPublicKeySec1KeyPoint) { + auto pub_key = key_->MakePublicKey(); + ASSERT_TRUE(pub_key); + + const std::vector key_point_from_priv = + key_->SerializeAsPublicSec1KeyPoint(); + ASSERT_FALSE(key_point_from_priv.empty()) + << "Failed to serialize from private key"; + const std::vector key_point_from_pub = + pub_key->SerializeAsSec1KeyPoint(); + ASSERT_FALSE(key_point_from_pub.empty()) + << "Failed to serialize from public key"; + + // Check that both are equal. + EXPECT_EQ(key_point_from_priv, key_point_from_pub); + + // Reload new public key from key point data. + auto loaded_key = + EccPublicKey::LoadKeyPoint(key_->curve(), key_point_from_priv); + ASSERT_TRUE(loaded_key) << "Failed to load public key from key point"; + + const std::vector key_point_from_loaded = + loaded_key->SerializeAsSec1KeyPoint(); + ASSERT_FALSE(key_point_from_loaded.empty()) + << "Failed to serialize from loaded key"; + + EXPECT_EQ(key_point_from_priv, key_point_from_loaded); +} + +// Same as above, except explicitly serializes key point in its +// compressed form. +TEST_P(OEMCryptoEccKeyTest, + SerializeAndReloadAsPublicKeySec1KeyPointCompressed) { + constexpr bool kCompressed = true; + auto pub_key = key_->MakePublicKey(); + ASSERT_TRUE(pub_key); + + const std::vector key_point_from_priv = + key_->SerializeAsPublicSec1KeyPoint(kCompressed); + ASSERT_FALSE(key_point_from_priv.empty()) + << "Failed to serialize from private key"; + const std::vector key_point_from_pub = + pub_key->SerializeAsSec1KeyPoint(kCompressed); + ASSERT_FALSE(key_point_from_pub.empty()) + << "Failed to serialize from public key"; + + // Check that both are equal. + EXPECT_EQ(key_point_from_priv, key_point_from_pub); + + // Reload new public key from key point data. + auto loaded_key = + EccPublicKey::LoadKeyPoint(key_->curve(), key_point_from_priv); + ASSERT_TRUE(loaded_key) << "Failed to load public key from key point"; + + const std::vector key_point_from_loaded = + loaded_key->SerializeAsSec1KeyPoint(kCompressed); + ASSERT_FALSE(key_point_from_loaded.empty()) + << "Failed to serialize from loaded key"; + + EXPECT_EQ(key_point_from_priv, key_point_from_loaded); +} + +// Same as above, except explicitly serializes key point in its +// uncompressed form. +TEST_P(OEMCryptoEccKeyTest, + SerializeAndReloadAsPublicKeySec1KeyPointUncompressed) { + constexpr bool kUncompressed = false; + auto pub_key = key_->MakePublicKey(); + ASSERT_TRUE(pub_key); + + const std::vector key_point_from_priv = + key_->SerializeAsPublicSec1KeyPoint(kUncompressed); + ASSERT_FALSE(key_point_from_priv.empty()) + << "Failed to serialize from private key"; + const std::vector key_point_from_pub = + pub_key->SerializeAsSec1KeyPoint(kUncompressed); + ASSERT_FALSE(key_point_from_pub.empty()) + << "Failed to serialize from public key"; + + // Check that both are equal. + EXPECT_EQ(key_point_from_priv, key_point_from_pub); + + // Reload new public key from key point data. + auto loaded_key = + EccPublicKey::LoadKeyPoint(key_->curve(), key_point_from_priv); + ASSERT_TRUE(loaded_key) << "Failed to load public key from key point"; + + const std::vector key_point_from_loaded = + loaded_key->SerializeAsSec1KeyPoint(kUncompressed); + ASSERT_FALSE(key_point_from_loaded.empty()) + << "Failed to serialize from loaded key"; + + EXPECT_EQ(key_point_from_priv, key_point_from_loaded); +} + // Checks that the ECC signature generating API operates similar to // existing signature generation functions. TEST_P(OEMCryptoEccKeyTest, GenerateSignature) { @@ -194,12 +292,14 @@ TEST_P(OEMCryptoEccKeyTest, GenerateSignature) { EXPECT_LE(signature_size, key_->SignatureSize()); } -// Checks that ECC signatures can be verified by an ECC public key. +// Checks that ECC signatures (ASN.1 DER encoded ECDSA-Sig-Value) can +// be verified by an ECC public key. TEST_P(OEMCryptoEccKeyTest, VerifySignature) { const std::vector message = RandomData(kMessageSize); ASSERT_FALSE(message.empty()) << "CdmRandom failed"; const std::vector signature = key_->GenerateSignature(message); + ASSERT_FALSE(signature.empty()); std::unique_ptr pub_key = key_->MakePublicKey(); ASSERT_TRUE(pub_key); @@ -217,6 +317,32 @@ TEST_P(OEMCryptoEccKeyTest, VerifySignature) { pub_key->VerifySignature(message, bad_signature)); } +// Checks that ECC raw signatures (concatinated same-width-big-endian +// encoded signature point values r and s) can be verified by an ECC +// public key. +TEST_P(OEMCryptoEccKeyTest, VerifyRawSignature) { + const std::vector message = RandomData(kMessageSize); + ASSERT_FALSE(message.empty()) << "CdmRandom failed"; + + const std::vector signature = key_->GenerateRawSignature(message); + ASSERT_FALSE(signature.empty()); + + std::unique_ptr pub_key = key_->MakePublicKey(); + ASSERT_TRUE(pub_key); + + EXPECT_EQ(OEMCrypto_SUCCESS, pub_key->VerifyRawSignature(message, signature)); + + // Check with different message. + const std::vector message_two = RandomData(kMessageSize); + EXPECT_EQ(OEMCrypto_ERROR_SIGNATURE_FAILURE, + pub_key->VerifyRawSignature(message_two, signature)); + + // Check with bad signature. + const std::vector bad_signature = RandomData(signature.size()); + EXPECT_EQ(OEMCrypto_ERROR_SIGNATURE_FAILURE, + pub_key->VerifyRawSignature(message, bad_signature)); +} + // Verifies the session key exchange protocol used by the licensing // server. TEST_P(OEMCryptoEccKeyTest, DeriveSessionKey) { diff --git a/util/src/string_conversions.cpp b/util/src/string_conversions.cpp index 37ac2ef..0470d3a 100644 --- a/util/src/string_conversions.cpp +++ b/util/src/string_conversions.cpp @@ -32,12 +32,12 @@ const char kBase64SafeCodes[] = // Decodes a single Base64 encoded character into its 6-bit value. // The provided |codes| must be a Base64 character map. -int DecodeBase64Char(char c, const char* codes) { +int32_t DecodeBase64Char(char c, const char* codes) { const char* c_in_codes = strchr(codes, c); if (c_in_codes == nullptr) return -1; const uintptr_t c_in_codes_int = reinterpret_cast(c_in_codes); const uintptr_t codes_int = reinterpret_cast(codes); - return static_cast(c_in_codes_int - codes_int); + return static_cast(c_in_codes_int - codes_int); } bool DecodeHexChar(char ch, uint8_t* digit) { @@ -124,7 +124,7 @@ std::vector Base64DecodeInternal(const char* encoded, size_t length, break; } - const int decoded = DecodeBase64Char(encoded[i], codes); + const int32_t decoded = DecodeBase64Char(encoded[i], codes); if (decoded < 0) { LOGE("base64Decode failed"); return std::vector(); @@ -167,8 +167,8 @@ std::vector a2b_hex(const std::string& byte) { } for (size_t i = 0; i < count / 2; ++i) { - unsigned char msb = 0; // most significant 4 bits - unsigned char lsb = 0; // least significant 4 bits + uint8_t msb = 0; // most significant 4 bits + uint8_t lsb = 0; // least significant 4 bits if (!DecodeHexChar(byte[i * 2], &msb) || !DecodeHexChar(byte[i * 2 + 1], &lsb)) { LOGE("Invalid hex value %c%c at index %zu", byte[i * 2], byte[i * 2 + 1], @@ -219,7 +219,7 @@ std::string unlimited_b2a_hex(const std::string& byte) { } std::string HexEncode(const uint8_t* in_buffer, size_t size) { - constexpr unsigned int kMaxSafeSize = 2048; + constexpr size_t kMaxSafeSize = 2048; if (size > kMaxSafeSize) size = kMaxSafeSize; return UnlimitedHexEncode(in_buffer, size); } @@ -229,7 +229,7 @@ std::string UnlimitedHexEncode(const uint8_t* in_buffer, size_t size) { if (size == 0) return ""; // Each input byte creates two output hex characters. std::string out_buffer(size * 2, '\0'); - for (unsigned int i = 0; i < size; ++i) { + for (size_t i = 0; i < size; ++i) { char byte = in_buffer[i]; out_buffer[(i << 1)] = kHexChars[(byte >> 4) & 0xf]; out_buffer[(i << 1) + 1] = kHexChars[byte & 0xf]; @@ -331,7 +331,7 @@ int64_t htonll64(int64_t x) { } // Encode unsigned integer into a big endian formatted string -std::string EncodeUint32(unsigned int u) { +std::string EncodeUint32(uint32_t u) { std::string s; s.push_back((u >> 24) & 0xFF); s.push_back((u >> 16) & 0xFF);