diff options
author | Bill Yi <byi@google.com> | 2015-11-20 16:46:49 -0800 |
---|---|---|
committer | Bill Yi <byi@google.com> | 2015-11-20 16:46:49 -0800 |
commit | ff2783e1bf1f26cb1173c821cbfa70c5f1cee954 (patch) | |
tree | 3fff41289b3402dbf046df39454e4119167b3ea0 | |
parent | 932dca120584df2887e105c7dfe4a64e7849751a (diff) | |
parent | b3c9cdcef463064d7ac8f1e6b4b88e62433f9d5d (diff) | |
download | tpm-ff2783e1bf1f26cb1173c821cbfa70c5f1cee954.tar.gz |
Add 'attestation/' from commit 'b3c9cdcef463064d7ac8f1e6b4b88e62433f9d5d'
git-subtree-dir: attestation
git-subtree-mainline: 932dca120584df2887e105c7dfe4a64e7849751a
git-subtree-split: b3c9cdcef463064d7ac8f1e6b4b88e62433f9d5d
57 files changed, 11667 insertions, 0 deletions
diff --git a/attestation/MODULE_LICENSE_APACHE2 b/attestation/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/attestation/MODULE_LICENSE_APACHE2 diff --git a/attestation/NOTICE b/attestation/NOTICE new file mode 100644 index 0000000..a849a94 --- /dev/null +++ b/attestation/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2014-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/attestation/OWNERS b/attestation/OWNERS new file mode 100644 index 0000000..699d270 --- /dev/null +++ b/attestation/OWNERS @@ -0,0 +1,3 @@ +set noparent +dkrahn@chromium.org +namnguyen@chromium.org diff --git a/attestation/PRESUBMIT.cfg b/attestation/PRESUBMIT.cfg new file mode 100644 index 0000000..087dfa3 --- /dev/null +++ b/attestation/PRESUBMIT.cfg @@ -0,0 +1,3 @@ +[Hook Overrides] +cros_license_check: false +aosp_license_check: true diff --git a/attestation/attestation.gyp b/attestation/attestation.gyp new file mode 100644 index 0000000..0f691f8 --- /dev/null +++ b/attestation/attestation.gyp @@ -0,0 +1,195 @@ +# +# 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. +# + +{ + 'target_defaults': { + 'variables': { + 'deps': [ # This is a list of pkg-config dependencies + 'libbrillo-<(libbase_ver)', + 'libchrome-<(libbase_ver)', + 'protobuf-lite', + ], + }, + 'include_dirs': [ + # We need this include dir because we include all the local code as + # "attestation/...". + '<(platform2_root)/../aosp/system/', + ], + }, + 'targets': [ + # A library for just the protobufs. + { + 'target_name': 'proto_library', + 'type': 'static_library', + # Use -fPIC so this code can be linked into a shared library. + 'cflags!': ['-fPIE'], + 'cflags': [ + '-fPIC', + '-fvisibility=default', + ], + 'variables': { + 'proto_in_dir': 'common', + 'proto_out_dir': 'include/attestation/common', + }, + 'sources': [ + '<(proto_in_dir)/attestation_ca.proto', + '<(proto_in_dir)/common.proto', + '<(proto_in_dir)/database.proto', + '<(proto_in_dir)/interface.proto', + 'common/print_common_proto.cc', + 'common/print_interface_proto.cc', + ], + 'includes': ['../../../platform2/common-mk/protoc.gypi'], + }, + # A library for common code. + { + 'target_name': 'common_library', + 'type': 'static_library', + 'sources': [ + 'common/crypto_utility_impl.cc', + 'common/tpm_utility_v1.cc', + ], + 'all_dependent_settings': { + 'variables': { + 'deps': [ + 'openssl', + ], + }, + 'libraries': [ + '-ltspi', + ], + }, + 'dependencies': [ + 'proto_library', + ], + }, + # A library for client code. + { + 'target_name': 'client_library', + 'type': 'static_library', + # Use -fPIC so this code can be linked into a shared library. + 'cflags!': ['-fPIE'], + 'cflags': [ + '-fPIC', + '-fvisibility=default', + ], + 'sources': [ + 'client/dbus_proxy.cc', + ], + 'dependencies': [ + 'proto_library', + ], + }, + # A shared library for clients. + { + 'target_name': 'libattestation', + 'type': 'shared_library', + 'cflags': ['-fvisibility=default'], + 'sources': [ + ], + 'dependencies': [ + 'client_library', + 'proto_library', + ], + }, + # A client command line utility. + { + 'target_name': 'attestation_client', + 'type': 'executable', + 'sources': [ + 'client/main.cc', + ], + 'dependencies': [ + 'client_library', + 'common_library', + 'proto_library', + ] + }, + # A library for server code. + { + 'target_name': 'server_library', + 'type': 'static_library', + 'sources': [ + 'server/attestation_service.cc', + 'server/dbus_service.cc', + 'server/database_impl.cc', + 'server/pkcs11_key_store.cc', + ], + 'all_dependent_settings': { + 'libraries': [ + '-lchaps', + ], + }, + 'dependencies': [ + 'proto_library', + ], + }, + # The attestation daemon. + { + 'target_name': 'attestationd', + 'type': 'executable', + 'sources': [ + 'server/main.cc', + ], + 'variables': { + 'deps': [ + 'libminijail', + ], + }, + 'dependencies': [ + 'common_library', + 'proto_library', + 'server_library', + ], + }, + ], + 'conditions': [ + ['USE_test == 1', { + 'targets': [ + { + 'target_name': 'attestation_testrunner', + 'type': 'executable', + 'includes': ['../../../platform2/common-mk/common_test.gypi'], + 'variables': { + 'deps': [ + 'libbrillo-test-<(libbase_ver)', + 'libchrome-test-<(libbase_ver)', + ], + }, + 'sources': [ + 'attestation_testrunner.cc', + 'client/dbus_proxy_test.cc', + 'common/crypto_utility_impl_test.cc', + 'common/mock_crypto_utility.cc', + 'common/mock_tpm_utility.cc', + 'server/attestation_service_test.cc', + 'server/database_impl_test.cc', + 'server/dbus_service_test.cc', + 'server/mock_database.cc', + 'server/mock_key_store.cc', + 'server/pkcs11_key_store_test.cc', + ], + 'dependencies': [ + 'common_library', + 'client_library', + 'proto_library', + 'server_library', + ], + }, + ], + }], + ], +} diff --git a/attestation/attestation_testrunner.cc b/attestation/attestation_testrunner.cc new file mode 100644 index 0000000..2aac479 --- /dev/null +++ b/attestation/attestation_testrunner.cc @@ -0,0 +1,31 @@ +// +// 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 <base/at_exit.h> +#include <base/command_line.h> +#include <base/logging.h> +#include <brillo/syslog_logging.h> +#include <gtest/gtest.h> + +int main(int argc, char **argv) { + base::CommandLine::Init(argc, argv); + brillo::InitLog(brillo::kLogToStderr); + // Enable verbose logging while running unit tests. + logging::SetMinLogLevel(logging::LOG_VERBOSE); + base::AtExitManager exit_manager; + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/attestation/client/dbus_proxy.cc b/attestation/client/dbus_proxy.cc new file mode 100644 index 0000000..bf37d0a --- /dev/null +++ b/attestation/client/dbus_proxy.cc @@ -0,0 +1,208 @@ +// +// 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/client/dbus_proxy.h" + +#include <brillo/bind_lambda.h> +#include <brillo/dbus/dbus_method_invoker.h> + +#include "attestation/common/dbus_interface.h" + +namespace { + +// Use a two minute timeout because TPM operations can take a long time and +// there may be a few of them queued up. +const int kDBusTimeoutMS = 120000; + +} // namespace + +namespace attestation { + +DBusProxy::DBusProxy() {} +DBusProxy::~DBusProxy() { + if (bus_) { + bus_->ShutdownAndBlock(); + } +} + +bool DBusProxy::Initialize() { + dbus::Bus::Options options; + options.bus_type = dbus::Bus::SYSTEM; + bus_ = new dbus::Bus(options); + object_proxy_ = bus_->GetObjectProxy( + attestation::kAttestationServiceName, + dbus::ObjectPath(attestation::kAttestationServicePath)); + return (object_proxy_ != nullptr); +} + +void DBusProxy::CreateGoogleAttestedKey( + const CreateGoogleAttestedKeyRequest& request, + const CreateGoogleAttestedKeyCallback& callback) { + auto on_error = [callback](brillo::Error* error) { + CreateGoogleAttestedKeyReply reply; + reply.set_status(STATUS_NOT_AVAILABLE); + callback.Run(reply); + }; + brillo::dbus_utils::CallMethodWithTimeout( + kDBusTimeoutMS, + object_proxy_, + attestation::kAttestationInterface, + attestation::kCreateGoogleAttestedKey, + callback, + base::Bind(on_error), + request); +} + +void DBusProxy::GetKeyInfo(const GetKeyInfoRequest& request, + const GetKeyInfoCallback& callback) { + auto on_error = [callback](brillo::Error* error) { + GetKeyInfoReply reply; + reply.set_status(STATUS_NOT_AVAILABLE); + callback.Run(reply); + }; + brillo::dbus_utils::CallMethodWithTimeout( + kDBusTimeoutMS, + object_proxy_, + attestation::kAttestationInterface, + attestation::kGetKeyInfo, + callback, + base::Bind(on_error), + request); +} + +void DBusProxy::GetEndorsementInfo(const GetEndorsementInfoRequest& request, + const GetEndorsementInfoCallback& callback) { + auto on_error = [callback](brillo::Error* error) { + GetEndorsementInfoReply reply; + reply.set_status(STATUS_NOT_AVAILABLE); + callback.Run(reply); + }; + brillo::dbus_utils::CallMethodWithTimeout( + kDBusTimeoutMS, + object_proxy_, + attestation::kAttestationInterface, + attestation::kGetEndorsementInfo, + callback, + base::Bind(on_error), + request); +} + +void DBusProxy::GetAttestationKeyInfo( + const GetAttestationKeyInfoRequest& request, + const GetAttestationKeyInfoCallback& callback) { + auto on_error = [callback](brillo::Error* error) { + GetAttestationKeyInfoReply reply; + reply.set_status(STATUS_NOT_AVAILABLE); + callback.Run(reply); + }; + brillo::dbus_utils::CallMethodWithTimeout( + kDBusTimeoutMS, + object_proxy_, + attestation::kAttestationInterface, + attestation::kGetAttestationKeyInfo, + callback, + base::Bind(on_error), + request); +} + +void DBusProxy::ActivateAttestationKey( + const ActivateAttestationKeyRequest& request, + const ActivateAttestationKeyCallback& callback) { + auto on_error = [callback](brillo::Error* error) { + ActivateAttestationKeyReply reply; + reply.set_status(STATUS_NOT_AVAILABLE); + callback.Run(reply); + }; + brillo::dbus_utils::CallMethodWithTimeout( + kDBusTimeoutMS, + object_proxy_, + attestation::kAttestationInterface, + attestation::kActivateAttestationKey, + callback, + base::Bind(on_error), + request); +} + +void DBusProxy::CreateCertifiableKey( + const CreateCertifiableKeyRequest& request, + const CreateCertifiableKeyCallback& callback) { + auto on_error = [callback](brillo::Error* error) { + CreateCertifiableKeyReply reply; + reply.set_status(STATUS_NOT_AVAILABLE); + callback.Run(reply); + }; + brillo::dbus_utils::CallMethodWithTimeout( + kDBusTimeoutMS, + object_proxy_, + attestation::kAttestationInterface, + attestation::kCreateCertifiableKey, + callback, + base::Bind(on_error), + request); +} + +void DBusProxy::Decrypt(const DecryptRequest& request, + const DecryptCallback& callback) { + auto on_error = [callback](brillo::Error* error) { + DecryptReply reply; + reply.set_status(STATUS_NOT_AVAILABLE); + callback.Run(reply); + }; + brillo::dbus_utils::CallMethodWithTimeout( + kDBusTimeoutMS, + object_proxy_, + attestation::kAttestationInterface, + attestation::kDecrypt, + callback, + base::Bind(on_error), + request); +} + +void DBusProxy::Sign(const SignRequest& request, const SignCallback& callback) { + auto on_error = [callback](brillo::Error* error) { + SignReply reply; + reply.set_status(STATUS_NOT_AVAILABLE); + callback.Run(reply); + }; + brillo::dbus_utils::CallMethodWithTimeout( + kDBusTimeoutMS, + object_proxy_, + attestation::kAttestationInterface, + attestation::kSign, + callback, + base::Bind(on_error), + request); +} + +void DBusProxy::RegisterKeyWithChapsToken( + const RegisterKeyWithChapsTokenRequest& request, + const RegisterKeyWithChapsTokenCallback& callback) { + auto on_error = [callback](brillo::Error* error) { + RegisterKeyWithChapsTokenReply reply; + reply.set_status(STATUS_NOT_AVAILABLE); + callback.Run(reply); + }; + brillo::dbus_utils::CallMethodWithTimeout( + kDBusTimeoutMS, + object_proxy_, + attestation::kAttestationInterface, + attestation::kRegisterKeyWithChapsToken, + callback, + base::Bind(on_error), + request); +} + +} // namespace attestation diff --git a/attestation/client/dbus_proxy.h b/attestation/client/dbus_proxy.h new file mode 100644 index 0000000..9ecd527 --- /dev/null +++ b/attestation/client/dbus_proxy.h @@ -0,0 +1,77 @@ +// +// 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. +// + +#ifndef ATTESTATION_CLIENT_DBUS_PROXY_H_ +#define ATTESTATION_CLIENT_DBUS_PROXY_H_ + +#include "attestation/common/attestation_interface.h" + +#include <string> + +#include <base/memory/ref_counted.h> +#include <dbus/bus.h> +#include <dbus/object_proxy.h> + +namespace attestation { + +// An implementation of AttestationInterface that forwards requests over D-Bus. +// Usage: +// std::unique_ptr<AttestationInterface> attestation = new DBusProxy(); +// attestation->Initialize(); +// attestation->CreateGoogleAttestedKey(...); +class DBusProxy : public AttestationInterface { + public: + DBusProxy(); + virtual ~DBusProxy(); + + // AttestationInterface methods. + bool Initialize() override; + void CreateGoogleAttestedKey( + const CreateGoogleAttestedKeyRequest& request, + const CreateGoogleAttestedKeyCallback& callback) override; + void GetKeyInfo(const GetKeyInfoRequest& request, + const GetKeyInfoCallback& callback) override; + void GetEndorsementInfo(const GetEndorsementInfoRequest& request, + const GetEndorsementInfoCallback& callback) override; + void GetAttestationKeyInfo( + const GetAttestationKeyInfoRequest& request, + const GetAttestationKeyInfoCallback& callback) override; + void ActivateAttestationKey( + const ActivateAttestationKeyRequest& request, + const ActivateAttestationKeyCallback& callback) override; + void CreateCertifiableKey( + const CreateCertifiableKeyRequest& request, + const CreateCertifiableKeyCallback& callback) override; + void Decrypt(const DecryptRequest& request, + const DecryptCallback& callback) override; + void Sign(const SignRequest& request, const SignCallback& callback) override; + void RegisterKeyWithChapsToken( + const RegisterKeyWithChapsTokenRequest& request, + const RegisterKeyWithChapsTokenCallback& callback) override; + + // Useful for testing. + void set_object_proxy(dbus::ObjectProxy* object_proxy) { + object_proxy_ = object_proxy; + } + + private: + scoped_refptr<dbus::Bus> bus_; + dbus::ObjectProxy* object_proxy_; +}; + +} // namespace attestation + +#endif // ATTESTATION_CLIENT_DBUS_PROXY_H_ diff --git a/attestation/client/dbus_proxy_test.cc b/attestation/client/dbus_proxy_test.cc new file mode 100644 index 0000000..7170a28 --- /dev/null +++ b/attestation/client/dbus_proxy_test.cc @@ -0,0 +1,414 @@ +// +// 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 <string> + +#include <brillo/bind_lambda.h> +#include <dbus/mock_object_proxy.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "attestation/client/dbus_proxy.h" + +using testing::_; +using testing::Invoke; +using testing::StrictMock; +using testing::WithArgs; + +namespace attestation { + +class DBusProxyTest : public testing::Test { + public: + ~DBusProxyTest() override = default; + void SetUp() override { + mock_object_proxy_ = new StrictMock<dbus::MockObjectProxy>( + nullptr, "", dbus::ObjectPath("")); + proxy_.set_object_proxy(mock_object_proxy_.get()); + } + protected: + scoped_refptr<StrictMock<dbus::MockObjectProxy>> mock_object_proxy_; + DBusProxy proxy_; +}; + +TEST_F(DBusProxyTest, CreateGoogleAttestedKey) { + auto fake_dbus_call = []( + dbus::MethodCall* method_call, + const dbus::MockObjectProxy::ResponseCallback& response_callback) { + // Verify request protobuf. + dbus::MessageReader reader(method_call); + CreateGoogleAttestedKeyRequest request_proto; + EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&request_proto)); + EXPECT_EQ("label", request_proto.key_label()); + EXPECT_EQ(KEY_TYPE_ECC, request_proto.key_type()); + EXPECT_EQ(KEY_USAGE_SIGN, request_proto.key_usage()); + EXPECT_EQ(ENTERPRISE_MACHINE_CERTIFICATE, + request_proto.certificate_profile()); + EXPECT_EQ("user", request_proto.username()); + EXPECT_EQ("origin", request_proto.origin()); + // Create reply protobuf. + auto response = dbus::Response::CreateEmpty(); + dbus::MessageWriter writer(response.get()); + CreateGoogleAttestedKeyReply reply_proto; + reply_proto.set_status(STATUS_SUCCESS); + reply_proto.set_certificate_chain("certificate"); + reply_proto.set_server_error("server_error"); + 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 CreateGoogleAttestedKeyReply& reply) { + callback_count++; + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ("certificate", reply.certificate_chain()); + EXPECT_EQ("server_error", reply.server_error()); + }; + CreateGoogleAttestedKeyRequest request; + request.set_key_label("label"); + request.set_key_type(KEY_TYPE_ECC); + request.set_key_usage(KEY_USAGE_SIGN); + request.set_certificate_profile(ENTERPRISE_MACHINE_CERTIFICATE); + request.set_username("user"); + request.set_origin("origin"); + proxy_.CreateGoogleAttestedKey(request, base::Bind(callback)); + EXPECT_EQ(1, callback_count); +} + +TEST_F(DBusProxyTest, GetKeyInfo) { + auto fake_dbus_call = []( + dbus::MethodCall* method_call, + const dbus::MockObjectProxy::ResponseCallback& response_callback) { + // Verify request protobuf. + dbus::MessageReader reader(method_call); + GetKeyInfoRequest request_proto; + EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&request_proto)); + EXPECT_EQ("label", request_proto.key_label()); + EXPECT_EQ("username", request_proto.username()); + // Create reply protobuf. + auto response = dbus::Response::CreateEmpty(); + dbus::MessageWriter writer(response.get()); + GetKeyInfoReply reply_proto; + reply_proto.set_status(STATUS_SUCCESS); + reply_proto.set_key_type(KEY_TYPE_ECC); + reply_proto.set_key_usage(KEY_USAGE_SIGN); + reply_proto.set_public_key("public_key"); + reply_proto.set_certify_info("certify_info"); + reply_proto.set_certify_info_signature("signature"); + reply_proto.set_certificate("certificate"); + 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 GetKeyInfoReply& reply) { + callback_count++; + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ(KEY_TYPE_ECC, reply.key_type()); + EXPECT_EQ(KEY_USAGE_SIGN, reply.key_usage()); + EXPECT_EQ("public_key", reply.public_key()); + EXPECT_EQ("certify_info", reply.certify_info()); + EXPECT_EQ("signature", reply.certify_info_signature()); + EXPECT_EQ("certificate", reply.certificate()); + }; + GetKeyInfoRequest request; + request.set_key_label("label"); + request.set_username("username"); + proxy_.GetKeyInfo(request, base::Bind(callback)); + EXPECT_EQ(1, callback_count); +} + +TEST_F(DBusProxyTest, GetEndorsementInfo) { + auto fake_dbus_call = []( + dbus::MethodCall* method_call, + const dbus::MockObjectProxy::ResponseCallback& response_callback) { + // Verify request protobuf. + dbus::MessageReader reader(method_call); + GetEndorsementInfoRequest request_proto; + EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&request_proto)); + EXPECT_EQ(KEY_TYPE_ECC, request_proto.key_type()); + // Create reply protobuf. + auto response = dbus::Response::CreateEmpty(); + dbus::MessageWriter writer(response.get()); + GetEndorsementInfoReply reply_proto; + reply_proto.set_status(STATUS_SUCCESS); + reply_proto.set_ek_public_key("public_key"); + reply_proto.set_ek_certificate("certificate"); + 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 GetEndorsementInfoReply& reply) { + callback_count++; + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ("public_key", reply.ek_public_key()); + EXPECT_EQ("certificate", reply.ek_certificate()); + }; + GetEndorsementInfoRequest request; + request.set_key_type(KEY_TYPE_ECC); + proxy_.GetEndorsementInfo(request, base::Bind(callback)); + EXPECT_EQ(1, callback_count); +} + +TEST_F(DBusProxyTest, GetAttestationKeyInfo) { + auto fake_dbus_call = []( + dbus::MethodCall* method_call, + const dbus::MockObjectProxy::ResponseCallback& response_callback) { + // Verify request protobuf. + dbus::MessageReader reader(method_call); + GetAttestationKeyInfoRequest request_proto; + EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&request_proto)); + EXPECT_EQ(KEY_TYPE_ECC, request_proto.key_type()); + // Create reply protobuf. + auto response = dbus::Response::CreateEmpty(); + dbus::MessageWriter writer(response.get()); + GetAttestationKeyInfoReply reply_proto; + reply_proto.set_status(STATUS_SUCCESS); + reply_proto.set_public_key("public_key"); + reply_proto.set_public_key_tpm_format("public_key_tpm_format"); + reply_proto.set_certificate("certificate"); + reply_proto.mutable_pcr0_quote()->set_quote("pcr0"); + reply_proto.mutable_pcr1_quote()->set_quote("pcr1"); + 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 GetAttestationKeyInfoReply& reply) { + callback_count++; + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ("public_key", reply.public_key()); + EXPECT_EQ("public_key_tpm_format", reply.public_key_tpm_format()); + EXPECT_EQ("certificate", reply.certificate()); + EXPECT_EQ("pcr0", reply.pcr0_quote().quote()); + EXPECT_EQ("pcr1", reply.pcr1_quote().quote()); + }; + GetAttestationKeyInfoRequest request; + request.set_key_type(KEY_TYPE_ECC); + proxy_.GetAttestationKeyInfo(request, base::Bind(callback)); + EXPECT_EQ(1, callback_count); +} + +TEST_F(DBusProxyTest, ActivateAttestationKey) { + auto fake_dbus_call = []( + dbus::MethodCall* method_call, + const dbus::MockObjectProxy::ResponseCallback& response_callback) { + // Verify request protobuf. + dbus::MessageReader reader(method_call); + ActivateAttestationKeyRequest request_proto; + EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&request_proto)); + EXPECT_EQ(KEY_TYPE_ECC, request_proto.key_type()); + EXPECT_EQ("encrypted1", + request_proto.encrypted_certificate().asym_ca_contents()); + EXPECT_EQ("encrypted2", + request_proto.encrypted_certificate().sym_ca_attestation()); + EXPECT_TRUE(request_proto.save_certificate()); + // Create reply protobuf. + auto response = dbus::Response::CreateEmpty(); + dbus::MessageWriter writer(response.get()); + ActivateAttestationKeyReply reply_proto; + reply_proto.set_status(STATUS_SUCCESS); + reply_proto.set_certificate("certificate"); + 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 ActivateAttestationKeyReply& reply) { + callback_count++; + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ("certificate", reply.certificate()); + }; + ActivateAttestationKeyRequest request; + request.set_key_type(KEY_TYPE_ECC); + request.mutable_encrypted_certificate()->set_asym_ca_contents("encrypted1"); + request.mutable_encrypted_certificate()->set_sym_ca_attestation("encrypted2"); + request.set_save_certificate(true); + proxy_.ActivateAttestationKey(request, base::Bind(callback)); + EXPECT_EQ(1, callback_count); +} + +TEST_F(DBusProxyTest, CreateCertifiableKey) { + auto fake_dbus_call = []( + dbus::MethodCall* method_call, + const dbus::MockObjectProxy::ResponseCallback& response_callback) { + // Verify request protobuf. + dbus::MessageReader reader(method_call); + CreateCertifiableKeyRequest request_proto; + EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&request_proto)); + EXPECT_EQ("label", request_proto.key_label()); + EXPECT_EQ(KEY_TYPE_ECC, request_proto.key_type()); + EXPECT_EQ(KEY_USAGE_SIGN, request_proto.key_usage()); + EXPECT_EQ("user", request_proto.username()); + // Create reply protobuf. + auto response = dbus::Response::CreateEmpty(); + dbus::MessageWriter writer(response.get()); + CreateCertifiableKeyReply reply_proto; + reply_proto.set_status(STATUS_SUCCESS); + reply_proto.set_public_key("public_key"); + reply_proto.set_certify_info("certify_info"); + reply_proto.set_certify_info_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 CreateCertifiableKeyReply& reply) { + callback_count++; + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ("public_key", reply.public_key()); + EXPECT_EQ("certify_info", reply.certify_info()); + EXPECT_EQ("signature", reply.certify_info_signature()); + }; + CreateCertifiableKeyRequest request; + request.set_key_label("label"); + request.set_key_type(KEY_TYPE_ECC); + request.set_key_usage(KEY_USAGE_SIGN); + request.set_username("user"); + proxy_.CreateCertifiableKey(request, base::Bind(callback)); + EXPECT_EQ(1, callback_count); +} + +TEST_F(DBusProxyTest, Decrypt) { + auto fake_dbus_call = []( + dbus::MethodCall* method_call, + const dbus::MockObjectProxy::ResponseCallback& response_callback) { + // Verify request protobuf. + dbus::MessageReader reader(method_call); + DecryptRequest 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.encrypted_data()); + // Create reply protobuf. + scoped_ptr<dbus::Response> response = dbus::Response::CreateEmpty(); + dbus::MessageWriter writer(response.get()); + DecryptReply reply_proto; + reply_proto.set_status(STATUS_SUCCESS); + reply_proto.set_decrypted_data("data"); + 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 DecryptReply& reply) { + callback_count++; + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ("data", reply.decrypted_data()); + }; + DecryptRequest request; + request.set_key_label("label"); + request.set_username("user"); + request.set_encrypted_data("data"); + proxy_.Decrypt(request, base::Bind(callback)); + 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); +} + +TEST_F(DBusProxyTest, RegisterKeyWithChapsToken) { + auto fake_dbus_call = []( + dbus::MethodCall* method_call, + const dbus::MockObjectProxy::ResponseCallback& response_callback) { + // Verify request protobuf. + dbus::MessageReader reader(method_call); + RegisterKeyWithChapsTokenRequest request_proto; + EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&request_proto)); + EXPECT_EQ("label", request_proto.key_label()); + EXPECT_EQ("user", request_proto.username()); + // Create reply protobuf. + auto response = dbus::Response::CreateEmpty(); + dbus::MessageWriter writer(response.get()); + RegisterKeyWithChapsTokenReply reply_proto; + reply_proto.set_status(STATUS_SUCCESS); + 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 RegisterKeyWithChapsTokenReply& reply) { + callback_count++; + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + }; + RegisterKeyWithChapsTokenRequest request; + request.set_key_label("label"); + request.set_username("user"); + proxy_.RegisterKeyWithChapsToken(request, base::Bind(callback)); + EXPECT_EQ(1, callback_count); +} + +} // namespace attestation diff --git a/attestation/client/main.cc b/attestation/client/main.cc new file mode 100644 index 0000000..868b42c --- /dev/null +++ b/attestation/client/main.cc @@ -0,0 +1,503 @@ +// +// 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(); +} diff --git a/attestation/common/attestation_ca.proto b/attestation/common/attestation_ca.proto new file mode 100644 index 0000000..b2cd3a3 --- /dev/null +++ b/attestation/common/attestation_ca.proto @@ -0,0 +1,193 @@ +// +// 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. +// + +option optimize_for = LITE_RUNTIME; + +import "common.proto"; + +package attestation; + +// This message holds all information to be sent to the attestation server in +// order to complete enrollment. +message AttestationEnrollmentRequest { + // The EK cert, in X.509 form, encrypted using the server's public key with + // the following parameters: + // Key encryption: RSA-OAEP with no custom parameters. + // Data encryption: 256-bit key, AES-CBC with PKCS5 padding. + // MAC: HMAC-SHA-512 using the AES key. + optional EncryptedData encrypted_endorsement_credential = 1; + // The AIK public key, in TPM_PUBKEY form. + optional bytes identity_public_key = 2; + // PCR0 quoted by AIK. + optional Quote pcr0_quote = 3; + // PCR1 quoted by AIK. + optional Quote pcr1_quote = 4; +} + +enum ResponseStatus { + OK = 0; + // Internal server error. + SERVER_ERROR = 1; + // The server cannot parse the request. + BAD_REQUEST = 2; + // The server rejects the request. + REJECT = 3; + // Only appears in enrollment response. The server returns the same generated + // id and reports the quota limit exceeded status when the number of reset + // action in a specified time window is more than self reset limitation. + QUOTA_LIMIT_EXCEEDED = 4; +} + +// The response from the attestation server for the enrollment request. +message AttestationEnrollmentResponse { + optional ResponseStatus status = 1; + // Detail response message. Included when the result is not OK. + optional string detail = 2; + optional EncryptedIdentityCredential encrypted_identity_credential = 3; +} + +// The certificate request to be sent to the attestation server. +message AttestationCertificateRequest { + // The AIK cert in X.509 format. + optional bytes identity_credential = 1; + // A certified public key in TPM_PUBKEY. + optional bytes certified_public_key = 3; + // The serialized TPM_CERTIFY_INFO for the certified key. + optional bytes certified_key_info = 4; + // The signature of the TPM_CERTIFY_INFO by the AIK. + optional bytes certified_key_proof = 5; + // A message identifier to be included in the response. + optional bytes message_id = 10; + // The certificate profile defines the type of certificate to issue. + optional CertificateProfile profile = 11; + // Information about the origin of the request which may be used depending on + // the certificate profile. + optional string origin = 12; + // The index of a temporal value. This may be used or ignored depending on + // the certificate profile. + optional int32 temporal_index = 13; +} + +// The response from the attestation server for the certificate request. +message AttestationCertificateResponse { + optional ResponseStatus status = 1; + // Detail response message. Included when the result is not OK. + optional string detail = 2; + // The credential of the certified key in X.509 format. + optional bytes certified_key_credential = 3; + // The issuer intermediate CA certificate in X.509 format. + optional bytes intermediate_ca_cert = 5; + // A message identifier from the request this message is responding to. + optional bytes message_id = 6; + // Additional intermediate CA certificates that can help in validation. + // Certificate chaining order is from the leaf to the root. That is, + // |certified_key_credential| is signed by + // |intermediate_ca_cert|, which is signed by + // |additional_intermediate_ca_cert(0)|, which is signed by + // |additional_intermediate_ca_cert(1)|, ... and so on. + repeated bytes additional_intermediate_ca_cert = 7; +} + +// The reset request to be sent to the attestation server. +message AttestationResetRequest { + // The AIK cert, in X.509 form, encrypted using the server's public key with + // the following parameters: + // Key encryption: RSA-OAEP with no custom parameters. + // Data encryption: 256-bit key, AES-CBC with PKCS5 padding. + // MAC: HMAC-SHA-512 using the AES key. + optional EncryptedData encrypted_identity_credential = 1; + + // The one time token to make sure the reset process can be triggered only once. + optional bytes token = 2; + + // The EK cert, in X.509 form, encrypted using the server's public key with + // the following parameters: + // Key encryption: RSA-OAEP with no custom parameters. + // Data encryption: 256-bit key, AES-CBC with PKCS5 padding. + // MAC: HMAC-SHA-512 using the AES key. + optional EncryptedData encrypted_endorsement_credential = 3; +} + +// The response from the attestation server for the reset request. +message AttestationResetResponse { + // The response status. + optional ResponseStatus status = 1; + // Detail response message. Included when the result is not OK. + optional string detail = 2; +} + +// The challenge data (as in challenge-response) generated by the server. +// Before transmitted to the client, this message will be wrapped as a +// SignedData message, in which the data field is the serialized Challenge +// message, and the signature field is the signature of the data field signed +// by the enterprise server using a hard-coded key. The signature algorithm is +// RSASSA-PKCS1-v1_5-SHA256. +message Challenge { + // A string for the client to sanity check a legitimate challenge. + optional string prefix = 1; + // A 256-bit random value generated by the server. + optional bytes nonce = 2; + // A timestamp for a stateless server to limit the timeframe during which the + // challenge may be replayed. + optional int64 timestamp = 3; +} + +// The response data (as in challenge-response) generated by the client. +// Before transmitted to the server, this message will be wrapped as a +// SignedData message, in which the data field is the serialized +// ChallengeResponse message, and the signature field is the signature of the +// data field signed by the client using the key being challenged. The +// signature algorithm is RSASSA-PKCS1-v1_5-SHA256. +message ChallengeResponse { + // The original challenge data. + optional SignedData challenge = 1; + // A 256-bit random value generated by the client. Mixing in this nonce + // prevents a caller from using a challenge to sign arbitrary data. + optional bytes nonce = 2; + // The KeyInfo message encrypted using a public encryption key, pushed via + // policy with the following parameters: + // Key encryption: RSA-OAEP with no custom parameters. + // Data encryption: 256-bit key, AES-CBC with PKCS5 padding. + // MAC: HMAC-SHA-512 using the AES key. + optional EncryptedData encrypted_key_info = 3; +} + +// The data type of the message decrypted from +// ChallengeResponse.encrypted_key_info.encrypted_data field. This message holds +// information required by enterprise server to complete the verification. +message KeyInfo { + // Indicates whether the key is an EMK or EUK. + optional KeyProfile key_type = 1; + // Domain information about the device or user associated with the key. For an + // EMK, this value is the enrolled domain. For an EUK, this value is the + // user's email address. + optional string domain = 2; + // The virtual device ID associated with the device or user. + optional bytes device_id = 3; + // If the key is an EUK, this value is the PCA-issued certificate for the key. + optional bytes certificate = 4; + // If the key is an EUK, this value may hold a SignedPublicKeyAndChallenge + // with a random challenge. The SignedPublicKeyAndChallenge specification is + // here: https://developer.mozilla.org/en-US/docs/HTML/Element/keygen. + optional bytes signed_public_key_and_challenge = 5; +} + +enum KeyProfile { + // Enterprise machine key. + EMK = 0; + // Enterprise user key. + EUK = 1; +} diff --git a/attestation/common/attestation_interface.h b/attestation/common/attestation_interface.h new file mode 100644 index 0000000..ba9f7ed --- /dev/null +++ b/attestation/common/attestation_interface.h @@ -0,0 +1,106 @@ +// +// 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. +// + +#ifndef ATTESTATION_COMMON_ATTESTATION_INTERFACE_H_ +#define ATTESTATION_COMMON_ATTESTATION_INTERFACE_H_ + +#include <string> + +#include <base/callback_forward.h> + +#include "attestation/common/interface.pb.h" + +namespace attestation { + +// The main attestation interface implemented by proxies and services. The +// anticipated flow looks like this: +// [APP] -> AttestationInterface -> [IPC] -> AttestationInterface +class AttestationInterface { + public: + virtual ~AttestationInterface() = default; + + // Performs initialization tasks that may take a long time. This method must + // be successfully called before calling any other method. Returns true on + // success. + virtual bool Initialize() = 0; + + // Processes a CreateGoogleAttestedKeyRequest and responds with a + // CreateGoogleAttestedKeyReply. + using CreateGoogleAttestedKeyCallback = + base::Callback<void(const CreateGoogleAttestedKeyReply&)>; + virtual void CreateGoogleAttestedKey( + const CreateGoogleAttestedKeyRequest& request, + const CreateGoogleAttestedKeyCallback& callback) = 0; + + // Processes a GetKeyInfoRequest and responds with a GetKeyInfoReply. + using GetKeyInfoCallback = base::Callback<void(const GetKeyInfoReply&)>; + virtual void GetKeyInfo(const GetKeyInfoRequest& request, + const GetKeyInfoCallback& callback) = 0; + + // Processes a GetEndorsementInfoRequest and responds with a + // GetEndorsementInfoReply. + using GetEndorsementInfoCallback = + base::Callback<void(const GetEndorsementInfoReply&)>; + virtual void GetEndorsementInfo( + const GetEndorsementInfoRequest& request, + const GetEndorsementInfoCallback& callback) = 0; + + // Processes a GetAttestationKeyInfoRequest and responds with a + // GetAttestationKeyInfoReply. + using GetAttestationKeyInfoCallback = + base::Callback<void(const GetAttestationKeyInfoReply&)>; + virtual void GetAttestationKeyInfo( + const GetAttestationKeyInfoRequest& request, + const GetAttestationKeyInfoCallback& callback) = 0; + + // Processes a ActivateAttestationKeyRequest and responds with a + // ActivateAttestationKeyReply. + using ActivateAttestationKeyCallback = + base::Callback<void(const ActivateAttestationKeyReply&)>; + virtual void ActivateAttestationKey( + const ActivateAttestationKeyRequest& request, + const ActivateAttestationKeyCallback& callback) = 0; + + // Processes a CreateCertifiableKeyRequest and responds with a + // CreateCertifiableKeyReply. + using CreateCertifiableKeyCallback = + base::Callback<void(const CreateCertifiableKeyReply&)>; + virtual void CreateCertifiableKey( + const CreateCertifiableKeyRequest& request, + const CreateCertifiableKeyCallback& callback) = 0; + + // Processes a DecryptRequest and responds with a DecryptReply. + 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; + + // Processes a RegisterKeyWithChapsTokenRequest and responds with a + // RegisterKeyWithChapsTokenReply. + using RegisterKeyWithChapsTokenCallback = + base::Callback<void(const RegisterKeyWithChapsTokenReply&)>; + virtual void RegisterKeyWithChapsToken( + const RegisterKeyWithChapsTokenRequest& request, + const RegisterKeyWithChapsTokenCallback& callback) = 0; +}; + +} // namespace attestation + +#endif // ATTESTATION_COMMON_ATTESTATION_INTERFACE_H_ diff --git a/attestation/common/common.proto b/attestation/common/common.proto new file mode 100644 index 0000000..bdb2775 --- /dev/null +++ b/attestation/common/common.proto @@ -0,0 +1,96 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +option optimize_for = LITE_RUNTIME; + +package attestation; + +// Describes key type. +enum KeyType { + KEY_TYPE_RSA = 1; + KEY_TYPE_ECC = 2; +} + +// Describes allowed key usage. +enum KeyUsage { + KEY_USAGE_SIGN = 1; + KEY_USAGE_DECRYPT = 2; +} + +// Enumerates various certificate profiles supported by the Attestation CA. +enum CertificateProfile { + // A certificate intended for enterprise-owned devices. It has the following + // subjectName fields: + // CN=<stable device identifier> + // OU=state:[verified|developer] + // O=Chrome Device Enterprise + ENTERPRISE_MACHINE_CERTIFICATE = 0; + + // A certificate intended for enterprise-owned user accounts. It has the + // following subjectName fields: + // OU=state:[verified|developer] + // O=Chrome Device Enterprise + ENTERPRISE_USER_CERTIFICATE = 1; + + // A certificate intended for platform verification by providers of protected + // content. It has the following subjectName fields: + // O=Chrome Device Content Protection + CONTENT_PROTECTION_CERTIFICATE = 2; + + // Like above, but it also includes a stable ID and origin. + // CN=<origin-specific device identifier> + // OU=<origin> + // O=Chrome Device Content Protection + CONTENT_PROTECTION_CERTIFICATE_WITH_STABLE_ID = 3; + + // A certificate intended for cast devices. + CAST_CERTIFICATE = 4; + + GFSC_CERTIFICATE = 5; +} + +// Holds information about a quote generated by the TPM. +message Quote { + // The quote; a signature generated with the AIK. + optional bytes quote = 1; + // The serialized data that was quoted; this assists in verifying the quote. + optional bytes quoted_data = 2; + // The value of the PCR(s) at the time the quote was generated. + optional bytes quoted_pcr_value = 3; + // Source data which was originally used to extend the PCR. If this field + // exists it can be expected that SHA1(pcr_source_hint) was extended into the + // PCR. + optional bytes pcr_source_hint = 4; +} + +// Holds encrypted data and information required to decrypt it. +message EncryptedData { + // A key that has been sealed to the TPM or wrapped by another key. + optional bytes wrapped_key = 2; + // The initialization vector used during encryption. + optional bytes iv = 3; + // MAC of (iv || encrypted_data). + optional bytes mac = 4; + optional bytes encrypted_data = 5; + // An identifier for the wrapping key to assist in decryption. + optional bytes wrapping_key_id = 6; +} + +// The wrapper message of any data and its signature. +message SignedData { + // The data to be signed. + optional bytes data = 1; + // The signature of the data field. + optional bytes signature = 2; +} + +// These two fields are suitable for passing to Tspi_TPM_ActivateIdentity() +// directly. +message EncryptedIdentityCredential { + // TPM_ASYM_CA_CONTENTS, encrypted with EK public key. + optional bytes asym_ca_contents = 1; + // TPM_SYM_CA_ATTESTATION, encrypted with the key in aysm_ca_contents. + optional bytes sym_ca_attestation = 2; +} + diff --git a/attestation/common/crypto_utility.h b/attestation/common/crypto_utility.h new file mode 100644 index 0000000..1b9e708 --- /dev/null +++ b/attestation/common/crypto_utility.h @@ -0,0 +1,98 @@ +// +// 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. +// + +#ifndef ATTESTATION_COMMON_CRYPTO_UTILITY_H_ +#define ATTESTATION_COMMON_CRYPTO_UTILITY_H_ + +#include <string> + +#include "attestation/common/common.pb.h" + +namespace attestation { + +// A class which provides helpers for cryptography-related tasks. +class CryptoUtility { + public: + virtual ~CryptoUtility() = default; + + // Generates |num_bytes| of |random_data|. Returns true on success. + virtual bool GetRandom(size_t num_bytes, std::string* random_data) const = 0; + + // Creates a random |aes_key| and seals it to the TPM's PCR0, producing a + // |sealed_key|. Returns true on success. + virtual bool CreateSealedKey(std::string* aes_key, + std::string* sealed_key) = 0; + + // Encrypts the given |data| using the |aes_key|. The |sealed_key| will be + // embedded in the |encrypted_data| to assist with decryption. It can be + // extracted from the |encrypted_data| using UnsealKey(). Returns true on + // success. + virtual bool EncryptData(const std::string& data, + const std::string& aes_key, + const std::string& sealed_key, + std::string* encrypted_data) = 0; + + // Extracts and unseals the |aes_key| from the |sealed_key| embedded in + // the given |encrypted_data|. The |sealed_key| is also provided as an output + // so callers can make subsequent calls to EncryptData() with the same key. + // Returns true on success. + virtual bool UnsealKey(const std::string& encrypted_data, + std::string* aes_key, + std::string* sealed_key) = 0; + + // Decrypts |encrypted_data| using |aes_key|, producing the decrypted |data|. + // Returns true on success. + virtual bool DecryptData(const std::string& encrypted_data, + const std::string& aes_key, + std::string* data) = 0; + + // Convert |public_key| from PKCS #1 RSAPublicKey to X.509 + // SubjectPublicKeyInfo. On success returns true and provides the + // |public_key_info|. + virtual bool GetRSASubjectPublicKeyInfo(const std::string& public_key, + std::string* public_key_info) = 0; + + // Convert |public_key_info| from X.509 SubjectPublicKeyInfo to PKCS #1 + // RSAPublicKey. On success returns true and provides the |public_key|. + virtual bool GetRSAPublicKey(const std::string& public_key_info, + std::string* public_key) = 0; + + // Encrypts a |credential| in a format compatible with TPM attestation key + // activation. The |ek_public_key_info| must be provided in X.509 + // SubjectPublicKeyInfo format and the |aik_public_key| must be provided in + // TPM_PUBKEY format. + virtual bool EncryptIdentityCredential( + const std::string& credential, + const std::string& ek_public_key_info, + const std::string& aik_public_key, + EncryptedIdentityCredential* encrypted) = 0; + + // Encrypts |data| in a format compatible with the TPM unbind operation. The + // |public_key| must be provided in X.509 SubjectPublicKeyInfo format. + 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 + +#endif // ATTESTATION_COMMON_CRYPTO_UTILITY_H_ diff --git a/attestation/common/crypto_utility_impl.cc b/attestation/common/crypto_utility_impl.cc new file mode 100644 index 0000000..c506fcb --- /dev/null +++ b/attestation/common/crypto_utility_impl.cc @@ -0,0 +1,472 @@ +// +// 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/common/crypto_utility_impl.h" + +#include <limits> +#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> +#include <openssl/hmac.h> +#include <openssl/rand.h> +#include <openssl/rsa.h> +#include <openssl/sha.h> +#include <openssl/x509.h> + +namespace { + +const size_t kAesKeySize = 32; +const size_t kAesBlockSize = 16; + +std::string GetOpenSSLError() { + BIO* bio = BIO_new(BIO_s_mem()); + ERR_print_errors(bio); + char* data = nullptr; + int data_len = BIO_get_mem_data(bio, &data); + std::string error_string(data, data_len); + BIO_free(bio); + return error_string; +} + +unsigned char* StringAsOpenSSLBuffer(std::string* s) { + return reinterpret_cast<unsigned char*>(string_as_array(s)); +} + +} // namespace + +namespace attestation { + +CryptoUtilityImpl::CryptoUtilityImpl(TpmUtility* tpm_utility) + : tpm_utility_(tpm_utility) { + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); +} + +CryptoUtilityImpl::~CryptoUtilityImpl() { + EVP_cleanup(); + ERR_free_strings(); +} + +bool CryptoUtilityImpl::GetRandom(size_t num_bytes, + std::string* random_data) const { + // OpenSSL takes a signed integer. + if (num_bytes > static_cast<size_t>(std::numeric_limits<int>::max())) { + return false; + } + random_data->resize(num_bytes); + unsigned char* buffer = StringAsOpenSSLBuffer(random_data); + return (RAND_bytes(buffer, num_bytes) == 1); +} + +bool CryptoUtilityImpl::CreateSealedKey(std::string* aes_key, + std::string* sealed_key) { + if (!GetRandom(kAesKeySize, aes_key)) { + LOG(ERROR) << __func__ << ": GetRandom failed."; + return false; + } + if (!tpm_utility_->SealToPCR0(*aes_key, sealed_key)) { + LOG(ERROR) << __func__ << ": Failed to seal cipher key."; + return false; + } + return true; +} + +bool CryptoUtilityImpl::EncryptData(const std::string& data, + const std::string& aes_key, + const std::string& sealed_key, + std::string* encrypted_data) { + std::string iv; + if (!GetRandom(kAesBlockSize, &iv)) { + LOG(ERROR) << __func__ << ": GetRandom failed."; + return false; + } + std::string raw_encrypted_data; + if (!AesEncrypt(data, aes_key, iv, &raw_encrypted_data)) { + LOG(ERROR) << __func__ << ": AES encryption failed."; + return false; + } + EncryptedData encrypted_pb; + encrypted_pb.set_wrapped_key(sealed_key); + encrypted_pb.set_iv(iv); + encrypted_pb.set_encrypted_data(raw_encrypted_data); + encrypted_pb.set_mac(HmacSha512(iv + raw_encrypted_data, aes_key)); + if (!encrypted_pb.SerializeToString(encrypted_data)) { + LOG(ERROR) << __func__ << ": Failed to serialize protobuf."; + return false; + } + return true; +} + +bool CryptoUtilityImpl::UnsealKey(const std::string& encrypted_data, + std::string* aes_key, + std::string* sealed_key) { + EncryptedData encrypted_pb; + if (!encrypted_pb.ParseFromString(encrypted_data)) { + LOG(ERROR) << __func__ << ": Failed to parse protobuf."; + return false; + } + *sealed_key = encrypted_pb.wrapped_key(); + if (!tpm_utility_->Unseal(*sealed_key, aes_key)) { + LOG(ERROR) << __func__ << ": Cannot unseal aes key."; + return false; + } + return true; +} + +bool CryptoUtilityImpl::DecryptData(const std::string& encrypted_data, + const std::string& aes_key, + std::string* data) { + EncryptedData encrypted_pb; + if (!encrypted_pb.ParseFromString(encrypted_data)) { + LOG(ERROR) << __func__ << ": Failed to parse protobuf."; + return false; + } + std::string mac = HmacSha512( + encrypted_pb.iv() + encrypted_pb.encrypted_data(), + aes_key); + if (mac.length() != encrypted_pb.mac().length()) { + LOG(ERROR) << __func__ << ": Corrupted data in encrypted pb."; + return false; + } + if (!crypto::SecureMemEqual(mac.data(), encrypted_pb.mac().data(), + mac.length())) { + LOG(ERROR) << __func__ << ": Corrupted data in encrypted pb."; + return false; + } + if (!AesDecrypt(encrypted_pb.encrypted_data(), aes_key, encrypted_pb.iv(), + data)) { + LOG(ERROR) << __func__ << ": AES decryption failed."; + return false; + } + return true; +} + +bool CryptoUtilityImpl::GetRSASubjectPublicKeyInfo( + const std::string& public_key, + std::string* public_key_info) { + auto asn1_ptr = reinterpret_cast<const unsigned char*>(public_key.data()); + crypto::ScopedRSA rsa(d2i_RSAPublicKey(nullptr, &asn1_ptr, + public_key.size())); + if (!rsa.get()) { + LOG(ERROR) << __func__ << ": Failed to decode public key: " + << GetOpenSSLError(); + return false; + } + unsigned char* buffer = nullptr; + int length = i2d_RSA_PUBKEY(rsa.get(), &buffer); + if (length <= 0) { + LOG(ERROR) << __func__ << ": Failed to encode public key: " + << GetOpenSSLError(); + return false; + } + crypto::ScopedOpenSSLBytes scoped_buffer(buffer); + public_key_info->assign(reinterpret_cast<char*>(buffer), length); + return true; +} + +bool CryptoUtilityImpl::GetRSAPublicKey(const std::string& public_key_info, + std::string* public_key) { + auto asn1_ptr = reinterpret_cast<const unsigned char*>( + public_key_info.data()); + crypto::ScopedRSA rsa(d2i_RSA_PUBKEY(NULL, &asn1_ptr, + public_key_info.size())); + if (!rsa.get()) { + LOG(ERROR) << __func__ << ": Failed to decode public key: " + << GetOpenSSLError(); + return false; + } + unsigned char* buffer = NULL; + int length = i2d_RSAPublicKey(rsa.get(), &buffer); + if (length <= 0) { + LOG(ERROR) << __func__ << ": Failed to encode public key: " + << GetOpenSSLError(); + return false; + } + crypto::ScopedOpenSSLBytes scoped_buffer(buffer); + public_key->assign(reinterpret_cast<char*>(buffer), length); + return true; +} + +bool CryptoUtilityImpl::EncryptIdentityCredential( + const std::string& credential, + const std::string& ek_public_key_info, + const std::string& aik_public_key, + EncryptedIdentityCredential* encrypted) { + const char kAlgAES256 = 9; // This comes from TPM_ALG_AES256. + const char kEncModeCBC = 2; // This comes from TPM_SYM_MODE_CBC. + const char kAsymContentHeader[] = + {0, 0, 0, kAlgAES256, 0, kEncModeCBC, 0, kAesKeySize}; + const char kSymContentHeader[12] = {}; + + // Generate an AES key and encrypt the credential. + std::string aes_key; + if (!GetRandom(kAesKeySize, &aes_key)) { + LOG(ERROR) << __func__ << ": GetRandom failed."; + return false; + } + std::string encrypted_credential; + if (!TssCompatibleEncrypt(credential, aes_key, &encrypted_credential)) { + LOG(ERROR) << __func__ << ": Failed to encrypt credential."; + return false; + } + + // 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 + + base::SHA1HashString(aik_public_key); + + // Encrypt the TPM_ASYM_CA_CONTENTS with the EK public key. + auto asn1_ptr = reinterpret_cast<const unsigned char*>( + ek_public_key_info.data()); + crypto::ScopedRSA rsa(d2i_RSA_PUBKEY(NULL, &asn1_ptr, + ek_public_key_info.size())); + if (!rsa.get()) { + LOG(ERROR) << __func__ << ": Failed to decode EK public key: " + << GetOpenSSLError(); + return false; + } + std::string encrypted_asym_content; + if (!TpmCompatibleOAEPEncrypt(asym_content, rsa.get(), + &encrypted_asym_content)) { + LOG(ERROR) << __func__ << ": Failed to encrypt with EK public key."; + return false; + } + + // Construct a TPM_SYM_CA_ATTESTATION structure. + uint32_t length = htonl(encrypted_credential.size()); + auto length_bytes = reinterpret_cast<const char*>(&length); + std::string length_blob(length_bytes, sizeof(uint32_t)); + std::string sym_header(std::begin(kSymContentHeader), + std::end(kSymContentHeader)); + std::string sym_content = length_blob + sym_header + encrypted_credential; + + encrypted->set_asym_ca_contents(encrypted_asym_content); + encrypted->set_sym_ca_attestation(sym_content); + return true; +} + +bool CryptoUtilityImpl::EncryptForUnbind(const std::string& public_key, + const std::string& data, + std::string* encrypted_data) { + // Construct a TPM_BOUND_DATA structure. + const char kBoundDataHeader[] = {1, 1, 0, 0, 2 /* TPM_PT_BIND */}; + std::string header(std::begin(kBoundDataHeader), std::end(kBoundDataHeader)); + std::string bound_data = header + data; + + // Encrypt using the TPM_ES_RSAESOAEP_SHA1_MGF1 scheme. + 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; + } + if (!TpmCompatibleOAEPEncrypt(bound_data, rsa.get(), encrypted_data)) { + LOG(ERROR) << __func__ << ": Failed to encrypt with public key."; + return false; + } + 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, + std::string* encrypted_data) { + if (key.size() != kAesKeySize || iv.size() != kAesBlockSize) { + return false; + } + if (data.size() > static_cast<size_t>(std::numeric_limits<int>::max())) { + // EVP_EncryptUpdate takes a signed int. + return false; + } + std::string mutable_data(data); + unsigned char* input_buffer = StringAsOpenSSLBuffer(&mutable_data); + std::string mutable_key(key); + unsigned char* key_buffer = StringAsOpenSSLBuffer(&mutable_key); + std::string mutable_iv(iv); + unsigned char* iv_buffer = StringAsOpenSSLBuffer(&mutable_iv); + // Allocate enough space for the output (including padding). + encrypted_data->resize(data.size() + kAesBlockSize); + auto output_buffer = reinterpret_cast<unsigned char*>( + string_as_array(encrypted_data)); + int output_size = 0; + const EVP_CIPHER* cipher = EVP_aes_256_cbc(); + EVP_CIPHER_CTX encryption_context; + EVP_CIPHER_CTX_init(&encryption_context); + if (!EVP_EncryptInit_ex(&encryption_context, cipher, nullptr, key_buffer, + iv_buffer)) { + LOG(ERROR) << __func__ << ": " << GetOpenSSLError(); + return false; + } + if (!EVP_EncryptUpdate(&encryption_context, output_buffer, &output_size, + input_buffer, data.size())) { + LOG(ERROR) << __func__ << ": " << GetOpenSSLError(); + EVP_CIPHER_CTX_cleanup(&encryption_context); + return false; + } + size_t total_size = output_size; + output_buffer += output_size; + output_size = 0; + if (!EVP_EncryptFinal_ex(&encryption_context, output_buffer, &output_size)) { + LOG(ERROR) << __func__ << ": " << GetOpenSSLError(); + EVP_CIPHER_CTX_cleanup(&encryption_context); + return false; + } + total_size += output_size; + encrypted_data->resize(total_size); + EVP_CIPHER_CTX_cleanup(&encryption_context); + return true; +} + +bool CryptoUtilityImpl::AesDecrypt(const std::string& encrypted_data, + const std::string& key, + const std::string& iv, + std::string* data) { + if (key.size() != kAesKeySize || iv.size() != kAesBlockSize) { + return false; + } + if (encrypted_data.size() > + static_cast<size_t>(std::numeric_limits<int>::max())) { + // EVP_DecryptUpdate takes a signed int. + return false; + } + std::string mutable_encrypted_data(encrypted_data); + unsigned char* input_buffer = StringAsOpenSSLBuffer(&mutable_encrypted_data); + std::string mutable_key(key); + unsigned char* key_buffer = StringAsOpenSSLBuffer(&mutable_key); + std::string mutable_iv(iv); + unsigned char* iv_buffer = StringAsOpenSSLBuffer(&mutable_iv); + // Allocate enough space for the output. + data->resize(encrypted_data.size()); + unsigned char* output_buffer = StringAsOpenSSLBuffer(data); + int output_size = 0; + const EVP_CIPHER* cipher = EVP_aes_256_cbc(); + EVP_CIPHER_CTX decryption_context; + EVP_CIPHER_CTX_init(&decryption_context); + if (!EVP_DecryptInit_ex(&decryption_context, cipher, nullptr, key_buffer, + iv_buffer)) { + LOG(ERROR) << __func__ << ": " << GetOpenSSLError(); + return false; + } + if (!EVP_DecryptUpdate(&decryption_context, output_buffer, &output_size, + input_buffer, encrypted_data.size())) { + LOG(ERROR) << __func__ << ": " << GetOpenSSLError(); + EVP_CIPHER_CTX_cleanup(&decryption_context); + return false; + } + size_t total_size = output_size; + output_buffer += output_size; + output_size = 0; + if (!EVP_DecryptFinal_ex(&decryption_context, output_buffer, &output_size)) { + LOG(ERROR) << __func__ << ": " << GetOpenSSLError(); + EVP_CIPHER_CTX_cleanup(&decryption_context); + return false; + } + total_size += output_size; + data->resize(total_size); + EVP_CIPHER_CTX_cleanup(&decryption_context); + return true; +} + +std::string CryptoUtilityImpl::HmacSha512(const std::string& data, + const std::string& key) { + unsigned char mac[SHA512_DIGEST_LENGTH]; + std::string mutable_data(data); + unsigned char* data_buffer = StringAsOpenSSLBuffer(&mutable_data); + HMAC(EVP_sha512(), key.data(), key.size(), data_buffer, data.size(), mac, + nullptr); + return std::string(std::begin(mac), std::end(mac)); +} + +bool CryptoUtilityImpl::TssCompatibleEncrypt(const std::string& input, + const std::string& key, + std::string* output) { + CHECK(output); + CHECK_EQ(key.size(), kAesKeySize); + std::string iv; + if (!GetRandom(kAesBlockSize, &iv)) { + LOG(ERROR) << __func__ << ": GetRandom failed."; + return false; + } + std::string encrypted; + if (!AesEncrypt(input, key, iv, &encrypted)) { + LOG(ERROR) << __func__ << ": Encryption failed."; + return false; + } + *output = iv + encrypted; + return true; +} + +bool CryptoUtilityImpl::TpmCompatibleOAEPEncrypt(const std::string& input, + RSA* key, + std::string* output) { + CHECK(output); + // The custom OAEP parameter as specified in TPM Main Part 1, Section 31.1.1. + const unsigned char oaep_param[4] = {'T', 'C', 'P', 'A'}; + std::string padded_input; + padded_input.resize(RSA_size(key)); + auto padded_buffer = reinterpret_cast<unsigned char*>( + string_as_array(&padded_input)); + 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)); + if (!result) { + LOG(ERROR) << __func__ << ": Failed to add OAEP padding: " + << GetOpenSSLError(); + return false; + } + output->resize(padded_input.size()); + auto output_buffer = reinterpret_cast<unsigned char*>( + string_as_array(output)); + result = RSA_public_encrypt(padded_input.size(), padded_buffer, + output_buffer, key, RSA_NO_PADDING); + if (result == -1) { + LOG(ERROR) << __func__ << ": Failed to encrypt OAEP padded input: " + << GetOpenSSLError(); + return false; + } + return true; +} + +} // namespace attestation diff --git a/attestation/common/crypto_utility_impl.h b/attestation/common/crypto_utility_impl.h new file mode 100644 index 0000000..0c88df7 --- /dev/null +++ b/attestation/common/crypto_utility_impl.h @@ -0,0 +1,100 @@ +// +// 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. +// + +#ifndef ATTESTATION_COMMON_CRYPTO_UTILITY_IMPL_H_ +#define ATTESTATION_COMMON_CRYPTO_UTILITY_IMPL_H_ + +#include "attestation/common/crypto_utility.h" + +#include <string> + +#include <openssl/rsa.h> + +#include "attestation/common/tpm_utility.h" + +namespace attestation { + +// An implementation of CryptoUtility. +class CryptoUtilityImpl : public CryptoUtility { + public: + // Does not take ownership of pointers. + explicit CryptoUtilityImpl(TpmUtility* tpm_utility); + ~CryptoUtilityImpl() override; + + // CryptoUtility methods. + bool GetRandom(size_t num_bytes, std::string* random_data) const override; + bool CreateSealedKey(std::string* aes_key, std::string* sealed_key) override; + bool EncryptData(const std::string& data, + const std::string& aes_key, + const std::string& sealed_key, + std::string* encrypted_data) override; + bool UnsealKey(const std::string& encrypted_data, + std::string* aes_key, + std::string* sealed_key) override; + bool DecryptData(const std::string& encrypted_data, + const std::string& aes_key, + std::string* data) override; + bool GetRSASubjectPublicKeyInfo(const std::string& public_key, + std::string* spki) override; + bool GetRSAPublicKey(const std::string& public_key_info, + std::string* public_key) override; + bool EncryptIdentityCredential( + const std::string& credential, + const std::string& ek_public_key_info, + const std::string& aik_public_key, + EncryptedIdentityCredential* encrypted) override; + 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 + // padding and produces the |encrypted_data|. Returns true on success. + bool AesEncrypt(const std::string& data, + const std::string& key, + const std::string& iv, + std::string* encrypted_data); + + // Decrypts |encrypted_data| using |key| and |iv| for AES in CBC mode with + // PKCS #5 padding and produces the decrypted |data|. Returns true on success. + bool AesDecrypt(const std::string& encrypted_data, + const std::string& key, + const std::string& iv, + std::string* data); + + // Computes and returns an HMAC of |data| using |key| and SHA-512. + std::string HmacSha512(const std::string& data, const std::string& key); + + // Encrypt like trousers does. This is like AesEncrypt but a random IV is + // included in the output. + bool TssCompatibleEncrypt(const std::string& input, + const std::string& key, + std::string* output); + + // Encrypts using RSA-OAEP and the TPM-specific OAEP parameter. + bool TpmCompatibleOAEPEncrypt(const std::string& input, + RSA* key, + std::string* output); + + TpmUtility* tpm_utility_; +}; + +} // namespace attestation + +#endif // ATTESTATION_COMMON_CRYPTO_UTILITY_IMPL_H_ diff --git a/attestation/common/crypto_utility_impl_test.cc b/attestation/common/crypto_utility_impl_test.cc new file mode 100644 index 0000000..9d37583 --- /dev/null +++ b/attestation/common/crypto_utility_impl_test.cc @@ -0,0 +1,241 @@ +// +// 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 <memory> +#include <string> +#include <vector> + +#include <base/logging.h> +#include <base/strings/string_number_conversions.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "attestation/common/crypto_utility_impl.h" +#include "attestation/common/mock_tpm_utility.h" + +using testing::_; +using testing::NiceMock; +using testing::Return; + +namespace { + +const char kValidPublicKeyHex[] = + "3082010A0282010100" + "961037BC12D2A298BEBF06B2D5F8C9B64B832A2237F8CF27D5F96407A6041A4D" + "AD383CB5F88E625F412E8ACD5E9D69DF0F4FA81FCE7955829A38366CBBA5A2B1" + "CE3B48C14B59E9F094B51F0A39155874C8DE18A0C299EBF7A88114F806BE4F25" + "3C29A509B10E4B19E31675AFE3B2DA77077D94F43D8CE61C205781ED04D183B4" + "C349F61B1956C64B5398A3A98FAFF17D1B3D9120C832763EDFC8F4137F6EFBEF" + "46D8F6DE03BD00E49DEF987C10BDD5B6F8758B6A855C23C982DDA14D8F0F2B74" + "E6DEFA7EEE5A6FC717EB0FF103CB8049F693A2C8A5039EF1F5C025DC44BD8435" + "E8D8375DADE00E0C0F5C196E04B8483CC98B1D5B03DCD7E0048B2AB343FFC11F" + "0203" + "010001"; + +std::string HexDecode(const std::string hex) { + std::vector<uint8_t> output; + CHECK(base::HexStringToBytes(hex, &output)); + return std::string(reinterpret_cast<char*>(output.data()), output.size()); +} + +} // namespace + +namespace attestation { + +class CryptoUtilityImplTest : public testing::Test { + public: + ~CryptoUtilityImplTest() override = default; + void SetUp() override { + crypto_utility_.reset(new CryptoUtilityImpl(&mock_tpm_utility_)); + } + + protected: + NiceMock<MockTpmUtility> mock_tpm_utility_; + std::unique_ptr<CryptoUtilityImpl> crypto_utility_; +}; + +TEST_F(CryptoUtilityImplTest, GetRandomSuccess) { + std::string random1; + EXPECT_TRUE(crypto_utility_->GetRandom(20, &random1)); + std::string random2; + EXPECT_TRUE(crypto_utility_->GetRandom(20, &random2)); + EXPECT_NE(random1, random2); +} + +TEST_F(CryptoUtilityImplTest, GetRandomIntOverflow) { + size_t num_bytes = -1; + std::string buffer; + EXPECT_FALSE(crypto_utility_->GetRandom(num_bytes, &buffer)); +} + +TEST_F(CryptoUtilityImplTest, PairwiseSealedEncryption) { + std::string key; + std::string sealed_key; + EXPECT_TRUE(crypto_utility_->CreateSealedKey(&key, &sealed_key)); + std::string data("test"); + std::string encrypted_data; + EXPECT_TRUE(crypto_utility_->EncryptData(data, key, sealed_key, + &encrypted_data)); + key.clear(); + sealed_key.clear(); + data.clear(); + EXPECT_TRUE(crypto_utility_->UnsealKey(encrypted_data, &key, &sealed_key)); + EXPECT_TRUE(crypto_utility_->DecryptData(encrypted_data, key, &data)); + EXPECT_EQ("test", data); +} + +TEST_F(CryptoUtilityImplTest, SealFailure) { + EXPECT_CALL(mock_tpm_utility_, SealToPCR0(_, _)) + .WillRepeatedly(Return(false)); + std::string key; + std::string sealed_key; + EXPECT_FALSE(crypto_utility_->CreateSealedKey(&key, &sealed_key)); +} + +TEST_F(CryptoUtilityImplTest, EncryptNoData) { + std::string key(32, 0); + std::string output; + EXPECT_TRUE(crypto_utility_->EncryptData(std::string(), key, key, &output)); +} + +TEST_F(CryptoUtilityImplTest, EncryptInvalidKey) { + std::string key(12, 0); + std::string output; + EXPECT_FALSE(crypto_utility_->EncryptData(std::string(), key, key, &output)); +} + +TEST_F(CryptoUtilityImplTest, UnsealInvalidData) { + std::string output; + EXPECT_FALSE(crypto_utility_->UnsealKey("invalid", &output, &output)); +} + +TEST_F(CryptoUtilityImplTest, UnsealError) { + EXPECT_CALL(mock_tpm_utility_, Unseal(_, _)) + .WillRepeatedly(Return(false)); + std::string key(32, 0); + std::string data; + EXPECT_TRUE(crypto_utility_->EncryptData("data", key, key, &data)); + std::string output; + EXPECT_FALSE(crypto_utility_->UnsealKey(data, &output, &output)); +} + +TEST_F(CryptoUtilityImplTest, DecryptInvalidKey) { + std::string key(12, 0); + std::string output; + EXPECT_FALSE(crypto_utility_->DecryptData(std::string(), key, &output)); +} + +TEST_F(CryptoUtilityImplTest, DecryptInvalidData) { + std::string key(32, 0); + std::string output; + EXPECT_FALSE(crypto_utility_->DecryptData("invalid", key, &output)); +} + +TEST_F(CryptoUtilityImplTest, DecryptInvalidData2) { + std::string key(32, 0); + std::string output; + EncryptedData proto; + std::string input; + proto.SerializeToString(&input); + EXPECT_FALSE(crypto_utility_->DecryptData(input, key, &output)); +} + +TEST_F(CryptoUtilityImplTest, GetRSASubjectPublicKeyInfo) { + std::string public_key = HexDecode(kValidPublicKeyHex); + std::string output; + EXPECT_TRUE(crypto_utility_->GetRSASubjectPublicKeyInfo(public_key, &output)); +} + +TEST_F(CryptoUtilityImplTest, GetRSASubjectPublicKeyInfoBadInput) { + std::string public_key = "bad_public_key"; + std::string output; + EXPECT_FALSE(crypto_utility_->GetRSASubjectPublicKeyInfo(public_key, + &output)); +} + +TEST_F(CryptoUtilityImplTest, GetRSASubjectPublicKeyInfoPairWise) { + std::string public_key = HexDecode(kValidPublicKeyHex); + std::string output; + EXPECT_TRUE(crypto_utility_->GetRSASubjectPublicKeyInfo(public_key, &output)); + std::string public_key2; + EXPECT_TRUE(crypto_utility_->GetRSAPublicKey(output, &public_key2)); + EXPECT_EQ(public_key, public_key2); +} + +TEST_F(CryptoUtilityImplTest, EncryptIdentityCredential) { + std::string public_key = HexDecode(kValidPublicKeyHex); + std::string public_key_info; + EXPECT_TRUE(crypto_utility_->GetRSASubjectPublicKeyInfo(public_key, + &public_key_info)); + EncryptedIdentityCredential output; + EXPECT_TRUE(crypto_utility_->EncryptIdentityCredential("credential", + public_key_info, + "aik", + &output)); + EXPECT_TRUE(output.has_asym_ca_contents()); + EXPECT_TRUE(output.has_sym_ca_attestation()); +} + +TEST_F(CryptoUtilityImplTest, EncryptIdentityCredentialBadEK) { + EncryptedIdentityCredential output; + EXPECT_FALSE(crypto_utility_->EncryptIdentityCredential("credential", + "bad_ek", + "aik", + &output)); +} + +TEST_F(CryptoUtilityImplTest, EncryptForUnbind) { + 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_TRUE(crypto_utility_->EncryptForUnbind(public_key_info, "input", + &output)); + EXPECT_FALSE(output.empty()); +} + +TEST_F(CryptoUtilityImplTest, EncryptForUnbindBadKey) { + std::string output; + EXPECT_FALSE(crypto_utility_->EncryptForUnbind("bad_key", "input", &output)); +} + +TEST_F(CryptoUtilityImplTest, EncryptForUnbindLargeInput) { + std::string public_key = HexDecode(kValidPublicKeyHex); + std::string public_key_info; + EXPECT_TRUE(crypto_utility_->GetRSASubjectPublicKeyInfo(public_key, + &public_key_info)); + std::string input(1000, 'A'); + std::string output; + EXPECT_FALSE(crypto_utility_->EncryptForUnbind(public_key_info, input, + &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/attestation/common/database.proto b/attestation/common/database.proto new file mode 100644 index 0000000..74d0f99 --- /dev/null +++ b/attestation/common/database.proto @@ -0,0 +1,113 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +option optimize_for = LITE_RUNTIME; + +import "common.proto"; + +package attestation; + +// Holds TPM credentials that the attestation server will need to see. These +// credentials must be cleared once the attestation server has certified the +// AIK. +message TPMCredentials { + optional bytes endorsement_public_key = 1; + optional bytes endorsement_credential = 2; + optional bytes platform_credential = 3; + optional bytes conformance_credential = 4; + // The |endorsement_credential| encrypted with a public key associated with + // the default Chrome OS Privacy CA. + optional EncryptedData default_encrypted_endorsement_credential = 5; + optional EncryptedData alternate_encrypted_endorsement_credential = 6; +} + +// Holds information relevant to a particular AIK. +message IdentityKey { + // The DER encoded public key. + optional bytes identity_public_key = 1; + // The TPM-specific key blob that can be loaded back into the TPM. + optional bytes identity_key_blob = 2; + // A credential issued by the attestation server. + optional bytes identity_credential = 3; +} + +// Holds information required to verify the binding of an AIK to an EK. This +// information should be cleared once the attestation server has certified the +// AIK. +message IdentityBinding { + // The binding data, as output by the TPM_MakeIdentity operation. + optional bytes identity_binding = 1; + // The AIK public key, DER encoded. + optional bytes identity_public_key_der = 2; + // The AIK public key, in TPM_PUBKEY form. + optional bytes identity_public_key = 3; + // The label used during AIK creation. + optional bytes identity_label = 4; + // The PCA public key used during AIK creation, in TPM_PUBKEY form. + optional bytes pca_public_key = 5; +} + +// Holds owner delegation information. +message Delegation { + // The delegate owner blob. + optional bytes blob = 1; + // The authorization secret. + optional bytes secret = 2; + // Whether this delegate has permissions to call TPM_ResetLockValue. + optional bool has_reset_lock_permissions = 3; +} + +// Holds information about a certified key. +message CertifiedKey { + // The TPM-wrapped key blob. + optional bytes key_blob = 1; + // The public key in ASN.1 DER form. + optional bytes public_key = 2; + // The credential of the certified key in X.509 format. + optional bytes certified_key_credential = 3; + // The issuer intermediate CA certificate in X.509 format. + optional bytes intermediate_ca_cert = 4; + // A key name. This is not necessarily a unique identifier. + optional bytes key_name = 5; + // An arbitrary payload associated with the key. + optional bytes payload = 6; + // Addtional intermediate CA certificates that helps chaining up to the root + // CA. See |AttestationCertificateResponse.additional_intermediate_ca_cert| + // for more detail. + repeated bytes additional_intermediate_ca_cert = 7; + // The public key in TPM_PUBKEY form. + optional bytes public_key_tpm_format = 8; + // The serialized TPM_CERTIFY_INFO for the certified key. + optional bytes certified_key_info = 9; + // The signature of the TPM_CERTIFY_INFO by the AIK. + optional bytes certified_key_proof = 10; + // The original key type specified when the key was created. + optional KeyType key_type = 11; + // The original key usage specified when the key was created. + optional KeyUsage key_usage = 12; +} + +// Holds all information that a client stores locally. +message AttestationDatabase { + optional TPMCredentials credentials = 2; + optional IdentityBinding identity_binding = 3; + optional IdentityKey identity_key = 4; + optional Quote pcr0_quote = 5; + optional Quote pcr1_quote = 12; + optional Delegation delegate = 6; + repeated CertifiedKey device_keys = 7; + + message TemporalIndexRecord { + optional bytes user_hash = 1; + optional bytes origin_hash = 2; + optional int32 temporal_index = 3; + } + repeated TemporalIndexRecord temporal_index_record = 8; + + optional IdentityBinding alternate_identity_binding = 9; + optional IdentityKey alternate_identity_key = 10; + optional Quote alternate_pcr0_quote = 11; + optional Quote alternate_pcr1_quote = 13; +} + diff --git a/attestation/common/dbus_interface.h b/attestation/common/dbus_interface.h new file mode 100644 index 0000000..06132b5 --- /dev/null +++ b/attestation/common/dbus_interface.h @@ -0,0 +1,40 @@ +// +// 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. +// + +#ifndef ATTESTATION_COMMON_DBUS_INTERFACE_H_ +#define ATTESTATION_COMMON_DBUS_INTERFACE_H_ + +namespace attestation { + +// TODO(namnguyen): Move to brillo/system_api once we're ready. +constexpr char kAttestationInterface[] = "org.chromium.Attestation"; +constexpr char kAttestationServicePath[] = "/org/chromium/Attestation"; +constexpr char kAttestationServiceName[] = "org.chromium.Attestation"; + +// Methods exported by attestation. +constexpr char kCreateGoogleAttestedKey[] = "CreateGoogleAttestedKey"; +constexpr char kGetKeyInfo[] = "GetKeyInfo"; +constexpr char kGetEndorsementInfo[] = "GetEndorsementInfo"; +constexpr char kGetAttestationKeyInfo[] = "GetAttestationKeyInfo"; +constexpr char kActivateAttestationKey[] = "ActivateAttestationKey"; +constexpr char kCreateCertifiableKey[] = "CreateCertifiableKey"; +constexpr char kDecrypt[] = "Decrypt"; +constexpr char kSign[] = "Sign"; +constexpr char kRegisterKeyWithChapsToken[] = "RegisterKeyWithChapsToken"; + +} // namespace attestation + +#endif // ATTESTATION_COMMON_DBUS_INTERFACE_H_ diff --git a/attestation/common/interface.proto b/attestation/common/interface.proto new file mode 100644 index 0000000..b8198bb --- /dev/null +++ b/attestation/common/interface.proto @@ -0,0 +1,173 @@ +// +// 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. +// + +option optimize_for = LITE_RUNTIME; + +import "common.proto"; + +package attestation; + +enum AttestationStatus { + STATUS_SUCCESS = 0; + STATUS_UNEXPECTED_DEVICE_ERROR = 1; + STATUS_NOT_AVAILABLE = 2; + STATUS_NOT_READY = 3; + STATUS_NOT_ALLOWED = 4; + STATUS_INVALID_PARAMETER = 5; + STATUS_REQUEST_DENIED_BY_CA = 6; + STATUS_CA_NOT_AVAILABLE = 7; +} + +message CreateGoogleAttestedKeyRequest { + // An arbitrary label which can be used to reference the key later. + optional string key_label = 1; + optional KeyType key_type = 2; + optional KeyUsage key_usage = 3; + // Describes the certificate to be requested of the CA. + optional CertificateProfile certificate_profile = 4; + // Provided if the new key should be accessible only by a particular user. If + // this field is not set or is the empty string, the key will be accessible + // system-wide. + optional string username = 5; + // If the |certificate_profile| is intended to be bound to a particular origin + // this field specifies the origin. For most profiles this is not required. + optional string origin = 6; +} + +message CreateGoogleAttestedKeyReply { + optional AttestationStatus status = 1; + // More information about a server-side error. This only exists + // if status=REQUEST_DENIED_BY_CA. + optional string server_error = 2; + // A PEM-encoded list of X.509 certificates starting with the requested + // certificate issued by the CA and followed by certificates for any + // intermediate authorities, in order. The Google Attestation CA root + // certificate is well-known and not included. + optional string certificate_chain = 3; +} + +message GetKeyInfoRequest { + optional string key_label = 1; + optional string username = 2; +} + +message GetKeyInfoReply { + optional AttestationStatus status = 1; + optional KeyType key_type = 2; + optional KeyUsage key_usage = 3; + // The public key (X.509/DER SubjectPublicKeyInfo). + optional bytes public_key = 4; + // The serialized TPM_CERTIFY_INFO or TPM2B_ATTEST for the new key. + optional bytes certify_info = 5; + // The signature of certify_info by the Attestation Key. + optional bytes certify_info_signature = 6; + // The certificate data associated with the key (if any). + optional bytes certificate = 7; +} + +message GetEndorsementInfoRequest { + optional KeyType key_type = 1; +} + +message GetEndorsementInfoReply { + optional AttestationStatus status = 1; + // The endorsement public key (X.509/DER SubjectPublicKeyInfo). + optional bytes ek_public_key = 2; + // The endorsement certificate (X.509/DER). + optional bytes ek_certificate = 3; +} + +message GetAttestationKeyInfoRequest { + optional KeyType key_type = 1; +} + +message GetAttestationKeyInfoReply { + optional AttestationStatus status = 1; + // The attestation public key (X.509/DER SubjectPublicKeyInfo). + optional bytes public_key = 2; + // The attestation public key in TPM_PUBKEY form. + optional bytes public_key_tpm_format = 3; + // The attestation key certificate. + optional bytes certificate = 4; + // A quote of PCR0 at the time of attestation key creation. + optional Quote pcr0_quote = 5; + // A quote of PCR1 at the time of attestation key creation. + optional Quote pcr1_quote = 6; +} + +message ActivateAttestationKeyRequest { + optional KeyType key_type = 1; + optional EncryptedIdentityCredential encrypted_certificate = 2; + optional bool save_certificate = 3; +} + +message ActivateAttestationKeyReply { + optional AttestationStatus status = 1; + // The decrypted attestation key certificate. + optional bytes certificate = 2; +} + +message CreateCertifiableKeyRequest { + // An arbitrary label which can be used to reference the key later. + optional string key_label = 1; + // Provided if the new key should be accessible only by a + // particular user. If this field is not set or is the empty + // string, the key will be accessible system-wide. + optional string username = 2; + optional KeyType key_type = 3; + optional KeyUsage key_usage = 4; +} + +message CreateCertifiableKeyReply { + optional AttestationStatus status = 1; + // The new public key (X.509/DER SubjectPublicKeyInfo). + optional bytes public_key = 2; + // The serialized TPM_CERTIFY_INFO or TPM2B_ATTEST for the new key. + optional bytes certify_info = 3; + // The signature of certify_info by the Attestation Key. + optional bytes certify_info_signature = 4; +} + +message DecryptRequest { + optional string key_label = 1; + optional string username = 2; + optional bytes encrypted_data = 3; +} + +message DecryptReply { + 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; +} + +message RegisterKeyWithChapsTokenRequest { + optional string key_label = 1; + optional string username = 2; +} + +message RegisterKeyWithChapsTokenReply { + optional AttestationStatus status = 1; +} diff --git a/attestation/common/mock_attestation_interface.h b/attestation/common/mock_attestation_interface.h new file mode 100644 index 0000000..cff4809 --- /dev/null +++ b/attestation/common/mock_attestation_interface.h @@ -0,0 +1,59 @@ +// +// 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. +// + +#ifndef ATTESTATION_COMMON_MOCK_ATTESTATION_INTERFACE_H_ +#define ATTESTATION_COMMON_MOCK_ATTESTATION_INTERFACE_H_ + +#include <string> + +#include <gmock/gmock.h> + +#include "attestation/common/attestation_interface.h" + +namespace attestation { + +class MockAttestationInterface : public AttestationInterface { + public: + MockAttestationInterface() = default; + virtual ~MockAttestationInterface() = default; + + MOCK_METHOD0(Initialize, bool()); + MOCK_METHOD2(CreateGoogleAttestedKey, void( + const CreateGoogleAttestedKeyRequest&, + const CreateGoogleAttestedKeyCallback&)); + MOCK_METHOD2(GetKeyInfo, void(const GetKeyInfoRequest&, + const GetKeyInfoCallback&)); + MOCK_METHOD2(GetEndorsementInfo, void(const GetEndorsementInfoRequest&, + const GetEndorsementInfoCallback&)); + MOCK_METHOD2(GetAttestationKeyInfo, + void(const GetAttestationKeyInfoRequest&, + const GetAttestationKeyInfoCallback&)); + MOCK_METHOD2(ActivateAttestationKey, + void(const ActivateAttestationKeyRequest&, + const ActivateAttestationKeyCallback&)); + MOCK_METHOD2(CreateCertifiableKey, void(const CreateCertifiableKeyRequest&, + const CreateCertifiableKeyCallback&)); + MOCK_METHOD2(Decrypt, void(const DecryptRequest&, const DecryptCallback&)); + MOCK_METHOD2(Sign, void(const SignRequest&, const SignCallback&)); + MOCK_METHOD2(RegisterKeyWithChapsToken, + void(const RegisterKeyWithChapsTokenRequest&, + const RegisterKeyWithChapsTokenCallback&)); +}; + +} // namespace attestation + +#endif // ATTESTATION_COMMON_MOCK_ATTESTATION_INTERFACE_H_ + diff --git a/attestation/common/mock_crypto_utility.cc b/attestation/common/mock_crypto_utility.cc new file mode 100644 index 0000000..fc251e0 --- /dev/null +++ b/attestation/common/mock_crypto_utility.cc @@ -0,0 +1,54 @@ +// +// 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/common/mock_crypto_utility.h" + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::WithArgs; + +namespace { + +bool FakeRandom(size_t num_bytes, std::string* output) { + *output = std::string(num_bytes, 'A'); + return true; +} + +bool CopyString(const std::string& s1, std::string* s2) { + *s2 = s1; + return true; +} + +} // namespace + +namespace attestation { + +MockCryptoUtility::MockCryptoUtility() { + ON_CALL(*this, GetRandom(_, _)).WillByDefault(Invoke(FakeRandom)); + ON_CALL(*this, CreateSealedKey(_, _)).WillByDefault(Return(true)); + ON_CALL(*this, UnsealKey(_, _, _)).WillByDefault(Return(true)); + ON_CALL(*this, EncryptData(_, _, _, _)) + .WillByDefault(WithArgs<0, 3>(Invoke(CopyString))); + ON_CALL(*this, DecryptData(_, _, _)) + .WillByDefault(WithArgs<0, 2>(Invoke(CopyString))); + ON_CALL(*this, GetRSASubjectPublicKeyInfo(_, _)) + .WillByDefault(Invoke(CopyString)); +} + +MockCryptoUtility::~MockCryptoUtility() {} + +} // namespace attestation diff --git a/attestation/common/mock_crypto_utility.h b/attestation/common/mock_crypto_utility.h new file mode 100644 index 0000000..4327d55 --- /dev/null +++ b/attestation/common/mock_crypto_utility.h @@ -0,0 +1,67 @@ +// +// 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. +// + +#ifndef ATTESTATION_COMMON_MOCK_CRYPTO_UTILITY_H_ +#define ATTESTATION_COMMON_MOCK_CRYPTO_UTILITY_H_ + +#include "attestation/common/crypto_utility.h" + +#include <string> + +#include <gmock/gmock.h> + +namespace attestation { + +class MockCryptoUtility : public CryptoUtility { + public: + MockCryptoUtility(); + ~MockCryptoUtility() override; + + MOCK_CONST_METHOD2(GetRandom, bool(size_t, std::string*)); + + MOCK_METHOD2(CreateSealedKey, bool(std::string* aes_key, + std::string* sealed_key)); + + MOCK_METHOD4(EncryptData, bool(const std::string& data, + const std::string& aes_key, + const std::string& sealed_key, + std::string* encrypted_data)); + + MOCK_METHOD3(UnsealKey, bool(const std::string& encrypted_data, + std::string* aes_key, + std::string* sealed_key)); + + MOCK_METHOD3(DecryptData, bool(const std::string& encrypted_data, + const std::string& aes_key, + std::string* data)); + MOCK_METHOD2(GetRSASubjectPublicKeyInfo, bool(const std::string&, + std::string*)); + MOCK_METHOD2(GetRSAPublicKey, bool(const std::string&, std::string*)); + MOCK_METHOD4(EncryptIdentityCredential, bool(const std::string&, + const std::string&, + const std::string&, + EncryptedIdentityCredential*)); + 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 + +#endif // ATTESTATION_COMMON_MOCK_CRYPTO_UTILITY_H_ diff --git a/attestation/common/mock_tpm_utility.cc b/attestation/common/mock_tpm_utility.cc new file mode 100644 index 0000000..0dc4eee --- /dev/null +++ b/attestation/common/mock_tpm_utility.cc @@ -0,0 +1,83 @@ +// +// 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/common/mock_tpm_utility.h" + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::WithArgs; + +namespace { + +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 + +namespace attestation { + +MockTpmUtility::MockTpmUtility() { + ON_CALL(*this, IsTpmReady()).WillByDefault(Return(true)); + ON_CALL(*this, ActivateIdentity(_, _, _, _, _, _)) + .WillByDefault(Return(true)); + ON_CALL(*this, CreateCertifiedKey(_, _, _, _, _, _, _, _, _)) + .WillByDefault(Return(true)); + ON_CALL(*this, SealToPCR0(_, _)) + .WillByDefault(Invoke(TransformString("SealToPCR0"))); + ON_CALL(*this, Unseal(_, _)) + .WillByDefault(Invoke(UntransformString("SealToPCR0"))); + ON_CALL(*this, Unbind(_, _, _)) + .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/attestation/common/mock_tpm_utility.h b/attestation/common/mock_tpm_utility.h new file mode 100644 index 0000000..46ccfb9 --- /dev/null +++ b/attestation/common/mock_tpm_utility.h @@ -0,0 +1,66 @@ +// +// 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. +// + +#ifndef ATTESTATION_COMMON_MOCK_TPM_UTILITY_H_ +#define ATTESTATION_COMMON_MOCK_TPM_UTILITY_H_ + +#include "attestation/common/tpm_utility.h" + +#include <string> + +#include <gmock/gmock.h> + +namespace attestation { + +class MockTpmUtility : public TpmUtility { + 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&, + const std::string&, + const std::string&, + const std::string&, + const std::string&, + std::string*)); + MOCK_METHOD9(CreateCertifiedKey, bool(KeyType, + KeyUsage, + const std::string&, + const std::string&, + std::string*, + std::string*, + std::string*, + std::string*, + std::string*)); + MOCK_METHOD2(SealToPCR0, bool(const std::string&, std::string*)); + MOCK_METHOD2(Unseal, bool(const std::string&, std::string*)); + 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 + +#endif // ATTESTATION_COMMON_MOCK_TPM_UTILITY_H_ diff --git a/attestation/common/print_common_proto.cc b/attestation/common/print_common_proto.cc new file mode 100644 index 0000000..077157e --- /dev/null +++ b/attestation/common/print_common_proto.cc @@ -0,0 +1,233 @@ +// +// 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. +// + +// THIS CODE IS GENERATED. + +#include "attestation/common/print_common_proto.h" + +#include <string> + +#include <base/strings/string_number_conversions.h> +#include <base/strings/stringprintf.h> + +namespace attestation { + +std::string GetProtoDebugString(KeyType value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent(KeyType value, int indent_size) { + if (value == KEY_TYPE_RSA) { + return "KEY_TYPE_RSA"; + } + if (value == KEY_TYPE_ECC) { + return "KEY_TYPE_ECC"; + } + return "<unknown>"; +} + +std::string GetProtoDebugString(KeyUsage value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent(KeyUsage value, int indent_size) { + if (value == KEY_USAGE_SIGN) { + return "KEY_USAGE_SIGN"; + } + if (value == KEY_USAGE_DECRYPT) { + return "KEY_USAGE_DECRYPT"; + } + return "<unknown>"; +} + +std::string GetProtoDebugString(CertificateProfile value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent(CertificateProfile value, + int indent_size) { + if (value == ENTERPRISE_MACHINE_CERTIFICATE) { + return "ENTERPRISE_MACHINE_CERTIFICATE"; + } + if (value == ENTERPRISE_USER_CERTIFICATE) { + return "ENTERPRISE_USER_CERTIFICATE"; + } + if (value == CONTENT_PROTECTION_CERTIFICATE) { + return "CONTENT_PROTECTION_CERTIFICATE"; + } + if (value == CONTENT_PROTECTION_CERTIFICATE_WITH_STABLE_ID) { + return "CONTENT_PROTECTION_CERTIFICATE_WITH_STABLE_ID"; + } + if (value == CAST_CERTIFICATE) { + return "CAST_CERTIFICATE"; + } + if (value == GFSC_CERTIFICATE) { + return "GFSC_CERTIFICATE"; + } + return "<unknown>"; +} + +std::string GetProtoDebugString(const Quote& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent(const Quote& value, int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_quote()) { + output += indent + " quote: "; + base::StringAppendF( + &output, "%s", + base::HexEncode(value.quote().data(), value.quote().size()).c_str()); + output += "\n"; + } + if (value.has_quoted_data()) { + output += indent + " quoted_data: "; + base::StringAppendF(&output, "%s", + base::HexEncode(value.quoted_data().data(), + value.quoted_data().size()).c_str()); + output += "\n"; + } + if (value.has_quoted_pcr_value()) { + output += indent + " quoted_pcr_value: "; + base::StringAppendF( + &output, "%s", + base::HexEncode(value.quoted_pcr_value().data(), + value.quoted_pcr_value().size()).c_str()); + output += "\n"; + } + if (value.has_pcr_source_hint()) { + output += indent + " pcr_source_hint: "; + base::StringAppendF( + &output, "%s", base::HexEncode(value.pcr_source_hint().data(), + value.pcr_source_hint().size()).c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const EncryptedData& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent(const EncryptedData& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_wrapped_key()) { + output += indent + " wrapped_key: "; + base::StringAppendF(&output, "%s", + base::HexEncode(value.wrapped_key().data(), + value.wrapped_key().size()).c_str()); + output += "\n"; + } + if (value.has_iv()) { + output += indent + " iv: "; + base::StringAppendF( + &output, "%s", + base::HexEncode(value.iv().data(), value.iv().size()).c_str()); + output += "\n"; + } + if (value.has_mac()) { + output += indent + " mac: "; + base::StringAppendF( + &output, "%s", + base::HexEncode(value.mac().data(), value.mac().size()).c_str()); + output += "\n"; + } + if (value.has_encrypted_data()) { + output += indent + " encrypted_data: "; + base::StringAppendF(&output, "%s", + base::HexEncode(value.encrypted_data().data(), + value.encrypted_data().size()).c_str()); + output += "\n"; + } + if (value.has_wrapping_key_id()) { + output += indent + " wrapping_key_id: "; + base::StringAppendF( + &output, "%s", base::HexEncode(value.wrapping_key_id().data(), + value.wrapping_key_id().size()).c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const SignedData& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent(const SignedData& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_data()) { + output += indent + " data: "; + base::StringAppendF( + &output, "%s", + base::HexEncode(value.data().data(), value.data().size()).c_str()); + output += "\n"; + } + if (value.has_signature()) { + output += indent + " signature: "; + base::StringAppendF(&output, "%s", + base::HexEncode(value.signature().data(), + value.signature().size()).c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const EncryptedIdentityCredential& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent( + const EncryptedIdentityCredential& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_asym_ca_contents()) { + output += indent + " asym_ca_contents: "; + base::StringAppendF( + &output, "%s", + base::HexEncode(value.asym_ca_contents().data(), + value.asym_ca_contents().size()).c_str()); + output += "\n"; + } + if (value.has_sym_ca_attestation()) { + output += indent + " sym_ca_attestation: "; + base::StringAppendF( + &output, "%s", + base::HexEncode(value.sym_ca_attestation().data(), + value.sym_ca_attestation().size()).c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +} // namespace attestation diff --git a/attestation/common/print_common_proto.h b/attestation/common/print_common_proto.h new file mode 100644 index 0000000..9a2511a --- /dev/null +++ b/attestation/common/print_common_proto.h @@ -0,0 +1,50 @@ +// +// 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. +// + +// THIS CODE IS GENERATED. + +#ifndef ATTESTATION_COMMON_PRINT_COMMON_PROTO_H_ +#define ATTESTATION_COMMON_PRINT_COMMON_PROTO_H_ + +#include <string> + +#include "attestation/common/common.pb.h" + +namespace attestation { + +std::string GetProtoDebugStringWithIndent(KeyType value, int indent_size); +std::string GetProtoDebugString(KeyType value); +std::string GetProtoDebugStringWithIndent(KeyUsage value, int indent_size); +std::string GetProtoDebugString(KeyUsage value); +std::string GetProtoDebugStringWithIndent(CertificateProfile value, + int indent_size); +std::string GetProtoDebugString(CertificateProfile value); +std::string GetProtoDebugStringWithIndent(const Quote& value, int indent_size); +std::string GetProtoDebugString(const Quote& value); +std::string GetProtoDebugStringWithIndent(const EncryptedData& value, + int indent_size); +std::string GetProtoDebugString(const EncryptedData& value); +std::string GetProtoDebugStringWithIndent(const SignedData& value, + int indent_size); +std::string GetProtoDebugString(const SignedData& value); +std::string GetProtoDebugStringWithIndent( + const EncryptedIdentityCredential& value, + int indent_size); +std::string GetProtoDebugString(const EncryptedIdentityCredential& value); + +} // namespace attestation + +#endif // ATTESTATION_COMMON_PRINT_COMMON_PROTO_H_ diff --git a/attestation/common/print_interface_proto.cc b/attestation/common/print_interface_proto.cc new file mode 100644 index 0000000..6776e96 --- /dev/null +++ b/attestation/common/print_interface_proto.cc @@ -0,0 +1,683 @@ +// +// 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. +// + +// THIS CODE IS GENERATED. + +#include "attestation/common/print_interface_proto.h" + +#include <string> + +#include <base/strings/string_number_conversions.h> +#include <base/strings/stringprintf.h> + +#include "attestation/common/print_common_proto.h" + +namespace attestation { + +std::string GetProtoDebugString(AttestationStatus value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent(AttestationStatus value, + int indent_size) { + if (value == STATUS_SUCCESS) { + return "STATUS_SUCCESS"; + } + if (value == STATUS_UNEXPECTED_DEVICE_ERROR) { + return "STATUS_UNEXPECTED_DEVICE_ERROR"; + } + if (value == STATUS_NOT_AVAILABLE) { + return "STATUS_NOT_AVAILABLE"; + } + if (value == STATUS_NOT_READY) { + return "STATUS_NOT_READY"; + } + if (value == STATUS_NOT_ALLOWED) { + return "STATUS_NOT_ALLOWED"; + } + if (value == STATUS_INVALID_PARAMETER) { + return "STATUS_INVALID_PARAMETER"; + } + if (value == STATUS_REQUEST_DENIED_BY_CA) { + return "STATUS_REQUEST_DENIED_BY_CA"; + } + if (value == STATUS_CA_NOT_AVAILABLE) { + return "STATUS_CA_NOT_AVAILABLE"; + } + return "<unknown>"; +} + +std::string GetProtoDebugString(const CreateGoogleAttestedKeyRequest& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent( + const CreateGoogleAttestedKeyRequest& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_key_label()) { + output += indent + " key_label: "; + base::StringAppendF(&output, "%s", value.key_label().c_str()); + output += "\n"; + } + if (value.has_key_type()) { + output += indent + " key_type: "; + base::StringAppendF(&output, "%s", + GetProtoDebugStringWithIndent(value.key_type(), + indent_size + 2).c_str()); + output += "\n"; + } + if (value.has_key_usage()) { + output += indent + " key_usage: "; + base::StringAppendF(&output, "%s", + GetProtoDebugStringWithIndent(value.key_usage(), + indent_size + 2).c_str()); + output += "\n"; + } + if (value.has_certificate_profile()) { + output += indent + " certificate_profile: "; + base::StringAppendF(&output, "%s", GetProtoDebugStringWithIndent( + value.certificate_profile(), + indent_size + 2).c_str()); + output += "\n"; + } + if (value.has_username()) { + output += indent + " username: "; + base::StringAppendF(&output, "%s", value.username().c_str()); + output += "\n"; + } + if (value.has_origin()) { + output += indent + " origin: "; + base::StringAppendF(&output, "%s", value.origin().c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const CreateGoogleAttestedKeyReply& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent( + const CreateGoogleAttestedKeyReply& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_status()) { + output += indent + " status: "; + base::StringAppendF( + &output, "%s", + GetProtoDebugStringWithIndent(value.status(), indent_size + 2).c_str()); + output += "\n"; + } + if (value.has_server_error()) { + output += indent + " server_error: "; + base::StringAppendF(&output, "%s", value.server_error().c_str()); + output += "\n"; + } + if (value.has_certificate_chain()) { + output += indent + " certificate_chain: "; + base::StringAppendF(&output, "%s", value.certificate_chain().c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const GetKeyInfoRequest& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent(const GetKeyInfoRequest& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_key_label()) { + output += indent + " key_label: "; + base::StringAppendF(&output, "%s", value.key_label().c_str()); + output += "\n"; + } + if (value.has_username()) { + output += indent + " username: "; + base::StringAppendF(&output, "%s", value.username().c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const GetKeyInfoReply& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent(const GetKeyInfoReply& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_status()) { + output += indent + " status: "; + base::StringAppendF( + &output, "%s", + GetProtoDebugStringWithIndent(value.status(), indent_size + 2).c_str()); + output += "\n"; + } + if (value.has_key_type()) { + output += indent + " key_type: "; + base::StringAppendF(&output, "%s", + GetProtoDebugStringWithIndent(value.key_type(), + indent_size + 2).c_str()); + output += "\n"; + } + if (value.has_key_usage()) { + output += indent + " key_usage: "; + base::StringAppendF(&output, "%s", + GetProtoDebugStringWithIndent(value.key_usage(), + indent_size + 2).c_str()); + output += "\n"; + } + if (value.has_public_key()) { + output += indent + " public_key: "; + base::StringAppendF(&output, "%s", + base::HexEncode(value.public_key().data(), + value.public_key().size()).c_str()); + output += "\n"; + } + if (value.has_certify_info()) { + output += indent + " certify_info: "; + base::StringAppendF(&output, "%s", + base::HexEncode(value.certify_info().data(), + value.certify_info().size()).c_str()); + output += "\n"; + } + if (value.has_certify_info_signature()) { + output += indent + " certify_info_signature: "; + base::StringAppendF( + &output, "%s", + base::HexEncode(value.certify_info_signature().data(), + value.certify_info_signature().size()).c_str()); + output += "\n"; + } + if (value.has_certificate()) { + output += indent + " certificate: "; + base::StringAppendF(&output, "%s", + base::HexEncode(value.certificate().data(), + value.certificate().size()).c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const GetEndorsementInfoRequest& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent( + const GetEndorsementInfoRequest& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_key_type()) { + output += indent + " key_type: "; + base::StringAppendF(&output, "%s", + GetProtoDebugStringWithIndent(value.key_type(), + indent_size + 2).c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const GetEndorsementInfoReply& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent(const GetEndorsementInfoReply& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_status()) { + output += indent + " status: "; + base::StringAppendF( + &output, "%s", + GetProtoDebugStringWithIndent(value.status(), indent_size + 2).c_str()); + output += "\n"; + } + if (value.has_ek_public_key()) { + output += indent + " ek_public_key: "; + base::StringAppendF(&output, "%s", + base::HexEncode(value.ek_public_key().data(), + value.ek_public_key().size()).c_str()); + output += "\n"; + } + if (value.has_ek_certificate()) { + output += indent + " ek_certificate: "; + base::StringAppendF(&output, "%s", + base::HexEncode(value.ek_certificate().data(), + value.ek_certificate().size()).c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const GetAttestationKeyInfoRequest& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent( + const GetAttestationKeyInfoRequest& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_key_type()) { + output += indent + " key_type: "; + base::StringAppendF(&output, "%s", + GetProtoDebugStringWithIndent(value.key_type(), + indent_size + 2).c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const GetAttestationKeyInfoReply& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent( + const GetAttestationKeyInfoReply& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_status()) { + output += indent + " status: "; + base::StringAppendF( + &output, "%s", + GetProtoDebugStringWithIndent(value.status(), indent_size + 2).c_str()); + output += "\n"; + } + if (value.has_public_key()) { + output += indent + " public_key: "; + base::StringAppendF(&output, "%s", + base::HexEncode(value.public_key().data(), + value.public_key().size()).c_str()); + output += "\n"; + } + if (value.has_public_key_tpm_format()) { + output += indent + " public_key_tpm_format: "; + base::StringAppendF( + &output, "%s", + base::HexEncode(value.public_key_tpm_format().data(), + value.public_key_tpm_format().size()).c_str()); + output += "\n"; + } + if (value.has_certificate()) { + output += indent + " certificate: "; + base::StringAppendF(&output, "%s", + base::HexEncode(value.certificate().data(), + value.certificate().size()).c_str()); + output += "\n"; + } + if (value.has_pcr0_quote()) { + output += indent + " pcr0_quote: "; + base::StringAppendF(&output, "%s", + GetProtoDebugStringWithIndent(value.pcr0_quote(), + indent_size + 2).c_str()); + output += "\n"; + } + if (value.has_pcr1_quote()) { + output += indent + " pcr1_quote: "; + base::StringAppendF(&output, "%s", + GetProtoDebugStringWithIndent(value.pcr1_quote(), + indent_size + 2).c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const ActivateAttestationKeyRequest& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent( + const ActivateAttestationKeyRequest& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_key_type()) { + output += indent + " key_type: "; + base::StringAppendF(&output, "%s", + GetProtoDebugStringWithIndent(value.key_type(), + indent_size + 2).c_str()); + output += "\n"; + } + if (value.has_encrypted_certificate()) { + output += indent + " encrypted_certificate: "; + base::StringAppendF(&output, "%s", GetProtoDebugStringWithIndent( + value.encrypted_certificate(), + indent_size + 2).c_str()); + output += "\n"; + } + if (value.has_save_certificate()) { + output += indent + " save_certificate: "; + base::StringAppendF(&output, "%s", + value.save_certificate() ? "true" : "false"); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const ActivateAttestationKeyReply& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent( + const ActivateAttestationKeyReply& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_status()) { + output += indent + " status: "; + base::StringAppendF( + &output, "%s", + GetProtoDebugStringWithIndent(value.status(), indent_size + 2).c_str()); + output += "\n"; + } + if (value.has_certificate()) { + output += indent + " certificate: "; + base::StringAppendF(&output, "%s", + base::HexEncode(value.certificate().data(), + value.certificate().size()).c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const CreateCertifiableKeyRequest& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent( + const CreateCertifiableKeyRequest& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_key_label()) { + output += indent + " key_label: "; + base::StringAppendF(&output, "%s", value.key_label().c_str()); + output += "\n"; + } + if (value.has_username()) { + output += indent + " username: "; + base::StringAppendF(&output, "%s", value.username().c_str()); + output += "\n"; + } + if (value.has_key_type()) { + output += indent + " key_type: "; + base::StringAppendF(&output, "%s", + GetProtoDebugStringWithIndent(value.key_type(), + indent_size + 2).c_str()); + output += "\n"; + } + if (value.has_key_usage()) { + output += indent + " key_usage: "; + base::StringAppendF(&output, "%s", + GetProtoDebugStringWithIndent(value.key_usage(), + indent_size + 2).c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const CreateCertifiableKeyReply& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent( + const CreateCertifiableKeyReply& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_status()) { + output += indent + " status: "; + base::StringAppendF( + &output, "%s", + GetProtoDebugStringWithIndent(value.status(), indent_size + 2).c_str()); + output += "\n"; + } + if (value.has_public_key()) { + output += indent + " public_key: "; + base::StringAppendF(&output, "%s", + base::HexEncode(value.public_key().data(), + value.public_key().size()).c_str()); + output += "\n"; + } + if (value.has_certify_info()) { + output += indent + " certify_info: "; + base::StringAppendF(&output, "%s", + base::HexEncode(value.certify_info().data(), + value.certify_info().size()).c_str()); + output += "\n"; + } + if (value.has_certify_info_signature()) { + output += indent + " certify_info_signature: "; + base::StringAppendF( + &output, "%s", + base::HexEncode(value.certify_info_signature().data(), + value.certify_info_signature().size()).c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const DecryptRequest& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent(const DecryptRequest& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_key_label()) { + output += indent + " key_label: "; + base::StringAppendF(&output, "%s", value.key_label().c_str()); + output += "\n"; + } + if (value.has_username()) { + output += indent + " username: "; + base::StringAppendF(&output, "%s", value.username().c_str()); + output += "\n"; + } + if (value.has_encrypted_data()) { + output += indent + " encrypted_data: "; + base::StringAppendF(&output, "%s", + base::HexEncode(value.encrypted_data().data(), + value.encrypted_data().size()).c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const DecryptReply& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent(const DecryptReply& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_status()) { + output += indent + " status: "; + base::StringAppendF( + &output, "%s", + GetProtoDebugStringWithIndent(value.status(), indent_size + 2).c_str()); + output += "\n"; + } + if (value.has_decrypted_data()) { + output += indent + " decrypted_data: "; + base::StringAppendF(&output, "%s", + base::HexEncode(value.decrypted_data().data(), + value.decrypted_data().size()).c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const SignRequest& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent(const SignRequest& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_key_label()) { + output += indent + " key_label: "; + base::StringAppendF(&output, "%s", value.key_label().c_str()); + output += "\n"; + } + if (value.has_username()) { + output += indent + " username: "; + base::StringAppendF(&output, "%s", value.username().c_str()); + output += "\n"; + } + if (value.has_data_to_sign()) { + output += indent + " data_to_sign: "; + base::StringAppendF(&output, "%s", + base::HexEncode(value.data_to_sign().data(), + value.data_to_sign().size()).c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const SignReply& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent(const SignReply& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_status()) { + output += indent + " status: "; + base::StringAppendF( + &output, "%s", + GetProtoDebugStringWithIndent(value.status(), indent_size + 2).c_str()); + output += "\n"; + } + if (value.has_signature()) { + output += indent + " signature: "; + base::StringAppendF(&output, "%s", + base::HexEncode(value.signature().data(), + value.signature().size()).c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const RegisterKeyWithChapsTokenRequest& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent( + const RegisterKeyWithChapsTokenRequest& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_key_label()) { + output += indent + " key_label: "; + base::StringAppendF(&output, "%s", value.key_label().c_str()); + output += "\n"; + } + if (value.has_username()) { + output += indent + " username: "; + base::StringAppendF(&output, "%s", value.username().c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +std::string GetProtoDebugString(const RegisterKeyWithChapsTokenReply& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent( + const RegisterKeyWithChapsTokenReply& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = + base::StringPrintf("[%s] {\n", value.GetTypeName().c_str()); + + if (value.has_status()) { + output += indent + " status: "; + base::StringAppendF( + &output, "%s", + GetProtoDebugStringWithIndent(value.status(), indent_size + 2).c_str()); + output += "\n"; + } + output += indent + "}\n"; + return output; +} + +} // namespace attestation diff --git a/attestation/common/print_interface_proto.h b/attestation/common/print_interface_proto.h new file mode 100644 index 0000000..49b7b42 --- /dev/null +++ b/attestation/common/print_interface_proto.h @@ -0,0 +1,99 @@ +// +// 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. +// + +// THIS CODE IS GENERATED. + +#ifndef ATTESTATION_COMMON_PRINT_INTERFACE_PROTO_H_ +#define ATTESTATION_COMMON_PRINT_INTERFACE_PROTO_H_ + +#include <string> + +#include "attestation/common/interface.pb.h" + +namespace attestation { + +std::string GetProtoDebugStringWithIndent(AttestationStatus value, + int indent_size); +std::string GetProtoDebugString(AttestationStatus value); +std::string GetProtoDebugStringWithIndent( + const CreateGoogleAttestedKeyRequest& value, + int indent_size); +std::string GetProtoDebugString(const CreateGoogleAttestedKeyRequest& value); +std::string GetProtoDebugStringWithIndent( + const CreateGoogleAttestedKeyReply& value, + int indent_size); +std::string GetProtoDebugString(const CreateGoogleAttestedKeyReply& value); +std::string GetProtoDebugStringWithIndent(const GetKeyInfoRequest& value, + int indent_size); +std::string GetProtoDebugString(const GetKeyInfoRequest& value); +std::string GetProtoDebugStringWithIndent(const GetKeyInfoReply& value, + int indent_size); +std::string GetProtoDebugString(const GetKeyInfoReply& value); +std::string GetProtoDebugStringWithIndent( + const GetEndorsementInfoRequest& value, + int indent_size); +std::string GetProtoDebugString(const GetEndorsementInfoRequest& value); +std::string GetProtoDebugStringWithIndent(const GetEndorsementInfoReply& value, + int indent_size); +std::string GetProtoDebugString(const GetEndorsementInfoReply& value); +std::string GetProtoDebugStringWithIndent( + const GetAttestationKeyInfoRequest& value, + int indent_size); +std::string GetProtoDebugString(const GetAttestationKeyInfoRequest& value); +std::string GetProtoDebugStringWithIndent( + const GetAttestationKeyInfoReply& value, + int indent_size); +std::string GetProtoDebugString(const GetAttestationKeyInfoReply& value); +std::string GetProtoDebugStringWithIndent( + const ActivateAttestationKeyRequest& value, + int indent_size); +std::string GetProtoDebugString(const ActivateAttestationKeyRequest& value); +std::string GetProtoDebugStringWithIndent( + const ActivateAttestationKeyReply& value, + int indent_size); +std::string GetProtoDebugString(const ActivateAttestationKeyReply& value); +std::string GetProtoDebugStringWithIndent( + const CreateCertifiableKeyRequest& value, + int indent_size); +std::string GetProtoDebugString(const CreateCertifiableKeyRequest& value); +std::string GetProtoDebugStringWithIndent( + const CreateCertifiableKeyReply& value, + int indent_size); +std::string GetProtoDebugString(const CreateCertifiableKeyReply& value); +std::string GetProtoDebugStringWithIndent(const DecryptRequest& value, + int indent_size); +std::string GetProtoDebugString(const DecryptRequest& value); +std::string GetProtoDebugStringWithIndent(const DecryptReply& value, + int indent_size); +std::string GetProtoDebugString(const DecryptReply& value); +std::string GetProtoDebugStringWithIndent(const SignRequest& value, + int indent_size); +std::string GetProtoDebugString(const SignRequest& value); +std::string GetProtoDebugStringWithIndent(const SignReply& value, + int indent_size); +std::string GetProtoDebugString(const SignReply& value); +std::string GetProtoDebugStringWithIndent( + const RegisterKeyWithChapsTokenRequest& value, + int indent_size); +std::string GetProtoDebugString(const RegisterKeyWithChapsTokenRequest& value); +std::string GetProtoDebugStringWithIndent( + const RegisterKeyWithChapsTokenReply& value, + int indent_size); +std::string GetProtoDebugString(const RegisterKeyWithChapsTokenReply& value); + +} // namespace attestation + +#endif // ATTESTATION_COMMON_PRINT_INTERFACE_PROTO_H_ diff --git a/attestation/common/proto_print.py b/attestation/common/proto_print.py new file mode 100755 index 0000000..63cffe6 --- /dev/null +++ b/attestation/common/proto_print.py @@ -0,0 +1,417 @@ +#!/usr/bin/python2 + +# +# 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. +# + +"""A C++ code generator for printing protobufs which use the LITE_RUNTIME. + +Normally printing a protobuf would be done with Message::DebugString(). However, +this is not available when using only MessageLite. This script generates code to +emulate Message::DebugString() without using reflection. The input must be a +valid .proto file. + +Usage: proto_print.py [--subdir=foo] <bar.proto> + +Files named print_bar_proto.h and print_bar_proto.cc will be created in the +current working directory. +""" + +from __future__ import print_function + +import argparse +import collections +from datetime import date +import os +import re +import subprocess + + +# Holds information about a protobuf message field. +# +# Attributes: +# repeated: Whether the field is a repeated field. +# type_: The type of the field. E.g. int32. +# name: The name of the field. +Field = collections.namedtuple('Field', 'repeated type_ name') + + +class Message(object): + """Holds information about a protobuf message. + + Attributes: + name: The name of the message. + fields: A list of Field tuples. + """ + + def __init__(self, name): + """Initializes a Message instance. + + Args: + name: The protobuf message name. + """ + self.name = name + self.fields = [] + + def AddField(self, attribute, field_type, field_name): + """Adds a new field to the message. + + Args: + attribute: This should be 'optional', 'required', or 'repeated'. + field_type: The type of the field. E.g. int32. + field_name: The name of the field. + """ + self.fields.append(Field(repeated=attribute == 'repeated', + type_=field_type, name=field_name)) + + +class Enum(object): + """Holds information about a protobuf enum. + + Attributes: + name: The name of the enum. + values: A list of enum value names. + """ + + def __init__(self, name): + """Initializes a Enum instance. + + Args: + name: The protobuf enum name. + """ + self.name = name + self.values = [] + + def AddValue(self, value_name): + """Adds a new value to the enum. + + Args: + value_name: The name of the value. + """ + self.values.append(value_name) + + +def ParseProto(input_file): + """Parses a proto file and returns a tuple of parsed information. + + Args: + input_file: The proto file to parse. + + Returns: + A tuple in the form (package, imports, messages, enums) where + package: A string holding the proto package. + imports: A list of strings holding proto imports. + messages: A list of Message objects; one for each message in the proto. + enums: A list of Enum objects; one for each enum in the proto. + """ + package = '' + imports = [] + messages = [] + enums = [] + current_message_stack = [] + current_enum = None + package_re = re.compile(r'package\s+(\w+);') + import_re = re.compile(r'import\s+"(\w+).proto";') + message_re = re.compile(r'message\s+(\w+)\s*{') + field_re = re.compile(r'(optional|required|repeated)\s+(\w+)\s+(\w+)\s*=') + enum_re = re.compile(r'enum\s+(\w+)\s*{') + enum_value_re = re.compile(r'(\w+)\s*=') + for line in input_file: + line = line.strip() + if not line or line.startswith('//'): + continue + # Close off the current scope. Enums first because they can't be nested. + if line == '}': + if current_enum: + enums.append(current_enum) + current_enum = None + if current_message_stack: + messages.append(current_message_stack.pop()) + continue + # Look for a message definition. + match = message_re.search(line) + if match: + prefix = '' + if current_message_stack: + prefix = '::'.join([m.name for m in current_message_stack]) + '::' + current_message_stack.append(Message(prefix + match.group(1))) + continue + # Look for a message field definition. + if current_message_stack: + match = field_re.search(line) + if match: + current_message_stack[-1].AddField(match.group(1), + match.group(2), + match.group(3)) + continue + # Look for an enum definition. + match = enum_re.search(line) + if match: + current_enum = Enum(match.group(1)) + continue + # Look for an enum value. + if current_enum: + match = enum_value_re.search(line) + if match: + current_enum.AddValue(match.group(1)) + continue + # Look for a package statement. + match = package_re.search(line) + if match: + package = match.group(1) + # Look for an import statement. + match = import_re.search(line) + if match: + imports.append(match.group(1)) + return package, imports, messages, enums + + +def GenerateFileHeaders(proto_name, package, imports, subdir, header_file_name, + header_file, impl_file): + """Generates and prints file headers. + + Args: + proto_name: The name of the proto file. + package: The protobuf package. + imports: A list of imported protos. + subdir: The --subdir arg. + header_file_name: The header file name. + header_file: The header file handle, open for writing. + impl_file: The implementation file handle, open for writing. + """ + if subdir: + guard_name = '%s_%s_PRINT_%s_PROTO_H_' % (package.upper(), + subdir.upper(), + proto_name.upper()) + package_with_subdir = '%s/%s' % (package, subdir) + else: + guard_name = '%s_PRINT_%s_PROTO_H_' % (package.upper(), proto_name.upper()) + package_with_subdir = package + includes = '\n'.join( + ['#include "%(package_with_subdir)s/print_%(import)s_proto.h"' % { + 'package_with_subdir': package_with_subdir, + 'import': current_import} for current_import in imports]) + header = """\ +// Copyright %(year)s The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// THIS CODE IS GENERATED. + +#ifndef %(guard_name)s +#define %(guard_name)s + +#include <string> + +#include "%(package_with_subdir)s/%(proto)s.pb.h" + +namespace %(package)s { +""" % {'year': date.today().year, + 'guard_name': guard_name, + 'package': package, + 'proto': proto_name, + 'package_with_subdir': package_with_subdir} + impl = """\ +// Copyright %(year)s The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// THIS CODE IS GENERATED. + +#include "%(package_with_subdir)s/%(header_file_name)s" + +#include <string> + +#include <base/strings/string_number_conversions.h> +#include <base/strings/stringprintf.h> + +%(includes)s + +namespace %(package)s { +""" % {'year': date.today().year, + 'package': package, + 'package_with_subdir': package_with_subdir, + 'header_file_name': header_file_name, + 'includes': includes} + + header_file.write(header) + impl_file.write(impl) + + +def GenerateFileFooters(proto_name, package, subdir, header_file, impl_file): + """Generates and prints file footers. + + Args: + proto_name: The name of the proto file. + package: The protobuf package. + subdir: The --subdir arg. + header_file: The header file handle, open for writing. + impl_file: The implementation file handle, open for writing. + """ + if subdir: + guard_name = '%s_%s_PRINT_%s_PROTO_H_' % (package.upper(), + subdir.upper(), + proto_name.upper()) + else: + guard_name = '%s_PRINT_%s_PROTO_H_' % (package.upper(), proto_name.upper()) + header = """ + +} // namespace %(package)s + +#endif // %(guard_name)s +""" % {'guard_name': guard_name, 'package': package} + impl = """ +} // namespace %(package)s +""" % {'package': package} + + header_file.write(header) + impl_file.write(impl) + + +def GenerateEnumPrinter(enum, header_file, impl_file): + """Generates and prints a function to print an enum value. + + Args: + enum: An Enum instance. + header_file: The header file handle, open for writing. + impl_file: The implementation file handle, open for writing. + """ + declare = """ +std::string GetProtoDebugStringWithIndent(%(name)s value, int indent_size); +std::string GetProtoDebugString(%(name)s value);""" % {'name': enum.name} + define_begin = """ +std::string GetProtoDebugString(%(name)s value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent(%(name)s value, int indent_size) { +""" % {'name': enum.name} + define_end = """ + return "<unknown>"; +} +""" + condition = """ + if (value == %(value_name)s) { + return "%(value_name)s"; + }""" + + header_file.write(declare) + impl_file.write(define_begin) + for value_name in enum.values: + impl_file.write(condition % {'value_name': value_name}) + impl_file.write(define_end) + + +def GenerateMessagePrinter(message, header_file, impl_file): + """Generates and prints a function to print a message. + + Args: + message: A Message instance. + header_file: The header file handle, open for writing. + impl_file: The implementation file handle, open for writing. + """ + declare = """ +std::string GetProtoDebugStringWithIndent(const %(name)s& value, + int indent_size); +std::string GetProtoDebugString(const %(name)s& value);""" % {'name': + message.name} + define_begin = """ +std::string GetProtoDebugString(const %(name)s& value) { + return GetProtoDebugStringWithIndent(value, 0); +} + +std::string GetProtoDebugStringWithIndent(const %(name)s& value, + int indent_size) { + std::string indent(indent_size, ' '); + std::string output = base::StringPrintf("[%%s] {\\n", + value.GetTypeName().c_str()); +""" % {'name': message.name} + define_end = """ + output += indent + "}\\n"; + return output; +} +""" + singular_field = """ + if (value.has_%(name)s()) { + output += indent + " %(name)s: "; + base::StringAppendF(&output, %(format)s); + output += "\\n"; + }""" + repeated_field = """ + output += indent + " %(name)s: {"; + for (int i = 0; i < value.%(name)s_size(); ++i) { + base::StringAppendF(&output, %(format)s); + } + output += "}\\n";""" + singular_field_get = 'value.%(name)s()' + repeated_field_get = 'value.%(name)s(i)' + formats = {'bool': '"%%s", %(value)s ? "true" : "false"', + 'int32': '"%%d", %(value)s', + 'int64': '"%%ld", %(value)s', + 'uint32': '"%%u", %(value)s', + 'uint64': '"%%lu", %(value)s', + 'string': '"%%s", %(value)s.c_str()', + 'bytes': """"%%s", base::HexEncode(%(value)s.data(), + %(value)s.size()).c_str()"""} + subtype_format = ('"%%s", GetProtoDebugStringWithIndent(%(value)s, ' + 'indent_size + 2).c_str()') + + header_file.write(declare) + impl_file.write(define_begin) + for field in message.fields: + if field.repeated: + value_get = repeated_field_get % {'name': field.name} + field_code = repeated_field + else: + value_get = singular_field_get % {'name': field.name} + field_code = singular_field + if field.type_ in formats: + value_format = formats[field.type_] % {'value': value_get} + else: + value_format = subtype_format % {'value': value_get} + impl_file.write(field_code % {'name': field.name, + 'format': value_format}) + impl_file.write(define_end) + + +def FormatFile(filename): + subprocess.call(['clang-format', '-i', '-style=Chromium', filename]) + + +def main(): + parser = argparse.ArgumentParser(description='print proto code generator') + parser.add_argument('input_file') + parser.add_argument('--subdir', default='') + args = parser.parse_args() + with open(args.input_file) as input_file: + package, imports, messages, enums = ParseProto(input_file) + proto_name = os.path.basename(args.input_file).rsplit('.', 1)[0] + header_file_name = 'print_%s_proto.h' % proto_name + impl_file_name = 'print_%s_proto.cc' % proto_name + with open(header_file_name, 'w') as header_file: + with open(impl_file_name, 'w') as impl_file: + GenerateFileHeaders(proto_name, package, imports, args.subdir, + header_file_name, header_file, impl_file) + for enum in enums: + GenerateEnumPrinter(enum, header_file, impl_file) + for message in messages: + GenerateMessagePrinter(message, header_file, impl_file) + GenerateFileFooters(proto_name, package, args.subdir, header_file, + impl_file) + FormatFile(header_file_name) + FormatFile(impl_file_name) + +if __name__ == '__main__': + main() diff --git a/attestation/common/tpm_utility.h b/attestation/common/tpm_utility.h new file mode 100644 index 0000000..5432d12 --- /dev/null +++ b/attestation/common/tpm_utility.h @@ -0,0 +1,97 @@ +// +// 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. +// + +#ifndef ATTESTATION_COMMON_TPM_UTILITY_H_ +#define ATTESTATION_COMMON_TPM_UTILITY_H_ + +#include <string> + +#include "attestation/common/interface.pb.h" + +namespace attestation { + +// A class which provides helpers for TPM-related tasks. +class TpmUtility { + public: + virtual ~TpmUtility() = default; + + // Returns true iff the TPM is enabled, owned, and ready for attestation. + virtual bool IsTpmReady() = 0; + + // Activates an attestation identity key. Effectively this decrypts a + // certificate or some other type of credential with the endorsement key. The + // |delegate_blob| and |delegate_secret| must be authorized to activate with + // owner privilege. The |identity_key_blob| is the key to which the credential + // is bound. The |asym_ca_contents| and |sym_ca_attestation| parameters are + // encrypted TPM structures, typically created by a CA (TPM_ASYM_CA_CONTENTS + // and TPM_SYM_CA_ATTESTATION respectively). On success returns true and + // populates the decrypted |credential|. + virtual bool ActivateIdentity(const std::string& delegate_blob, + const std::string& delegate_secret, + const std::string& identity_key_blob, + const std::string& asym_ca_contents, + const std::string& sym_ca_attestation, + std::string* credential) = 0; + + // Generates and certifies a non-migratable key in the TPM. The new key will + // correspond to |key_type| and |key_usage|. The parent key will be the + // storage root key. The new key will be certified with the attestation + // identity key represented by |identity_key_blob|. The |external_data| will + // be included in the |key_info|. On success, returns true and populates + // |public_key_tpm_format| with the public key of |key_blob| in TPM_PUBKEY + // format, |key_info| with the TPM_CERTIFY_INFO that was signed, and |proof| + // with the signature of |key_info| by the identity key. + virtual bool CreateCertifiedKey(KeyType key_type, + KeyUsage key_usage, + const std::string& identity_key_blob, + const std::string& external_data, + std::string* key_blob, + std::string* public_key, + std::string* public_key_tpm_format, + std::string* key_info, + std::string* proof) = 0; + + // Seals |data| to the current value of PCR0 with the SRK and produces the + // |sealed_data|. Returns true on success. + virtual bool SealToPCR0(const std::string& data, + std::string* sealed_data) = 0; + + // Unseals |sealed_data| previously sealed with the SRK and produces the + // unsealed |data|. Returns true on success. + virtual bool Unseal(const std::string& sealed_data, std::string* data) = 0; + + // Reads the endorsement public key from the TPM. + virtual bool GetEndorsementPublicKey(std::string* public_key) = 0; + + // Unbinds |bound_data| with the key loaded from |key_blob| by decrypting + // using the TPM_ES_RSAESOAEP_SHA1_MGF1 scheme. The input must be in the + // format of a TPM_BOUND_DATA structure. On success returns true and provides + // the decrypted |data|. + 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 + +#endif // ATTESTATION_COMMON_TPM_UTILITY_H_ diff --git a/attestation/common/tpm_utility_v1.cc b/attestation/common/tpm_utility_v1.cc new file mode 100644 index 0000000..6b7ab17 --- /dev/null +++ b/attestation/common/tpm_utility_v1.cc @@ -0,0 +1,720 @@ +// +// 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/common/tpm_utility_v1.h" + +#include <base/files/file_path.h> +#include <base/files/file_util.h> +#include <base/logging.h> +#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> + +#define TPM_LOG(severity, result) \ + LOG(severity) << "TPM error 0x" << std::hex << result \ + << " (" << Trspi_Error_String(result) << "): " + +using trousers::ScopedTssContext; +using trousers::ScopedTssKey; +using trousers::ScopedTssMemory; +using trousers::ScopedTssPcrs; + +namespace { + +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; + base::ReadFileToString(base::FilePath(file_name), &content); + if (content.size() > 1) { + content.resize(1); + } + return content; +} + +BYTE* StringAsTSSBuffer(std::string* s) { + return reinterpret_cast<BYTE*>(string_as_array(s)); +} + +std::string TSSBufferAsString(const BYTE* buffer, size_t length) { + return std::string(reinterpret_cast<const char*>(buffer), length); +} + +} // namespace + +namespace attestation { + +TpmUtilityV1::~TpmUtilityV1() {} + +bool TpmUtilityV1::Initialize() { + if (!ConnectContext(&context_handle_, &tpm_handle_)) { + LOG(ERROR) << __func__ << ": Failed to connect to the TPM."; + return false; + } + if (!IsTpmReady()) { + LOG(WARNING) << __func__ << ": TPM is not owned; attestation services will " + << "not be available until ownership is taken."; + } + return true; +} + +bool TpmUtilityV1::IsTpmReady() { + if (!is_ready_) { + is_ready_ = (GetFirstByte(kTpmEnabledFile) == "1" && + GetFirstByte(kTpmOwnedFile) == "1"); + } + return is_ready_; +} + +bool TpmUtilityV1::ActivateIdentity(const std::string& delegate_blob, + const std::string& delegate_secret, + const std::string& identity_key_blob, + const std::string& asym_ca_contents, + const std::string& sym_ca_attestation, + std::string* credential) { + CHECK(credential); + if (!SetupSrk()) { + LOG(ERROR) << "SRK is not ready."; + return false; + } + + // Connect to the TPM as the owner delegate. + ScopedTssContext context_handle; + TSS_HTPM tpm_handle; + if (!ConnectContextAsDelegate(delegate_blob, delegate_secret, + &context_handle, &tpm_handle)) { + LOG(ERROR) << __func__ << ": Could not connect to the TPM."; + return false; + } + // Load the Storage Root Key. + TSS_RESULT result; + ScopedTssKey srk_handle(context_handle); + if (!LoadSrk(context_handle, &srk_handle)) { + LOG(ERROR) << __func__ << ": Failed to load SRK."; + return false; + } + // Load the AIK (which is wrapped by the SRK). + std::string mutable_identity_key_blob(identity_key_blob); + BYTE* identity_key_blob_buffer = StringAsTSSBuffer( + &mutable_identity_key_blob); + ScopedTssKey identity_key(context_handle); + result = Tspi_Context_LoadKeyByBlob( + context_handle, + srk_handle, + identity_key_blob.size(), + identity_key_blob_buffer, + identity_key.ptr()); + if (TPM_ERROR(result)) { + TPM_LOG(ERROR, result) << __func__ << ": Failed to load AIK."; + return false; + } + std::string mutable_asym_ca_contents(asym_ca_contents); + BYTE* asym_ca_contents_buffer = StringAsTSSBuffer(&mutable_asym_ca_contents); + std::string mutable_sym_ca_attestation(sym_ca_attestation); + BYTE* sym_ca_attestation_buffer = StringAsTSSBuffer( + &mutable_sym_ca_attestation); + UINT32 credential_length = 0; + ScopedTssMemory credential_buffer(context_handle); + result = Tspi_TPM_ActivateIdentity(tpm_handle, identity_key, + asym_ca_contents.size(), + asym_ca_contents_buffer, + sym_ca_attestation.size(), + sym_ca_attestation_buffer, + &credential_length, + credential_buffer.ptr()); + if (TPM_ERROR(result)) { + TPM_LOG(ERROR, result) << __func__ << ": Failed to activate identity."; + return false; + } + credential->assign(TSSBufferAsString(credential_buffer.value(), + credential_length)); + return true; +} + +bool TpmUtilityV1::CreateCertifiedKey(KeyType key_type, + KeyUsage key_usage, + const std::string& identity_key_blob, + const std::string& external_data, + std::string* key_blob, + std::string* public_key, + std::string* public_key_tpm_format, + std::string* key_info, + std::string* proof) { + CHECK(key_blob && public_key && public_key_tpm_format && key_info && proof); + if (!SetupSrk()) { + LOG(ERROR) << "SRK is not ready."; + return false; + } + if (key_type != KEY_TYPE_RSA) { + LOG(ERROR) << "Only RSA supported on TPM v1.2."; + return false; + } + + // Load the AIK (which is wrapped by the SRK). + ScopedTssKey identity_key(context_handle_); + if (!LoadKeyFromBlob(identity_key_blob, context_handle_, srk_handle_, + &identity_key)) { + LOG(ERROR) << __func__ << "Failed to load AIK."; + return false; + } + + // Create a non-migratable RSA key. + ScopedTssKey key(context_handle_); + UINT32 tss_key_type = (key_usage == KEY_USAGE_SIGN) ? TSS_KEY_TYPE_SIGNING : + TSS_KEY_TYPE_BIND; + UINT32 init_flags = tss_key_type | + TSS_KEY_NOT_MIGRATABLE | + TSS_KEY_VOLATILE | + TSS_KEY_NO_AUTHORIZATION | + TSS_KEY_SIZE_2048; + TSS_RESULT result = Tspi_Context_CreateObject(context_handle_, + TSS_OBJECT_TYPE_RSAKEY, + init_flags, key.ptr()); + if (TPM_ERROR(result)) { + TPM_LOG(ERROR, result) << __func__ << ": Failed to create object."; + return false; + } + if (key_usage == KEY_USAGE_SIGN) { + result = Tspi_SetAttribUint32(key, + TSS_TSPATTRIB_KEY_INFO, + TSS_TSPATTRIB_KEYINFO_SIGSCHEME, + TSS_SS_RSASSAPKCS1V15_DER); + } else { + result = Tspi_SetAttribUint32(key, + TSS_TSPATTRIB_KEY_INFO, + TSS_TSPATTRIB_KEYINFO_ENCSCHEME, + TSS_ES_RSAESOAEP_SHA1_MGF1); + } + if (TPM_ERROR(result)) { + TPM_LOG(ERROR, result) << __func__ << ": Failed to set scheme."; + return false; + } + result = Tspi_Key_CreateKey(key, srk_handle_, 0); + if (TPM_ERROR(result)) { + TPM_LOG(ERROR, result) << __func__ << ": Failed to create key."; + return false; + } + result = Tspi_Key_LoadKey(key, srk_handle_); + if (TPM_ERROR(result)) { + TPM_LOG(ERROR, result) << __func__ << ": Failed to load key."; + return false; + } + + // Certify the key. + TSS_VALIDATION validation; + memset(&validation, 0, sizeof(validation)); + validation.ulExternalDataLength = external_data.size(); + std::string mutable_external_data(external_data); + validation.rgbExternalData = StringAsTSSBuffer(&mutable_external_data); + result = Tspi_Key_CertifyKey(key, identity_key, &validation); + if (TPM_ERROR(result)) { + TPM_LOG(ERROR, result) << __func__ << ": Failed to certify key."; + return false; + } + ScopedTssMemory scoped_certified_data(0, validation.rgbData); + ScopedTssMemory scoped_proof(0, validation.rgbValidationData); + + // Get the certified public key. + if (!GetDataAttribute(context_handle_, + key, + TSS_TSPATTRIB_KEY_BLOB, + TSS_TSPATTRIB_KEYBLOB_PUBLIC_KEY, + public_key_tpm_format)) { + LOG(ERROR) << __func__ << ": Failed to read public key."; + return false; + } + if (!ConvertPublicKeyToDER(*public_key_tpm_format, public_key)) { + return false; + } + + // Get the certified key blob so we can load it later. + if (!GetDataAttribute(context_handle_, + key, + TSS_TSPATTRIB_KEY_BLOB, + TSS_TSPATTRIB_KEYBLOB_BLOB, + key_blob)) { + LOG(ERROR) << __func__ << ": Failed to read key blob."; + return false; + } + + // Get the data that was certified. + key_info->assign(TSSBufferAsString(validation.rgbData, + validation.ulDataLength)); + + // Get the certification proof. + proof->assign(TSSBufferAsString(validation.rgbValidationData, + validation.ulValidationDataLength)); + return true; +} + +bool TpmUtilityV1::SealToPCR0(const std::string& data, + std::string* sealed_data) { + CHECK(sealed_data); + if (!SetupSrk()) { + LOG(ERROR) << "SRK is not ready."; + return false; + } + + // Create a PCRS object which holds the value of PCR0. + ScopedTssPcrs pcrs_handle(context_handle_); + TSS_RESULT result; + if (TPM_ERROR(result = Tspi_Context_CreateObject(context_handle_, + TSS_OBJECT_TYPE_PCRS, + TSS_PCRS_STRUCT_INFO, + pcrs_handle.ptr()))) { + TPM_LOG(ERROR, result) + << __func__ << ": Error calling Tspi_Context_CreateObject"; + return false; + } + UINT32 pcr_length = 0; + ScopedTssMemory pcr_value(context_handle_); + Tspi_TPM_PcrRead(tpm_handle_, 0, &pcr_length, pcr_value.ptr()); + Tspi_PcrComposite_SetPcrValue(pcrs_handle, 0, pcr_length, pcr_value.value()); + + // Create a ENCDATA object to receive the sealed data. + ScopedTssKey encrypted_data_handle(context_handle_); + if (TPM_ERROR(result = Tspi_Context_CreateObject( + context_handle_, + TSS_OBJECT_TYPE_ENCDATA, + TSS_ENCDATA_SEAL, + encrypted_data_handle.ptr()))) { + TPM_LOG(ERROR, result) + << __func__ << ": Error calling Tspi_Context_CreateObject"; + return false; + } + + // Seal the given value with the SRK. + std::string mutable_data(data); + BYTE* data_buffer = StringAsTSSBuffer(&mutable_data); + if (TPM_ERROR(result = Tspi_Data_Seal( + encrypted_data_handle, + srk_handle_, + data.size(), + data_buffer, + pcrs_handle))) { + TPM_LOG(ERROR, result) << __func__ << ": Error calling Tspi_Data_Seal"; + return false; + } + + // Extract the sealed value. + ScopedTssMemory encrypted_data(context_handle_); + UINT32 encrypted_data_length = 0; + if (TPM_ERROR(result = Tspi_GetAttribData(encrypted_data_handle, + TSS_TSPATTRIB_ENCDATA_BLOB, + TSS_TSPATTRIB_ENCDATABLOB_BLOB, + &encrypted_data_length, + encrypted_data.ptr()))) { + TPM_LOG(ERROR, result) << __func__ << ": Error calling Tspi_GetAttribData"; + return false; + } + sealed_data->assign(TSSBufferAsString(encrypted_data.value(), + encrypted_data_length)); + return true; +} + +bool TpmUtilityV1::Unseal(const std::string& sealed_data, std::string* data) { + CHECK(data); + if (!SetupSrk()) { + LOG(ERROR) << "SRK is not ready."; + return false; + } + + // Create an ENCDATA object with the sealed value. + ScopedTssKey encrypted_data_handle(context_handle_); + TSS_RESULT result; + if (TPM_ERROR(result = Tspi_Context_CreateObject( + context_handle_, + TSS_OBJECT_TYPE_ENCDATA, + TSS_ENCDATA_SEAL, + encrypted_data_handle.ptr()))) { + TPM_LOG(ERROR, result) + << __func__ << ": Error calling Tspi_Context_CreateObject"; + return false; + } + + std::string mutable_sealed_data(sealed_data); + BYTE* sealed_data_buffer = StringAsTSSBuffer(&mutable_sealed_data); + if (TPM_ERROR(result = Tspi_SetAttribData(encrypted_data_handle, + TSS_TSPATTRIB_ENCDATA_BLOB, + TSS_TSPATTRIB_ENCDATABLOB_BLOB, + sealed_data.size(), + sealed_data_buffer))) { + TPM_LOG(ERROR, result) << __func__ << ": Error calling Tspi_SetAttribData"; + return false; + } + + // Unseal using the SRK. + ScopedTssMemory decrypted_data(context_handle_); + UINT32 decrypted_data_length = 0; + if (TPM_ERROR(result = Tspi_Data_Unseal(encrypted_data_handle, + srk_handle_, + &decrypted_data_length, + decrypted_data.ptr()))) { + TPM_LOG(ERROR, result) << __func__ << ": Error calling Tspi_Data_Unseal"; + return false; + } + data->assign(TSSBufferAsString(decrypted_data.value(), + decrypted_data_length)); + return true; +} + +bool TpmUtilityV1::GetEndorsementPublicKey(std::string* public_key) { + // Get a handle to the EK public key. + ScopedTssKey ek_public_key_object(context_handle_); + TSS_RESULT result = Tspi_TPM_GetPubEndorsementKey(tpm_handle_, false, nullptr, + ek_public_key_object.ptr()); + if (TPM_ERROR(result)) { + TPM_LOG(ERROR, result) << __func__ << ": Failed to get key."; + return false; + } + // Get the public key in TPM_PUBKEY form. + std::string ek_public_key_blob; + if (!GetDataAttribute(context_handle_, + ek_public_key_object, + TSS_TSPATTRIB_KEY_BLOB, + TSS_TSPATTRIB_KEYBLOB_PUBLIC_KEY, + &ek_public_key_blob)) { + LOG(ERROR) << __func__ << ": Failed to read public key."; + return false; + } + // Get the public key in DER encoded form. + if (!ConvertPublicKeyToDER(ek_public_key_blob, public_key)) { + return false; + } + return true; +} + +bool TpmUtilityV1::Unbind(const std::string& key_blob, + const std::string& bound_data, + std::string* data) { + CHECK(data); + 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; + } + TSS_RESULT result; + ScopedTssEncryptedData data_handle(context_handle_); + if (TPM_ERROR(result = Tspi_Context_CreateObject(context_handle_, + TSS_OBJECT_TYPE_ENCDATA, + TSS_ENCDATA_BIND, + data_handle.ptr()))) { + TPM_LOG(ERROR, result) << __func__ << ": Tspi_Context_CreateObject failed."; + return false; + } + std::string mutable_bound_data(bound_data); + if (TPM_ERROR(result = Tspi_SetAttribData( + data_handle, + TSS_TSPATTRIB_ENCDATA_BLOB, + TSS_TSPATTRIB_ENCDATABLOB_BLOB, + bound_data.size(), + StringAsTSSBuffer(&mutable_bound_data)))) { + TPM_LOG(ERROR, result) << __func__ << ": Tspi_SetAttribData failed."; + return false; + } + + ScopedTssMemory decrypted_data(context_handle_); + UINT32 length = 0; + if (TPM_ERROR(result = Tspi_Data_Unbind(data_handle, key_handle, + &length, decrypted_data.ptr()))) { + TPM_LOG(ERROR, result) << __func__ << ": Tspi_Data_Unbind failed."; + return false; + } + data->assign(TSSBufferAsString(decrypted_data.value(), length)); + 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; + if (TPM_ERROR(result = Tspi_Context_Create(context->ptr()))) { + TPM_LOG(ERROR, result) << __func__ << ": Error calling Tspi_Context_Create"; + return false; + } + if (TPM_ERROR(result = Tspi_Context_Connect(*context, nullptr))) { + TPM_LOG(ERROR, result) << __func__ + << ": Error calling Tspi_Context_Connect"; + return false; + } + if (TPM_ERROR(result = Tspi_Context_GetTpmObject(*context, tpm))) { + TPM_LOG(ERROR, result) << __func__ + << ": Error calling Tspi_Context_GetTpmObject"; + return false; + } + return true; +} + +bool TpmUtilityV1::ConnectContextAsDelegate(const std::string& delegate_blob, + const std::string& delegate_secret, + ScopedTssContext* context, + TSS_HTPM* tpm) { + *tpm = 0; + if (!ConnectContext(context, tpm)) { + return false; + } + TSS_RESULT result; + TSS_HPOLICY tpm_usage_policy; + if (TPM_ERROR(result = Tspi_GetPolicyObject(*tpm, + TSS_POLICY_USAGE, + &tpm_usage_policy))) { + TPM_LOG(ERROR, result) << __func__ + << ": Error calling Tspi_GetPolicyObject"; + return false; + } + std::string mutable_delegate_secret(delegate_secret); + BYTE* secret_buffer = StringAsTSSBuffer(&mutable_delegate_secret); + if (TPM_ERROR(result = Tspi_Policy_SetSecret(tpm_usage_policy, + TSS_SECRET_MODE_PLAIN, + delegate_secret.size(), + secret_buffer))) { + TPM_LOG(ERROR, result) << __func__ + << ": Error calling Tspi_Policy_SetSecret"; + return false; + } + std::string mutable_delegate_blob(delegate_blob); + BYTE* blob_buffer = StringAsTSSBuffer(&mutable_delegate_blob); + if (TPM_ERROR(result = Tspi_SetAttribData( + tpm_usage_policy, + TSS_TSPATTRIB_POLICY_DELEGATION_INFO, + TSS_TSPATTRIB_POLDEL_OWNERBLOB, + delegate_blob.size(), + blob_buffer))) { + TPM_LOG(ERROR, result) << __func__ << ": Error calling Tspi_SetAttribData"; + return false; + } + return true; +} + +bool TpmUtilityV1::SetupSrk() { + if (!IsTpmReady()) { + return false; + } + if (srk_handle_) { + return true; + } + srk_handle_.reset(context_handle_, 0); + if (!LoadSrk(context_handle_, &srk_handle_)) { + LOG(ERROR) << __func__ << ": Failed to load SRK."; + return false; + } + // In order to wrap a key with the SRK we need access to the SRK public key + // and we need to get it manually. Once it's in the key object, we don't need + // to do this again. + UINT32 length = 0; + ScopedTssMemory buffer(context_handle_); + TSS_RESULT result; + result = Tspi_Key_GetPubKey(srk_handle_, &length, buffer.ptr()); + if (result != TSS_SUCCESS) { + TPM_LOG(INFO, result) << __func__ << ": Failed to read SRK public key."; + return false; + } + return true; +} + +bool TpmUtilityV1::LoadSrk(TSS_HCONTEXT context_handle, + ScopedTssKey* srk_handle) { + TSS_RESULT result; + TSS_UUID uuid = TSS_UUID_SRK; + if (TPM_ERROR(result = Tspi_Context_LoadKeyByUUID(context_handle, + TSS_PS_TYPE_SYSTEM, + uuid, + srk_handle->ptr()))) { + TPM_LOG(ERROR, result) << __func__ + << ": Error calling Tspi_Context_LoadKeyByUUID"; + return false; + } + // Check if the SRK wants a password. + UINT32 auth_usage; + if (TPM_ERROR(result = Tspi_GetAttribUint32(*srk_handle, + TSS_TSPATTRIB_KEY_INFO, + TSS_TSPATTRIB_KEYINFO_AUTHUSAGE, + &auth_usage))) { + TPM_LOG(ERROR, result) << __func__ + << ": Error calling Tspi_GetAttribUint32"; + return false; + } + if (auth_usage) { + // Give it an empty password if needed. + TSS_HPOLICY usage_policy; + if (TPM_ERROR(result = Tspi_GetPolicyObject(*srk_handle, + TSS_POLICY_USAGE, + &usage_policy))) { + TPM_LOG(ERROR, result) << __func__ + << ": Error calling Tspi_GetPolicyObject"; + return false; + } + + BYTE empty_password[] = {}; + if (TPM_ERROR(result = Tspi_Policy_SetSecret(usage_policy, + TSS_SECRET_MODE_PLAIN, + 0, empty_password))) { + TPM_LOG(ERROR, result) << __func__ + << ": Error calling Tspi_Policy_SetSecret"; + return false; + } + } + return true; +} + +bool TpmUtilityV1::LoadKeyFromBlob(const std::string& key_blob, + TSS_HCONTEXT context_handle, + TSS_HKEY parent_key_handle, + ScopedTssKey* key_handle) { + std::string mutable_key_blob(key_blob); + BYTE* key_blob_buffer = StringAsTSSBuffer(&mutable_key_blob); + TSS_RESULT result = Tspi_Context_LoadKeyByBlob( + context_handle, + parent_key_handle, + key_blob.size(), + key_blob_buffer, + key_handle->ptr()); + if (TPM_ERROR(result)) { + TPM_LOG(ERROR, result) << __func__ << ": Failed to load key by blob."; + return false; + } + return true; +} + +bool TpmUtilityV1::GetDataAttribute(TSS_HCONTEXT context, + TSS_HOBJECT object, + TSS_FLAG flag, + TSS_FLAG sub_flag, + std::string* data) { + UINT32 length = 0; + ScopedTssMemory buffer(context); + TSS_RESULT result = Tspi_GetAttribData(object, flag, sub_flag, &length, + buffer.ptr()); + if (TPM_ERROR(result)) { + TPM_LOG(ERROR, result) << __func__ << "Failed to read object attribute."; + return false; + } + data->assign(TSSBufferAsString(buffer.value(), length)); + return true; +} + +bool TpmUtilityV1::ConvertPublicKeyToDER(const std::string& public_key, + std::string* public_key_der) { + // Parse the serialized TPM_PUBKEY. + UINT64 offset = 0; + std::string mutable_public_key(public_key); + BYTE* buffer = StringAsTSSBuffer(&mutable_public_key); + TPM_PUBKEY parsed; + TSS_RESULT result = Trspi_UnloadBlob_PUBKEY(&offset, buffer, &parsed); + if (TPM_ERROR(result)) { + TPM_LOG(ERROR, result) << "Failed to parse TPM_PUBKEY."; + return false; + } + ScopedByteArray scoped_key(parsed.pubKey.key); + ScopedByteArray scoped_parms(parsed.algorithmParms.parms); + TPM_RSA_KEY_PARMS* parms = + reinterpret_cast<TPM_RSA_KEY_PARMS*>(parsed.algorithmParms.parms); + crypto::ScopedRSA rsa(RSA_new()); + CHECK(rsa.get()); + // Get the public exponent. + if (parms->exponentSize == 0) { + rsa.get()->e = BN_new(); + CHECK(rsa.get()->e); + BN_set_word(rsa.get()->e, kWellKnownExponent); + } else { + rsa.get()->e = BN_bin2bn(parms->exponent, parms->exponentSize, nullptr); + CHECK(rsa.get()->e); + } + // Get the modulus. + rsa.get()->n = BN_bin2bn(parsed.pubKey.key, parsed.pubKey.keyLength, nullptr); + CHECK(rsa.get()->n); + + // DER encode. + int der_length = i2d_RSAPublicKey(rsa.get(), nullptr); + if (der_length < 0) { + LOG(ERROR) << "Failed to DER-encode public key."; + return false; + } + public_key_der->resize(der_length); + unsigned char* der_buffer = reinterpret_cast<unsigned char*>( + string_as_array(public_key_der)); + der_length = i2d_RSAPublicKey(rsa.get(), &der_buffer); + if (der_length < 0) { + LOG(ERROR) << "Failed to DER-encode public key."; + return false; + } + public_key_der->resize(der_length); + return true; +} + +} // namespace attestation diff --git a/attestation/common/tpm_utility_v1.h b/attestation/common/tpm_utility_v1.h new file mode 100644 index 0000000..6cabbcc --- /dev/null +++ b/attestation/common/tpm_utility_v1.h @@ -0,0 +1,120 @@ +// +// 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. +// + +#ifndef ATTESTATION_COMMON_TPM_UTILITY_V1_H_ +#define ATTESTATION_COMMON_TPM_UTILITY_V1_H_ + +#include "attestation/common/tpm_utility.h" + +#include <string> + +#include <base/macros.h> +#include <trousers/scoped_tss_type.h> +#include <trousers/tss.h> + +namespace attestation { + +// A TpmUtility implementation for TPM v1.2 modules. +class TpmUtilityV1 : public TpmUtility { + public: + TpmUtilityV1() = default; + ~TpmUtilityV1() override; + + // Initializes a TpmUtilityV1 instance. This method must be called + // successfully before calling any other methods. + bool Initialize(); + + // TpmUtility methods. + bool IsTpmReady() override; + bool ActivateIdentity(const std::string& delegate_blob, + const std::string& delegate_secret, + const std::string& identity_key_blob, + const std::string& asym_ca_contents, + const std::string& sym_ca_attestation, + std::string* credential) override; + bool CreateCertifiedKey(KeyType key_type, + KeyUsage key_usage, + const std::string& identity_key_blob, + const std::string& external_data, + std::string* key_blob, + std::string* public_key, + std::string* public_key_tpm_format, + std::string* key_info, + std::string* proof) override; + bool SealToPCR0(const std::string& data, std::string* sealed_data) override; + bool Unseal(const std::string& sealed_data, std::string* data) override; + bool GetEndorsementPublicKey(std::string* public_key) override; + 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 + // its matching TPM object iff the context can be created and a TPM object + // exists in the TSS. Returns true on success. + bool ConnectContext(trousers::ScopedTssContext* context_handle, + TSS_HTPM* tpm_handle); + + // Populates |context_handle| with a valid TSS_HCONTEXT and |tpm_handle| with + // its matching TPM object authorized by the given |delegate_blob| and + // |delegate_secret|. Returns true on success. + bool ConnectContextAsDelegate(const std::string& delegate_blob, + const std::string& delegate_secret, + trousers::ScopedTssContext* context, + TSS_HTPM* tpm); + + // Sets up srk_handle_ if necessary. Returns true iff the SRK is ready. + bool SetupSrk(); + + // Loads the storage root key (SRK) and populates |srk_handle|. The + // |context_handle| must be connected and valid. Returns true on success. + bool LoadSrk(TSS_HCONTEXT context_handle, trousers::ScopedTssKey* srk_handle); + + // Loads a key in the TPM given a |key_blob| and a |parent_key_handle|. The + // |context_handle| must be connected and valid. Returns true and populates + // |key_handle| on success. + bool LoadKeyFromBlob(const std::string& key_blob, + TSS_HCONTEXT context_handle, + TSS_HKEY parent_key_handle, + trousers::ScopedTssKey* key_handle); + + // Retrieves a |data| attribute defined by |flag| and |sub_flag| from a TSS + // |object_handle|. The |context_handle| is only used for TSS memory + // management. + bool GetDataAttribute(TSS_HCONTEXT context_handle, + TSS_HOBJECT object_handle, + TSS_FLAG flag, + TSS_FLAG sub_flag, + std::string* data); + + // Converts a public in TPM_PUBKEY format to a DER-encoded RSAPublicKey. + bool ConvertPublicKeyToDER(const std::string& public_key, + std::string* public_key_der); + + bool is_ready_{false}; + trousers::ScopedTssContext context_handle_; + TSS_HTPM tpm_handle_{0}; + trousers::ScopedTssKey srk_handle_{0}; + + DISALLOW_COPY_AND_ASSIGN(TpmUtilityV1); +}; + +} // namespace attestation + +#endif // ATTESTATION_COMMON_TPM_UTILITY_V1_H_ diff --git a/attestation/server/attestation_service.cc b/attestation/server/attestation_service.cc new file mode 100644 index 0000000..d7f7974 --- /dev/null +++ b/attestation/server/attestation_service.cc @@ -0,0 +1,937 @@ +// +// 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 diff --git a/attestation/server/attestation_service.h b/attestation/server/attestation_service.h new file mode 100644 index 0000000..8426272 --- /dev/null +++ b/attestation/server/attestation_service.h @@ -0,0 +1,313 @@ +// +// 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. +// + +#ifndef ATTESTATION_SERVER_ATTESTATION_SERVICE_H_ +#define ATTESTATION_SERVER_ATTESTATION_SERVICE_H_ + +#include "attestation/common/attestation_interface.h" + +#include <memory> +#include <string> + +#include <base/callback.h> +#include <base/macros.h> +#include <base/memory/weak_ptr.h> +#include <base/threading/thread.h> +#include <brillo/bind_lambda.h> +#include <brillo/http/http_transport.h> + +#include "attestation/common/crypto_utility.h" +#include "attestation/common/crypto_utility_impl.h" +#include "attestation/common/tpm_utility.h" +#include "attestation/common/tpm_utility_v1.h" +#include "attestation/server/database.h" +#include "attestation/server/database_impl.h" +#include "attestation/server/key_store.h" +#include "attestation/server/pkcs11_key_store.h" + +namespace attestation { + +// An implementation of AttestationInterface for the core attestation service. +// Access to TPM, network and local file-system resources occurs asynchronously +// with the exception of Initialize(). All methods must be called on the same +// thread that originally called Initialize(). +// Usage: +// std::unique_ptr<AttestationInterface> attestation = +// new AttestationService(); +// CHECK(attestation->Initialize()); +// attestation->CreateGoogleAttestedKey(...); +// +// THREADING NOTES: +// This class runs a worker thread and delegates all calls to it. This keeps the +// public methods non-blocking while allowing complex implementation details +// with dependencies on the TPM, network, and filesystem to be coded in a more +// readable way. It also serves to serialize method execution which reduces +// complexity with TPM state. +// +// Tasks that run on the worker thread are bound with base::Unretained which is +// safe because the thread is owned by this class (so it is guaranteed not to +// process a task after destruction). Weak pointers are used to post replies +// back to the main thread. +class AttestationService : public AttestationInterface { + public: + AttestationService(); + ~AttestationService() override = default; + + // AttestationInterface methods. + bool Initialize() override; + void CreateGoogleAttestedKey( + const CreateGoogleAttestedKeyRequest& request, + const CreateGoogleAttestedKeyCallback& callback) override; + void GetKeyInfo(const GetKeyInfoRequest& request, + const GetKeyInfoCallback& callback) override; + void GetEndorsementInfo(const GetEndorsementInfoRequest& request, + const GetEndorsementInfoCallback& callback) override; + void GetAttestationKeyInfo( + const GetAttestationKeyInfoRequest& request, + const GetAttestationKeyInfoCallback& callback) override; + void ActivateAttestationKey( + const ActivateAttestationKeyRequest& request, + const ActivateAttestationKeyCallback& callback) override; + void CreateCertifiableKey( + const CreateCertifiableKeyRequest& request, + const CreateCertifiableKeyCallback& callback) override; + void Decrypt(const DecryptRequest& request, + const DecryptCallback& callback) override; + void Sign(const SignRequest& request, const SignCallback& callback) override; + void RegisterKeyWithChapsToken( + const RegisterKeyWithChapsTokenRequest& request, + const RegisterKeyWithChapsTokenCallback& callback) override; + + // Mutators useful for testing. + void set_crypto_utility(CryptoUtility* crypto_utility) { + crypto_utility_ = crypto_utility; + } + + void set_database(Database* database) { + database_ = database; + } + + void set_http_transport( + const std::shared_ptr<brillo::http::Transport>& transport) { + http_transport_ = transport; + } + + void set_key_store(KeyStore* key_store) { + key_store_ = key_store; + } + + void set_tpm_utility(TpmUtility* tpm_utility) { + tpm_utility_ = tpm_utility; + } + + // So tests don't need to duplicate URL decisions. + const std::string& attestation_ca_origin() { + return attestation_ca_origin_; + } + + private: + enum ACARequestType { + kEnroll, // Enrolls a device, certifying an identity key. + kGetCertificate, // Issues a certificate for a TPM-backed key. + }; + + // A relay callback which allows the use of weak pointer semantics for a reply + // to TaskRunner::PostTaskAndReply. + template<typename ReplyProtobufType> + void TaskRelayCallback( + const base::Callback<void(const ReplyProtobufType&)> callback, + const std::shared_ptr<ReplyProtobufType>& reply) { + callback.Run(*reply); + } + + // A blocking implementation of CreateGoogleAttestedKey appropriate to run on + // the worker thread. + void CreateGoogleAttestedKeyTask( + const CreateGoogleAttestedKeyRequest& request, + const std::shared_ptr<CreateGoogleAttestedKeyReply>& result); + + // A blocking implementation of GetKeyInfo. + void GetKeyInfoTask( + const GetKeyInfoRequest& request, + const std::shared_ptr<GetKeyInfoReply>& result); + + // A blocking implementation of GetEndorsementInfo. + void GetEndorsementInfoTask( + const GetEndorsementInfoRequest& request, + const std::shared_ptr<GetEndorsementInfoReply>& result); + + // A blocking implementation of GetAttestationKeyInfo. + void GetAttestationKeyInfoTask( + const GetAttestationKeyInfoRequest& request, + const std::shared_ptr<GetAttestationKeyInfoReply>& result); + + // A blocking implementation of ActivateAttestationKey. + void ActivateAttestationKeyTask( + const ActivateAttestationKeyRequest& request, + const std::shared_ptr<ActivateAttestationKeyReply>& result); + + // A blocking implementation of CreateCertifiableKey. + void CreateCertifiableKeyTask( + const CreateCertifiableKeyRequest& request, + const std::shared_ptr<CreateCertifiableKeyReply>& result); + + // 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); + + // A synchronous implementation of RegisterKeyWithChapsToken. + void RegisterKeyWithChapsTokenTask( + const RegisterKeyWithChapsTokenRequest& request, + const std::shared_ptr<RegisterKeyWithChapsTokenReply>& result); + + // Returns true iff all information required for enrollment with the Google + // Attestation CA is available. + bool IsPreparedForEnrollment(); + + // Returns true iff enrollment with the Google Attestation CA has been + // completed. + bool IsEnrolled(); + + // Creates an enrollment request compatible with the Google Attestation CA. + // Returns true on success. + bool CreateEnrollRequest(std::string* enroll_request); + + // Finishes enrollment given an |enroll_response| from the Google Attestation + // CA. Returns true on success. On failure, returns false and sets + // |server_error| to the error string from the CA. + bool FinishEnroll(const std::string& enroll_response, + std::string* server_error); + + // Creates a |certificate_request| compatible with the Google Attestation CA + // for the given |key|, according to the given |profile|, |username| and + // |origin|. + bool CreateCertificateRequest(const std::string& username, + const CertifiedKey& key, + CertificateProfile profile, + const std::string& origin, + std::string* certificate_request, + std::string* message_id); + + // Finishes a certificate request by decoding the |certificate_response| to + // recover the |certificate_chain| and storing it in association with the + // |key| identified by |username| and |key_label|. Returns true on success. On + // failure, returns false and sets |server_error| to the error string from the + // CA. + bool 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); + + // Sends a |request_type| |request| to the Google Attestation CA and waits for + // the |reply|. Returns true on success. + bool SendACARequestAndBlock(ACARequestType request_type, + const std::string& request, + std::string* reply); + + // Creates, certifies, and saves a new |key| for |username| with the given + // |key_label|, |key_type|, and |key_usage|. Returns true on success. + bool CreateKey(const std::string& username, + const std::string& key_label, + KeyType key_type, + KeyUsage key_usage, + CertifiedKey* key); + + // Finds the |key| associated with |username| and |key_label|. Returns false + // if such a key does not exist. + bool FindKeyByLabel(const std::string& username, + const std::string& key_label, + CertifiedKey* key); + + // Saves the |key| associated with |username| and |key_label|. Returns true on + // success. + bool SaveKey(const std::string& username, + const std::string& key_label, + const CertifiedKey& key); + + // Deletes the key associated with |username| and |key_label|. + void DeleteKey(const std::string& username, + const std::string& key_label); + + // Adds named device-wide key to the attestation database. + bool AddDeviceKey(const std::string& key_label, const CertifiedKey& key); + + // Removes a device-wide key from the attestation database. + void RemoveDeviceKey(const std::string& key_label); + + // Creates a PEM certificate chain from the credential fields of a |key|. + std::string CreatePEMCertificateChain(const CertifiedKey& key); + + // Creates a certificate in PEM format from a DER encoded X.509 certificate. + std::string CreatePEMCertificate(const std::string& certificate); + + // Chooses a temporal index which will be used by the ACA to create a + // certificate. This decision factors in the currently signed-in |user| and + // the |origin| of the certificate request. The strategy is to find an index + // which has not already been used by another user for the same origin. + int ChooseTemporalIndex(const std::string& user, const std::string& origin); + + // Creates a Google Attestation CA URL for the given |request_type|. + std::string GetACAURL(ACARequestType request_type) const; + + // Creates a X.509/DER SubjectPublicKeyInfo for the given |key_type| and + // |public_key|. On success returns true and provides |public_key_info|. + bool GetSubjectPublicKeyInfo(KeyType key_type, + const std::string& public_key, + std::string* public_key_info) const; + + base::WeakPtr<AttestationService> GetWeakPtr(); + + + const std::string attestation_ca_origin_; + + // Other than initialization and destruction, these are used only by the + // worker thread. + CryptoUtility* crypto_utility_{nullptr}; + Database* database_{nullptr}; + std::shared_ptr<brillo::http::Transport> http_transport_; + KeyStore* key_store_{nullptr}; + TpmUtility* tpm_utility_{nullptr}; + + // Default implementations for the above interfaces. These will be setup + // during Initialize() if the corresponding interface has not been set with a + // mutator. + std::unique_ptr<CryptoUtilityImpl> default_crypto_utility_; + std::unique_ptr<DatabaseImpl> default_database_; + std::unique_ptr<Pkcs11KeyStore> default_key_store_; + std::unique_ptr<chaps::TokenManagerClient> pkcs11_token_manager_; + std::unique_ptr<TpmUtilityV1> default_tpm_utility_; + + // All work is done in the background. This serves to serialize requests and + // allow synchronous implementation of complex methods. This is intentionally + // declared after the thread-owned members. + std::unique_ptr<base::Thread> worker_thread_; + + // Declared last so any weak pointers are destroyed first. + base::WeakPtrFactory<AttestationService> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(AttestationService); +}; + +} // namespace attestation + +#endif // ATTESTATION_SERVER_ATTESTATION_SERVICE_H_ diff --git a/attestation/server/attestation_service_test.cc b/attestation/server/attestation_service_test.cc new file mode 100644 index 0000000..494904b --- /dev/null +++ b/attestation/server/attestation_service_test.cc @@ -0,0 +1,1190 @@ +// +// 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 <string> + +#include <base/bind.h> +#include <base/callback.h> +#include <base/message_loop/message_loop.h> +#include <base/run_loop.h> +#include <brillo/bind_lambda.h> +#include <brillo/data_encoding.h> +#include <brillo/http/http_transport_fake.h> +#include <brillo/mime_utils.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "attestation/common/attestation_ca.pb.h" +#include "attestation/common/mock_crypto_utility.h" +#include "attestation/common/mock_tpm_utility.h" +#include "attestation/server/attestation_service.h" +#include "attestation/server/mock_database.h" +#include "attestation/server/mock_key_store.h" + +using brillo::http::fake::ServerRequest; +using brillo::http::fake::ServerResponse; +using testing::_; +using testing::DoAll; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; +using testing::SetArgumentPointee; + +namespace attestation { + +class AttestationServiceTest : public testing::Test { + public: + enum FakeCAState { + kSuccess, // Valid successful response. + kCommandFailure, // Valid error response. + kHttpFailure, // Responds with an HTTP error. + kBadMessageID, // Valid successful response but a message ID mismatch. + }; + + ~AttestationServiceTest() override = default; + void SetUp() override { + service_.reset(new AttestationService); + service_->set_database(&mock_database_); + service_->set_crypto_utility(&mock_crypto_utility_); + fake_http_transport_ = std::make_shared<brillo::http::fake::Transport>(); + service_->set_http_transport(fake_http_transport_); + service_->set_key_store(&mock_key_store_); + service_->set_tpm_utility(&mock_tpm_utility_); + // Setup a fake wrapped EK certificate by default. + mock_database_.GetMutableProtobuf()->mutable_credentials()-> + mutable_default_encrypted_endorsement_credential()-> + set_wrapping_key_id("default"); + // Setup a fake Attestation CA for success by default. + SetupFakeCAEnroll(kSuccess); + SetupFakeCASign(kSuccess); + CHECK(service_->Initialize()); + } + + protected: + void SetupFakeCAEnroll(FakeCAState state) { + fake_http_transport_->AddHandler( + service_->attestation_ca_origin() + "/enroll", + brillo::http::request_type::kPost, + base::Bind(&AttestationServiceTest::FakeCAEnroll, + base::Unretained(this), + state)); + } + + void SetupFakeCASign(FakeCAState state) { + fake_http_transport_->AddHandler( + service_->attestation_ca_origin() + "/sign", + brillo::http::request_type::kPost, + base::Bind(&AttestationServiceTest::FakeCASign, + base::Unretained(this), + state)); + } + + std::string GetFakeCertificateChain() { + const std::string kBeginCertificate = "-----BEGIN CERTIFICATE-----\n"; + const std::string kEndCertificate = "-----END CERTIFICATE-----"; + std::string pem = kBeginCertificate; + pem += brillo::data_encoding::Base64EncodeWrapLines("fake_cert"); + pem += kEndCertificate + "\n" + kBeginCertificate; + pem += brillo::data_encoding::Base64EncodeWrapLines("fake_ca_cert"); + pem += kEndCertificate + "\n" + kBeginCertificate; + pem += brillo::data_encoding::Base64EncodeWrapLines("fake_ca_cert2"); + pem += kEndCertificate; + return pem; + } + + CreateGoogleAttestedKeyRequest GetCreateRequest() { + CreateGoogleAttestedKeyRequest request; + request.set_key_label("label"); + request.set_key_type(KEY_TYPE_ECC); + request.set_key_usage(KEY_USAGE_SIGN); + request.set_certificate_profile(ENTERPRISE_MACHINE_CERTIFICATE); + request.set_username("user"); + request.set_origin("origin"); + return request; + } + + void Run() { + run_loop_.Run(); + } + + void RunUntilIdle() { + run_loop_.RunUntilIdle(); + } + + void Quit() { + run_loop_.Quit(); + } + + std::shared_ptr<brillo::http::fake::Transport> fake_http_transport_; + NiceMock<MockCryptoUtility> mock_crypto_utility_; + NiceMock<MockDatabase> mock_database_; + NiceMock<MockKeyStore> mock_key_store_; + NiceMock<MockTpmUtility> mock_tpm_utility_; + std::unique_ptr<AttestationService> service_; + + private: + void FakeCAEnroll(FakeCAState state, + const ServerRequest& request, + ServerResponse* response) { + AttestationEnrollmentRequest request_pb; + EXPECT_TRUE(request_pb.ParseFromString(request.GetDataAsString())); + if (state == kHttpFailure) { + response->ReplyText(brillo::http::status_code::NotFound, std::string(), + brillo::mime::application::kOctet_stream); + return; + } + AttestationEnrollmentResponse response_pb; + if (state == kCommandFailure) { + response_pb.set_status(SERVER_ERROR); + response_pb.set_detail("fake_enroll_error"); + } else if (state == kSuccess) { + response_pb.set_status(OK); + response_pb.set_detail(""); + response_pb.mutable_encrypted_identity_credential()-> + set_asym_ca_contents("1234"); + response_pb.mutable_encrypted_identity_credential()-> + set_sym_ca_attestation("5678"); + } else { + NOTREACHED(); + } + std::string tmp; + response_pb.SerializeToString(&tmp); + response->ReplyText(brillo::http::status_code::Ok, tmp, + brillo::mime::application::kOctet_stream); + } + + void FakeCASign(FakeCAState state, + const ServerRequest& request, + ServerResponse* response) { + AttestationCertificateRequest request_pb; + EXPECT_TRUE(request_pb.ParseFromString(request.GetDataAsString())); + if (state == kHttpFailure) { + response->ReplyText(brillo::http::status_code::NotFound, std::string(), + brillo::mime::application::kOctet_stream); + return; + } + AttestationCertificateResponse response_pb; + if (state == kCommandFailure) { + response_pb.set_status(SERVER_ERROR); + response_pb.set_detail("fake_sign_error"); + } else if (state == kSuccess || state == kBadMessageID) { + response_pb.set_status(OK); + response_pb.set_detail(""); + if (state == kSuccess) { + response_pb.set_message_id(request_pb.message_id()); + } + response_pb.set_certified_key_credential("fake_cert"); + response_pb.set_intermediate_ca_cert("fake_ca_cert"); + *response_pb.add_additional_intermediate_ca_cert() = "fake_ca_cert2"; + } + std::string tmp; + response_pb.SerializeToString(&tmp); + response->ReplyText(brillo::http::status_code::Ok, tmp, + brillo::mime::application::kOctet_stream); + } + + base::MessageLoop message_loop_; + base::RunLoop run_loop_; +}; + +TEST_F(AttestationServiceTest, CreateGoogleAttestedKeySuccess) { + // Set expectations on the outputs. + auto callback = [this](const CreateGoogleAttestedKeyReply& reply) { + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ(GetFakeCertificateChain(), reply.certificate_chain()); + EXPECT_FALSE(reply.has_server_error()); + Quit(); + }; + service_->CreateGoogleAttestedKey(GetCreateRequest(), base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateGoogleAttestedKeySuccessNoUser) { + // Set expectations on the outputs. + auto callback = [this](const CreateGoogleAttestedKeyReply& reply) { + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ(GetFakeCertificateChain(), reply.certificate_chain()); + EXPECT_FALSE(reply.has_server_error()); + Quit(); + }; + CreateGoogleAttestedKeyRequest request = GetCreateRequest(); + request.clear_username(); + service_->CreateGoogleAttestedKey(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateGoogleAttestedKeyWithEnrollHttpError) { + SetupFakeCAEnroll(kHttpFailure); + // Set expectations on the outputs. + auto callback = [this](const CreateGoogleAttestedKeyReply& reply) { + EXPECT_EQ(STATUS_CA_NOT_AVAILABLE, reply.status()); + EXPECT_FALSE(reply.has_certificate_chain()); + EXPECT_EQ("", reply.server_error()); + Quit(); + }; + service_->CreateGoogleAttestedKey(GetCreateRequest(), base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateGoogleAttestedKeyWithSignHttpError) { + SetupFakeCASign(kHttpFailure); + // Set expectations on the outputs. + auto callback = [this](const CreateGoogleAttestedKeyReply& reply) { + EXPECT_EQ(STATUS_CA_NOT_AVAILABLE, reply.status()); + EXPECT_FALSE(reply.has_certificate_chain()); + EXPECT_EQ("", reply.server_error()); + Quit(); + }; + service_->CreateGoogleAttestedKey(GetCreateRequest(), base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateGoogleAttestedKeyWithCAEnrollFailure) { + SetupFakeCAEnroll(kCommandFailure); + // Set expectations on the outputs. + auto callback = [this](const CreateGoogleAttestedKeyReply& reply) { + EXPECT_EQ(STATUS_REQUEST_DENIED_BY_CA, reply.status()); + EXPECT_FALSE(reply.has_certificate_chain()); + EXPECT_EQ("fake_enroll_error", reply.server_error()); + Quit(); + }; + service_->CreateGoogleAttestedKey(GetCreateRequest(), base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateGoogleAttestedKeyWithCASignFailure) { + SetupFakeCASign(kCommandFailure); + // Set expectations on the outputs. + auto callback = [this](const CreateGoogleAttestedKeyReply& reply) { + EXPECT_EQ(STATUS_REQUEST_DENIED_BY_CA, reply.status()); + EXPECT_FALSE(reply.has_certificate_chain()); + EXPECT_EQ("fake_sign_error", reply.server_error()); + Quit(); + }; + service_->CreateGoogleAttestedKey(GetCreateRequest(), base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateGoogleAttestedKeyWithBadCAMessageID) { + SetupFakeCASign(kBadMessageID); + // Set expectations on the outputs. + auto callback = [this](const CreateGoogleAttestedKeyReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + EXPECT_FALSE(reply.has_certificate_chain()); + EXPECT_EQ("", reply.server_error()); + Quit(); + }; + service_->CreateGoogleAttestedKey(GetCreateRequest(), base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateGoogleAttestedKeyWithNoEKCertificate) { + // Remove the default credential setup. + mock_database_.GetMutableProtobuf()->clear_credentials(); + // Set expectations on the outputs. + auto callback = [this](const CreateGoogleAttestedKeyReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + EXPECT_FALSE(reply.has_certificate_chain()); + EXPECT_EQ("", reply.server_error()); + Quit(); + }; + service_->CreateGoogleAttestedKey(GetCreateRequest(), base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateGoogleAttestedKeyWithRNGFailure) { + EXPECT_CALL(mock_crypto_utility_, GetRandom(_, _)) + .WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const CreateGoogleAttestedKeyReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + EXPECT_FALSE(reply.has_certificate_chain()); + EXPECT_EQ("", reply.server_error()); + Quit(); + }; + service_->CreateGoogleAttestedKey(GetCreateRequest(), base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateGoogleAttestedKeyWithRNGFailure2) { + EXPECT_CALL(mock_crypto_utility_, GetRandom(_, _)) + .WillOnce(Return(true)) + .WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const CreateGoogleAttestedKeyReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + EXPECT_FALSE(reply.has_certificate_chain()); + EXPECT_EQ("", reply.server_error()); + Quit(); + }; + service_->CreateGoogleAttestedKey(GetCreateRequest(), base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateGoogleAttestedKeyWithDBFailure) { + EXPECT_CALL(mock_database_, SaveChanges()).WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const CreateGoogleAttestedKeyReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + EXPECT_FALSE(reply.has_certificate_chain()); + EXPECT_EQ("", reply.server_error()); + Quit(); + }; + service_->CreateGoogleAttestedKey(GetCreateRequest(), base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateGoogleAttestedKeyWithDBFailureNoUser) { + EXPECT_CALL(mock_database_, SaveChanges()).WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const CreateGoogleAttestedKeyReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + EXPECT_FALSE(reply.has_certificate_chain()); + EXPECT_EQ("", reply.server_error()); + Quit(); + }; + CreateGoogleAttestedKeyRequest request = GetCreateRequest(); + request.clear_username(); + service_->CreateGoogleAttestedKey(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateGoogleAttestedKeyWithKeyWriteFailure) { + EXPECT_CALL(mock_key_store_, Write(_, _, _)) + .WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const CreateGoogleAttestedKeyReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + EXPECT_FALSE(reply.has_certificate_chain()); + EXPECT_EQ("", reply.server_error()); + Quit(); + }; + service_->CreateGoogleAttestedKey(GetCreateRequest(), base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateGoogleAttestedKeyWithTpmNotReady) { + EXPECT_CALL(mock_tpm_utility_, IsTpmReady()) + .WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const CreateGoogleAttestedKeyReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + EXPECT_FALSE(reply.has_certificate_chain()); + EXPECT_EQ("", reply.server_error()); + Quit(); + }; + service_->CreateGoogleAttestedKey(GetCreateRequest(), base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateGoogleAttestedKeyWithTpmActivateFailure) { + EXPECT_CALL(mock_tpm_utility_, ActivateIdentity(_, _, _, _, _, _)) + .WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const CreateGoogleAttestedKeyReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + EXPECT_FALSE(reply.has_certificate_chain()); + EXPECT_EQ("", reply.server_error()); + Quit(); + }; + service_->CreateGoogleAttestedKey(GetCreateRequest(), base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateGoogleAttestedKeyWithTpmCreateFailure) { + EXPECT_CALL(mock_tpm_utility_, CreateCertifiedKey(_, _, _, _, _, _, _, _, _)) + .WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const CreateGoogleAttestedKeyReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + EXPECT_FALSE(reply.has_certificate_chain()); + EXPECT_EQ("", reply.server_error()); + Quit(); + }; + service_->CreateGoogleAttestedKey(GetCreateRequest(), base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateGoogleAttestedKeyAndCancel) { + // Set expectations on the outputs. + int callback_count = 0; + auto callback = [&callback_count](const CreateGoogleAttestedKeyReply& reply) { + callback_count++; + }; + service_->CreateGoogleAttestedKey(GetCreateRequest(), base::Bind(callback)); + // Bring down the service, which should cancel any callbacks. + service_.reset(); + EXPECT_EQ(0, callback_count); +} + +TEST_F(AttestationServiceTest, CreateGoogleAttestedKeyAndCancel2) { + // Set expectations on the outputs. + int callback_count = 0; + auto callback = [&callback_count](const CreateGoogleAttestedKeyReply& reply) { + callback_count++; + }; + service_->CreateGoogleAttestedKey(GetCreateRequest(), base::Bind(callback)); + // Give threads a chance to run. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); + // Bring down the service, which should cancel any callbacks. + service_.reset(); + // Pump the loop to make sure no callbacks were posted. + RunUntilIdle(); + EXPECT_EQ(0, callback_count); +} + +TEST_F(AttestationServiceTest, GetKeyInfoSuccess) { + // Setup a certified key in the key store. + CertifiedKey key; + key.set_public_key("public_key"); + key.set_certified_key_credential("fake_cert"); + key.set_intermediate_ca_cert("fake_ca_cert"); + *key.add_additional_intermediate_ca_cert() = "fake_ca_cert2"; + key.set_key_name("label"); + key.set_certified_key_info("certify_info"); + key.set_certified_key_proof("signature"); + key.set_key_type(KEY_TYPE_RSA); + key.set_key_usage(KEY_USAGE_SIGN); + std::string key_bytes; + key.SerializeToString(&key_bytes); + EXPECT_CALL(mock_key_store_, Read("user", "label", _)) + .WillOnce(DoAll(SetArgumentPointee<2>(key_bytes), Return(true))); + + // Set expectations on the outputs. + auto callback = [this](const GetKeyInfoReply& reply) { + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ(KEY_TYPE_RSA, reply.key_type()); + EXPECT_EQ(KEY_USAGE_SIGN, reply.key_usage()); + EXPECT_EQ("public_key", reply.public_key()); + EXPECT_EQ("certify_info", reply.certify_info()); + EXPECT_EQ("signature", reply.certify_info_signature()); + EXPECT_EQ(GetFakeCertificateChain(), reply.certificate()); + Quit(); + }; + GetKeyInfoRequest request; + request.set_key_label("label"); + request.set_username("user"); + service_->GetKeyInfo(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, GetKeyInfoSuccessNoUser) { + // Setup a certified key in the device key store. + CertifiedKey& key = *mock_database_.GetMutableProtobuf()->add_device_keys(); + key.set_public_key("public_key"); + key.set_certified_key_credential("fake_cert"); + key.set_intermediate_ca_cert("fake_ca_cert"); + *key.add_additional_intermediate_ca_cert() = "fake_ca_cert2"; + key.set_key_name("label"); + key.set_certified_key_info("certify_info"); + key.set_certified_key_proof("signature"); + key.set_key_type(KEY_TYPE_RSA); + key.set_key_usage(KEY_USAGE_SIGN); + + // Set expectations on the outputs. + auto callback = [this](const GetKeyInfoReply& reply) { + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ(KEY_TYPE_RSA, reply.key_type()); + EXPECT_EQ(KEY_USAGE_SIGN, reply.key_usage()); + EXPECT_EQ("public_key", reply.public_key()); + EXPECT_EQ("certify_info", reply.certify_info()); + EXPECT_EQ("signature", reply.certify_info_signature()); + EXPECT_EQ(GetFakeCertificateChain(), reply.certificate()); + Quit(); + }; + GetKeyInfoRequest request; + request.set_key_label("label"); + service_->GetKeyInfo(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, GetKeyInfoNoKey) { + EXPECT_CALL(mock_key_store_, Read("user", "label", _)) + .WillRepeatedly(Return(false)); + + // Set expectations on the outputs. + auto callback = [this](const GetKeyInfoReply& reply) { + EXPECT_EQ(STATUS_INVALID_PARAMETER, reply.status()); + Quit(); + }; + GetKeyInfoRequest request; + request.set_key_label("label"); + request.set_username("user"); + service_->GetKeyInfo(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, GetKeyInfoBadPublicKey) { + EXPECT_CALL(mock_crypto_utility_, GetRSASubjectPublicKeyInfo(_, _)) + .WillRepeatedly(Return(false)); + + // Set expectations on the outputs. + auto callback = [this](const GetKeyInfoReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + Quit(); + }; + GetKeyInfoRequest request; + request.set_key_label("label"); + request.set_username("user"); + service_->GetKeyInfo(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, GetEndorsementInfoSuccess) { + AttestationDatabase* database = mock_database_.GetMutableProtobuf(); + database->mutable_credentials()->set_endorsement_public_key("public_key"); + database->mutable_credentials()->set_endorsement_credential("certificate"); + // Set expectations on the outputs. + auto callback = [this](const GetEndorsementInfoReply& reply) { + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ("public_key", reply.ek_public_key()); + EXPECT_EQ("certificate", reply.ek_certificate()); + Quit(); + }; + GetEndorsementInfoRequest request; + request.set_key_type(KEY_TYPE_RSA); + service_->GetEndorsementInfo(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, GetEndorsementInfoNoInfo) { + // Set expectations on the outputs. + auto callback = [this](const GetEndorsementInfoReply& reply) { + EXPECT_EQ(STATUS_NOT_AVAILABLE, reply.status()); + EXPECT_FALSE(reply.has_ek_public_key()); + EXPECT_FALSE(reply.has_ek_certificate()); + Quit(); + }; + GetEndorsementInfoRequest request; + request.set_key_type(KEY_TYPE_RSA); + service_->GetEndorsementInfo(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, GetEndorsementInfoNoCert) { + AttestationDatabase* database = mock_database_.GetMutableProtobuf(); + database->mutable_credentials()->set_endorsement_public_key("public_key"); + // Set expectations on the outputs. + auto callback = [this](const GetEndorsementInfoReply& reply) { + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ("public_key", reply.ek_public_key()); + EXPECT_FALSE(reply.has_ek_certificate()); + Quit(); + }; + GetEndorsementInfoRequest request; + request.set_key_type(KEY_TYPE_RSA); + service_->GetEndorsementInfo(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, GetAttestationKeyInfoSuccess) { + AttestationDatabase* database = mock_database_.GetMutableProtobuf(); + database->mutable_identity_key()->set_identity_public_key("public_key"); + database->mutable_identity_key()->set_identity_credential("certificate"); + database->mutable_pcr0_quote()->set_quote("pcr0"); + database->mutable_pcr1_quote()->set_quote("pcr1"); + database->mutable_identity_binding()->set_identity_public_key("public_key2"); + // Set expectations on the outputs. + auto callback = [this](const GetAttestationKeyInfoReply& reply) { + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ("public_key", reply.public_key()); + EXPECT_EQ("public_key2", reply.public_key_tpm_format()); + EXPECT_EQ("certificate", reply.certificate()); + EXPECT_EQ("pcr0", reply.pcr0_quote().quote()); + EXPECT_EQ("pcr1", reply.pcr1_quote().quote()); + Quit(); + }; + GetAttestationKeyInfoRequest request; + request.set_key_type(KEY_TYPE_RSA); + service_->GetAttestationKeyInfo(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, GetAttestationKeyInfoNoInfo) { + // Set expectations on the outputs. + auto callback = [this](const GetAttestationKeyInfoReply& reply) { + EXPECT_EQ(STATUS_NOT_AVAILABLE, reply.status()); + EXPECT_FALSE(reply.has_public_key()); + EXPECT_FALSE(reply.has_public_key_tpm_format()); + EXPECT_FALSE(reply.has_certificate()); + EXPECT_FALSE(reply.has_pcr0_quote()); + EXPECT_FALSE(reply.has_pcr1_quote()); + Quit(); + }; + GetAttestationKeyInfoRequest request; + request.set_key_type(KEY_TYPE_RSA); + service_->GetAttestationKeyInfo(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, GetAttestationKeyInfoSomeInfo) { + AttestationDatabase* database = mock_database_.GetMutableProtobuf(); + database->mutable_identity_key()->set_identity_credential("certificate"); + database->mutable_pcr1_quote()->set_quote("pcr1"); + // Set expectations on the outputs. + auto callback = [this](const GetAttestationKeyInfoReply& reply) { + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_FALSE(reply.has_public_key()); + EXPECT_FALSE(reply.has_public_key_tpm_format()); + EXPECT_EQ("certificate", reply.certificate()); + EXPECT_FALSE(reply.has_pcr0_quote()); + EXPECT_EQ("pcr1", reply.pcr1_quote().quote()); + Quit(); + }; + GetAttestationKeyInfoRequest request; + request.set_key_type(KEY_TYPE_RSA); + service_->GetAttestationKeyInfo(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, ActivateAttestationKeySuccess) { + EXPECT_CALL(mock_database_, SaveChanges()).Times(1); + EXPECT_CALL(mock_tpm_utility_, ActivateIdentity(_, _, _, "encrypted1", + "encrypted2", _)) + .WillOnce(DoAll(SetArgumentPointee<5>(std::string("certificate")), + Return(true))); + // Set expectations on the outputs. + auto callback = [this](const ActivateAttestationKeyReply& reply) { + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ("certificate", reply.certificate()); + Quit(); + }; + ActivateAttestationKeyRequest request; + request.set_key_type(KEY_TYPE_RSA); + request.mutable_encrypted_certificate()->set_asym_ca_contents("encrypted1"); + request.mutable_encrypted_certificate()->set_sym_ca_attestation("encrypted2"); + request.set_save_certificate(true); + service_->ActivateAttestationKey(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, ActivateAttestationKeySuccessNoSave) { + EXPECT_CALL(mock_database_, GetMutableProtobuf()).Times(0); + EXPECT_CALL(mock_database_, SaveChanges()).Times(0); + EXPECT_CALL(mock_tpm_utility_, ActivateIdentity(_, _, _, "encrypted1", + "encrypted2", _)) + .WillOnce(DoAll(SetArgumentPointee<5>(std::string("certificate")), + Return(true))); + // Set expectations on the outputs. + auto callback = [this](const ActivateAttestationKeyReply& reply) { + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ("certificate", reply.certificate()); + Quit(); + }; + ActivateAttestationKeyRequest request; + request.set_key_type(KEY_TYPE_RSA); + request.mutable_encrypted_certificate()->set_asym_ca_contents("encrypted1"); + request.mutable_encrypted_certificate()->set_sym_ca_attestation("encrypted2"); + request.set_save_certificate(false); + service_->ActivateAttestationKey(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, ActivateAttestationKeySaveFailure) { + EXPECT_CALL(mock_database_, SaveChanges()).WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const ActivateAttestationKeyReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + Quit(); + }; + ActivateAttestationKeyRequest request; + request.set_key_type(KEY_TYPE_RSA); + request.mutable_encrypted_certificate()->set_asym_ca_contents("encrypted1"); + request.mutable_encrypted_certificate()->set_sym_ca_attestation("encrypted2"); + request.set_save_certificate(true); + service_->ActivateAttestationKey(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, ActivateAttestationKeyActivateFailure) { + EXPECT_CALL(mock_tpm_utility_, ActivateIdentity(_, _, _, "encrypted1", + "encrypted2", _)) + .WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const ActivateAttestationKeyReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + Quit(); + }; + ActivateAttestationKeyRequest request; + request.set_key_type(KEY_TYPE_RSA); + request.mutable_encrypted_certificate()->set_asym_ca_contents("encrypted1"); + request.mutable_encrypted_certificate()->set_sym_ca_attestation("encrypted2"); + request.set_save_certificate(true); + service_->ActivateAttestationKey(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateCertifiableKeySuccess) { + // Configure a fake TPM response. + EXPECT_CALL(mock_tpm_utility_, CreateCertifiedKey(KEY_TYPE_ECC, + KEY_USAGE_SIGN, + _, _, _, _, _, _, _)) + .WillOnce(DoAll(SetArgumentPointee<5>(std::string("public_key")), + SetArgumentPointee<7>(std::string("certify_info")), + SetArgumentPointee<8>( + std::string("certify_info_signature")), + Return(true))); + // Expect the key to be written exactly once. + EXPECT_CALL(mock_key_store_, Write("user", "label", _)).Times(1); + // Set expectations on the outputs. + auto callback = [this](const CreateCertifiableKeyReply& reply) { + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ("public_key", reply.public_key()); + EXPECT_EQ("certify_info", reply.certify_info()); + EXPECT_EQ("certify_info_signature", reply.certify_info_signature()); + Quit(); + }; + CreateCertifiableKeyRequest request; + request.set_key_label("label"); + request.set_key_type(KEY_TYPE_ECC); + request.set_key_usage(KEY_USAGE_SIGN); + request.set_username("user"); + service_->CreateCertifiableKey(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateCertifiableKeySuccessNoUser) { + // Configure a fake TPM response. + EXPECT_CALL(mock_tpm_utility_, CreateCertifiedKey(KEY_TYPE_ECC, + KEY_USAGE_SIGN, + _, _, _, _, _, _, _)) + .WillOnce(DoAll(SetArgumentPointee<5>(std::string("public_key")), + SetArgumentPointee<7>(std::string("certify_info")), + SetArgumentPointee<8>( + std::string("certify_info_signature")), + Return(true))); + // Expect the key to be written exactly once. + EXPECT_CALL(mock_database_, SaveChanges()).Times(1); + // Set expectations on the outputs. + auto callback = [this](const CreateCertifiableKeyReply& reply) { + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ("public_key", reply.public_key()); + EXPECT_EQ("certify_info", reply.certify_info()); + EXPECT_EQ("certify_info_signature", reply.certify_info_signature()); + Quit(); + }; + CreateCertifiableKeyRequest request; + request.set_key_label("label"); + request.set_key_type(KEY_TYPE_ECC); + request.set_key_usage(KEY_USAGE_SIGN); + service_->CreateCertifiableKey(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateCertifiableKeyRNGFailure) { + EXPECT_CALL(mock_crypto_utility_, GetRandom(_, _)) + .WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const CreateCertifiableKeyReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + EXPECT_FALSE(reply.has_public_key()); + EXPECT_FALSE(reply.has_certify_info()); + EXPECT_FALSE(reply.has_certify_info_signature()); + Quit(); + }; + CreateCertifiableKeyRequest request; + request.set_key_label("label"); + request.set_key_type(KEY_TYPE_ECC); + request.set_key_usage(KEY_USAGE_SIGN); + service_->CreateCertifiableKey(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateCertifiableKeyTpmCreateFailure) { + EXPECT_CALL(mock_tpm_utility_, CreateCertifiedKey(_, _, _, _, _, _, _, _, _)) + .WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const CreateCertifiableKeyReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + EXPECT_FALSE(reply.has_public_key()); + EXPECT_FALSE(reply.has_certify_info()); + EXPECT_FALSE(reply.has_certify_info_signature()); + Quit(); + }; + CreateCertifiableKeyRequest request; + request.set_key_label("label"); + request.set_key_type(KEY_TYPE_ECC); + request.set_key_usage(KEY_USAGE_SIGN); + service_->CreateCertifiableKey(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateCertifiableKeyDBFailure) { + EXPECT_CALL(mock_key_store_, Write(_, _, _)).WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const CreateCertifiableKeyReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + EXPECT_FALSE(reply.has_public_key()); + EXPECT_FALSE(reply.has_certify_info()); + EXPECT_FALSE(reply.has_certify_info_signature()); + Quit(); + }; + CreateCertifiableKeyRequest request; + request.set_key_label("label"); + request.set_key_type(KEY_TYPE_ECC); + request.set_key_usage(KEY_USAGE_SIGN); + request.set_username("username"); + service_->CreateCertifiableKey(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, CreateCertifiableKeyDBFailureNoUser) { + EXPECT_CALL(mock_database_, SaveChanges()).WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const CreateCertifiableKeyReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + EXPECT_FALSE(reply.has_public_key()); + EXPECT_FALSE(reply.has_certify_info()); + EXPECT_FALSE(reply.has_certify_info_signature()); + Quit(); + }; + CreateCertifiableKeyRequest request; + request.set_key_label("label"); + request.set_key_type(KEY_TYPE_ECC); + request.set_key_usage(KEY_USAGE_SIGN); + service_->CreateCertifiableKey(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, DecryptSuccess) { + // Set expectations on the outputs. + auto callback = [this](const DecryptReply& reply) { + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ(MockTpmUtility::Transform("Unbind", "data"), + reply.decrypted_data()); + Quit(); + }; + DecryptRequest request; + request.set_key_label("label"); + request.set_username("user"); + request.set_encrypted_data("data"); + service_->Decrypt(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, DecryptSuccessNoUser) { + mock_database_.GetMutableProtobuf()->add_device_keys()->set_key_name("label"); + // Set expectations on the outputs. + auto callback = [this](const DecryptReply& reply) { + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ(MockTpmUtility::Transform("Unbind", "data"), + reply.decrypted_data()); + Quit(); + }; + DecryptRequest request; + request.set_key_label("label"); + request.set_encrypted_data("data"); + service_->Decrypt(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, DecryptKeyNotFound) { + EXPECT_CALL(mock_key_store_, Read("user", "label", _)) + .WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const DecryptReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + EXPECT_FALSE(reply.has_decrypted_data()); + Quit(); + }; + DecryptRequest request; + request.set_key_label("label"); + request.set_username("user"); + request.set_encrypted_data("data"); + service_->Decrypt(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, DecryptKeyNotFoundNoUser) { + // Set expectations on the outputs. + auto callback = [this](const DecryptReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + EXPECT_FALSE(reply.has_decrypted_data()); + Quit(); + }; + DecryptRequest request; + request.set_key_label("label"); + request.set_encrypted_data("data"); + service_->Decrypt(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, DecryptUnbindFailure) { + EXPECT_CALL(mock_tpm_utility_, Unbind(_, _, _)).WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const DecryptReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + EXPECT_FALSE(reply.has_decrypted_data()); + Quit(); + }; + DecryptRequest request; + request.set_key_label("label"); + request.set_username("user"); + request.set_encrypted_data("data"); + service_->Decrypt(request, base::Bind(callback)); + 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(); +} + +TEST_F(AttestationServiceTest, RegisterSuccess) { + // Setup a key in the user key store. + CertifiedKey key; + key.set_key_blob("key_blob"); + key.set_public_key("public_key"); + key.set_certified_key_credential("fake_cert"); + key.set_intermediate_ca_cert("fake_ca_cert"); + *key.add_additional_intermediate_ca_cert() = "fake_ca_cert2"; + key.set_key_name("label"); + key.set_key_type(KEY_TYPE_RSA); + key.set_key_usage(KEY_USAGE_SIGN); + std::string key_bytes; + key.SerializeToString(&key_bytes); + EXPECT_CALL(mock_key_store_, Read("user", "label", _)) + .WillOnce(DoAll(SetArgumentPointee<2>(key_bytes), Return(true))); + // Cardinality is verified here to verify various steps are performed and to + // catch performance regressions. + EXPECT_CALL(mock_key_store_, Register("user", + "label", + KEY_TYPE_RSA, + KEY_USAGE_SIGN, + "key_blob", + "public_key", + "fake_cert")).Times(1); + EXPECT_CALL(mock_key_store_, RegisterCertificate("user", "fake_ca_cert")) + .Times(1); + EXPECT_CALL(mock_key_store_, RegisterCertificate("user", "fake_ca_cert2")) + .Times(1); + EXPECT_CALL(mock_key_store_, Delete("user", "label")).Times(1); + // Set expectations on the outputs. + auto callback = [this](const RegisterKeyWithChapsTokenReply& reply) { + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + Quit(); + }; + RegisterKeyWithChapsTokenRequest request; + request.set_key_label("label"); + request.set_username("user"); + service_->RegisterKeyWithChapsToken(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, RegisterSuccessNoUser) { + // Setup a key in the device_keys field. + CertifiedKey& key = *mock_database_.GetMutableProtobuf()->add_device_keys(); + key.set_key_blob("key_blob"); + key.set_public_key("public_key"); + key.set_certified_key_credential("fake_cert"); + key.set_intermediate_ca_cert("fake_ca_cert"); + *key.add_additional_intermediate_ca_cert() = "fake_ca_cert2"; + key.set_key_name("label"); + key.set_key_type(KEY_TYPE_RSA); + key.set_key_usage(KEY_USAGE_SIGN); + // Cardinality is verified here to verify various steps are performed and to + // catch performance regressions. + EXPECT_CALL(mock_key_store_, Register("", + "label", + KEY_TYPE_RSA, + KEY_USAGE_SIGN, + "key_blob", + "public_key", + "fake_cert")).Times(1); + EXPECT_CALL(mock_key_store_, RegisterCertificate("", "fake_ca_cert")) + .Times(1); + EXPECT_CALL(mock_key_store_, RegisterCertificate("", "fake_ca_cert2")) + .Times(1); + // Set expectations on the outputs. + auto callback = [this](const RegisterKeyWithChapsTokenReply& reply) { + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ(0, mock_database_.GetMutableProtobuf()->device_keys_size()); + Quit(); + }; + RegisterKeyWithChapsTokenRequest request; + request.set_key_label("label"); + service_->RegisterKeyWithChapsToken(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, RegisterNoKey) { + EXPECT_CALL(mock_key_store_, Read("user", "label", _)) + .WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const RegisterKeyWithChapsTokenReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + Quit(); + }; + RegisterKeyWithChapsTokenRequest request; + request.set_key_label("label"); + request.set_username("user"); + service_->RegisterKeyWithChapsToken(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, RegisterNoKeyNoUser) { + // Set expectations on the outputs. + auto callback = [this](const RegisterKeyWithChapsTokenReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + Quit(); + }; + RegisterKeyWithChapsTokenRequest request; + request.set_key_label("label"); + service_->RegisterKeyWithChapsToken(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, RegisterFailure) { + // Setup a key in the user key store. + CertifiedKey key; + key.set_key_name("label"); + std::string key_bytes; + key.SerializeToString(&key_bytes); + EXPECT_CALL(mock_key_store_, Read("user", "label", _)) + .WillOnce(DoAll(SetArgumentPointee<2>(key_bytes), Return(true))); + EXPECT_CALL(mock_key_store_, Register(_, _, _, _, _, _, _)) + .WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const RegisterKeyWithChapsTokenReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + Quit(); + }; + RegisterKeyWithChapsTokenRequest request; + request.set_key_label("label"); + request.set_username("user"); + service_->RegisterKeyWithChapsToken(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, RegisterIntermediateFailure) { + // Setup a key in the user key store. + CertifiedKey key; + key.set_key_name("label"); + key.set_intermediate_ca_cert("fake_ca_cert"); + std::string key_bytes; + key.SerializeToString(&key_bytes); + EXPECT_CALL(mock_key_store_, Read("user", "label", _)) + .WillOnce(DoAll(SetArgumentPointee<2>(key_bytes), Return(true))); + EXPECT_CALL(mock_key_store_, RegisterCertificate(_, _)) + .WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const RegisterKeyWithChapsTokenReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + Quit(); + }; + RegisterKeyWithChapsTokenRequest request; + request.set_key_label("label"); + request.set_username("user"); + service_->RegisterKeyWithChapsToken(request, base::Bind(callback)); + Run(); +} + +TEST_F(AttestationServiceTest, RegisterAdditionalFailure) { + // Setup a key in the user key store. + CertifiedKey key; + key.set_key_name("label"); + *key.add_additional_intermediate_ca_cert() = "fake_ca_cert2"; + std::string key_bytes; + key.SerializeToString(&key_bytes); + EXPECT_CALL(mock_key_store_, Read("user", "label", _)) + .WillOnce(DoAll(SetArgumentPointee<2>(key_bytes), Return(true))); + EXPECT_CALL(mock_key_store_, RegisterCertificate(_, _)) + .WillRepeatedly(Return(false)); + // Set expectations on the outputs. + auto callback = [this](const RegisterKeyWithChapsTokenReply& reply) { + EXPECT_NE(STATUS_SUCCESS, reply.status()); + Quit(); + }; + RegisterKeyWithChapsTokenRequest request; + request.set_key_label("label"); + request.set_username("user"); + service_->RegisterKeyWithChapsToken(request, base::Bind(callback)); + Run(); +} + +} // namespace attestation diff --git a/attestation/server/attestationd-seccomp-amd64.policy b/attestation/server/attestationd-seccomp-amd64.policy new file mode 100644 index 0000000..9cdff43 --- /dev/null +++ b/attestation/server/attestationd-seccomp-amd64.policy @@ -0,0 +1,80 @@ +# +# 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. +# + +# Tested on link +gettid: 1 +getuid: 1 +geteuid: 1 +getgid: 1 +getegid: 1 +getresuid: 1 +getresgid: 1 + +clock_getres: 1 +clock_gettime: 1 +gettimeofday: 1 +time: 1 + +socket: 1 +socketpair: 1 +connect: 1 +getsockname: 1 +pipe: 1 +sendmsg: 1 +sendto: 1 +recvmsg: 1 +recvfrom: 1 + +epoll_create: 1 +epoll_wait: 1 +epoll_ctl: 1 +poll: 1 + +open: 1 +read: 1 +write: 1 +close: 1 + +inotify_init: 1 +inotify_add_watch: 1 +inotify_rm_watch: 1 +select: 1 + +fstat: 1 +stat: 1 +lseek: 1 +lstat: 1 +fcntl: 1 + +futex: 1 +set_robust_list: 1 +restart_syscall: 1 +exit: 1 +exit_group: 1 +rt_sigreturn: 1 +rt_sigprocmask: 1 +signalfd4: 1 + +brk: 1 +mmap: 1 +madvise: 1 +mprotect: 1 +munmap: 1 + +clone: 1 +# These calls are attempted but apparently not necessary; return EPERM +prctl: return 1 +ioctl: return 1 diff --git a/attestation/server/attestationd-seccomp-arm.policy b/attestation/server/attestationd-seccomp-arm.policy new file mode 100644 index 0000000..16d2a9f --- /dev/null +++ b/attestation/server/attestationd-seccomp-arm.policy @@ -0,0 +1,50 @@ +# Tested on daisy_spring board +gettid: 1 +getuid32: 1 +geteuid32: 1 +getgid32: 1 +getegid32: 1 +getresuid32: 1 +getresgid32: 1 + +clock_getres: 1 +clock_gettime: 1 +gettimeofday: 1 + +# Allow socket(domain==PF_LOCAL) or socket(domain==PF_NETLINK) +socket: arg0 == 0x1 || arg0 == 0x10 +socketpair: 1 +connect: 1 +getsockname: 1 +pipe: 1 +send: 1 +sendmsg: 1 +recvmsg: 1 + +epoll_create: 1 +epoll_wait: 1 +epoll_ctl: 1 +poll: 1 + +open: 1 +read: 1 +write: 1 +close: 1 + +fstat64: 1 +stat64: 1 +_llseek: 1 +fcntl64: 1 + +futex: 1 + +restart_syscall: 1 +exit: 1 +exit_group: 1 +rt_sigreturn: 1 +rt_sigprocmask: 1 +signalfd4: 1 + +brk: 1 +mmap2: 1 +munmap: 1 diff --git a/attestation/server/attestationd-seccomp-x86.policy b/attestation/server/attestationd-seccomp-x86.policy new file mode 100644 index 0000000..015e01e --- /dev/null +++ b/attestation/server/attestationd-seccomp-x86.policy @@ -0,0 +1,45 @@ +# Tested on alex board +gettid: 1 +geteuid32: 1 +getegid32: 1 +getuid32: 1 +getgid32: 1 +getresuid32: 1 +getresgid32: 1 + +clock_getres: 1 +clock_gettime: 1 +gettimeofday: 1 +time: 1 + +# TODO(namnguyen): filter socket system calls. +socketcall: 1 +pipe: 1 + +open: 1 +read: 1 +write: 1 +close: 1 + +brk: 1 +mmap2: 1 +munmap: 1 + +fstat64: 1 +stat64: 1 +_llseek: 1 +fcntl64: 1 + +futex: 1 + +restart_syscall: 1 +exit: 1 +exit_group: 1 +rt_sigreturn: 1 +rt_sigprocmask: 1 +signalfd4: 1 + +epoll_create: 1 +epoll_wait: 1 +epoll_ctl: 1 +poll: 1 diff --git a/attestation/server/attestationd.conf b/attestation/server/attestationd.conf new file mode 100644 index 0000000..0d5a74d --- /dev/null +++ b/attestation/server/attestationd.conf @@ -0,0 +1,24 @@ +# +# 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. +# + +description "Chromium OS device attestation service." +author "chromium-os-dev@chromium.org" + +start on started tcsd and started boot-services +stop on stopping boot-services +respawn + +exec /usr/sbin/attestationd diff --git a/attestation/server/database.h b/attestation/server/database.h new file mode 100644 index 0000000..7416e57 --- /dev/null +++ b/attestation/server/database.h @@ -0,0 +1,46 @@ +// +// 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. +// + +#ifndef ATTESTATION_SERVER_DATABASE_H_ +#define ATTESTATION_SERVER_DATABASE_H_ + +#include "attestation/common/database.pb.h" + +namespace attestation { + +// Manages a persistent database of attestation-related data. +class Database { + public: + virtual ~Database() = default; + + // Const access to the database protobuf. + virtual const AttestationDatabase& GetProtobuf() const = 0; + + // Mutable access to the database protobuf. Changes made to the protobuf will + // be reflected immediately by GetProtobuf() but will not be persisted to disk + // until SaveChanges is called successfully. + virtual AttestationDatabase* GetMutableProtobuf() = 0; + + // Writes the current database protobuf to disk. + virtual bool SaveChanges() = 0; + + // Reloads the database protobuf from disk. + virtual bool Reload() = 0; +}; + +} // namespace attestation + +#endif // ATTESTATION_SERVER_DATABASE_H_ diff --git a/attestation/server/database_impl.cc b/attestation/server/database_impl.cc new file mode 100644 index 0000000..48cdfe5 --- /dev/null +++ b/attestation/server/database_impl.cc @@ -0,0 +1,199 @@ +// +// 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/database_impl.h" + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <string> + +#include <base/files/file_path.h> +#include <base/files/file_util.h> +#include <base/files/important_file_writer.h> +#include <base/logging.h> +#include <base/stl_util.h> +#include <brillo/secure_blob.h> + +using base::FilePath; + +namespace { + +const char kDatabasePath[] = + "/mnt/stateful_partition/unencrypted/preserve/attestation.epb"; +const mode_t kDatabasePermissions = 0600; + +// A base::FilePathWatcher::Callback that just relays to |callback|. +void FileWatcherCallback(const base::Closure& callback, const FilePath&, bool) { + callback.Run(); +} + +} // namespace + +namespace attestation { + +DatabaseImpl::DatabaseImpl(CryptoUtility* crypto) : io_(this), crypto_(crypto) { +} + +DatabaseImpl::~DatabaseImpl() { + brillo::SecureMemset(string_as_array(&database_key_), 0, + database_key_.size()); +} + +void DatabaseImpl::Initialize() { + // Start thread-checking now. + thread_checker_.DetachFromThread(); + DCHECK(thread_checker_.CalledOnValidThread()); + io_->Watch(base::Bind(base::IgnoreResult(&DatabaseImpl::Reload), + base::Unretained(this))); + if (!Reload()) { + LOG(WARNING) << "Creating new attestation database."; + } +} + +const AttestationDatabase& DatabaseImpl::GetProtobuf() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return protobuf_; +} + +AttestationDatabase* DatabaseImpl::GetMutableProtobuf() { + DCHECK(thread_checker_.CalledOnValidThread()); + return &protobuf_; +} + +bool DatabaseImpl::SaveChanges() { + DCHECK(thread_checker_.CalledOnValidThread()); + std::string buffer; + if (!EncryptProtobuf(&buffer)) { + return false; + } + return io_->Write(buffer); +} + +bool DatabaseImpl::Reload() { + DCHECK(thread_checker_.CalledOnValidThread()); + LOG(INFO) << "Loading attestation database."; + std::string buffer; + if (!io_->Read(&buffer)) { + return false; + } + return DecryptProtobuf(buffer); +} + +bool DatabaseImpl::Read(std::string* data) { + const int kMask = base::FILE_PERMISSION_OTHERS_MASK; + FilePath path(kDatabasePath); + int permissions = 0; + if (base::GetPosixFilePermissions(path, &permissions) && + (permissions & kMask) != 0) { + base::SetPosixFilePermissions(path, permissions & ~kMask); + } + return base::ReadFileToString(path, data); +} + +bool DatabaseImpl::Write(const std::string& data) { + FilePath file_path(kDatabasePath); + if (!base::CreateDirectory(file_path.DirName())) { + LOG(ERROR) << "Cannot create directory: " << file_path.DirName().value(); + return false; + } + if (!base::ImportantFileWriter::WriteFileAtomically(file_path, data)) { + LOG(ERROR) << "Failed to write file: " << file_path.value(); + return false; + } + if (!base::SetPosixFilePermissions(file_path, kDatabasePermissions)) { + LOG(ERROR) << "Failed to set permissions for file: " << file_path.value(); + return false; + } + // Sync the parent directory. + std::string dir_name = file_path.DirName().value(); + int dir_fd = HANDLE_EINTR(open(dir_name.c_str(), O_RDONLY|O_DIRECTORY)); + if (dir_fd < 0) { + PLOG(WARNING) << "Could not open " << dir_name << " for syncing"; + return false; + } + // POSIX specifies EINTR as a possible return value of fsync(). + int result = HANDLE_EINTR(fsync(dir_fd)); + if (result < 0) { + PLOG(WARNING) << "Failed to sync " << dir_name; + close(dir_fd); + return false; + } + // close() may not be retried on error. + result = IGNORE_EINTR(close(dir_fd)); + if (result < 0) { + PLOG(WARNING) << "Failed to close after sync " << dir_name; + return false; + } + return true; +} + +void DatabaseImpl::Watch(const base::Closure& callback) { + if (!file_watcher_) { + file_watcher_.reset(new base::FilePathWatcher()); + file_watcher_->Watch(FilePath(kDatabasePath), false, + base::Bind(&FileWatcherCallback, callback)); + } +} + +bool DatabaseImpl::EncryptProtobuf(std::string* encrypted_output) { + std::string serial_proto; + if (!protobuf_.SerializeToString(&serial_proto)) { + LOG(ERROR) << "Failed to serialize db."; + return false; + } + if (database_key_.empty() || sealed_database_key_.empty()) { + if (!crypto_->CreateSealedKey(&database_key_, &sealed_database_key_)) { + LOG(ERROR) << "Failed to generate database key."; + return false; + } + } + if (!crypto_->EncryptData(serial_proto, database_key_, sealed_database_key_, + encrypted_output)) { + LOG(ERROR) << "Attestation: Failed to encrypt database."; + return false; + } + return true; +} + +bool DatabaseImpl::DecryptProtobuf(const std::string& encrypted_input) { + if (!crypto_->UnsealKey(encrypted_input, &database_key_, + &sealed_database_key_)) { + LOG(ERROR) << "Attestation: Could not unseal decryption key."; + return false; + } + std::string serial_proto; + if (!crypto_->DecryptData(encrypted_input, database_key_, &serial_proto)) { + LOG(ERROR) << "Attestation: Failed to decrypt database."; + return false; + } + if (!protobuf_.ParseFromString(serial_proto)) { + // Previously the DB was encrypted with CryptoLib::AesEncrypt which appends + // a SHA-1. This can be safely ignored. + const size_t kLegacyJunkSize = 20; + if (serial_proto.size() < kLegacyJunkSize || + !protobuf_.ParseFromArray(serial_proto.data(), + serial_proto.length() - kLegacyJunkSize)) { + LOG(ERROR) << "Failed to parse database."; + return false; + } + } + return true; +} + +} // namespace attestation diff --git a/attestation/server/database_impl.h b/attestation/server/database_impl.h new file mode 100644 index 0000000..9007d92 --- /dev/null +++ b/attestation/server/database_impl.h @@ -0,0 +1,91 @@ +// +// 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. +// + +#ifndef ATTESTATION_SERVER_DATABASE_IMPL_H_ +#define ATTESTATION_SERVER_DATABASE_IMPL_H_ + +#include "attestation/server/database.h" + +#include <string> + +#include <base/callback_forward.h> +#include <base/files/file_path_watcher.h> +#include <base/threading/thread_checker.h> + +#include "attestation/common/crypto_utility.h" + +namespace attestation { + +// An I/O abstraction to help with testing. +class DatabaseIO { + public: + // Reads the persistent database blob. + virtual bool Read(std::string* data) = 0; + // Writes the persistent database blob. + virtual bool Write(const std::string& data) = 0; + // Watch for external changes to the database. + virtual void Watch(const base::Closure& callback) = 0; +}; + +// An implementation of Database backed by an ordinary file. Not thread safe. +// All methods must be called on the same thread as the Initialize() call. +class DatabaseImpl : public Database, + public DatabaseIO { + public: + // Does not take ownership of pointers. + explicit DatabaseImpl(CryptoUtility* crypto); + ~DatabaseImpl() override; + + // Reads and decrypts any existing database on disk synchronously. Must be + // called before calling other methods. + void Initialize(); + + // Database methods. + const AttestationDatabase& GetProtobuf() const override; + AttestationDatabase* GetMutableProtobuf() override; + bool SaveChanges() override; + bool Reload() override; + + // DatabaseIO methods. + bool Read(std::string* data) override; + bool Write(const std::string& data) override; + void Watch(const base::Closure& callback) override; + + // Useful for testing. + void set_io(DatabaseIO* io) { + io_ = io; + } + + private: + // Encrypts |protobuf_| into |encrypted_output|. Returns true on success. + bool EncryptProtobuf(std::string* encrypted_output); + + // Decrypts |encrypted_input| as output by EncryptProtobuf into |protobuf_|. + // Returns true on success. + bool DecryptProtobuf(const std::string& encrypted_input); + + AttestationDatabase protobuf_; + DatabaseIO* io_; + CryptoUtility* crypto_; + std::string database_key_; + std::string sealed_database_key_; + std::unique_ptr<base::FilePathWatcher> file_watcher_; + base::ThreadChecker thread_checker_; +}; + +} // namespace attestation + +#endif // ATTESTATION_SERVER_DATABASE_IMPL_H_ diff --git a/attestation/server/database_impl_test.cc b/attestation/server/database_impl_test.cc new file mode 100644 index 0000000..9b3d750 --- /dev/null +++ b/attestation/server/database_impl_test.cc @@ -0,0 +1,163 @@ +// +// 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 <memory> +#include <string> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "attestation/common/mock_crypto_utility.h" +#include "attestation/server/database_impl.h" + +using testing::_; +using testing::Invoke; +using testing::NiceMock; +using testing::Return; +using testing::WithArgs; + +namespace { + +const char kFakeCredential[] = "1234"; + +} // namespace + +namespace attestation { + +class DatabaseImplTest : public testing::Test, + public DatabaseIO { + public: + ~DatabaseImplTest() override = default; + void SetUp() override { + database_.reset(new DatabaseImpl(&mock_crypto_utility_)); + database_->set_io(this); + InitializeFakeData(); + database_->Initialize(); + } + + // Fake DatabaseIO::Read. + bool Read(std::string* data) override { + if (fake_persistent_data_readable_) { + *data = fake_persistent_data_; + } + return fake_persistent_data_readable_; + } + + // Fake DatabaseIO::Write. + bool Write(const std::string& data) override { + if (fake_persistent_data_writable_) { + fake_persistent_data_ = data; + } + return fake_persistent_data_writable_; + } + + // Fake DatabaseIO::Watch. + void Watch(const base::Closure& callback) override { + fake_watch_callback_ = callback; + } + + // Initializes fake_persistent_data_ with a default value. + void InitializeFakeData() { + AttestationDatabase proto; + proto.mutable_credentials()->set_conformance_credential(kFakeCredential); + proto.SerializeToString(&fake_persistent_data_); + } + + protected: + std::string fake_persistent_data_; + bool fake_persistent_data_readable_{true}; + bool fake_persistent_data_writable_{true}; + base::Closure fake_watch_callback_; + NiceMock<MockCryptoUtility> mock_crypto_utility_; + std::unique_ptr<DatabaseImpl> database_; +}; + +TEST_F(DatabaseImplTest, ReadSuccess) { + database_->GetMutableProtobuf()->Clear(); + EXPECT_TRUE(database_->Reload()); + EXPECT_EQ(std::string(kFakeCredential), + database_->GetProtobuf().credentials().conformance_credential()); +} + +TEST_F(DatabaseImplTest, ReadFailure) { + fake_persistent_data_readable_ = false; + database_->GetMutableProtobuf()->Clear(); + EXPECT_FALSE(database_->Reload()); + EXPECT_FALSE(database_->GetProtobuf().has_credentials()); +} + +TEST_F(DatabaseImplTest, DecryptFailure) { + EXPECT_CALL(mock_crypto_utility_, DecryptData(_, _, _)) + .WillRepeatedly(Return(false)); + database_->GetMutableProtobuf()->Clear(); + EXPECT_FALSE(database_->Reload()); + EXPECT_FALSE(database_->GetProtobuf().has_credentials()); +} + +TEST_F(DatabaseImplTest, WriteSuccess) { + database_->GetMutableProtobuf()->mutable_credentials()-> + set_platform_credential("test"); + std::string expected_data; + database_->GetProtobuf().SerializeToString(&expected_data); + EXPECT_TRUE(database_->SaveChanges()); + EXPECT_EQ(expected_data, fake_persistent_data_); +} + +TEST_F(DatabaseImplTest, WriteFailure) { + fake_persistent_data_writable_ = false; + database_->GetMutableProtobuf()->mutable_credentials()-> + set_platform_credential("test"); + EXPECT_FALSE(database_->SaveChanges()); +} + +TEST_F(DatabaseImplTest, EncryptFailure) { + EXPECT_CALL(mock_crypto_utility_, EncryptData(_, _, _, _)) + .WillRepeatedly(Return(false)); + database_->GetMutableProtobuf()->mutable_credentials()-> + set_platform_credential("test"); + EXPECT_FALSE(database_->SaveChanges()); +} + +TEST_F(DatabaseImplTest, IgnoreLegacyEncryptJunk) { + // Legacy encryption scheme appended a SHA-1 hash before encrypting. + fake_persistent_data_ += std::string(20, 'A'); + EXPECT_EQ(std::string(kFakeCredential), + database_->GetProtobuf().credentials().conformance_credential()); +} + +TEST_F(DatabaseImplTest, Reload) { + AttestationDatabase proto; + proto.mutable_credentials()->set_platform_credential(kFakeCredential); + proto.SerializeToString(&fake_persistent_data_); + EXPECT_EQ(std::string(), + database_->GetProtobuf().credentials().platform_credential()); + EXPECT_TRUE(database_->Reload()); + EXPECT_EQ(std::string(kFakeCredential), + database_->GetProtobuf().credentials().platform_credential()); +} + +TEST_F(DatabaseImplTest, AutoReload) { + AttestationDatabase proto; + proto.mutable_credentials()->set_platform_credential(kFakeCredential); + proto.SerializeToString(&fake_persistent_data_); + EXPECT_EQ(std::string(), + database_->GetProtobuf().credentials().platform_credential()); + fake_watch_callback_.Run(); + EXPECT_EQ(std::string(kFakeCredential), + database_->GetProtobuf().credentials().platform_credential()); +} + +} // namespace attestation diff --git a/attestation/server/dbus_service.cc b/attestation/server/dbus_service.cc new file mode 100644 index 0000000..9c25dab --- /dev/null +++ b/attestation/server/dbus_service.cc @@ -0,0 +1,242 @@ +// +// 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 "attestation/server/dbus_service.h" + +#include <memory> +#include <string> + +#include <brillo/bind_lambda.h> +#include <dbus/bus.h> +#include <dbus/object_path.h> + +#include "attestation/common/dbus_interface.h" + +using brillo::dbus_utils::DBusMethodResponse; + +namespace attestation { + +DBusService::DBusService(const scoped_refptr<dbus::Bus>& bus, + AttestationInterface* service) + : dbus_object_(nullptr, bus, dbus::ObjectPath(kAttestationServicePath)), + service_(service) { +} + +void DBusService::Register(const CompletionAction& callback) { + brillo::dbus_utils::DBusInterface* dbus_interface = + dbus_object_.AddOrGetInterface(kAttestationInterface); + + dbus_interface->AddMethodHandler(kCreateGoogleAttestedKey, + base::Unretained(this), + &DBusService::HandleCreateGoogleAttestedKey); + dbus_interface->AddMethodHandler(kGetKeyInfo, + base::Unretained(this), + &DBusService::HandleGetKeyInfo); + dbus_interface->AddMethodHandler(kGetEndorsementInfo, + base::Unretained(this), + &DBusService::HandleGetEndorsementInfo); + dbus_interface->AddMethodHandler(kGetAttestationKeyInfo, + base::Unretained(this), + &DBusService::HandleGetAttestationKeyInfo); + dbus_interface->AddMethodHandler(kActivateAttestationKey, + base::Unretained(this), + &DBusService::HandleActivateAttestationKey); + dbus_interface->AddMethodHandler(kCreateCertifiableKey, + base::Unretained(this), + &DBusService::HandleCreateCertifiableKey); + dbus_interface->AddMethodHandler(kDecrypt, + base::Unretained(this), + &DBusService::HandleDecrypt); + dbus_interface->AddMethodHandler(kSign, + base::Unretained(this), + &DBusService::HandleSign); + dbus_interface->AddMethodHandler( + kRegisterKeyWithChapsToken, + base::Unretained(this), + &DBusService::HandleRegisterKeyWithChapsToken); + + dbus_object_.RegisterAsync(callback); +} + +void DBusService::HandleCreateGoogleAttestedKey( + std::unique_ptr<DBusMethodResponse<const CreateGoogleAttestedKeyReply&>> + response, + const CreateGoogleAttestedKeyRequest& request) { + VLOG(1) << __func__; + // Convert |response| to a shared_ptr so |service_| can safely copy the + // callback. + using SharedResponsePointer = std::shared_ptr< + DBusMethodResponse<const CreateGoogleAttestedKeyReply&>>; + // A callback that fills the reply protobuf and sends it. + auto callback = [](const SharedResponsePointer& response, + const CreateGoogleAttestedKeyReply& reply) { + response->Return(reply); + }; + service_->CreateGoogleAttestedKey( + request, + base::Bind(callback, SharedResponsePointer(std::move(response)))); +} + +void DBusService::HandleGetKeyInfo( + std::unique_ptr<DBusMethodResponse<const GetKeyInfoReply&>> response, + const GetKeyInfoRequest& request) { + VLOG(1) << __func__; + // Convert |response| to a shared_ptr so |service_| can safely copy the + // callback. + using SharedResponsePointer = std::shared_ptr< + DBusMethodResponse<const GetKeyInfoReply&>>; + // A callback that fills the reply protobuf and sends it. + auto callback = [](const SharedResponsePointer& response, + const GetKeyInfoReply& reply) { + response->Return(reply); + }; + service_->GetKeyInfo( + request, + base::Bind(callback, SharedResponsePointer(std::move(response)))); +} + +void DBusService::HandleGetEndorsementInfo( + std::unique_ptr<DBusMethodResponse<const GetEndorsementInfoReply&>> + response, + const GetEndorsementInfoRequest& request) { + VLOG(1) << __func__; + // Convert |response| to a shared_ptr so |service_| can safely copy the + // callback. + using SharedResponsePointer = std::shared_ptr< + DBusMethodResponse<const GetEndorsementInfoReply&>>; + // A callback that fills the reply protobuf and sends it. + auto callback = [](const SharedResponsePointer& response, + const GetEndorsementInfoReply& reply) { + response->Return(reply); + }; + service_->GetEndorsementInfo( + request, + base::Bind(callback, SharedResponsePointer(std::move(response)))); +} + +void DBusService::HandleGetAttestationKeyInfo( + std::unique_ptr<DBusMethodResponse<const GetAttestationKeyInfoReply&>> + response, + const GetAttestationKeyInfoRequest& request) { + VLOG(1) << __func__; + // Convert |response| to a shared_ptr so |service_| can safely copy the + // callback. + using SharedResponsePointer = std::shared_ptr< + DBusMethodResponse<const GetAttestationKeyInfoReply&>>; + // A callback that fills the reply protobuf and sends it. + auto callback = [](const SharedResponsePointer& response, + const GetAttestationKeyInfoReply& reply) { + response->Return(reply); + }; + service_->GetAttestationKeyInfo( + request, + base::Bind(callback, SharedResponsePointer(std::move(response)))); +} + +void DBusService::HandleActivateAttestationKey( + std::unique_ptr<DBusMethodResponse<const ActivateAttestationKeyReply&>> + response, + const ActivateAttestationKeyRequest& request) { + VLOG(1) << __func__; + // Convert |response| to a shared_ptr so |service_| can safely copy the + // callback. + using SharedResponsePointer = std::shared_ptr< + DBusMethodResponse<const ActivateAttestationKeyReply&>>; + // A callback that fills the reply protobuf and sends it. + auto callback = [](const SharedResponsePointer& response, + const ActivateAttestationKeyReply& reply) { + response->Return(reply); + }; + service_->ActivateAttestationKey( + request, + base::Bind(callback, SharedResponsePointer(std::move(response)))); +} + +void DBusService::HandleCreateCertifiableKey( + std::unique_ptr<DBusMethodResponse<const CreateCertifiableKeyReply&>> + response, + const CreateCertifiableKeyRequest& request) { + VLOG(1) << __func__; + // Convert |response| to a shared_ptr so |service_| can safely copy the + // callback. + using SharedResponsePointer = std::shared_ptr< + DBusMethodResponse<const CreateCertifiableKeyReply&>>; + // A callback that fills the reply protobuf and sends it. + auto callback = [](const SharedResponsePointer& response, + const CreateCertifiableKeyReply& reply) { + response->Return(reply); + }; + service_->CreateCertifiableKey( + request, + base::Bind(callback, SharedResponsePointer(std::move(response)))); +} + +void DBusService::HandleDecrypt( + std::unique_ptr<DBusMethodResponse<const DecryptReply&>> response, + const DecryptRequest& request) { + VLOG(1) << __func__; + // Convert |response| to a shared_ptr so |service_| can safely copy the + // callback. + using SharedResponsePointer = std::shared_ptr< + DBusMethodResponse<const DecryptReply&>>; + // A callback that fills the reply protobuf and sends it. + auto callback = [](const SharedResponsePointer& response, + const DecryptReply& reply) { + response->Return(reply); + }; + service_->Decrypt( + request, + 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)))); +} + +void DBusService::HandleRegisterKeyWithChapsToken( + std::unique_ptr<DBusMethodResponse<const RegisterKeyWithChapsTokenReply&>> + response, + const RegisterKeyWithChapsTokenRequest& request) { + VLOG(1) << __func__; + // Convert |response| to a shared_ptr so |service_| can safely copy the + // callback. + using SharedResponsePointer = std::shared_ptr< + DBusMethodResponse<const RegisterKeyWithChapsTokenReply&>>; + // A callback that fills the reply protobuf and sends it. + auto callback = [](const SharedResponsePointer& response, + const RegisterKeyWithChapsTokenReply& reply) { + response->Return(reply); + }; + service_->RegisterKeyWithChapsToken( + request, + base::Bind(callback, SharedResponsePointer(std::move(response)))); +} + +} // namespace attestation diff --git a/attestation/server/dbus_service.h b/attestation/server/dbus_service.h new file mode 100644 index 0000000..dd4281c --- /dev/null +++ b/attestation/server/dbus_service.h @@ -0,0 +1,115 @@ +// +// 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. +// + +#ifndef ATTESTATION_SERVER_DBUS_SERVICE_H_ +#define ATTESTATION_SERVER_DBUS_SERVICE_H_ + +#include <memory> + +#include <brillo/dbus/dbus_method_response.h> +#include <brillo/dbus/dbus_object.h> +#include <dbus/bus.h> + +#include "attestation/common/attestation_interface.h" + +namespace attestation { + +using CompletionAction = + brillo::dbus_utils::AsyncEventSequencer::CompletionAction; + +// Handles D-Bus calls to the attestation daemon. +class DBusService { + public: + // DBusService does not take ownership of |service|; it must remain valid for + // the lifetime of the DBusService instance. + DBusService(const scoped_refptr<dbus::Bus>& bus, + AttestationInterface* service); + virtual ~DBusService() = default; + + // Connects to D-Bus system bus and exports methods. + void Register(const CompletionAction& callback); + + // Useful for testing. + void set_service(AttestationInterface* service) { + service_ = service; + } + + private: + friend class DBusServiceTest; + + // Handles a CreateGoogleAttestedKey D-Bus call. + void HandleCreateGoogleAttestedKey( + std::unique_ptr<brillo::dbus_utils::DBusMethodResponse< + const CreateGoogleAttestedKeyReply&>> response, + const CreateGoogleAttestedKeyRequest& request); + + // Handles a GetKeyInfo D-Bus call. + void HandleGetKeyInfo( + std::unique_ptr<brillo::dbus_utils::DBusMethodResponse< + const GetKeyInfoReply&>> response, + const GetKeyInfoRequest& request); + + // Handles a GetEndorsementInfo D-Bus call. + void HandleGetEndorsementInfo( + std::unique_ptr<brillo::dbus_utils::DBusMethodResponse< + const GetEndorsementInfoReply&>> response, + const GetEndorsementInfoRequest& request); + + // Handles a GetAttestationKeyInfo D-Bus call. + void HandleGetAttestationKeyInfo( + std::unique_ptr<brillo::dbus_utils::DBusMethodResponse< + const GetAttestationKeyInfoReply&>> response, + const GetAttestationKeyInfoRequest& request); + + // Handles a ActivateAttestationKey D-Bus call. + void HandleActivateAttestationKey( + std::unique_ptr<brillo::dbus_utils::DBusMethodResponse< + const ActivateAttestationKeyReply&>> response, + const ActivateAttestationKeyRequest& request); + + // Handles a CreateCertifiableKey D-Bus call. + void HandleCreateCertifiableKey( + std::unique_ptr<brillo::dbus_utils::DBusMethodResponse< + const CreateCertifiableKeyReply&>> response, + const CreateCertifiableKeyRequest& request); + + // Handles a Decrypt D-Bus call. + void HandleDecrypt( + std::unique_ptr<brillo::dbus_utils::DBusMethodResponse< + const DecryptReply&>> response, + const DecryptRequest& request); + + // Handles a Sign D-Bus call. + void HandleSign( + std::unique_ptr<brillo::dbus_utils::DBusMethodResponse< + const SignReply&>> response, + const SignRequest& request); + + // Handles a RegisterKeyWithChapsToken D-Bus call. + void HandleRegisterKeyWithChapsToken( + std::unique_ptr<brillo::dbus_utils::DBusMethodResponse< + const RegisterKeyWithChapsTokenReply&>> response, + const RegisterKeyWithChapsTokenRequest& request); + + brillo::dbus_utils::DBusObject dbus_object_; + AttestationInterface* service_; + + DISALLOW_COPY_AND_ASSIGN(DBusService); +}; + +} // namespace attestation + +#endif // ATTESTATION_SERVER_DBUS_SERVICE_H_ diff --git a/attestation/server/dbus_service_test.cc b/attestation/server/dbus_service_test.cc new file mode 100644 index 0000000..ebe6acc --- /dev/null +++ b/attestation/server/dbus_service_test.cc @@ -0,0 +1,382 @@ +// +// 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 <string> + +#include <brillo/bind_lambda.h> +#include <brillo/dbus/dbus_object_test_helpers.h> +#include <dbus/mock_bus.h> +#include <dbus/mock_exported_object.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "attestation/common/dbus_interface.h" +#include "attestation/common/mock_attestation_interface.h" +#include "attestation/server/dbus_service.h" + +using testing::_; +using testing::Invoke; +using testing::NiceMock; +using testing::Return; +using testing::StrictMock; +using testing::WithArgs; + +namespace attestation { + +class DBusServiceTest : public testing::Test { + public: + ~DBusServiceTest() override = default; + void SetUp() override { + dbus::Bus::Options options; + mock_bus_ = new NiceMock<dbus::MockBus>(options); + dbus::ObjectPath path(kAttestationServicePath); + mock_exported_object_ = new NiceMock<dbus::MockExportedObject>( + mock_bus_.get(), path); + ON_CALL(*mock_bus_, GetExportedObject(path)) + .WillByDefault(Return(mock_exported_object_.get())); + dbus_service_.reset(new DBusService(mock_bus_, &mock_service_)); + dbus_service_->Register(brillo::dbus_utils::AsyncEventSequencer:: + GetDefaultCompletionAction()); + } + + std::unique_ptr<dbus::Response> CallMethod(dbus::MethodCall* method_call) { + return brillo::dbus_utils::testing::CallMethod( + dbus_service_->dbus_object_, method_call); + } + + std::unique_ptr<dbus::MethodCall> CreateMethodCall( + const std::string& method_name) { + std::unique_ptr<dbus::MethodCall> call(new dbus::MethodCall( + kAttestationInterface, method_name)); + call->SetSerial(1); + return call; + } + + protected: + scoped_refptr<dbus::MockBus> mock_bus_; + scoped_refptr<dbus::MockExportedObject> mock_exported_object_; + StrictMock<MockAttestationInterface> mock_service_; + std::unique_ptr<DBusService> dbus_service_; +}; + +TEST_F(DBusServiceTest, CreateGoogleAttestedKey) { + CreateGoogleAttestedKeyRequest request; + request.set_key_label("label"); + request.set_key_type(KEY_TYPE_ECC); + request.set_key_usage(KEY_USAGE_SIGN); + request.set_certificate_profile(ENTERPRISE_MACHINE_CERTIFICATE); + request.set_username("username"); + request.set_origin("origin"); + EXPECT_CALL(mock_service_, CreateGoogleAttestedKey(_, _)) + .WillOnce(Invoke([]( + const CreateGoogleAttestedKeyRequest& request, + const AttestationInterface:: + CreateGoogleAttestedKeyCallback& callback) { + EXPECT_EQ("label", request.key_label()); + EXPECT_EQ(KEY_TYPE_ECC, request.key_type()); + EXPECT_EQ(KEY_USAGE_SIGN, request.key_usage()); + EXPECT_EQ(ENTERPRISE_MACHINE_CERTIFICATE, + request.certificate_profile()); + EXPECT_EQ("username", request.username()); + EXPECT_EQ("origin", request.origin()); + CreateGoogleAttestedKeyReply reply; + reply.set_status(STATUS_SUCCESS); + reply.set_certificate_chain("certificate"); + reply.set_server_error("server_error"); + callback.Run(reply); + })); + std::unique_ptr<dbus::MethodCall> call = CreateMethodCall( + kCreateGoogleAttestedKey); + dbus::MessageWriter writer(call.get()); + writer.AppendProtoAsArrayOfBytes(request); + auto response = CallMethod(call.get()); + dbus::MessageReader reader(response.get()); + CreateGoogleAttestedKeyReply reply; + EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&reply)); + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ("certificate", reply.certificate_chain()); + EXPECT_EQ("server_error", reply.server_error()); +} + +TEST_F(DBusServiceTest, CopyableCallback) { + EXPECT_CALL(mock_service_, CreateGoogleAttestedKey(_, _)) + .WillOnce(WithArgs<1>(Invoke([](const AttestationInterface:: + CreateGoogleAttestedKeyCallback& callback) { + // Copy the callback, then call the original. + CreateGoogleAttestedKeyReply reply; + base::Closure copy = base::Bind(callback, reply); + callback.Run(reply); + }))); + std::unique_ptr<dbus::MethodCall> call = CreateMethodCall( + kCreateGoogleAttestedKey); + CreateGoogleAttestedKeyRequest request; + dbus::MessageWriter writer(call.get()); + writer.AppendProtoAsArrayOfBytes(request); + auto response = CallMethod(call.get()); + dbus::MessageReader reader(response.get()); + CreateGoogleAttestedKeyReply reply; + EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&reply)); +} + +TEST_F(DBusServiceTest, GetKeyInfo) { + GetKeyInfoRequest request; + request.set_key_label("label"); + request.set_username("username"); + EXPECT_CALL(mock_service_, GetKeyInfo(_, _)) + .WillOnce(Invoke([]( + const GetKeyInfoRequest& request, + const AttestationInterface::GetKeyInfoCallback& callback) { + EXPECT_EQ("label", request.key_label()); + EXPECT_EQ("username", request.username()); + GetKeyInfoReply reply; + reply.set_status(STATUS_SUCCESS); + reply.set_key_type(KEY_TYPE_ECC); + reply.set_key_usage(KEY_USAGE_SIGN); + reply.set_public_key("public_key"); + reply.set_certify_info("certify"); + reply.set_certify_info_signature("signature"); + reply.set_certificate("certificate"); + callback.Run(reply); + })); + std::unique_ptr<dbus::MethodCall> call = CreateMethodCall(kGetKeyInfo); + dbus::MessageWriter writer(call.get()); + writer.AppendProtoAsArrayOfBytes(request); + auto response = CallMethod(call.get()); + dbus::MessageReader reader(response.get()); + GetKeyInfoReply reply; + EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&reply)); + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ(KEY_TYPE_ECC, reply.key_type()); + EXPECT_EQ(KEY_USAGE_SIGN, reply.key_usage()); + EXPECT_EQ("public_key", reply.public_key()); + EXPECT_EQ("certify", reply.certify_info()); + EXPECT_EQ("signature", reply.certify_info_signature()); + EXPECT_EQ("certificate", reply.certificate()); +} + +TEST_F(DBusServiceTest, GetEndorsementInfo) { + GetEndorsementInfoRequest request; + request.set_key_type(KEY_TYPE_ECC); + EXPECT_CALL(mock_service_, GetEndorsementInfo(_, _)) + .WillOnce(Invoke([]( + const GetEndorsementInfoRequest& request, + const AttestationInterface::GetEndorsementInfoCallback& callback) { + EXPECT_EQ(KEY_TYPE_ECC, request.key_type()); + GetEndorsementInfoReply reply; + reply.set_status(STATUS_SUCCESS); + reply.set_ek_public_key("public_key"); + reply.set_ek_certificate("certificate"); + callback.Run(reply); + })); + std::unique_ptr<dbus::MethodCall> call = + CreateMethodCall(kGetEndorsementInfo); + dbus::MessageWriter writer(call.get()); + writer.AppendProtoAsArrayOfBytes(request); + auto response = CallMethod(call.get()); + dbus::MessageReader reader(response.get()); + GetEndorsementInfoReply reply; + EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&reply)); + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ("public_key", reply.ek_public_key()); + EXPECT_EQ("certificate", reply.ek_certificate()); +} + +TEST_F(DBusServiceTest, GetAttestationKeyInfo) { + GetAttestationKeyInfoRequest request; + request.set_key_type(KEY_TYPE_ECC); + EXPECT_CALL(mock_service_, GetAttestationKeyInfo(_, _)) + .WillOnce(Invoke([]( + const GetAttestationKeyInfoRequest& request, + const AttestationInterface::GetAttestationKeyInfoCallback& callback) { + EXPECT_EQ(KEY_TYPE_ECC, request.key_type()); + GetAttestationKeyInfoReply reply; + reply.set_status(STATUS_SUCCESS); + reply.set_public_key("public_key"); + reply.set_public_key_tpm_format("public_key_tpm_format"); + reply.set_certificate("certificate"); + reply.mutable_pcr0_quote()->set_quote("pcr0"); + reply.mutable_pcr1_quote()->set_quote("pcr1"); + callback.Run(reply); + })); + std::unique_ptr<dbus::MethodCall> call = + CreateMethodCall(kGetAttestationKeyInfo); + dbus::MessageWriter writer(call.get()); + writer.AppendProtoAsArrayOfBytes(request); + auto response = CallMethod(call.get()); + dbus::MessageReader reader(response.get()); + GetAttestationKeyInfoReply reply; + EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&reply)); + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ("public_key", reply.public_key()); + EXPECT_EQ("public_key_tpm_format", reply.public_key_tpm_format()); + EXPECT_EQ("certificate", reply.certificate()); + EXPECT_EQ("pcr0", reply.pcr0_quote().quote()); + EXPECT_EQ("pcr1", reply.pcr1_quote().quote()); +} + +TEST_F(DBusServiceTest, ActivateAttestationKey) { + ActivateAttestationKeyRequest request; + request.set_key_type(KEY_TYPE_ECC); + request.mutable_encrypted_certificate()->set_asym_ca_contents("encrypted1"); + request.mutable_encrypted_certificate()->set_sym_ca_attestation("encrypted2"); + request.set_save_certificate(true); + EXPECT_CALL(mock_service_, ActivateAttestationKey(_, _)) + .WillOnce(Invoke([]( + const ActivateAttestationKeyRequest& request, + const AttestationInterface::ActivateAttestationKeyCallback& + callback) { + EXPECT_EQ(KEY_TYPE_ECC, request.key_type()); + EXPECT_EQ("encrypted1", + request.encrypted_certificate().asym_ca_contents()); + EXPECT_EQ("encrypted2", + request.encrypted_certificate().sym_ca_attestation()); + EXPECT_TRUE(request.save_certificate()); + ActivateAttestationKeyReply reply; + reply.set_status(STATUS_SUCCESS); + reply.set_certificate("certificate"); + callback.Run(reply); + })); + std::unique_ptr<dbus::MethodCall> call = + CreateMethodCall(kActivateAttestationKey); + dbus::MessageWriter writer(call.get()); + writer.AppendProtoAsArrayOfBytes(request); + auto response = CallMethod(call.get()); + dbus::MessageReader reader(response.get()); + ActivateAttestationKeyReply reply; + EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&reply)); + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ("certificate", reply.certificate()); +} + +TEST_F(DBusServiceTest, CreateCertifiableKey) { + CreateCertifiableKeyRequest request; + request.set_key_label("label"); + request.set_key_type(KEY_TYPE_ECC); + request.set_key_usage(KEY_USAGE_SIGN); + request.set_username("user"); + EXPECT_CALL(mock_service_, CreateCertifiableKey(_, _)) + .WillOnce(Invoke([]( + const CreateCertifiableKeyRequest& request, + const AttestationInterface::CreateCertifiableKeyCallback& callback) { + EXPECT_EQ("label", request.key_label()); + EXPECT_EQ(KEY_TYPE_ECC, request.key_type()); + EXPECT_EQ(KEY_USAGE_SIGN, request.key_usage()); + EXPECT_EQ("user", request.username()); + CreateCertifiableKeyReply reply; + reply.set_status(STATUS_SUCCESS); + reply.set_public_key("public_key"); + reply.set_certify_info("certify_info"); + reply.set_certify_info_signature("signature"); + callback.Run(reply); + })); + std::unique_ptr<dbus::MethodCall> call = + CreateMethodCall(kCreateCertifiableKey); + dbus::MessageWriter writer(call.get()); + writer.AppendProtoAsArrayOfBytes(request); + auto response = CallMethod(call.get()); + dbus::MessageReader reader(response.get()); + CreateCertifiableKeyReply reply; + EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&reply)); + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + EXPECT_EQ("public_key", reply.public_key()); + EXPECT_EQ("certify_info", reply.certify_info()); + EXPECT_EQ("signature", reply.certify_info_signature()); +} + +TEST_F(DBusServiceTest, Decrypt) { + DecryptRequest request; + request.set_key_label("label"); + request.set_username("user"); + request.set_encrypted_data("data"); + EXPECT_CALL(mock_service_, Decrypt(_, _)) + .WillOnce(Invoke([]( + const DecryptRequest& request, + const AttestationInterface::DecryptCallback& callback) { + EXPECT_EQ("label", request.key_label()); + EXPECT_EQ("user", request.username()); + EXPECT_EQ("data", request.encrypted_data()); + DecryptReply reply; + reply.set_status(STATUS_SUCCESS); + reply.set_decrypted_data("data"); + callback.Run(reply); + })); + std::unique_ptr<dbus::MethodCall> call = CreateMethodCall(kDecrypt); + dbus::MessageWriter writer(call.get()); + writer.AppendProtoAsArrayOfBytes(request); + auto response = CallMethod(call.get()); + dbus::MessageReader reader(response.get()); + DecryptReply reply; + EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&reply)); + EXPECT_EQ(STATUS_SUCCESS, reply.status()); + 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()); +} + +TEST_F(DBusServiceTest, RegisterKeyWithChapsToken) { + RegisterKeyWithChapsTokenRequest request; + request.set_key_label("label"); + request.set_username("user"); + EXPECT_CALL(mock_service_, RegisterKeyWithChapsToken(_, _)) + .WillOnce(Invoke([]( + const RegisterKeyWithChapsTokenRequest& request, + const AttestationInterface::RegisterKeyWithChapsTokenCallback& + callback) { + EXPECT_EQ("label", request.key_label()); + EXPECT_EQ("user", request.username()); + RegisterKeyWithChapsTokenReply reply; + reply.set_status(STATUS_SUCCESS); + callback.Run(reply); + })); + std::unique_ptr<dbus::MethodCall> call = + CreateMethodCall(kRegisterKeyWithChapsToken); + dbus::MessageWriter writer(call.get()); + writer.AppendProtoAsArrayOfBytes(request); + auto response = CallMethod(call.get()); + dbus::MessageReader reader(response.get()); + RegisterKeyWithChapsTokenReply reply; + EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&reply)); + EXPECT_EQ(STATUS_SUCCESS, reply.status()); +} + + +} // namespace attestation diff --git a/attestation/server/key_store.h b/attestation/server/key_store.h new file mode 100644 index 0000000..7137223 --- /dev/null +++ b/attestation/server/key_store.h @@ -0,0 +1,82 @@ +// +// 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. +// + +#ifndef ATTESTATION_SERVER_KEY_STORE_H_ +#define ATTESTATION_SERVER_KEY_STORE_H_ + +#include <string> + +#include <base/macros.h> + +#include "attestation/common/common.pb.h" + +namespace attestation { + +// A mock-able key storage interface. +class KeyStore { + public: + KeyStore() {} + virtual ~KeyStore() {} + + // Reads key data from the store for the key identified by |key_label| and by + // |username|. On success true is returned and |key_data| is populated. + virtual bool Read(const std::string& username, + const std::string& key_label, + std::string* key_data) = 0; + + // Writes key data to the store for the key identified by |key_label| and by + // |username|. If such a key already exists the existing data will be + // overwritten. + virtual bool Write(const std::string& username, + const std::string& key_label, + const std::string& key_data) = 0; + + // Deletes key data for the key identified by |key_label| and by |username|. + // Returns false if key data exists but could not be deleted. + virtual bool Delete(const std::string& username, + const std::string& key_label) = 0; + + // Deletes key data for all keys identified by |key_prefix| and by |username| + // Returns false if key data exists but could not be deleted. + virtual bool DeleteByPrefix(const std::string& username, + const std::string& key_prefix) = 0; + + // Registers a key to be associated with |username|. + // The provided |label| will be associated with all registered objects. + // |private_key_blob| holds the private key in some opaque format and + // |public_key_der| holds the public key in PKCS #1 RSAPublicKey format. + // If a non-empty |certificate| is provided it will be registered along with + // the key. Returns true on success. + virtual bool Register(const std::string& username, + const std::string& label, + KeyType key_type, + KeyUsage key_usage, + const std::string& private_key_blob, + const std::string& public_key_der, + const std::string& certificate) = 0; + + // Registers a |certificate| that is not associated to a registered key. The + // certificate will be associated with |username|. + virtual bool RegisterCertificate(const std::string& username, + const std::string& certificate) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(KeyStore); +}; + +} // namespace attestation + +#endif // ATTESTATION_SERVER_KEY_STORE_H_ diff --git a/attestation/server/main.cc b/attestation/server/main.cc new file mode 100644 index 0000000..b22ba18 --- /dev/null +++ b/attestation/server/main.cc @@ -0,0 +1,113 @@ +// +// 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 <sysexits.h> + +#include <memory> +#include <string> + +#include <base/command_line.h> +#include <brillo/daemons/dbus_daemon.h> +#include <brillo/dbus/async_event_sequencer.h> +#include <brillo/minijail/minijail.h> +#include <brillo/syslog_logging.h> +#include <brillo/userdb_utils.h> + +#include "attestation/common/dbus_interface.h" +#include "attestation/server/attestation_service.h" +#include "attestation/server/dbus_service.h" + +#include <chromeos/libminijail.h> + +namespace { + +const uid_t kRootUID = 0; +const char kAttestationUser[] = "attestation"; +const char kAttestationGroup[] = "attestation"; +const char kAttestationSeccompPath[] = + "/usr/share/policy/attestationd-seccomp.policy"; + +void InitMinijailSandbox() { + uid_t attestation_uid; + gid_t attestation_gid; + CHECK(brillo::userdb::GetUserInfo(kAttestationUser, + &attestation_uid, + &attestation_gid)) + << "Error getting attestation uid and gid."; + CHECK_EQ(getuid(), kRootUID) << "AttestationDaemon not initialized as root."; + brillo::Minijail* minijail = brillo::Minijail::GetInstance(); + struct minijail* jail = minijail->New(); + + minijail->DropRoot(jail, kAttestationUser, kAttestationGroup); + minijail->UseSeccompFilter(jail, kAttestationSeccompPath); + minijail->Enter(jail); + minijail->Destroy(jail); + CHECK_EQ(getuid(), attestation_uid) + << "AttestationDaemon was not able to drop to attestation user."; + CHECK_EQ(getgid(), attestation_gid) + << "AttestationDaemon was not able to drop to attestation group."; +} + +} // namespace + +using brillo::dbus_utils::AsyncEventSequencer; + +class AttestationDaemon : public brillo::DBusServiceDaemon { + public: + AttestationDaemon() + : brillo::DBusServiceDaemon(attestation::kAttestationServiceName) { + attestation_service_.reset(new attestation::AttestationService); + // Move initialize call down to OnInit + CHECK(attestation_service_->Initialize()); + } + + protected: + int OnInit() override { + int result = brillo::DBusServiceDaemon::OnInit(); + if (result != EX_OK) { + LOG(ERROR) << "Error starting attestation dbus daemon."; + return result; + } + return EX_OK; + } + + void RegisterDBusObjectsAsync(AsyncEventSequencer* sequencer) override { + dbus_service_.reset(new attestation::DBusService( + bus_, + attestation_service_.get())); + dbus_service_->Register(sequencer->GetHandler("Register() failed.", true)); + } + + private: + std::unique_ptr<attestation::AttestationInterface> attestation_service_; + std::unique_ptr<attestation::DBusService> dbus_service_; + + DISALLOW_COPY_AND_ASSIGN(AttestationDaemon); +}; + +int main(int argc, char* argv[]) { + base::CommandLine::Init(argc, argv); + base::CommandLine *cl = base::CommandLine::ForCurrentProcess(); + int flags = brillo::kLogToSyslog; + if (cl->HasSwitch("log_to_stderr")) { + flags |= brillo::kLogToStderr; + } + brillo::InitLog(flags); + AttestationDaemon daemon; + LOG(INFO) << "Attestation Daemon Started."; + InitMinijailSandbox(); + return daemon.Run(); +} diff --git a/attestation/server/mock_database.cc b/attestation/server/mock_database.cc new file mode 100644 index 0000000..fd0bdc6 --- /dev/null +++ b/attestation/server/mock_database.cc @@ -0,0 +1,33 @@ +// +// 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/mock_database.h" + +using testing::Return; +using testing::ReturnRef; + +namespace attestation { + +MockDatabase::MockDatabase() { + ON_CALL(*this, GetProtobuf()).WillByDefault(ReturnRef(fake_)); + ON_CALL(*this, GetMutableProtobuf()).WillByDefault(Return(&fake_)); + ON_CALL(*this, SaveChanges()).WillByDefault(Return(true)); + ON_CALL(*this, Reload()).WillByDefault(Return(true)); +} + +MockDatabase::~MockDatabase() {} + +} // namespace attestation diff --git a/attestation/server/mock_database.h b/attestation/server/mock_database.h new file mode 100644 index 0000000..08cef22 --- /dev/null +++ b/attestation/server/mock_database.h @@ -0,0 +1,42 @@ +// +// 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. +// + +#ifndef ATTESTATION_SERVER_MOCK_DATABASE_H_ +#define ATTESTATION_SERVER_MOCK_DATABASE_H_ + +#include "attestation/server/database.h" + +#include <gmock/gmock.h> + +namespace attestation { + +class MockDatabase : public Database { + public: + MockDatabase(); + ~MockDatabase() override; + + MOCK_CONST_METHOD0(GetProtobuf, const AttestationDatabase&()); + MOCK_METHOD0(GetMutableProtobuf, AttestationDatabase*()); + MOCK_METHOD0(SaveChanges, bool()); + MOCK_METHOD0(Reload, bool()); + + private: + AttestationDatabase fake_; +}; + +} // namespace attestation + +#endif // ATTESTATION_SERVER_MOCK_DATABASE_H_ diff --git a/attestation/server/mock_key_store.cc b/attestation/server/mock_key_store.cc new file mode 100644 index 0000000..56aa632 --- /dev/null +++ b/attestation/server/mock_key_store.cc @@ -0,0 +1,35 @@ +// +// 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/mock_key_store.h" + +using ::testing::_; +using ::testing::Return; + +namespace attestation { + +MockKeyStore::MockKeyStore() { + ON_CALL(*this, Read(_, _, _)).WillByDefault(Return(true)); + ON_CALL(*this, Write(_, _, _)).WillByDefault(Return(true)); + ON_CALL(*this, Delete(_, _)).WillByDefault(Return(true)); + ON_CALL(*this, DeleteByPrefix(_, _)).WillByDefault(Return(true)); + ON_CALL(*this, Register(_, _, _, _, _, _, _)).WillByDefault(Return(true)); + ON_CALL(*this, RegisterCertificate(_, _)).WillByDefault(Return(true)); +} + +MockKeyStore::~MockKeyStore() {} + +} // namespace attestation diff --git a/attestation/server/mock_key_store.h b/attestation/server/mock_key_store.h new file mode 100644 index 0000000..f6566ca --- /dev/null +++ b/attestation/server/mock_key_store.h @@ -0,0 +1,60 @@ +// +// 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. +// + +#ifndef ATTESTATION_SERVER_MOCK_KEY_STORE_H_ +#define ATTESTATION_SERVER_MOCK_KEY_STORE_H_ + +#include "attestation/server/key_store.h" + +#include <string> + +#include <base/macros.h> +#include <gmock/gmock.h> + +namespace attestation { + +class MockKeyStore : public KeyStore { + public: + MockKeyStore(); + virtual ~MockKeyStore(); + + MOCK_METHOD3(Read, bool(const std::string& username, + const std::string& name, + std::string* key_data)); + MOCK_METHOD3(Write, bool(const std::string& username, + const std::string& name, + const std::string& key_data)); + MOCK_METHOD2(Delete, bool(const std::string& username, + const std::string& name)); + MOCK_METHOD2(DeleteByPrefix, bool(const std::string& username, + const std::string& key_prefix)); + MOCK_METHOD7(Register, bool(const std::string& username, + const std::string& label, + KeyType key_type, + KeyUsage key_usage, + const std::string& private_key_blob, + const std::string& public_key_der, + const std::string& certificate)); + MOCK_METHOD2(RegisterCertificate, bool(const std::string& username, + const std::string& certificate)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockKeyStore); +}; + +} // namespace attestation + +#endif // ATTESTATION_SERVER_MOCK_KEY_STORE_H_ diff --git a/attestation/server/org.chromium.Attestation.conf b/attestation/server/org.chromium.Attestation.conf new file mode 100644 index 0000000..2adae8e --- /dev/null +++ b/attestation/server/org.chromium.Attestation.conf @@ -0,0 +1,15 @@ +<!DOCTYPE busconfig PUBLIC + "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<busconfig> + <policy user="attestation"> + <allow own="org.chromium.Attestation" /> + <allow send_destination="org.chromium.Attestation" /> + </policy> + <policy context="default"> + <allow send_destination="org.chromium.Attestation" /> + <!-- introspection denied --> + <deny send_destination="org.chromium.Attestation" + send_interface="org.freedesktop.DBus.Introspectable" /> + </policy> +</busconfig> diff --git a/attestation/server/pkcs11_key_store.cc b/attestation/server/pkcs11_key_store.cc new file mode 100644 index 0000000..45be47e --- /dev/null +++ b/attestation/server/pkcs11_key_store.cc @@ -0,0 +1,685 @@ +// +// 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/pkcs11_key_store.h" + +#include <memory> +#include <string> + +#include <base/bind.h> +#include <base/callback.h> +#include <base/files/file_path.h> +#include <base/logging.h> +#include <base/stl_util.h> +#include <base/strings/string_util.h> +#include <chaps/isolate.h> +#include <chaps/pkcs11/cryptoki.h> +#include <chaps/token_manager_client.h> +#include <brillo/cryptohome.h> +#include <crypto/scoped_openssl_types.h> +#include <openssl/rsa.h> +#include <openssl/sha.h> +#include <openssl/x509.h> + +namespace { + +std::string Sha1(const std::string& input) { + unsigned char output[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast<const unsigned char*>(input.data()), input.size(), + output); + return std::string(reinterpret_cast<char*>(output), SHA_DIGEST_LENGTH); +} + +} // namespace + +namespace attestation { + +typedef crypto::ScopedOpenSSL<X509, X509_free> ScopedX509; + +// An arbitrary application ID to identify PKCS #11 objects. +const char kApplicationID[] = "CrOS_d5bbc079d2497110feadfc97c40d718ae46f4658"; + +// A helper class to scope a PKCS #11 session. +class ScopedSession { + public: + explicit ScopedSession(CK_SLOT_ID slot) : handle_(CK_INVALID_HANDLE) { + CK_RV rv = C_Initialize(nullptr); + if (rv != CKR_OK && rv != CKR_CRYPTOKI_ALREADY_INITIALIZED) { + // This may be normal in a test environment. + LOG(INFO) << "PKCS #11 is not available."; + return; + } + CK_FLAGS flags = CKF_RW_SESSION | CKF_SERIAL_SESSION; + if (C_OpenSession(slot, flags, nullptr, nullptr, &handle_) != CKR_OK) { + LOG(ERROR) << "Failed to open PKCS #11 session."; + return; + } + } + + ~ScopedSession() { + if (IsValid() && (C_CloseSession(handle_) != CKR_OK)) { + LOG(WARNING) << "Failed to close PKCS #11 session."; + handle_ = CK_INVALID_HANDLE; + } + } + + CK_SESSION_HANDLE handle() const { + return handle_; + } + + bool IsValid() const { + return (handle_ != CK_INVALID_HANDLE); + } + + private: + CK_SESSION_HANDLE handle_; + + DISALLOW_COPY_AND_ASSIGN(ScopedSession); +}; + +Pkcs11KeyStore::Pkcs11KeyStore(chaps::TokenManagerClient* token_manager) + : token_manager_(token_manager) {} + +Pkcs11KeyStore::~Pkcs11KeyStore() {} + +bool Pkcs11KeyStore::Read(const std::string& username, + const std::string& key_name, + std::string* key_data) { + CK_SLOT_ID slot; + if (!GetUserSlot(username, &slot)) { + LOG(ERROR) << "Pkcs11KeyStore: No token for user."; + return false; + } + ScopedSession session(slot); + if (!session.IsValid()) { + LOG(ERROR) << "Pkcs11KeyStore: Failed to open token session."; + return false; + } + CK_OBJECT_HANDLE key_handle = FindObject(session.handle(), key_name); + if (key_handle == CK_INVALID_HANDLE) { + LOG(WARNING) << "Pkcs11KeyStore: Key does not exist: " << key_name; + return false; + } + // First get the attribute with a NULL buffer which will give us the length. + CK_ATTRIBUTE attribute = {CKA_VALUE, nullptr, 0}; + if (C_GetAttributeValue(session.handle(), + key_handle, + &attribute, 1) != CKR_OK) { + LOG(ERROR) << "Pkcs11KeyStore: Failed to read key data: " << key_name; + return false; + } + key_data->resize(attribute.ulValueLen); + attribute.pValue = string_as_array(key_data); + if (C_GetAttributeValue(session.handle(), + key_handle, + &attribute, 1) != CKR_OK) { + LOG(ERROR) << "Pkcs11KeyStore: Failed to read key data: " << key_name; + return false; + } + key_data->resize(attribute.ulValueLen); + return true; +} + +bool Pkcs11KeyStore::Write(const std::string& username, + const std::string& key_name, + const std::string& key_data) { + // Delete any existing key with the same name. + if (!Delete(username, key_name)) { + return false; + } + CK_SLOT_ID slot; + if (!GetUserSlot(username, &slot)) { + LOG(ERROR) << "Pkcs11KeyStore: No token for user."; + return false; + } + ScopedSession session(slot); + if (!session.IsValid()) { + LOG(ERROR) << "Pkcs11KeyStore: Failed to open token session."; + return false; + } + std::string mutable_key_name(key_name); + std::string mutable_key_data(key_data); + std::string mutable_application_id(kApplicationID); + // Create a new data object for the key. + CK_OBJECT_CLASS object_class = CKO_DATA; + CK_BBOOL true_value = CK_TRUE; + CK_BBOOL false_value = CK_FALSE; + CK_ATTRIBUTE attributes[] = { + {CKA_CLASS, &object_class, sizeof(object_class)}, + { + CKA_LABEL, + string_as_array(&mutable_key_name), + mutable_key_name.size() + }, + { + CKA_VALUE, + string_as_array(&mutable_key_data), + mutable_key_data.size() + }, + { + CKA_APPLICATION, + string_as_array(&mutable_application_id), + mutable_application_id.size() + }, + {CKA_TOKEN, &true_value, sizeof(true_value)}, + {CKA_PRIVATE, &true_value, sizeof(true_value)}, + {CKA_MODIFIABLE, &false_value, sizeof(false_value)} + }; + CK_OBJECT_HANDLE key_handle = CK_INVALID_HANDLE; + if (C_CreateObject(session.handle(), + attributes, + arraysize(attributes), + &key_handle) != CKR_OK) { + LOG(ERROR) << "Pkcs11KeyStore: Failed to write key data: " << key_name; + return false; + } + return true; +} + +bool Pkcs11KeyStore::Delete(const std::string& username, + const std::string& key_name) { + CK_SLOT_ID slot; + if (!GetUserSlot(username, &slot)) { + LOG(ERROR) << "Pkcs11KeyStore: No token for user."; + return false; + } + ScopedSession session(slot); + if (!session.IsValid()) { + LOG(ERROR) << "Pkcs11KeyStore: Failed to open token session."; + return false; + } + CK_OBJECT_HANDLE key_handle = FindObject(session.handle(), key_name); + if (key_handle != CK_INVALID_HANDLE) { + if (C_DestroyObject(session.handle(), key_handle) != CKR_OK) { + LOG(ERROR) << "Pkcs11KeyStore: Failed to delete key data."; + return false; + } + } + return true; +} + +bool Pkcs11KeyStore::DeleteByPrefix(const std::string& username, + const std::string& key_prefix) { + CK_SLOT_ID slot; + if (!GetUserSlot(username, &slot)) { + LOG(ERROR) << "Pkcs11KeyStore: No token for user."; + return false; + } + ScopedSession session(slot); + if (!session.IsValid()) { + LOG(ERROR) << "Pkcs11KeyStore: Failed to open token session."; + return false; + } + EnumObjectsCallback callback = base::Bind( + &Pkcs11KeyStore::DeleteIfMatchesPrefix, + base::Unretained(this), + session.handle(), + key_prefix); + if (!EnumObjects(session.handle(), callback)) { + LOG(ERROR) << "Pkcs11KeyStore: Failed to delete key data."; + return false; + } + return true; +} + +bool Pkcs11KeyStore::Register(const std::string& username, + const std::string& label, + KeyType key_type, + KeyUsage key_usage, + const std::string& private_key_blob, + const std::string& public_key_der, + const std::string& certificate) { + const CK_ATTRIBUTE_TYPE kKeyBlobAttribute = CKA_VENDOR_DEFINED + 1; + + if (key_type != KEY_TYPE_RSA) { + LOG(ERROR) << "Pkcs11KeyStore: Only RSA supported."; + return false; + } + CK_SLOT_ID slot; + if (!GetUserSlot(username, &slot)) { + LOG(ERROR) << "Pkcs11KeyStore: No token for user."; + return false; + } + ScopedSession session(slot); + if (!session.IsValid()) { + LOG(ERROR) << "Pkcs11KeyStore: Failed to open token session."; + return false; + } + + // Extract the modulus from the public key. + const unsigned char* asn1_ptr = reinterpret_cast<const unsigned char*>( + public_key_der.data()); + crypto::ScopedRSA public_key(d2i_RSAPublicKey(nullptr, + &asn1_ptr, + public_key_der.size())); + if (!public_key.get()) { + LOG(ERROR) << "Pkcs11KeyStore: Failed to decode public key."; + return false; + } + std::string modulus(BN_num_bytes(public_key.get()->n), 0); + int length = BN_bn2bin(public_key.get()->n, reinterpret_cast<unsigned char*>( + string_as_array(&modulus))); + if (length <= 0) { + LOG(ERROR) << "Pkcs11KeyStore: Failed to extract public key modulus."; + return false; + } + modulus.resize(length); + + // Construct a PKCS #11 template for the public key object. + CK_BBOOL true_value = CK_TRUE; + CK_BBOOL false_value = CK_FALSE; + CK_KEY_TYPE p11_key_type = CKK_RSA; + CK_OBJECT_CLASS public_key_class = CKO_PUBLIC_KEY; + std::string id = Sha1(modulus); + std::string mutable_label(label); + CK_ULONG modulus_bits = modulus.size() * 8; + CK_BBOOL sign_usage = (key_usage == KEY_USAGE_SIGN); + CK_BBOOL decrypt_usage = (key_usage == KEY_USAGE_DECRYPT); + unsigned char public_exponent[] = {1, 0, 1}; + CK_ATTRIBUTE public_key_attributes[] = { + {CKA_CLASS, &public_key_class, sizeof(public_key_class)}, + {CKA_TOKEN, &true_value, sizeof(true_value)}, + {CKA_DERIVE, &false_value, sizeof(false_value)}, + {CKA_WRAP, &false_value, sizeof(false_value)}, + {CKA_VERIFY, &sign_usage, sizeof(sign_usage)}, + {CKA_VERIFY_RECOVER, &false_value, sizeof(false_value)}, + {CKA_ENCRYPT, &decrypt_usage, sizeof(decrypt_usage)}, + {CKA_KEY_TYPE, &p11_key_type, sizeof(p11_key_type)}, + {CKA_ID, string_as_array(&id), id.size()}, + {CKA_LABEL, string_as_array(&mutable_label), mutable_label.size()}, + {CKA_MODULUS_BITS, &modulus_bits, sizeof(modulus_bits)}, + {CKA_PUBLIC_EXPONENT, public_exponent, arraysize(public_exponent)}, + {CKA_MODULUS, string_as_array(&modulus), modulus.size()} + }; + + CK_OBJECT_HANDLE object_handle = CK_INVALID_HANDLE; + if (C_CreateObject(session.handle(), + public_key_attributes, + arraysize(public_key_attributes), + &object_handle) != CKR_OK) { + LOG(ERROR) << "Pkcs11KeyStore: Failed to create public key object."; + return false; + } + + // Construct a PKCS #11 template for the private key object. + std::string mutable_private_key_blob(private_key_blob); + CK_OBJECT_CLASS private_key_class = CKO_PRIVATE_KEY; + CK_ATTRIBUTE private_key_attributes[] = { + {CKA_CLASS, &private_key_class, sizeof(private_key_class)}, + {CKA_TOKEN, &true_value, sizeof(true_value)}, + {CKA_PRIVATE, &true_value, sizeof(true_value)}, + {CKA_SENSITIVE, &true_value, sizeof(true_value)}, + {CKA_EXTRACTABLE, &false_value, sizeof(false_value)}, + {CKA_DERIVE, &false_value, sizeof(false_value)}, + {CKA_UNWRAP, &false_value, sizeof(false_value)}, + {CKA_SIGN, &sign_usage, sizeof(sign_usage)}, + {CKA_SIGN_RECOVER, &false_value, sizeof(false_value)}, + {CKA_DECRYPT, &decrypt_usage, sizeof(decrypt_usage)}, + {CKA_KEY_TYPE, &p11_key_type, sizeof(p11_key_type)}, + {CKA_ID, string_as_array(&id), id.size()}, + {CKA_LABEL, string_as_array(&mutable_label), mutable_label.size()}, + {CKA_PUBLIC_EXPONENT, public_exponent, arraysize(public_exponent)}, + {CKA_MODULUS, string_as_array(&modulus), modulus.size()}, + { + kKeyBlobAttribute, + string_as_array(&mutable_private_key_blob), + mutable_private_key_blob.size() + } + }; + + if (C_CreateObject(session.handle(), + private_key_attributes, + arraysize(private_key_attributes), + &object_handle) != CKR_OK) { + LOG(ERROR) << "Pkcs11KeyStore: Failed to create private key object."; + return false; + } + + if (!certificate.empty()) { + std::string subject; + std::string issuer; + std::string serial_number; + if (!GetCertificateFields(certificate, &subject, &issuer, &serial_number)) { + LOG(WARNING) << "Pkcs11KeyStore: Failed to find certificate fields."; + } + // Construct a PKCS #11 template for a certificate object. + std::string mutable_certificate = certificate; + CK_OBJECT_CLASS certificate_class = CKO_CERTIFICATE; + CK_CERTIFICATE_TYPE certificate_type = CKC_X_509; + CK_ATTRIBUTE certificate_attributes[] = { + {CKA_CLASS, &certificate_class, sizeof(certificate_class)}, + {CKA_TOKEN, &true_value, sizeof(true_value)}, + {CKA_PRIVATE, &false_value, sizeof(false_value)}, + {CKA_ID, string_as_array(&id), id.size()}, + {CKA_LABEL, string_as_array(&mutable_label), mutable_label.size()}, + {CKA_CERTIFICATE_TYPE, &certificate_type, sizeof(certificate_type)}, + {CKA_SUBJECT, string_as_array(&subject), subject.size()}, + {CKA_ISSUER, string_as_array(&issuer), issuer.size()}, + { + CKA_SERIAL_NUMBER, + string_as_array(&serial_number), + serial_number.size() + }, + { + CKA_VALUE, + string_as_array(&mutable_certificate), + mutable_certificate.size() + } + }; + + if (C_CreateObject(session.handle(), + certificate_attributes, + arraysize(certificate_attributes), + &object_handle) != CKR_OK) { + LOG(ERROR) << "Pkcs11KeyStore: Failed to create certificate object."; + return false; + } + } + + // Close all sessions in an attempt to trigger other modules to find the new + // objects. + C_CloseAllSessions(slot); + + return true; +} + +bool Pkcs11KeyStore::RegisterCertificate(const std::string& username, + const std::string& certificate) { + CK_SLOT_ID slot; + if (!GetUserSlot(username, &slot)) { + LOG(ERROR) << "Pkcs11KeyStore: No token for user."; + return false; + } + ScopedSession session(slot); + if (!session.IsValid()) { + LOG(ERROR) << "Pkcs11KeyStore: Failed to open token session."; + return false; + } + + if (DoesCertificateExist(session.handle(), certificate)) { + LOG(INFO) << "Pkcs11KeyStore: Certificate already exists."; + return true; + } + std::string subject; + std::string issuer; + std::string serial_number; + if (!GetCertificateFields(certificate, &subject, &issuer, &serial_number)) { + LOG(WARNING) << "Pkcs11KeyStore: Failed to find certificate fields."; + } + // Construct a PKCS #11 template for a certificate object. + std::string mutable_certificate = certificate; + CK_OBJECT_CLASS certificate_class = CKO_CERTIFICATE; + CK_CERTIFICATE_TYPE certificate_type = CKC_X_509; + CK_BBOOL true_value = CK_TRUE; + CK_BBOOL false_value = CK_FALSE; + CK_ATTRIBUTE certificate_attributes[] = { + {CKA_CLASS, &certificate_class, sizeof(certificate_class)}, + {CKA_TOKEN, &true_value, sizeof(true_value)}, + {CKA_PRIVATE, &false_value, sizeof(false_value)}, + {CKA_CERTIFICATE_TYPE, &certificate_type, sizeof(certificate_type)}, + {CKA_SUBJECT, string_as_array(&subject), subject.size()}, + {CKA_ISSUER, string_as_array(&issuer), issuer.size()}, + {CKA_SERIAL_NUMBER, string_as_array(&serial_number), serial_number.size()}, + { + CKA_VALUE, + string_as_array(&mutable_certificate), + mutable_certificate.size() + } + }; + CK_OBJECT_HANDLE object_handle = CK_INVALID_HANDLE; + if (C_CreateObject(session.handle(), + certificate_attributes, + arraysize(certificate_attributes), + &object_handle) != CKR_OK) { + LOG(ERROR) << "Pkcs11KeyStore: Failed to create certificate object."; + return false; + } + return true; +} + +CK_OBJECT_HANDLE Pkcs11KeyStore::FindObject(CK_SESSION_HANDLE session_handle, + const std::string& key_name) { + // Assemble a search template. + std::string mutable_key_name(key_name); + std::string mutable_application_id(kApplicationID); + CK_OBJECT_CLASS object_class = CKO_DATA; + CK_BBOOL true_value = CK_TRUE; + CK_BBOOL false_value = CK_FALSE; + CK_ATTRIBUTE attributes[] = { + {CKA_CLASS, &object_class, sizeof(object_class)}, + { + CKA_LABEL, + string_as_array(&mutable_key_name), + mutable_key_name.size() + }, + { + CKA_APPLICATION, + string_as_array(&mutable_application_id), + mutable_application_id.size() + }, + {CKA_TOKEN, &true_value, sizeof(true_value)}, + {CKA_PRIVATE, &true_value, sizeof(true_value)}, + {CKA_MODIFIABLE, &false_value, sizeof(false_value)} + }; + CK_OBJECT_HANDLE key_handle = CK_INVALID_HANDLE; + CK_ULONG count = 0; + if ((C_FindObjectsInit(session_handle, + attributes, + arraysize(attributes)) != CKR_OK) || + (C_FindObjects(session_handle, &key_handle, 1, &count) != CKR_OK) || + (C_FindObjectsFinal(session_handle) != CKR_OK)) { + LOG(ERROR) << "Key search failed: " << key_name; + return CK_INVALID_HANDLE; + } + if (count == 1) + return key_handle; + return CK_INVALID_HANDLE; +} + +bool Pkcs11KeyStore::GetUserSlot(const std::string& username, + CK_SLOT_ID_PTR slot) { + const char kChapsDaemonName[] = "chaps"; + const char kChapsSystemToken[] = "/var/lib/chaps"; + base::FilePath token_path = username.empty() ? + base::FilePath(kChapsSystemToken) : + brillo::cryptohome::home::GetDaemonPath(username, kChapsDaemonName); + CK_RV rv; + rv = C_Initialize(nullptr); + if (rv != CKR_OK && rv != CKR_CRYPTOKI_ALREADY_INITIALIZED) { + LOG(WARNING) << __func__ << ": C_Initialize failed."; + return false; + } + CK_ULONG num_slots = 0; + rv = C_GetSlotList(CK_TRUE, nullptr, &num_slots); + if (rv != CKR_OK) { + LOG(WARNING) << __func__ << ": C_GetSlotList(nullptr) failed."; + return false; + } + std::unique_ptr<CK_SLOT_ID[]> slot_list(new CK_SLOT_ID[num_slots]); + rv = C_GetSlotList(CK_TRUE, slot_list.get(), &num_slots); + if (rv != CKR_OK) { + LOG(WARNING) << __func__ << ": C_GetSlotList failed."; + return false; + } + // Look through all slots for |token_path|. + for (CK_ULONG i = 0; i < num_slots; ++i) { + base::FilePath slot_path; + if (token_manager_->GetTokenPath( + chaps::IsolateCredentialManager::GetDefaultIsolateCredential(), + slot_list[i], + &slot_path) && (token_path == slot_path)) { + *slot = slot_list[i]; + return true; + } + } + LOG(WARNING) << __func__ << ": Path not found."; + return false; +} + +bool Pkcs11KeyStore::EnumObjects( + CK_SESSION_HANDLE session_handle, + const Pkcs11KeyStore::EnumObjectsCallback& callback) { + std::string mutable_application_id(kApplicationID); + // Assemble a search template. + CK_OBJECT_CLASS object_class = CKO_DATA; + CK_BBOOL true_value = CK_TRUE; + CK_BBOOL false_value = CK_FALSE; + CK_ATTRIBUTE attributes[] = { + {CKA_CLASS, &object_class, sizeof(object_class)}, + { + CKA_APPLICATION, + string_as_array(&mutable_application_id), + mutable_application_id.size() + }, + {CKA_TOKEN, &true_value, sizeof(true_value)}, + {CKA_PRIVATE, &true_value, sizeof(true_value)}, + {CKA_MODIFIABLE, &false_value, sizeof(false_value)} + }; + const CK_ULONG kMaxHandles = 100; // Arbitrary. + CK_OBJECT_HANDLE handles[kMaxHandles]; + CK_ULONG count = 0; + if ((C_FindObjectsInit(session_handle, + attributes, + arraysize(attributes)) != CKR_OK) || + (C_FindObjects(session_handle, handles, kMaxHandles, &count) != CKR_OK)) { + LOG(ERROR) << "Key search failed."; + return false; + } + while (count > 0) { + for (CK_ULONG i = 0; i < count; ++i) { + std::string key_name; + if (!GetKeyName(session_handle, handles[i], &key_name)) { + LOG(WARNING) << "Found key object but failed to get name."; + continue; + } + if (!callback.Run(key_name, handles[i])) + return false; + } + if (C_FindObjects(session_handle, handles, kMaxHandles, &count) != CKR_OK) { + LOG(ERROR) << "Key search continuation failed."; + return false; + } + } + if (C_FindObjectsFinal(session_handle) != CKR_OK) { + LOG(WARNING) << "Failed to finalize key search."; + } + return true; +} + +bool Pkcs11KeyStore::GetKeyName(CK_SESSION_HANDLE session_handle, + CK_OBJECT_HANDLE object_handle, + std::string* key_name) { + CK_ATTRIBUTE attribute = {CKA_LABEL, nullptr, 0}; + if (C_GetAttributeValue(session_handle, object_handle, &attribute, 1) != + CKR_OK) { + LOG(ERROR) << "C_GetAttributeValue(CKA_LABEL) [length] failed."; + return false; + } + key_name->resize(attribute.ulValueLen); + attribute.pValue = string_as_array(key_name); + if (C_GetAttributeValue(session_handle, object_handle, &attribute, 1) != + CKR_OK) { + LOG(ERROR) << "C_GetAttributeValue(CKA_LABEL) failed."; + return false; + } + return true; +} + +bool Pkcs11KeyStore::DeleteIfMatchesPrefix(CK_SESSION_HANDLE session_handle, + const std::string& key_prefix, + const std::string& key_name, + CK_OBJECT_HANDLE object_handle) { + if (base::StartsWithASCII(key_name, key_prefix, true /*case_sensitive*/)) { + if (C_DestroyObject(session_handle, object_handle) != CKR_OK) { + LOG(ERROR) << "C_DestroyObject failed."; + return false; + } + } + return true; +} + +bool Pkcs11KeyStore::GetCertificateFields(const std::string& certificate, + std::string* subject, + std::string* issuer, + std::string* serial_number) { + const unsigned char* asn1_ptr = reinterpret_cast<const unsigned char*>( + certificate.data()); + ScopedX509 x509(d2i_X509(nullptr, &asn1_ptr, certificate.size())); + if (!x509.get() || !x509->cert_info || !x509->cert_info->subject) { + LOG(WARNING) << "Pkcs11KeyStore: Failed to decode certificate."; + return false; + } + unsigned char* subject_buffer = nullptr; + int length = i2d_X509_NAME(x509->cert_info->subject, &subject_buffer); + crypto::ScopedOpenSSLBytes scoped_subject_buffer(subject_buffer); + if (length <= 0) { + LOG(WARNING) << "Pkcs11KeyStore: Failed to encode certificate subject."; + return false; + } + subject->assign(reinterpret_cast<char*>(subject_buffer), length); + + unsigned char* issuer_buffer = nullptr; + length = i2d_X509_NAME(x509->cert_info->issuer, &issuer_buffer); + crypto::ScopedOpenSSLBytes scoped_issuer_buffer(issuer_buffer); + if (length <= 0) { + LOG(WARNING) << "Pkcs11KeyStore: Failed to encode certificate issuer."; + return false; + } + issuer->assign(reinterpret_cast<char*>(issuer_buffer), length); + + unsigned char* serial_number_buffer = nullptr; + length = i2d_ASN1_INTEGER(x509->cert_info->serialNumber, + &serial_number_buffer); + crypto::ScopedOpenSSLBytes scoped_serial_number_buffer(serial_number_buffer); + if (length <= 0) { + LOG(WARNING) << "Pkcs11KeyStore: Failed to encode certificate serial " + "number."; + return false; + } + serial_number->assign(reinterpret_cast<char*>(serial_number_buffer), length); + return true; +} + +bool Pkcs11KeyStore::DoesCertificateExist( + CK_SESSION_HANDLE session_handle, + const std::string& certificate) { + CK_OBJECT_CLASS object_class = CKO_CERTIFICATE; + CK_BBOOL true_value = CK_TRUE; + CK_BBOOL false_value = CK_FALSE; + std::string mutable_certificate = certificate; + CK_ATTRIBUTE attributes[] = { + {CKA_CLASS, &object_class, sizeof(object_class)}, + {CKA_TOKEN, &true_value, sizeof(true_value)}, + {CKA_PRIVATE, &false_value, sizeof(false_value)}, + { + CKA_VALUE, + string_as_array(&mutable_certificate), + mutable_certificate.size() + } + }; + CK_OBJECT_HANDLE object_handle = CK_INVALID_HANDLE; + CK_ULONG count = 0; + if ((C_FindObjectsInit(session_handle, + attributes, + arraysize(attributes)) != CKR_OK) || + (C_FindObjects(session_handle, &object_handle, 1, &count) != CKR_OK) || + (C_FindObjectsFinal(session_handle) != CKR_OK)) { + return false; + } + return (count > 0); +} + +} // namespace attestation diff --git a/attestation/server/pkcs11_key_store.h b/attestation/server/pkcs11_key_store.h new file mode 100644 index 0000000..a1ccd7e --- /dev/null +++ b/attestation/server/pkcs11_key_store.h @@ -0,0 +1,123 @@ +// +// 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. +// + +#ifndef ATTESTATION_SERVER_PKCS11_KEY_STORE_H_ +#define ATTESTATION_SERVER_PKCS11_KEY_STORE_H_ + +#include "attestation/server/key_store.h" + +#include <string> + +#include <base/callback_forward.h> +#include <base/macros.h> +#include <chaps/pkcs11/cryptoki.h> +#include <chaps/token_manager_client.h> + +namespace attestation { + +// This class uses a PKCS #11 token as storage for key data. The key data is +// stored in data objects with the following attributes: +// CKA_CLASS - CKO_DATA +// CKA_LABEL - A key name. +// CKA_VALUE - Binary key data (opaque to this class and the PKCS #11 token). +// CKA_APPLICATION - A constant value associated with this class. +// CKA_TOKEN - True +// CKA_PRIVATE - True +// CKA_MODIFIABLE - False +// There is no barrier between the objects created by this class and any other +// objects residing in the same token. In practice, this means that any +// component with access to the PKCS #11 token also has access to read or delete +// key data. +class Pkcs11KeyStore : public KeyStore { + public: + // Does not take ownership of pointers. + explicit Pkcs11KeyStore(chaps::TokenManagerClient* token_manager); + ~Pkcs11KeyStore() override; + + // KeyStore interface. + bool Read(const std::string& username, + const std::string& key_name, + std::string* key_data) override; + bool Write(const std::string& username, + const std::string& key_name, + const std::string& key_data) override; + bool Delete(const std::string& username, + const std::string& key_name) override; + bool DeleteByPrefix(const std::string& username, + const std::string& key_prefix) override; + bool Register(const std::string& username, + const std::string& label, + KeyType key_type, + KeyUsage key_usage, + const std::string& private_key_blob, + const std::string& public_key_der, + const std::string& certificate) override; + bool RegisterCertificate(const std::string& username, + const std::string& certificate) override; + + private: + using EnumObjectsCallback = + base::Callback<bool(const std::string& key_name, + CK_OBJECT_HANDLE object_handle)>; + + // Searches for a PKCS #11 object for a given key name. If one exists, the + // object handle is returned, otherwise CK_INVALID_HANDLE is returned. + CK_OBJECT_HANDLE FindObject(CK_SESSION_HANDLE session_handle, + const std::string& key_name); + + // Gets a slot for the given |username| if |is_user_specific| or the system + // slot otherwise. Returns false if no appropriate slot is found. + bool GetUserSlot(const std::string& username, + CK_SLOT_ID_PTR slot); + + // Enumerates all PKCS #11 objects associated with keys. The |callback| is + // called once for each object. + bool EnumObjects(CK_SESSION_HANDLE session_handle, + const EnumObjectsCallback& callback); + + // Looks up the key name for the given |object_handle| which is associated + // with a key. Returns true on success. + bool GetKeyName(CK_SESSION_HANDLE session_handle, + CK_OBJECT_HANDLE object_handle, + std::string* key_name); + + // An EnumObjectsCallback for use with DeleteByPrefix. Destroys the key + // object identified by |object_handle| if |key_name| matches |key_prefix|. + // Returns true on success. + bool DeleteIfMatchesPrefix(CK_SESSION_HANDLE session_handle, + const std::string& key_prefix, + const std::string& key_name, + CK_OBJECT_HANDLE object_handle); + + // Extracts the |subject|, |issuer|, and |serial_number| information from an + // X.509 |certificate|. Returns false if the value cannot be determined. + bool GetCertificateFields(const std::string& certificate, + std::string* subject, + std::string* issuer, + std::string* serial_number); + + // Returns true iff the given certificate already exists in the token. + bool DoesCertificateExist(CK_SESSION_HANDLE session_handle, + const std::string& certificate); + + chaps::TokenManagerClient* token_manager_; + + DISALLOW_COPY_AND_ASSIGN(Pkcs11KeyStore); +}; + +} // namespace attestation + +#endif // ATTESTATION_SERVER_PKCS11_KEY_STORE_H_ diff --git a/attestation/server/pkcs11_key_store_test.cc b/attestation/server/pkcs11_key_store_test.cc new file mode 100644 index 0000000..5cd4e82 --- /dev/null +++ b/attestation/server/pkcs11_key_store_test.cc @@ -0,0 +1,598 @@ +// +// 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/pkcs11_key_store.h" + +#include <map> +#include <string> +#include <vector> + +#include <base/logging.h> +#include <base/strings/string_number_conversions.h> +#include <chaps/attributes.h> +#include <chaps/chaps_proxy_mock.h> +#include <chaps/token_manager_client_mock.h> +#include <brillo/cryptohome.h> +#include <brillo/map_utils.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::SetArgumentPointee; + +namespace { + +const uint64_t kSession = 7; // Arbitrary non-zero value. +const char kDefaultUser[] = "test_user"; + +const char kValidPublicKeyHex[] = + "3082010A0282010100" + "961037BC12D2A298BEBF06B2D5F8C9B64B832A2237F8CF27D5F96407A6041A4D" + "AD383CB5F88E625F412E8ACD5E9D69DF0F4FA81FCE7955829A38366CBBA5A2B1" + "CE3B48C14B59E9F094B51F0A39155874C8DE18A0C299EBF7A88114F806BE4F25" + "3C29A509B10E4B19E31675AFE3B2DA77077D94F43D8CE61C205781ED04D183B4" + "C349F61B1956C64B5398A3A98FAFF17D1B3D9120C832763EDFC8F4137F6EFBEF" + "46D8F6DE03BD00E49DEF987C10BDD5B6F8758B6A855C23C982DDA14D8F0F2B74" + "E6DEFA7EEE5A6FC717EB0FF103CB8049F693A2C8A5039EF1F5C025DC44BD8435" + "E8D8375DADE00E0C0F5C196E04B8483CC98B1D5B03DCD7E0048B2AB343FFC11F" + "0203" + "010001"; + +const char kValidCertificateHex[] = + "3082040f308202f7a003020102020900bd0f8fd6bf496b67300d06092a864886" + "f70d01010b050030819d310b3009060355040613025553311330110603550408" + "0c0a43616c69666f726e69613116301406035504070c0d4d6f756e7461696e20" + "5669657731133011060355040a0c0a4368726f6d69756d4f533111300f060355" + "040b0c08556e6974546573743117301506035504030c0e506b637331314b6579" + "53746f72653120301e06092a864886f70d010901161174657374406368726f6d" + "69756d2e6f7267301e170d3135303231383137303132345a170d313731313133" + "3137303132345a30819d310b3009060355040613025553311330110603550408" + "0c0a43616c69666f726e69613116301406035504070c0d4d6f756e7461696e20" + "5669657731133011060355040a0c0a4368726f6d69756d4f533111300f060355" + "040b0c08556e6974546573743117301506035504030c0e506b637331314b6579" + "53746f72653120301e06092a864886f70d010901161174657374406368726f6d" + "69756d2e6f726730820122300d06092a864886f70d01010105000382010f0030" + "82010a0282010100a8fb9e12b1e5298b9a24fabc3901d00c32057392c763836e" + "0b55cff8e67d39b9b9853920fd615688b3e13c03a10cb5668187819172d1d269" + "70f0ff8d4371ac581f6970a0e43a1d0d61a94741a771fe86aee45ab0ca059b1f" + "c067f7416f08544cc4d08ec884b6d4327bb3ec0dc0789639375bd159df0efd87" + "1cf4d605778c7a68c96b94cf0a6c29f9a23bc027e8250084eb2dfca817b20f57" + "a6fe09513f884389db7b90788aea70c6e1638f24e39553ac0f859e585965c425" + "9ed7b9680fde3e059f254d8c9494f6ab425ede80d63366dfcb7cc311f5bc6fb0" + "1c27d81f4c5112d04b7614c37ba19c014916816372c773e4e44564fac34565ad" + "ebf38fe56c1413170203010001a350304e301d0603551d0e04160414fe13c7db" + "459bd2881e9113198e1f072e16cea144301f0603551d23041830168014fe13c7" + "db459bd2881e9113198e1f072e16cea144300c0603551d13040530030101ff30" + "0d06092a864886f70d01010b05000382010100a163d636ac64bd6f67eca53708" + "5f92abc993a40fd0c0222a56b262c29f88057a3edf9abac024756ad85d7453d8" + "4782e0be65d176aecfb0fbfc88ca567d17124fa190cb5ce832264360dd6daee1" + "e121428de28dda0b8ba117a1be3cf438efd060a3b5fc812e7eba70cec12cb609" + "738fc7d0912546c42b5aaadb142adce2167c7f30cd9e0049687d384334335aff" + "72aebd1745a0aac4be816365969347f064f36f7fdec69f970f28b87061650470" + "c63be8475bb23d0485985fb77c7cdd9d9fe008211a9ddd0fe68efb0b47cf629c" + "941d31e3c2f88e670e7e4ef1129febad000e6a16222779fbfe34641e5243ca38" + "74e2ad06f9585a00bec014744d3175ecc4808d"; + +std::string HexDecode(const std::string hex) { + std::vector<uint8> output; + CHECK(base::HexStringToBytes(hex, &output)); + return std::string(reinterpret_cast<char*>(output.data()), output.size()); +} + +class ScopedFakeSalt { + public: + ScopedFakeSalt() : salt_(128, 0) { + brillo::cryptohome::home::SetSystemSalt(&salt_); + } + ~ScopedFakeSalt() { + brillo::cryptohome::home::SetSystemSalt(nullptr); + } + + private: + std::string salt_; +}; + +class ScopedDisableVerboseLogging { + public: + ScopedDisableVerboseLogging() + : original_severity_(logging::GetMinLogLevel()) { + logging::SetMinLogLevel(logging::LOG_INFO); + } + ~ScopedDisableVerboseLogging() { + logging::SetMinLogLevel(original_severity_); + } + + private: + logging::LogSeverity original_severity_; +}; + +} // namespace + +namespace attestation { + +typedef chaps::ChapsProxyMock Pkcs11Mock; + +// Implements a fake PKCS #11 object store. Labeled data blobs can be stored +// and later retrieved. The mocked interface is ChapsInterface so these +// tests must be linked with the Chaps PKCS #11 library. The mock class itself +// is part of the Chaps package; it is reused here to avoid duplication (see +// chaps_proxy_mock.h). +class KeyStoreTest : public testing::Test { + public: + KeyStoreTest() + : pkcs11_(false), // Do not pre-initialize the mock PKCS #11 library. + // This just controls whether the first call to + // C_Initialize returns 'already initialized'. + next_handle_(1) {} + ~KeyStoreTest() override = default; + + void SetUp() override { + std::vector<uint64_t> slot_list = {0, 1}; + ON_CALL(pkcs11_, GetSlotList(_, _, _)) + .WillByDefault(DoAll(SetArgumentPointee<2>(slot_list), Return(0))); + ON_CALL(pkcs11_, OpenSession(_, _, _, _)) + .WillByDefault(DoAll(SetArgumentPointee<3>(kSession), Return(0))); + ON_CALL(pkcs11_, CloseSession(_, _)) + .WillByDefault(Return(0)); + ON_CALL(pkcs11_, CreateObject(_, _, _, _)) + .WillByDefault(Invoke(this, &KeyStoreTest::CreateObject)); + ON_CALL(pkcs11_, DestroyObject(_, _, _)) + .WillByDefault(Invoke(this, &KeyStoreTest::DestroyObject)); + ON_CALL(pkcs11_, GetAttributeValue(_, _, _, _, _)) + .WillByDefault(Invoke(this, &KeyStoreTest::GetAttributeValue)); + ON_CALL(pkcs11_, SetAttributeValue(_, _, _, _)) + .WillByDefault(Invoke(this, &KeyStoreTest::SetAttributeValue)); + ON_CALL(pkcs11_, FindObjectsInit(_, _, _)) + .WillByDefault(Invoke(this, &KeyStoreTest::FindObjectsInit)); + ON_CALL(pkcs11_, FindObjects(_, _, _, _)) + .WillByDefault(Invoke(this, &KeyStoreTest::FindObjects)); + ON_CALL(pkcs11_, FindObjectsFinal(_, _)) + .WillByDefault(Return(0)); + base::FilePath system_path("/var/lib/chaps"); + ON_CALL(token_manager_, GetTokenPath(_, 0, _)) + .WillByDefault(DoAll(SetArgumentPointee<2>(system_path), Return(true))); + base::FilePath user_path(brillo::cryptohome::home::GetDaemonPath( + kDefaultUser, "chaps")); + ON_CALL(token_manager_, GetTokenPath(_, 1, _)) + .WillByDefault(DoAll(SetArgumentPointee<2>(user_path), Return(true))); + } + + // Stores a new labeled object, only CKA_LABEL and CKA_VALUE are relevant. + virtual uint32_t CreateObject(const brillo::SecureBlob& isolate, + uint64_t session_id, + const std::vector<uint8_t>& attributes, + uint64_t* new_object_handle) { + *new_object_handle = next_handle_++; + std::string label = GetValue(attributes, CKA_LABEL); + handles_[*new_object_handle] = label; + values_[label] = GetValue(attributes, CKA_VALUE); + labels_[label] = *new_object_handle; + return CKR_OK; + } + + // Deletes a labeled object. + virtual uint32_t DestroyObject(const brillo::SecureBlob& isolate, + uint64_t session_id, + uint64_t object_handle) { + std::string label = handles_[object_handle]; + handles_.erase(object_handle); + values_.erase(label); + labels_.erase(label); + return CKR_OK; + } + + // Supports reading CKA_VALUE. + virtual uint32_t GetAttributeValue(const brillo::SecureBlob& isolate, + uint64_t session_id, + uint64_t object_handle, + const std::vector<uint8_t>& attributes_in, + std::vector<uint8_t>* attributes_out) { + std::string label = handles_[object_handle]; + std::string value = values_[label]; + chaps::Attributes parsed; + parsed.Parse(attributes_in); + if (parsed.num_attributes() == 1 && + parsed.attributes()[0].type == CKA_LABEL) + value = label; + if (parsed.num_attributes() != 1 || + (parsed.attributes()[0].type != CKA_VALUE && + parsed.attributes()[0].type != CKA_LABEL) || + (parsed.attributes()[0].pValue && + parsed.attributes()[0].ulValueLen != value.size())) + return CKR_GENERAL_ERROR; + parsed.attributes()[0].ulValueLen = value.size(); + if (parsed.attributes()[0].pValue) + memcpy(parsed.attributes()[0].pValue, value.data(), value.size()); + parsed.Serialize(attributes_out); + return CKR_OK; + } + + // Supports writing CKA_VALUE. + virtual uint32_t SetAttributeValue( + const brillo::SecureBlob& isolate, + uint64_t session_id, + uint64_t object_handle, + const std::vector<uint8_t>& attributes) { + values_[handles_[object_handle]] = GetValue(attributes, CKA_VALUE); + return CKR_OK; + } + + // Finds stored objects by CKA_LABEL or CKA_VALUE. If no CKA_LABEL or + // CKA_VALUE, find all objects. + virtual uint32_t FindObjectsInit(const brillo::SecureBlob& isolate, + uint64_t session_id, + const std::vector<uint8_t>& attributes) { + std::string label = GetValue(attributes, CKA_LABEL); + std::string value = GetValue(attributes, CKA_VALUE); + found_objects_.clear(); + if (label.empty() && value.empty()) { + // Find all objects. + found_objects_ = brillo::GetMapKeysAsVector(handles_); + } else if (!label.empty() && labels_.count(label) > 0) { + // Find only the object with |label|. + found_objects_.push_back(labels_[label]); + } else { + // Find all objects with |value|. + for (const auto& item : values_) { + if (item.second == value && labels_.count(item.first) > 0) { + found_objects_.push_back(labels_[item.first]); + } + } + } + return CKR_OK; + } + + // Reports a 'found' object based on find_status_. + virtual uint32_t FindObjects(const brillo::SecureBlob& isolate, + uint64_t session_id, + uint64_t max_object_count, + std::vector<uint64_t>* object_list) { + while (!found_objects_.empty() && object_list->size() < max_object_count) { + object_list->push_back(found_objects_.back()); + found_objects_.pop_back(); + } + return CKR_OK; + } + + protected: + NiceMock<Pkcs11Mock> pkcs11_; + NiceMock<chaps::TokenManagerClientMock> token_manager_; + + private: + // A helper to pull the value for a given attribute out of a serialized + // template. + std::string GetValue(const std::vector<uint8_t>& attributes, + CK_ATTRIBUTE_TYPE type) { + chaps::Attributes parsed; + parsed.Parse(attributes); + CK_ATTRIBUTE_PTR array = parsed.attributes(); + for (CK_ULONG i = 0; i < parsed.num_attributes(); ++i) { + if (array[i].type == type) { + if (!array[i].pValue) + return ""; + return std::string(reinterpret_cast<char*>(array[i].pValue), + array[i].ulValueLen); + } + } + return ""; + } + + std::map<std::string, std::string> values_; // The fake store: label->value + std::map<uint64_t, std::string> handles_; // The fake store: handle->label + std::map<std::string, uint64_t> labels_; // The fake store: label->handle + std::vector<uint64_t> found_objects_; // The most recent search results + uint64_t next_handle_; // Tracks handle assignment + ScopedFakeSalt fake_system_salt_; + // We want to avoid all the Chaps verbose logging. + ScopedDisableVerboseLogging no_verbose_logging; + + DISALLOW_COPY_AND_ASSIGN(KeyStoreTest); +}; + +// This test assumes that chaps in not available on the system running the test. +// The purpose of this test is to exercise the C_Initialize failure code path. +// Without a mock, the Chaps library will attempt to connect to the Chaps daemon +// unsuccessfully, resulting in a C_Initialize failure. +TEST(KeyStoreTest_NoMock, Pkcs11NotAvailable) { + chaps::TokenManagerClient token_manager; + Pkcs11KeyStore key_store(&token_manager); + std::string blob; + EXPECT_FALSE(key_store.Read(kDefaultUser, "test", &blob)); + EXPECT_FALSE(key_store.Write(kDefaultUser, "test", blob)); + EXPECT_FALSE(key_store.Read("", "test", &blob)); + EXPECT_FALSE(key_store.Write("", "test", blob)); +} + +// Exercises the key store when PKCS #11 returns success. This exercises all +// non-error-handling code paths. +TEST_F(KeyStoreTest, Pkcs11Success) { + Pkcs11KeyStore key_store(&token_manager_); + std::string blob; + EXPECT_FALSE(key_store.Read(kDefaultUser, "test", &blob)); + EXPECT_TRUE(key_store.Write(kDefaultUser, "test", "test_data")); + EXPECT_TRUE(key_store.Read(kDefaultUser, "test", &blob)); + EXPECT_EQ("test_data", blob); + // Try with a different key name. + EXPECT_FALSE(key_store.Read(kDefaultUser, "test2", &blob)); + EXPECT_TRUE(key_store.Write(kDefaultUser, "test2", "test_data2")); + EXPECT_TRUE(key_store.Read(kDefaultUser, "test2", &blob)); + EXPECT_EQ("test_data2", blob); + // Read the original key again. + EXPECT_TRUE(key_store.Read(kDefaultUser, "test", &blob)); + EXPECT_EQ("test_data", blob); + // Replace key data. + EXPECT_TRUE(key_store.Write(kDefaultUser, "test", "test_data3")); + EXPECT_TRUE(key_store.Read(kDefaultUser, "test", &blob)); + EXPECT_EQ("test_data3", blob); + // Delete key data. + EXPECT_TRUE(key_store.Delete(kDefaultUser, "test2")); + EXPECT_FALSE(key_store.Read(kDefaultUser, "test2", &blob)); + EXPECT_TRUE(key_store.Read(kDefaultUser, "test", &blob)); +} + +TEST_F(KeyStoreTest, Pkcs11Success_NoUser) { + Pkcs11KeyStore key_store(&token_manager_); + std::string blob; + EXPECT_FALSE(key_store.Read("", "test", &blob)); + EXPECT_TRUE(key_store.Write("", "test", "test_data")); + EXPECT_TRUE(key_store.Read("", "test", &blob)); + EXPECT_EQ("test_data", blob); + // Try with a different key name. + EXPECT_FALSE(key_store.Read("", "test2", &blob)); + EXPECT_TRUE(key_store.Write("", "test2", "test_data2")); + EXPECT_TRUE(key_store.Read("", "test2", &blob)); + EXPECT_EQ("test_data2", blob); + // Read the original key again. + EXPECT_TRUE(key_store.Read("", "test", &blob)); + EXPECT_EQ("test_data", blob); + // Replace key data. + EXPECT_TRUE(key_store.Write("", "test", "test_data3")); + EXPECT_TRUE(key_store.Read("", "test", &blob)); + EXPECT_EQ("test_data3", blob); + // Delete key data. + EXPECT_TRUE(key_store.Delete("", "test2")); + EXPECT_FALSE(key_store.Read("", "test2", &blob)); + EXPECT_TRUE(key_store.Read("", "test", &blob)); +} + +// Tests the key store when PKCS #11 has no token for the given user. +TEST_F(KeyStoreTest, TokenNotAvailable) { + EXPECT_CALL(token_manager_, GetTokenPath(_, _, _)) + .WillRepeatedly(Return(false)); + Pkcs11KeyStore key_store(&token_manager_); + std::string blob; + EXPECT_FALSE(key_store.Read(kDefaultUser, "test", &blob)); + EXPECT_FALSE(key_store.Write(kDefaultUser, "test", blob)); + EXPECT_FALSE(key_store.Read("", "test", &blob)); + EXPECT_FALSE(key_store.Write("", "test", blob)); +} + +// Tests the key store when PKCS #11 fails to open a session. +TEST_F(KeyStoreTest, NoSession) { + EXPECT_CALL(pkcs11_, OpenSession(_, _, _, _)) + .WillRepeatedly(Return(CKR_GENERAL_ERROR)); + Pkcs11KeyStore key_store(&token_manager_); + std::string blob; + EXPECT_FALSE(key_store.Write(kDefaultUser, "test", "test_data")); + EXPECT_FALSE(key_store.Read(kDefaultUser, "test", &blob)); +} + +// Tests the key store when PKCS #11 fails to create an object. +TEST_F(KeyStoreTest, CreateObjectFail) { + EXPECT_CALL(pkcs11_, CreateObject(_, _, _, _)) + .WillRepeatedly(Return(CKR_GENERAL_ERROR)); + Pkcs11KeyStore key_store(&token_manager_); + std::string blob; + EXPECT_FALSE(key_store.Write(kDefaultUser, "test", "test_data")); + EXPECT_FALSE(key_store.Read(kDefaultUser, "test", &blob)); +} + +// Tests the key store when PKCS #11 fails to read attribute values. +TEST_F(KeyStoreTest, ReadValueFail) { + EXPECT_CALL(pkcs11_, GetAttributeValue(_, _, _, _, _)) + .WillRepeatedly(Return(CKR_GENERAL_ERROR)); + Pkcs11KeyStore key_store(&token_manager_); + std::string blob; + EXPECT_TRUE(key_store.Write(kDefaultUser, "test", "test_data")); + EXPECT_FALSE(key_store.Read(kDefaultUser, "test", &blob)); +} + +// Tests the key store when PKCS #11 fails to delete key data. +TEST_F(KeyStoreTest, DeleteValueFail) { + EXPECT_CALL(pkcs11_, DestroyObject(_, _, _)) + .WillRepeatedly(Return(CKR_GENERAL_ERROR)); + Pkcs11KeyStore key_store(&token_manager_); + EXPECT_TRUE(key_store.Write(kDefaultUser, "test", "test_data")); + EXPECT_FALSE(key_store.Write(kDefaultUser, "test", "test_data2")); + EXPECT_FALSE(key_store.Delete(kDefaultUser, "test")); +} + +// Tests the key store when PKCS #11 fails to find objects. Tests each part of +// the multi-part find operation individually. +TEST_F(KeyStoreTest, FindFail) { + EXPECT_CALL(pkcs11_, FindObjectsInit(_, _, _)) + .WillRepeatedly(Return(CKR_GENERAL_ERROR)); + Pkcs11KeyStore key_store(&token_manager_); + std::string blob; + EXPECT_TRUE(key_store.Write(kDefaultUser, "test", "test_data")); + EXPECT_FALSE(key_store.Read(kDefaultUser, "test", &blob)); + + EXPECT_CALL(pkcs11_, FindObjectsInit(_, _, _)) + .WillRepeatedly(Return(CKR_OK)); + EXPECT_CALL(pkcs11_, FindObjects(_, _, _, _)) + .WillRepeatedly(Return(CKR_GENERAL_ERROR)); + EXPECT_TRUE(key_store.Write(kDefaultUser, "test", "test_data")); + EXPECT_FALSE(key_store.Read(kDefaultUser, "test", &blob)); + + EXPECT_CALL(pkcs11_, FindObjects(_, _, _, _)) + .WillRepeatedly(Return(CKR_OK)); + EXPECT_CALL(pkcs11_, FindObjectsFinal(_, _)) + .WillRepeatedly(Return(CKR_GENERAL_ERROR)); + EXPECT_TRUE(key_store.Write(kDefaultUser, "test", "test_data")); + EXPECT_FALSE(key_store.Read(kDefaultUser, "test", &blob)); +} + +// Tests the key store when PKCS #11 successfully finds zero objects. +TEST_F(KeyStoreTest, FindNoObjects) { + std::vector<uint64_t> empty; + EXPECT_CALL(pkcs11_, FindObjects(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgumentPointee<3>(empty), Return(CKR_OK))); + Pkcs11KeyStore key_store(&token_manager_); + std::string blob; + EXPECT_TRUE(key_store.Write(kDefaultUser, "test", "test_data")); + EXPECT_FALSE(key_store.Read(kDefaultUser, "test", &blob)); +} + +TEST_F(KeyStoreTest, RegisterKeyWithoutCertificate) { + Pkcs11KeyStore key_store(&token_manager_); + // Try with a malformed public key. + EXPECT_FALSE(key_store.Register(kDefaultUser, "test_label", KEY_TYPE_RSA, + KEY_USAGE_SIGN, "private_key_blob", + "bad_pubkey", "")); + // Try with a well-formed public key. + std::string public_key_der = HexDecode(kValidPublicKeyHex); + EXPECT_CALL(pkcs11_, CreateObject(_, _, _, _)) + .Times(2) // Public, private (no certificate). + .WillRepeatedly(Return(CKR_OK)); + EXPECT_TRUE(key_store.Register(kDefaultUser, "test_label", KEY_TYPE_RSA, + KEY_USAGE_SIGN, "private_key_blob", + public_key_der, "")); +} + +TEST_F(KeyStoreTest, RegisterKeyWithCertificate) { + EXPECT_CALL(pkcs11_, CreateObject(_, _, _, _)) + .Times(3) // Public, private, and certificate. + .WillRepeatedly(Return(CKR_OK)); + Pkcs11KeyStore key_store(&token_manager_); + std::string public_key_der = HexDecode(kValidPublicKeyHex); + std::string certificate_der = HexDecode(kValidCertificateHex); + EXPECT_TRUE(key_store.Register(kDefaultUser, "test_label", KEY_TYPE_RSA, + KEY_USAGE_SIGN, "private_key_blob", + public_key_der, certificate_der)); + // Also try with the system token. + EXPECT_CALL(pkcs11_, CreateObject(_, _, _, _)) + .Times(3) // Public, private, and certificate. + .WillRepeatedly(Return(CKR_OK)); + EXPECT_TRUE(key_store.Register(kDefaultUser, "test_label", KEY_TYPE_RSA, + KEY_USAGE_SIGN, "private_key_blob", + public_key_der, certificate_der)); +} + +TEST_F(KeyStoreTest, RegisterKeyWithBadCertificate) { + EXPECT_CALL(pkcs11_, CreateObject(_, _, _, _)) + .Times(3) // Public, private, and certificate. + .WillRepeatedly(Return(CKR_OK)); + Pkcs11KeyStore key_store(&token_manager_); + std::string public_key_der = HexDecode(kValidPublicKeyHex); + EXPECT_TRUE(key_store.Register(kDefaultUser, "test_label", KEY_TYPE_RSA, + KEY_USAGE_SIGN, "private_key_blob", + public_key_der, "bad_certificate")); +} + +TEST_F(KeyStoreTest, RegisterWithUnsupportedKeyType) { + Pkcs11KeyStore key_store(&token_manager_); + std::string public_key_der = HexDecode(kValidPublicKeyHex); + EXPECT_FALSE(key_store.Register(kDefaultUser, "test_label", KEY_TYPE_ECC, + KEY_USAGE_SIGN, "private_key_blob", + public_key_der, "")); +} + +TEST_F(KeyStoreTest, RegisterDecryptionKey) { + EXPECT_CALL(pkcs11_, CreateObject(_, _, _, _)) + .WillRepeatedly(Return(CKR_OK)); + Pkcs11KeyStore key_store(&token_manager_); + std::string public_key_der = HexDecode(kValidPublicKeyHex); + EXPECT_TRUE(key_store.Register(kDefaultUser, "test_label", KEY_TYPE_RSA, + KEY_USAGE_DECRYPT, "private_key_blob", + public_key_der, "")); +} + +TEST_F(KeyStoreTest, RegisterCertificate) { + Pkcs11KeyStore key_store(&token_manager_); + std::string certificate_der = HexDecode(kValidCertificateHex); + EXPECT_CALL(pkcs11_, CreateObject(_, _, _, _)) + .Times(2); // Once for valid, once for invalid. + // Try with a valid certificate (hit multiple times to check dup logic). + EXPECT_TRUE(key_store.RegisterCertificate(kDefaultUser, certificate_der)); + EXPECT_TRUE(key_store.RegisterCertificate(kDefaultUser, certificate_der)); + EXPECT_TRUE(key_store.RegisterCertificate(kDefaultUser, certificate_der)); + // Try with an invalid certificate. + EXPECT_TRUE(key_store.RegisterCertificate(kDefaultUser, "bad_certificate")); +} + +TEST_F(KeyStoreTest, RegisterCertificateError) { + Pkcs11KeyStore key_store(&token_manager_); + std::string certificate_der = HexDecode(kValidCertificateHex); + // Handle an error from PKCS #11. + EXPECT_CALL(pkcs11_, CreateObject(_, _, _, _)) + .WillOnce(Return(CKR_GENERAL_ERROR)); + EXPECT_FALSE(key_store.RegisterCertificate(kDefaultUser, certificate_der)); +} + +TEST_F(KeyStoreTest, RegisterCertificateSystemToken) { + Pkcs11KeyStore key_store(&token_manager_); + std::string certificate_der = HexDecode(kValidCertificateHex); + // Try with the system token. + EXPECT_CALL(pkcs11_, CreateObject(_, _, _, _)) + .WillOnce(Return(CKR_OK)); + EXPECT_TRUE(key_store.RegisterCertificate(kDefaultUser, certificate_der)); +} + +// Tests that the DeleteByPrefix() method removes the correct objects and only +// the correct objects. +TEST_F(KeyStoreTest, DeleteByPrefix) { + Pkcs11KeyStore key_store(&token_manager_); + + // Test with no keys. + ASSERT_TRUE(key_store.DeleteByPrefix(kDefaultUser, "prefix")); + + // Test with a single matching key. + ASSERT_TRUE(key_store.Write(kDefaultUser, "prefix_test", "test")); + ASSERT_TRUE(key_store.DeleteByPrefix(kDefaultUser, "prefix")); + std::string blob; + EXPECT_FALSE(key_store.Read(kDefaultUser, "prefix_test", &blob)); + + // Test with a single non-matching key. + ASSERT_TRUE(key_store.Write(kDefaultUser, "_prefix_", "test")); + ASSERT_TRUE(key_store.DeleteByPrefix(kDefaultUser, "prefix")); + EXPECT_TRUE(key_store.Read(kDefaultUser, "_prefix_", &blob)); + + // Test with an empty prefix. + ASSERT_TRUE(key_store.DeleteByPrefix(kDefaultUser, "")); + EXPECT_FALSE(key_store.Read(kDefaultUser, "_prefix_", &blob)); + + // Test with multiple matching and non-matching keys. + const int kNumKeys = 110; // Pkcs11KeyStore max is 100 for FindObjects. + key_store.Write(kDefaultUser, "other1", "test"); + for (int i = 0; i < kNumKeys; ++i) { + std::string key_name = std::string("prefix") + base::IntToString(i); + key_store.Write(kDefaultUser, key_name, std::string(key_name)); + } + ASSERT_TRUE(key_store.Write(kDefaultUser, "other2", "test")); + ASSERT_TRUE(key_store.DeleteByPrefix(kDefaultUser, "prefix")); + EXPECT_TRUE(key_store.Read(kDefaultUser, "other1", &blob)); + EXPECT_TRUE(key_store.Read(kDefaultUser, "other2", &blob)); + for (int i = 0; i < kNumKeys; ++i) { + std::string key_name = std::string("prefix") + base::IntToString(i); + EXPECT_FALSE(key_store.Read(kDefaultUser, key_name, &blob)); + } +} + +} // namespace attestation |