// Copyright 2012 Google Inc. All Rights Reserved. #include "license.h" #include #include "crypto_engine.h" #include "crypto_session.h" #include "log.h" #include "policy_engine.h" #include "properties.h" #include "string_conversions.h" #include "wv_cdm_constants.h" namespace { std::string kCompanyNameKey = "company_name"; std::string kCompanyNameValueGoogle = "Google"; std::string kModelNameKey = "model_name"; std::string kArchitectureNameKey = "architecture_name"; std::string kDeviceNameKey = "device_name"; std::string kProductNameKey = "product_name"; std::string kBuildInfoKey = "build_info"; std::string kDeviceIdKey = "device_id"; } namespace wvcdm { // Protobuf generated classes. using video_widevine_server::sdk::ClientIdentification; using video_widevine_server::sdk::ClientIdentification_NameValue; using video_widevine_server::sdk::LicenseRequest; using video_widevine_server::sdk::LicenseRequest_ContentIdentification; using video_widevine_server::sdk::LicenseRequest_ContentIdentification_CENC; using video_widevine_server::sdk::LicenseRequest_ContentIdentification_ExistingLicense; using video_widevine_server::sdk::License; using video_widevine_server::sdk::License_KeyContainer; using video_widevine_server::sdk::LicenseError; using video_widevine_server::sdk::SignedMessage; using video_widevine_server::sdk::STREAMING; using video_widevine_server::sdk::VERSION_2_1; static std::vector ExtractContentKeys(const License& license) { std::vector key_array; // Extract content key(s) for (int i = 0; i < license.key_size(); ++i) { // TODO(fredgc): Figure out what key.type is for Generic Keys. // If the generic signing key is CONTENT, then the extra size log below is good. // If it is SIGNING, then we are ignoring it. -- we should fix that by adding // an else clause to this if statement. if (license.key(i).type() == License_KeyContainer::CONTENT) { CryptoKey key; key.set_key_id(license.key(i).id()); // Strip off PKCS#5 padding - since we know the key is 16 or 32 bytes, the // padding will always be 16 bytes. size_t length = license.key(i).key().size() - 16; key.set_key_data( license.key(i).key().substr(0, length)); key.set_key_data_iv(license.key(i).iv()); if (license.key(i).has_key_control()) { key.set_key_control( license.key(i).key_control().key_control_block()); key.set_key_control_iv( license.key(i).key_control().iv()); } key_array.push_back(key); } } return key_array; } CdmLicense::CdmLicense(): session_(NULL) {} CdmLicense::~CdmLicense() {} bool CdmLicense::Init(const std::string& token, CryptoSession* session, PolicyEngine* policy_engine) { if (token.size() == 0) return false; if (session == NULL || !session->IsValid() || !session->IsOpen()) return false; token_ = token; session_ = session; policy_engine_ = policy_engine; return true; } bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, CdmKeyMessage* signed_request, std::string* server_url) { if (!session_ || token_.empty()) { return false; } if (init_data.empty()) { LOGE("CdmLicense::PrepareKeyRequest : No init data provided;"); return false; } if (!signed_request) { LOGE("CdmLicense::PrepareKeyRequest : No signed request provided."); return false; } if (!server_url) { LOGE("CdmLicense::PrepareKeyRequest : No server url provided."); return false; } // TODO(gmorgan): Request ID owned by session? std::string request_id; session_->GenerateRequestId(request_id); LicenseRequest license_request; ClientIdentification* client_id = license_request.mutable_client_id(); if (Properties::use_certificates_as_identification()) client_id->set_type(ClientIdentification::DEVICE_CERTIFICATE); else client_id->set_type(ClientIdentification::KEYBOX); client_id->set_token(token_); ClientIdentification_NameValue* client_info; CdmAppParameterMap::const_iterator iter; for (iter = app_parameters.begin(); iter != app_parameters.end(); iter++) { client_info = client_id->add_client_info(); client_info->set_name(iter->first); client_info->set_value(iter->second); } std::string value; client_info = client_id->add_client_info(); client_info->set_name(kCompanyNameKey); client_info->set_value(kCompanyNameValueGoogle); if (Properties::GetModelName(value)) { client_info = client_id->add_client_info(); client_info->set_name(kModelNameKey); client_info->set_value(value); } if (Properties::GetArchitectureName(value)) { client_info = client_id->add_client_info(); client_info->set_name(kArchitectureNameKey); client_info->set_value(value); } if (Properties::GetDeviceName(value)) { client_info = client_id->add_client_info(); client_info->set_name(kDeviceNameKey); client_info->set_value(value); } if (Properties::GetProductName(value)) { client_info = client_id->add_client_info(); client_info->set_name(kProductNameKey); client_info->set_value(value); } if (Properties::GetBuildInfo(value)) { client_info = client_id->add_client_info(); client_info->set_name(kBuildInfoKey); client_info->set_value(value); } if (CryptoEngine::GetInstance()->GetDeviceUniqueId(&value)) { client_info = client_id->add_client_info(); client_info->set_name(kDeviceIdKey); client_info->set_value(value); } // Content Identification may be a cenc_id, a webm_id or a license_id LicenseRequest_ContentIdentification* content_id = license_request.mutable_content_id(); LicenseRequest_ContentIdentification_CENC* cenc_content_id = content_id->mutable_cenc_id(); cenc_content_id->add_pssh(init_data); cenc_content_id->set_license_type(STREAMING); cenc_content_id->set_request_id(request_id); // TODO(jfore): The time field will be updated once the cdm wrapper // has been updated to pass us in the time. license_request.set_request_time(0); license_request.set_type(LicenseRequest::NEW); // Get/set the nonce. This value will be reflected in the Key Control Block // of the license response. uint32_t nonce; if (!session_->GenerateNonce(&nonce)) { return false; } license_request.set_key_control_nonce(UintToString(nonce)); LOGD("PrepareKeyRequest: nonce=%u", nonce); license_request.set_protocol_version(VERSION_2_1); // License request is complete. Serialize it. std::string serialized_license_req; license_request.SerializeToString(&serialized_license_req); if (Properties::use_certificates_as_identification()) key_request_ = serialized_license_req; // Derive signing and encryption keys and construct signature. std::string license_request_signature; if (!session_->PrepareRequest(serialized_license_req, &license_request_signature, false)) { signed_request->clear(); return false; } if (license_request_signature.empty()) { signed_request->clear(); return false; } // Put serialize license request and signature together SignedMessage signed_message; signed_message.set_type(SignedMessage::LICENSE_REQUEST); signed_message.set_signature(license_request_signature); signed_message.set_msg(serialized_license_req); signed_message.SerializeToString(signed_request); *server_url = server_url_; return true; } bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request, std::string* server_url) { if (!session_) { return false; } if (!signed_request) { LOGE("CdmLicense::PrepareKeyRenewalRequest : No signed request provided."); return false; } if (!server_url) { LOGE("CdmLicense::PrepareKeyRenewalRequest : No server url provided."); return false; } LicenseRequest license_request; license_request.set_type(LicenseRequest::RENEWAL); LicenseRequest_ContentIdentification_ExistingLicense* current_license = license_request.mutable_content_id()->mutable_license(); current_license->mutable_license_id()->CopyFrom(license_id_); // Get/set the nonce. This value will be reflected in the Key Control Block // of the license response. uint32_t nonce; if (!session_->GenerateNonce(&nonce)) { return false; } license_request.set_key_control_nonce(UintToString(nonce)); LOGD("PrepareKeyRenewalRequest: nonce=%u", nonce); license_request.set_protocol_version(VERSION_2_1); // License request is complete. Serialize it. std::string serialized_license_req; license_request.SerializeToString(&serialized_license_req); // Construct signature. std::string license_request_signature; if (!session_->PrepareRenewalRequest(serialized_license_req, &license_request_signature)) return false; if (license_request_signature.empty()) return false; // Put serialize license request and signature together SignedMessage signed_message; signed_message.set_type(SignedMessage::LICENSE_REQUEST); signed_message.set_signature(license_request_signature); signed_message.set_msg(serialized_license_req); signed_message.SerializeToString(signed_request); *server_url = server_url_; return true; } CdmResponseType CdmLicense::HandleKeyResponse( const CdmKeyResponse& license_response) { if (!session_) { return KEY_ERROR; } if (license_response.empty()) { LOGE("CdmLicense::HandleKeyResponse : Empty license response."); return KEY_ERROR; } SignedMessage signed_response; if (!signed_response.ParseFromString(license_response)) return KEY_ERROR; if (signed_response.type() == SignedMessage::ERROR) { return HandleKeyErrorResponse(signed_response); } if (!signed_response.has_signature()) return KEY_ERROR; License license; if (!license.ParseFromString(signed_response.msg())) return KEY_ERROR; if (Properties::use_certificates_as_identification()) { if (!signed_response.has_session_key()) return KEY_ERROR; if (!session_->GenerateDerivedKeys(key_request_, signed_response.session_key())) return KEY_ERROR; } // Extract mac key std::string mac_key_iv; std::string mac_key; if (license.policy().can_renew()) { for (int i = 0; i < license.key_size(); ++i) { if (license.key(i).type() == License_KeyContainer::SIGNING) { mac_key_iv.assign(license.key(i).iv()); // Strip off PKCS#5 padding mac_key.assign(license.key(i).key().data(), MAC_KEY_SIZE); } } if (mac_key_iv.size() != KEY_IV_SIZE || mac_key.size() != MAC_KEY_SIZE) { return KEY_ERROR; } // License Id should not be empty for renewable license if (!license.has_id()) return KEY_ERROR; license_id_.CopyFrom(license.id()); } std::vector key_array = ExtractContentKeys(license); if (!key_array.size()) { LOGE("CdmLicense::HandleKeyResponse : No content keys."); return KEY_ERROR; } if (license.policy().has_renewal_server_url()) { server_url_ = license.policy().renewal_server_url(); } policy_engine_->SetLicense(license); if (session_->LoadKeys(signed_response.msg(), signed_response.signature(), mac_key_iv, mac_key, key_array.size(), &key_array[0])) { return KEY_ADDED; } else { return KEY_ERROR; } } CdmResponseType CdmLicense::HandleKeyRenewalResponse( const CdmKeyResponse& license_response) { if (!session_) { return KEY_ERROR; } if (license_response.empty()) { LOGE("CdmLicense::HandleKeyRenewalResponse : Empty license response."); return KEY_ERROR; } SignedMessage signed_response; if (!signed_response.ParseFromString(license_response)) return KEY_ERROR; if (signed_response.type() == SignedMessage::ERROR) { return HandleKeyErrorResponse(signed_response); } if (!signed_response.has_signature()) return KEY_ERROR; License license; if (!license.ParseFromString(signed_response.msg())) return KEY_ERROR; if (!license.has_id()) return KEY_ERROR; if (license.id().version() > license_id_.version()) { // This is the normal case. license_id_.CopyFrom(license.id()); if (license.policy().has_renewal_server_url() && license.policy().renewal_server_url().size() > 0) { server_url_ = license.policy().renewal_server_url(); } policy_engine_->UpdateLicense(license); std::vector key_array = ExtractContentKeys(license); if (session_->RefreshKeys(signed_response.msg(), signed_response.signature(), key_array.size(), &key_array[0])) { return KEY_ADDED; } else { return KEY_ERROR; } } // This isn't supposed to happen. // TODO(jfore): Handle wrap? We can miss responses and that should be // considered normal until retries are exhausted. return KEY_ERROR; } CdmResponseType CdmLicense::HandleKeyErrorResponse( const SignedMessage& signed_message) { LicenseError license_error; if (!license_error.ParseFromString(signed_message.msg())) return KEY_ERROR; switch (license_error.error_code()) { case LicenseError::INVALID_CREDENTIALS: return NEED_PROVISIONING; case LicenseError::REVOKED_CREDENTIALS: return DEVICE_REVOKED; case LicenseError::SERVICE_UNAVAILABLE: default: return KEY_ERROR; } } } // namespace wvcdm