attestation: Implemented a Sign operation.
Keys that were created with KEY_USAGE_SIGN usage can be used to sign
arbitrary data with the RSA-PKCS1v15-SHA256 mechanism.
BUG=brillo:737
TEST=unit, manual using 'attestation_client sign' and
'attestation_client verify'
Change-Id: Iafaf938e3df9bc65f82e5b134ccf94fa7f56b6b1
Reviewed-on: https://chromium-review.googlesource.com/270008
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Darren Krahn <dkrahn@chromium.org>
Tested-by: Darren Krahn <dkrahn@chromium.org>
diff --git a/client/dbus_proxy.cc b/client/dbus_proxy.cc
index b505f04..2089f05 100644
--- a/client/dbus_proxy.cc
+++ b/client/dbus_proxy.cc
@@ -159,4 +159,20 @@
request);
}
+void DBusProxy::Sign(const SignRequest& request, const SignCallback& callback) {
+ auto on_error = [callback](chromeos::Error* error) {
+ SignReply reply;
+ reply.set_status(STATUS_NOT_AVAILABLE);
+ callback.Run(reply);
+ };
+ chromeos::dbus_utils::CallMethodWithTimeout(
+ kDBusTimeoutMS,
+ object_proxy_,
+ attestation::kAttestationInterface,
+ attestation::kSign,
+ callback,
+ base::Bind(on_error),
+ request);
+}
+
} // namespace attestation
diff --git a/client/dbus_proxy.h b/client/dbus_proxy.h
index 49ae1e7..678070d 100644
--- a/client/dbus_proxy.h
+++ b/client/dbus_proxy.h
@@ -44,6 +44,7 @@
const CreateCertifiableKeyCallback& callback) override;
void Decrypt(const DecryptRequest& request,
const DecryptCallback& callback) override;
+ void Sign(const SignRequest& request, const SignCallback& callback) override;
// Useful for testing.
void set_object_proxy(dbus::ObjectProxy* object_proxy) {
diff --git a/client/dbus_proxy_test.cc b/client/dbus_proxy_test.cc
index 51f24d4..f0855f0 100644
--- a/client/dbus_proxy_test.cc
+++ b/client/dbus_proxy_test.cc
@@ -326,4 +326,42 @@
EXPECT_EQ(1, callback_count);
}
+TEST_F(DBusProxyTest, Sign) {
+ auto fake_dbus_call = [](
+ dbus::MethodCall* method_call,
+ const dbus::MockObjectProxy::ResponseCallback& response_callback) {
+ // Verify request protobuf.
+ dbus::MessageReader reader(method_call);
+ SignRequest request_proto;
+ EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&request_proto));
+ EXPECT_EQ("label", request_proto.key_label());
+ EXPECT_EQ("user", request_proto.username());
+ EXPECT_EQ("data", request_proto.data_to_sign());
+ // Create reply protobuf.
+ auto response = dbus::Response::CreateEmpty();
+ dbus::MessageWriter writer(response.get());
+ SignReply reply_proto;
+ reply_proto.set_status(STATUS_SUCCESS);
+ reply_proto.set_signature("signature");
+ writer.AppendProtoAsArrayOfBytes(reply_proto);
+ response_callback.Run(response.release());
+ };
+ EXPECT_CALL(*mock_object_proxy_, CallMethodWithErrorCallback(_, _, _, _))
+ .WillOnce(WithArgs<0, 2>(Invoke(fake_dbus_call)));
+
+ // Set expectations on the outputs.
+ int callback_count = 0;
+ auto callback = [&callback_count](const SignReply& reply) {
+ callback_count++;
+ EXPECT_EQ(STATUS_SUCCESS, reply.status());
+ EXPECT_EQ("signature", reply.signature());
+ };
+ SignRequest request;
+ request.set_key_label("label");
+ request.set_username("user");
+ request.set_data_to_sign("data");
+ proxy_.Sign(request, base::Bind(callback));
+ EXPECT_EQ(1, callback_count);
+}
+
} // namespace attestation
diff --git a/client/main.cc b/client/main.cc
index 9c8b68e..1d00b18 100644
--- a/client/main.cc
+++ b/client/main.cc
@@ -31,6 +31,8 @@
const char kEncryptForActivateCommand[] = "encrypt_for_activate";
const char kEncryptCommand[] = "encrypt";
const char kDecryptCommand[] = "decrypt";
+const char kSignCommand[] = "sign";
+const char kVerifyCommand[] = "verify";
const char kUsage[] = R"(
Usage: attestation_client <command> [<args>]
Commands:
@@ -56,10 +58,18 @@
encrypt [--user=<email>] [--label=<keylabel>] --input=<input_file>
--output=<output_file>
- Encrypts the content of |input_file| as required by the TPM for a decrypt
+ 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 content of |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|.
)";
// The Daemon class works well as a client loop as well.
@@ -191,6 +201,44 @@
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 {
return EX_USAGE;
}
@@ -363,6 +411,49 @@
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();
+ }
+
std::unique_ptr<attestation::AttestationInterface> attestation_;
// Declare this last so weak pointers will be destroyed first.
diff --git a/common/attestation_interface.h b/common/attestation_interface.h
index c5b7111..2ed7213 100644
--- a/common/attestation_interface.h
+++ b/common/attestation_interface.h
@@ -75,6 +75,12 @@
using DecryptCallback = base::Callback<void(const DecryptReply&)>;
virtual void Decrypt(const DecryptRequest& request,
const DecryptCallback& callback) = 0;
+
+ // Processes a SignRequest and responds with a
+ // SignReply.
+ using SignCallback = base::Callback<void(const SignReply&)>;
+ virtual void Sign(const SignRequest& request,
+ const SignCallback& callback) = 0;
};
} // namespace attestation
diff --git a/common/crypto_utility.h b/common/crypto_utility.h
index 222c005..430c4ad 100644
--- a/common/crypto_utility.h
+++ b/common/crypto_utility.h
@@ -73,6 +73,12 @@
virtual bool EncryptForUnbind(const std::string& public_key,
const std::string& data,
std::string* encrypted_data) = 0;
+
+ // Verifies a PKCS #1 v1.5 SHA-256 |signature| over |data|. The |public_key|
+ // must be provided in X.509 SubjectPublicKeyInfo format.
+ virtual bool VerifySignature(const std::string& public_key,
+ const std::string& data,
+ const std::string& signature) = 0;
};
} // namespace attestation
diff --git a/common/crypto_utility_impl.cc b/common/crypto_utility_impl.cc
index dda2731..abf0b4c 100644
--- a/common/crypto_utility_impl.cc
+++ b/common/crypto_utility_impl.cc
@@ -8,9 +8,11 @@
#include <string>
#include <arpa/inet.h>
+#include <base/sha1.h>
#include <base/stl_util.h>
#include <crypto/scoped_openssl_types.h>
#include <crypto/secure_util.h>
+#include <crypto/sha2.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>
@@ -220,7 +222,8 @@
// Construct a TPM_ASYM_CA_CONTENTS structure.
std::string asym_header(std::begin(kAsymContentHeader),
std::end(kAsymContentHeader));
- std::string asym_content = asym_header + aes_key + Sha1(aik_public_key);
+ std::string asym_content = asym_header + aes_key +
+ base::SHA1HashString(aik_public_key);
// Encrypt the TPM_ASYM_CA_CONTENTS with the EK public key.
auto asn1_ptr = reinterpret_cast<const unsigned char*>(
@@ -261,8 +264,7 @@
std::string bound_data = header + data;
// Encrypt using the TPM_ES_RSAESOAEP_SHA1_MGF1 scheme.
- const unsigned char* asn1_ptr = reinterpret_cast<const unsigned char*>(
- public_key.data());
+ auto asn1_ptr = reinterpret_cast<const unsigned char*>(public_key.data());
crypto::ScopedRSA rsa(d2i_RSA_PUBKEY(NULL, &asn1_ptr, public_key.size()));
if (!rsa.get()) {
LOG(ERROR) << __func__ << ": Failed to decode public key: "
@@ -276,6 +278,24 @@
return true;
}
+bool CryptoUtilityImpl::VerifySignature(const std::string& public_key,
+ const std::string& data,
+ const std::string& signature) {
+ auto asn1_ptr = reinterpret_cast<const unsigned char*>(public_key.data());
+ crypto::ScopedRSA rsa(d2i_RSA_PUBKEY(NULL, &asn1_ptr, public_key.size()));
+ if (!rsa.get()) {
+ LOG(ERROR) << __func__ << ": Failed to decode public key: "
+ << GetOpenSSLError();
+ return false;
+ }
+ std::string digest = crypto::SHA256HashString(data);
+ auto digest_buffer = reinterpret_cast<const unsigned char*>(digest.data());
+ std::string mutable_signature(signature);
+ unsigned char* signature_buffer = StringAsOpenSSLBuffer(&mutable_signature);
+ return (RSA_verify(NID_sha256, digest_buffer, digest.size(),
+ signature_buffer, signature.size(), rsa.get()) == 1);
+}
+
bool CryptoUtilityImpl::AesEncrypt(const std::string& data,
const std::string& key,
const std::string& iv,
@@ -386,13 +406,6 @@
return std::string(std::begin(mac), std::end(mac));
}
-std::string CryptoUtilityImpl::Sha1(const std::string& input) {
- unsigned char digest[SHA_DIGEST_LENGTH];
- SHA1(reinterpret_cast<const unsigned char*>(input.data()), input.size(),
- digest);
- return std::string(reinterpret_cast<const char*>(digest), SHA_DIGEST_LENGTH);
-}
-
bool CryptoUtilityImpl::TssCompatibleEncrypt(const std::string& input,
const std::string& key,
std::string* output) {
@@ -422,8 +435,7 @@
padded_input.resize(RSA_size(key));
auto padded_buffer = reinterpret_cast<unsigned char*>(
string_as_array(&padded_input));
- const unsigned char* input_buffer = reinterpret_cast<const unsigned char*>(
- input.data());
+ auto input_buffer = reinterpret_cast<const unsigned char*>(input.data());
int result = RSA_padding_add_PKCS1_OAEP(padded_buffer, padded_input.size(),
input_buffer, input.size(),
oaep_param, arraysize(oaep_param));
diff --git a/common/crypto_utility_impl.h b/common/crypto_utility_impl.h
index dbb917b..04521c0 100644
--- a/common/crypto_utility_impl.h
+++ b/common/crypto_utility_impl.h
@@ -47,6 +47,9 @@
bool EncryptForUnbind(const std::string& public_key,
const std::string& data,
std::string* encrypted_data) override;
+ bool VerifySignature(const std::string& public_key,
+ const std::string& data,
+ const std::string& signature) override;
private:
// Encrypts |data| using |key| and |iv| for AES in CBC mode with PKCS #5
@@ -66,9 +69,6 @@
// Computes and returns an HMAC of |data| using |key| and SHA-512.
std::string HmacSha512(const std::string& data, const std::string& key);
- // Computes and returns the SHA-1 digest of |input|.
- std::string Sha1(const std::string& input);
-
// Encrypt like trousers does. This is like AesEncrypt but a random IV is
// included in the output.
bool TssCompatibleEncrypt(const std::string& input,
diff --git a/common/crypto_utility_impl_test.cc b/common/crypto_utility_impl_test.cc
index 0892ac9..fdd16b8 100644
--- a/common/crypto_utility_impl_test.cc
+++ b/common/crypto_utility_impl_test.cc
@@ -212,4 +212,18 @@
&output));
}
+TEST_F(CryptoUtilityImplTest, VerifySignatureBadSignature) {
+ std::string public_key = HexDecode(kValidPublicKeyHex);
+ std::string public_key_info;
+ EXPECT_TRUE(crypto_utility_->GetRSASubjectPublicKeyInfo(public_key,
+ &public_key_info));
+ std::string output;
+ EXPECT_FALSE(crypto_utility_->VerifySignature(public_key_info, "input",
+ "signature"));
+}
+
+TEST_F(CryptoUtilityImplTest, VerifySignatureBadKey) {
+ EXPECT_FALSE(crypto_utility_->VerifySignature("bad_key", "input", ""));
+}
+
} // namespace attestation
diff --git a/common/dbus_interface.h b/common/dbus_interface.h
index 83c3de6..97cde33 100644
--- a/common/dbus_interface.h
+++ b/common/dbus_interface.h
@@ -20,6 +20,7 @@
constexpr char kActivateAttestationKey[] = "ActivateAttestationKey";
constexpr char kCreateCertifiableKey[] = "CreateCertifiableKey";
constexpr char kDecrypt[] = "Decrypt";
+constexpr char kSign[] = "Sign";
} // namespace attestation
diff --git a/common/interface.proto b/common/interface.proto
index 946735c..0caa67b 100644
--- a/common/interface.proto
+++ b/common/interface.proto
@@ -137,3 +137,14 @@
optional AttestationStatus status = 1;
optional bytes decrypted_data = 2;
}
+
+message SignRequest {
+ optional string key_label = 1;
+ optional string username = 2;
+ optional bytes data_to_sign = 3;
+}
+
+message SignReply {
+ optional AttestationStatus status = 1;
+ optional bytes signature = 2;
+}
diff --git a/common/mock_attestation_interface.h b/common/mock_attestation_interface.h
index e9e89b7..d568a43 100644
--- a/common/mock_attestation_interface.h
+++ b/common/mock_attestation_interface.h
@@ -35,6 +35,7 @@
MOCK_METHOD2(CreateCertifiableKey, void(const CreateCertifiableKeyRequest&,
const CreateCertifiableKeyCallback&));
MOCK_METHOD2(Decrypt, void(const DecryptRequest&, const DecryptCallback&));
+ MOCK_METHOD2(Sign, void(const SignRequest&, const SignCallback&));
};
} // namespace attestation
diff --git a/common/mock_crypto_utility.h b/common/mock_crypto_utility.h
index 0b68885..4aaa1db 100644
--- a/common/mock_crypto_utility.h
+++ b/common/mock_crypto_utility.h
@@ -45,6 +45,9 @@
MOCK_METHOD3(EncryptForUnbind, bool(const std::string&,
const std::string&,
std::string*));
+ MOCK_METHOD3(VerifySignature, bool(const std::string&,
+ const std::string&,
+ const std::string&));
};
} // namespace attestation
diff --git a/common/mock_tpm_utility.cc b/common/mock_tpm_utility.cc
index 2b38909..055bccb 100644
--- a/common/mock_tpm_utility.cc
+++ b/common/mock_tpm_utility.cc
@@ -11,10 +11,34 @@
namespace {
-bool CopyString(const std::string& in, std::string* out) {
- *out = in;
- return true;
-}
+class TransformString {
+ public:
+ explicit TransformString(std::string method) : method_(method) {}
+ bool operator()(const std::string& in, std::string* out) {
+ *out = attestation::MockTpmUtility::Transform(method_, in);
+ return true;
+ }
+
+ private:
+ std::string method_;
+};
+
+class UntransformString {
+ public:
+ explicit UntransformString(std::string method) : method_(method) {}
+ bool operator()(const std::string& in, std::string* out) {
+ std::string suffix = "_fake_transform_" + method_;
+ auto position = in.find(suffix);
+ if (position == std::string::npos) {
+ return false;
+ }
+ *out = in.substr(0, position);
+ return true;
+ }
+
+ private:
+ std::string method_;
+};
} // namespace
@@ -27,13 +51,21 @@
ON_CALL(*this, CreateCertifiedKey(_, _, _, _, _, _, _, _, _))
.WillByDefault(Return(true));
ON_CALL(*this, SealToPCR0(_, _))
- .WillByDefault(Invoke(CopyString));
+ .WillByDefault(Invoke(TransformString("SealToPCR0")));
ON_CALL(*this, Unseal(_, _))
- .WillByDefault(Invoke(CopyString));
+ .WillByDefault(Invoke(UntransformString("SealToPCR0")));
ON_CALL(*this, Unbind(_, _, _))
- .WillByDefault(WithArgs<1, 2>(Invoke(CopyString)));
+ .WillByDefault(WithArgs<1, 2>(Invoke(TransformString("Unbind"))));
+ ON_CALL(*this, Sign(_, _, _))
+ .WillByDefault(WithArgs<1, 2>(Invoke(TransformString("Sign"))));
}
MockTpmUtility::~MockTpmUtility() {}
+// static
+std::string MockTpmUtility::Transform(const std::string& method,
+ const std::string& input) {
+ return input + "_fake_transform_" + method;
+}
+
} // namespace attestation
diff --git a/common/mock_tpm_utility.h b/common/mock_tpm_utility.h
index deb9746..5ebf93f 100644
--- a/common/mock_tpm_utility.h
+++ b/common/mock_tpm_utility.h
@@ -17,6 +17,12 @@
public:
MockTpmUtility();
~MockTpmUtility() override;
+ // By default this class will fake seal/unbind/sign operations by passing the
+ // input through Transform(<method>). E.g. The expected output of a fake Sign
+ // operation on "foo" can be computed by calling
+ // MockTpmUtility::Transform("Sign", "foo").
+ static std::string Transform(const std::string& method,
+ const std::string& input);
MOCK_METHOD0(IsTpmReady, bool());
MOCK_METHOD6(ActivateIdentity, bool(const std::string&,
@@ -39,6 +45,8 @@
MOCK_METHOD1(GetEndorsementPublicKey, bool(std::string*));
MOCK_METHOD3(Unbind, bool(const std::string&, const std::string&,
std::string*));
+ MOCK_METHOD3(Sign, bool(const std::string&, const std::string&,
+ std::string*));
};
} // namespace attestation
diff --git a/common/tpm_utility.h b/common/tpm_utility.h
index 98591c3..e9cc1e3 100644
--- a/common/tpm_utility.h
+++ b/common/tpm_utility.h
@@ -71,6 +71,13 @@
virtual bool Unbind(const std::string& key_blob,
const std::string& bound_data,
std::string* data) = 0;
+
+ // Signs |data_to_sign| with the key loaded from |key_blob| using the
+ // TPM_SS_RSASSAPKCS1v15_DER scheme with SHA-256. On success returns true and
+ // provides the |signature|.
+ virtual bool Sign(const std::string& key_blob,
+ const std::string& data_to_sign,
+ std::string* signature) = 0;
};
} // namespace attestation
diff --git a/common/tpm_utility_v1.cc b/common/tpm_utility_v1.cc
index b6749ae..6e918f0 100644
--- a/common/tpm_utility_v1.cc
+++ b/common/tpm_utility_v1.cc
@@ -10,7 +10,9 @@
#include <base/memory/scoped_ptr.h>
#include <base/stl_util.h>
#include <crypto/scoped_openssl_types.h>
+#include <crypto/sha2.h>
#include <openssl/rsa.h>
+#include <openssl/sha.h>
#include <trousers/scoped_tss_type.h>
#include <trousers/trousers.h>
#include <trousers/tss.h>
@@ -28,10 +30,15 @@
using ScopedByteArray = scoped_ptr<BYTE, base::FreeDeleter>;
using ScopedTssEncryptedData = trousers::ScopedTssObject<TSS_HENCDATA>;
+using ScopedTssHash = trousers::ScopedTssObject<TSS_HHASH>;
const char* kTpmEnabledFile = "/sys/class/misc/tpm0/device/enabled";
const char* kTpmOwnedFile = "/sys/class/misc/tpm0/device/owned";
const unsigned int kWellKnownExponent = 65537;
+const unsigned char kSha256DigestInfo[] = {
+ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04,
+ 0x02, 0x01, 0x05, 0x00, 0x04, 0x20
+};
std::string GetFirstByte(const char* file_name) {
std::string content;
@@ -437,6 +444,50 @@
return true;
}
+bool TpmUtilityV1::Sign(const std::string& key_blob,
+ const std::string& data_to_sign,
+ std::string* signature) {
+ CHECK(signature);
+ if (!SetupSrk()) {
+ LOG(ERROR) << "SRK is not ready.";
+ return false;
+ }
+ ScopedTssKey key_handle(context_handle_);
+ if (!LoadKeyFromBlob(key_blob, context_handle_, srk_handle_, &key_handle)) {
+ return false;
+ }
+ // Construct an ASN.1 DER DigestInfo.
+ std::string digest_to_sign(std::begin(kSha256DigestInfo),
+ std::end(kSha256DigestInfo));
+ digest_to_sign += crypto::SHA256HashString(data_to_sign);
+ // Create a hash object to hold the digest.
+ ScopedTssHash hash_handle(context_handle_);
+ TSS_RESULT result = Tspi_Context_CreateObject(context_handle_,
+ TSS_OBJECT_TYPE_HASH,
+ TSS_HASH_OTHER,
+ hash_handle.ptr());
+ if (TPM_ERROR(result)) {
+ TPM_LOG(ERROR, result) << __func__ << ": Failed to create hash object.";
+ return false;
+ }
+ result = Tspi_Hash_SetHashValue(hash_handle,
+ digest_to_sign.size(),
+ StringAsTSSBuffer(&digest_to_sign));
+ if (TPM_ERROR(result)) {
+ TPM_LOG(ERROR, result) << __func__ << ": Failed to set hash data.";
+ return false;
+ }
+ UINT32 length = 0;
+ ScopedTssMemory buffer(context_handle_);
+ result = Tspi_Hash_Sign(hash_handle, key_handle, &length, buffer.ptr());
+ if (TPM_ERROR(result)) {
+ TPM_LOG(ERROR, result) << __func__ << ": Failed to generate signature.";
+ return false;
+ }
+ signature->assign(TSSBufferAsString(buffer.value(), length));
+ return true;
+}
+
bool TpmUtilityV1::ConnectContext(ScopedTssContext* context, TSS_HTPM* tpm) {
*tpm = 0;
TSS_RESULT result;
@@ -654,5 +705,4 @@
return true;
}
-
} // namespace attestation
diff --git a/common/tpm_utility_v1.h b/common/tpm_utility_v1.h
index 29de576..79b9073 100644
--- a/common/tpm_utility_v1.h
+++ b/common/tpm_utility_v1.h
@@ -48,6 +48,9 @@
bool Unbind(const std::string& key_blob,
const std::string& bound_data,
std::string* data) override;
+ bool Sign(const std::string& key_blob,
+ const std::string& data_to_sign,
+ std::string* signature) override;
private:
// Populates |context_handle| with a valid TSS_HCONTEXT and |tpm_handle| with
diff --git a/server/attestation_service.cc b/server/attestation_service.cc
index c0cf126..2d50ecf 100644
--- a/server/attestation_service.cc
+++ b/server/attestation_service.cc
@@ -429,6 +429,37 @@
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);
+}
+
bool AttestationService::IsPreparedForEnrollment() {
if (!tpm_utility_->IsTpmReady()) {
return false;
diff --git a/server/attestation_service.h b/server/attestation_service.h
index ae6dead..1876c2a 100644
--- a/server/attestation_service.h
+++ b/server/attestation_service.h
@@ -74,6 +74,7 @@
const CreateCertifiableKeyCallback& callback) override;
void Decrypt(const DecryptRequest& request,
const DecryptCallback& callback) override;
+ void Sign(const SignRequest& request, const SignCallback& callback) override;
// Mutators useful for testing.
void set_crypto_utility(CryptoUtility* crypto_utility) {
@@ -117,41 +118,45 @@
callback.Run(*reply);
}
- // A synchronous implementation of CreateGoogleAttestedKey appropriate to run
- // on the worker thread.
+ // A blocking implementation of CreateGoogleAttestedKey appropriate to run on
+ // the worker thread.
void CreateGoogleAttestedKeyTask(
const CreateGoogleAttestedKeyRequest& request,
const std::shared_ptr<CreateGoogleAttestedKeyReply>& result);
- // A synchronous implementation of GetKeyInfo.
+ // A blocking implementation of GetKeyInfo.
void GetKeyInfoTask(
const GetKeyInfoRequest& request,
const std::shared_ptr<GetKeyInfoReply>& result);
- // A synchronous implementation of GetEndorsementInfo.
+ // A blocking implementation of GetEndorsementInfo.
void GetEndorsementInfoTask(
const GetEndorsementInfoRequest& request,
const std::shared_ptr<GetEndorsementInfoReply>& result);
- // A synchronous implementation of GetAttestationKeyInfo.
+ // A blocking implementation of GetAttestationKeyInfo.
void GetAttestationKeyInfoTask(
const GetAttestationKeyInfoRequest& request,
const std::shared_ptr<GetAttestationKeyInfoReply>& result);
- // A synchronous implementation of ActivateAttestationKey.
+ // A blocking implementation of ActivateAttestationKey.
void ActivateAttestationKeyTask(
const ActivateAttestationKeyRequest& request,
const std::shared_ptr<ActivateAttestationKeyReply>& result);
- // A synchronous implementation of CreateCertifiableKey.
+ // A blocking implementation of CreateCertifiableKey.
void CreateCertifiableKeyTask(
const CreateCertifiableKeyRequest& request,
const std::shared_ptr<CreateCertifiableKeyReply>& result);
- // A synchronous implementation of Decrypt.
+ // A blocking implementation of Decrypt.
void DecryptTask(const DecryptRequest& request,
const std::shared_ptr<DecryptReply>& result);
+ // A blocking implementation of Sign.
+ void SignTask(const SignRequest& request,
+ const std::shared_ptr<SignReply>& result);
+
// Returns true iff all information required for enrollment with the Google
// Attestation CA is available.
bool IsPreparedForEnrollment();
diff --git a/server/attestation_service_test.cc b/server/attestation_service_test.cc
index ecc2eec..ab2e2f6 100644
--- a/server/attestation_service_test.cc
+++ b/server/attestation_service_test.cc
@@ -851,7 +851,8 @@
// Set expectations on the outputs.
auto callback = [this](const DecryptReply& reply) {
EXPECT_EQ(STATUS_SUCCESS, reply.status());
- EXPECT_EQ("data", reply.decrypted_data());
+ EXPECT_EQ(MockTpmUtility::Transform("Unbind", "data"),
+ reply.decrypted_data());
Quit();
};
DecryptRequest request;
@@ -867,7 +868,8 @@
// Set expectations on the outputs.
auto callback = [this](const DecryptReply& reply) {
EXPECT_EQ(STATUS_SUCCESS, reply.status());
- EXPECT_EQ("data", reply.decrypted_data());
+ EXPECT_EQ(MockTpmUtility::Transform("Unbind", "data"),
+ reply.decrypted_data());
Quit();
};
DecryptRequest request;
@@ -924,4 +926,81 @@
Run();
}
+TEST_F(AttestationServiceTest, SignSuccess) {
+ // Set expectations on the outputs.
+ auto callback = [this](const SignReply& reply) {
+ EXPECT_EQ(STATUS_SUCCESS, reply.status());
+ EXPECT_EQ(MockTpmUtility::Transform("Sign", "data"), reply.signature());
+ Quit();
+ };
+ SignRequest request;
+ request.set_key_label("label");
+ request.set_username("user");
+ request.set_data_to_sign("data");
+ service_->Sign(request, base::Bind(callback));
+ Run();
+}
+
+TEST_F(AttestationServiceTest, SignSuccessNoUser) {
+ mock_database_.GetMutableProtobuf()->add_device_keys()->set_key_name("label");
+ // Set expectations on the outputs.
+ auto callback = [this](const SignReply& reply) {
+ EXPECT_EQ(STATUS_SUCCESS, reply.status());
+ EXPECT_EQ(MockTpmUtility::Transform("Sign", "data"), reply.signature());
+ Quit();
+ };
+ SignRequest request;
+ request.set_key_label("label");
+ request.set_data_to_sign("data");
+ service_->Sign(request, base::Bind(callback));
+ Run();
+}
+
+TEST_F(AttestationServiceTest, SignKeyNotFound) {
+ EXPECT_CALL(mock_key_store_, Read("user", "label", _))
+ .WillRepeatedly(Return(false));
+ // Set expectations on the outputs.
+ auto callback = [this](const SignReply& reply) {
+ EXPECT_NE(STATUS_SUCCESS, reply.status());
+ EXPECT_FALSE(reply.has_signature());
+ Quit();
+ };
+ SignRequest request;
+ request.set_key_label("label");
+ request.set_username("user");
+ request.set_data_to_sign("data");
+ service_->Sign(request, base::Bind(callback));
+ Run();
+}
+
+TEST_F(AttestationServiceTest, SignKeyNotFoundNoUser) {
+ // Set expectations on the outputs.
+ auto callback = [this](const SignReply& reply) {
+ EXPECT_NE(STATUS_SUCCESS, reply.status());
+ EXPECT_FALSE(reply.has_signature());
+ Quit();
+ };
+ SignRequest request;
+ request.set_key_label("label");
+ request.set_data_to_sign("data");
+ service_->Sign(request, base::Bind(callback));
+ Run();
+}
+
+TEST_F(AttestationServiceTest, SignUnbindFailure) {
+ EXPECT_CALL(mock_tpm_utility_, Sign(_, _, _)).WillRepeatedly(Return(false));
+ // Set expectations on the outputs.
+ auto callback = [this](const SignReply& reply) {
+ EXPECT_NE(STATUS_SUCCESS, reply.status());
+ EXPECT_FALSE(reply.has_signature());
+ Quit();
+ };
+ SignRequest request;
+ request.set_key_label("label");
+ request.set_username("user");
+ request.set_data_to_sign("data");
+ service_->Sign(request, base::Bind(callback));
+ Run();
+}
+
} // namespace attestation
diff --git a/server/dbus_service.cc b/server/dbus_service.cc
index 815d0f5..d8bc29a 100644
--- a/server/dbus_service.cc
+++ b/server/dbus_service.cc
@@ -48,6 +48,9 @@
dbus_interface->AddMethodHandler(kDecrypt,
base::Unretained(this),
&DBusService::HandleDecrypt);
+ dbus_interface->AddMethodHandler(kSign,
+ base::Unretained(this),
+ &DBusService::HandleSign);
dbus_object_.RegisterAsync(callback);
}
@@ -183,4 +186,22 @@
base::Bind(callback, SharedResponsePointer(std::move(response))));
}
+void DBusService::HandleSign(
+ std::unique_ptr<DBusMethodResponse<const SignReply&>> response,
+ const SignRequest& request) {
+ VLOG(1) << __func__;
+ // Convert |response| to a shared_ptr so |service_| can safely copy the
+ // callback.
+ using SharedResponsePointer = std::shared_ptr<
+ DBusMethodResponse<const SignReply&>>;
+ // A callback that fills the reply protobuf and sends it.
+ auto callback = [](const SharedResponsePointer& response,
+ const SignReply& reply) {
+ response->Return(reply);
+ };
+ service_->Sign(
+ request,
+ base::Bind(callback, SharedResponsePointer(std::move(response))));
+}
+
} // namespace attestation
diff --git a/server/dbus_service.h b/server/dbus_service.h
index 35fe7be..01e18f7 100644
--- a/server/dbus_service.h
+++ b/server/dbus_service.h
@@ -80,6 +80,12 @@
const DecryptReply&>> response,
const DecryptRequest& request);
+ // Handles a Sign D-Bus call.
+ void HandleSign(
+ std::unique_ptr<chromeos::dbus_utils::DBusMethodResponse<
+ const SignReply&>> response,
+ const SignRequest& request);
+
chromeos::dbus_utils::DBusObject dbus_object_;
AttestationInterface* service_;
diff --git a/server/dbus_service_test.cc b/server/dbus_service_test.cc
index 11f1987..8894e3b 100644
--- a/server/dbus_service_test.cc
+++ b/server/dbus_service_test.cc
@@ -301,8 +301,7 @@
reply.set_decrypted_data("data");
callback.Run(reply);
}));
- std::unique_ptr<dbus::MethodCall> call =
- CreateMethodCall(kDecrypt);
+ std::unique_ptr<dbus::MethodCall> call = CreateMethodCall(kDecrypt);
dbus::MessageWriter writer(call.get());
writer.AppendProtoAsArrayOfBytes(request);
auto response = CallMethod(call.get());
@@ -313,4 +312,32 @@
EXPECT_EQ("data", reply.decrypted_data());
}
+TEST_F(DBusServiceTest, Sign) {
+ SignRequest request;
+ request.set_key_label("label");
+ request.set_username("user");
+ request.set_data_to_sign("data");
+ EXPECT_CALL(mock_service_, Sign(_, _))
+ .WillOnce(Invoke([](
+ const SignRequest& request,
+ const AttestationInterface::SignCallback& callback) {
+ EXPECT_EQ("label", request.key_label());
+ EXPECT_EQ("user", request.username());
+ EXPECT_EQ("data", request.data_to_sign());
+ SignReply reply;
+ reply.set_status(STATUS_SUCCESS);
+ reply.set_signature("signature");
+ callback.Run(reply);
+ }));
+ std::unique_ptr<dbus::MethodCall> call = CreateMethodCall(kSign);
+ dbus::MessageWriter writer(call.get());
+ writer.AppendProtoAsArrayOfBytes(request);
+ auto response = CallMethod(call.get());
+ dbus::MessageReader reader(response.get());
+ SignReply reply;
+ EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&reply));
+ EXPECT_EQ(STATUS_SUCCESS, reply.status());
+ EXPECT_EQ("signature", reply.signature());
+}
+
} // namespace attestation