937 lines
32 KiB
C++
937 lines
32 KiB
C++
//
|
|
// 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
|