From 52bd1d206e815adba0b0eb8802bb83e3755be412 Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Wed, 15 Sep 2021 02:56:19 -0700 Subject: [PATCH] Added an OTA keybox provisioner. [ Merge of http://go/wvgerrit/133729 ] The OtaKeyboxProvisioner is a system-wide provisioner for sharing the provisioning workflow between CDM engines. Bug: 189232882 Test: GtsMediaTestCases Change-Id: I873af3087cc05e1831bdd1d2c14fb002b73e6902 Added keybox provisioning proto fields. [ Merge of http://go/wvgerrit/133730 and http://go/ag/15113032 ] This CL copies over the required license_protocol.proto changes that are required for OTA keybox provisioning. These fields are defined in the server-side certificate_provisioning.proto, defined in http://cl/377533774. Note, changes are slightly different from server proto due to the RVC version of license_protocol.proto being out of date with SC and newer changes. Bug: 189232882 Test: run_x86_64_tests Change-Id: I55fcf6a7ac2ba4b6026b9acc63e822ff33c431d9 Added OTA keybox provisioning device files. [ Merge of http://go/wvgerrit/133743 and http://go/ag/15421141 ] This change adds a new set of proto messages/fields the CDM's device files for recording device and engine information around OTA keybox provisioning (OKP). To make cleanup and thread protection possible, there is a single file which will contain all the information for the device as a whole and each CDM engine tied to an app/origin. Bug: 189232882 Test: Linux unit tests Change-Id: Iaf80cd6342f32657e04416750d9b278d935821a5 Client ID for OKP requests. [ Merge of http://go/wvgerrit/133744 and http://go/ag/15645331 ] Extended the CDM ClientIdentification class to support a subset of client info used for OKP requests. Bug: 189232882 Test: Android unit tests Merged-In: I6aafb4f2164efe69bc733ece0a912f0e91893b91 Change-Id: I6aafb4f2164efe69bc733ece0a912f0e91893b91 --- libwvdrmengine/cdm/Android.bp | 1 + .../core/include/certificate_provisioning.h | 5 + .../cdm/core/include/client_identification.h | 26 +- .../cdm/core/include/crypto_session.h | 16 +- .../cdm/core/include/ota_keybox_provisioner.h | 68 +++++ .../cdm/core/src/certificate_provisioning.cpp | 12 +- .../cdm/core/src/client_identification.cpp | 28 +- .../cdm/core/src/crypto_session.cpp | 42 ++- .../cdm/core/src/device_files.proto | 67 +++++ libwvdrmengine/cdm/core/src/license.cpp | 3 +- .../cdm/core/src/license_protocol.proto | 23 ++ .../cdm/core/src/ota_keybox_provisioner.cpp | 83 ++++++ .../core/test/ota_keybox_provisioner_test.cpp | 242 ++++++++++++++++++ 13 files changed, 586 insertions(+), 30 deletions(-) create mode 100644 libwvdrmengine/cdm/core/include/ota_keybox_provisioner.h create mode 100644 libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp create mode 100644 libwvdrmengine/cdm/core/test/ota_keybox_provisioner_test.cpp diff --git a/libwvdrmengine/cdm/Android.bp b/libwvdrmengine/cdm/Android.bp index 4e66959b..c7ea68af 100644 --- a/libwvdrmengine/cdm/Android.bp +++ b/libwvdrmengine/cdm/Android.bp @@ -54,6 +54,7 @@ cc_library_static { CORE_SRC_DIR + "/license.cpp", CORE_SRC_DIR + "/license_key_status.cpp", CORE_SRC_DIR + "/oemcrypto_adapter_dynamic.cpp", + CORE_SRC_DIR + "/ota_keybox_provisioner.cpp", CORE_SRC_DIR + "/policy_engine.cpp", CORE_SRC_DIR + "/policy_timers.cpp", CORE_SRC_DIR + "/policy_timers_v15.cpp", diff --git a/libwvdrmengine/cdm/core/include/certificate_provisioning.h b/libwvdrmengine/cdm/core/include/certificate_provisioning.h index afdc4770..7baac324 100644 --- a/libwvdrmengine/cdm/core/include/certificate_provisioning.h +++ b/libwvdrmengine/cdm/core/include/certificate_provisioning.h @@ -67,6 +67,11 @@ class CertificateProvisioning { static bool ExtractAndDecodeSignedMessageForTesting( const std::string& provisioning_response, std::string* result); + // Retrieve the provisioning server URL used for certificate + // provisioning. This will be the same value as returned in + // |default_url| by GetProvisioningRequest(). + static void GetProvisioningServerUrl(std::string* default_url); + private: CdmResponseType GetProvisioningRequestInternal( SecurityLevel requested_security_level, CdmCertificateType cert_type, diff --git a/libwvdrmengine/cdm/core/include/client_identification.h b/libwvdrmengine/cdm/core/include/client_identification.h index ed96de0a..7f6078be 100644 --- a/libwvdrmengine/cdm/core/include/client_identification.h +++ b/libwvdrmengine/cdm/core/include/client_identification.h @@ -5,33 +5,35 @@ #ifndef WVCDM_CORE_CLIENT_IDENTIFICATION_H_ #define WVCDM_CORE_CLIENT_IDENTIFICATION_H_ +#include + // ClientIdentification fills in the ClientIdentification portion // of the License or Provisioning request messages. - #include "disallow_copy_and_assign.h" #include "license_protocol.pb.h" #include "wv_cdm_types.h" namespace wvcdm { - class CryptoSession; class ClientIdentification { public: - ClientIdentification() : is_license_request_(true) {} + ClientIdentification() {} virtual ~ClientIdentification() {} // Call this method when used with provisioning requests - CdmResponseType Init(CryptoSession* crypto_session); + CdmResponseType InitForProvisioning(CryptoSession* crypto_session); // Use in conjunction with license requests // |client_token| must be provided // |crypto_session| input parameter, mandatory - CdmResponseType Init(const std::string& client_token, - CryptoSession* crypto_session); + CdmResponseType InitForLicenseRequest(const std::string& client_token, + CryptoSession* crypto_session); - // Fill the ClientIdentification portion of the license or provisioning - // request + CdmResponseType InitForOtaKeyboxProvisioning(CryptoSession* crypto_session); + + // Fill the ClientIdentification portion of the license, DRM cert + // provisioning or OTA keybox provisioning request. // |app_parameters| parameters provided by client/app to be included in // provisioning/license request. optional, only used // if |is_license_request| is true @@ -49,13 +51,13 @@ class ClientIdentification { bool GetProvisioningTokenType( video_widevine::ClientIdentification::TokenType* token_type); - bool is_license_request_; + bool is_license_request_ = false; + bool is_okp_request_ = false; std::string client_token_; - CryptoSession* crypto_session_; + std::string device_id_; + CryptoSession* crypto_session_ = nullptr; CORE_DISALLOW_COPY_AND_ASSIGN(ClientIdentification); }; - } // namespace wvcdm - #endif // WVCDM_CORE_CLIENT_IDENTIFICATION_H_ diff --git a/libwvdrmengine/cdm/core/include/crypto_session.h b/libwvdrmengine/cdm/core/include/crypto_session.h index e516a1b3..a052feed 100644 --- a/libwvdrmengine/cdm/core/include/crypto_session.h +++ b/libwvdrmengine/cdm/core/include/crypto_session.h @@ -26,6 +26,7 @@ namespace wvcdm { class CryptoKey; class CryptoSessionFactory; +class OtaKeyboxProvisioner; class UsageTableHeader; using CryptoKeyMap = std::map; @@ -41,6 +42,7 @@ OEMCrypto_Substring GetSubstring(const std::string& message = "", bool set_zero = false); OEMCryptoCipherMode ToOEMCryptoCipherMode(CdmCipherMode cipher_mode); + class CryptoSession { public: using HdcpCapability = OEMCrypto_HDCP_Capability; @@ -288,7 +290,14 @@ class CryptoSession { SecurityLevel requested_security_level, CdmClientTokenType* token_type); // OTA Provisioning - // TODO(sigquit): include rest of http://go/wvgerrit/126004 + + bool needs_keybox_provisioning() const { return needs_keybox_provisioning_; } + + // Returns a system-wide singleton instance of OtaKeyboxProvisioner + // to be used for OTA provisioning requests/responses across apps. + // Returns a null pointer if OTA provisioning is NOT supported, or + // if the device has already been provisioned. + virtual OtaKeyboxProvisioner* GetOtaKeyboxProvisioner(); // Generates an OTA provisioning request. // This should only be called by an instance of OtaKeyboxProvisioner. @@ -504,6 +513,11 @@ class CryptoSession { static std::mutex factory_mutex_; static std::unique_ptr factory_; + // A singleton instance of OtaKeyboxProvisioner. Only one will + // be created for the system if OTA keybox provisioning is both + // required and supported by L1. + static std::unique_ptr ota_keybox_provisioner_l1_; + CORE_DISALLOW_COPY_AND_ASSIGN(CryptoSession); }; // class CryptoSession diff --git a/libwvdrmengine/cdm/core/include/ota_keybox_provisioner.h b/libwvdrmengine/cdm/core/include/ota_keybox_provisioner.h new file mode 100644 index 00000000..297f16b8 --- /dev/null +++ b/libwvdrmengine/cdm/core/include/ota_keybox_provisioner.h @@ -0,0 +1,68 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WVCDM_CORE_OTA_KEYBOX_PROVISIONER_H_ +#define WVCDM_CORE_OTA_KEYBOX_PROVISIONER_H_ + +#include +#include +#include + +#include "disallow_copy_and_assign.h" +#include "wv_cdm_types.h" + +namespace wvcdm { +class CryptoSession; + +// Wrapper around an OEMCrypto system-wide OTA keybox provisioning +// workflow. +class OtaKeyboxProvisioner { + public: + // Creates a new OTA keybox provisioner. This should only be + // created once and object ownership belongs to the CryptoSession + // module. + static std::unique_ptr Create(); + + ~OtaKeyboxProvisioner(); + + // === Request/response API === + + // Returns true if a provisioning response has been provided + // and accepted by OEMCrytpo. + bool IsProvisioned() const { return is_provisioned_; } + + uint32_t request_count() const { return request_count_; } + uint32_t response_count() const { return response_count_; } + + // Generates an OTA provisioning request. + // Generating a request will succeed so long as OTA provisioning + // is supported and no valid response has been provided. + CdmResponseType GenerateProvisioningRequest(CryptoSession* crypto_session, + std::string* request); + + // Accepts a provisioning response from the OTA provisioning + // server. The first response which is successfully loaded is + // is used. Any subsequent response after the first successful + // response is silently discarded. + CdmResponseType HandleProvisioningResponse(CryptoSession* crypto_session, + const std::string& response); + + private: + OtaKeyboxProvisioner(); + + bool is_provisioned_ = false; + + // These counters are for debugging purposes. + // Number of requests generated. + uint32_t request_count_ = 0; + // Number of responses provided. + uint32_t response_count_ = 0; + + // It is expected that multiple CDM engines may interact with the + // OtaKeyboxProvisioner instance simultaneously. + mutable std::mutex mutex_; + + CORE_DISALLOW_COPY_AND_ASSIGN(OtaKeyboxProvisioner); +}; // class OtaKeyboxProvisioner +} // namespace wvcdm +#endif // WVCDM_CORE_OTA_KEYBOX_PROVISIONER_H_ diff --git a/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp b/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp index 78242bf6..f7fcf296 100644 --- a/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp +++ b/libwvdrmengine/cdm/core/src/certificate_provisioning.cpp @@ -118,6 +118,16 @@ using video_widevine::SignedProvisioningMessage; using video_widevine:: SignedProvisioningMessage_ProvisioningProtocolVersion_VERSION_1_1; +// static +void CertificateProvisioning::GetProvisioningServerUrl( + std::string* default_url) { + if (default_url == nullptr) { + LOGE("Output |default_url| is null"); + return; + } + default_url->assign(kProvisioningServerUrl); +} + CdmResponseType CertificateProvisioning::Init( const std::string& service_certificate) { std::string certificate = service_certificate.empty() @@ -221,7 +231,7 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal( ProvisioningRequest provisioning_request; wvcdm::ClientIdentification id; - status = id.Init(crypto_session_.get()); + status = id.InitForProvisioning(crypto_session_.get()); if (status != NO_ERROR) return status; video_widevine::ClientIdentification client_id; diff --git a/libwvdrmengine/cdm/core/src/client_identification.cpp b/libwvdrmengine/cdm/core/src/client_identification.cpp index e8e399fd..2b068e6a 100644 --- a/libwvdrmengine/cdm/core/src/client_identification.cpp +++ b/libwvdrmengine/cdm/core/src/client_identification.cpp @@ -66,35 +66,44 @@ using video_widevine::ProvisioningRequest; using video_widevine::ProvisioningResponse; using video_widevine::SignedProvisioningMessage; -CdmResponseType ClientIdentification::Init(CryptoSession* crypto_session) { +CdmResponseType ClientIdentification::InitForProvisioning( + CryptoSession* crypto_session) { if (crypto_session == nullptr) { LOGE("Crypto session not provided"); return PARAMETER_NULL; } - is_license_request_ = false; crypto_session_ = crypto_session; return NO_ERROR; } -CdmResponseType ClientIdentification::Init(const std::string& client_token, - CryptoSession* crypto_session) { +CdmResponseType ClientIdentification::InitForLicenseRequest( + const std::string& client_token, CryptoSession* crypto_session) { if (crypto_session == nullptr) { LOGE("Crypto session not provided"); return PARAMETER_NULL; } - if (client_token.empty()) { LOGE("Client token is empty"); return PARAMETER_NULL; } - is_license_request_ = true; client_token_ = client_token; crypto_session_ = crypto_session; return NO_ERROR; } +CdmResponseType ClientIdentification::InitForOtaKeyboxProvisioning( + CryptoSession* crypto_session) { + if (crypto_session == nullptr) { + LOGE("Crypto session not provided"); + return PARAMETER_NULL; + } + is_okp_request_ = true; + crypto_session_ = crypto_session; + return NO_ERROR; +} + /* * Return the ClientIdentification message token type for provisioning request. * NOTE: a DRM Cert should never be presented to the provisioning server. @@ -107,7 +116,7 @@ CdmResponseType ClientIdentification::Prepare( client_id->set_type( video_widevine::ClientIdentification::DRM_DEVICE_CERTIFICATE); client_id->set_token(client_token_); - } else { + } else if (!is_okp_request_) { video_widevine::ClientIdentification::TokenType token_type; if (!GetProvisioningTokenType(&token_type)) { LOGE("Failed to get provisioning token type"); @@ -189,6 +198,11 @@ CdmResponseType ClientIdentification::Prepare( client_id->set_provider_client_token(provider_client_token); } + if (is_okp_request_) { + // Capabilities is not important for OTA keybox provisionining. + return NO_ERROR; + } + ClientCapabilities* client_capabilities = client_id->mutable_client_capabilities(); diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 7967b49b..7249f9dd 100644 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -20,6 +20,7 @@ #include "crypto_key.h" #include "entitlement_key_session.h" #include "log.h" +#include "ota_keybox_provisioner.h" #include "platform.h" #include "privacy_crypto.h" #include "properties.h" @@ -178,6 +179,7 @@ std::unique_ptr CryptoSession::usage_table_header_l1_; std::unique_ptr CryptoSession::usage_table_header_l3_; std::recursive_mutex CryptoSession::usage_table_mutex_; std::atomic CryptoSession::request_id_index_source_(0); +std::unique_ptr CryptoSession::ota_keybox_provisioner_l1_; size_t GetOffset(std::string message, std::string field) { size_t pos = message.find(field); @@ -360,8 +362,15 @@ void CryptoSession::Init() { LOGD("OEMCrypto version (L3 security level): %s.%s", api_version.c_str(), api_minor_version.c_str()); if (needs_keybox_provisioning_) { - LOGE("OEMCrypto needs provisioning"); - // TODO(fredgc,sigquit,rfrias): handle provisioning. + WithStaticFieldWriteLock("OtaKeyboxProvisioner", [&] { + if (!ota_keybox_provisioner_l1_) { + LOGD("OEMCrypto needs keybox provisioning"); + // Only create once. Possible that OEMCrypto is initialized + // and terminated many times over the life cycle of the OTA + // keybox provisioning process. + ota_keybox_provisioner_l1_ = OtaKeyboxProvisioner::Create(); + } + }); } } } @@ -3011,11 +3020,20 @@ OEMCryptoResult CryptoSession::LegacyDecryptInChunks( remaining_input_data -= chunk_size; AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, chunk_size); } - return sts; } -// TODO(sigquit): include rest of http://go/wvgerrit/126004 +OtaKeyboxProvisioner* CryptoSession::GetOtaKeyboxProvisioner() { + const auto getter = [&]() -> OtaKeyboxProvisioner* { + // If not set, then OTA keybox provisioning is not supported or + // not needed. + if (!ota_keybox_provisioner_l1_) return nullptr; + // May have already been initialized. + if (ota_keybox_provisioner_l1_->IsProvisioned()) return nullptr; + return ota_keybox_provisioner_l1_.get(); + }; + return WithStaticFieldReadLock("GetOtaKeyboxProvisioner", getter); +} CdmResponseType CryptoSession::PrepareOtaProvisioningRequest( bool use_test_key, std::string* request) { @@ -3026,17 +3044,25 @@ CdmResponseType CryptoSession::PrepareOtaProvisioningRequest( if (status != OEMCrypto_ERROR_SHORT_BUFFER) return MapOEMCryptoResult(status, UNKNOWN_ERROR, "PrepareOtaProvisioningRequest"); - std::string temp_buffer(buffer_length, '\0'); - uint8_t* buf = reinterpret_cast(&temp_buffer[0]); + if (buffer_length == 0) { + LOGE("OTA request size is zero"); + return UNKNOWN_ERROR; + } + request->resize(buffer_length); + uint8_t* buf = reinterpret_cast(&request->front()); status = OEMCrypto_GenerateOTARequest(buf, &buffer_length, use_test_key); - if (OEMCrypto_SUCCESS == status) request->assign(temp_buffer); + if (OEMCrypto_SUCCESS != status) { + request->clear(); + } else if (buffer_length != request->size()) { + request->resize(buffer_length); + } return MapOEMCryptoResult(status, UNKNOWN_ERROR, "PrepareOtaProvisioningRequest"); } CdmResponseType CryptoSession::LoadOtaProvisioning( bool use_test_key, const std::string& response) { - OEMCryptoResult status = OEMCrypto_ProcessOTAKeybox( + const OEMCryptoResult status = OEMCrypto_ProcessOTAKeybox( reinterpret_cast(response.data()), response.size(), use_test_key); return MapOEMCryptoResult(status, UNKNOWN_ERROR, "LoadOtaProvisioning"); diff --git a/libwvdrmengine/cdm/core/src/device_files.proto b/libwvdrmengine/cdm/core/src/device_files.proto index c52cee1f..98f15701 100644 --- a/libwvdrmengine/cdm/core/src/device_files.proto +++ b/libwvdrmengine/cdm/core/src/device_files.proto @@ -125,6 +125,71 @@ message UsageTableInfo { optional bool use_lru = 3 [default = false]; } +// Stores information related to a device's experience with OTA Keybox +// Provisioning (OKP). Only devices which both support OKP and require +// OKP should create this file. Otherwise, this information is not +// needed. +message OtaKeyboxProvisioningInfo { + // Engine-specific information about OKP. + message OkpEngineInfo { + // Engine identifier. + optional bytes app_id = 1; + optional bytes origin = 2; + reserved 3 to 5; // Reserved for future engine composite keys. + // Counters for engine-specific OKP events. + // These counters are reset after a certain amount of time + // (OKP period) since the last event. + // Number of calls to openSession() where it is recommended + // to the app to try keybox provisioning. + optional uint32 try_okp_counter = 6; + // Number of calls to getProvisionRequest(). + optional uint32 generate_request_counter = 7; + // Number of failed calls to provideProvisionRequest(). + optional uint32 failed_response_counter = 8; + + // The value of |last_event_time| and |backoff_start_time| are set + // using the system's wall-clock in epoch seconds. A value of + // zero indicates it's not set. + + // Time of the last engine OKP event (change of the above counters; + // the beginning of the current OKP period). + // Zero indicates no event has yet occurred. + optional int64 last_event_time = 9; + // Beginning of an app/origin backoff period. + // Zero indicates that engine is not in a backoff state. + optional int64 backoff_start_time = 10; + // Intended length of “backoff period”. This will be assigned a + // random duration initially, then double each time an engine + // enters a backoff state. This is base on Google's recommended + // exponential backoff rules. + // Value of 0 indicates that backoff has not yet occurred. + optional int64 backoff_duration = 11; + } + + enum OkpDeviceState { + // Not yet checked for provisioning state. This should be a + // transitory state only. Device which do not need OTA Keybox + // Provisioning should simply not store this file. + OKP_UNKNOWN = 0; + // OEMCrypto has reported that keybox provisioning is required and + // that the device supports OKP. Device may or may not be in the + // process of performing provisioning. + OKP_NEEDS_PROVISIONING = 1; + // The device has successfully provisioned its keybox. + OKP_PROVISIONED = 2; + } + // Device-wide OKP state. + optional OkpDeviceState state = 1; + // Time when the CDM service first discovers that it needs to + // provision the L1 keybox. + optional int64 first_checked_time = 2; + // System time of when a successful provisioning request has been + // received. Only relevant if |state| is OKP_PROVISIONED. + optional int64 provisioning_time = 3; + // A list of all records for each identifiable engine. + repeated OkpEngineInfo engine_infos = 4; +} + message File { enum FileType { DEVICE_CERTIFICATE = 1; @@ -132,6 +197,7 @@ message File { USAGE_INFO = 3; HLS_ATTRIBUTES = 4; USAGE_TABLE_INFO = 5; + OKP_INFO = 6; } enum FileVersion { VERSION_1 = 1; } @@ -143,6 +209,7 @@ message File { optional UsageInfo usage_info = 5; optional HlsAttributes hls_attributes = 6; optional UsageTableInfo usage_table_info = 7; + optional OtaKeyboxProvisioningInfo okp_info = 8; } message HashedFile { diff --git a/libwvdrmengine/cdm/core/src/license.cpp b/libwvdrmengine/cdm/core/src/license.cpp index f85cbfe6..51b8805a 100644 --- a/libwvdrmengine/cdm/core/src/license.cpp +++ b/libwvdrmengine/cdm/core/src/license.cpp @@ -1046,7 +1046,8 @@ CdmResponseType CdmLicense::PrepareClientId( return CLIENT_TOKEN_NOT_SET; } - CdmResponseType status = id.Init(client_token_, crypto_session_); + CdmResponseType status = + id.InitForLicenseRequest(client_token_, crypto_session_); if (status != NO_ERROR) return status; video_widevine::ClientIdentification* client_id = diff --git a/libwvdrmengine/cdm/core/src/license_protocol.proto b/libwvdrmengine/cdm/core/src/license_protocol.proto index eaf2a45e..1ad2ce1a 100644 --- a/libwvdrmengine/cdm/core/src/license_protocol.proto +++ b/libwvdrmengine/cdm/core/src/license_protocol.proto @@ -532,6 +532,14 @@ message ProvisioningRequest { // Serialized, encrypted session keys. Required. optional bytes encrypted_session_keys = 2; } + // This message contains the custom serialized message for OTA provisioning + // using Android Attestation and a device id as authentication. + message AndroidAttestationOtaKeyboxRequest { + // The request contains custom serialized and signed data for the + // Android Attestation OTA request. + optional bytes ota_request = 1; + } + oneof clear_or_encrypted_client_id { // Device root of trust and other client identification. Required. ClientIdentification client_id = 1; @@ -555,6 +563,8 @@ message ProvisioningRequest { // SessionKeys encrypted using a service cert public key. // Required for keybox provisioning. optional EncryptedSessionKeys encrypted_session_keys = 8; + // The custom request for Android Attestation OTA. + optional AndroidAttestationOtaKeyboxRequest android_ota_keybox_request = 9; } // Provisioning response sent by the provisioning server to client devices. @@ -579,6 +589,14 @@ message ProvisioningResponse { // Devices in this series have been revoked. Provisioning is not possible. REVOKED_DEVICE_SERIES = 2; } + // This message contains the custom response for Android Attestation OTA + // provisioning which uses the Android Attestation keybox and a device id + // from the chip set. + message AndroidAttestationOtaKeyboxResponse { + // The response contains custom serialized and signed data for the + // Android Attestation OTA keybox provisioning. + optional bytes ota_response = 1; + } // AES-128 encrypted device private RSA key. PKCS#1 ASN.1 DER-encoded. // Required. For X.509 certificates, the private RSA key may also include @@ -603,6 +621,9 @@ message ProvisioningResponse { // than |status| may be empty and should be ignored if the |status| // is present and not NO_ERROR optional ProvisioningStatus status = 7; + // The Android Attestation OTA response. Only populated if the request + // was an Android Attestation OTA request. + optional AndroidAttestationOtaKeyboxResponse android_ota_keybox_response = 8; } // Protocol-specific context data used to hold the state of the server in @@ -654,6 +675,8 @@ message SignedProvisioningMessage { PROVISIONING_20 = 2; // Keybox factory-provisioned devices. PROVISIONING_30 = 3; // OEM certificate factory-provisioned devices. ARCPP_PROVISIONING = 4; // ChromeOS/Arc++ devices. + // Android-Attestation-based OTA keyboxes. + ANDROID_ATTESTATION_KEYBOX_OTA = 6; INTEL_SIGMA_101 = 101; // Intel Sigma 1.0.1 protocol. } diff --git a/libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp b/libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp new file mode 100644 index 00000000..e88109a8 --- /dev/null +++ b/libwvdrmengine/cdm/core/src/ota_keybox_provisioner.cpp @@ -0,0 +1,83 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#include "ota_keybox_provisioner.h" + +#include + +#include "crypto_session.h" +#include "log.h" +#include "string_conversions.h" + +namespace wvcdm { +using UniqueLock = std::unique_lock; +namespace { +// Indicates not to use the test keybox for OTA provisioning. +constexpr bool kProductionKeybox = false; +} // namespace + +// static +std::unique_ptr OtaKeyboxProvisioner::Create() { + return std::unique_ptr(new OtaKeyboxProvisioner()); +} + +OtaKeyboxProvisioner::OtaKeyboxProvisioner() : mutex_() {} + +OtaKeyboxProvisioner::~OtaKeyboxProvisioner() {} + +CdmResponseType OtaKeyboxProvisioner::GenerateProvisioningRequest( + CryptoSession* crypto_session, std::string* request) { + if (crypto_session == nullptr) { + LOGE("Input |crypto_session| is null"); + return PARAMETER_NULL; + } + if (request == nullptr) { + LOGE("Output |request| is null"); + return PARAMETER_NULL; + } + UniqueLock lock(mutex_); + // Do not generate new requests if already provisioned. + if (IsProvisioned()) { + LOGW("Already provisioned"); + // TODO(sigquit): Use a response code that will indicate to the + // caller that the system is already provisioned. + return UNKNOWN_ERROR; + } + const CdmResponseType result = + crypto_session->PrepareOtaProvisioningRequest(kProductionKeybox, request); + if (result != NO_ERROR) { + return result; + } + LOGV("OTA request generated: request = %s", b2a_hex(*request).c_str()); + request_count_++; + return NO_ERROR; +} + +CdmResponseType OtaKeyboxProvisioner::HandleProvisioningResponse( + CryptoSession* crypto_session, const std::string& response) { + if (crypto_session == nullptr) { + LOGE("Input |crypto_session| is null"); + return PARAMETER_NULL; + } + if (response.empty()) { + LOGE("OTA provisioning response is empty"); + return EMPTY_PROVISIONING_RESPONSE; + } + UniqueLock lock(mutex_); + if (IsProvisioned()) { + // Response already received, silently dropping. + response_count_++; + return NO_ERROR; + } + const CdmResponseType result = + crypto_session->LoadOtaProvisioning(kProductionKeybox, response); + if (result != NO_ERROR) { + return result; + } + LOGD("OTA response successfully processed: response = %s", + b2a_hex(response).c_str()); + is_provisioned_ = true; + response_count_ = 1; + return NO_ERROR; +} +} // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/test/ota_keybox_provisioner_test.cpp b/libwvdrmengine/cdm/core/test/ota_keybox_provisioner_test.cpp new file mode 100644 index 00000000..ca10e39c --- /dev/null +++ b/libwvdrmengine/cdm/core/test/ota_keybox_provisioner_test.cpp @@ -0,0 +1,242 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#include "ota_keybox_provisioner.h" + +#include +#include + +#include "crypto_session.h" + +namespace wvcdm { +using ::testing::DoAll; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SetArgPointee; +namespace { +const std::string kEmptyString; +const std::string kFakeOtaProvisioningRequest = + "Totally real device ID, not fake" // Device ID : 32 bytes + "Totally real model ID, also real" // Model ID : 32 bytes + "Super secure key" // Encryped session key : 16 bytes + "Undoubtedly authentic signature!"; // Signature : 32 bytes + +const std::string kFakeOtaProvisioningResponse = + "Definitely an IV" // IV : 16 bytes + // Keybox : 128 bytes + "I'm a keybox, look at my keys and box-like appearance []. You might " + "be thinking 'you are not a real keybox', but you'd be wrong" + "Scribble scribble dot slash dot "; // Signature : 32 bytes + +const std::string kAnotherFakeOtaProvisioningResponse = + "Looks like an IV" // IV : 16 bytes + // Keybox : 128 bytes + "I am also a keybox. It's so safe to assume I'm a real keybox that " + "attempting to verify that will look very embarrassing for you" + "A drawing of boat with dolphins "; // Signature : 32 bytes + +class MockCryptoSession : public CryptoSession { + public: + MockCryptoSession() : CryptoSession(&crypto_metrics_) {} + ~MockCryptoSession() {} + + bool IsOpen() override { return is_open_; } + void SetIsOpen(bool is_open) { is_open_ = is_open; } + + MOCK_METHOD2(PrepareOtaProvisioningRequest, + CdmResponseType(bool, std::string*)); + MOCK_METHOD2(LoadOtaProvisioning, CdmResponseType(bool, const std::string&)); + + void ExpectRequest(const std::string& request, CdmResponseType result) { + if (result == NO_ERROR) { + EXPECT_CALL(*this, PrepareOtaProvisioningRequest(false, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(request), Return(NO_ERROR))); + } else { + EXPECT_CALL(*this, PrepareOtaProvisioningRequest(false, NotNull())) + .WillOnce(Return(result)); + } + } + + void ExpectResponse(const std::string& response, CdmResponseType result) { + EXPECT_CALL(*this, LoadOtaProvisioning(false, response)) + .WillOnce(Return(result)); + } + + private: + bool is_open_ = false; + + static metrics::CryptoMetrics crypto_metrics_; +}; + +metrics::CryptoMetrics MockCryptoSession::crypto_metrics_; +} // namespace + +class OtaKeyboxProvisionerTest : public ::testing::Test { + protected: + void SetUp() override { + crypto_session_.reset(new MockCryptoSession()); + provisioner_ = OtaKeyboxProvisioner::Create(); + } + + void TearDown() override { + crypto_session_.reset(); + provisioner_.reset(); + } + + std::unique_ptr crypto_session_; + std::unique_ptr provisioner_; +}; + +TEST_F(OtaKeyboxProvisionerTest, SingleRequestSingleResponse) { + // Pre-request conditions. + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_EQ(0u, provisioner_->request_count()); + EXPECT_EQ(0u, provisioner_->response_count()); + + // Generate request. + crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); + std::string request; + EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( + crypto_session_.get(), &request)); + EXPECT_EQ(request, kFakeOtaProvisioningRequest); + + // Post-request, pre-response conditions. + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_EQ(1u, provisioner_->request_count()); + EXPECT_EQ(0u, provisioner_->response_count()); + + // Load response. + const std::string response = kFakeOtaProvisioningResponse; + crypto_session_->ExpectResponse(response, NO_ERROR); + EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse( + crypto_session_.get(), response)); + + // Post-response conditions. + EXPECT_TRUE(provisioner_->IsProvisioned()); + EXPECT_EQ(1u, provisioner_->request_count()); + EXPECT_EQ(1u, provisioner_->response_count()); +} + +TEST_F(OtaKeyboxProvisionerTest, MultipleRequestsMultipleResponse) { + // Generate first request. + crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); + std::string first_request; + EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( + crypto_session_.get(), &first_request)); + EXPECT_EQ(first_request, kFakeOtaProvisioningRequest); + + // Generate second request. + crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); + std::string second_request; + EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( + crypto_session_.get(), &second_request)); + EXPECT_EQ(second_request, kFakeOtaProvisioningRequest); + + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_EQ(2u, provisioner_->request_count()); + EXPECT_EQ(0u, provisioner_->response_count()); + + // Load first response. + const std::string first_response = kFakeOtaProvisioningResponse; + crypto_session_->ExpectResponse(first_response, NO_ERROR); + EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse( + crypto_session_.get(), first_response)); + + // Loading second response should be silently dropped. + // No call to CryptoSession. + const std::string second_response = kAnotherFakeOtaProvisioningResponse; + EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse( + crypto_session_.get(), second_response)); + + // Post-response conditions. + EXPECT_TRUE(provisioner_->IsProvisioned()); + EXPECT_EQ(2u, provisioner_->request_count()); + EXPECT_EQ(2u, provisioner_->response_count()); +} + +TEST_F(OtaKeyboxProvisionerTest, NullRequestParameters) { + // Null CryptoSession + std::string request; + EXPECT_NE(NO_ERROR, + provisioner_->GenerateProvisioningRequest(nullptr, &request)); + + // Null request, no call to CryptoSession. + EXPECT_NE(NO_ERROR, provisioner_->GenerateProvisioningRequest( + crypto_session_.get(), nullptr)); + // Counter should not increase. + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_EQ(0u, provisioner_->request_count()); + EXPECT_EQ(0u, provisioner_->response_count()); +} + +TEST_F(OtaKeyboxProvisionerTest, EmptyRequest) { + // Generate request. + crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); + std::string request; + EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( + crypto_session_.get(), &request)); + + // Attempt to load empty response. No call to CryptoSession. + const std::string response = ""; + EXPECT_NE(NO_ERROR, provisioner_->HandleProvisioningResponse( + crypto_session_.get(), response)); + + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_EQ(1u, provisioner_->request_count()); + EXPECT_EQ(0u, provisioner_->response_count()); +} + +TEST_F(OtaKeyboxProvisionerTest, OtaProvisioningNotImplemented) { + // Generate request. + crypto_session_->ExpectRequest(kEmptyString, NOT_IMPLEMENTED_ERROR); + std::string request; + EXPECT_EQ(NOT_IMPLEMENTED_ERROR, provisioner_->GenerateProvisioningRequest( + crypto_session_.get(), &request)); + // Counter should not increase. + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_EQ(0u, provisioner_->request_count()); + EXPECT_EQ(0u, provisioner_->response_count()); +} + +TEST_F(OtaKeyboxProvisionerTest, ResponseRejected) { + // Generate request. + crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); + std::string request; + EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( + crypto_session_.get(), &request)); + + // Attempt to load response. OEMCrypto rejects response. + const std::string response = kFakeOtaProvisioningResponse; + crypto_session_->ExpectResponse(response, UNKNOWN_ERROR); + EXPECT_NE(NO_ERROR, provisioner_->HandleProvisioningResponse( + crypto_session_.get(), response)); + + // Should not be provisioned. + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_EQ(1u, provisioner_->request_count()); + EXPECT_EQ(0u, provisioner_->response_count()); +} + +TEST_F(OtaKeyboxProvisionerTest, GenerateRequestAfterProvisioning) { + // Generate request. + crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); + std::string first_request; + EXPECT_EQ(NO_ERROR, provisioner_->GenerateProvisioningRequest( + crypto_session_.get(), &first_request)); + + // Load response. + const std::string response = kFakeOtaProvisioningResponse; + crypto_session_->ExpectResponse(response, NO_ERROR); + EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse( + crypto_session_.get(), response)); + + // Attempt to generate second request. Should fail. + std::string second_request; + EXPECT_NE(NO_ERROR, provisioner_->GenerateProvisioningRequest( + crypto_session_.get(), &second_request)); + + EXPECT_TRUE(provisioner_->IsProvisioned()); + EXPECT_EQ(1u, provisioner_->request_count()); + EXPECT_EQ(1u, provisioner_->response_count()); +} +} // namespace wvcdm