| // Copyright 2016 The Fuchsia Authors |
| // |
| // 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 "util/crypto_util/cipher.h" |
| |
| #include <cstring> |
| #include <memory> |
| #include <vector> |
| |
| #include "third_party/boringssl/src/include/openssl/aead.h" |
| #include "third_party/boringssl/src/include/openssl/bn.h" |
| #include "third_party/boringssl/src/include/openssl/digest.h" |
| #include "third_party/boringssl/src/include/openssl/ec.h" |
| #include "third_party/boringssl/src/include/openssl/ecdh.h" |
| #include "third_party/boringssl/src/include/openssl/evp.h" |
| #include "third_party/boringssl/src/include/openssl/hkdf.h" |
| #include "util/crypto_util/errors.h" |
| #include "util/crypto_util/random.h" |
| |
| namespace cobalt { |
| |
| namespace crypto { |
| |
| namespace { |
| // Note(pseudorandom) Curve constants are defined in |
| // third_party/boringssl/src/include/openssl/nid.h. NID_X9_62_prime256v1 |
| // refers to the NIST p256 curve (secp256r1 or P-256 defined in FIPS 186-4 |
| // here http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf) |
| #define EC_CURVE_CONSTANT NID_X9_62_prime256v1 |
| |
| const EVP_AEAD* GetAEAD() { |
| // Note(rudominer) The constants KEY_SIZE and NONCE_SIZE are set based |
| // on the algorithm chosen. If this algorithm changes you must also |
| // change those constants accordingly. |
| // |
| // NOTE(pseudorandom) By using a 256-bit curve in EC_CURVE_CONSTANT for |
| // public-key cryptography when SymmetricCipher is used in HybridCipher, |
| // the effective security level is AES-128 and not AES-256. |
| return EVP_aead_aes_256_gcm(); |
| } |
| static const size_t GROUP_ELEMENT_SIZE = 256 / 8; // (g^xy) object length |
| |
| // For hybrid mode, we can fix the nonce to all zeroes without losing |
| // security. See: https://goto.google.com/aes-gcm-zero-nonce-security |
| const byte kAllZeroNonce[SymmetricCipher::NONCE_SIZE] = {0x00}; |
| |
| } // namespace |
| |
| class CipherContext { |
| public: |
| ~CipherContext() { EVP_AEAD_CTX_cleanup(&impl_); } |
| |
| bool SetKey(const byte key[SymmetricCipher::KEY_SIZE]) { |
| EVP_AEAD_CTX_cleanup(&impl_); |
| return EVP_AEAD_CTX_init(&impl_, GetAEAD(), key, SymmetricCipher::KEY_SIZE, |
| EVP_AEAD_DEFAULT_TAG_LENGTH, NULL); |
| } |
| |
| EVP_AEAD_CTX* get() { return &impl_; } |
| |
| private: |
| EVP_AEAD_CTX impl_; |
| }; |
| |
| class HybridCipherContext { |
| public: |
| HybridCipherContext() : key_(nullptr, ::EVP_PKEY_free) {} |
| |
| bool ResetKey() { |
| key_.reset(EVP_PKEY_new()); |
| return (nullptr != key_); |
| } |
| |
| EVP_PKEY* GetKey() { return key_.get(); } |
| |
| private: |
| std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)> key_; |
| }; |
| |
| // SymmetricCipher methods. |
| |
| SymmetricCipher::SymmetricCipher() : context_(new CipherContext()) {} |
| |
| SymmetricCipher::~SymmetricCipher() {} |
| |
| bool SymmetricCipher::set_key(const byte key[KEY_SIZE]) { |
| return context_->SetKey(key); |
| } |
| |
| bool SymmetricCipher::Encrypt(const byte nonce[NONCE_SIZE], const byte* ptext, |
| int ptext_len, std::vector<byte>* ctext) { |
| int max_out_len = EVP_AEAD_max_overhead(GetAEAD()) + ptext_len; |
| ctext->resize(max_out_len); |
| size_t out_len; |
| int rc = |
| EVP_AEAD_CTX_seal(context_->get(), ctext->data(), &out_len, max_out_len, |
| nonce, NONCE_SIZE, ptext, ptext_len, NULL, 0); |
| ctext->resize(out_len); |
| return rc; |
| } |
| |
| bool SymmetricCipher::Decrypt(const byte nonce[NONCE_SIZE], const byte* ctext, |
| int ctext_len, std::vector<byte>* ptext) { |
| ptext->resize(ctext_len); |
| size_t out_len; |
| int rc = |
| EVP_AEAD_CTX_open(context_->get(), ptext->data(), &out_len, ptext->size(), |
| nonce, NONCE_SIZE, ctext, ctext_len, NULL, 0); |
| ptext->resize(out_len); |
| return rc; |
| } |
| |
| // HybridCipher methods. |
| |
| HybridCipher::HybridCipher() |
| : context_(new HybridCipherContext()), symm_cipher_(new SymmetricCipher) {} |
| |
| HybridCipher::~HybridCipher() {} |
| |
| bool HybridCipher::set_public_key(const byte public_key[PUBLIC_KEY_SIZE]) { |
| std::unique_ptr<EC_KEY, decltype(&::EC_KEY_free)> eckey( |
| EC_KEY_new_by_curve_name(EC_CURVE_CONSTANT), EC_KEY_free); |
| if (!eckey) { |
| return false; |
| } |
| |
| std::unique_ptr<EC_POINT, decltype(&::EC_POINT_free)> ecpoint( |
| EC_POINT_new(EC_KEY_get0_group(eckey.get())), EC_POINT_free); |
| if (!ecpoint) { |
| return false; |
| } |
| |
| // Read bytes from public_key into ecpoint |
| if (!EC_POINT_oct2point(EC_KEY_get0_group(eckey.get()), ecpoint.get(), |
| public_key, PUBLIC_KEY_SIZE, nullptr)) { |
| return false; |
| } |
| |
| // Setup eckey with public key from ecpoint |
| if (!EC_KEY_set_public_key(eckey.get(), ecpoint.get())) { |
| return false; |
| } |
| |
| // Setup pkey with EC public key eckey |
| context_->ResetKey(); |
| if (!EVP_PKEY_set1_EC_KEY(context_->GetKey(), eckey.get())) { |
| return false; |
| } |
| |
| // Success |
| return true; |
| } |
| |
| bool HybridCipher::set_private_key(const byte private_key[PRIVATE_KEY_SIZE]) { |
| std::unique_ptr<EC_KEY, decltype(&::EC_KEY_free)> eckey( |
| EC_KEY_new_by_curve_name(EC_CURVE_CONSTANT), EC_KEY_free); |
| if (!eckey) { |
| return false; |
| } |
| |
| std::unique_ptr<BIGNUM, decltype(&::BN_free)> bn_private_key( |
| BN_bin2bn(private_key, PRIVATE_KEY_SIZE, nullptr), BN_free); |
| if (!bn_private_key) { |
| return false; |
| } |
| |
| // Read bytes from public_key into BIGNUM object |
| if (!EC_KEY_set_private_key(eckey.get(), bn_private_key.get())) { |
| return false; |
| } |
| |
| // Setup pkey with EC public key eckey |
| context_->ResetKey(); |
| if (!EVP_PKEY_set1_EC_KEY(context_->GetKey(), eckey.get())) { |
| return false; |
| } |
| |
| // Success |
| return true; |
| } |
| |
| bool HybridCipher::Encrypt(const byte* ptext, int ptext_len, |
| std::vector<byte>* hybrid_ctext) { |
| byte public_key_part[PUBLIC_KEY_SIZE]; |
| byte salt[SALT_SIZE]; |
| std::vector<byte> symmetric_ctext; |
| if (!EncryptInternal(ptext, ptext_len, public_key_part, salt, |
| &symmetric_ctext)) { |
| return false; |
| } |
| hybrid_ctext->resize(symmetric_ctext.size() + PUBLIC_KEY_SIZE + SALT_SIZE); |
| std::memcpy(hybrid_ctext->data(), public_key_part, PUBLIC_KEY_SIZE); |
| std::memcpy(hybrid_ctext->data() + PUBLIC_KEY_SIZE, salt, SALT_SIZE); |
| std::memcpy(hybrid_ctext->data() + PUBLIC_KEY_SIZE + SALT_SIZE, |
| symmetric_ctext.data(), symmetric_ctext.size()); |
| return true; |
| } |
| |
| bool HybridCipher::EncryptInternal(const byte* ptext, int ptext_len, |
| byte public_key_part_out[PUBLIC_KEY_SIZE], |
| byte salt_out[SALT_SIZE], |
| std::vector<byte>* symmetric_ctext_out) { |
| std::unique_ptr<EC_KEY, decltype(&::EC_KEY_free)> eckey( |
| EC_KEY_new_by_curve_name(EC_CURVE_CONSTANT), EC_KEY_free); |
| if (!eckey) { |
| return false; |
| } |
| |
| // Generate fresh EC key. The public key will be published in |
| // public_key_part and the EC key will be used to generate a symmetric key |
| // that encrypts ptext bytes into symmetric_ctext_out |
| if (!EC_KEY_generate_key(eckey.get())) { |
| return false; |
| } |
| |
| // Write EC public key into public_key_part |
| if (EC_POINT_point2oct(EC_KEY_get0_group(eckey.get()), |
| EC_KEY_get0_public_key(eckey.get()), |
| POINT_CONVERSION_COMPRESSED, public_key_part_out, |
| PUBLIC_KEY_SIZE, nullptr) != PUBLIC_KEY_SIZE) { |
| return false; |
| } |
| |
| byte shared_key[GROUP_ELEMENT_SIZE]; // To store g^(xy) after ECDH |
| const EC_POINT* ec_pub_point = |
| EC_KEY_get0_public_key(EVP_PKEY_get0_EC_KEY(context_->GetKey())); |
| size_t shared_key_len = ECDH_compute_key(shared_key, sizeof(shared_key), |
| ec_pub_point, eckey.get(), nullptr); |
| if (shared_key_len != sizeof(shared_key)) { |
| return false; |
| } |
| |
| // Fill salt with random bytes |
| Random rand; |
| rand.RandomBytes(salt_out, SALT_SIZE); |
| |
| // Derive hkdf_derived_key by running HKDF with SHA512 and random salt |
| byte hkdf_derived_key[SymmetricCipher::KEY_SIZE]; |
| std::vector<byte> hkdf_input(PUBLIC_KEY_SIZE + GROUP_ELEMENT_SIZE); |
| std::memcpy(hkdf_input.data(), public_key_part_out, PUBLIC_KEY_SIZE); |
| std::memcpy(hkdf_input.data() + PUBLIC_KEY_SIZE, shared_key, |
| GROUP_ELEMENT_SIZE); |
| if (!HKDF(hkdf_derived_key, SymmetricCipher::KEY_SIZE, EVP_sha512(), |
| hkdf_input.data(), hkdf_input.size(), salt_out, SALT_SIZE, nullptr, |
| 0)) { |
| return false; |
| } |
| |
| // Do symmetric encryption with hkdf_derived_key |
| if (!symm_cipher_->set_key(hkdf_derived_key)) { |
| return false; |
| } |
| // For hybrid mode, we can fix the nonce to all zeroes without losing |
| // security. See: https://goto.google.com/aes-gcm-zero-nonce-security |
| if (!symm_cipher_->Encrypt(kAllZeroNonce, ptext, ptext_len, |
| symmetric_ctext_out)) { |
| return false; |
| } |
| |
| // Success |
| return true; |
| } |
| |
| bool HybridCipher::Decrypt(const byte* hybrid_ctext, int ctext_len, |
| std::vector<byte>* ptext) { |
| if (!hybrid_ctext || ctext_len < PUBLIC_KEY_SIZE + SALT_SIZE + 1) { |
| return false; |
| } |
| return DecryptInternal(hybrid_ctext, hybrid_ctext + PUBLIC_KEY_SIZE, |
| hybrid_ctext + PUBLIC_KEY_SIZE + SALT_SIZE, |
| ctext_len - (PUBLIC_KEY_SIZE + SALT_SIZE), ptext); |
| } |
| |
| bool HybridCipher::DecryptInternal(const byte public_key_part[PUBLIC_KEY_SIZE], |
| const byte salt[SALT_SIZE], |
| const byte* symmetric_ctext, |
| int symmetric_ctext_len, |
| std::vector<byte>* ptext) { |
| // Read public_key_part into new EVP_PKEY object |
| std::unique_ptr<EC_KEY, decltype(&::EC_KEY_free)> eckey( |
| EC_KEY_new_by_curve_name(EC_CURVE_CONSTANT), EC_KEY_free); |
| if (!eckey) { |
| return false; |
| } |
| std::unique_ptr<EC_POINT, decltype(&::EC_POINT_free)> ecpoint( |
| EC_POINT_new(EC_KEY_get0_group(eckey.get())), EC_POINT_free); |
| if (!ecpoint) { |
| return false; |
| } |
| |
| // Read bytes from public_key_part into ecpoint |
| if (!EC_POINT_oct2point(EC_KEY_get0_group(eckey.get()), ecpoint.get(), |
| public_key_part, PUBLIC_KEY_SIZE, nullptr)) { |
| return false; |
| } |
| |
| // Setup eckey with public key from ecpoint |
| if (!EC_KEY_set_public_key(eckey.get(), ecpoint.get())) { |
| return false; |
| } |
| |
| byte shared_key[GROUP_ELEMENT_SIZE]; // To store g^(xy) after ECDH |
| size_t shared_key_len = |
| ECDH_compute_key(shared_key, sizeof(shared_key), ecpoint.get(), |
| EVP_PKEY_get0_EC_KEY(context_->GetKey()), nullptr); |
| if (shared_key_len != sizeof(shared_key)) { |
| return false; |
| } |
| |
| // Derive hkdf_derived_key by running HKDF with SHA512 and given salt |
| byte hkdf_derived_key[SymmetricCipher::KEY_SIZE]; |
| std::vector<byte> hkdf_input(PUBLIC_KEY_SIZE + GROUP_ELEMENT_SIZE); |
| std::memcpy(hkdf_input.data(), public_key_part, PUBLIC_KEY_SIZE); |
| std::memcpy(hkdf_input.data() + PUBLIC_KEY_SIZE, shared_key, |
| GROUP_ELEMENT_SIZE); |
| if (!HKDF(hkdf_derived_key, SymmetricCipher::KEY_SIZE, EVP_sha512(), |
| hkdf_input.data(), hkdf_input.size(), salt, SALT_SIZE, nullptr, |
| 0)) { |
| return false; |
| } |
| |
| // Decrypt using symm_cipher_ interface |
| if (!symm_cipher_->set_key(hkdf_derived_key)) { |
| return false; |
| } |
| |
| // Our encryption always uses the all-zero nonce. |
| if (!symm_cipher_->Decrypt(kAllZeroNonce, symmetric_ctext, |
| symmetric_ctext_len, ptext)) { |
| return false; |
| } |
| |
| // Success |
| return true; |
| } |
| |
| } // namespace crypto |
| |
| } // namespace cobalt |