| // |
| // Copyright (C) 2014 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 <stdio.h> |
| #include <sysexits.h> |
| |
| #include <memory> |
| #include <string> |
| |
| #include <base/command_line.h> |
| #include <base/files/file_util.h> |
| #include <base/message_loop/message_loop.h> |
| #include <brillo/bind_lambda.h> |
| #include <brillo/daemons/daemon.h> |
| #include <brillo/syslog_logging.h> |
| |
| #include "attestation/client/dbus_proxy.h" |
| #include "attestation/common/attestation_ca.pb.h" |
| #include "attestation/common/crypto_utility_impl.h" |
| #include "attestation/common/interface.pb.h" |
| #include "attestation/common/print_interface_proto.h" |
| |
| namespace attestation { |
| |
| const char kCreateAndCertifyCommand[] = "create_and_certify"; |
| const char kCreateCommand[] = "create"; |
| const char kInfoCommand[] = "info"; |
| const char kEndorsementCommand[] = "endorsement"; |
| const char kAttestationKeyCommand[] = "attestation_key"; |
| const char kActivateCommand[] = "activate"; |
| const char kEncryptForActivateCommand[] = "encrypt_for_activate"; |
| const char kEncryptCommand[] = "encrypt"; |
| const char kDecryptCommand[] = "decrypt"; |
| const char kSignCommand[] = "sign"; |
| const char kVerifyCommand[] = "verify"; |
| const char kRegisterCommand[] = "register"; |
| const char kUsage[] = R"( |
| Usage: attestation_client <command> [<args>] |
| Commands: |
| create_and_certify [--user=<email>] [--label=<keylabel>] |
| Creates a key and requests certification by the Google Attestation CA. |
| This is the default command. |
| create [--user=<email>] [--label=<keylabel] [--usage=sign|decrypt] |
| Creates a certifiable key. |
| |
| info [--user=<email>] [--label=<keylabel>] |
| Prints info about a key. |
| endorsement |
| Prints info about the TPM endorsement. |
| attestation_key |
| Prints info about the TPM attestation key. |
| |
| activate --input=<input_file> |
| Activates an attestation key using the encrypted credential in |
| |input_file|. |
| encrypt_for_activate --input=<input_file> --output=<output_file> |
| Encrypts the content of |input_file| as required by the TPM for activating |
| an attestation key. The result is written to |output_file|. |
| |
| encrypt [--user=<email>] [--label=<keylabel>] --input=<input_file> |
| --output=<output_file> |
| Encrypts the contents of |input_file| as required by the TPM for a decrypt |
| operation. The result is written to |output_file|. |
| decrypt [--user=<email>] [--label=<keylabel>] --input=<input_file> |
| Decrypts the contents of |input_file|. |
| |
| sign [--user=<email>] [--label=<keylabel>] --input=<input_file> |
| [--output=<output_file>] |
| Signs the contents of |input_file|. |
| verify [--user=<email>] [--label=<keylabel] --input=<signed_data_file> |
| --signature=<signature_file> |
| Verifies the signature in |signature_file| against the contents of |
| |input_file|. |
| |
| register [--user=<email>] [--label=<keylabel] |
| Registers a key with a PKCS #11 token. |
| )"; |
| |
| // The Daemon class works well as a client loop as well. |
| using ClientLoopBase = brillo::Daemon; |
| |
| class ClientLoop : public ClientLoopBase { |
| public: |
| ClientLoop() = default; |
| ~ClientLoop() override = default; |
| |
| protected: |
| int OnInit() override { |
| int exit_code = ClientLoopBase::OnInit(); |
| if (exit_code != EX_OK) { |
| return exit_code; |
| } |
| attestation_.reset(new attestation::DBusProxy()); |
| if (!attestation_->Initialize()) { |
| return EX_UNAVAILABLE; |
| } |
| exit_code = ScheduleCommand(); |
| if (exit_code == EX_USAGE) { |
| printf("%s", kUsage); |
| } |
| return exit_code; |
| } |
| |
| void OnShutdown(int* exit_code) override { |
| attestation_.reset(); |
| ClientLoopBase::OnShutdown(exit_code); |
| } |
| |
| private: |
| // Posts tasks according to the command line options. |
| int ScheduleCommand() { |
| base::Closure task; |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| const auto& args = command_line->GetArgs(); |
| if (command_line->HasSwitch("help") || command_line->HasSwitch("h") || |
| (!args.empty() && args.front() == "help")) { |
| return EX_USAGE; |
| } |
| if (args.empty() || args.front() == kCreateAndCertifyCommand) { |
| task = base::Bind(&ClientLoop::CallCreateGoogleAttestedKey, |
| weak_factory_.GetWeakPtr(), |
| command_line->GetSwitchValueASCII("label"), |
| command_line->GetSwitchValueASCII("user")); |
| } else if (args.front() == kCreateCommand) { |
| std::string usage_str = command_line->GetSwitchValueASCII("usage"); |
| KeyUsage usage; |
| if (usage_str.empty() || usage_str == "sign") { |
| usage = KEY_USAGE_SIGN; |
| } else if (usage_str == "decrypt") { |
| usage = KEY_USAGE_DECRYPT; |
| } else { |
| return EX_USAGE; |
| } |
| task = base::Bind(&ClientLoop::CallCreateCertifiableKey, |
| weak_factory_.GetWeakPtr(), |
| command_line->GetSwitchValueASCII("label"), |
| command_line->GetSwitchValueASCII("user"), usage); |
| } else if (args.front() == kInfoCommand) { |
| task = base::Bind(&ClientLoop::CallGetKeyInfo, weak_factory_.GetWeakPtr(), |
| command_line->GetSwitchValueASCII("label"), |
| command_line->GetSwitchValueASCII("user")); |
| } else if (args.front() == kEndorsementCommand) { |
| task = base::Bind(&ClientLoop::CallGetEndorsementInfo, |
| weak_factory_.GetWeakPtr()); |
| } else if (args.front() == kAttestationKeyCommand) { |
| task = base::Bind(&ClientLoop::CallGetAttestationKeyInfo, |
| weak_factory_.GetWeakPtr()); |
| } else if (args.front() == kActivateCommand) { |
| if (!command_line->HasSwitch("input")) { |
| return EX_USAGE; |
| } |
| std::string input; |
| base::FilePath filename(command_line->GetSwitchValueASCII("input")); |
| if (!base::ReadFileToString(filename, &input)) { |
| LOG(ERROR) << "Failed to read file: " << filename.value(); |
| return EX_NOINPUT; |
| } |
| task = base::Bind(&ClientLoop::CallActivateAttestationKey, |
| weak_factory_.GetWeakPtr(), input); |
| } else if (args.front() == kEncryptForActivateCommand) { |
| if (!command_line->HasSwitch("input") || |
| !command_line->HasSwitch("output")) { |
| return EX_USAGE; |
| } |
| std::string input; |
| base::FilePath filename(command_line->GetSwitchValueASCII("input")); |
| if (!base::ReadFileToString(filename, &input)) { |
| LOG(ERROR) << "Failed to read file: " << filename.value(); |
| return EX_NOINPUT; |
| } |
| task = base::Bind(&ClientLoop::EncryptForActivate, |
| weak_factory_.GetWeakPtr(), input); |
| } else if (args.front() == kEncryptCommand) { |
| if (!command_line->HasSwitch("input") || |
| !command_line->HasSwitch("output")) { |
| return EX_USAGE; |
| } |
| std::string input; |
| base::FilePath filename(command_line->GetSwitchValueASCII("input")); |
| if (!base::ReadFileToString(filename, &input)) { |
| LOG(ERROR) << "Failed to read file: " << filename.value(); |
| return EX_NOINPUT; |
| } |
| task = base::Bind(&ClientLoop::Encrypt, weak_factory_.GetWeakPtr(), |
| command_line->GetSwitchValueASCII("label"), |
| command_line->GetSwitchValueASCII("user"), input); |
| } else if (args.front() == kDecryptCommand) { |
| if (!command_line->HasSwitch("input")) { |
| return EX_USAGE; |
| } |
| std::string input; |
| base::FilePath filename(command_line->GetSwitchValueASCII("input")); |
| if (!base::ReadFileToString(filename, &input)) { |
| LOG(ERROR) << "Failed to read file: " << filename.value(); |
| return EX_NOINPUT; |
| } |
| task = base::Bind(&ClientLoop::CallDecrypt, weak_factory_.GetWeakPtr(), |
| command_line->GetSwitchValueASCII("label"), |
| command_line->GetSwitchValueASCII("user"), input); |
| } else if (args.front() == kSignCommand) { |
| if (!command_line->HasSwitch("input")) { |
| return EX_USAGE; |
| } |
| std::string input; |
| base::FilePath filename(command_line->GetSwitchValueASCII("input")); |
| if (!base::ReadFileToString(filename, &input)) { |
| LOG(ERROR) << "Failed to read file: " << filename.value(); |
| return EX_NOINPUT; |
| } |
| task = base::Bind(&ClientLoop::CallSign, weak_factory_.GetWeakPtr(), |
| command_line->GetSwitchValueASCII("label"), |
| command_line->GetSwitchValueASCII("user"), input); |
| } else if (args.front() == kVerifyCommand) { |
| if (!command_line->HasSwitch("input") || |
| !command_line->HasSwitch("signature")) { |
| return EX_USAGE; |
| } |
| std::string input; |
| base::FilePath filename(command_line->GetSwitchValueASCII("input")); |
| if (!base::ReadFileToString(filename, &input)) { |
| LOG(ERROR) << "Failed to read file: " << filename.value(); |
| return EX_NOINPUT; |
| } |
| std::string signature; |
| base::FilePath filename2(command_line->GetSwitchValueASCII("signature")); |
| if (!base::ReadFileToString(filename2, &signature)) { |
| LOG(ERROR) << "Failed to read file: " << filename2.value(); |
| return EX_NOINPUT; |
| } |
| task = base::Bind( |
| &ClientLoop::VerifySignature, weak_factory_.GetWeakPtr(), |
| command_line->GetSwitchValueASCII("label"), |
| command_line->GetSwitchValueASCII("user"), input, signature); |
| } else if (args.front() == kRegisterCommand) { |
| task = base::Bind(&ClientLoop::CallRegister, weak_factory_.GetWeakPtr(), |
| command_line->GetSwitchValueASCII("label"), |
| command_line->GetSwitchValueASCII("user")); |
| } else { |
| return EX_USAGE; |
| } |
| base::MessageLoop::current()->PostTask(FROM_HERE, task); |
| return EX_OK; |
| } |
| |
| template <typename ProtobufType> |
| void PrintReplyAndQuit(const ProtobufType& reply) { |
| printf("%s\n", GetProtoDebugString(reply).c_str()); |
| Quit(); |
| } |
| |
| void WriteOutput(const std::string& output) { |
| base::FilePath filename( |
| base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII("output")); |
| if (base::WriteFile(filename, output.data(), output.size()) != |
| static_cast<int>(output.size())) { |
| LOG(ERROR) << "Failed to write file: " << filename.value(); |
| QuitWithExitCode(EX_IOERR); |
| } |
| } |
| |
| void CallCreateGoogleAttestedKey(const std::string& label, |
| const std::string& username) { |
| CreateGoogleAttestedKeyRequest request; |
| request.set_key_label(label); |
| request.set_key_type(KEY_TYPE_RSA); |
| request.set_key_usage(KEY_USAGE_SIGN); |
| request.set_certificate_profile(ENTERPRISE_MACHINE_CERTIFICATE); |
| request.set_username(username); |
| attestation_->CreateGoogleAttestedKey( |
| request, |
| base::Bind(&ClientLoop::PrintReplyAndQuit<CreateGoogleAttestedKeyReply>, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void CallGetKeyInfo(const std::string& label, const std::string& username) { |
| GetKeyInfoRequest request; |
| request.set_key_label(label); |
| request.set_username(username); |
| attestation_->GetKeyInfo( |
| request, base::Bind(&ClientLoop::PrintReplyAndQuit<GetKeyInfoReply>, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void CallGetEndorsementInfo() { |
| GetEndorsementInfoRequest request; |
| request.set_key_type(KEY_TYPE_RSA); |
| attestation_->GetEndorsementInfo( |
| request, |
| base::Bind(&ClientLoop::PrintReplyAndQuit<GetEndorsementInfoReply>, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void CallGetAttestationKeyInfo() { |
| GetAttestationKeyInfoRequest request; |
| request.set_key_type(KEY_TYPE_RSA); |
| attestation_->GetAttestationKeyInfo( |
| request, |
| base::Bind(&ClientLoop::PrintReplyAndQuit<GetAttestationKeyInfoReply>, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void CallActivateAttestationKey(const std::string& input) { |
| ActivateAttestationKeyRequest request; |
| request.set_key_type(KEY_TYPE_RSA); |
| request.mutable_encrypted_certificate()->ParseFromString(input); |
| request.set_save_certificate(true); |
| attestation_->ActivateAttestationKey( |
| request, |
| base::Bind(&ClientLoop::PrintReplyAndQuit<ActivateAttestationKeyReply>, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void EncryptForActivate(const std::string& input) { |
| GetEndorsementInfoRequest request; |
| request.set_key_type(KEY_TYPE_RSA); |
| attestation_->GetEndorsementInfo( |
| request, base::Bind(&ClientLoop::EncryptForActivate2, |
| weak_factory_.GetWeakPtr(), input)); |
| } |
| |
| void EncryptForActivate2(const std::string& input, |
| const GetEndorsementInfoReply& endorsement_info) { |
| if (endorsement_info.status() != STATUS_SUCCESS) { |
| PrintReplyAndQuit(endorsement_info); |
| } |
| GetAttestationKeyInfoRequest request; |
| request.set_key_type(KEY_TYPE_RSA); |
| attestation_->GetAttestationKeyInfo( |
| request, |
| base::Bind(&ClientLoop::EncryptForActivate3, weak_factory_.GetWeakPtr(), |
| input, endorsement_info)); |
| } |
| |
| void EncryptForActivate3( |
| const std::string& input, |
| const GetEndorsementInfoReply& endorsement_info, |
| const GetAttestationKeyInfoReply& attestation_key_info) { |
| if (attestation_key_info.status() != STATUS_SUCCESS) { |
| PrintReplyAndQuit(attestation_key_info); |
| } |
| CryptoUtilityImpl crypto(nullptr); |
| EncryptedIdentityCredential encrypted; |
| if (!crypto.EncryptIdentityCredential( |
| input, endorsement_info.ek_public_key(), |
| attestation_key_info.public_key_tpm_format(), &encrypted)) { |
| QuitWithExitCode(EX_SOFTWARE); |
| } |
| std::string output; |
| encrypted.SerializeToString(&output); |
| WriteOutput(output); |
| Quit(); |
| } |
| |
| void CallCreateCertifiableKey(const std::string& label, |
| const std::string& username, |
| KeyUsage usage) { |
| CreateCertifiableKeyRequest request; |
| request.set_key_label(label); |
| request.set_username(username); |
| request.set_key_type(KEY_TYPE_RSA); |
| request.set_key_usage(usage); |
| attestation_->CreateCertifiableKey( |
| request, |
| base::Bind(&ClientLoop::PrintReplyAndQuit<CreateCertifiableKeyReply>, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void Encrypt(const std::string& label, |
| const std::string& username, |
| const std::string& input) { |
| GetKeyInfoRequest request; |
| request.set_key_label(label); |
| request.set_username(username); |
| attestation_->GetKeyInfo( |
| request, |
| base::Bind(&ClientLoop::Encrypt2, weak_factory_.GetWeakPtr(), input)); |
| } |
| |
| void Encrypt2(const std::string& input, const GetKeyInfoReply& key_info) { |
| CryptoUtilityImpl crypto(nullptr); |
| std::string output; |
| if (!crypto.EncryptForUnbind(key_info.public_key(), input, &output)) { |
| QuitWithExitCode(EX_SOFTWARE); |
| } |
| WriteOutput(output); |
| Quit(); |
| } |
| |
| void CallDecrypt(const std::string& label, |
| const std::string& username, |
| const std::string& input) { |
| DecryptRequest request; |
| request.set_key_label(label); |
| request.set_username(username); |
| request.set_encrypted_data(input); |
| attestation_->Decrypt( |
| request, base::Bind(&ClientLoop::PrintReplyAndQuit<DecryptReply>, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void CallSign(const std::string& label, |
| const std::string& username, |
| const std::string& input) { |
| SignRequest request; |
| request.set_key_label(label); |
| request.set_username(username); |
| request.set_data_to_sign(input); |
| attestation_->Sign(request, base::Bind(&ClientLoop::OnSignComplete, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void OnSignComplete(const SignReply& reply) { |
| if (reply.status() == STATUS_SUCCESS && |
| base::CommandLine::ForCurrentProcess()->HasSwitch("output")) { |
| WriteOutput(reply.signature()); |
| } |
| PrintReplyAndQuit<SignReply>(reply); |
| } |
| |
| void VerifySignature(const std::string& label, |
| const std::string& username, |
| const std::string& input, |
| const std::string& signature) { |
| GetKeyInfoRequest request; |
| request.set_key_label(label); |
| request.set_username(username); |
| attestation_->GetKeyInfo( |
| request, base::Bind(&ClientLoop::VerifySignature2, |
| weak_factory_.GetWeakPtr(), input, signature)); |
| } |
| |
| void VerifySignature2(const std::string& input, |
| const std::string& signature, |
| const GetKeyInfoReply& key_info) { |
| CryptoUtilityImpl crypto(nullptr); |
| if (crypto.VerifySignature(key_info.public_key(), input, signature)) { |
| printf("Signature is OK!\n"); |
| } else { |
| printf("Signature is BAD!\n"); |
| } |
| Quit(); |
| } |
| |
| void CallRegister(const std::string& label, const std::string& username) { |
| RegisterKeyWithChapsTokenRequest request; |
| request.set_key_label(label); |
| request.set_username(username); |
| attestation_->RegisterKeyWithChapsToken( |
| request, |
| base::Bind( |
| &ClientLoop::PrintReplyAndQuit<RegisterKeyWithChapsTokenReply>, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| std::unique_ptr<attestation::AttestationInterface> attestation_; |
| |
| // Declare this last so weak pointers will be destroyed first. |
| base::WeakPtrFactory<ClientLoop> weak_factory_{this}; |
| |
| DISALLOW_COPY_AND_ASSIGN(ClientLoop); |
| }; |
| |
| } // namespace attestation |
| |
| int main(int argc, char* argv[]) { |
| base::CommandLine::Init(argc, argv); |
| brillo::InitLog(brillo::kLogToStderr); |
| attestation::ClientLoop loop; |
| return loop.Run(); |
| } |