| // |
| // Copyright (C) 2020 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 "tpm_gatekeeper.h" |
| |
| #include <algorithm> |
| #include <optional> |
| #include <vector> |
| |
| #include <android-base/logging.h> |
| #include <tss2/tss2_esys.h> |
| #include <tss2/tss2_mu.h> |
| #include <tss2/tss2_rc.h> |
| |
| #include "host/commands/secure_env/primary_key_builder.h" |
| #include "host/commands/secure_env/tpm_auth.h" |
| #include "host/commands/secure_env/tpm_hmac.h" |
| #include "host/commands/secure_env/tpm_random_source.h" |
| |
| TpmGatekeeper::TpmGatekeeper( |
| TpmResourceManager& resource_manager, |
| GatekeeperStorage& secure_storage, |
| GatekeeperStorage& insecure_storage) |
| : resource_manager_(resource_manager) |
| , secure_storage_(secure_storage) |
| , insecure_storage_(insecure_storage) { |
| } |
| |
| /* |
| * The reinterpret_cast and kPasswordUnique data is combined together with TPM |
| * internal state to create the actual key used for Gatekeeper operations. |
| */ |
| |
| bool TpmGatekeeper::GetAuthTokenKey( |
| const uint8_t** auth_token_key, uint32_t* length) const { |
| static constexpr char kAuthTokenUnique[] = "TpmGatekeeper auth token key"; |
| *auth_token_key = reinterpret_cast<const uint8_t*>(kAuthTokenUnique); |
| *length = sizeof(kAuthTokenUnique); |
| return true; |
| } |
| |
| void TpmGatekeeper::GetPasswordKey( |
| const uint8_t** password_key, uint32_t* length) { |
| static constexpr char kPasswordUnique[] = "TpmGatekeeper password key"; |
| *password_key = reinterpret_cast<const uint8_t*>(kPasswordUnique); |
| *length = sizeof(kPasswordUnique); |
| } |
| |
| void TpmGatekeeper::ComputePasswordSignature( |
| uint8_t* signature, |
| uint32_t signature_length, |
| const uint8_t* key, |
| uint32_t key_length, |
| const uint8_t* password, |
| uint32_t password_length, |
| gatekeeper::salt_t salt) const { |
| std::vector<uint8_t> message(password_length + sizeof(salt)); |
| memcpy(message.data(), password, password_length); |
| memcpy(message.data() + password_length, &salt, sizeof(salt)); |
| return ComputeSignature( |
| signature, |
| signature_length, |
| key, |
| key_length, |
| message.data(), |
| message.size()); |
| } |
| |
| void TpmGatekeeper::GetRandom(void* random, uint32_t requested_size) const { |
| auto random_uint8 = reinterpret_cast<uint8_t*>(random); |
| TpmRandomSource(resource_manager_.Esys()) |
| .GenerateRandom(random_uint8, requested_size); |
| } |
| |
| void TpmGatekeeper::ComputeSignature( |
| uint8_t* signature, |
| uint32_t signature_length, |
| const uint8_t* key, |
| uint32_t key_length, |
| const uint8_t* message, |
| uint32_t length) const { |
| memset(signature, 0, signature_length); |
| std::string key_unique(reinterpret_cast<const char*>(key), key_length); |
| PrimaryKeyBuilder key_builder; |
| key_builder.UniqueData(key_unique); |
| key_builder.SigningKey(); |
| auto key_slot = key_builder.CreateKey(resource_manager_); |
| if (!key_slot) { |
| LOG(ERROR) << "Unable to load signing key into TPM memory"; |
| return; |
| } |
| auto calculated_signature = |
| TpmHmac( |
| resource_manager_, |
| key_slot->get(), |
| TpmAuth(ESYS_TR_PASSWORD), |
| message, |
| length); |
| if (!calculated_signature) { |
| LOG(ERROR) << "Failure in calculating signature"; |
| return; |
| } |
| memcpy( |
| signature, |
| calculated_signature->buffer, |
| std::min((int) calculated_signature->size, (int) signature_length)); |
| } |
| |
| uint64_t TpmGatekeeper::GetMillisecondsSinceBoot() const { |
| struct timespec time; |
| int res = clock_gettime(CLOCK_BOOTTIME, &time); |
| if (res < 0) return 0; |
| return (time.tv_sec * 1000) + (time.tv_nsec / 1000 / 1000); |
| } |
| |
| gatekeeper::failure_record_t DefaultRecord( |
| gatekeeper::secure_id_t secure_user_id) { |
| return (gatekeeper::failure_record_t) { |
| .secure_user_id = secure_user_id, |
| .last_checked_timestamp = 0, |
| .failure_counter = 0, |
| }; |
| } |
| |
| static std::unique_ptr<TPM2B_MAX_NV_BUFFER> RecordToNvBuffer( |
| const gatekeeper::failure_record_t& record) { |
| auto ret = std::make_unique<TPM2B_MAX_NV_BUFFER>(); |
| static_assert(sizeof(ret->buffer) >= sizeof(record)); |
| ret->size = sizeof(record); |
| std::memcpy(ret->buffer, &record, sizeof(record)); |
| return ret; |
| } |
| |
| static std::optional<gatekeeper::failure_record_t> NvBufferToRecord( |
| const TPM2B_MAX_NV_BUFFER& buffer) { |
| gatekeeper::failure_record_t ret; |
| if (buffer.size != sizeof(ret)) { |
| LOG(ERROR) << "NV Buffer had an incorrect size."; |
| return {}; |
| } |
| memcpy(&ret, buffer.buffer, sizeof(ret)); |
| return ret; |
| } |
| |
| static bool GetFailureRecordImpl( |
| GatekeeperStorage& storage, |
| uint32_t uid, |
| gatekeeper::secure_id_t secure_user_id, |
| gatekeeper::failure_record_t *record) { |
| Json::Value key{std::to_string(uid)}; // jsoncpp integer comparisons are janky |
| if (!storage.HasKey(key)) { |
| if (!storage.Allocate(key, sizeof(gatekeeper::failure_record_t))) { |
| LOG(ERROR) << "Allocation failed for user " << uid; |
| return false; |
| } |
| auto buf = RecordToNvBuffer(DefaultRecord(secure_user_id)); |
| if (!storage.Write(key, *buf)) { |
| LOG(ERROR) << "Failed to write record for " << uid; |
| return false; |
| } |
| } |
| auto record_read = storage.Read(key); |
| if (!record_read) { |
| LOG(ERROR) << "Failed to read record for " << uid; |
| return false; |
| } |
| auto record_decoded = NvBufferToRecord(*record_read); |
| if (!record_decoded) { |
| LOG(ERROR) << "Failed to deserialize record for " << uid; |
| return false; |
| } |
| if (record_decoded->secure_user_id == secure_user_id) { |
| *record = *record_decoded; |
| return true; |
| } |
| LOG(DEBUG) << "User id mismatch for " << uid; |
| auto buf = RecordToNvBuffer(DefaultRecord(secure_user_id)); |
| if (!storage.Write(key, *buf)) { |
| LOG(ERROR) << "Failed to write record for " << uid; |
| return false; |
| } |
| *record = DefaultRecord(secure_user_id); |
| return true; |
| } |
| |
| bool TpmGatekeeper::GetFailureRecord( |
| uint32_t uid, |
| gatekeeper::secure_id_t secure_user_id, |
| gatekeeper::failure_record_t *record, |
| bool secure) { |
| GatekeeperStorage& storage = secure ? secure_storage_ : insecure_storage_; |
| return GetFailureRecordImpl(storage, uid, secure_user_id, record); |
| } |
| |
| static bool WriteFailureRecordImpl( |
| GatekeeperStorage& storage, |
| uint32_t uid, |
| gatekeeper::failure_record_t* record) { |
| Json::Value key{std::to_string(uid)}; // jsoncpp integer comparisons are janky |
| if (!storage.HasKey(key)) { |
| if (!storage.Allocate(key, sizeof(gatekeeper::failure_record_t))) { |
| LOG(ERROR) << "Allocation failed for user " << uid; |
| return false; |
| } |
| } |
| auto buf = RecordToNvBuffer(*record); |
| if (!storage.Write(key, *buf)) { |
| LOG(ERROR) << "Failed to write record for " << uid; |
| return false; |
| } |
| return true; |
| } |
| |
| bool TpmGatekeeper::ClearFailureRecord( |
| uint32_t uid, gatekeeper::secure_id_t secure_user_id, bool secure) { |
| GatekeeperStorage& storage = secure ? secure_storage_ : insecure_storage_; |
| gatekeeper::failure_record_t record = DefaultRecord(secure_user_id); |
| return WriteFailureRecordImpl(storage, uid, &record); |
| } |
| |
| bool TpmGatekeeper::WriteFailureRecord( |
| uint32_t uid, gatekeeper::failure_record_t *record, bool secure) { |
| GatekeeperStorage& storage = secure ? secure_storage_ : insecure_storage_; |
| return WriteFailureRecordImpl(storage, uid, record); |
| } |
| |
| bool TpmGatekeeper::IsHardwareBacked() const { |
| return true; |
| } |
| |