diff options
author | Joshua Duong <joshuaduong@google.com> | 2020-02-07 11:05:03 -0800 |
---|---|---|
committer | Joshua Duong <joshuaduong@google.com> | 2020-02-21 21:04:39 +0000 |
commit | 391aeb2e140de8f6046425dfd73905a0530341b5 (patch) | |
tree | c50d0e497437d60a0e74d012508ee71e6902a4fa /pairing_auth | |
parent | 44a12390d44c1f99e9a692262be6b7c80bec64ca (diff) | |
download | adb-391aeb2e140de8f6046425dfd73905a0530341b5.tar.gz |
[adbwifi] Add pairing_auth library.
Bug: 111434128
Bug: 119494503
Test: atest adb_pairing_auth_test
Change-Id: Ieada7b8d9d8817292175623af55eac235b938c65
Exempt-From-Owner-Approval: approved already
Diffstat (limited to 'pairing_auth')
-rw-r--r-- | pairing_auth/Android.bp | 83 | ||||
-rw-r--r-- | pairing_auth/aes_128_gcm.cpp | 183 | ||||
-rw-r--r-- | pairing_auth/include/adb/pairing/aes_128_gcm.h | 72 | ||||
-rw-r--r-- | pairing_auth/include/adb/pairing/counter.h | 49 | ||||
-rw-r--r-- | pairing_auth/include/adb/pairing/pairing_auth.h | 186 | ||||
-rw-r--r-- | pairing_auth/libadb_pairing_auth.map.txt | 15 | ||||
-rw-r--r-- | pairing_auth/pairing_auth.cpp | 300 | ||||
-rw-r--r-- | pairing_auth/tests/Android.bp | 39 | ||||
-rw-r--r-- | pairing_auth/tests/aes_128_gcm_test.cpp | 128 | ||||
-rw-r--r-- | pairing_auth/tests/counter_test.cpp | 70 | ||||
-rw-r--r-- | pairing_auth/tests/pairing_auth_test.cpp | 330 |
11 files changed, 1455 insertions, 0 deletions
diff --git a/pairing_auth/Android.bp b/pairing_auth/Android.bp new file mode 100644 index 00000000..0850047f --- /dev/null +++ b/pairing_auth/Android.bp @@ -0,0 +1,83 @@ +// 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. + +cc_defaults { + name: "libadb_pairing_auth_defaults", + cflags: [ + "-Wall", + "-Wextra", + "-Wthread-safety", + "-Werror", + ], + + compile_multilib: "both", + + srcs: [ + "aes_128_gcm.cpp", + "pairing_auth.cpp", + ], + target: { + android: { + version_script: "libadb_pairing_auth.map.txt", + }, + windows: { + compile_multilib: "first", + enabled: true, + }, + }, + export_include_dirs: ["include"], + + visibility: [ + "//art:__subpackages__", + "//system/core/adb:__subpackages__", + ], + + // libadb_pairing_auth doesn't need an embedded build number. + use_version_lib: false, + + host_supported: true, + recovery_available: true, + + stl: "libc++_static", + + static_libs: ["libbase"], + shared_libs: [ + "libcrypto", + "liblog", + ], +} + +cc_library { + name: "libadb_pairing_auth", + defaults: ["libadb_pairing_auth_defaults"], + + apex_available: [ + "com.android.adbd", + ], + + stubs: { + symbol_file: "libadb_pairing_auth.map.txt", + versions: ["30"], + }, +} + +// For running atest (b/147158681) +cc_library_static { + name: "libadb_pairing_auth_static", + defaults: ["libadb_pairing_auth_defaults"], + + apex_available: [ + "//apex_available:platform", + ], +} diff --git a/pairing_auth/aes_128_gcm.cpp b/pairing_auth/aes_128_gcm.cpp new file mode 100644 index 00000000..2978834d --- /dev/null +++ b/pairing_auth/aes_128_gcm.cpp @@ -0,0 +1,183 @@ +/* + * 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/aes_128_gcm.h" + +#include <android-base/endian.h> +#include <android-base/logging.h> + +#include <openssl/crypto.h> +#include <openssl/evp.h> +#include <openssl/hkdf.h> +#include <openssl/rand.h> + +namespace adb { +namespace pairing { + +namespace { +static const size_t kHkdfKeyLength = 256; + +struct Header { + uint32_t payload; + uint8_t iv[AES_128_GCM_IV_SIZE]; + uint8_t tag[AES_128_GCM_TAG_SIZE]; +} __attribute__((packed)); + +} // namespace + +// static +const EVP_CIPHER* Aes128Gcm::cipher_ = EVP_aes_128_gcm(); + +Aes128Gcm::Aes128Gcm(const uint8_t* key_material, size_t key_material_len) { + CHECK(key_material); + CHECK_NE(key_material_len, 0ul); + context_.reset(EVP_CIPHER_CTX_new()); + CHECK(context_.get()); + + // Start with a random number for our counter + CHECK_EQ(RAND_bytes(counter_.data(), counter_.size()), 1); + + uint8_t key[kHkdfKeyLength] = {}; + uint8_t salt[64] = "this is the salt"; + uint8_t info[64] = "this is the info"; + CHECK_EQ(HKDF(key, sizeof(key), EVP_sha256(), key_material, key_material_len, salt, + sizeof(salt), info, sizeof(info)), + 1); + CHECK_EQ(AES_set_encrypt_key(key, sizeof(key), &aes_key_), 0); +} + +int Aes128Gcm::Encrypt(const uint8_t* in, size_t in_len, uint8_t* out, size_t out_len) { + if (out_len < EncryptedSize(in_len)) { + LOG(ERROR) << "out buffer size (sz=" << out_len + << ") not big enough (sz=" << EncryptedSize(in_len) << ")"; + return -1; + } + auto& header = *reinterpret_cast<Header*>(out); + // Place the IV in the header + memcpy(header.iv, counter_.data(), counter_.size()); + int status = EVP_EncryptInit_ex(context_.get(), cipher_, nullptr, + reinterpret_cast<const uint8_t*>(&aes_key_), counter_.data()); + counter_.Increase(); + if (status != 1) { + return -1; + } + + int cipherLen = 0; + out += sizeof(header); + status = EVP_EncryptUpdate(context_.get(), out, &cipherLen, in, in_len); + if (status != 1 || cipherLen < 0) { + return -1; + } + + // Padding is enabled by default, so EVP_EncryptFinal_ex will pad any + // remaining partial data up to the block size. + int padding = 0; + status = EVP_EncryptFinal_ex(context_.get(), out + cipherLen, &padding); + if (status != 1 || padding < 0) { + return -1; + } + + // Place the tag in the header + status = EVP_CIPHER_CTX_ctrl(context_.get(), EVP_CTRL_GCM_GET_TAG, sizeof(header.tag), + header.tag); + if (status != 1) { + return -1; + } + // Place the payload size in the header + uint32_t totalLen = sizeof(header) + cipherLen + padding; + header.payload = htonl(static_cast<uint32_t>(cipherLen) + static_cast<uint32_t>(padding)); + return totalLen; +} + +int Aes128Gcm::Decrypt(const uint8_t* in, size_t in_len, uint8_t* out, size_t out_len) { + if (in_len < sizeof(Header)) { + return 0; + } + if (out_len < DecryptedSize(in, in_len)) { + return 0; + } + const auto& header = *reinterpret_cast<const Header*>(in); + uint32_t payload = ntohl(header.payload); + uint32_t expected_inlen = sizeof(Header) + payload; + if (in_len < expected_inlen) { + // Not enough data available + return 0; + } + // Initialized with expected IV from header + int status = EVP_DecryptInit_ex(context_.get(), cipher_, nullptr, + reinterpret_cast<const uint8_t*>(&aes_key_), header.iv); + if (status != 1) { + return -1; + } + + int decrypted_len = 0; + status = EVP_DecryptUpdate(context_.get(), out, &decrypted_len, in + sizeof(header), payload); + if (status != 1 || decrypted_len < 0) { + return -1; + } + + // Set expected tag from header + status = EVP_CIPHER_CTX_ctrl(context_.get(), EVP_CTRL_GCM_SET_TAG, sizeof(header.tag), + const_cast<uint8_t*>(header.tag)); + if (status != 1) { + return -1; + } + + // This is the padding. It can be ignored. + int len = 0; + status = EVP_DecryptFinal_ex(context_.get(), out + decrypted_len, &len); + if (status != 1) { + LOG(ERROR) << "EVP_DecryptFinal_ex failed. Tag mismatch"; + return -1; + } + + // Return the length without the padding. + return decrypted_len; +} + +size_t Aes128Gcm::EncryptedSize(size_t size) { + // We need to account for block alignment of the encrypted data. + // According to openssl.org/docs/man1.0.2/man3/EVP_EncryptUpdate.html, + // "The amount of data written depends on the block alignment of the + // encrypted data ..." + // ".. the amount of data written may be anything from zero bytes to + // (inl + cipher_block_size - 1) ..." + const size_t cipher_block_size = EVP_CIPHER_block_size(cipher_); + size_t padding = cipher_block_size - (size % cipher_block_size); + if (padding != cipher_block_size) { + size += padding; + } + return size + sizeof(Header); +} + +size_t Aes128Gcm::DecryptedSize(const uint8_t* encrypted_data, size_t encrypted_size) { + if (encrypted_size < sizeof(Header)) { + // Not enough data yet + return 0; + } + auto header = reinterpret_cast<const Header*>(encrypted_data); + uint32_t payload = ntohl(header->payload); + size_t total_size = payload + sizeof(Header); + if (encrypted_size < total_size) { + // There's enough data for the header but not enough data for the + // payload. Indicate that there's not enough data for now. + return 0; + } + return payload; +} + +} // namespace pairing +} // namespace adb diff --git a/pairing_auth/include/adb/pairing/aes_128_gcm.h b/pairing_auth/include/adb/pairing/aes_128_gcm.h new file mode 100644 index 00000000..490dd12a --- /dev/null +++ b/pairing_auth/include/adb/pairing/aes_128_gcm.h @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#pragma once + +#include <openssl/aes.h> +#include <openssl/cipher.h> + +#include <stdint.h> + +#include "adb/pairing/counter.h" + +// This is the default size of the initialization vector (iv) for AES-128-GCM +#define AES_128_GCM_IV_SIZE 12 +// This is the full tag size for AES-128-GCM +#define AES_128_GCM_TAG_SIZE 16 + +namespace adb { +namespace pairing { + +class Aes128Gcm { + public: + explicit Aes128Gcm(const uint8_t* key_material, size_t key_material_len); + + // Encrypt a block of data in |in| of length |in_len|, this consumes all data + // in |in| and places the encrypted data in |out| if |out_len| indicates that + // there is enough space. The data contains information needed for + // decryption that is specific to this implementation and is therefore only + // suitable for decryption with this class. + // The method returns the number of bytes placed in |out| on success and a + // negative value if an error occurs. + int Encrypt(const uint8_t* in, size_t in_len, uint8_t* out, size_t out_len); + // Decrypt a block of data in |in| of length |in_len|, this consumes all data + // in |in_len| bytes of data. The decrypted output is placed in the |out| + // buffer of length |out_len|. On successful decryption the number of bytes in + // |out| will be placed in |out_len|. + // The method returns the number of bytes consumed from the |in| buffer. If + // there is not enough data available in |in| the method returns zero. If + // an error occurs the method returns a negative value. + int Decrypt(const uint8_t* in, size_t in_len, uint8_t* out, size_t out_len); + + // Return a safe amount of buffer storage needed to encrypt |size| bytes. + size_t EncryptedSize(size_t size); + // Return a safe amount of buffer storage needed to decrypt the encrypted + // data in |encrypted_data| which is of length |encrypted_size|. Returns 0 if + // there is not enough data available to determine the required size. + size_t DecryptedSize(const uint8_t* encrypted_data, size_t encrypted_size); + + static const EVP_CIPHER* cipher_; + + private: + bssl::UniquePtr<EVP_CIPHER_CTX> context_; + AES_KEY aes_key_; + // We're going to use this counter for our iv so that it never repeats + Counter<AES_128_GCM_IV_SIZE> counter_; +}; + +} // namespace pairing +} // namespace adb diff --git a/pairing_auth/include/adb/pairing/counter.h b/pairing_auth/include/adb/pairing/counter.h new file mode 100644 index 00000000..263ceb78 --- /dev/null +++ b/pairing_auth/include/adb/pairing/counter.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#pragma once + +#include <stddef.h> +#include <stdint.h> + +namespace adb { +namespace pairing { + +template <size_t N> +class Counter { + public: + void Increase() { + for (size_t i = sizeof(counter_) - 1; i < sizeof(counter_); --i) { + if (++counter_[i] != 0) { + break; + } + } + } + + uint8_t* data() { return counter_; } + const uint8_t* data() const { return counter_; } + + constexpr size_t size() const { return sizeof(counter_); } + + uint8_t& operator[](size_t index) { return counter_[index]; } + const uint8_t& operator[](size_t index) const { return counter_[index]; } + + private: + uint8_t counter_[N]; +}; + +} // namespace pairing +} // namespace adb diff --git a/pairing_auth/include/adb/pairing/pairing_auth.h b/pairing_auth/include/adb/pairing/pairing_auth.h new file mode 100644 index 00000000..9ef97e26 --- /dev/null +++ b/pairing_auth/include/adb/pairing/pairing_auth.h @@ -0,0 +1,186 @@ +/* + * 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. + */ + +#pragma once + +#include <stddef.h> +#include <stdint.h> +#include <sys/cdefs.h> + +#if !defined(__INTRODUCED_IN) +#define __INTRODUCED_IN(__api_level) /* nothing */ +#endif + +__BEGIN_DECLS +#if !defined(__ANDROID__) || __ANDROID_API__ >= 30 + +/** + * PairingAuthCtx is a wrapper around the SPAKE2 protocol + cipher initialization + * for encryption. On construction, the |password| will be used to generate a + * SPAKE2 message. Each peer will exchange the messages in |pairing_auth_get_msg| + * to initialize their ciphers in |pairing_auth_init_cipher|. If both peers used the + * same |password|, then both sides will be able to decrypt each other's messages. + * + * On creation of a PairingAuthCtx, |pairing_auth_init_cipher| prior to using + * the encrypt and decrypt APIs. Furthermore, you can only initialize the cipher + * once. + * + * See pairing_auth_test.cpp for example usage. + * + */ +struct PairingAuthCtx; +typedef struct PairingAuthCtx PairingAuthCtx; + +/** + * Creates a new PairingAuthCtx instance as the server. + * + * @param pswd the shared secret the server and client use to authenticate each + * other. Will abort if null. + * @param len the length of the pswd in bytes. Will abort if 0. + * @return a new PairingAuthCtx server instance. Caller is responsible for + * destroying the context via #pairing_auth_destroy. + */ +PairingAuthCtx* pairing_auth_server_new(const uint8_t* pswd, size_t len) __INTRODUCED_IN(30); + +/** + * Creates a new PairingAuthCtx instance as the client. + * + * @param pswd the shared secret the server and client use to authenticate each + * other. Will abort if null. + * @param len the length of the pswd in bytes. Will abort if 0. + * @return a new PairingAuthCtx client instance. Caller is responsible for + * destroying the context via #pairing_auth_destroy. + */ +PairingAuthCtx* pairing_auth_client_new(const uint8_t* pswd, size_t len) __INTRODUCED_IN(30); + +/** + * Destroys the PairingAuthCtx. + * + * @param ctx the PairingAuthCtx instance to destroy. Will abort if null. + */ +void pairing_auth_destroy(PairingAuthCtx* ctx) __INTRODUCED_IN(30); + +/** + * Returns the exact size of the SPAKE2 msg. + * + * Use this size as the buffer size when retrieving the message via + * #pairing_auth_get_msg. + * + * @param ctx the PairingAuthCtx instance. Will abort if null. + * @return the size of the SPAKE2 message in bytes. This is guaranteed to be > 0. + */ +size_t pairing_auth_msg_size(PairingAuthCtx* ctx) __INTRODUCED_IN(30); + +/** + * Writes the SPAKE2 message to exchange with the other party to |out_buf|. + * + * This is guaranteed to write a valid message to |out_buf|. Use #pairing_auth_msg_size + * to get the size the |out_buf| should be. The SPAKE2 messages will be used to + * initialize the cipher for encryption/decryption (see #pairing_auth_init_cipher). + * + * @param ctx the PairingAuthCtx instance. Will abort if null. + * @param out_buf the buffer the message is written to. The buffer is assumed to + * be have at least #pairing_auth_msg_size size. Will abort if + * out_buf is null. + */ +void pairing_auth_get_spake2_msg(PairingAuthCtx* ctx, uint8_t* out_buf) __INTRODUCED_IN(30); + +/** + * Processes the peer's |their_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. On success, you can use the #pairing_auth_decrypt and #pairing_auth_encrypt + * methods to exchange any further information securely. On failure, this + * PairingAuthCtx instance has no more purpose and should be destroyed. + * + * @param ctx the PairingAuthCtx instance. Will abort if null. + * @param their_msg the peer's SPAKE2 msg. See #pairing_auth_get_msg. Will abort + * if null. + * @param msg_len the length of their_msg in bytes. Will abort if 0. + * @return true iff the client and server used the same password when creating + * the PairingAuthCtx. See + * https: *commondatastorage.googleapis.com/chromium-boringssl-docs/curve25519.h.html#SPAKE2 + * for more details on the SPAKE2 protocol. + */ +bool pairing_auth_init_cipher(PairingAuthCtx* ctx, const uint8_t* their_msg, size_t msg_len) + __INTRODUCED_IN(30); + +/** + * Returns a safe buffer size for encrypting data of a certain size. + * + * IMPORTANT: This will abort if either #pairing_auth_init_cipher was not called + * or #pairing_auth_init_cipher failed. + * + * @param ctx the PairingAuthCtx instance. Will abort if null. + * @param len the size of the message wanting to encrypt in bytes. + * @return the minimum buffer size, in bytes, to hold an encrypted message of size len. See + * #pairing_auth_encrypt for usage. + */ +size_t pairing_auth_safe_encrypted_size(PairingAuthCtx* ctx, size_t len) __INTRODUCED_IN(30); + +/** + * Encrypts input data and writes the encrypted data into a user-provided buffer. + * + * IMPORTANT: This will abort if either #pairing_auth_init_cipher was not called + * or #pairing_auth_init_cipher failed. + * + * @param ctx the PairingAuthCtx instance. Will abort if null. + * @param inbuf the buffer containing the data to encrypt. Will abort if null. + * @param inlen the size of inbuf in bytes. Will abort if 0. + * @param outbuf the buffer to write the encrypted data to. Will abort if null + * @param outlen the size of outbuf in bytes. See #pairing_auth_safe_encrypted_size. + * @return true if all the data was encrypted and written to outbuf, false + * otherwise. + */ +bool pairing_auth_encrypt(PairingAuthCtx* ctx, const uint8_t* inbuf, size_t inlen, uint8_t* outbuf, + size_t* outlen) __INTRODUCED_IN(30); + +/** + * Returns a safe buffer size for decrypting data of a certain size. + * + * IMPORTANT: This will abort if either #pairing_auth_init_cipher was not called + * or #pairing_auth_init_cipher failed. + * + * @param ctx the PairingAuthCtx instance. Will abort if null. + * @param buf the buffer containing the encrypted data. Will abort if null. + * @param len the size of the buf in bytes. Will abort if 0. + * @return the minimum buffer size, in bytes, to hold a decrypted message of size len. See + * #pairing_auth_decrypt for usage. + */ +size_t pairing_auth_safe_decrypted_size(PairingAuthCtx* ctx, const uint8_t* buf, size_t len) + __INTRODUCED_IN(30); + +/** + * Decrypts input data and writes the decrypted data into a user-provided buffer. + * + * IMPORTANT: This will abort if either #pairing_auth_init_cipher was not called + * or #pairing_auth_init_cipher failed. + * + * @param ctx the PairingAuthCtx instance. Will abort if null. + * @param inbuf the buffer containing the data to decrypt. Will abort if null. + * @param inlen the size of inbuf in bytes. WIll abort if 0. + * @param outbuf the buffer to write the decrypted data to. Will abort if null. + * @param outlen the size of outbuf in bytes. See #pairing_auth_safe_decrypted_size. + * Will abort if 0. + * @return true if all the data was decrypted and written to outbuf, false + * otherwise. + */ +bool pairing_auth_decrypt(PairingAuthCtx* ctx, const uint8_t* inbuf, size_t inlen, uint8_t* outbuf, + size_t* outlen) __INTRODUCED_IN(30); + +#endif //!__ANDROID__ || __ANDROID_API__ >= 30 +__END_DECLS diff --git a/pairing_auth/libadb_pairing_auth.map.txt b/pairing_auth/libadb_pairing_auth.map.txt new file mode 100644 index 00000000..fdc15570 --- /dev/null +++ b/pairing_auth/libadb_pairing_auth.map.txt @@ -0,0 +1,15 @@ +LIBADB_PAIRING_AUTH { + global: + pairing_auth_msg_size; # apex introduced=30 + pairing_auth_get_spake2_msg; # apex introduced=30 + pairing_auth_init_cipher; # apex introduced=30 + pairing_auth_safe_encrypted_size; # apex introduced=30 + pairing_auth_encrypt; # apex introduced=30 + pairing_auth_safe_decrypted_size; # apex introduced=30 + pairing_auth_decrypt; # apex introduced=30 + pairing_auth_server_new; # apex introduced=30 + pairing_auth_client_new; # apex introduced=30 + pairing_auth_destroy; # apex introduced=30 + local: + *; +}; diff --git a/pairing_auth/pairing_auth.cpp b/pairing_auth/pairing_auth.cpp new file mode 100644 index 00000000..96bc1106 --- /dev/null +++ b/pairing_auth/pairing_auth.cpp @@ -0,0 +1,300 @@ +/* + * 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" + +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 |buf|. + size_t SafeDecryptedSize(const Data& buf); + + 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())); + int bytes = cipher_->Encrypt(data.data(), data.size(), encrypted.data(), encrypted.size()); + if (bytes < 0) { + LOG(ERROR) << "Unable to encrypt data"; + return Data(); + } + encrypted.resize(bytes); + + 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.data(), data.size())); + size_t decrypted_size = decrypted.size(); + int bytes = cipher_->Decrypt(data.data(), data.size(), decrypted.data(), decrypted_size); + if (bytes <= 0) { + LOG(ERROR) << "Unable to decrypt data"; + return Data(); + } + decrypted.resize(bytes); + + return decrypted; +} + +size_t PairingAuthCtx::SafeEncryptedSize(size_t len) { + CHECK(cipher_); + return cipher_->EncryptedSize(len); +} + +size_t PairingAuthCtx::SafeDecryptedSize(const PairingAuthCtx::Data& buf) { + CHECK(cipher_); + return cipher_->DecryptedSize(buf.data(), buf.size()); +} + +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); + std::vector<uint8_t> p(buf, buf + len); + return ctx->SafeDecryptedSize(p); +} + +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; +} diff --git a/pairing_auth/tests/Android.bp b/pairing_auth/tests/Android.bp new file mode 100644 index 00000000..292fff52 --- /dev/null +++ b/pairing_auth/tests/Android.bp @@ -0,0 +1,39 @@ +// +// 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. +// + +cc_test { + name: "adb_pairing_auth_test", + srcs: [ + "aes_128_gcm_test.cpp", + "counter_test.cpp", + "pairing_auth_test.cpp", + ], + + compile_multilib: "first", + + shared_libs: [ + "libbase", + "libcrypto", + ], + + // Let's statically link them so we don't have to install it onto the + // system image for testing. + static_libs: [ + "libadb_pairing_auth_static", + ], + + test_suites: ["device-tests"], +} diff --git a/pairing_auth/tests/aes_128_gcm_test.cpp b/pairing_auth/tests/aes_128_gcm_test.cpp new file mode 100644 index 00000000..e1a20e80 --- /dev/null +++ b/pairing_auth/tests/aes_128_gcm_test.cpp @@ -0,0 +1,128 @@ +/* + * 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 <gtest/gtest.h> + +#include <memory> + +#include <adb/pairing/aes_128_gcm.h> +#include <openssl/rand.h> + +namespace adb { +namespace pairing { + +TEST(Aes128GcmTest, init_null_material) { + std::unique_ptr<Aes128Gcm> cipher; + ASSERT_DEATH({ cipher.reset(new Aes128Gcm(nullptr, 42)); }, ""); +} + +TEST(Aes128GcmTest, init_empty_material) { + uint8_t material[64]; + std::unique_ptr<Aes128Gcm> cipher; + ASSERT_DEATH({ cipher.reset(new Aes128Gcm(material, 0)); }, ""); +} + +TEST(Aes128GcmTest, encrypt_decrypt) { + const uint8_t msg[] = "alice and bob, sitting in a binary tree"; + uint8_t material[256]; + uint8_t encrypted[1024]; + uint8_t out_buf[1024]; + + RAND_bytes(material, sizeof(material)); + Aes128Gcm alice(material, sizeof(material)); + Aes128Gcm bob(material, sizeof(material)); + ; + + ASSERT_GE(alice.EncryptedSize(sizeof(msg)), sizeof(msg)); + int encrypted_size = alice.Encrypt(msg, sizeof(msg), encrypted, sizeof(encrypted)); + ASSERT_GT(encrypted_size, 0); + size_t out_size = sizeof(out_buf); + ASSERT_GE(bob.DecryptedSize(encrypted, sizeof(encrypted)), sizeof(msg)); + int decrypted_size = bob.Decrypt(encrypted, sizeof(encrypted), out_buf, out_size); + ASSERT_EQ(sizeof(msg), decrypted_size); + memset(out_buf + decrypted_size, 0, sizeof(out_buf) - decrypted_size); + ASSERT_STREQ(reinterpret_cast<const char*>(msg), reinterpret_cast<const char*>(out_buf)); +} + +TEST(Aes128GcmTest, padding) { + // Test with block-align data as well as unaligned data. + const size_t cipher_block_size = EVP_CIPHER_block_size(Aes128Gcm::cipher_); + uint8_t material[256]; + RAND_bytes(material, sizeof(material)); + Aes128Gcm alice(material, sizeof(material)); + Aes128Gcm bob(material, sizeof(material)); + ; + std::vector<uint8_t> msg; + std::vector<uint8_t> encrypted; + std::vector<uint8_t> decrypted; + + // Test with aligned data + { + msg.resize(cipher_block_size); + RAND_bytes(msg.data(), msg.size()); + + // encrypt + size_t safe_encrypted_sz = alice.EncryptedSize(msg.size()); + ASSERT_GE(safe_encrypted_sz, msg.size()); + encrypted.resize(safe_encrypted_sz); + int encrypted_size = + alice.Encrypt(msg.data(), msg.size(), encrypted.data(), encrypted.size()); + ASSERT_GT(encrypted_size, 0); + ASSERT_LE(encrypted_size, safe_encrypted_sz); + encrypted.resize(encrypted_size); + + // decrypt + size_t safe_decrypted_size = bob.DecryptedSize(encrypted.data(), encrypted.size()); + ASSERT_GE(safe_decrypted_size, msg.size()); + decrypted.resize(safe_decrypted_size); + int decrypted_size = + bob.Decrypt(encrypted.data(), encrypted.size(), decrypted.data(), decrypted.size()); + ASSERT_GT(decrypted_size, 0); + ASSERT_LE(decrypted_size, safe_decrypted_size); + ASSERT_EQ(msg.size(), decrypted_size); + ASSERT_EQ(memcmp(msg.data(), decrypted.data(), decrypted.size()), 0); + } + + // Test with unaligned data + { + msg.resize(cipher_block_size + 1); + RAND_bytes(msg.data(), msg.size()); + + // encrypt + size_t safe_encrypted_sz = alice.EncryptedSize(msg.size()); + ASSERT_GE(safe_encrypted_sz, msg.size()); + encrypted.resize(safe_encrypted_sz); + int encrypted_size = + alice.Encrypt(msg.data(), msg.size(), encrypted.data(), encrypted.size()); + ASSERT_GT(encrypted_size, 0); + ASSERT_LE(encrypted_size, safe_encrypted_sz); + encrypted.resize(encrypted_size); + + // decrypt + size_t safe_decrypted_size = bob.DecryptedSize(encrypted.data(), encrypted.size()); + ASSERT_GE(safe_decrypted_size, msg.size()); + decrypted.resize(safe_decrypted_size); + int decrypted_size = + bob.Decrypt(encrypted.data(), encrypted.size(), decrypted.data(), decrypted.size()); + ASSERT_GT(decrypted_size, 0); + ASSERT_LE(decrypted_size, safe_decrypted_size); + ASSERT_EQ(msg.size(), decrypted_size); + ASSERT_EQ(memcmp(msg.data(), decrypted.data(), decrypted.size()), 0); + } +} + +} // namespace pairing +} // namespace adb diff --git a/pairing_auth/tests/counter_test.cpp b/pairing_auth/tests/counter_test.cpp new file mode 100644 index 00000000..b3385518 --- /dev/null +++ b/pairing_auth/tests/counter_test.cpp @@ -0,0 +1,70 @@ +/* + * 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 <gtest/gtest.h> + +#include <adb/pairing/counter.h> + +namespace adb { +namespace pairing { + +static constexpr size_t kTestCounterSize = 13; +static const uint8_t kZeroes[64] = {0}; + +TEST(AdbCounterTest, size_match) { + Counter<kTestCounterSize> counter; + ASSERT_EQ(kTestCounterSize, counter.size()); +} + +TEST(AdbCounterTest, Increase) { + Counter<kTestCounterSize> counter; + memset(counter.data(), 0, counter.size()); + counter.Increase(); + EXPECT_EQ(1, counter[counter.size() - 1]); + EXPECT_EQ(0, memcmp(counter.data(), kZeroes, counter.size() - 1)); +} + +TEST(AdbCounterTest, rollover_first_byte) { + Counter<kTestCounterSize> counter; + memset(counter.data(), 0, counter.size()); + counter[counter.size() - 1] = 0xFF; + counter.Increase(); + EXPECT_EQ(0, counter[counter.size() - 1]); + EXPECT_EQ(1, counter[counter.size() - 2]); + EXPECT_EQ(0, memcmp(counter.data(), kZeroes, counter.size() - 2)); +} + +TEST(AdbCounterTest, multiple_rollover) { + Counter<kTestCounterSize> counter; + memset(counter.data(), 0xFF, counter.size()); + memset(counter.data(), 0, counter.size() - 3); + counter.Increase(); + EXPECT_EQ(0, counter[counter.size() - 5]); + EXPECT_EQ(1, counter[counter.size() - 4]); + EXPECT_EQ(0, counter[counter.size() - 3]); + EXPECT_EQ(0, counter[counter.size() - 2]); + EXPECT_EQ(0, counter[counter.size() - 1]); +} + +TEST(AdbCounterTest, full_rollover) { + Counter<kTestCounterSize> counter; + memset(counter.data(), 0xFF, counter.size()); + counter.Increase(); + EXPECT_EQ(0, memcmp(counter.data(), kZeroes, counter.size())); +} + +} // namespace pairing +} // namespace adb diff --git a/pairing_auth/tests/pairing_auth_test.cpp b/pairing_auth/tests/pairing_auth_test.cpp new file mode 100644 index 00000000..fdc07f1f --- /dev/null +++ b/pairing_auth/tests/pairing_auth_test.cpp @@ -0,0 +1,330 @@ +/* + * Copyright 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. + */ + +#define LOG_TAG "AdbPairingAuthTest" + +#include <gtest/gtest.h> + +#include <adb/pairing/pairing_auth.h> +#include <android-base/endian.h> + +namespace adb { +namespace pairing { + +static void PairingAuthDeleter(PairingAuthCtx* p) { + pairing_auth_destroy(p); +} + +class AdbPairingAuthTest : public testing::Test { + protected: + virtual void SetUp() override {} + + virtual void TearDown() override {} + + using PairingAuthUniquePtr = std::unique_ptr<PairingAuthCtx, decltype(&PairingAuthDeleter)>; + + PairingAuthUniquePtr makeClient(std::vector<uint8_t> pswd) { + return PairingAuthUniquePtr(pairing_auth_client_new(pswd.data(), pswd.size()), + PairingAuthDeleter); + } + + PairingAuthUniquePtr makeServer(std::vector<uint8_t> pswd) { + return PairingAuthUniquePtr(pairing_auth_server_new(pswd.data(), pswd.size()), + PairingAuthDeleter); + } +}; + +TEST_F(AdbPairingAuthTest, EmptyPassword) { + // Context creation should fail if password is empty + PairingAuthUniquePtr client(nullptr, PairingAuthDeleter); + ASSERT_DEATH( + { + client = PairingAuthUniquePtr(pairing_auth_client_new(nullptr, 0), + PairingAuthDeleter); + }, + ""); + ASSERT_DEATH( + { + client = PairingAuthUniquePtr(pairing_auth_client_new(nullptr, 2), + PairingAuthDeleter); + }, + ""); + ASSERT_DEATH( + { + uint8_t p; + client = PairingAuthUniquePtr(pairing_auth_client_new(&p, 0), PairingAuthDeleter); + }, + ""); +} + +TEST_F(AdbPairingAuthTest, ValidPassword) { + const char* kPswd = "password"; + std::vector<uint8_t> pswd(kPswd, kPswd + sizeof(kPswd)); + auto client = makeClient(pswd); + auto server = makeServer(pswd); + + ASSERT_NE(nullptr, client); + ASSERT_NE(nullptr, server); + + // msg should not be empty. + { + size_t msg_size = pairing_auth_msg_size(client.get()); + std::vector<uint8_t> buf(msg_size); + ASSERT_GT(msg_size, 0); + pairing_auth_get_spake2_msg(client.get(), buf.data()); + } + { + size_t msg_size = pairing_auth_msg_size(server.get()); + std::vector<uint8_t> buf(msg_size); + ASSERT_GT(msg_size, 0); + pairing_auth_get_spake2_msg(server.get(), buf.data()); + } +} + +TEST_F(AdbPairingAuthTest, NoInitCipher) { + // Register a non-empty password, but not the peer's msg. + // You should not be able to encrypt/decrypt messages. + const char* kPswd = "password"; + std::vector<uint8_t> pswd(kPswd, kPswd + sizeof(kPswd)); + std::vector<uint8_t> data{0x01, 0x02, 0x03}; + uint8_t outbuf[256]; + size_t outsize; + + // All other functions should crash if cipher hasn't been initialized. + ASSERT_DEATH( + { + auto server = makeServer(pswd); + pairing_auth_init_cipher(server.get(), nullptr, 0); + }, + ""); + ASSERT_DEATH( + { + auto server = makeServer(pswd); + pairing_auth_encrypt(server.get(), data.data(), data.size(), outbuf, &outsize); + }, + ""); + ASSERT_DEATH( + { + auto server = makeServer(pswd); + pairing_auth_decrypt(server.get(), data.data(), data.size(), outbuf, &outsize); + }, + ""); + ASSERT_DEATH( + { + auto server = makeServer(pswd); + pairing_auth_safe_decrypted_size(server.get(), data.data(), data.size()); + }, + ""); + ASSERT_DEATH( + { + auto server = makeServer(pswd); + pairing_auth_safe_encrypted_size(server.get(), data.size()); + }, + ""); +} + +TEST_F(AdbPairingAuthTest, DifferentPasswords) { + // Register different passwords and then exchange the msgs. The + // encryption should succeed, but the decryption should fail, since the + // ciphers have been initialized with different keys. + auto client = makeClient({0x01, 0x02, 0x03}); + std::vector<uint8_t> client_msg(pairing_auth_msg_size(client.get())); + ASSERT_FALSE(client_msg.empty()); + pairing_auth_get_spake2_msg(client.get(), client_msg.data()); + + auto server = makeServer({0x01, 0x02, 0x04}); + std::vector<uint8_t> server_msg(pairing_auth_msg_size(server.get())); + ASSERT_FALSE(server_msg.empty()); + pairing_auth_get_spake2_msg(server.get(), server_msg.data()); + + EXPECT_TRUE(pairing_auth_init_cipher(client.get(), server_msg.data(), server_msg.size())); + EXPECT_TRUE(pairing_auth_init_cipher(server.get(), client_msg.data(), client_msg.size())); + + // We shouldn't be able to decrypt. + std::vector<uint8_t> msg{0x2a, 0x2b, 0x2c}; + // Client encrypts, server can't decrypt + size_t out_size; + client_msg.resize(pairing_auth_safe_encrypted_size(client.get(), msg.size())); + ASSERT_GT(client_msg.size(), 0); + ASSERT_TRUE(pairing_auth_encrypt(client.get(), msg.data(), msg.size(), client_msg.data(), + &out_size)); + ASSERT_GT(out_size, 0); + client_msg.resize(out_size); + + server_msg.resize( + pairing_auth_safe_decrypted_size(server.get(), client_msg.data(), client_msg.size())); + ASSERT_GT(server_msg.size(), 0); + ASSERT_FALSE(pairing_auth_decrypt(server.get(), client_msg.data(), client_msg.size(), + server_msg.data(), &out_size)); + + // Server encrypts, client can't decrypt + server_msg.resize(pairing_auth_safe_encrypted_size(server.get(), msg.size())); + ASSERT_GT(server_msg.size(), 0); + ASSERT_TRUE(pairing_auth_encrypt(server.get(), msg.data(), msg.size(), server_msg.data(), + &out_size)); + ASSERT_GT(out_size, 0); + server_msg.resize(out_size); + + client_msg.resize( + pairing_auth_safe_decrypted_size(client.get(), server_msg.data(), server_msg.size())); + ASSERT_GT(client_msg.size(), 0); + ASSERT_FALSE(pairing_auth_decrypt(client.get(), server_msg.data(), server_msg.size(), + client_msg.data(), &out_size)); +} + +TEST_F(AdbPairingAuthTest, SamePasswords) { + // Register same password and then exchange the msgs. The + // encryption and decryption should succeed and have the same, unencrypted + // values. + std::vector<uint8_t> pswd{0x4f, 0x5a, 0x01, 0x46}; + auto client = makeClient(pswd); + std::vector<uint8_t> client_msg(pairing_auth_msg_size(client.get())); + ASSERT_FALSE(client_msg.empty()); + pairing_auth_get_spake2_msg(client.get(), client_msg.data()); + + auto server = makeServer(pswd); + std::vector<uint8_t> server_msg(pairing_auth_msg_size(server.get())); + ASSERT_FALSE(server_msg.empty()); + pairing_auth_get_spake2_msg(server.get(), server_msg.data()); + + EXPECT_TRUE(pairing_auth_init_cipher(client.get(), server_msg.data(), server_msg.size())); + EXPECT_TRUE(pairing_auth_init_cipher(server.get(), client_msg.data(), client_msg.size())); + + // We should be able to decrypt. + std::vector<uint8_t> msg{0x2a, 0x2b, 0x2c, 0xff, 0x45, 0x12, 0x33}; + // Client encrypts, server decrypts + size_t out_size; + client_msg.resize(pairing_auth_safe_encrypted_size(client.get(), msg.size())); + ASSERT_GT(client_msg.size(), 0); + ASSERT_TRUE(pairing_auth_encrypt(client.get(), msg.data(), msg.size(), client_msg.data(), + &out_size)); + ASSERT_GT(out_size, 0); + client_msg.resize(out_size); + + server_msg.resize( + pairing_auth_safe_decrypted_size(server.get(), client_msg.data(), client_msg.size())); + ASSERT_GT(server_msg.size(), 0); + ASSERT_TRUE(pairing_auth_decrypt(server.get(), client_msg.data(), client_msg.size(), + server_msg.data(), &out_size)); + ASSERT_EQ(out_size, msg.size()); + EXPECT_EQ(memcmp(msg.data(), server_msg.data(), out_size), 0); + + // Server encrypts, client decrypt + server_msg.resize(pairing_auth_safe_encrypted_size(server.get(), msg.size())); + ASSERT_GT(server_msg.size(), 0); + ASSERT_TRUE(pairing_auth_encrypt(server.get(), msg.data(), msg.size(), server_msg.data(), + &out_size)); + ASSERT_GT(out_size, 0); + server_msg.resize(out_size); + + client_msg.resize( + pairing_auth_safe_decrypted_size(client.get(), server_msg.data(), server_msg.size())); + ASSERT_GT(client_msg.size(), 0); + ASSERT_TRUE(pairing_auth_decrypt(client.get(), server_msg.data(), server_msg.size(), + client_msg.data(), &out_size)); + ASSERT_EQ(out_size, msg.size()); + EXPECT_EQ(memcmp(msg.data(), client_msg.data(), out_size), 0); +} + +TEST_F(AdbPairingAuthTest, CorruptedPayload) { + // Do a matching password for both server/client, but let's fudge with the + // header payload field. The decryption should fail. + std::vector<uint8_t> pswd{0x4f, 0x5a, 0x01, 0x46}; + auto client = makeClient(pswd); + std::vector<uint8_t> client_msg(pairing_auth_msg_size(client.get())); + ASSERT_FALSE(client_msg.empty()); + pairing_auth_get_spake2_msg(client.get(), client_msg.data()); + + auto server = makeServer(pswd); + std::vector<uint8_t> server_msg(pairing_auth_msg_size(server.get())); + ASSERT_FALSE(server_msg.empty()); + pairing_auth_get_spake2_msg(server.get(), server_msg.data()); + + EXPECT_TRUE(pairing_auth_init_cipher(client.get(), server_msg.data(), server_msg.size())); + EXPECT_TRUE(pairing_auth_init_cipher(server.get(), client_msg.data(), client_msg.size())); + + std::vector<uint8_t> msg{0x2a, 0x2b, 0x2c, 0xff, 0x45, 0x12, + 0x33, 0x45, 0x12, 0xea, 0xf2, 0xdb}; + { + // Client encrypts whole msg, server decrypts msg. Should be fine. + size_t out_size; + client_msg.resize(pairing_auth_safe_encrypted_size(client.get(), msg.size())); + ASSERT_GT(client_msg.size(), 0); + ASSERT_TRUE(pairing_auth_encrypt(client.get(), msg.data(), msg.size(), client_msg.data(), + &out_size)); + ASSERT_GT(out_size, 0); + client_msg.resize(out_size); + + server_msg.resize(pairing_auth_safe_decrypted_size(server.get(), client_msg.data(), + client_msg.size())); + ASSERT_GT(server_msg.size(), 0); + ASSERT_TRUE(pairing_auth_decrypt(server.get(), client_msg.data(), client_msg.size(), + server_msg.data(), &out_size)); + ASSERT_EQ(out_size, msg.size()); + EXPECT_EQ(memcmp(msg.data(), server_msg.data(), out_size), 0); + } + { + // 1) Client encrypts msg + // 2) append some data to the encrypted msg + // 3) change the payload field + // 4) server tries to decrypt. It should fail. + size_t out_size; + client_msg.resize(pairing_auth_safe_encrypted_size(client.get(), msg.size())); + ASSERT_GT(client_msg.size(), 0); + ASSERT_TRUE(pairing_auth_encrypt(client.get(), msg.data(), msg.size(), client_msg.data(), + &out_size)); + ASSERT_GT(out_size, 0); + client_msg.resize(out_size); + client_msg.push_back(0xaa); + // This requires knowledge of the layout of the data. payload is the + // first four bytes of the client_msg. + uint32_t* payload = reinterpret_cast<uint32_t*>(client_msg.data()); + *payload = ntohl(*payload); + *payload = htonl(*payload + 1); + + server_msg.resize(pairing_auth_safe_decrypted_size(server.get(), client_msg.data(), + client_msg.size())); + ASSERT_GT(server_msg.size(), 0); + ASSERT_FALSE(pairing_auth_decrypt(server.get(), client_msg.data(), client_msg.size(), + server_msg.data(), &out_size)); + } + { + // 1) Client encrypts msg + // 3) decrement the payload field + // 4) server tries to decrypt. It should fail. + size_t out_size; + client_msg.resize(pairing_auth_safe_encrypted_size(client.get(), msg.size())); + ASSERT_GT(client_msg.size(), 0); + ASSERT_TRUE(pairing_auth_encrypt(client.get(), msg.data(), msg.size(), client_msg.data(), + &out_size)); + ASSERT_GT(out_size, 0); + client_msg.resize(out_size); + // This requires knowledge of the layout of the data. payload is the + // first four bytes of the client_msg. + uint32_t* payload = reinterpret_cast<uint32_t*>(client_msg.data()); + *payload = ntohl(*payload); + *payload = htonl(*payload - 1); + + server_msg.resize(pairing_auth_safe_decrypted_size(server.get(), client_msg.data(), + client_msg.size())); + ASSERT_GT(server_msg.size(), 0); + ASSERT_FALSE(pairing_auth_decrypt(server.get(), client_msg.data(), client_msg.size(), + server_msg.data(), &out_size)); + } +} + +} // namespace pairing +} // namespace adb |