| // Copyright 2017 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. |
| |
| #include "hammerd/pair_utils.h" |
| |
| #include <string.h> |
| |
| #include <string> |
| |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/threading/platform_thread.h> |
| #include <chromeos/dbus/service_constants.h> |
| #include <openssl/hmac.h> |
| #include <openssl/rand.h> |
| #include <openssl/sha.h> |
| |
| namespace hammerd { |
| |
| // Implementation of PairManager. |
| ChallengeStatus PairManager::PairChallenge(FirmwareUpdaterInterface* fw_updater, |
| DBusWrapperInterface* dbus_wrapper) { |
| // Generate Challenge request. |
| PairChallengeRequest request; |
| uint8_t private_key[X25519_PRIVATE_KEY_LEN]; |
| GenerateChallenge(&request, private_key); |
| std::string request_payload(reinterpret_cast<const char*>(&request), |
| sizeof(request)); |
| |
| // Send the request to the hammer. |
| PairChallengeResponse response; |
| if (!fw_updater->SendSubcommandReceiveResponse( |
| UpdateExtraCommand::kPairChallenge, request_payload, |
| reinterpret_cast<void*>(&response), sizeof(response))) { |
| if (response.status == |
| static_cast<uint8_t>(EcResponseStatus::kUnavailable)) { |
| LOG(ERROR) << "Need to inject the entropy."; |
| // Because we will inject entropy and try to pair again, we don't send |
| // kPairChallengeFailed signal here. |
| return ChallengeStatus::kNeedInjectEntropy; |
| } |
| // If the base is disconnected, then do not send DBus message. |
| // There is a short delay between device disconnected and kernel react to |
| // it. Add a short delay before check. |
| constexpr int kernel_delay_ms = 100; |
| base::PlatformThread::Sleep( |
| base::TimeDelta::FromMilliseconds(kernel_delay_ms)); |
| if (!fw_updater->UsbSysfsExists()) { |
| LOG(ERROR) << "USB device is disconnected."; |
| return ChallengeStatus::kConnectionError; |
| } |
| LOG(ERROR) << "Unknown error! The status of response: " |
| << static_cast<int>(response.status); |
| dbus_wrapper->SendSignal(kPairChallengeFailedSignal); |
| return ChallengeStatus::kUnknownError; |
| } |
| |
| // Verify the response. |
| if (VerifyChallenge(request, private_key, response)) { |
| LOG(INFO) << "The pair challenge passed."; |
| dbus_wrapper->SendSignalWithArg(kPairChallengeSucceededSignal, |
| response.public_key, |
| sizeof(response.public_key)); |
| return ChallengeStatus::kChallengePassed; |
| } |
| LOG(ERROR) << "The pair challenge failed."; |
| dbus_wrapper->SendSignal(kPairChallengeFailedSignal); |
| return ChallengeStatus::kChallengeFailed; |
| } |
| |
| void PairManager::GenerateChallenge(PairChallengeRequest* request, |
| uint8_t* private_key) { |
| X25519_keypair(request->public_key, private_key); |
| RAND_bytes(request->nonce, sizeof(request->nonce)); |
| } |
| |
| bool PairManager::VerifyChallenge(const PairChallengeRequest& request, |
| uint8_t* private_key, |
| const PairChallengeResponse& resp) { |
| uint8_t shared[X25519_PRIVATE_KEY_LEN]; |
| uint8_t myauth[SHA256_DIGEST_LENGTH]; |
| |
| X25519(shared, private_key, resp.public_key); |
| |
| HMAC(EVP_sha256(), shared, sizeof(shared), request.nonce, |
| sizeof(request.nonce), myauth, nullptr); |
| |
| LOG(INFO) << "Authenticator (local):\n" |
| << base::HexEncode(myauth, sizeof(myauth)); |
| // The authenticator is truncated, so we only compare the remaining part. |
| static_assert(sizeof(resp.authenticator) <= SHA256_DIGEST_LENGTH, |
| "size of authenticator must be <= SHA256_DIGEST_LENGTH."); |
| if (memcmp(myauth, resp.authenticator, sizeof(resp.authenticator)) == 0) { |
| LOG(INFO) << "Authenticator matches."; |
| return true; |
| } |
| LOG(ERROR) << "Authenticator does not match (remote):" |
| << base::HexEncode(resp.authenticator, sizeof(resp.authenticator)); |
| return false; |
| } |
| |
| } // namespace hammerd |