Source release 14.0.0

This commit is contained in:
John W. Bruce
2018-05-16 17:35:40 -07:00
parent 31381a1311
commit 3ab70cec4e
2053 changed files with 1585838 additions and 4614 deletions

View File

@@ -6,9 +6,9 @@
* Reference APIs needed to support Widevine's crypto algorithms.
*
* See the document "WV Modular DRM Security Integration Guide for Common
* Encryption (CENC) -- version 13" for a description of this API. You
* Encryption (CENC) -- version 14" for a description of this API. You
* can find this document in the widevine repository as
* docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v13.pdf
* docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v14.pdf
* Changes between different versions of this API are documented in the files
* docs/Widevine_Modular_DRM_Version_*_Delta.pdf
*
@@ -79,6 +79,8 @@ typedef enum OEMCryptoResult {
OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE = 48,
OEMCrypto_ERROR_ENTRY_IN_USE = 49,
OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE = 50, // Reserved. Do not use.
OEMCrypto_KEY_NOT_LOADED = 51,
OEMCrypto_KEY_NOT_ENTITLED = 52,
} OEMCryptoResult;
/*
@@ -144,7 +146,7 @@ typedef struct {
} buffer;
} OEMCrypto_DestBufferDesc;
/** OEMCryptoCipherMode is used in LoadKeys to prepare a key for either CTR
/** OEMCryptoCipherMode is used in SelectKey to prepare a key for either CTR
* decryption or CBC decryption.
*/
typedef enum OEMCryptoCipherMode {
@@ -152,6 +154,14 @@ typedef enum OEMCryptoCipherMode {
OEMCrypto_CipherMode_CBC,
} OEMCryptoCipherMode;
/** OEMCrypto_LicenseType is used in LoadKeys to indicate if the key objects
* are for content keys, or for entitlement keys.
*/
typedef enum OEMCrypto_LicenseType {
OEMCrypto_ContentLicense = 0,
OEMCrypto_EntitlementLicense = 1
} OEMCrypto_LicenseType;
/*
* OEMCrypto_KeyObject
* Points to the relevant fields for a content key. The fields are extracted
@@ -170,8 +180,6 @@ typedef enum OEMCryptoCipherMode {
* key_control field.
* key_control - the key control block. It is encrypted (AES-128-CBC) with
* the content key from the key_data field.
* cipher_mode - whether the key should be prepared for CTR mode or CBC mode
* when used in later calls to DecryptCENC.
*
* The memory for the OEMCrypto_KeyObject fields is allocated and freed
* by the caller of OEMCrypto_LoadKeys().
@@ -184,9 +192,31 @@ typedef struct {
size_t key_data_length;
const uint8_t* key_control_iv;
const uint8_t* key_control;
OEMCryptoCipherMode cipher_mode;
} OEMCrypto_KeyObject;
/*
* OEMCrypto_EntitledContentKeyObject
* Contains encrypted content key data for loading into the sessions keytable.
* The content key data is encrypted using AES-256-CBC encryption, with PKCS#7
* padding.
* entitlement_key_id - entitlement key id to be matched to key table.
* entitlement_key_id_length - length of entitlment_key_id in bytes (1 to 16).
* content_key_id - content key id to be loaded into key table.
* content_key_id_length - length of content key id in bytes (1 to 16).
* key_data_iv - the IV for performing AES-256-CBC decryption of the key data.
* key_data - encrypted content key data.
* key_data_length - length of key_data - 16 or 32 depending on intended use.
*/
typedef struct {
const uint8_t* entitlement_key_id;
size_t entitlement_key_id_length;
const uint8_t* content_key_id;
size_t content_key_id_length;
const uint8_t* content_key_data_iv;
const uint8_t* content_key_data;
size_t content_key_data_length;
} OEMCrypto_EntitledContentKeyObject;
/*
* OEMCrypto_KeyRefreshObject
* Points to the relevant fields for renewing a content key. The fields are
@@ -323,6 +353,16 @@ typedef enum OEMCrypto_ProvisioningMethod {
#define OEMCrypto_Hash_Not_Supported 0
#define OEMCrypto_HMAC_Clear_Buffer 1
/*
* Return values from OEMCrypto_GetAnalogOutputFlags.
*/
#define OEMCrypto_No_Analog_Output 0x0
#define OEMCrypto_Supports_Analog_Output 0x1
#define OEMCrypto_Can_Disable_Analog_Ouptput 0x2
#define OEMCrypto_Supports_CGMS_A 0x4
// Unknown_Analog_Output is used only for backwards compatibility.
#define OEMCrypto_Unknown_Analog_Output (1<<31)
/*
* Obfuscation Renames.
*/
@@ -342,7 +382,7 @@ typedef enum OEMCrypto_ProvisioningMethod {
#define OEMCrypto_GenerateNonce _oecc14
#define OEMCrypto_LoadKeys_V8 _oecc15
#define OEMCrypto_RefreshKeys _oecc16
#define OEMCrypto_SelectKey _oecc17
#define OEMCrypto_SelectKey_V13 _oecc17
#define OEMCrypto_RewrapDeviceRSAKey _oecc18
#define OEMCrypto_LoadDeviceRSAKey _oecc19
#define OEMCrypto_GenerateRSASignature_V8 _oecc20
@@ -367,7 +407,7 @@ typedef enum OEMCrypto_ProvisioningMethod {
#define OEMCrypto_IsAntiRollbackHwPresent _oecc39
#define OEMCrypto_CopyBuffer _oecc40
#define OEMCrypto_QueryKeyControl _oecc41
#define OEMCrypto_LoadTestKeybox _oecc42
#define OEMCrypto_LoadTestKeybox_V13 _oecc42
#define OEMCrypto_ForceDeleteUsageEntry _oecc43
#define OEMCrypto_GetHDCPCapability _oecc44
#define OEMCrypto_LoadTestRSAKey _oecc45
@@ -381,7 +421,7 @@ typedef enum OEMCrypto_ProvisioningMethod {
#define OEMCrypto_IsSRMUpdateSupported _oecc53
#define OEMCrypto_GetCurrentSRMVersion _oecc54
#define OEMCrypto_LoadSRM _oecc55
#define OEMCrypto_LoadKeys _oecc56
#define OEMCrypto_LoadKeys_V13 _oecc56
#define OEMCrypto_RemoveSRM _oecc57
#define OEMCrypto_CreateUsageTableHeader _oecc61
#define OEMCrypto_LoadUsageTableHeader _oecc62
@@ -393,7 +433,11 @@ typedef enum OEMCrypto_ProvisioningMethod {
#define OEMCrypto_MoveEntry _oecc68
#define OEMCrypto_CopyOldUsageEntry _oecc69
#define OEMCrypto_CreateOldUsageEntry _oecc70
#define OEMCrypto_GetAnalogOutputFlags _oecc71
#define OEMCrypto_LoadTestKeybox _oecc78
#define OEMCrypto_LoadEntitledContentKeys _oecc79
#define OEMCrypto_SelectKey _oecc81
#define OEMCrypto_LoadKeys _oecc82
/*
* OEMCrypto_Initialize
@@ -694,6 +738,17 @@ OEMCryptoResult OEMCrypto_GenerateSignature(OEMCrypto_SESSION session,
* Refer to document "Widevine Modular DRM Security Integration Guide for
* CENC" for details.
*
* If the parameter license_type is OEMCrypto_ContentLicense, then the fields
* key_id and key_data in an OEMCrypto_KeyObject are loaded in to the
* content_key_id and content_key_data fields of the key table entry. In
* this case, entitlement key ids and entitlement key data is left blank.
*
* If the parameter license_type is OEMCrypto_EntitlementLicense, then the
* fields key_id and key_data in an OEMCrypto_KeyObject are loaded in to the
* entitlement_key_id and entitlement_key_data fields of the key table entry.
* In this case, content key ids and content key data will be loaded later
* with a call to OEMCrypto_LoadEntitledContentKeys().
*
* OEMCrypto may assume that the key_id_length is at most 16. However,
* OEMCrypto shall correctly handle key id lengths from 1 to 16 bytes.
*
@@ -804,6 +859,11 @@ OEMCryptoResult OEMCrypto_GenerateSignature(OEMCrypto_SESSION session,
* key_array (in) - set of keys to be installed.
* pst (in) - the Provider Session Token.
* pst_length (in) - the length of pst.
* srm_restriction_data (in) - optional data specifying the minimum SRM
* version.
* license_type (in) - specifies if the license contains content keys or
* entitlement keys.
*
* Threading:
* This function may be called simultaneously with functions on other
@@ -826,14 +886,75 @@ OEMCryptoResult OEMCrypto_GenerateSignature(OEMCrypto_SESSION session,
* larger than the supported size.
*
* Version:
* This method changed in API version 11.
* This method changed in API version 14.
*/
OEMCryptoResult OEMCrypto_LoadKeys(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
const uint8_t* signature, size_t signature_length,
const uint8_t* enc_mac_keys_iv, const uint8_t* enc_mac_keys,
size_t num_keys, const OEMCrypto_KeyObject* key_array, const uint8_t* pst,
size_t pst_length, const uint8_t* srm_requirement);
size_t pst_length, const uint8_t* srm_requirement,
OEMCrypto_LicenseType license_type);
/*
* OEMCrypto_LoadEntitledContentKeys
*
* Description:
* Load content keys into a session which already has entitlement
* keys loaded. This function will only be called for a session after a call
* to OEMCrypto_LoadKeys with the parameter type license_type equal to
* OEMCrypto_EntitlementLicense. This function may be called multiple times
* for the same session.
*
* If the session does not have license_type equal to
* OEMCrypto_EntitlementLicense, return OEMCrypto_ERROR_INVALID_CONTEXT and
* perform no work.
*
* For each key object in key_array, OEMCrypto shall look up the entry in the
* key table with the corresponding entitlement_key_id.
* 1) If no entry is found, return OEMCrypto_KEY_NOT_ENTITLED.
* 2) If the entry already has a content_key_id and content_key_data, that id
* and data are erased.
* 3) The content_key_id from the key_array is copied to the entry's
* content_key_id.
* 4) The content_key_data decrypted using the entitlement_key_data as a key
* for AES-256-CBC with an IV of content_key_data_iv, and using PKCS#7
* padding. Notice that the entitlement key will be an AES 256 bit key.
* The clear content key data will be stored in the entry's
* content_key_data.
*
* Entries in the key table that do not correspond to anything in the
* key_array are not modified or removed.
*
* For devices that use a hardware key ladder, it may be more appropriate to
* store the encrypted content key data in the key table, and defer decrypting
* it until the function SelectKey is called.
*
* Parameters:
* session (in) - handle for the session to be used.
* num_keys (in) - number of keys present.
* key_array (in) - set of key updates.
*
* Returns
* OEMCrypto_SUCCESS success
* OEMCrypto_ERROR_INVALID_SESSION
* OEMCrypto_ERROR_INVALID_CONTEXT
* OEMCrypto_ERROR_INSUFFICIENT_RESOURCES
* OEMCrypto_ERROR_UNKNOWN_FAILURE
* OEMCrypto_KEY_NOT_ENTITLED
* Threading
*
* This function may be called simultaneously with functions on other
* sessions, but not with other functions on this session.
*
* Version
* This method is new in API version 14.
*/
OEMCryptoResult OEMCrypto_LoadEntitledContentKeys(
OEMCrypto_SESSION session,
size_t num_keys,
const OEMCrypto_EntitledContentKeyObject* key_array);
/*
* OEMCrypto_RefreshKeys
@@ -872,6 +993,19 @@ OEMCryptoResult OEMCrypto_LoadKeys(
* this case, key_control_iv will also be null and the control block will not
* be encrypted.
*
* If the session's license_type is OEMCrypto_ContentLicense, and the
* KeyRefreshObject's key_id is not null, then the entry in the
* keytable with the matching content_key_id is updated.
*
* If the session's license_type is OEMCrypto_EntitlementLicense, and the
* KeyRefreshObject's key_id is not null, then the entry in the keytable with
* the matching entitlment_key_id is updated.
*
* If the key_id is not null, and no matching entry is found in the key
* table, then return OEMCrypto_KEY_NOT_LOADED.
*
* Aside from the key's duration, no other values in the key control block
* should be updated by this function.
*
* Verification:
* The following checks should be performed. If any check fails, an error is
@@ -920,6 +1054,7 @@ OEMCryptoResult OEMCrypto_LoadKeys(
* OEMCrypto_ERROR_INSUFFICIENT_RESOURCES
* OEMCrypto_ERROR_UNKNOWN_FAILURE
* OEMCrypto_ERROR_BUFFER_TOO_LARGE
* OEMCrypto_KEY_NOT_LOADED
*
* Buffer Sizes
* OEMCrypto shall support message sizes of at least 8 KiB.
@@ -964,8 +1099,8 @@ OEMCryptoResult OEMCrypto_RefreshKeys(
* OEMCrypto_ERROR_NO_CONTENT_KEY.
*
* Parameters
* key_id (in) - The unique id of the key of interest.
* key_id_length (in) - The length of key_id, in bytes. From 1 to 16
* content_key_id (in) - The unique id of the content key of interest.
* content_key_id_length (in) - The length of key_id, in bytes. From 1 to 16
* inclusive.
* key_control_block(out) - A caller-owned buffer.
* key_control_block_length (in/out) - The length of key_control_block buffer.
@@ -991,8 +1126,8 @@ OEMCryptoResult OEMCrypto_RefreshKeys(
* This method is added in API version 10.
*/
OEMCryptoResult OEMCrypto_QueryKeyControl(OEMCrypto_SESSION session,
const uint8_t* key_id,
size_t key_id_length,
const uint8_t* content_key_id,
size_t content_key_id_length,
uint8_t* key_control_block,
size_t* key_control_block_length);
@@ -1018,36 +1153,49 @@ OEMCryptoResult OEMCrypto_QueryKeyControl(OEMCrypto_SESSION session,
* Step 3: use the latched content key to decrypt (AES-128-CTR) buffers
* passed in via OEMCrypto_DecryptCENC(). If the key is 256 bits it
* will be used for OEMCrypto_Generic_Sign or
* OEMCrypto_Generic_Verify as specified in the key control
* block. Continue to use this key until OEMCrypto_SelectKey() is
* called again, or until OEMCrypto_CloseSession() is called.
* OEMCrypto_Generic_Verify as specified in the key control block.
* If the key will be used with OEMCrypto_Generic_Encrypt or
* OEMCrypto_Generic_Decrypt, the cipher mode will always be
* OEMCrypto_CipherMode_CBC. Continue to use this key until
* OEMCrypto_SelectKey() is called again, or until
* OEMCrypto_CloseSession() is called.
*
* Verification:
* The following checks should be performed if is_encrypted is true. If any
* check fails, an error is returned, and no decryption is performed.
*
* 1. If the current key's control block has a nonzero Duration field, then
* 1. If the key id is not found in the keytable for this session, then the
* key state is not changed and OEMCrypto shall return
* OEMCrypto_KEY_NOT_LOADED.
*
* 2. If the current key's control block has a nonzero Duration field, then
* the API shall verify that the duration is greater than the session's
* elapsed time clock before the key is used. OEMCrypto may return
* OEMCrypto_ERROR_KEY_EXPIRED from OEMCrypto_SelectKey, or SelectKey may
* return success from select key and the decrypt or generic crypto call will
* return OEMCrypto_ERROR_KEY_EXPIRED.
* 2. If the key control block has the bit Disable_Analog_Output set, then
*
* 3. If the key control block has the bit Disable_Analog_Output set, then
* the device should disable analog video output. If the device has analog
* output that cannot be disabled, then the key is not selected, and
* OEMCrypto_ERROR_ANALOG_OUTPUT is returned.
* 3. If the key control block has HDCP required, and the device cannot
*
* 4. If the key control block has HDCP required, and the device cannot
* enforce HDCP, then the key is not selected, and
* OEMCrypto_ERROR_INSUFFICIENT_HDCP is returned.
* 4. If the key control block has a nonzero value for HDCP_Version, and the
*
* 5. If the key control block has a nonzero value for HDCP_Version, and the
* device cannot enforce at least that version of HDCP, then the key is not
* selected, and OEMCrypto_ERROR_INSUFFICIENT_HDCP is returned.
*
* Parameters:
* session (in) - crypto session identifier
* key_id (in) - pointer to the Key ID
* key_id_length (in) - length of the Key ID in bytes. From 1 to 16
* content_key_id (in) - pointer to the Content Key ID
* content_key_id_length (in) - length of the Key ID in bytes. From 1 to 16
* inclusive.
* cipher_mode (in) - whether the key should be prepared for CTR mode or CBC
* mode when used in later calls to DecryptCENC. This
* should be ignored when the key is used for Generic Crypto calls.
*
* Threading:
* This function may be called simultaneously with functions on other
@@ -1066,13 +1214,15 @@ OEMCryptoResult OEMCrypto_QueryKeyControl(OEMCrypto_SESSION session,
* OEMCrypto_ERROR_KEY_EXPIRED
* OEMCrypto_ERROR_ANALOG_OUTPUT
* OEMCrypto_ERROR_INSUFFICIENT_HDCP
* OEMCrypto_KEY_NOT_LOADED
*
* Version:
* This method changed in API version 8.
* This method changed in API version 14.
*/
OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session,
const uint8_t* key_id,
size_t key_id_length);
const uint8_t* content_key_id,
size_t content_key_id_length,
OEMCryptoCipherMode cipher_mode);
/*
* OEMCrypto_DecryptCENC
@@ -1132,7 +1282,7 @@ OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session,
* change the status to "active" and set the time_of_first_decrypt.
*
* The decryption mode, either OEMCrypto_CipherMode_CTR or
* OEMCrypto_CipherMode_CBC, was specified in the call to OEMCrypto_LoadKeys.
* OEMCrypto_CipherMode_CBC, was specified in the call to OEMCrypto_SelectKey.
* The encryption pattern is specified by the fields in the parameter
* pattern. A description of partial encryption patterns can be found in the
* document Draft International Standard ISO/IEC DIS 23001-7. Search for the
@@ -1502,7 +1652,8 @@ OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(OEMCrypto_SESSION session,
* The test keybox can be found in the reference implementation.
*
* Parameters
* none
* buffer (in) - pointer to memory containing test keybox, in binary form.
* length (in) - length of the buffer, in bytes.
*
* Returns
* OEMCrypto_SUCCESS success
@@ -1515,7 +1666,7 @@ OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(OEMCrypto_SESSION session,
* Version
* This method is added in API version 10.
*/
OEMCryptoResult OEMCrypto_LoadTestKeybox();
OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t *buffer, size_t length);
/*
* OEMCrypto_IsKeyboxValid
@@ -1709,7 +1860,9 @@ OEMCryptoResult OEMCrypto_GetRandom(uint8_t* randomData, size_t dataLength);
*
* Parameters:
* session (in) - crypto session identifier.
* unaligned_nonce (in) - The nonce provided in the provisioning response.
* unaligned_nonce (in) - The nonce provided in the provisioning
* - response. This points to an uint32_t that might
* - not be aligned to a word boundary.
* encrypted_message_key (in) - message_key encrypted by private key
* - from OEM cert.
* encrypted_message_key_length (in) - length of encrypted_message_key in
@@ -1842,7 +1995,9 @@ OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30(
* - signature for message, received from the
* - provisioning server.
* signature_length (in) - length of the signature, in bytes.
* unaligned_nonce (in) - The nonce provided in the provisioning response.
* unaligned_nonce (in) - The nonce provided in the provisioning
* - response. This points to an uint32_t that might
* - not be aligned to a word boundary.
* enc_rsa_key (in) - Encrypted device private RSA key received from
* - the provisioning server. Format is PKCS#8
* - binary DER encoded, encrypted with the derived
@@ -2329,8 +2484,13 @@ OEMCryptoResult OEMCrypto_GetMaxNumberOfSessions(size_t* max);
* High end devices should also support 3072 bit RSA keys. Devices that are
* cast receivers should also support RSA cast receiver certificates.
*
* Parameters:
* none
* Beginning with OEMCrypto v14, the provisioning server may deliver to the
* device an RSA key that uses the Carmichael totient. This does not change
* the RSA algorithm -- however the product of the private and public keys is
* not necessarily the Euler number phi. OEMCrypto should not reject such
* keys.
*
* Parameters: none
*
* Threading:
* This function may be called simultaneously with any other functions.
@@ -2521,6 +2681,7 @@ OEMCryptoResult OEMCrypto_Generic_Decrypt(
* OEMCrypto_ERROR_INSUFFICIENT_RESOURCES
* OEMCrypto_ERROR_UNKNOWN_FAILURE
* OEMCrypto_ERROR_BUFFER_TOO_LARGE
* OEMCrypto_ERROR_NOT_IMPLEMENTED
*
* Buffer Sizes
* OEMCrypto shall support buffer sizes of at least 100 KiB for generic
@@ -2586,6 +2747,7 @@ OEMCryptoResult OEMCrypto_Generic_Sign(OEMCrypto_SESSION session,
* OEMCrypto_ERROR_INSUFFICIENT_RESOURCES
* OEMCrypto_ERROR_UNKNOWN_FAILURE
* OEMCrypto_ERROR_BUFFER_TOO_LARGE
* OEMCrypto_ERROR_NOT_IMPLEMENTED
*
* Buffer Sizes
* OEMCrypto shall support buffer sizes of at least 100 KiB for generic
@@ -3261,6 +3423,38 @@ OEMCryptoResult OEMCrypto_CopyOldUsageEntry(OEMCrypto_SESSION session,
const uint8_t*pst,
size_t pst_length);
/*
* OEMCrypto_GetAnalogOutputFlags
*
* Description:
* Returns whether the device supports analog output or not. This
* information will be sent to the license server, and may be used to
* determine the type of license allowed. This function is for reporting
* only. It is paired with the key control block flags Disable_Analog_Output
* and CGMS.
*
* Parameters:
* none.
*
* Threading:
* This function will not be called simultaneously with any session functions.
*
* Returns:
* Returns a bitwise OR of the following flags.
* 0x0 = OEMCrypto_No_Analog_Output -- the device has no analog output.
* 0x1 = OEMCrypto_Supports_Analog_Output - the device does have analog
* output.
* 0x2 = OEMCrypto_Can_Disable_Analog_Ouptput - the device does have analog
* output, but it will disable analog output if required by the key
* control block.
* 0x4 = OEMCrypto_Supports_CGMS_A - the device supports signaling 2-bit
* CGMS-A, if required by the key control block
*
* Version:
* This method is new in API version 14.
*/
uint32_t OEMCrypto_GetAnalogOutputFlags();
#ifdef __cplusplus
}
#endif

View File

@@ -1,22 +0,0 @@
// Copyright 2017 Google Inc. All Rights Reserved
/*********************************************************************
* level3_file_system_factory.h
*
* Creates a single OEMCrypto_Level3FileSystem.
*********************************************************************/
#ifndef LEVEL3_FILE_SYSTEM_FACTORY_H_
#define LEVEL3_FILE_SYSTEM_FACTORY_H_
#include "level3_file_system.h"
namespace wvoec3 {
OEMCrypto_Level3FileSystem* createLevel3FileSystem();
void deleteLevel3FileSystem(OEMCrypto_Level3FileSystem* file_system);
}
#endif // LEVEL3_FILE_SYSTEM_FACTORY_H_

View File

@@ -1,146 +0,0 @@
// Copyright 2017 Google Inc. All Rights Reserved.
/*********************************************************************
* pst_report.h
*
* Reference APIs needed to support Widevine's crypto algorithms.
*********************************************************************/
#ifndef PST_REPORT_H_
#define PST_REPORT_H_
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "OEMCryptoCENC.h"
#include "string_conversions.h" // needed for htonll64.
namespace wvcdm {
class Unpacked_PST_Report {
public:
// This object does not own the buffer, and does not check that buffer
// is not null.
Unpacked_PST_Report(uint8_t *buffer) : buffer_(buffer) {}
// Copy and move semantics of this class is like that of a pointer.
Unpacked_PST_Report(const Unpacked_PST_Report& other) :
buffer_(other.buffer_) {}
Unpacked_PST_Report& operator=(const Unpacked_PST_Report& other) {
buffer_ = other.buffer_;
return *this;
}
size_t report_size() const {
return pst_length() + kraw_pst_report_size;
}
static size_t report_size(size_t pst_length) {
return pst_length + kraw_pst_report_size;
}
uint8_t status() const {
return static_cast<uint8_t>(* (buffer_ + kstatus_offset));
}
void set_status(uint8_t value) {
buffer_[kstatus_offset] = value;
}
uint8_t* signature() {
return buffer_ + ksignature_offset;
}
uint8_t clock_security_level() const {
return static_cast<uint8_t>(* (buffer_ + kclock_security_level_offset));
}
void set_clock_security_level(uint8_t value) {
buffer_[kclock_security_level_offset] = value;
}
uint8_t pst_length() const {
return static_cast<uint8_t>(* (buffer_ + kpst_length_offset));
}
void set_pst_length(uint8_t value) {
buffer_[kpst_length_offset] = value;
}
uint8_t padding() const {
return static_cast<uint8_t>(* (buffer_ + kpadding_offset));
}
void set_padding(uint8_t value) {
buffer_[kpadding_offset] = value;
}
// In host byte order.
int64_t seconds_since_license_received() const {
int64_t time;
memcpy(&time, buffer_ + kseconds_since_license_received_offset,
sizeof(int64_t));
return wvcdm::ntohll64(time);
}
// Parameter time is in host byte order.
void set_seconds_since_license_received(int64_t time) const {
time = wvcdm::ntohll64(time);
memcpy(buffer_ + kseconds_since_license_received_offset, &time,
sizeof(int64_t));
}
// In host byte order.
int64_t seconds_since_first_decrypt() const {
int64_t time;
memcpy(&time, buffer_ + kseconds_since_first_decrypt_offset,
sizeof(int64_t));
return wvcdm::ntohll64(time);
}
// Parameter time is in host byte order.
void set_seconds_since_first_decrypt(int64_t time) const {
time = wvcdm::ntohll64(time);
memcpy(buffer_ + kseconds_since_first_decrypt_offset, &time,
sizeof(int64_t));
}
// In host byte order.
int64_t seconds_since_last_decrypt() const {
int64_t time;
memcpy(&time, buffer_ + kseconds_since_last_decrypt_offset,
sizeof(int64_t));
return wvcdm::ntohll64(time);
}
// Parameter time is in host byte order.
void set_seconds_since_last_decrypt(int64_t time) const {
time = wvcdm::ntohll64(time);
memcpy(buffer_ + kseconds_since_last_decrypt_offset, &time,
sizeof(int64_t));
}
uint8_t* pst() {
return (buffer_ + kpst_offset);
}
private:
uint8_t *buffer_;
// Size of the PST_Report without the pst string.
static const size_t kraw_pst_report_size = 48;
static const size_t ksignature_offset = 0;
static const size_t kstatus_offset = 20;
static const size_t kclock_security_level_offset = 21;
static const size_t kpst_length_offset = 22;
static const size_t kpadding_offset = 23;
static const size_t kseconds_since_license_received_offset = 24;
static const size_t kseconds_since_first_decrypt_offset = 32;
static const size_t kseconds_since_last_decrypt_offset = 40;
static const size_t kpst_offset = 48;
};
} // namespace wvcdm
#endif // PST_REPORT_H_

View File

@@ -21,7 +21,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
Session s;
s.open();
s.GenerateDerivedKeysFromKeybox();
s.GenerateDerivedKeysFromKeybox(session_helper.keybox_);
static const uint32_t SignatureBufferMaxLength = size;
vector<uint8_t> signature(SignatureBufferMaxLength);

View File

@@ -140,6 +140,7 @@ std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) {
if (api_version < 11) FilterOut(&filter, "*API11*");
if (api_version < 12) FilterOut(&filter, "*API12*");
if (api_version < 13) FilterOut(&filter, "*API13*");
if (api_version < 14) FilterOut(&filter, "*API14*");
// Performance tests take a long time. Filter them out if they are not
// specifically requested.
if (filter.find("Performance") == std::string::npos) {
@@ -172,7 +173,7 @@ void DeviceFeatures::PickDerivedKey() {
}
if (uses_keybox) {
// If device uses a keybox, try to load the test keybox.
if (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_LoadTestKeybox()) {
if (OEMCrypto_ERROR_NOT_IMPLEMENTED != OEMCrypto_LoadTestKeybox(NULL, 0)) {
derive_key_method = LOAD_TEST_KEYBOX;
} else if (IsTestKeyboxInstalled()) {
derive_key_method = EXISTING_TEST_KEYBOX;
@@ -187,8 +188,8 @@ bool DeviceFeatures::IsTestKeyboxInstalled() {
size_t key_data_len = sizeof(key_data);
if (OEMCrypto_GetKeyData(key_data, &key_data_len) != OEMCrypto_SUCCESS)
return false;
if (key_data_len != kKeyboxDataSize) return false;
if (memcmp(key_data, &kKeybox[kKeyboxDataOffset], key_data_len)) return false;
if (key_data_len != sizeof(wvcdm_test_auth::kValidKeybox01.data_)) return false;
if (memcmp(key_data, wvcdm_test_auth::kValidKeybox01.data_, key_data_len)) return false;
uint8_t dev_id[128] = {0};
size_t dev_id_len = 128;
if (OEMCrypto_GetDeviceID(dev_id, &dev_id_len) != OEMCrypto_SUCCESS)
@@ -197,8 +198,8 @@ bool DeviceFeatures::IsTestKeyboxInstalled() {
// multiple '\0' characters at the end of the device id.
return 0 == strncmp(
reinterpret_cast<const char*>(dev_id),
reinterpret_cast<const char*>(&kKeybox[kKeyboxDeviceIdOffset]),
kKeyboxDeviceIdSize);
reinterpret_cast<const char*>(wvcdm_test_auth::kValidKeybox01.device_id_),
sizeof(wvcdm_test_auth::kValidKeybox01.device_id_));
}
void DeviceFeatures::FilterOut(std::string* current_filter,

View File

@@ -7,6 +7,8 @@
#include <arpa/inet.h> // needed for ntoh()
#include <openssl/aes.h>
#include <openssl/bio.h>
#include <openssl/cmac.h>
#include <openssl/err.h>
#include <openssl/hmac.h>
#include <openssl/pem.h>
@@ -17,6 +19,7 @@
#include <gtest/gtest.h>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
@@ -43,29 +46,20 @@ void PrintTo(const vector<uint8_t>& value, ostream* os) {
namespace {
int GetRandBytes(unsigned char* buf, int num) {
// returns 1 on success, -1 if not supported, or 0 if other failure.
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
return RAND_pseudo_bytes(buf, num);
#else
return RAND_bytes(buf, num);
#endif
}
#ifdef OPENSSL_IS_BORINGSSL
void DeleteX509Stack(STACK_OF(X509)* stack) {
sk_X509_pop_free(stack, X509_free);
}
typedef size_t X509Count;
#else
typedef int X509Count;
#endif
} // namespace
namespace wvoec {
// Increment counter for AES-CTR. The CENC spec specifies we increment only
// the low 64 bits of the IV counter, and leave the high 64 bits alone. This
// is different from the OpenSSL implementation, so we implement the CTR loop
// is different from the BoringSSL implementation, so we implement the CTR loop
// ourselves.
void ctr128_inc64(int64_t increaseBy, uint8_t* iv) {
ASSERT_NE(static_cast<void*>(NULL), iv);
@@ -77,19 +71,19 @@ void ctr128_inc64(int64_t increaseBy, uint8_t* iv) {
// Some compilers don't like the macro htonl within an ASSERT_EQ.
uint32_t htonl_fnc(uint32_t x) { return htonl(x); }
void dump_openssl_error() {
void dump_boringssl_error() {
while (unsigned long err = ERR_get_error()) {
char buffer[120];
ERR_error_string_n(err, buffer, sizeof(buffer));
cout << "openssl error -- " << buffer << "\n";
cout << "BoringSSL Error -- " << buffer << "\n";
}
}
template <typename T, void (*func)(T*)>
class openssl_ptr {
class boringssl_ptr {
public:
explicit openssl_ptr(T* p = NULL) : ptr_(p) {}
~openssl_ptr() {
explicit boringssl_ptr(T* p = NULL) : ptr_(p) {}
~boringssl_ptr() {
if (ptr_) func(ptr_);
}
T& operator*() const { return *ptr_; }
@@ -99,7 +93,7 @@ class openssl_ptr {
private:
T* ptr_;
CORE_DISALLOW_COPY_AND_ASSIGN(openssl_ptr);
CORE_DISALLOW_COPY_AND_ASSIGN(boringssl_ptr);
};
Session::Session()
@@ -111,8 +105,9 @@ Session::Session()
enc_key_(wvcdm::KEY_SIZE),
public_rsa_(0),
message_size_(sizeof(MessageData)),
num_keys_(4) { // Most tests only use 4 keys.
// Other tests will explicitly call set_num_keys.
num_keys_(4), // Most tests only use 4 keys.
// Other tests will explicitly call set_num_keys.
has_entitlement_license_(false) {
// Stripe the padded message.
for (size_t i = 0; i < sizeof(padded_message_.padding); i++) {
padded_message_.padding[i] = i % 0x100;
@@ -180,7 +175,53 @@ void Session::FillDefaultContext(vector<uint8_t>* mac_context,
"180120002a0c31383836373837343035000000000080");
}
void Session::GenerateDerivedKeysFromKeybox() {
void Session::DeriveKey(const uint8_t* key, const vector<uint8_t>& context,
int counter, vector<uint8_t>* out) {
ASSERT_FALSE(context.empty());
ASSERT_GE(4, counter);
ASSERT_NE(static_cast<void*>(NULL), out);
const EVP_CIPHER* cipher = EVP_aes_128_cbc();
CMAC_CTX* cmac_ctx = CMAC_CTX_new();
ASSERT_NE(static_cast<void*>(NULL), cmac_ctx);
ASSERT_EQ(1, CMAC_Init(cmac_ctx, key, wvcdm::KEY_SIZE, cipher, 0));
std::vector<uint8_t> message;
message.push_back(counter);
message.insert(message.end(), context.begin(), context.end());
ASSERT_EQ(1, CMAC_Update(cmac_ctx, &message[0], message.size()));
size_t reslen;
uint8_t res[128];
ASSERT_EQ(1, CMAC_Final(cmac_ctx, res, &reslen));
out->assign(res, res + reslen);
CMAC_CTX_free(cmac_ctx);
}
void Session::DeriveKeys(const uint8_t* master_key,
const vector<uint8_t>& mac_key_context,
const vector<uint8_t>& enc_key_context) {
// Generate derived key for mac key
std::vector<uint8_t> mac_key_part2;
DeriveKey(master_key, mac_key_context, 1, &mac_key_server_);
DeriveKey(master_key, mac_key_context, 2, &mac_key_part2);
mac_key_server_.insert(mac_key_server_.end(), mac_key_part2.begin(),
mac_key_part2.end());
DeriveKey(master_key, mac_key_context, 3, &mac_key_client_);
DeriveKey(master_key, mac_key_context, 4, &mac_key_part2);
mac_key_client_.insert(mac_key_client_.end(), mac_key_part2.begin(),
mac_key_part2.end());
// Generate derived key for encryption key
DeriveKey(master_key, enc_key_context, 1, &enc_key_);
}
void Session::GenerateDerivedKeysFromKeybox(
const wvcdm_test_auth::WidevineKeybox& keybox) {
GenerateNonce();
vector<uint8_t> mac_context;
vector<uint8_t> enc_context;
@@ -190,15 +231,7 @@ void Session::GenerateDerivedKeysFromKeybox() {
mac_context.size(), &enc_context[0],
enc_context.size()));
// Expected MAC and ENC keys generated from context strings
// with test keybox "installed".
mac_key_server_ = wvcdm::a2b_hex(
"3CFD60254786AF350B353B4FBB700AB382558400356866BA16C256BCD8C502BF");
mac_key_client_ = wvcdm::a2b_hex(
"A9DE7B3E4E199ED8D1FBC29CD6B4C772CC4538C8B0D3E208B3E76F2EC0FD6F47");
enc_key_ = wvcdm::a2b_hex("D0BFC35DA9E33436E81C4229E78CB9F4");
DeriveKeys(keybox.device_key_, mac_context, enc_context);
}
void Session::GenerateDerivedKeysFromSessionKey() {
@@ -217,13 +250,7 @@ void Session::GenerateDerivedKeysFromSessionKey() {
&mac_context[0], mac_context.size(), &enc_context[0],
enc_context.size()));
// Expected MAC and ENC keys generated from context strings
// with RSA certificate "installed".
mac_key_server_ = wvcdm::a2b_hex(
"1E451E59CB663DA1646194DD28880788ED8ED2EFF913CBD6A0D535D1D5A90381");
mac_key_client_ = wvcdm::a2b_hex(
"F9AAE74690909F2207B53B13307FCA096CA8C49CC6DFE3659873CB952889A74B");
enc_key_ = wvcdm::a2b_hex("CB477D09014D72C9B8DCE76C33EA43B3");
DeriveKeys(&session_key[0], mac_context, enc_context);
}
void Session::LoadTestKeys(const std::string& pst, bool new_mac_keys) {
@@ -237,7 +264,8 @@ void Session::LoadTestKeys(const std::string& pst, bool new_mac_keys) {
&signature_[0], signature_.size(),
encrypted_license().mac_key_iv,
encrypted_license().mac_keys, num_keys_,
key_array_, pst_ptr, pst.length(), NULL));
key_array_, pst_ptr, pst.length(), NULL,
OEMCrypto_ContentLicense));
// Update new generated keys.
memcpy(&mac_key_server_[0], license_.mac_keys, wvcdm::MAC_KEY_SIZE);
memcpy(&mac_key_client_[0], license_.mac_keys + wvcdm::MAC_KEY_SIZE,
@@ -247,11 +275,114 @@ void Session::LoadTestKeys(const std::string& pst, bool new_mac_keys) {
OEMCrypto_SUCCESS,
OEMCrypto_LoadKeys(session_id(), message_ptr(), message_size_,
&signature_[0], signature_.size(), NULL, NULL,
num_keys_, key_array_, pst_ptr, pst.length(), NULL));
num_keys_, key_array_, pst_ptr, pst.length(), NULL,
OEMCrypto_ContentLicense));
}
VerifyTestKeys();
}
void Session::LoadEnitlementTestKeys(const std::string& pst,
bool new_mac_keys,
OEMCryptoResult expected_sts) {
uint8_t* pst_ptr = NULL;
if (pst.length() > 0) {
pst_ptr = encrypted_license().pst;
}
if (new_mac_keys) {
ASSERT_EQ(expected_sts,
OEMCrypto_LoadKeys(session_id(), message_ptr(), message_size_,
&signature_[0], signature_.size(),
encrypted_license().mac_key_iv,
encrypted_license().mac_keys, num_keys_,
key_array_, pst_ptr, pst.length(), NULL,
OEMCrypto_EntitlementLicense));
// Update new generated keys.
memcpy(&mac_key_server_[0], license_.mac_keys, wvcdm::MAC_KEY_SIZE);
memcpy(&mac_key_client_[0], license_.mac_keys + wvcdm::MAC_KEY_SIZE,
wvcdm::MAC_KEY_SIZE);
} else {
ASSERT_EQ(
expected_sts,
OEMCrypto_LoadKeys(session_id(), message_ptr(), message_size_,
&signature_[0], signature_.size(), NULL, NULL,
num_keys_, key_array_, pst_ptr, pst.length(), NULL,
OEMCrypto_EntitlementLicense));
}
}
void Session::FillEntitledKeyArray() {
has_entitlement_license_ = true;
for (size_t i = 0; i < num_keys_; ++i) {
EntitledContentKeyData* key_data = &entitled_key_data_[i];
entitled_key_array_[i].entitlement_key_id = key_array_[i].key_id;
entitled_key_array_[i].entitlement_key_id_length =
key_array_[i].key_id_length;
EXPECT_EQ(
1, GetRandBytes(key_data->content_key_id,
sizeof(key_data->content_key_id)));
entitled_key_array_[i].content_key_id = key_data->content_key_id;
entitled_key_array_[i].content_key_id_length =
sizeof(key_data->content_key_id);
EXPECT_EQ(
1, GetRandBytes(key_data->content_key_data,
sizeof(key_data->content_key_data)));
entitled_key_array_[i].content_key_data = key_data->content_key_data;
entitled_key_array_[i].content_key_data_length =
sizeof(key_data->content_key_data);
EXPECT_EQ(
1, GetRandBytes(entitled_key_data_[i].content_key_data_iv,
sizeof(entitled_key_data_[i].content_key_data_iv)));
entitled_key_array_[i].content_key_data_iv = key_data->content_key_data_iv;
}
}
void Session::LoadEntitledContentKeys(OEMCryptoResult expected_sts) {
// Create a copy of the stored |entitled_key_array_|.
std::vector<OEMCrypto_EntitledContentKeyObject> encrypted_entitled_key_array;
encrypted_entitled_key_array.resize(num_keys_);
memcpy(&encrypted_entitled_key_array[0], &entitled_key_array_[0],
sizeof(OEMCrypto_EntitledContentKeyObject) * num_keys_);
// Create a encrypted version of all of the content keys stored in
// |entitled_key_array_|.
std::vector<std::vector<uint8_t> > encrypted_content_keys;
encrypted_content_keys.resize(num_keys_);
for (size_t i = 0; i < num_keys_; ++i) {
// Load the entitlement key from |key_array_|.
AES_KEY aes_key;
AES_set_encrypt_key(&key_array_[i].key_data[0], 256, &aes_key);
encrypted_content_keys[i].resize(
encrypted_entitled_key_array[i].content_key_data_length);
// Encrypt the content key with the entitlement key.
uint8_t iv[16];
memcpy(&iv[0], &encrypted_entitled_key_array[i].content_key_data[0], 16);
AES_cbc_encrypt(
&entitled_key_array_[i].content_key_data[0],
const_cast<uint8_t*>(
&encrypted_entitled_key_array[i].content_key_data[0]),
encrypted_entitled_key_array[i].content_key_data_length,
&aes_key, iv, AES_ENCRYPT);
// Set the |encrypted_entitled_key_array| to point to the encrypted copy
// of the content key.
encrypted_entitled_key_array[i].content_key_data =
encrypted_content_keys[i].data();
}
ASSERT_EQ(expected_sts,
OEMCrypto_LoadEntitledContentKeys(
session_id(), num_keys_, &encrypted_entitled_key_array[0]));
if (expected_sts != OEMCrypto_SUCCESS) {
return;
}
VerifyEntitlementTestKeys();
}
void Session::VerifyTestKeys() {
for (unsigned int i = 0; i < num_keys_; i++) {
KeyControlBlock block;
@@ -274,6 +405,29 @@ void Session::VerifyTestKeys() {
}
}
void Session::VerifyEntitlementTestKeys() {
for (unsigned int i = 0; i < num_keys_; i++) {
KeyControlBlock block;
size_t size = sizeof(block);
OEMCryptoResult sts = OEMCrypto_QueryKeyControl(
session_id(), entitled_key_array_[i].content_key_id,
entitled_key_array_[i].content_key_id_length,
reinterpret_cast<uint8_t*>(&block), &size);
if (sts != OEMCrypto_ERROR_NOT_IMPLEMENTED) {
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
ASSERT_EQ(sizeof(block), size);
// control duration and bits stored in network byte order. For printing
// we change to host byte order.
ASSERT_EQ((htonl_fnc(license_.keys[i].control.duration)),
(htonl_fnc(block.duration)))
<< "For key " << i;
ASSERT_EQ(htonl_fnc(license_.keys[i].control.control_bits),
htonl_fnc(block.control_bits))
<< "For key " << i;
}
}
}
void Session::RefreshTestKeys(const size_t key_count, uint32_t control_bits,
uint32_t nonce, OEMCryptoResult expected_result) {
// Note: we store the message in encrypted_license_, but the refresh key
@@ -329,7 +483,57 @@ void Session::FillSimpleMessage(uint32_t duration, uint32_t control,
sizeof(license_.keys[i].key_iv)));
EXPECT_EQ(1, GetRandBytes(license_.keys[i].control_iv,
sizeof(license_.keys[i].control_iv)));
if (global_features.api_version == 13) {
if (global_features.api_version == 14) {
// For version 14, we require OEMCrypto to handle kc14 for all licenses.
memcpy(license_.keys[i].control.verification, "kc14", 4);
} else if (global_features.api_version == 13) {
// For version 13, we require OEMCrypto to handle kc13 for all licenses.
memcpy(license_.keys[i].control.verification, "kc13", 4);
} else if (global_features.api_version == 12) {
// For version 12, we require OEMCrypto to handle kc12 for all licenses.
memcpy(license_.keys[i].control.verification, "kc12", 4);
} else if (control & wvoec_mock::kControlSecurityPatchLevelMask) {
// For versions before 12, we require the special key control block only
// when there are newer features present.
memcpy(license_.keys[i].control.verification, "kc11", 4);
} else if (control & wvoec_mock::kControlRequireAntiRollbackHardware) {
memcpy(license_.keys[i].control.verification, "kc10", 4);
} else if (control & (wvoec_mock::kControlHDCPVersionMask |
wvoec_mock::kControlReplayMask)) {
memcpy(license_.keys[i].control.verification, "kc09", 4);
} else {
memcpy(license_.keys[i].control.verification, "kctl", 4);
}
license_.keys[i].control.duration = htonl(duration);
license_.keys[i].control.nonce = htonl(nonce);
license_.keys[i].control.control_bits = htonl(control);
license_.keys[i].cipher_mode = OEMCrypto_CipherMode_CTR;
}
memcpy(license_.pst, pst.c_str(), min(sizeof(license_.pst), pst.length()));
pst_ = pst;
}
void Session::FillSimpleEntitlementMessage(
uint32_t duration, uint32_t control, uint32_t nonce,
const std::string& pst) {
EXPECT_EQ(
1, GetRandBytes(license_.mac_key_iv, sizeof(license_.mac_key_iv)));
EXPECT_EQ(1, GetRandBytes(license_.mac_keys, sizeof(license_.mac_keys)));
for (unsigned int i = 0; i < num_keys_; i++) {
memset(license_.keys[i].key_id, 0, kTestKeyIdMaxLength);
license_.keys[i].key_id_length = kDefaultKeyIdLength;
memset(license_.keys[i].key_id, i, license_.keys[i].key_id_length);
EXPECT_EQ(1, GetRandBytes(license_.keys[i].key_data,
sizeof(license_.keys[i].key_data)));
license_.keys[i].key_data_length = wvcdm::KEY_SIZE * 2; // AES-256 keys
EXPECT_EQ(1, GetRandBytes(license_.keys[i].key_iv,
sizeof(license_.keys[i].key_iv)));
EXPECT_EQ(1, GetRandBytes(license_.keys[i].control_iv,
sizeof(license_.keys[i].control_iv)));
if (global_features.api_version == 14) {
// For version 13, we require OEMCrypto to handle kc14 for all licenses.
memcpy(license_.keys[i].control.verification, "kc14", 4);
} else if (global_features.api_version == 13) {
// For version 13, we require OEMCrypto to handle kc13 for all licenses.
memcpy(license_.keys[i].control.verification, "kc13", 4);
} else if (global_features.api_version == 12) {
@@ -362,7 +566,10 @@ void Session::FillRefreshMessage(size_t key_count, uint32_t control_bits,
encrypted_license().keys[i].key_id_length = license_.keys[i].key_id_length;
memcpy(encrypted_license().keys[i].key_id, license_.keys[i].key_id,
encrypted_license().keys[i].key_id_length);
if (global_features.api_version == 13) {
if (global_features.api_version == 14) {
// For version 14, we require OEMCrypto to handle kc14 for all licenses.
memcpy(encrypted_license().keys[i].control.verification, "kc14", 4);
} else if (global_features.api_version == 13) {
// For version 13, we require OEMCrypto to handle kc13 for all licenses.
memcpy(encrypted_license().keys[i].control.verification, "kc13", 4);
} else if (global_features.api_version == 12) {
@@ -389,9 +596,10 @@ void Session::EncryptAndSign() {
AES_cbc_encrypt(&license_.mac_keys[0], &encrypted_license().mac_keys[0],
2 * wvcdm::MAC_KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT);
int key_size = has_entitlement_license() ? 256 : 128;
for (unsigned int i = 0; i < num_keys_; i++) {
memcpy(iv_buffer, &license_.keys[i].control_iv[0], wvcdm::KEY_IV_SIZE);
AES_set_encrypt_key(&license_.keys[i].key_data[0], 128, &aes_key);
AES_set_encrypt_key(&license_.keys[i].key_data[0], key_size, &aes_key);
AES_cbc_encrypt(
reinterpret_cast<const uint8_t*>(&license_.keys[i].control),
reinterpret_cast<uint8_t*>(&encrypted_license().keys[i].control),
@@ -474,7 +682,6 @@ void Session::FillKeyArray(const MessageData& data,
key_array[i].key_control_iv = data.keys[i].control_iv;
key_array[i].key_control =
reinterpret_cast<const uint8_t*>(&data.keys[i].control);
key_array[i].cipher_mode = data.keys[i].cipher_mode;
}
}
@@ -524,7 +731,8 @@ void Session::TestDecryptCTR(bool select_key_first,
if (select_key_first) {
// Select the key (from FillSimpleMessage)
sts = OEMCrypto_SelectKey(session_id(), license_.keys[key_index].key_id,
license_.keys[key_index].key_id_length);
license_.keys[key_index].key_id_length,
OEMCrypto_CipherMode_CTR);
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
}
@@ -588,7 +796,8 @@ void Session::TestSelectExpired(unsigned int key_index) {
if (global_features.api_version >= 13) {
OEMCryptoResult status =
OEMCrypto_SelectKey(session_id(), license().keys[key_index].key_id,
license().keys[key_index].key_id_length);
license().keys[key_index].key_id_length,
OEMCrypto_CipherMode_CTR);
// It is OK for SelectKey to succeed with an expired key, but if there is
// an error, it must be OEMCrypto_ERROR_KEY_EXIRED.
if (status != OEMCrypto_SUCCESS) {
@@ -610,43 +819,30 @@ void Session::LoadOEMCert(bool verify_cert) {
OEMCrypto_GetOEMPublicCertificate(session_id(), &public_cert[0],
&public_cert_length));
// Load the certificate chain into an OpenSSL X509 Stack
#ifdef OPENSSL_IS_BORINGSSL
const openssl_ptr<STACK_OF(X509), DeleteX509Stack> x509_stack(
// Load the certificate chain into a BoringSSL X509 Stack
const boringssl_ptr<STACK_OF(X509), DeleteX509Stack> x509_stack(
sk_X509_new_null());
ASSERT_TRUE(x509_stack.NotNull()) << "Unable to allocate X509 stack.";
CBS pkcs7;
CBS_init(&pkcs7, public_cert.data(), public_cert.size());
if (!PKCS7_get_certificates(x509_stack.get(), &pkcs7)) {
dump_openssl_error();
dump_boringssl_error();
FAIL() << "Unable to deserialize certificate chain.";
}
STACK_OF(X509)* certs = x509_stack.get();
#else
// load the cert into rsa_key_.
openssl_ptr<BIO, BIO_vfree> bio(
BIO_new_mem_buf(&public_cert[0], public_cert_length));
ASSERT_TRUE(bio.NotNull());
openssl_ptr<PKCS7, PKCS7_free> cert(
d2i_PKCS7_bio(bio.get(), NULL));
ASSERT_TRUE(cert.NotNull());
EXPECT_EQ(OBJ_obj2nid(cert->type), NID_pkcs7_signed);
STACK_OF(X509)* certs = cert->d.sign->cert;
#endif
// Load the public cert's key into public_rsa_ and verify, if requested
for (X509Count i = 0; certs && i < sk_X509_num(certs); i++) {
for (size_t i = 0; certs && i < sk_X509_num(certs); i++) {
X509* x509_cert = sk_X509_value(certs, i);
openssl_ptr<EVP_PKEY, EVP_PKEY_free> pubkey(X509_get_pubkey(x509_cert));
boringssl_ptr<EVP_PKEY, EVP_PKEY_free> pubkey(X509_get_pubkey(x509_cert));
ASSERT_TRUE(pubkey.NotNull());
if (i == 0) {
public_rsa_ = EVP_PKEY_get1_RSA(pubkey.get());
if (!public_rsa_) {
cout << "d2i_RSAPrivateKey failed.\n";
dump_openssl_error();
dump_boringssl_error();
ASSERT_TRUE(NULL != public_rsa_);
}
}
@@ -656,9 +852,9 @@ void Session::LoadOEMCert(bool verify_cert) {
X509_NAME* name = X509_get_subject_name(x509_cert);
printf(" OEM Certificate Name: %s\n",
X509_NAME_oneline(name, &buffer[0], buffer.size()));
openssl_ptr<X509_STORE, X509_STORE_free> store(X509_STORE_new());
boringssl_ptr<X509_STORE, X509_STORE_free> store(X509_STORE_new());
ASSERT_TRUE(store.NotNull());
openssl_ptr<X509_STORE_CTX, X509_STORE_CTX_free> store_ctx(
boringssl_ptr<X509_STORE_CTX, X509_STORE_CTX_free> store_ctx(
X509_STORE_CTX_new());
ASSERT_TRUE(store_ctx.NotNull());
@@ -667,23 +863,13 @@ void Session::LoadOEMCert(bool verify_cert) {
// TODO(fredgc): Verify cert is signed by Google.
int result = X509_verify_cert(store_ctx.get());
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
ASSERT_GE(0, result) << " OEM Cert not valid. " <<
X509_verify_cert_error_string(store_ctx->error);
#else
ASSERT_GE(0, result) << " OEM Cert not valid. " <<
X509_verify_cert_error_string(
X509_STORE_CTX_get_error(store_ctx.get()));
#endif
if (result == 0) {
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
printf("Cert not verified: %s.\n",
X509_verify_cert_error_string(store_ctx->error));
#else
printf("Cert not verified: %s.\n",
X509_verify_cert_error_string(
X509_STORE_CTX_get_error(store_ctx.get())));
#endif
}
}
}
@@ -767,32 +953,32 @@ void Session::RewrapRSAKey30(const struct RSAPrivateKeyMessage& encrypted,
void Session::PreparePublicKey(const uint8_t* rsa_key, size_t rsa_key_length) {
if (rsa_key == NULL) {
rsa_key = wvcdm_test_auth::kRsaPrivateKey_2048;
rsa_key_length = wvcdm_test_auth::kRsaPrivateKeySize_2048;
rsa_key = wvcdm_test_auth::kTestRSAPKCS8PrivateKeyInfo2_2048;
rsa_key_length = wvcdm_test_auth::kTestRSAPKCS8PrivateKeyInfo2_2048_Size;
}
uint8_t* p = const_cast<uint8_t*>(rsa_key);
openssl_ptr<BIO, BIO_vfree> bio(BIO_new_mem_buf(p, rsa_key_length));
boringssl_ptr<BIO, BIO_vfree> bio(BIO_new_mem_buf(p, rsa_key_length));
ASSERT_TRUE(bio.NotNull());
openssl_ptr<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free> pkcs8_pki(
boringssl_ptr<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free> pkcs8_pki(
d2i_PKCS8_PRIV_KEY_INFO_bio(bio.get(), NULL));
ASSERT_TRUE(pkcs8_pki.NotNull());
openssl_ptr<EVP_PKEY, EVP_PKEY_free> evp(EVP_PKCS82PKEY(pkcs8_pki.get()));
boringssl_ptr<EVP_PKEY, EVP_PKEY_free> evp(EVP_PKCS82PKEY(pkcs8_pki.get()));
ASSERT_TRUE(evp.NotNull());
if (public_rsa_) RSA_free(public_rsa_);
public_rsa_ = EVP_PKEY_get1_RSA(evp.get());
if (!public_rsa_) {
cout << "d2i_RSAPrivateKey failed. ";
dump_openssl_error();
dump_boringssl_error();
FAIL() << "Could not parse public RSA key.";
}
switch (RSA_check_key(public_rsa_)) {
case 1: // valid.
return;
case 0: // not valid.
dump_openssl_error();
dump_boringssl_error();
FAIL() << "[rsa key not valid] ";
default: // -1 == check failed.
dump_openssl_error();
dump_boringssl_error();
FAIL() << "[error checking rsa key] ";
}
}
@@ -801,13 +987,9 @@ bool Session::VerifyPSSSignature(EVP_PKEY* pkey, const uint8_t* message,
size_t message_length,
const uint8_t* signature,
size_t signature_length) {
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
EVP_MD_CTX md_ctx_struct;
EVP_MD_CTX* md_ctx = &md_ctx_struct;
EVP_MD_CTX_init(md_ctx);
#else
EVP_MD_CTX* md_ctx = EVP_MD_CTX_new();
#endif
EVP_PKEY_CTX* pkey_ctx = NULL;
if (EVP_DigestVerifyInit(md_ctx, &pkey_ctx, EVP_sha1(), NULL /* no ENGINE */,
@@ -845,20 +1027,12 @@ bool Session::VerifyPSSSignature(EVP_PKEY* pkey, const uint8_t* message,
goto err;
}
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
EVP_MD_CTX_cleanup(md_ctx);
#else
EVP_MD_CTX_free(md_ctx);
#endif
return true;
err:
dump_openssl_error();
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
dump_boringssl_error();
EVP_MD_CTX_cleanup(md_ctx);
#else
EVP_MD_CTX_free(md_ctx);
#endif
return false;
}
@@ -874,7 +1048,7 @@ void Session::VerifyRSASignature(const vector<uint8_t>& message,
<< RSA_size(public_rsa_) << "\n";
if (padding_scheme == kSign_RSASSA_PSS) {
openssl_ptr<EVP_PKEY, EVP_PKEY_free> pkey(EVP_PKEY_new());
boringssl_ptr<EVP_PKEY, EVP_PKEY_free> pkey(EVP_PKEY_new());
ASSERT_EQ(1, EVP_PKEY_set1_RSA(pkey.get(), public_rsa_));
const bool ok = VerifyPSSSignature(pkey.get(), &message[0], message.size(),
@@ -909,7 +1083,7 @@ bool Session::GenerateRSASessionKey(vector<uint8_t>* session_key,
int size = static_cast<int>(RSA_size(public_rsa_));
if (status != size) {
cout << "GenerateRSASessionKey error encrypting session key.\n";
dump_openssl_error();
dump_boringssl_error();
return false;
}
return true;
@@ -1022,15 +1196,17 @@ void Session::VerifyPST(const Test_PST_Report& expected) {
char* pst_ptr = reinterpret_cast<char *>(computed.pst());
std::string computed_pst(pst_ptr, pst_ptr + computed.pst_length());
ASSERT_EQ(expected.pst, computed_pst);
EXPECT_NEAR(expected.seconds_since_license_received,
time_t now = time(NULL);
int64_t age = now - expected.time_created; // How old is this report.
EXPECT_NEAR(expected.seconds_since_license_received + age,
computed.seconds_since_license_received(),
kTimeTolerance);
// Decrypt times only valid on licenses that have been active.
if (expected.status == kActive || expected.status == kInactiveUsed) {
EXPECT_NEAR(expected.seconds_since_first_decrypt,
EXPECT_NEAR(expected.seconds_since_first_decrypt + age,
computed.seconds_since_first_decrypt(),
kUsageTableTimeTolerance);
EXPECT_NEAR(expected.seconds_since_last_decrypt,
EXPECT_NEAR(expected.seconds_since_last_decrypt + age,
computed.seconds_since_last_decrypt(),
kUsageTableTimeTolerance);
}
@@ -1041,7 +1217,7 @@ void Session::VerifyPST(const Test_PST_Report& expected) {
pst_report_buffer_.size() - SHA_DIGEST_LENGTH,
&signature[0], &md_len)) {
cout << "Error computing HMAC.\n";
dump_openssl_error();
dump_boringssl_error();
}
EXPECT_EQ(0, memcmp(computed.signature(), &signature[0],
SHA_DIGEST_LENGTH));

View File

@@ -11,6 +11,7 @@
#include "oec_device_features.h"
#include "pst_report.h"
#include "test_keybox.h"
#include "wv_cdm_constants.h"
using namespace std;
@@ -94,13 +95,21 @@ struct RSAPrivateKeyMessage {
struct Test_PST_Report {
Test_PST_Report(const std::string& pst_in,
OEMCrypto_Usage_Entry_Status status_in)
: status(status_in), pst(pst_in) {}
: status(status_in), pst(pst_in), time_created(time(NULL)) {}
OEMCrypto_Usage_Entry_Status status;
int64_t seconds_since_license_received;
int64_t seconds_since_first_decrypt;
int64_t seconds_since_last_decrypt;
std::string pst;
time_t time_created;
};
struct EntitledContentKeyData {
uint8_t entitlement_key_id[wvcdm::KEY_SIZE];
uint8_t content_key_id[wvcdm::KEY_SIZE];
uint8_t content_key_data_iv[wvcdm::KEY_SIZE];
uint8_t content_key_data[wvcdm::KEY_SIZE];
};
// Increment counter for AES-CTR. The CENC spec specifies we increment only
@@ -112,8 +121,8 @@ void ctr128_inc64(int64_t increaseBy, uint8_t* iv);
// Some compilers don't like the macro htonl within an ASSERT_EQ.
uint32_t htonl_fnc(uint32_t x);
// Prints error string from openSSL
void dump_openssl_error();
// Prints error string from BoringSSL
void dump_boringssl_error();
class Session {
public:
@@ -142,7 +151,8 @@ class Session {
vector<uint8_t>* enc_context);
// Generate known mac and enc keys using OEMCrypto_GenerateDerivedKeys and
// also fill out enc_key_, mac_key_server_, and mac_key_client_.
void GenerateDerivedKeysFromKeybox();
void GenerateDerivedKeysFromKeybox(
const wvcdm_test_auth::WidevineKeybox& keybox);
// Generate known mac and enc keys using OEMCrypto_DeriveKeysFromSessionKey
// and also fill out enc_key_, mac_key_server_, and mac_key_client_.
void GenerateDerivedKeysFromSessionKey();
@@ -151,9 +161,27 @@ class Session {
// by FillSimpleMessage, modified if needed, and then encrypted and signed by
// the server's mac key in EncryptAndSign.
void LoadTestKeys(const std::string& pst = "", bool new_mac_keys = true);
// Loads the entitlement keys in the message pointed to by message_ptr()
// using OEMCrypto_LoadKeys. This message should have already been created
// by FillSimpleEntitlementMessage, modified if needed, and then encrypted
// and signed by the server's mac key in EncryptAndSign.
void LoadEnitlementTestKeys(const std::string& pst = "",
bool new_mac_keys = true,
OEMCryptoResult expected_sts = OEMCrypto_SUCCESS);
// Fills an OEMCrypto_EntitledContentKeyObject using the information from
// the license_ and randomly generated content keys. This method should be
// called after LoadEnitlementTestKeys.
void FillEntitledKeyArray();
// Encrypts and loads the entitled content keys via
// OEMCrypto_LoadEntitledContentKeys.
void LoadEntitledContentKeys(
OEMCryptoResult expected_sts = OEMCrypto_SUCCESS);
// This uses OEMCrypto_QueryKeyControl to check that the keys in OEMCrypto
// have the correct key control data.
void VerifyTestKeys();
// This uses OEMCrypto_QueryKeyControl to check that the keys in OEMCrypto
// have the correct key control data.
void VerifyEntitlementTestKeys();
// This creates a refresh key or license renewal message, signs it with the
// server's mac key, and calls OEMCrypto_RefreshKeys.
void RefreshTestKeys(const size_t key_count, uint32_t control_bits,
@@ -166,6 +194,12 @@ class Session {
// before being loaded in LoadTestKeys.
void FillSimpleMessage(uint32_t duration, uint32_t control, uint32_t nonce,
const std::string& pst = "");
// This fills the data structure license_ with entitlement key information.
// This data can be modified, and then should be encrypted and signed in
// EncryptAndSign before being loaded in LoadEnitlementTestKeys.
void FillSimpleEntitlementMessage(
uint32_t duration, uint32_t control,
uint32_t nonce, const std::string& pst = "");
// Like FillSimpleMessage, this fills encrypted_license_ with data. The name
// is a little misleading: the license renewal message is not encrypted, it
// is just signed. The signature is computed in RefreshTestKeys, above.
@@ -339,7 +373,18 @@ class Session {
// The size of the encrypted message.
size_t message_size() { return message_size_; }
// If this session has an entitlement license.
bool has_entitlement_license() const { return has_entitlement_license_; }
private:
// Generate mac and enc keys give the master key.
void DeriveKeys(const uint8_t* master_key,
const vector<uint8_t>& mac_key_context,
const vector<uint8_t>& enc_key_context);
// Internal utility function to derive key using CMAC-128
void DeriveKey(const uint8_t* key, const vector<uint8_t>& context,
int counter, vector<uint8_t>* out);
bool open_;
bool forced_session_id_;
OEMCrypto_SESSION session_id_;
@@ -360,6 +405,13 @@ class Session {
vector<uint8_t> encrypted_usage_entry_;
uint32_t usage_entry_number_;
string pst_;
bool has_entitlement_license_;
// Clear Entitlement key data. This is the backing data for
// |entitled_key_array_|.
EntitledContentKeyData entitled_key_data_[kMaxNumKeys];
// Entitled key object. Pointers are backed by |entitled_key_data_|.
OEMCrypto_EntitledContentKeyObject entitled_key_array_[kMaxNumKeys];
};
} // namespace wvoec

View File

@@ -2,8 +2,6 @@
#include <gtest/gtest.h>
#include "test_keybox.h"
using namespace std;
using namespace wvoec;
@@ -26,7 +24,7 @@ void SessionUtil::CreateWrappedRSAKeyFromKeybox(uint32_t allowed_schemes,
bool force) {
Session s;
ASSERT_NO_FATAL_FAILURE(s.open());
ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromKeybox());
ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromKeybox(keybox_));
// Provisioning request would be signed by the client and verified by the
// server.
ASSERT_NO_FATAL_FAILURE(s.VerifyClientSignature());
@@ -83,14 +81,16 @@ void SessionUtil::CreateWrappedRSAKey(uint32_t allowed_schemes,
}
}
void SessionUtil::InstallKeybox(const uint8_t* keybox, bool good) {
const size_t keybox_size = wvcdm_test_auth::kKeyboxSize;
uint8_t wrapped[keybox_size];
size_t length = keybox_size;
void SessionUtil::InstallKeybox(const wvcdm_test_auth::WidevineKeybox& keybox,
bool good) {
uint8_t wrapped[sizeof(wvcdm_test_auth::WidevineKeybox)];
size_t length = sizeof(wvcdm_test_auth::WidevineKeybox);
keybox_ = keybox;
ASSERT_EQ(
OEMCrypto_SUCCESS,
OEMCrypto_WrapKeybox(keybox, keybox_size, wrapped, &length, NULL, 0));
OEMCryptoResult sts = OEMCrypto_InstallKeybox(wrapped, length);
OEMCrypto_WrapKeybox(reinterpret_cast<const uint8_t*>(&keybox),
sizeof(keybox), wrapped, &length, NULL, 0));
OEMCryptoResult sts = OEMCrypto_InstallKeybox(wrapped, sizeof(keybox));
if (good) {
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
} else {
@@ -101,16 +101,30 @@ void SessionUtil::InstallKeybox(const uint8_t* keybox, bool good) {
void SessionUtil::EnsureTestKeys() {
switch (global_features.derive_key_method) {
case DeviceFeatures::LOAD_TEST_KEYBOX:
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadTestKeybox());
keybox_ = wvcdm_test_auth::kTestKeybox;
/* Note: If you are upgrading from an older version, it may be easier to
* force the following condition. This uses the same test keybox as we
* used in older versions of this test.
*/
if (global_features.api_version < 14) {
keybox_ = wvcdm_test_auth::kValidKeybox01;
}
ASSERT_EQ(OEMCrypto_SUCCESS,
OEMCrypto_LoadTestKeybox(
reinterpret_cast<const uint8_t*>(&keybox_),
sizeof(keybox_)));
break;
case DeviceFeatures::LOAD_TEST_RSA_KEY:
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadTestRSAKey());
break;
case DeviceFeatures::EXISTING_TEST_KEYBOX:
// already has test keybox.
// already has old test keybox.
keybox_ = wvcdm_test_auth::kValidKeybox01;
break;
case DeviceFeatures::FORCE_TEST_KEYBOX:
InstallKeybox(wvcdm_test_auth::kKeybox, true);
keybox_ = wvcdm_test_auth::kTestKeybox;
InstallKeybox(keybox_, true);
break;
case DeviceFeatures::TEST_PROVISION_30:
// Can use oem certificate to install test rsa key.
@@ -140,7 +154,7 @@ void SessionUtil::InstallTestSessionKeys(Session* s) {
s->GenerateDerivedKeysFromSessionKey());
} else { // Just uses keybox. Test keybox should already be installed.
ASSERT_NO_FATAL_FAILURE(
s->GenerateDerivedKeysFromKeybox());
s->GenerateDerivedKeysFromKeybox(keybox_));
}
}

View File

@@ -7,6 +7,7 @@
#include "oec_session_util.h"
#include "OEMCryptoCENC.h"
#include "test_keybox.h"
#include "test_rsa_key.h"
namespace wvoec {
@@ -14,9 +15,10 @@ namespace wvoec {
class SessionUtil {
public:
SessionUtil()
: encoded_rsa_key_(wvcdm_test_auth::kRsaPrivateKey_2048,
wvcdm_test_auth::kRsaPrivateKey_2048 +
wvcdm_test_auth::kRsaPrivateKeySize_2048) {}
: encoded_rsa_key_(
wvcdm_test_auth::kTestRSAPKCS8PrivateKeyInfo2_2048,
wvcdm_test_auth::kTestRSAPKCS8PrivateKeyInfo2_2048 +
wvcdm_test_auth::kTestRSAPKCS8PrivateKeyInfo2_2048_Size) {}
// If force is true, we assert that the key loads successfully.
void CreateWrappedRSAKeyFromKeybox(uint32_t allowed_schemes, bool force);
@@ -27,7 +29,7 @@ public:
// If force is true, we assert that the key loads successfully.
void CreateWrappedRSAKey(uint32_t allowed_schemes, bool force);
void InstallKeybox(const uint8_t* keybox, bool good);
void InstallKeybox(const wvcdm_test_auth::WidevineKeybox& keybox, bool good);
void EnsureTestKeys();
@@ -35,6 +37,7 @@ public:
std::vector<uint8_t> encoded_rsa_key_;
std::vector<uint8_t> wrapped_rsa_key_;
wvcdm_test_auth::WidevineKeybox keybox_;
};
} // namespace wvoec

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,8 @@
#include <gtest/gtest.h>
#include "OEMCryptoCENC.h"
#include "test_keybox.h"
#include "test_rsa_key.h"
#include "log.h"
@@ -105,7 +107,9 @@ TEST_F(OEMCryptoAndroidMNCTest, MinVersionNumber10) {
TEST_F(OEMCryptoAndroidMNCTest, LoadTestKeybox) {
if (OEMCrypto_Keybox == OEMCrypto_GetProvisioningMethod()) {
OEMCryptoResult status = OEMCrypto_LoadTestKeybox();
OEMCryptoResult status = OEMCrypto_LoadTestKeybox(
reinterpret_cast<const uint8_t*>(&kTestKeybox),
sizeof(kTestKeybox)));
// OEMCrypto may return success or not implemented.
if (status == OEMCrypto_SUCCESS) {
LOGV("OEMCrypto_LoadTestKeybox is implemented.");
@@ -149,4 +153,12 @@ TEST_F(OEMCryptoAndroidOCTest, MinVersionNumber13) {
ASSERT_GE(version, 13u);
}
// These tests are required for Pi MR1 Android devices.
class OEMCryptoAndroidPiMR1Test : public OEMCryptoAndroidOCTest {};
TEST_F(OEMCryptoAndroidPiMR1Test, MinVersionNumber14) {
uint32_t version = OEMCrypto_APIVersion();
ASSERT_GE(version, 14u);
}
} // namespace wvoec