// Copyright 2013 Google Inc. All Rights Reserved. #include "certificate_provisioning.h" #include "device_files.h" #include "file_store.h" #include "license_protocol.pb.h" #include "log.h" #include "properties.h" #include "service_certificate.h" #include "string_conversions.h" #include "wv_cdm_constants.h" namespace { // 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"; /* * 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. */ void 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; size_t start = provisioning_response.find(json_start_substr); if (start == provisioning_response.npos) { // Message is not properly wrapped - reject it. LOGE("ExtractAndDecodeSignedMessage: cannot locate start substring"); result->clear(); return; } else { // Appears to be JSON-wrapped protobuf - find end of protobuf portion. size_t end = provisioning_response.find(json_end_substr, start + json_start_substr.length()); if (end == provisioning_response.npos) { LOGE("ExtractAndDecodeSignedMessage: cannot locate end substring"); result->clear(); return; } 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("ExtractAndDecodeSignedMessage: CdmProvisioningResponse is empty"); result->clear(); return; } // Decode the base64-encoded message. const std::vector decoded_message = wvcdm::Base64SafeDecode(message_string); result->assign(decoded_message.begin(), decoded_message.end()); } } // namespace namespace wvcdm { // Protobuf generated classes. using video_widevine::ClientIdentification; using video_widevine::EncryptedClientIdentification; using video_widevine::ProvisioningOptions; using video_widevine::ProvisioningRequest; using video_widevine::ProvisioningResponse; using video_widevine::SignedProvisioningMessage; /* * Return the ClientIdentification message token type for provisioning request. * NOTE: a DRM Cert should never be presented to the provisioning server. */ bool CertificateProvisioning::GetProvisioningTokenType( ClientIdentification::TokenType* token_type) { CdmClientTokenType token = crypto_session_.GetPreProvisionTokenType(); switch (token) { case kClientTokenKeybox: *token_type = ClientIdentification::KEYBOX; return true; case kClientTokenOemCert: *token_type = ClientIdentification::OEM_DEVICE_CERTIFICATE; return true; case kClientTokenDrmCert: default: // shouldn't happen LOGE("CertificateProvisioning::GetProvisioningTokenType: unexpected " "provisioning type: %d", token); return false; } } /* * 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. */ bool CertificateProvisioning::SetSpoidParameter( const std::string& origin, const std::string& spoid, ProvisioningRequest* request) { if (!request) { LOGE("CertificateProvisioning::SetSpoidParameter: No request buffer " "passed to method."); return false; } 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()) { request->set_provider_id(service_certificate_->provider_id()); } else { LOGE("CertificateProvisioning::SetSpoidParameter: Failure getting " "provider ID"); return false; } } else if (origin != EMPTY_ORIGIN) { // Legacy behavior - Concatenate Unique ID with Origin std::string device_unique_id; if (!crypto_session_.GetInternalDeviceUniqueId(&device_unique_id)) { LOGE("CertificateProvisioning::SetSpoidParameter: Failure getting " "device unique ID"); return false; } request->set_stable_id(device_unique_id + origin); } // No else clause, by design. It is valid to do nothing. return true; } /* * Return the provisioning protocol version - dictated by OEMCrypto * support for OEM certificates. */ SignedProvisioningMessage::ProtocolVersion CertificateProvisioning::GetProtocolVersion() { if (crypto_session_.GetPreProvisionTokenType() == kClientTokenOemCert) return SignedProvisioningMessage::VERSION_3; else return SignedProvisioningMessage::VERSION_2; } /* * 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( SecurityLevel 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 (!default_url) { LOGE("GetProvisioningRequest: pointer for returning URL is NULL"); return CERT_PROVISIONING_REQUEST_ERROR_1; } default_url->assign(kProvisioningServerUrl); CdmResponseType status = crypto_session_.Open(requested_security_level); if (NO_ERROR != status) { LOGE("GetProvisioningRequest: fails to create a crypto session"); return status; } // Prepare device provisioning request. ProvisioningRequest provisioning_request; std::string token; ClientIdentification* client_id = provisioning_request.mutable_client_id(); ClientIdentification::TokenType token_type; if (!GetProvisioningTokenType(&token_type)) { LOGE("GetProvisioningRequest: bad token type"); return CERT_PROVISIONING_CLIENT_TOKEN_ERROR_1; } if (!crypto_session_.GetProvisioningToken(&token)) { LOGE("GetProvisioningRequest: failure getting provisioning token"); return CERT_PROVISIONING_CLIENT_TOKEN_ERROR_2; } client_id->set_token(token); client_id->set_type(token_type); #if 0 // TODO(gmorgan) Encrypt ClientIdentification. Pending Design. if (service_certificate_->has_certificate()) { EncryptedClientIdentification* encrypted_client_id = provisioning_request.mutable_encrypted_client_id(); CdmResponseType status; status = service_certificate_->EncryptClientId(&crypto_session_, client_id, encrypted_client_id); if (status == NO_ERROR) { provisioning_request.clear_client_id(); } else { provisioning_request.clear_encrypted_client_id(); } return status; } #endif uint32_t nonce; if (!crypto_session_.GenerateNonce(&nonce)) { LOGE("GetProvisioningRequest: fails to generate a nonce"); return CERT_PROVISIONING_NONCE_GENERATION_ERROR; } // The provisioning server does not convert the nonce to uint32_t, it just // passes the binary data to the response message. std::string the_nonce(reinterpret_cast(&nonce), sizeof(nonce)); provisioning_request.set_nonce(the_nonce); ProvisioningOptions* options = provisioning_request.mutable_options(); switch (cert_type) { case kCertificateWidevine: options->set_certificate_type( video_widevine::ProvisioningOptions_CertificateType_WIDEVINE_DRM); break; case kCertificateX509: options->set_certificate_type( video_widevine::ProvisioningOptions_CertificateType_X509); break; default: LOGE("GetProvisioningRequest: unknown certificate type %ld", cert_type); return CERT_PROVISIONING_INVALID_CERT_TYPE; } cert_type_ = cert_type; options->set_certificate_authority(cert_authority); if (!SetSpoidParameter(origin, spoid, &provisioning_request)) { return CERT_PROVISIONING_GET_KEYBOX_ERROR_2; } std::string serialized_message; provisioning_request.SerializeToString(&serialized_message); // Derives signing and encryption keys and constructs signature. std::string request_signature; if (!crypto_session_.PrepareRequest(serialized_message, true, &request_signature)) { LOGE("GetProvisioningRequest: fails to prepare request"); return CERT_PROVISIONING_REQUEST_ERROR_3; } if (request_signature.empty()) { LOGE("GetProvisioningRequest: request signature is empty"); return 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_protocol_version(GetProtocolVersion()); std::string serialized_request; signed_provisioning_msg.SerializeToString(&serialized_request); if (!wvcdm::Properties::provisioning_messages_are_binary()) { // Return request as web-safe base64 string std::vector request_vector(serialized_request.begin(), serialized_request.end()); request->assign(Base64SafeEncodeNoPad(request_vector)); } else { request->swap(serialized_request); } return NO_ERROR; } /* * The response message consists of a device certificate and the device RSA key. * The device RSA key is stored in the T.E.E. The device certificate is stored * in the device. * * Returns NO_ERROR for success and CERT_PROVISIONING_RESPONSE_ERROR_? if fails. */ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( FileSystem* file_system, const CdmProvisioningResponse& response_message, std::string* cert, std::string* wrapped_key) { if (response_message.empty()) { LOGE("HandleProvisioningResponse: response message is empty."); return 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. ExtractAndDecodeSignedMessage(response_message, &response); if (response.empty()) { LOGE("HandleProvisioningResponse: response message is " "an invalid JSON/base64 string."); return 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("HandleProvisioningResponse: fails to parse signed response"); return CERT_PROVISIONING_RESPONSE_ERROR_2; } bool error = false; if (!signed_response.has_signature()) { LOGE("HandleProvisioningResponse: signature not found"); error = true; } if (!signed_response.has_message()) { LOGE("HandleProvisioningResponse: message not found"); error = true; } if (error) return CERT_PROVISIONING_RESPONSE_ERROR_3; const std::string& signed_message = signed_response.message(); const std::string& signature = signed_response.signature(); ProvisioningResponse provisioning_response; if (!provisioning_response.ParseFromString(signed_message)) { LOGE("HandleProvisioningResponse: Fails to parse signed message"); return CERT_PROVISIONING_RESPONSE_ERROR_4; } if (!provisioning_response.has_device_rsa_key()) { LOGE("HandleProvisioningResponse: key not found"); return CERT_PROVISIONING_RESPONSE_ERROR_5; } // If Provisioning 3.0 (OEM Cert provisioned), verify that the // message is properly signed. if (crypto_session_.GetPreProvisionTokenType() == kClientTokenOemCert) { if (service_certificate_->VerifySignedMessage(signed_message, signature) != NO_ERROR) { LOGE("HandleProvisioningResponse: message not properly signed"); return CERT_PROVISIONING_RESPONSE_ERROR_6; } } const std::string& new_private_key = provisioning_response.device_rsa_key(); const std::string& nonce = provisioning_response.nonce(); const std::string& iv = provisioning_response.device_rsa_key_iv(); const std::string& wrapping_key = (provisioning_response.has_wrapping_key()) ? provisioning_response.wrapping_key() : std::string(); std::string wrapped_private_key; if (!crypto_session_.RewrapCertificate(signed_message, signature, nonce, new_private_key, iv, wrapping_key, &wrapped_private_key)) { LOGE("HandleProvisioningResponse: RewrapCertificate fails"); return CERT_PROVISIONING_RESPONSE_ERROR_6; } crypto_session_.Close(); if (cert_type_ == kCertificateX509) { *cert = provisioning_response.device_certificate(); *wrapped_key = wrapped_private_key; return NO_ERROR; } // This is the entire certificate (SignedDrmDeviceCertificate). // This will be stored to the device as the final step in the device // provisioning process. const std::string& device_certificate = provisioning_response.device_certificate(); DeviceFiles handle(file_system); if (!handle.Init(crypto_session_.GetSecurityLevel())) { LOGE("HandleProvisioningResponse: failed to init DeviceFiles"); return CERT_PROVISIONING_RESPONSE_ERROR_7; } if (!handle.StoreCertificate(device_certificate, wrapped_private_key)) { LOGE("HandleProvisioningResponse: failed to save provisioning certificate"); return CERT_PROVISIONING_RESPONSE_ERROR_8; } return NO_ERROR; } } // namespace wvcdm