You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
873 lines
32 KiB
873 lines
32 KiB
// |
|
// 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
|
|
|