| // |
| // Copyright (C) 2015 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| |
| #include "attestation/server/attestation_service.h" |
| |
| #include <string> |
| |
| #include <base/callback.h> |
| #include <brillo/bind_lambda.h> |
| #include <brillo/data_encoding.h> |
| #include <brillo/http/http_utils.h> |
| #include <brillo/mime_utils.h> |
| #include <crypto/sha2.h> |
| |
| #include "attestation/common/attestation_ca.pb.h" |
| #include "attestation/common/database.pb.h" |
| #include "attestation/server/database_impl.h" |
| |
| namespace { |
| |
| #ifndef USE_TEST_ACA |
| const char kACAWebOrigin[] = "https://chromeos-ca.gstatic.com"; |
| #else |
| const char kACAWebOrigin[] = "https://asbestos-qa.corp.google.com"; |
| #endif |
| const size_t kNonceSize = 20; // As per TPM_NONCE definition. |
| const int kNumTemporalValues = 5; |
| |
| } // namespace |
| |
| namespace attestation { |
| |
| AttestationService::AttestationService() |
| : attestation_ca_origin_(kACAWebOrigin), weak_factory_(this) {} |
| |
| bool AttestationService::Initialize() { |
| LOG(INFO) << "Attestation service started."; |
| worker_thread_.reset(new base::Thread("Attestation Service Worker")); |
| worker_thread_->StartWithOptions( |
| base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); |
| if (!tpm_utility_) { |
| default_tpm_utility_.reset(new TpmUtilityV1()); |
| if (!default_tpm_utility_->Initialize()) { |
| return false; |
| } |
| tpm_utility_ = default_tpm_utility_.get(); |
| } |
| if (!crypto_utility_) { |
| default_crypto_utility_.reset(new CryptoUtilityImpl(tpm_utility_)); |
| crypto_utility_ = default_crypto_utility_.get(); |
| } |
| if (!database_) { |
| default_database_.reset(new DatabaseImpl(crypto_utility_)); |
| worker_thread_->task_runner()->PostTask( |
| FROM_HERE, base::Bind(&DatabaseImpl::Initialize, |
| base::Unretained(default_database_.get()))); |
| database_ = default_database_.get(); |
| } |
| if (!key_store_) { |
| pkcs11_token_manager_.reset(new chaps::TokenManagerClient()); |
| default_key_store_.reset(new Pkcs11KeyStore(pkcs11_token_manager_.get())); |
| key_store_ = default_key_store_.get(); |
| } |
| return true; |
| } |
| |
| void AttestationService::CreateGoogleAttestedKey( |
| const CreateGoogleAttestedKeyRequest& request, |
| const CreateGoogleAttestedKeyCallback& callback) { |
| auto result = std::make_shared<CreateGoogleAttestedKeyReply>(); |
| base::Closure task = |
| base::Bind(&AttestationService::CreateGoogleAttestedKeyTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = base::Bind( |
| &AttestationService::TaskRelayCallback<CreateGoogleAttestedKeyReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::CreateGoogleAttestedKeyTask( |
| const CreateGoogleAttestedKeyRequest& request, |
| const std::shared_ptr<CreateGoogleAttestedKeyReply>& result) { |
| LOG(INFO) << "Creating attested key: " << request.key_label(); |
| if (!IsPreparedForEnrollment()) { |
| LOG(ERROR) << "Attestation: TPM is not ready."; |
| result->set_status(STATUS_NOT_READY); |
| return; |
| } |
| if (!IsEnrolled()) { |
| std::string enroll_request; |
| if (!CreateEnrollRequest(&enroll_request)) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| std::string enroll_reply; |
| if (!SendACARequestAndBlock(kEnroll, enroll_request, &enroll_reply)) { |
| result->set_status(STATUS_CA_NOT_AVAILABLE); |
| return; |
| } |
| std::string server_error; |
| if (!FinishEnroll(enroll_reply, &server_error)) { |
| if (server_error.empty()) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| result->set_status(STATUS_REQUEST_DENIED_BY_CA); |
| result->set_server_error(server_error); |
| return; |
| } |
| } |
| CertifiedKey key; |
| if (!CreateKey(request.username(), request.key_label(), request.key_type(), |
| request.key_usage(), &key)) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| std::string certificate_request; |
| std::string message_id; |
| if (!CreateCertificateRequest(request.username(), key, |
| request.certificate_profile(), request.origin(), |
| &certificate_request, &message_id)) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| std::string certificate_reply; |
| if (!SendACARequestAndBlock(kGetCertificate, certificate_request, |
| &certificate_reply)) { |
| result->set_status(STATUS_CA_NOT_AVAILABLE); |
| return; |
| } |
| std::string certificate_chain; |
| std::string server_error; |
| if (!FinishCertificateRequest(certificate_reply, request.username(), |
| request.key_label(), message_id, &key, |
| &certificate_chain, &server_error)) { |
| if (server_error.empty()) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| result->set_status(STATUS_REQUEST_DENIED_BY_CA); |
| result->set_server_error(server_error); |
| return; |
| } |
| result->set_certificate_chain(certificate_chain); |
| } |
| |
| void AttestationService::GetKeyInfo(const GetKeyInfoRequest& request, |
| const GetKeyInfoCallback& callback) { |
| auto result = std::make_shared<GetKeyInfoReply>(); |
| base::Closure task = base::Bind(&AttestationService::GetKeyInfoTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = |
| base::Bind(&AttestationService::TaskRelayCallback<GetKeyInfoReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::GetKeyInfoTask( |
| const GetKeyInfoRequest& request, |
| const std::shared_ptr<GetKeyInfoReply>& result) { |
| CertifiedKey key; |
| if (!FindKeyByLabel(request.username(), request.key_label(), &key)) { |
| result->set_status(STATUS_INVALID_PARAMETER); |
| return; |
| } |
| std::string public_key_info; |
| if (!GetSubjectPublicKeyInfo(key.key_type(), key.public_key(), |
| &public_key_info)) { |
| LOG(ERROR) << __func__ << ": Bad public key."; |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| result->set_key_type(key.key_type()); |
| result->set_key_usage(key.key_usage()); |
| result->set_public_key(public_key_info); |
| result->set_certify_info(key.certified_key_info()); |
| result->set_certify_info_signature(key.certified_key_proof()); |
| if (key.has_intermediate_ca_cert()) { |
| result->set_certificate(CreatePEMCertificateChain(key)); |
| } else { |
| result->set_certificate(key.certified_key_credential()); |
| } |
| } |
| |
| void AttestationService::GetEndorsementInfo( |
| const GetEndorsementInfoRequest& request, |
| const GetEndorsementInfoCallback& callback) { |
| auto result = std::make_shared<GetEndorsementInfoReply>(); |
| base::Closure task = base::Bind(&AttestationService::GetEndorsementInfoTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = base::Bind( |
| &AttestationService::TaskRelayCallback<GetEndorsementInfoReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::GetEndorsementInfoTask( |
| const GetEndorsementInfoRequest& request, |
| const std::shared_ptr<GetEndorsementInfoReply>& result) { |
| if (request.key_type() != KEY_TYPE_RSA) { |
| result->set_status(STATUS_INVALID_PARAMETER); |
| return; |
| } |
| auto database_pb = database_->GetProtobuf(); |
| if (!database_pb.has_credentials() || |
| !database_pb.credentials().has_endorsement_public_key()) { |
| // Try to read the public key directly. |
| std::string public_key; |
| if (!tpm_utility_->GetEndorsementPublicKey(&public_key)) { |
| result->set_status(STATUS_NOT_AVAILABLE); |
| return; |
| } |
| database_pb.mutable_credentials()->set_endorsement_public_key(public_key); |
| } |
| std::string public_key_info; |
| if (!GetSubjectPublicKeyInfo( |
| request.key_type(), |
| database_pb.credentials().endorsement_public_key(), |
| &public_key_info)) { |
| LOG(ERROR) << __func__ << ": Bad public key."; |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| result->set_ek_public_key(public_key_info); |
| if (database_pb.credentials().has_endorsement_credential()) { |
| result->set_ek_certificate( |
| database_pb.credentials().endorsement_credential()); |
| } |
| } |
| |
| void AttestationService::GetAttestationKeyInfo( |
| const GetAttestationKeyInfoRequest& request, |
| const GetAttestationKeyInfoCallback& callback) { |
| auto result = std::make_shared<GetAttestationKeyInfoReply>(); |
| base::Closure task = |
| base::Bind(&AttestationService::GetAttestationKeyInfoTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = base::Bind( |
| &AttestationService::TaskRelayCallback<GetAttestationKeyInfoReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::GetAttestationKeyInfoTask( |
| const GetAttestationKeyInfoRequest& request, |
| const std::shared_ptr<GetAttestationKeyInfoReply>& result) { |
| if (request.key_type() != KEY_TYPE_RSA) { |
| result->set_status(STATUS_INVALID_PARAMETER); |
| return; |
| } |
| auto database_pb = database_->GetProtobuf(); |
| if (!IsPreparedForEnrollment() || !database_pb.has_identity_key()) { |
| result->set_status(STATUS_NOT_AVAILABLE); |
| return; |
| } |
| if (database_pb.identity_key().has_identity_public_key()) { |
| std::string public_key_info; |
| if (!GetSubjectPublicKeyInfo( |
| request.key_type(), |
| database_pb.identity_key().identity_public_key(), |
| &public_key_info)) { |
| LOG(ERROR) << __func__ << ": Bad public key."; |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| result->set_public_key(public_key_info); |
| } |
| if (database_pb.has_identity_binding() && |
| database_pb.identity_binding().has_identity_public_key()) { |
| result->set_public_key_tpm_format( |
| database_pb.identity_binding().identity_public_key()); |
| } |
| if (database_pb.identity_key().has_identity_credential()) { |
| result->set_certificate(database_pb.identity_key().identity_credential()); |
| } |
| if (database_pb.has_pcr0_quote()) { |
| *result->mutable_pcr0_quote() = database_pb.pcr0_quote(); |
| } |
| if (database_pb.has_pcr1_quote()) { |
| *result->mutable_pcr1_quote() = database_pb.pcr1_quote(); |
| } |
| } |
| |
| void AttestationService::ActivateAttestationKey( |
| const ActivateAttestationKeyRequest& request, |
| const ActivateAttestationKeyCallback& callback) { |
| auto result = std::make_shared<ActivateAttestationKeyReply>(); |
| base::Closure task = |
| base::Bind(&AttestationService::ActivateAttestationKeyTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = base::Bind( |
| &AttestationService::TaskRelayCallback<ActivateAttestationKeyReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::ActivateAttestationKeyTask( |
| const ActivateAttestationKeyRequest& request, |
| const std::shared_ptr<ActivateAttestationKeyReply>& result) { |
| if (request.key_type() != KEY_TYPE_RSA) { |
| result->set_status(STATUS_INVALID_PARAMETER); |
| return; |
| } |
| std::string certificate; |
| auto database_pb = database_->GetProtobuf(); |
| if (!tpm_utility_->ActivateIdentity( |
| database_pb.delegate().blob(), database_pb.delegate().secret(), |
| database_pb.identity_key().identity_key_blob(), |
| request.encrypted_certificate().asym_ca_contents(), |
| request.encrypted_certificate().sym_ca_attestation(), &certificate)) { |
| LOG(ERROR) << __func__ << ": Failed to activate identity."; |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| if (request.save_certificate()) { |
| database_->GetMutableProtobuf() |
| ->mutable_identity_key() |
| ->set_identity_credential(certificate); |
| if (!database_->SaveChanges()) { |
| LOG(ERROR) << __func__ << ": Failed to persist database changes."; |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| } |
| } |
| result->set_certificate(certificate); |
| } |
| |
| void AttestationService::CreateCertifiableKey( |
| const CreateCertifiableKeyRequest& request, |
| const CreateCertifiableKeyCallback& callback) { |
| auto result = std::make_shared<CreateCertifiableKeyReply>(); |
| base::Closure task = base::Bind(&AttestationService::CreateCertifiableKeyTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = base::Bind( |
| &AttestationService::TaskRelayCallback<CreateCertifiableKeyReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::CreateCertifiableKeyTask( |
| const CreateCertifiableKeyRequest& request, |
| const std::shared_ptr<CreateCertifiableKeyReply>& result) { |
| CertifiedKey key; |
| if (!CreateKey(request.username(), request.key_label(), request.key_type(), |
| request.key_usage(), &key)) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| std::string public_key_info; |
| if (!GetSubjectPublicKeyInfo(key.key_type(), key.public_key(), |
| &public_key_info)) { |
| LOG(ERROR) << __func__ << ": Bad public key."; |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| result->set_public_key(public_key_info); |
| result->set_certify_info(key.certified_key_info()); |
| result->set_certify_info_signature(key.certified_key_proof()); |
| } |
| |
| void AttestationService::Decrypt(const DecryptRequest& request, |
| const DecryptCallback& callback) { |
| auto result = std::make_shared<DecryptReply>(); |
| base::Closure task = base::Bind(&AttestationService::DecryptTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = |
| base::Bind(&AttestationService::TaskRelayCallback<DecryptReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::DecryptTask( |
| const DecryptRequest& request, |
| const std::shared_ptr<DecryptReply>& result) { |
| CertifiedKey key; |
| if (!FindKeyByLabel(request.username(), request.key_label(), &key)) { |
| result->set_status(STATUS_INVALID_PARAMETER); |
| return; |
| } |
| std::string data; |
| if (!tpm_utility_->Unbind(key.key_blob(), request.encrypted_data(), &data)) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| result->set_decrypted_data(data); |
| } |
| |
| void AttestationService::Sign(const SignRequest& request, |
| const SignCallback& callback) { |
| auto result = std::make_shared<SignReply>(); |
| base::Closure task = base::Bind(&AttestationService::SignTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = |
| base::Bind(&AttestationService::TaskRelayCallback<SignReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::SignTask(const SignRequest& request, |
| const std::shared_ptr<SignReply>& result) { |
| CertifiedKey key; |
| if (!FindKeyByLabel(request.username(), request.key_label(), &key)) { |
| result->set_status(STATUS_INVALID_PARAMETER); |
| return; |
| } |
| std::string signature; |
| if (!tpm_utility_->Sign(key.key_blob(), request.data_to_sign(), &signature)) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| result->set_signature(signature); |
| } |
| |
| void AttestationService::RegisterKeyWithChapsToken( |
| const RegisterKeyWithChapsTokenRequest& request, |
| const RegisterKeyWithChapsTokenCallback& callback) { |
| auto result = std::make_shared<RegisterKeyWithChapsTokenReply>(); |
| base::Closure task = |
| base::Bind(&AttestationService::RegisterKeyWithChapsTokenTask, |
| base::Unretained(this), request, result); |
| base::Closure reply = base::Bind( |
| &AttestationService::TaskRelayCallback<RegisterKeyWithChapsTokenReply>, |
| GetWeakPtr(), callback, result); |
| worker_thread_->task_runner()->PostTaskAndReply(FROM_HERE, task, reply); |
| } |
| |
| void AttestationService::RegisterKeyWithChapsTokenTask( |
| const RegisterKeyWithChapsTokenRequest& request, |
| const std::shared_ptr<RegisterKeyWithChapsTokenReply>& result) { |
| CertifiedKey key; |
| if (!FindKeyByLabel(request.username(), request.key_label(), &key)) { |
| result->set_status(STATUS_INVALID_PARAMETER); |
| return; |
| } |
| if (!key_store_->Register(request.username(), request.key_label(), |
| key.key_type(), key.key_usage(), key.key_blob(), |
| key.public_key(), key.certified_key_credential())) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| if (key.has_intermediate_ca_cert() && |
| !key_store_->RegisterCertificate(request.username(), |
| key.intermediate_ca_cert())) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| for (int i = 0; i < key.additional_intermediate_ca_cert_size(); ++i) { |
| if (!key_store_->RegisterCertificate( |
| request.username(), key.additional_intermediate_ca_cert(i))) { |
| result->set_status(STATUS_UNEXPECTED_DEVICE_ERROR); |
| return; |
| } |
| } |
| DeleteKey(request.username(), request.key_label()); |
| } |
| |
| bool AttestationService::IsPreparedForEnrollment() { |
| if (!tpm_utility_->IsTpmReady()) { |
| return false; |
| } |
| auto database_pb = database_->GetProtobuf(); |
| if (!database_pb.has_credentials()) { |
| return false; |
| } |
| return ( |
| database_pb.credentials().has_endorsement_credential() || |
| database_pb.credentials().has_default_encrypted_endorsement_credential()); |
| } |
| |
| bool AttestationService::IsEnrolled() { |
| auto database_pb = database_->GetProtobuf(); |
| return database_pb.has_identity_key() && |
| database_pb.identity_key().has_identity_credential(); |
| } |
| |
| bool AttestationService::CreateEnrollRequest(std::string* enroll_request) { |
| if (!IsPreparedForEnrollment()) { |
| LOG(ERROR) << __func__ << ": Enrollment is not possible, attestation data " |
| << "does not exist."; |
| return false; |
| } |
| auto database_pb = database_->GetProtobuf(); |
| AttestationEnrollmentRequest request_pb; |
| *request_pb.mutable_encrypted_endorsement_credential() = |
| database_pb.credentials().default_encrypted_endorsement_credential(); |
| request_pb.set_identity_public_key( |
| database_pb.identity_binding().identity_public_key()); |
| *request_pb.mutable_pcr0_quote() = database_pb.pcr0_quote(); |
| *request_pb.mutable_pcr1_quote() = database_pb.pcr1_quote(); |
| if (!request_pb.SerializeToString(enroll_request)) { |
| LOG(ERROR) << __func__ << ": Failed to serialize protobuf."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool AttestationService::FinishEnroll(const std::string& enroll_response, |
| std::string* server_error) { |
| if (!tpm_utility_->IsTpmReady()) { |
| return false; |
| } |
| AttestationEnrollmentResponse response_pb; |
| if (!response_pb.ParseFromString(enroll_response)) { |
| LOG(ERROR) << __func__ << ": Failed to parse response from CA."; |
| return false; |
| } |
| if (response_pb.status() != OK) { |
| *server_error = response_pb.detail(); |
| LOG(ERROR) << __func__ |
| << ": Error received from CA: " << response_pb.detail(); |
| return false; |
| } |
| std::string credential; |
| auto database_pb = database_->GetProtobuf(); |
| if (!tpm_utility_->ActivateIdentity( |
| database_pb.delegate().blob(), database_pb.delegate().secret(), |
| database_pb.identity_key().identity_key_blob(), |
| response_pb.encrypted_identity_credential().asym_ca_contents(), |
| response_pb.encrypted_identity_credential().sym_ca_attestation(), |
| &credential)) { |
| LOG(ERROR) << __func__ << ": Failed to activate identity."; |
| return false; |
| } |
| database_->GetMutableProtobuf() |
| ->mutable_identity_key() |
| ->set_identity_credential(credential); |
| if (!database_->SaveChanges()) { |
| LOG(ERROR) << __func__ << ": Failed to persist database changes."; |
| return false; |
| } |
| LOG(INFO) << "Attestation: Enrollment complete."; |
| return true; |
| } |
| |
| bool AttestationService::CreateCertificateRequest( |
| const std::string& username, |
| const CertifiedKey& key, |
| CertificateProfile profile, |
| const std::string& origin, |
| std::string* certificate_request, |
| std::string* message_id) { |
| if (!tpm_utility_->IsTpmReady()) { |
| return false; |
| } |
| if (!IsEnrolled()) { |
| LOG(ERROR) << __func__ << ": Device is not enrolled for attestation."; |
| return false; |
| } |
| AttestationCertificateRequest request_pb; |
| if (!crypto_utility_->GetRandom(kNonceSize, message_id)) { |
| LOG(ERROR) << __func__ << ": GetRandom(message_id) failed."; |
| return false; |
| } |
| request_pb.set_message_id(*message_id); |
| auto database_pb = database_->GetProtobuf(); |
| request_pb.set_identity_credential( |
| database_pb.identity_key().identity_credential()); |
| request_pb.set_profile(profile); |
| if (!origin.empty() && |
| (profile == CONTENT_PROTECTION_CERTIFICATE_WITH_STABLE_ID)) { |
| request_pb.set_origin(origin); |
| request_pb.set_temporal_index(ChooseTemporalIndex(username, origin)); |
| } |
| request_pb.set_certified_public_key(key.public_key_tpm_format()); |
| request_pb.set_certified_key_info(key.certified_key_info()); |
| request_pb.set_certified_key_proof(key.certified_key_proof()); |
| if (!request_pb.SerializeToString(certificate_request)) { |
| LOG(ERROR) << __func__ << ": Failed to serialize protobuf."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool AttestationService::FinishCertificateRequest( |
| const std::string& certificate_response, |
| const std::string& username, |
| const std::string& key_label, |
| const std::string& message_id, |
| CertifiedKey* key, |
| std::string* certificate_chain, |
| std::string* server_error) { |
| if (!tpm_utility_->IsTpmReady()) { |
| return false; |
| } |
| AttestationCertificateResponse response_pb; |
| if (!response_pb.ParseFromString(certificate_response)) { |
| LOG(ERROR) << __func__ << ": Failed to parse response from Privacy CA."; |
| return false; |
| } |
| if (response_pb.status() != OK) { |
| *server_error = response_pb.detail(); |
| LOG(ERROR) << __func__ |
| << ": Error received from Privacy CA: " << response_pb.detail(); |
| return false; |
| } |
| if (message_id != response_pb.message_id()) { |
| LOG(ERROR) << __func__ << ": Message ID mismatch."; |
| return false; |
| } |
| |
| // Finish populating the CertifiedKey protobuf and store it. |
| key->set_certified_key_credential(response_pb.certified_key_credential()); |
| key->set_intermediate_ca_cert(response_pb.intermediate_ca_cert()); |
| key->mutable_additional_intermediate_ca_cert()->MergeFrom( |
| response_pb.additional_intermediate_ca_cert()); |
| if (!SaveKey(username, key_label, *key)) { |
| return false; |
| } |
| LOG(INFO) << "Attestation: Certified key credential received and stored."; |
| *certificate_chain = CreatePEMCertificateChain(*key); |
| return true; |
| } |
| |
| bool AttestationService::SendACARequestAndBlock(ACARequestType request_type, |
| const std::string& request, |
| std::string* reply) { |
| std::shared_ptr<brillo::http::Transport> transport = http_transport_; |
| if (!transport) { |
| transport = brillo::http::Transport::CreateDefault(); |
| } |
| std::unique_ptr<brillo::http::Response> response = PostBinaryAndBlock( |
| GetACAURL(request_type), request.data(), request.size(), |
| brillo::mime::application::kOctet_stream, {}, // headers |
| transport, |
| nullptr); // error |
| if (!response || !response->IsSuccessful()) { |
| LOG(ERROR) << "HTTP request to Attestation CA failed."; |
| return false; |
| } |
| *reply = response->ExtractDataAsString(); |
| return true; |
| } |
| |
| bool AttestationService::FindKeyByLabel(const std::string& username, |
| const std::string& key_label, |
| CertifiedKey* key) { |
| if (!username.empty()) { |
| std::string key_data; |
| if (!key_store_->Read(username, key_label, &key_data)) { |
| LOG(INFO) << "Key not found: " << key_label; |
| return false; |
| } |
| if (key && !key->ParseFromString(key_data)) { |
| LOG(ERROR) << "Failed to parse key: " << key_label; |
| return false; |
| } |
| return true; |
| } |
| auto database_pb = database_->GetProtobuf(); |
| for (int i = 0; i < database_pb.device_keys_size(); ++i) { |
| if (database_pb.device_keys(i).key_name() == key_label) { |
| *key = database_pb.device_keys(i); |
| return true; |
| } |
| } |
| LOG(INFO) << "Key not found: " << key_label; |
| return false; |
| } |
| |
| bool AttestationService::CreateKey(const std::string& username, |
| const std::string& key_label, |
| KeyType key_type, |
| KeyUsage key_usage, |
| CertifiedKey* key) { |
| std::string nonce; |
| if (!crypto_utility_->GetRandom(kNonceSize, &nonce)) { |
| LOG(ERROR) << __func__ << ": GetRandom(nonce) failed."; |
| return false; |
| } |
| std::string key_blob; |
| std::string public_key; |
| std::string public_key_tpm_format; |
| std::string key_info; |
| std::string proof; |
| auto database_pb = database_->GetProtobuf(); |
| if (!tpm_utility_->CreateCertifiedKey( |
| key_type, key_usage, database_pb.identity_key().identity_key_blob(), |
| nonce, &key_blob, &public_key, &public_key_tpm_format, &key_info, |
| &proof)) { |
| return false; |
| } |
| key->set_key_blob(key_blob); |
| key->set_public_key(public_key); |
| key->set_key_name(key_label); |
| key->set_public_key_tpm_format(public_key_tpm_format); |
| key->set_certified_key_info(key_info); |
| key->set_certified_key_proof(proof); |
| return SaveKey(username, key_label, *key); |
| } |
| |
| bool AttestationService::SaveKey(const std::string& username, |
| const std::string& key_label, |
| const CertifiedKey& key) { |
| if (!username.empty()) { |
| std::string key_data; |
| if (!key.SerializeToString(&key_data)) { |
| LOG(ERROR) << __func__ << ": Failed to serialize protobuf."; |
| return false; |
| } |
| if (!key_store_->Write(username, key_label, key_data)) { |
| LOG(ERROR) << __func__ << ": Failed to store certified key for user."; |
| return false; |
| } |
| } else { |
| if (!AddDeviceKey(key_label, key)) { |
| LOG(ERROR) << __func__ << ": Failed to store certified key for device."; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void AttestationService::DeleteKey(const std::string& username, |
| const std::string& key_label) { |
| if (!username.empty()) { |
| key_store_->Delete(username, key_label); |
| } else { |
| RemoveDeviceKey(key_label); |
| } |
| } |
| |
| bool AttestationService::AddDeviceKey(const std::string& key_label, |
| const CertifiedKey& key) { |
| // If a key by this name already exists, reuse the field. |
| auto* database_pb = database_->GetMutableProtobuf(); |
| bool found = false; |
| for (int i = 0; i < database_pb->device_keys_size(); ++i) { |
| if (database_pb->device_keys(i).key_name() == key_label) { |
| found = true; |
| *database_pb->mutable_device_keys(i) = key; |
| break; |
| } |
| } |
| if (!found) |
| *database_pb->add_device_keys() = key; |
| return database_->SaveChanges(); |
| } |
| |
| void AttestationService::RemoveDeviceKey(const std::string& key_label) { |
| auto* database_pb = database_->GetMutableProtobuf(); |
| bool found = false; |
| for (int i = 0; i < database_pb->device_keys_size(); ++i) { |
| if (database_pb->device_keys(i).key_name() == key_label) { |
| found = true; |
| int last = database_pb->device_keys_size() - 1; |
| if (i < last) { |
| database_pb->mutable_device_keys()->SwapElements(i, last); |
| } |
| database_pb->mutable_device_keys()->RemoveLast(); |
| break; |
| } |
| } |
| if (found) { |
| if (!database_->SaveChanges()) { |
| LOG(WARNING) << __func__ << ": Failed to persist key deletion."; |
| } |
| } |
| } |
| |
| std::string AttestationService::CreatePEMCertificateChain( |
| const CertifiedKey& key) { |
| if (key.certified_key_credential().empty()) { |
| LOG(WARNING) << "Certificate is empty."; |
| return std::string(); |
| } |
| std::string pem = CreatePEMCertificate(key.certified_key_credential()); |
| if (!key.intermediate_ca_cert().empty()) { |
| pem += "\n"; |
| pem += CreatePEMCertificate(key.intermediate_ca_cert()); |
| } |
| for (int i = 0; i < key.additional_intermediate_ca_cert_size(); ++i) { |
| pem += "\n"; |
| pem += CreatePEMCertificate(key.additional_intermediate_ca_cert(i)); |
| } |
| return pem; |
| } |
| |
| std::string AttestationService::CreatePEMCertificate( |
| const std::string& certificate) { |
| const char kBeginCertificate[] = "-----BEGIN CERTIFICATE-----\n"; |
| const char kEndCertificate[] = "-----END CERTIFICATE-----"; |
| |
| std::string pem = kBeginCertificate; |
| pem += brillo::data_encoding::Base64EncodeWrapLines(certificate); |
| pem += kEndCertificate; |
| return pem; |
| } |
| |
| int AttestationService::ChooseTemporalIndex(const std::string& user, |
| const std::string& origin) { |
| std::string user_hash = crypto::SHA256HashString(user); |
| std::string origin_hash = crypto::SHA256HashString(origin); |
| int histogram[kNumTemporalValues] = {}; |
| auto database_pb = database_->GetProtobuf(); |
| for (int i = 0; i < database_pb.temporal_index_record_size(); ++i) { |
| const AttestationDatabase::TemporalIndexRecord& record = |
| database_pb.temporal_index_record(i); |
| // Ignore out-of-range index values. |
| if (record.temporal_index() < 0 || |
| record.temporal_index() >= kNumTemporalValues) |
| continue; |
| if (record.origin_hash() == origin_hash) { |
| if (record.user_hash() == user_hash) { |
| // We've previously chosen this index for this user, reuse it. |
| return record.temporal_index(); |
| } else { |
| // We've previously chosen this index for another user. |
| ++histogram[record.temporal_index()]; |
| } |
| } |
| } |
| int least_used_index = 0; |
| for (int i = 1; i < kNumTemporalValues; ++i) { |
| if (histogram[i] < histogram[least_used_index]) |
| least_used_index = i; |
| } |
| if (histogram[least_used_index] > 0) { |
| LOG(WARNING) << "Unique origin-specific identifiers have been exhausted."; |
| } |
| // Record our choice for later reference. |
| AttestationDatabase::TemporalIndexRecord* new_record = |
| database_pb.add_temporal_index_record(); |
| new_record->set_origin_hash(origin_hash); |
| new_record->set_user_hash(user_hash); |
| new_record->set_temporal_index(least_used_index); |
| database_->SaveChanges(); |
| return least_used_index; |
| } |
| |
| std::string AttestationService::GetACAURL(ACARequestType request_type) const { |
| std::string url = attestation_ca_origin_; |
| switch (request_type) { |
| case kEnroll: |
| url += "/enroll"; |
| break; |
| case kGetCertificate: |
| url += "/sign"; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| return url; |
| } |
| |
| bool AttestationService::GetSubjectPublicKeyInfo( |
| KeyType key_type, |
| const std::string& public_key, |
| std::string* public_key_info) const { |
| // Only RSA is supported currently. |
| if (key_type != KEY_TYPE_RSA) { |
| return false; |
| } |
| return crypto_utility_->GetRSASubjectPublicKeyInfo(public_key, |
| public_key_info); |
| } |
| |
| base::WeakPtr<AttestationService> AttestationService::GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| } // namespace attestation |