| /* |
| * 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 "adb/pairing/pairing_auth.h" |
| |
| #include <android-base/logging.h> |
| |
| #include <openssl/curve25519.h> |
| #include <openssl/mem.h> |
| |
| #include <iomanip> |
| #include <sstream> |
| #include <vector> |
| |
| #include "adb/pairing/aes_128_gcm.h" |
| |
| #include <string.h> |
| |
| using namespace adb::pairing; |
| |
| static constexpr spake2_role_t kClientRole = spake2_role_alice; |
| static constexpr spake2_role_t kServerRole = spake2_role_bob; |
| |
| static const uint8_t kClientName[] = "adb pair client"; |
| static const uint8_t kServerName[] = "adb pair server"; |
| |
| // This class is basically a wrapper around the SPAKE2 protocol + initializing a |
| // cipher with the generated key material for encryption. |
| struct PairingAuthCtx { |
| public: |
| using Data = std::vector<uint8_t>; |
| enum class Role { |
| Client, |
| Server, |
| }; |
| |
| explicit PairingAuthCtx(Role role, const Data& pswd); |
| |
| // Returns the message to exchange with the other party. This is guaranteed |
| // to have a non-empty message if creating this object with |
| // |PairingAuthCtx::Create|, so you won't need to check. |
| const Data& msg() const; |
| |
| // Processes the peer's |msg| and attempts to initialize the cipher for |
| // encryption. You can only call this method ONCE with a non-empty |msg|, |
| // regardless of success or failure. Subsequent calls will always return |
| // false. On success, you can use the |decrypt| |
| // and |encrypt| methods to exchange any further information securely. |
| // |
| // Note: Once you call this with a non-empty key, the state is locked, which |
| // means that you cannot try and register another key, regardless of the |
| // return value. In order to register another key, you have to create a new |
| // instance of PairingAuthCtx. |
| bool InitCipher(const Data& their_msg); |
| |
| // Encrypts |data| and returns the result. If encryption fails, the return |
| // will be an empty vector. |
| Data Encrypt(const Data& data); |
| |
| // Decrypts |data| and returns the result. If decryption fails, the return |
| // will be an empty vector. |
| Data Decrypt(const Data& data); |
| |
| // Returns a safe buffer size for encrypting a buffer of size |len|. |
| size_t SafeEncryptedSize(size_t len); |
| |
| // Returns a safe buffer size for decrypting a buffer of size |len|. |
| size_t SafeDecryptedSize(size_t len); |
| |
| private: |
| Data our_msg_; |
| Role role_; |
| bssl::UniquePtr<SPAKE2_CTX> spake2_ctx_; |
| std::unique_ptr<Aes128Gcm> cipher_; |
| }; // PairingAuthCtx |
| |
| PairingAuthCtx::PairingAuthCtx(Role role, const Data& pswd) : role_(role) { |
| CHECK(!pswd.empty()); |
| // Try to create the spake2 context and generate the public key. |
| spake2_role_t spake_role; |
| const uint8_t* my_name = nullptr; |
| const uint8_t* their_name = nullptr; |
| size_t my_len = 0; |
| size_t their_len = 0; |
| |
| // Create the SPAKE2 context |
| switch (role_) { |
| case Role::Client: |
| spake_role = kClientRole; |
| my_name = kClientName; |
| my_len = sizeof(kClientName); |
| their_name = kServerName; |
| their_len = sizeof(kServerName); |
| break; |
| case Role::Server: |
| spake_role = kServerRole; |
| my_name = kServerName; |
| my_len = sizeof(kServerName); |
| their_name = kClientName; |
| their_len = sizeof(kClientName); |
| break; |
| } |
| spake2_ctx_.reset(SPAKE2_CTX_new(spake_role, my_name, my_len, their_name, their_len)); |
| if (spake2_ctx_ == nullptr) { |
| LOG(ERROR) << "Unable to create a SPAKE2 context."; |
| return; |
| } |
| |
| // Generate the SPAKE2 public key |
| size_t key_size = 0; |
| uint8_t key[SPAKE2_MAX_MSG_SIZE]; |
| int status = SPAKE2_generate_msg(spake2_ctx_.get(), key, &key_size, SPAKE2_MAX_MSG_SIZE, |
| pswd.data(), pswd.size()); |
| if (status != 1 || key_size == 0) { |
| LOG(ERROR) << "Unable to generate the SPAKE2 public key."; |
| return; |
| } |
| our_msg_.assign(key, key + key_size); |
| } |
| |
| const PairingAuthCtx::Data& PairingAuthCtx::msg() const { |
| return our_msg_; |
| } |
| |
| bool PairingAuthCtx::InitCipher(const PairingAuthCtx::Data& their_msg) { |
| // You can only register a key once. |
| CHECK(!their_msg.empty()); |
| CHECK(!cipher_); |
| |
| // Don't even try to process a message over the SPAKE2_MAX_MSG_SIZE |
| if (their_msg.size() > SPAKE2_MAX_MSG_SIZE) { |
| LOG(ERROR) << "their_msg size [" << their_msg.size() << "] greater then max size [" |
| << SPAKE2_MAX_MSG_SIZE << "]."; |
| return false; |
| } |
| |
| size_t key_material_len = 0; |
| uint8_t key_material[SPAKE2_MAX_KEY_SIZE]; |
| int status = SPAKE2_process_msg(spake2_ctx_.get(), key_material, &key_material_len, |
| sizeof(key_material), their_msg.data(), their_msg.size()); |
| if (status != 1) { |
| LOG(ERROR) << "Unable to process their public key"; |
| return false; |
| } |
| |
| // Once SPAKE2_process_msg returns successfully, you can't do anything else |
| // with the context, besides destroy it. |
| cipher_.reset(new Aes128Gcm(key_material, key_material_len)); |
| |
| return true; |
| } |
| |
| PairingAuthCtx::Data PairingAuthCtx::Encrypt(const PairingAuthCtx::Data& data) { |
| CHECK(cipher_); |
| CHECK(!data.empty()); |
| |
| // Determine the size for the encrypted data based on the raw data. |
| Data encrypted(cipher_->EncryptedSize(data.size())); |
| auto out_size = cipher_->Encrypt(data.data(), data.size(), encrypted.data(), encrypted.size()); |
| if (!out_size.has_value() || *out_size == 0) { |
| LOG(ERROR) << "Unable to encrypt data"; |
| return Data(); |
| } |
| encrypted.resize(*out_size); |
| |
| return encrypted; |
| } |
| |
| PairingAuthCtx::Data PairingAuthCtx::Decrypt(const PairingAuthCtx::Data& data) { |
| CHECK(cipher_); |
| CHECK(!data.empty()); |
| |
| // Determine the size for the decrypted data based on the raw data. |
| Data decrypted(cipher_->DecryptedSize(data.size())); |
| size_t decrypted_size = decrypted.size(); |
| auto out_size = cipher_->Decrypt(data.data(), data.size(), decrypted.data(), decrypted_size); |
| if (!out_size.has_value() || *out_size == 0) { |
| LOG(ERROR) << "Unable to decrypt data"; |
| return Data(); |
| } |
| decrypted.resize(*out_size); |
| |
| return decrypted; |
| } |
| |
| size_t PairingAuthCtx::SafeEncryptedSize(size_t len) { |
| CHECK(cipher_); |
| return cipher_->EncryptedSize(len); |
| } |
| |
| size_t PairingAuthCtx::SafeDecryptedSize(size_t len) { |
| CHECK(cipher_); |
| return cipher_->DecryptedSize(len); |
| } |
| |
| PairingAuthCtx* pairing_auth_server_new(const uint8_t* pswd, size_t len) { |
| CHECK(pswd); |
| CHECK_GT(len, 0U); |
| std::vector<uint8_t> p(pswd, pswd + len); |
| auto* ret = new PairingAuthCtx(PairingAuthCtx::Role::Server, std::move(p)); |
| CHECK(!ret->msg().empty()); |
| return ret; |
| } |
| |
| PairingAuthCtx* pairing_auth_client_new(const uint8_t* pswd, size_t len) { |
| CHECK(pswd); |
| CHECK_GT(len, 0U); |
| std::vector<uint8_t> p(pswd, pswd + len); |
| auto* ret = new PairingAuthCtx(PairingAuthCtx::Role::Client, std::move(p)); |
| CHECK(!ret->msg().empty()); |
| return ret; |
| } |
| |
| size_t pairing_auth_msg_size(PairingAuthCtx* ctx) { |
| CHECK(ctx); |
| return ctx->msg().size(); |
| } |
| |
| void pairing_auth_get_spake2_msg(PairingAuthCtx* ctx, uint8_t* out_buf) { |
| CHECK(ctx); |
| CHECK(out_buf); |
| auto& msg = ctx->msg(); |
| memcpy(out_buf, msg.data(), msg.size()); |
| } |
| |
| bool pairing_auth_init_cipher(PairingAuthCtx* ctx, const uint8_t* their_msg, size_t msg_len) { |
| CHECK(ctx); |
| CHECK(their_msg); |
| CHECK_GT(msg_len, 0U); |
| |
| std::vector<uint8_t> p(their_msg, their_msg + msg_len); |
| return ctx->InitCipher(p); |
| } |
| |
| size_t pairing_auth_safe_encrypted_size(PairingAuthCtx* ctx, size_t len) { |
| CHECK(ctx); |
| return ctx->SafeEncryptedSize(len); |
| } |
| |
| bool pairing_auth_encrypt(PairingAuthCtx* ctx, const uint8_t* inbuf, size_t inlen, uint8_t* outbuf, |
| size_t* outlen) { |
| CHECK(ctx); |
| CHECK(inbuf); |
| CHECK(outbuf); |
| CHECK(outlen); |
| CHECK_GT(inlen, 0U); |
| |
| std::vector<uint8_t> in(inbuf, inbuf + inlen); |
| auto out = ctx->Encrypt(in); |
| if (out.empty()) { |
| return false; |
| } |
| |
| memcpy(outbuf, out.data(), out.size()); |
| *outlen = out.size(); |
| return true; |
| } |
| |
| size_t pairing_auth_safe_decrypted_size(PairingAuthCtx* ctx, const uint8_t* buf, size_t len) { |
| CHECK(ctx); |
| CHECK(buf); |
| CHECK_GT(len, 0U); |
| // We no longer need buf for EVP_AEAD |
| return ctx->SafeDecryptedSize(len); |
| } |
| |
| bool pairing_auth_decrypt(PairingAuthCtx* ctx, const uint8_t* inbuf, size_t inlen, uint8_t* outbuf, |
| size_t* outlen) { |
| CHECK(ctx); |
| CHECK(inbuf); |
| CHECK(outbuf); |
| CHECK(outlen); |
| CHECK_GT(inlen, 0U); |
| |
| std::vector<uint8_t> in(inbuf, inbuf + inlen); |
| auto out = ctx->Decrypt(in); |
| if (out.empty()) { |
| return false; |
| } |
| |
| memcpy(outbuf, out.data(), out.size()); |
| *outlen = out.size(); |
| return true; |
| } |
| |
| void pairing_auth_destroy(PairingAuthCtx* ctx) { |
| CHECK(ctx); |
| delete ctx; |
| } |