// 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 "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"; } namespace wvcdm { // Protobuf generated classes. using video_widevine::ClientIdentification; using video_widevine::ProvisioningOptions; using video_widevine::ProvisioningRequest; using video_widevine::ProvisioningResponse; using video_widevine::SignedProvisioningMessage; /* * This function converts SignedProvisioningRequest into base64 string. It then * wraps it in JSON format expected by the frontend. This server requires a * "web-safe" base 64 encoding, where '+' becomes '-' and '/' becomes '_'. * * Returns the JSON formated string in *request. The JSON string will be * appended as a query parameter, i.e. signedRequest=. All base64 '=' padding chars must be removed. * * The JSON formated request takes the following format: * * base64 encoded message */ void CertificateProvisioning::ComposeJsonRequestAsQueryString( const std::string& message, CdmProvisioningRequest* request) { // Performs base64 encoding for message std::vector message_vector(message.begin(), message.end()); std::string message_b64 = Base64SafeEncodeNoPad(message_vector); request->assign(message_b64); } /* * 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() && false /* TODO(gmorgan): use provider ID. */) { // Use the provider ID from the service certificate // TODO(gmorgan): use provider ID. // request->set_provider_id(???); } 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("CryptoSession::GetStableIdField: Failure to get 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; } /* * Composes a device provisioning request and output the request in JSON format * in *request. It also returns the default url for the provisioning server * in *default_url. * * 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 sts = crypto_session_.Open(requested_security_level); if (NO_ERROR != sts) { LOGE("GetProvisioningRequest: fails to create a crypto session"); return sts; } // Prepares 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); 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); // Converts request into JSON string ComposeJsonRequestAsQueryString(serialized_request, request); return NO_ERROR; } /* * Parses the input json_str and locates substring using start_substr and * end_stubstr. The found base64 substring is then decoded and returns * in *result. * * Returns true for success and false if fails. */ bool CertificateProvisioning::ParseJsonResponse( const CdmProvisioningResponse& json_str, const std::string& start_substr, const std::string& end_substr, std::string* result) { std::string b64_string; size_t start = json_str.find(start_substr); if (start == json_str.npos) { LOGE("ParseJsonResponse: cannot find start substring"); return false; } size_t end = json_str.find(end_substr, start + start_substr.length()); if (end == json_str.npos) { LOGE("ParseJsonResponse cannot locate end substring"); return false; } size_t b64_string_size = end - start - start_substr.length(); b64_string.assign(json_str, start + start_substr.length(), b64_string_size); // Decodes base64 substring and returns it in *result std::vector result_vector = Base64SafeDecode(b64_string); result->assign(result_vector.begin(), result_vector.end()); return true; } /* * 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, std::string* cert, std::string* wrapped_key) { // Extracts signed response from JSON string, decodes base64 signed response const std::string kMessageStart = "\"signedResponse\": \""; const std::string kMessageEnd = "\""; std::string serialized_signed_response; if (!ParseJsonResponse(response, kMessageStart, kMessageEnd, &serialized_signed_response)) { LOGE("Fails to extract signed serialized response from JSON response"); 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(serialized_signed_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(); 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; } const std::string& enc_rsa_key = provisioning_response.device_rsa_key(); const std::string& nonce = provisioning_response.nonce(); const std::string& rsa_key_iv = provisioning_response.device_rsa_key_iv(); const std::string& wrapping_key = (provisioning_response.has_wrapping_key()) ? provisioning_response.wrapping_key() : std::string(); const std::string& signature = signed_response.signature(); std::string wrapped_rsa_key; if (!crypto_session_.RewrapCertificate(signed_message, signature, nonce, enc_rsa_key, rsa_key_iv, wrapping_key, &wrapped_rsa_key)) { LOGE("HandleProvisioningResponse: RewrapDeviceRSAKey fails"); return CERT_PROVISIONING_RESPONSE_ERROR_6; } crypto_session_.Close(); if (cert_type_ == kCertificateX509) { *cert = provisioning_response.device_certificate(); *wrapped_key = wrapped_rsa_key; return NO_ERROR; } 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_rsa_key)) { LOGE("HandleProvisioningResponse: failed to save provisioning certificate"); return CERT_PROVISIONING_RESPONSE_ERROR_8; } return NO_ERROR; } } // namespace wvcdm