// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. #include "certificate_provisioning.h" #include "client_identification.h" #include "crypto_wrapped_key.h" #include "device_files.h" #include "file_store.h" #include "license_protocol.pb.h" #include "license_protocol_conversions.h" #include "log.h" #include "properties.h" #include "service_certificate.h" #include "string_conversions.h" #include "wv_cdm_constants.h" namespace wvcdm { namespace { const std::string kEmptyString; // URL for Google Provisioning Server. // The provisioning server supplies the certificate that is needed // to communicate with the License Server. const std::string kProvisioningServerUrl = "https://www.googleapis.com/" "certificateprovisioning/v1/devicecertificates/create" "?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE"; // In case of provisioning 4, the default url is used as a way to inform app of // the current provisioning stage. In the first stage, this suffix is appended // to kProvisioningServerUrl; in the second stage, there is no change to // kProvisioningServerUrl. const std::string kProv40FirstStageServerUrlSuffix = "&preProvisioning=true"; // NOTE: Provider ID = widevine.com const std::string kCpProductionServiceCertificate = wvutil::a2bs_hex( "0ab9020803121051434fe2a44c763bcc2c826a2d6ef9a718f7d793d005228e02" "3082010a02820101009e27088659dbd9126bc6ed594caf652b0eaab82abb9862" "ada1ee6d2cb5247e94b28973fef5a3e11b57d0b0872c930f351b5694354a8c77" "ed4ee69834d2630372b5331c5710f38bdbb1ec3024cfadb2a8ac94d977d391b7" "d87c20c5c046e9801a9bffaf49a36a9ee6c5163eff5cdb63bfc750cf4a218618" "984e485e23a10f08587ec5d990e9ab0de71460dfc334925f3fb9b55761c61e28" "8398c387a0925b6e4dcaa1b36228d9feff7e789ba6e5ef6cf3d97e6ae05525db" "38f826e829e9b8764c9e2c44530efe6943df4e048c3c5900ca2042c5235dc80d" "443789e734bf8e59a55804030061ed48e7d139b521fbf35524b3000b3e2f6de0" "001f5eeb99e9ec635f02030100013a0c7769646576696e652e636f6d12800332" "2c2f3fedc47f8b7ba88a135a355466e378ed56a6fc29ce21f0cafc7fb253b073" "c55bed253d8650735417aad02afaefbe8d5687902b56a164490d83d590947515" "68860e7200994d322b5de07f82ef98204348a6c2c9619092340eb87df26f63bf" "56c191dc069b80119eb3060d771afaaeb2d30b9da399ef8a41d16f45fd121e09" "a0c5144da8f8eb46652c727225537ad65e2a6a55799909bbfb5f45b5775a1d1e" "ac4e06116c57adfa9ce0672f19b70b876f88e8b9fbc4f96ccc500c676cfb173c" "b6f52601573e2e45af1d9d2a17ef1487348c05cfc6d638ec2cae3fadb655e943" "1330a75d2ceeaa54803e371425111e20248b334a3a50c8eca683c448b8ac402c" "76e6f76e2751fbefb669f05703cec8c64cf7a62908d5fb870375eb0cc96c508e" "26e0c050f3fd3ebe68cef9903ef6405b25fc6e31f93559fcff05657662b3653a" "8598ed5751b38694419242a875d9e00d5a5832933024b934859ec8be78adccbb" "1ec7127ae9afeef9c5cd2e15bd3048e8ce652f7d8c5d595a0323238c598a28"); // Used in provisioning 4 client identification name value pairs. const std::string kKeyAppParameterSpoid = "spoid"; const std::string kKeyAppParameterProviderId = "provider_id"; const std::string kKeyAppParameterStableId = "stable_id"; // Retrieves |stored_oem_cert| from |file_handle|, and load the OEM private key // to |crypto_session|. Returns true if all operations are successful. bool RetrieveOemCertificateAndLoadPrivateKey(CryptoSession& crypto_session, DeviceFiles& file_handle, std::string& stored_oem_cert) { stored_oem_cert.clear(); CryptoWrappedKey wrapped_private_key; if (file_handle.RetrieveOemCertificate(&stored_oem_cert, &wrapped_private_key) != DeviceFiles::kCertificateValid) { LOGE("An invalid stored OEM certificated is retrieved"); stored_oem_cert.clear(); return false; } if (crypto_session.LoadOemCertificatePrivateKey(wrapped_private_key) != NO_ERROR) { LOGE("Can not load the OEM private key"); stored_oem_cert.clear(); return false; } return true; } } // namespace // Protobuf generated classes. using video_widevine::DrmCertificate; using video_widevine::EncryptedClientIdentification; using video_widevine::HashAlgorithmProto; using video_widevine::ProvisioningOptions; using video_widevine::ProvisioningRequest; using video_widevine::ProvisioningResponse; using video_widevine::PublicKeyToCertify; using video_widevine::SignedDrmCertificate; using video_widevine::SignedProvisioningMessage; // 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) { const std::string certificate = service_certificate.empty() ? kCpProductionServiceCertificate : service_certificate; return service_certificate_->Init(certificate); } // Fill in the appropriate SPOID (Stable Per-Origin IDentifier) option. // One of spoid, provider_id or stable_id will be passed to the provisioning // server for determining a unique per origin ID for the device. // It is also valid (though deprecated) to leave the settings unset. CdmResponseType CertificateProvisioning::SetSpoidParameter( const std::string& origin, const std::string& spoid, ProvisioningRequest* request) { if (!request) { LOGE("Output parameter |request| is not provided"); return CdmResponseType(PARAMETER_NULL); } if (!spoid.empty()) { // Use the SPOID that has been pre-provided request->set_spoid(spoid); } else if (Properties::UseProviderIdInProvisioningRequest()) { if (service_certificate_->provider_id().empty()) { LOGE( "Failed to set provider ID: " "Service certificate provider ID is empty"); return CdmResponseType(SERVICE_CERTIFICATE_PROVIDER_ID_EMPTY); } request->set_provider_id(service_certificate_->provider_id()); } else if (origin != EMPTY_ORIGIN) { // Legacy behavior - Concatenate Unique ID with Origin std::string device_unique_id; CdmResponseType status = crypto_session_->GetInternalDeviceUniqueId(&device_unique_id); if (status != NO_ERROR) { LOGE("Failed to get device unique ID: status = %d", static_cast(status)); return status; } request->set_stable_id(device_unique_id + origin); } // No else clause, by design. It is valid to do nothing. return CdmResponseType(NO_ERROR); } // Return the provisioning protocol version - dictated by OEMCrypto // support for OEM certificates. SignedProvisioningMessage::ProvisioningType CertificateProvisioning::GetProvisioningType() { switch (crypto_session_->GetPreProvisionTokenType()) { case kClientTokenBootCertChain: return SignedProvisioningMessage::PROVISIONING_40; case kClientTokenOemCert: return SignedProvisioningMessage::PROVISIONING_30; default: return SignedProvisioningMessage::PROVISIONING_20; } } // Compose a device provisioning request and output *request in a // JSON-compatible format (web-safe base64). // Also return *default_url of the provisioning server. // // Returns NO_ERROR for success and CERT_PROVISIONING_REQUEST_ERROR_? if fails. CdmResponseType CertificateProvisioning::GetProvisioningRequest( wvutil::FileSystem* file_system, RequestedSecurityLevel requested_security_level, CdmCertificateType cert_type, const std::string& cert_authority, const std::string& origin, const std::string& spoid, CdmProvisioningRequest* request, std::string* default_url) { return CloseSessionOnError(GetProvisioningRequestInternal( file_system, requested_security_level, cert_type, cert_authority, origin, spoid, request, default_url)); } CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal( wvutil::FileSystem* file_system, RequestedSecurityLevel requested_security_level, CdmCertificateType cert_type, const std::string& cert_authority, const std::string& origin, const std::string& spoid, CdmProvisioningRequest* request, std::string* default_url) { if (!request || !default_url) { LOGE("Output parameter |%s| is not provided", request ? "default_url" : "request"); return CdmResponseType(CERT_PROVISIONING_REQUEST_ERROR_1); } default_url->assign(kProvisioningServerUrl); CloseSession(); CdmResponseType status = crypto_session_->Open(requested_security_level); if (NO_ERROR != status) { LOGE("Failed to create a crypto session: status = %d", static_cast(status)); return status; } if (crypto_session_->GetPreProvisionTokenType() == kClientTokenBootCertChain) { return GetProvisioning40RequestInternal(file_system, origin, spoid, request, default_url); } // Prepare device provisioning request. ProvisioningRequest provisioning_request; status = FillEncryptedClientId(/*client_token=*/"", provisioning_request, *service_certificate_); if (status != NO_ERROR) return status; uint32_t nonce; status = crypto_session_->GenerateNonce(&nonce); if (status != NO_ERROR) { LOGE("Failed to generate a nonce: status = %d", static_cast(status)); return status == NONCE_GENERATION_ERROR ? CdmResponseType(CERT_PROVISIONING_NONCE_GENERATION_ERROR) : status; } // The provisioning server does not convert the nonce to uint32_t, it just // passes the binary data to the response message. const std::string encoded_nonce(reinterpret_cast(&nonce), sizeof(nonce)); provisioning_request.set_nonce(encoded_nonce); ProvisioningOptions* options = provisioning_request.mutable_options(); switch (cert_type) { case kCertificateWidevine: options->set_certificate_type(ProvisioningOptions::WIDEVINE_DRM); break; case kCertificateX509: options->set_certificate_type(ProvisioningOptions::X509); break; default: LOGE("Unknown certificate type: %d", static_cast(cert_type)); return CdmResponseType(CERT_PROVISIONING_INVALID_CERT_TYPE); } cert_type_ = cert_type; options->set_certificate_authority(cert_authority); status = SetSpoidParameter(origin, spoid, &provisioning_request); if (status != NO_ERROR) return status; std::string serialized_message; provisioning_request.SerializeToString(&serialized_message); // Derives signing and encryption keys and constructs signature. std::string core_message; std::string request_signature; bool should_specify_algorithm; OEMCrypto_SignatureHashAlgorithm oec_algorithm = OEMCrypto_SHA1; status = crypto_session_->PrepareAndSignProvisioningRequest( serialized_message, &core_message, &request_signature, should_specify_algorithm, oec_algorithm); if (status != NO_ERROR) { LOGE("Failed to prepare provisioning request: status = %d", static_cast(status)); return status; } if (request_signature.empty()) { LOGE("Request signature is empty"); return CdmResponseType(CERT_PROVISIONING_REQUEST_ERROR_4); } SignedProvisioningMessage signed_provisioning_msg; signed_provisioning_msg.set_message(serialized_message); signed_provisioning_msg.set_signature(request_signature); signed_provisioning_msg.set_provisioning_type(GetProvisioningType()); signed_provisioning_msg.set_oemcrypto_core_message(core_message); signed_provisioning_msg.set_protocol_version( SignedProvisioningMessage::VERSION_1_1); if (should_specify_algorithm) { HashAlgorithmProto proto_algorithm = HashAlgorithmProto::HASH_ALGORITHM_UNSPECIFIED; if (!OecAlgorithmToProtoAlgorithm(oec_algorithm, proto_algorithm)) { return CdmResponseType(UNSUPPORTED_SIGNATURE_HASH_ALGORITHM_3); } signed_provisioning_msg.set_hash_algorithm(proto_algorithm); } std::string serialized_request; signed_provisioning_msg.SerializeToString(&serialized_request); if (!wvcdm::Properties::provisioning_messages_are_binary()) { // Return request as web-safe base64 string *request = wvutil::Base64SafeEncodeNoPad(serialized_request); } else { *request = std::move(serialized_request); } return CdmResponseType(NO_ERROR); } CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal( wvutil::FileSystem* file_system, const std::string& origin, const std::string& spoid, CdmProvisioningRequest* request, std::string* default_url) { if (!crypto_session_->IsOpen()) { LOGE("Crypto session is not open"); return CdmResponseType(PROVISIONING_4_CRYPTO_SESSION_NOT_OPEN); } if (file_system == nullptr) { LOGE("file_system is nullptr but is required in provisioning 4"); return CdmResponseType(PROVISIONING_4_FILE_SYSTEM_IS_NULL); } const CdmSecurityLevel security_level = crypto_session_->GetSecurityLevel(); wvutil::FileSystem global_file_system; DeviceFiles global_file_handle(&global_file_system); if (!global_file_handle.Init(security_level)) { LOGE("Failed to initialize global DeviceFiles"); return CdmResponseType(PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES); } ProvisioningRequest provisioning_request; // Determine the current stage by checking if OEM cert exists. std::string stored_oem_cert; if (global_file_handle.HasOemCertificate()) { // This is second stage requesting for DRM cert. We try to use the stored // OEM cert. In case of error, we just fall back to the first stage // provisioning (request for an OEM cert). if (!RetrieveOemCertificateAndLoadPrivateKey( *crypto_session_, global_file_handle, stored_oem_cert)) { stored_oem_cert.clear(); LOGD("Deleting the stored OEM certificate due to unsuccessful read"); if (!global_file_handle.RemoveOemCertificate()) { // This should not happen. LOGE("Failed to delete the OEM certificate certificate"); } } } // Retrieve the Spoid, but put it to the client identification instead, so it // is encrypted. CdmAppParameterMap additional_parameter; CdmResponseType status = SetSpoidParameter(origin, spoid, &provisioning_request); if (status != NO_ERROR) return status; if (provisioning_request.has_spoid()) { additional_parameter[kKeyAppParameterSpoid] = provisioning_request.spoid(); provisioning_request.clear_spoid(); } if (provisioning_request.has_provider_id()) { additional_parameter[kKeyAppParameterProviderId] = provisioning_request.provider_id(); provisioning_request.clear_provider_id(); } if (provisioning_request.has_stable_id()) { additional_parameter[kKeyAppParameterStableId] = provisioning_request.stable_id(); provisioning_request.clear_stable_id(); } if (stored_oem_cert.empty()) { // This is the first stage provisioning. default_url->assign(kProvisioningServerUrl + kProv40FirstStageServerUrlSuffix); // First-stage provisioning always uses the WV production service cert for // encryption. ServiceCertificate wv_service_cert; status = wv_service_cert.Init(kCpProductionServiceCertificate); if (status != NO_ERROR) return status; // Since |stored_oem_cert| is empty, the client identification token will be // retrieved from OEMCrypto, which is the BCC in this case. status = FillEncryptedClientIdWithAdditionalParameter( stored_oem_cert, additional_parameter, provisioning_request, wv_service_cert); if (status != NO_ERROR) return status; } else { // This is the second stage provisioning. default_url->assign(kProvisioningServerUrl); // Since |stored_oem_cert| is non-empty, it will be used as the client // identification token. status = FillEncryptedClientIdWithAdditionalParameter( stored_oem_cert, additional_parameter, provisioning_request, *service_certificate_); if (status != NO_ERROR) return status; } std::string public_key; std::string public_key_signature; provisioning_40_wrapped_private_key_.clear(); provisioning_40_key_type_ = CryptoWrappedKey::kUninitialized; status = crypto_session_->GenerateCertificateKeyPair( &public_key, &public_key_signature, &provisioning_40_wrapped_private_key_, &provisioning_40_key_type_); if (status != NO_ERROR) return status; PublicKeyToCertify* key_to_certify = provisioning_request.mutable_certificate_public_key(); key_to_certify->set_public_key(public_key); key_to_certify->set_signature(public_key_signature); key_to_certify->set_key_type(provisioning_40_key_type_ == CryptoWrappedKey::kRsa ? PublicKeyToCertify::RSA : PublicKeyToCertify::ECC); std::string serialized_message; provisioning_request.SerializeToString(&serialized_message); SignedProvisioningMessage signed_provisioning_msg; signed_provisioning_msg.set_message(serialized_message); signed_provisioning_msg.set_provisioning_type(GetProvisioningType()); signed_provisioning_msg.set_protocol_version( SignedProvisioningMessage::VERSION_1_1); // Core message and request signature are added to the provisioning request // starting OEMCrypto v18 uint32_t api_version = 0; const bool core_message_signature_required = crypto_session_->GetApiVersion(&api_version) && (api_version >= OEM_CRYPTO_API_VERSION_SUPPORTS_PROV40_CORE_MESSAGE); if (core_message_signature_required) { std::string core_message; std::string request_signature; bool should_specify_algorithm; OEMCrypto_SignatureHashAlgorithm oec_algorithm = OEMCrypto_SHA1; status = crypto_session_->PrepareAndSignProvisioningRequest( serialized_message, &core_message, &request_signature, should_specify_algorithm, oec_algorithm); if (status != NO_ERROR) { LOGE("Failed to prepare provisioning 4.0 request: status = %d", static_cast(status)); return status; } if (core_message.empty()) { LOGE("Core message is empty"); return CdmResponseType(CERT_PROVISIONING_REQUEST_ERROR_4); } if (request_signature.empty()) { LOGE("Request signature is empty"); return CdmResponseType(CERT_PROVISIONING_REQUEST_ERROR_4); } signed_provisioning_msg.set_oemcrypto_core_message(core_message); signed_provisioning_msg.set_signature(request_signature); if (should_specify_algorithm) { HashAlgorithmProto proto_algorithm = HashAlgorithmProto::HASH_ALGORITHM_UNSPECIFIED; if (!OecAlgorithmToProtoAlgorithm(oec_algorithm, proto_algorithm)) { return CdmResponseType(UNSUPPORTED_SIGNATURE_HASH_ALGORITHM_4); } signed_provisioning_msg.set_hash_algorithm(proto_algorithm); } } std::string serialized_request; signed_provisioning_msg.SerializeToString(&serialized_request); if (!wvcdm::Properties::provisioning_messages_are_binary()) { // Return request as web-safe base64 string *request = wvutil::Base64SafeEncodeNoPad(serialized_request); } else { *request = std::move(serialized_request); } return CdmResponseType(NO_ERROR); } CdmResponseType CertificateProvisioning::FillEncryptedClientId( const std::string& client_token, ProvisioningRequest& provisioning_request, const ServiceCertificate& service_certificate) { CdmAppParameterMap app_parameter; return FillEncryptedClientIdWithAdditionalParameter( client_token, app_parameter, provisioning_request, service_certificate); } CdmResponseType CertificateProvisioning::FillEncryptedClientIdWithAdditionalParameter( const std::string& client_token, const CdmAppParameterMap& additional_parameter, ProvisioningRequest& provisioning_request, const ServiceCertificate& service_certificate) { if (!crypto_session_->IsOpen()) { return CdmResponseType(UNKNOWN_ERROR); } wvcdm::ClientIdentification id; CdmResponseType status = id.InitForProvisioningRequest(client_token, crypto_session_.get()); if (status != NO_ERROR) return status; video_widevine::ClientIdentification client_id; status = id.Prepare(additional_parameter, kEmptyString, &client_id); if (status != NO_ERROR) return status; if (!service_certificate.has_certificate()) { LOGE("Service certificate not staged"); return CdmResponseType(CERT_PROVISIONING_EMPTY_SERVICE_CERTIFICATE); } // Encrypt client identification return service_certificate.EncryptClientId( crypto_session_.get(), &client_id, provisioning_request.mutable_encrypted_client_id()); } CdmResponseType CertificateProvisioning::HandleProvisioning40Response( wvutil::FileSystem* file_system, const std::string& response_message) { ProvisioningResponse provisioning_response; if (response_message.empty() || !provisioning_response.ParseFromString(response_message)) { return CdmResponseType(PROVISIONING_4_RESPONSE_FAILED_TO_PARSE_MESSAGE); } if (provisioning_response.has_status() && provisioning_response.status() != ProvisioningResponse::NO_ERROR) { LOGE("Provisioning Response status: %d", provisioning_response.status()); switch (provisioning_response.status()) { case ProvisioningResponse::REVOKED_DEVICE_CREDENTIALS: case ProvisioningResponse::REVOKED_DEVICE_SERIES: return CdmResponseType(DEVICE_REVOKED); default: return CdmResponseType(PROVISIONING_4_RESPONSE_HAS_ERROR_STATUS); } } const std::string& device_certificate = provisioning_response.device_certificate(); if (device_certificate.empty()) { LOGE("Provisioning response has no certificate"); return CdmResponseType(PROVISIONING_4_RESPONSE_HAS_NO_CERTIFICATE); } if (provisioning_40_wrapped_private_key_.empty()) { LOGE("No private key was generated"); return CdmResponseType(PROVISIONING_4_NO_PRIVATE_KEY); } const CryptoWrappedKey private_key(provisioning_40_key_type_, provisioning_40_wrapped_private_key_); const CdmSecurityLevel security_level = crypto_session_->GetSecurityLevel(); CloseSession(); wvutil::FileSystem global_file_system; DeviceFiles global_file_handle(&global_file_system); if (!global_file_handle.Init(security_level)) { LOGE("Failed to initialize global DeviceFiles"); return CdmResponseType(PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES_2); } // Check the stage of the provisioning by checking if an OEM cert is already // stored in the file system. if (!global_file_handle.HasOemCertificate()) { // No OEM cert already stored => the response is expected to be an OEM cert. if (!global_file_handle.StoreOemCertificate(device_certificate, private_key)) { LOGE("Failed to store provisioning 4 OEM certificate"); return CdmResponseType(PROVISIONING_4_FAILED_TO_STORE_OEM_CERTIFICATE); } } else { // The response is assumed to be an DRM cert. DeviceFiles per_origin_file_handle(file_system); if (!per_origin_file_handle.Init(security_level)) { LOGE("Failed to initialize per-origin DeviceFiles"); return CdmResponseType( PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES_3); } if (!per_origin_file_handle.StoreCertificate(device_certificate, private_key)) { LOGE("Failed to store provisioning 4 DRM certificate"); return CdmResponseType(PROVISIONING_4_FAILED_TO_STORE_DRM_CERTIFICATE); } } return CdmResponseType(NO_ERROR); } // The response message consists of a device certificate and the // wrapped device private key (either RSA or ECC). The wrapped device // private key is loaded into the TEE, unwrapped, verified and // re-wrapped. The device certificate and re-wrapped device private // key are stored on the device. // // Returns NO_ERROR for success and CERT_PROVISIONING_RESPONSE_ERROR_? if fails. CdmResponseType CertificateProvisioning::HandleProvisioningResponse( wvutil::FileSystem* file_system, const CdmProvisioningResponse& response_message, std::string* cert, std::string* wrapped_key) { if (response_message.empty()) { LOGE("Provisioning response message is empty"); return CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_1); } std::string response; if (wvcdm::Properties::provisioning_messages_are_binary()) { response.assign(response_message); } else { // The response is base64 encoded in a JSON wrapper. // Extract it and decode it. On error return an empty string. const bool result = ExtractAndDecodeSignedMessage(response_message, &response); if (!result || response.empty()) { LOGE("Provisioning response message is an invalid JSON/base64 string: %s", response.c_str()); return CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_1); } } // Authenticates provisioning response using D1s (server key derived from // the provisioing request's input). Validate provisioning response and // stores private device RSA key and certificate. SignedProvisioningMessage signed_response; if (!signed_response.ParseFromString(response)) { LOGE("Failed to parse signed provisioining response"); return CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_2); } if (signed_response.provisioning_type() == SignedProvisioningMessage::PROVISIONING_40) { return HandleProvisioning40Response(file_system, signed_response.message()); } bool error = false; if (!signed_response.has_signature()) { LOGE("Signed response does not have signature"); error = true; } if (!signed_response.has_message()) { LOGE("Signed response does not have message"); error = true; } if (!signed_response.has_oemcrypto_core_message()) { LOGE("Signed response does not have core message"); error = true; } if (error) return CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_3); const std::string& signed_message = signed_response.message(); const std::string& signature = signed_response.signature(); const std::string& core_message = signed_response.oemcrypto_core_message(); ProvisioningResponse provisioning_response; if (!provisioning_response.ParseFromString(signed_message)) { LOGE("Failed to parse provisioning response"); return CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_4); } if (provisioning_response.has_status()) { if (provisioning_response.status() != ProvisioningResponse::NO_ERROR) { LOGE("Provisioning response status: %d", provisioning_response.status()); } switch (provisioning_response.status()) { case ProvisioningResponse::NO_ERROR: break; case ProvisioningResponse::REVOKED_DEVICE_CREDENTIALS: case ProvisioningResponse::REVOKED_DEVICE_SERIES: return CdmResponseType(DEVICE_REVOKED); default: return CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_10); } } CryptoWrappedKey private_key; const CdmResponseType status = crypto_session_->LoadProvisioning( signed_message, core_message, signature, &private_key.key()); if (status != NO_ERROR) { LOGE("LoadProvisioning failed: status = %d", static_cast(status)); return status; } const CdmSecurityLevel security_level = crypto_session_->GetSecurityLevel(); CloseSession(); // This is the entire certificate (SignedDrmCertificate). const std::string& device_cert_data = provisioning_response.device_certificate(); if (cert_type_ == kCertificateX509) { *cert = device_cert_data; *wrapped_key = private_key.key(); return CdmResponseType(NO_ERROR); } // Need to parse cert for key type. SignedDrmCertificate signed_device_cert; if (!signed_device_cert.ParseFromString(device_cert_data)) { LOGE("Failed to parse signed DRM certificate"); return CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_9); } DrmCertificate device_cert; if (!device_cert.ParseFromString(signed_device_cert.drm_certificate())) { LOGE("Failed to parse DRM certificate"); return CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_9); } if (!device_cert.has_algorithm()) { LOGW("DRM certificate does not specify algorithm type, assuming RSA"); private_key.set_type(CryptoWrappedKey::kRsa); } else { switch (device_cert.algorithm()) { case DrmCertificate::RSA: private_key.set_type(CryptoWrappedKey::kRsa); break; case DrmCertificate::ECC_SECP256R1: case DrmCertificate::ECC_SECP384R1: case DrmCertificate::ECC_SECP521R1: private_key.set_type(CryptoWrappedKey::kEcc); break; default: LOGE("Unknown DRM key type: algorithm = %d", static_cast(device_cert.algorithm())); return CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_9); } } // The certificate will be stored to the device as the final step in // the device provisioning process. DeviceFiles handle(file_system); if (!handle.Init(security_level)) { LOGE("Failed to initialize DeviceFiles"); return CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_7); } if (!handle.StoreCertificate(device_cert_data, private_key)) { LOGE("Failed to store provisioning certificate"); return CdmResponseType(CERT_PROVISIONING_RESPONSE_ERROR_8); } return CdmResponseType(NO_ERROR); } // Provisioning response is a base64-encoded protobuf, optionally within a // JSON wrapper. If the JSON wrapper is present, extract the embedded response // message. Then perform the base64 decode and return the result. // // If an error occurs during the parse or the decode, return an empty string. // static bool CertificateProvisioning::ExtractAndDecodeSignedMessage( const std::string& provisioning_response, std::string* result) { const std::string json_start_substr("\"signedResponse\": \""); const std::string json_end_substr("\""); std::string message_string; if (result == nullptr) { LOGE("Output parameter |result| is not provided"); return false; } size_t start = provisioning_response.find(json_start_substr); if (start == provisioning_response.npos) { // Message is not properly wrapped - reject it. LOGE("Cannot locate start substring '%s'", json_start_substr.c_str()); result->clear(); return false; } // Appears to be JSON-wrapped protobuf - find end of protobuf portion. const size_t end = provisioning_response.find( json_end_substr, start + json_start_substr.length()); if (end == provisioning_response.npos) { LOGE("Cannot locate end substring '%s'", json_end_substr.c_str()); result->clear(); return false; } size_t b64_string_size = end - start - json_start_substr.length(); message_string.assign(provisioning_response, start + json_start_substr.length(), b64_string_size); if (message_string.empty()) { LOGE("CDM provisioning response is empty"); result->clear(); return false; } // Decode the base64-encoded message. const std::vector decoded_message = wvutil::Base64SafeDecode(message_string); result->assign(decoded_message.begin(), decoded_message.end()); return true; } bool CertificateProvisioning::ExtractDeviceInfo( const std::string& device_certificate, std::string* serial_number, uint32_t* system_id, int64_t* creation_time_seconds, int64_t* expiration_time_seconds) { LOGV("Extracting device info"); if (serial_number == nullptr && system_id == nullptr) { LOGE("Output parameters |serial_number| and |system_id| not provided"); return false; } // Get serial number and system ID from certificate SignedDrmCertificate signed_drm_certificate; if (!signed_drm_certificate.ParseFromString(device_certificate) || !signed_drm_certificate.has_drm_certificate()) { LOGE("Failed to parse signed DRM device certificate"); return false; } DrmCertificate drm_certificate; if (!drm_certificate.ParseFromString( signed_drm_certificate.drm_certificate()) || (drm_certificate.type() != DrmCertificate::DEVICE)) { LOGE("Failed to parse DRM device certificate message"); return false; } if (serial_number != nullptr) { *serial_number = drm_certificate.serial_number(); } if (system_id != nullptr) { *system_id = drm_certificate.system_id(); } if (creation_time_seconds != nullptr) { *creation_time_seconds = drm_certificate.has_creation_time_seconds() ? drm_certificate.creation_time_seconds() : INVALID_TIME; } if (expiration_time_seconds != nullptr) { *expiration_time_seconds = drm_certificate.has_expiration_time_seconds() ? drm_certificate.expiration_time_seconds() : INVALID_TIME; } return true; } void CertificateProvisioning::CloseSession() { if (crypto_session_->IsOpen()) crypto_session_->Close(); } CdmResponseType CertificateProvisioning::CloseSessionOnError( CdmResponseType status) { if (status != NO_ERROR) CloseSession(); return status; } } // namespace wvcdm