| // Copyright (c) 2012 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 "cryptohome/cryptolib.h" |
| |
| #include <limits> |
| #include <utility> |
| #include <vector> |
| |
| #include <malloc.h> |
| #include <openssl/err.h> |
| #include <openssl/evp.h> |
| #include <openssl/hmac.h> |
| #include <openssl/kdf.h> |
| #include <openssl/rand.h> |
| #include <openssl/sha.h> |
| #include <unistd.h> |
| |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/notreached.h> |
| #include <base/numerics/safe_conversions.h> |
| #include <base/stl_util.h> |
| #include <brillo/secure_blob.h> |
| #include <crypto/libcrypto-compat.h> |
| #include <crypto/scoped_openssl_types.h> |
| |
| #include "cryptohome/libscrypt_compat.h" |
| #include "cryptohome/platform.h" |
| |
| using brillo::SecureBlob; |
| |
| namespace { |
| |
| template <class T, class U> |
| T Sha1Helper(const U& data) { |
| SHA_CTX sha_context; |
| unsigned char md_value[SHA_DIGEST_LENGTH]; |
| T hash; |
| |
| SHA1_Init(&sha_context); |
| SHA1_Update(&sha_context, data.data(), data.size()); |
| SHA1_Final(md_value, &sha_context); |
| hash.resize(sizeof(md_value)); |
| memcpy(hash.data(), md_value, sizeof(md_value)); |
| // Zero the stack to match expectations set by SecureBlob. |
| brillo::SecureClearBytes(md_value, sizeof(md_value)); |
| return hash; |
| } |
| |
| template <class T, class U> |
| T Sha256Helper(const U& data) { |
| SHA256_CTX sha_context; |
| unsigned char md_value[SHA256_DIGEST_LENGTH]; |
| T hash; |
| |
| SHA256_Init(&sha_context); |
| SHA256_Update(&sha_context, data.data(), data.size()); |
| SHA256_Final(md_value, &sha_context); |
| hash.resize(sizeof(md_value)); |
| memcpy(hash.data(), md_value, sizeof(md_value)); |
| // Zero the stack to match expectations set by SecureBlob. |
| brillo::SecureClearBytes(md_value, sizeof(md_value)); |
| return hash; |
| } |
| |
| template <class T> |
| brillo::SecureBlob HmacSha512Helper(const brillo::SecureBlob& key, |
| const T& data) { |
| const int kSha512OutputSize = 64; |
| unsigned char mac[kSha512OutputSize]; |
| HMAC(EVP_sha512(), key.data(), key.size(), data.data(), data.size(), mac, |
| NULL); |
| return brillo::SecureBlob(std::begin(mac), std::end(mac)); |
| } |
| |
| template <class T> |
| brillo::SecureBlob HmacSha256Helper(const brillo::SecureBlob& key, |
| const T& data) { |
| const int kSha256OutputSize = 32; |
| unsigned char mac[kSha256OutputSize]; |
| HMAC(EVP_sha256(), key.data(), key.size(), data.data(), data.size(), mac, |
| NULL); |
| return brillo::SecureBlob(std::begin(mac), std::end(mac)); |
| } |
| |
| template <class T> |
| void BlobToHexToBufferHelper(const T& data, |
| void* buffer, |
| size_t buffer_length) { |
| static const char table[] = "0123456789abcdef"; |
| char* char_buffer = reinterpret_cast<char*>(buffer); |
| char* char_buffer_end = char_buffer + buffer_length; |
| for (uint8_t byte : data) { |
| if (char_buffer == char_buffer_end) |
| break; |
| *char_buffer++ = table[(byte >> 4) & 0x0f]; |
| if (char_buffer == char_buffer_end) |
| break; |
| *char_buffer++ = table[byte & 0x0f]; |
| } |
| if (char_buffer != char_buffer_end) |
| *char_buffer = '\x00'; |
| } |
| |
| } // namespace |
| |
| namespace cryptohome { |
| |
| // The well-known exponent used when generating RSA keys. Cryptohome only |
| // generates one RSA key, which is the system-wide cryptohome key. This is the |
| // common public exponent. |
| const unsigned int kWellKnownExponent = 65537; |
| |
| // The current number of hash rounds we use. Large enough to be a measurable |
| // amount of time, but not add too much overhead to login (around 10ms). |
| const unsigned int kDefaultPasswordRounds = 1337; |
| |
| // AES block size in bytes. |
| const unsigned int kAesBlockSize = 16; |
| |
| // The size of the AES-GCM IV (96-bits). |
| constexpr unsigned int kAesGcmIVSize = 96 / (sizeof(uint8_t) * CHAR_BIT); |
| |
| // The size of an AES-GCM key in cryptohome code (256-bits). |
| constexpr unsigned int kAesGcm256KeySize = 256 / (sizeof(uint8_t) * CHAR_BIT); |
| |
| // The size of the AES-GCM tag. |
| constexpr unsigned int kAesGcmTagSize = 16; |
| |
| // AES key size in bytes (256-bit). This key size is used for all key creation, |
| // though we currently only use 128 bits for the eCryptfs File Encryption Key |
| // (FEK). Larger than 128-bit has too great of a CPU overhead on unaccelerated |
| // architectures. |
| constexpr unsigned int kDefaultAesKeySize = 32; |
| |
| // The number of hash rounds we originally used when converting a password to a |
| // key. This is used when converting older cryptohome vault keysets. |
| const unsigned int kDefaultLegacyPasswordRounds = 1; |
| |
| // The maximum number of times to try decryption with the TPM. |
| constexpr int kTpmDecryptMaxRetries = 2; |
| |
| // The size in bytes of password blob to be generated by Scrypt. Should be the |
| // same size as the modulus of cryptohome key, since we need to be able to |
| // decrypt it. |
| constexpr unsigned int kDefaultPassBlobSize = 256; |
| |
| // Scrypt creates a header in the cipher text that we need to account for in |
| // buffer sizing. This accounts for both the header and the HMAC. |
| constexpr unsigned int kScryptMetadataSize = 128; |
| |
| // An upper bound on the amount of memory that we allow Scrypt to use when |
| // performing key strengthening (32MB). A large size is okay since we only use |
| // Scrypt during the login process, before the user is logged in. This memory |
| // is managed (and freed) by the scrypt library. |
| const unsigned int kScryptMaxMem = 32 * 1024 * 1024; |
| |
| // An upper bound on the amount of time we allow Scrypt to use when performing |
| // key strenthening (1/3s) for encryption. |
| constexpr double kScryptMaxEncryptTime = 0.333; |
| |
| // These are the params to use for production code. |
| constexpr ScryptParameters kDefaultScryptParams; |
| |
| // scrypt, with the default params, is too slow for unit testing so here are |
| // some fast parameters only for test code. |
| constexpr ScryptParameters kTestScryptParams = {1024, 8, 1}; |
| |
| // static |
| ScryptParameters CryptoLib::gScryptParams = kDefaultScryptParams; |
| |
| void CryptoLib::GetSecureRandom(unsigned char* buf, size_t length) { |
| // In unlikely situations, such as the random generator lacks enough entropy, |
| // RAND_bytes can fail. |
| CHECK_EQ(1, RAND_bytes(buf, base::checked_cast<int>(length))); |
| } |
| |
| SecureBlob CryptoLib::CreateSecureRandomBlob(size_t length) { |
| SecureBlob blob(length); |
| GetSecureRandom(reinterpret_cast<unsigned char*>(blob.data()), length); |
| return blob; |
| } |
| |
| bool CryptoLib::CreateRsaKey(size_t key_bits, SecureBlob* n, SecureBlob* p) { |
| crypto::ScopedRSA rsa(RSA_new()); |
| crypto::ScopedBIGNUM e(BN_new()); |
| if (!rsa || !e) { |
| LOG(ERROR) << "Failed to allocate RSA or BIGNUM."; |
| return false; |
| } |
| if (!BN_set_word(e.get(), kWellKnownExponent) || |
| !RSA_generate_key_ex(rsa.get(), key_bits, e.get(), nullptr)) { |
| LOG(ERROR) << "RSA key generation failed."; |
| return false; |
| } |
| |
| SecureBlob local_n(RSA_size(rsa.get())); |
| const BIGNUM* rsa_n; |
| RSA_get0_key(rsa.get(), &rsa_n, nullptr, nullptr); |
| if (BN_bn2bin(rsa_n, local_n.data()) <= 0) { |
| LOG(ERROR) << "Unable to get modulus from RSA key."; |
| return false; |
| } |
| |
| const BIGNUM* rsa_p; |
| RSA_get0_factors(rsa.get(), &rsa_p, nullptr); |
| SecureBlob local_p(BN_num_bytes(rsa_p)); |
| if (BN_bn2bin(rsa_p, local_p.data()) <= 0) { |
| LOG(ERROR) << "Unable to get private key from RSA key."; |
| return false; |
| } |
| |
| n->swap(local_n); |
| p->swap(local_p); |
| return true; |
| } |
| |
| bool CryptoLib::FillRsaPrivateKeyFromSecretPrime(const SecureBlob& secret_prime, |
| RSA* rsa) { |
| crypto::ScopedOpenSSL<BN_CTX, BN_CTX_free> bn_context(BN_CTX_new()); |
| if (!bn_context) { |
| LOG(ERROR) << "Failed to allocate BN_CTX structure"; |
| return false; |
| } |
| // Load the first prime from the parameter. |
| crypto::ScopedBIGNUM p(BN_new()), q(BN_new()), remainder(BN_new()); |
| if (!p || !q || !remainder) { |
| LOG(ERROR) << "Failed to allocate BIGNUM structure"; |
| return false; |
| } |
| |
| if (!BN_bin2bn(secret_prime.data(), secret_prime.size(), p.get())) { |
| LOG(ERROR) << "Failed to construct secret prime from binary blob"; |
| return false; |
| } |
| // Calculate the second prime by dividing the public modulus. |
| const BIGNUM* rsa_n; |
| const BIGNUM* rsa_e; |
| RSA_get0_key(rsa, &rsa_n, &rsa_e, nullptr); |
| if (!BN_div(q.get(), remainder.get(), rsa_n, p.get(), bn_context.get())) { |
| LOG(ERROR) << "Failed to divide public modulus"; |
| return false; |
| } |
| if (!BN_is_zero(remainder.get())) { |
| LOG(ERROR) << "Bad secret prime: does not divide the modulus evenly"; |
| return false; |
| } |
| |
| // Calculate the private exponent. |
| crypto::ScopedBIGNUM d(BN_new()); |
| crypto::ScopedBIGNUM decremented_p(BN_new()); |
| crypto::ScopedBIGNUM decremented_q(BN_new()); |
| crypto::ScopedBIGNUM totient(BN_new()); |
| if (!d || !decremented_p || !decremented_q || !totient) { |
| LOG(ERROR) << "Failed to allocate BIGNUM structure"; |
| return false; |
| } |
| if (!BN_sub(decremented_p.get(), p.get(), BN_value_one()) || |
| !BN_sub(decremented_q.get(), q.get(), BN_value_one()) || |
| !BN_mul(totient.get(), decremented_p.get(), decremented_q.get(), |
| bn_context.get())) { |
| LOG(ERROR) << "Failed to calculate totient function"; |
| return false; |
| } |
| if (!BN_mod_inverse(d.get(), rsa_e, totient.get(), bn_context.get())) { |
| LOG(ERROR) << "Failed to calculate modular inverse"; |
| return false; |
| } |
| |
| // Calculate the private exponent modulo the decremented first and second |
| // primes. |
| crypto::ScopedBIGNUM dmp1(BN_new()), dmq1(BN_new()), iqmp(BN_new()); |
| if (!dmp1 || !dmq1 || !iqmp) { |
| LOG(ERROR) << "Failed to allocate BIGNUM structure"; |
| return false; |
| } |
| if (!BN_mod(dmp1.get(), d.get(), decremented_p.get(), bn_context.get()) || |
| !BN_mod(dmq1.get(), d.get(), decremented_q.get(), bn_context.get())) { |
| LOG(ERROR) << "Failed to calculate the private exponent over the modulo"; |
| return false; |
| } |
| // Calculate the inverse of the second prime modulo the first prime. |
| if (!BN_mod_inverse(iqmp.get(), q.get(), p.get(), bn_context.get())) { |
| LOG(ERROR) << "Failed to calculate the inverse of the prime module the " |
| "other prime"; |
| return false; |
| } |
| |
| // All checks pass, now assign fields |
| if (!RSA_set0_factors(rsa, p.release(), q.release()) || |
| !RSA_set0_key(rsa, nullptr, nullptr, d.release()) || |
| !RSA_set0_crt_params(rsa, dmp1.release(), dmq1.release(), |
| iqmp.release())) { |
| LOG(ERROR) << "Failed to set RSA parameters."; |
| return false; |
| } |
| return true; |
| } |
| |
| brillo::Blob CryptoLib::Sha1(const brillo::Blob& data) { |
| return Sha1Helper<brillo::Blob, brillo::Blob>(data); |
| } |
| |
| brillo::SecureBlob CryptoLib::Sha1ToSecureBlob(const brillo::Blob& data) { |
| return Sha1Helper<brillo::SecureBlob, brillo::Blob>(data); |
| } |
| |
| brillo::SecureBlob CryptoLib::Sha1(const brillo::SecureBlob& data) { |
| return Sha1Helper<brillo::SecureBlob, brillo::SecureBlob>(data); |
| } |
| |
| brillo::Blob CryptoLib::Sha256(const brillo::Blob& data) { |
| return Sha256Helper<brillo::Blob, brillo::Blob>(data); |
| } |
| |
| brillo::SecureBlob CryptoLib::Sha256ToSecureBlob(const brillo::Blob& data) { |
| return Sha256Helper<brillo::SecureBlob, brillo::Blob>(data); |
| } |
| |
| brillo::SecureBlob CryptoLib::Sha256(const brillo::SecureBlob& data) { |
| return Sha256Helper<brillo::SecureBlob, brillo::SecureBlob>(data); |
| } |
| |
| brillo::SecureBlob CryptoLib::HmacSha512(const brillo::SecureBlob& key, |
| const brillo::Blob& data) { |
| return HmacSha512Helper(key, data); |
| } |
| |
| brillo::SecureBlob CryptoLib::HmacSha512(const brillo::SecureBlob& key, |
| const brillo::SecureBlob& data) { |
| return HmacSha512Helper(key, data); |
| } |
| |
| brillo::SecureBlob CryptoLib::HmacSha256(const brillo::SecureBlob& key, |
| const brillo::Blob& data) { |
| return HmacSha256Helper(key, data); |
| } |
| |
| brillo::SecureBlob CryptoLib::HmacSha256(const brillo::SecureBlob& key, |
| const brillo::SecureBlob& data) { |
| return HmacSha256Helper(key, data); |
| } |
| |
| size_t CryptoLib::GetAesBlockSize() { |
| return EVP_CIPHER_block_size(EVP_aes_256_cbc()); |
| } |
| |
| bool CryptoLib::PasskeyToAesKey(const brillo::SecureBlob& passkey, |
| const brillo::SecureBlob& salt, |
| unsigned int rounds, |
| SecureBlob* key, |
| SecureBlob* iv) { |
| if (salt.size() != PKCS5_SALT_LEN) { |
| LOG(ERROR) << "Bad salt size."; |
| return false; |
| } |
| |
| const EVP_CIPHER* cipher = EVP_aes_256_cbc(); |
| SecureBlob aes_key(EVP_CIPHER_key_length(cipher)); |
| SecureBlob local_iv(EVP_CIPHER_iv_length(cipher)); |
| |
| // Convert the passkey to a key |
| if (!EVP_BytesToKey(cipher, EVP_sha1(), salt.data(), passkey.data(), |
| passkey.size(), rounds, aes_key.data(), |
| local_iv.data())) { |
| LOG(ERROR) << "Failure converting bytes to key"; |
| return false; |
| } |
| |
| key->swap(aes_key); |
| if (iv) { |
| iv->swap(local_iv); |
| } |
| |
| return true; |
| } |
| |
| bool CryptoLib::AesGcmDecrypt(const brillo::SecureBlob& ciphertext, |
| const brillo::SecureBlob& tag, |
| const brillo::SecureBlob& key, |
| const brillo::SecureBlob& iv, |
| brillo::SecureBlob* plaintext) { |
| CHECK_EQ(key.size(), kAesGcm256KeySize); |
| CHECK_EQ(iv.size(), kAesGcmIVSize); |
| CHECK_EQ(tag.size(), kAesGcmTagSize); |
| |
| crypto::ScopedEVP_CIPHER_CTX ctx(EVP_CIPHER_CTX_new()); |
| if (ctx.get() == nullptr) { |
| LOG(ERROR) << "Failed to create cipher ctx."; |
| return false; |
| } |
| |
| if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr, nullptr, |
| nullptr) != 1) { |
| LOG(ERROR) << "Failed to init decrypt."; |
| return false; |
| } |
| |
| if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, kAesGcmIVSize, |
| nullptr) != 1) { |
| LOG(ERROR) << "Failed to set iv size."; |
| return false; |
| } |
| |
| if (EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key.data(), iv.data()) != |
| 1) { |
| LOG(ERROR) << "Failed to add key and iv to decrypt operation."; |
| return false; |
| } |
| |
| plaintext->resize(ciphertext.size()); |
| int output_size = 0; |
| if (EVP_DecryptUpdate(ctx.get(), plaintext->data(), &output_size, |
| ciphertext.data(), ciphertext.size()) != 1) { |
| LOG(ERROR) << "Failed to decrypt the plaintext."; |
| return false; |
| } |
| |
| if (output_size != ciphertext.size()) { |
| LOG(ERROR) << "Failed to process entire ciphertext."; |
| return false; |
| } |
| |
| uint8_t* tag_ptr = const_cast<uint8_t*>(tag.data()); |
| if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), |
| tag_ptr) != 1) { |
| LOG(ERROR) << "Failed to set the tag."; |
| return false; |
| } |
| |
| output_size = 0; |
| int ret_val = EVP_DecryptFinal_ex(ctx.get(), nullptr, &output_size); |
| |
| return output_size == 0 && ret_val > 0; |
| } |
| |
| bool CryptoLib::AesGcmEncrypt(const brillo::SecureBlob& plaintext, |
| const brillo::SecureBlob& key, |
| brillo::SecureBlob* iv, |
| brillo::SecureBlob* tag, |
| brillo::SecureBlob* ciphertext) { |
| CHECK_EQ(key.size(), kAesGcm256KeySize); |
| |
| iv->resize(kAesGcmIVSize); |
| GetSecureRandom(iv->data(), kAesGcmIVSize); |
| |
| crypto::ScopedEVP_CIPHER_CTX ctx(EVP_CIPHER_CTX_new()); |
| if (ctx.get() == nullptr) { |
| LOG(ERROR) << "Failed to create context."; |
| return false; |
| } |
| |
| if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr, nullptr, |
| nullptr) != 1) { |
| LOG(ERROR) << "Failed to init aes-gcm-256."; |
| return false; |
| } |
| |
| if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, kAesGcmIVSize, |
| nullptr) != 1) { |
| LOG(ERROR) << "Failed to set IV length."; |
| return false; |
| } |
| |
| if (EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr, key.data(), iv->data()) != |
| 1) { |
| LOG(ERROR) << "Failed to init key and iv."; |
| return false; |
| } |
| |
| ciphertext->resize(plaintext.size()); |
| int processed_bytes = 0; |
| if (EVP_EncryptUpdate(ctx.get(), ciphertext->data(), &processed_bytes, |
| plaintext.data(), plaintext.size()) != 1) { |
| LOG(ERROR) << "Failed to encrypt plaintext."; |
| return false; |
| } |
| |
| if (plaintext.size() != processed_bytes) { |
| LOG(ERROR) << "Did not process the entire plaintext."; |
| return false; |
| } |
| |
| int unused_output_length; |
| if (EVP_EncryptFinal_ex(ctx.get(), nullptr, &unused_output_length) != 1) { |
| LOG(ERROR) << "Failed to finalize encryption."; |
| return false; |
| } |
| |
| tag->resize(kAesGcmTagSize); |
| if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, kAesGcmTagSize, |
| tag->data()) != 1) { |
| LOG(ERROR) << "Failed to retrieve tag."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CryptoLib::AesEncryptDeprecated(const SecureBlob& plaintext, |
| const SecureBlob& key, |
| const SecureBlob& iv, |
| SecureBlob* ciphertext) { |
| return AesEncryptSpecifyBlockMode(plaintext, 0, plaintext.size(), key, iv, |
| kPaddingCryptohomeDefaultDeprecated, kCbc, |
| ciphertext); |
| } |
| |
| bool CryptoLib::AesDecryptDeprecated(const SecureBlob& ciphertext, |
| const SecureBlob& key, |
| const SecureBlob& iv, |
| SecureBlob* plaintext) { |
| return AesDecryptSpecifyBlockMode(ciphertext, 0, ciphertext.size(), key, iv, |
| kPaddingCryptohomeDefaultDeprecated, kCbc, |
| plaintext); |
| } |
| |
| // This is the reverse operation of AesEncryptSpecifyBlockMode above. See that |
| // method for a description of how padding and block_mode affect the crypto |
| // operations. This method automatically removes and verifies the padding, so |
| // plain_text (on success) will contain the original data. |
| // |
| // Note that a call to AesDecryptSpecifyBlockMode needs to have the same padding |
| // and block_mode as the corresponding encrypt call. Changing the block mode |
| // will drastically alter the decryption. And an incorrect PaddingScheme will |
| // result in the padding verification failing, for which the method call fails, |
| // even if the key and initialization vector were correct. |
| bool CryptoLib::AesDecryptSpecifyBlockMode(const SecureBlob& encrypted, |
| unsigned int start, |
| unsigned int count, |
| const SecureBlob& key, |
| const SecureBlob& iv, |
| PaddingScheme padding, |
| BlockMode block_mode, |
| SecureBlob* plain_text) { |
| if ((start > encrypted.size()) || ((start + count) > encrypted.size()) || |
| ((start + count) < start)) { |
| return false; |
| } |
| SecureBlob local_plain_text(count); |
| |
| if (local_plain_text.size() > |
| static_cast<unsigned int>(std::numeric_limits<int>::max())) { |
| // EVP_DecryptUpdate takes a signed int |
| return false; |
| } |
| int final_size = 0; |
| int decrypt_size = local_plain_text.size(); |
| |
| const EVP_CIPHER* cipher; |
| switch (block_mode) { |
| case kCbc: |
| cipher = EVP_aes_256_cbc(); |
| break; |
| case kEcb: |
| cipher = EVP_aes_256_ecb(); |
| break; |
| case kCtr: |
| cipher = EVP_aes_256_ctr(); |
| break; |
| default: |
| LOG(ERROR) << "Invalid block mode specified: " << block_mode; |
| return false; |
| } |
| if (key.size() != static_cast<unsigned int>(EVP_CIPHER_key_length(cipher))) { |
| LOG(ERROR) << "Invalid key length of " << key.size() << ", expected " |
| << EVP_CIPHER_key_length(cipher); |
| return false; |
| } |
| // ECB ignores the IV, so only check the IV length if we are using a different |
| // block mode. |
| if ((block_mode != kEcb) && |
| (iv.size() != static_cast<unsigned int>(EVP_CIPHER_iv_length(cipher)))) { |
| LOG(ERROR) << "Invalid iv length of " << iv.size() << ", expected " |
| << EVP_CIPHER_iv_length(cipher); |
| return false; |
| } |
| |
| crypto::ScopedEVP_CIPHER_CTX decryption_context(EVP_CIPHER_CTX_new()); |
| if (!decryption_context) { |
| LOG(ERROR) << "Failed to allocate EVP_CIPHER_CTX"; |
| return false; |
| } |
| EVP_DecryptInit_ex(decryption_context.get(), cipher, nullptr, key.data(), |
| iv.data()); |
| if (padding == kPaddingNone) { |
| EVP_CIPHER_CTX_set_padding(decryption_context.get(), 0); |
| } |
| |
| // Make sure we're not pointing into an empty buffer or past the end. |
| const unsigned char* encrypted_buf = NULL; |
| if (start < encrypted.size()) |
| encrypted_buf = &encrypted[start]; |
| |
| if (!EVP_DecryptUpdate(decryption_context.get(), local_plain_text.data(), |
| &decrypt_size, encrypted_buf, count)) { |
| LOG(ERROR) << "DecryptUpdate failed"; |
| return false; |
| } |
| |
| // In the case of local_plain_text being full, we must avoid trying to |
| // point past the end of the buffer when calling EVP_DecryptFinal_ex(). |
| unsigned char* final_buf = NULL; |
| if (static_cast<unsigned int>(decrypt_size) < local_plain_text.size()) |
| final_buf = &local_plain_text[decrypt_size]; |
| |
| if (!EVP_DecryptFinal_ex(decryption_context.get(), final_buf, &final_size)) { |
| unsigned long err = ERR_get_error(); // NOLINT openssl types |
| ERR_load_ERR_strings(); |
| ERR_load_crypto_strings(); |
| |
| LOG(ERROR) << "DecryptFinal Error: " << err << ": " |
| << ERR_lib_error_string(err) << ", " |
| << ERR_func_error_string(err) << ", " |
| << ERR_reason_error_string(err); |
| |
| return false; |
| } |
| final_size += decrypt_size; |
| |
| if (padding == kPaddingCryptohomeDefaultDeprecated) { |
| if (final_size < SHA_DIGEST_LENGTH) { |
| LOG(ERROR) << "Plain text was too small."; |
| return false; |
| } |
| |
| final_size -= SHA_DIGEST_LENGTH; |
| |
| SHA_CTX sha_context; |
| unsigned char md_value[SHA_DIGEST_LENGTH]; |
| |
| SHA1_Init(&sha_context); |
| SHA1_Update(&sha_context, local_plain_text.data(), final_size); |
| SHA1_Final(md_value, &sha_context); |
| |
| const unsigned char* md_ptr = local_plain_text.data(); |
| md_ptr += final_size; |
| if (brillo::SecureMemcmp(md_ptr, md_value, SHA_DIGEST_LENGTH)) { |
| LOG(ERROR) << "Digest verification failed."; |
| return false; |
| } |
| } |
| |
| local_plain_text.resize(final_size); |
| plain_text->swap(local_plain_text); |
| return true; |
| } |
| |
| // AesEncryptSpecifyBlockMode encrypts the bytes in plain_text using AES, |
| // placing the output into encrypted. Aside from range constraints (start and |
| // count) and the key and initialization vector, this method has two parameters |
| // that control how the ciphertext is generated and are useful in encrypting |
| // specific types of data in cryptohome. |
| // |
| // First, padding specifies whether and how the plaintext is padded before |
| // encryption. The three options, described in the PaddingScheme enumeration |
| // are used as such: |
| // - kPaddingNone is used to mix the user's passkey (derived from the |
| // password) into the encrypted blob storing the vault keyset when the TPM |
| // is used. This is described in more detail in the README file. There is |
| // no padding in this case, and the size of plain_text needs to be a |
| // multiple of the AES block size (16 bytes). |
| // - kPaddingStandard uses standard PKCS padding, which is the default for |
| // OpenSSL. |
| // - kPaddingCryptohomeDefaultDeprecated appends a SHA1 hash of the plaintext |
| // in plain_text before passing it to OpenSSL, which still uses PKCS padding |
| // so that we do not have to re-implement block-multiple padding ourselves. |
| // This padding scheme allows us to strongly verify the plaintext on |
| // decryption, which is essential when, for example, test decrypting a nonce |
| // to test whether a password was correct (we do this in user_session.cc). |
| // This padding is now deprecated and a standard integrity checking |
| // algorithm such as AES-GCM should be used instead. |
| // |
| // The block mode switches between ECB and CBC. Generally, CBC is used for most |
| // AES crypto that we perform, since it is a better mode for us for data that is |
| // larger than the block size. We use ECB only when mixing the user passkey |
| // into the TPM-encrypted blob, since we only encrypt a single block of that |
| // data. |
| bool CryptoLib::AesEncryptSpecifyBlockMode(const SecureBlob& plain_text, |
| unsigned int start, |
| unsigned int count, |
| const SecureBlob& key, |
| const SecureBlob& iv, |
| PaddingScheme padding, |
| BlockMode block_mode, |
| SecureBlob* encrypted) { |
| // Verify that the range is within the data passed |
| if ((start > plain_text.size()) || ((start + count) > plain_text.size()) || |
| ((start + count) < start)) { |
| return false; |
| } |
| if (count > static_cast<unsigned int>(std::numeric_limits<int>::max())) { |
| // EVP_EncryptUpdate takes a signed int |
| return false; |
| } |
| |
| // First set the output size based on the padding scheme. No padding means |
| // that the input needs to be a multiple of the block size, and the output |
| // size is equal to the input size. Standard padding means we should allocate |
| // up to a full block additional for the PKCS padding. Cryptohome default |
| // means we should allocate a full block additional for the PKCS padding and |
| // enough for a SHA1 hash. |
| unsigned int block_size = GetAesBlockSize(); |
| unsigned int needed_size = count; |
| switch (padding) { |
| case kPaddingCryptohomeDefaultDeprecated: |
| // The AES block size and SHA digest length are not enough for this to |
| // overflow, as needed_size is initialized to count, which must be <= |
| // INT_MAX, but needed_size is itself an unsigned. The block size and |
| // digest length are fixed by the algorithm. |
| needed_size += block_size + SHA_DIGEST_LENGTH; |
| break; |
| case kPaddingStandard: |
| needed_size += block_size; |
| break; |
| case kPaddingNone: |
| if (count % block_size) { |
| LOG(ERROR) << "Data size (" << count << ") was not a multiple " |
| << "of the block size (" << block_size << ")"; |
| return false; |
| } |
| break; |
| default: |
| LOG(ERROR) << "Invalid padding specified"; |
| return false; |
| break; |
| } |
| SecureBlob cipher_text(needed_size); |
| |
| // Set the block mode |
| const EVP_CIPHER* cipher; |
| switch (block_mode) { |
| case kCbc: |
| cipher = EVP_aes_256_cbc(); |
| break; |
| case kEcb: |
| cipher = EVP_aes_256_ecb(); |
| break; |
| case kCtr: |
| cipher = EVP_aes_256_ctr(); |
| break; |
| default: |
| LOG(ERROR) << "Invalid block mode specified"; |
| return false; |
| } |
| if (key.size() != static_cast<unsigned int>(EVP_CIPHER_key_length(cipher))) { |
| LOG(ERROR) << "Invalid key length of " << key.size() << ", expected " |
| << EVP_CIPHER_key_length(cipher); |
| return false; |
| } |
| |
| // ECB ignores the IV, so only check the IV length if we are using a different |
| // block mode. |
| if ((block_mode != kEcb) && |
| (iv.size() != static_cast<unsigned int>(EVP_CIPHER_iv_length(cipher)))) { |
| LOG(ERROR) << "Invalid iv length of " << iv.size() << ", expected " |
| << EVP_CIPHER_iv_length(cipher); |
| return false; |
| } |
| |
| // Initialize the OpenSSL crypto context |
| crypto::ScopedEVP_CIPHER_CTX encryption_context(EVP_CIPHER_CTX_new()); |
| if (!encryption_context) { |
| LOG(ERROR) << "Failed to allocate EVP_CIPHER_CTX"; |
| return false; |
| } |
| |
| EVP_EncryptInit_ex(encryption_context.get(), cipher, nullptr, key.data(), |
| iv.data()); |
| if (padding == kPaddingNone) { |
| EVP_CIPHER_CTX_set_padding(encryption_context.get(), 0); |
| } |
| |
| // First, encrypt the plain_text data |
| unsigned int current_size = 0; |
| int encrypt_size = 0; |
| |
| // Make sure we're not pointing into an empty buffer or past the end. |
| const unsigned char* plain_buf = NULL; |
| if (start < plain_text.size()) |
| plain_buf = &plain_text[start]; |
| |
| if (!EVP_EncryptUpdate(encryption_context.get(), &cipher_text[current_size], |
| &encrypt_size, plain_buf, count)) { |
| LOG(ERROR) << "EncryptUpdate failed"; |
| return false; |
| } |
| current_size += encrypt_size; |
| encrypt_size = 0; |
| |
| // Next, if the padding uses the cryptohome default scheme, encrypt a SHA1 |
| // hash of the preceding plain_text into the output data |
| if (padding == kPaddingCryptohomeDefaultDeprecated) { |
| SHA_CTX sha_context; |
| unsigned char md_value[SHA_DIGEST_LENGTH]; |
| |
| SHA1_Init(&sha_context); |
| SHA1_Update(&sha_context, &plain_text[start], count); |
| SHA1_Final(md_value, &sha_context); |
| if (!EVP_EncryptUpdate(encryption_context.get(), &cipher_text[current_size], |
| &encrypt_size, md_value, sizeof(md_value))) { |
| LOG(ERROR) << "EncryptUpdate failed"; |
| return false; |
| } |
| current_size += encrypt_size; |
| encrypt_size = 0; |
| } |
| |
| // In the case of cipher_text being full, we must avoid trying to |
| // point past the end of the buffer when calling EVP_EncryptFinal_ex(). |
| unsigned char* final_buf = NULL; |
| if (static_cast<unsigned int>(current_size) < cipher_text.size()) |
| final_buf = &cipher_text[current_size]; |
| |
| // Finally, finish the encryption |
| if (!EVP_EncryptFinal_ex(encryption_context.get(), final_buf, |
| &encrypt_size)) { |
| LOG(ERROR) << "EncryptFinal failed"; |
| return false; |
| } |
| current_size += encrypt_size; |
| cipher_text.resize(current_size); |
| |
| encrypted->swap(cipher_text); |
| return true; |
| } |
| |
| // Obscure (and Unobscure) RSA messages. |
| // Let k be a key derived from the user passphrase. On disk, we store |
| // m = ObscureRSAMessage(RSA-on-TPM(random-data), k). The reason for this |
| // function is the existence of an ambiguity in the TPM spec: the format of data |
| // returned by Tspi_Data_Bind is unspecified, so it's _possible_ (although does |
| // not happen in practice) that RSA-on-TPM(random-data) could start with some |
| // kind of ASN.1 header or whatever (some known data). If this was true, and we |
| // encrypted all of RSA-on-TPM(random-data), then one could test values of k by |
| // decrypting RSA-on-TPM(random-data) and looking for the known header, which |
| // would allow brute-forcing the user passphrase without talking to the TPM. |
| // |
| // Therefore, we instead encrypt _one block_ of RSA-on-TPM(random-data) with AES |
| // in ECB mode; we pick the last AES block, in the hope that that block will be |
| // part of the RSA message. TODO(ellyjones): why? if the TPM could add a header, |
| // it could also add a footer, and we'd be just as sunk. |
| // |
| // If we do encrypt part of the RSA message, the entirety of |
| // RSA-on-TPM(random-data) should be impossible to decrypt, without encrypting |
| // any known plaintext. This approach also requires brute-force attempts on k to |
| // go through the TPM, since there's no way to test a potential decryption |
| // without doing UnRSA-on-TPM() to see if the message is valid now. |
| bool CryptoLib::ObscureRSAMessage(const SecureBlob& plaintext, |
| const SecureBlob& key, |
| SecureBlob* ciphertext) { |
| unsigned int aes_block_size = GetAesBlockSize(); |
| if (plaintext.size() < aes_block_size * 2) { |
| LOG(ERROR) << "Plaintext is too small."; |
| return false; |
| } |
| unsigned int offset = plaintext.size() - aes_block_size; |
| |
| SecureBlob obscured_chunk; |
| if (!AesEncryptSpecifyBlockMode(plaintext, offset, aes_block_size, key, |
| SecureBlob(0), kPaddingNone, kEcb, |
| &obscured_chunk)) { |
| LOG(ERROR) << "AES encryption failed."; |
| return false; |
| } |
| ciphertext->resize(plaintext.size()); |
| char* data = reinterpret_cast<char*>(ciphertext->data()); |
| memcpy(data, plaintext.data(), plaintext.size()); |
| memcpy(data + offset, obscured_chunk.data(), obscured_chunk.size()); |
| return true; |
| } |
| |
| bool CryptoLib::UnobscureRSAMessage(const SecureBlob& ciphertext, |
| const SecureBlob& key, |
| SecureBlob* plaintext) { |
| unsigned int aes_block_size = GetAesBlockSize(); |
| if (ciphertext.size() < aes_block_size * 2) { |
| LOG(ERROR) << "Ciphertext is is too small."; |
| return false; |
| } |
| unsigned int offset = ciphertext.size() - aes_block_size; |
| |
| SecureBlob unobscured_chunk; |
| if (!AesDecryptSpecifyBlockMode(ciphertext, offset, aes_block_size, key, |
| SecureBlob(0), kPaddingNone, kEcb, |
| &unobscured_chunk)) { |
| LOG(ERROR) << "AES decryption failed."; |
| return false; |
| } |
| plaintext->resize(ciphertext.size()); |
| char* data = reinterpret_cast<char*>(plaintext->data()); |
| memcpy(data, ciphertext.data(), ciphertext.size()); |
| memcpy(data + offset, unobscured_chunk.data(), unobscured_chunk.size()); |
| return true; |
| } |
| |
| bool CryptoLib::RsaOaepEncrypt(const brillo::SecureBlob& plaintext, |
| RSA* key, |
| brillo::Blob* ciphertext) { |
| if (plaintext.empty()) |
| return false; |
| ciphertext->resize(RSA_size(key)); |
| const int encryption_result = |
| RSA_public_encrypt(plaintext.size(), plaintext.data(), ciphertext->data(), |
| key, RSA_PKCS1_OAEP_PADDING); |
| if (encryption_result == -1) { |
| LOG(ERROR) << "Failed to perform RSAES-OAEP MGF1 encryption"; |
| return false; |
| } |
| if (encryption_result != ciphertext->size()) { |
| NOTREACHED() |
| << "RSAES-OAEP MGF1 encryption returned unexpected amount of data"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool CryptoLib::RsaOaepDecrypt(const brillo::SecureBlob& ciphertext, |
| const brillo::SecureBlob& oaep_label, |
| RSA* key, |
| brillo::SecureBlob* plaintext) { |
| const int key_size = RSA_size(key); |
| SecureBlob raw_decrypted_data(key_size); |
| const int decryption_result = |
| RSA_private_decrypt(ciphertext.size(), ciphertext.data(), |
| raw_decrypted_data.data(), key, RSA_NO_PADDING); |
| if (decryption_result == -1) { |
| LOG(ERROR) << "RSA raw decryption failed: " |
| << ERR_error_string(ERR_get_error(), nullptr); |
| return false; |
| } |
| if (decryption_result != key_size) { |
| LOG(ERROR) << "RSA raw decryption returned too few data"; |
| return false; |
| } |
| SecureBlob local_plaintext(key_size); |
| const int padding_check_result = RSA_padding_check_PKCS1_OAEP( |
| local_plaintext.data(), local_plaintext.size(), raw_decrypted_data.data(), |
| raw_decrypted_data.size(), key_size, oaep_label.data(), |
| oaep_label.size()); |
| if (padding_check_result == -1) { |
| LOG(ERROR) |
| << "Failed to perform RSA OAEP decoding of the raw decrypted data"; |
| return false; |
| } |
| local_plaintext.resize(padding_check_result); |
| *plaintext = std::move(local_plaintext); |
| return true; |
| } |
| |
| std::string CryptoLib::BlobToHex(const brillo::Blob& blob) { |
| std::string buffer(blob.size() * 2, '\x00'); |
| BlobToHexToBuffer(blob, &buffer[0], buffer.size()); |
| return buffer; |
| } |
| |
| std::string CryptoLib::SecureBlobToHex(const brillo::SecureBlob& blob) { |
| std::string buffer(blob.size() * 2, '\x00'); |
| SecureBlobToHexToBuffer(blob, &buffer[0], buffer.size()); |
| return buffer; |
| } |
| |
| void CryptoLib::BlobToHexToBuffer(const brillo::Blob& blob, |
| void* buffer, |
| size_t buffer_length) { |
| BlobToHexToBufferHelper(blob, buffer, buffer_length); |
| } |
| |
| void CryptoLib::SecureBlobToHexToBuffer(const brillo::SecureBlob& blob, |
| void* buffer, |
| size_t buffer_length) { |
| BlobToHexToBufferHelper(blob, buffer, buffer_length); |
| } |
| |
| std::string CryptoLib::ComputeEncryptedDataHMAC( |
| const EncryptedData& encrypted_data, const SecureBlob& hmac_key) { |
| SecureBlob blob1(encrypted_data.iv().begin(), encrypted_data.iv().end()); |
| SecureBlob blob2(encrypted_data.encrypted_data().begin(), |
| encrypted_data.encrypted_data().end()); |
| SecureBlob result = SecureBlob::Combine(blob1, blob2); |
| SecureBlob hmac = HmacSha512(hmac_key, result); |
| return hmac.to_string(); |
| } |
| |
| bool CryptoLib::TpmCompatibleOAEPEncrypt(RSA* key, |
| const brillo::SecureBlob& input, |
| brillo::SecureBlob* output) { |
| CHECK(output); |
| |
| // The custom OAEP parameter as specified in TPM Main Part 1, Section 31.1.1. |
| const unsigned char oaep_param[4] = {'T', 'C', 'P', 'A'}; |
| brillo::SecureBlob padded_input(RSA_size(key)); |
| unsigned char* padded_buffer = padded_input.data(); |
| const unsigned char* input_buffer = input.data(); |
| int result = RSA_padding_add_PKCS1_OAEP(padded_buffer, padded_input.size(), |
| input_buffer, input.size(), |
| oaep_param, base::size(oaep_param)); |
| if (!result) { |
| LOG(ERROR) << "Failed to add OAEP padding."; |
| return false; |
| } |
| |
| output->resize(padded_input.size()); |
| unsigned char* output_buffer = output->data(); |
| result = RSA_public_encrypt(padded_input.size(), padded_buffer, output_buffer, |
| key, RSA_NO_PADDING); |
| if (result == -1) { |
| LOG(ERROR) << "Failed to encrypt OAEP padded input."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Checks an RSA key modulus for the ROCA fingerprint (i.e. whether the RSA |
| // modulus has a discrete logarithm modulus small primes). See research paper |
| // for details: https://crocs.fi.muni.cz/public/papers/rsa_ccs17 |
| bool CryptoLib::TestRocaVulnerable(const BIGNUM* rsa_modulus) { |
| const BN_ULONG kPrimes[] = { |
| 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, |
| 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, |
| 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, |
| }; |
| |
| for (BN_ULONG prime : kPrimes) { |
| BN_ULONG remainder = BN_mod_word(rsa_modulus, prime); |
| |
| // Enumerate all elements F4 generates in the small |prime| subgroup and |
| // check whether |remainder| is among them. |
| BN_ULONG power = 1; |
| do { |
| power = (power * 65537) % prime; |
| } while (power != 1 && power != remainder); |
| |
| // No discrete logarithm -> modulus isn't of the ROCA form and thus not |
| // vulnerable. |
| if (power != remainder) { |
| return false; |
| } |
| } |
| |
| // Discrete logarithms exist for all small primes -> vulnerable with |
| // negligible chance of false positive result. |
| return true; |
| } |
| |
| // static |
| bool CryptoLib::DeriveSecretsScrypt( |
| const brillo::SecureBlob& passkey, |
| const brillo::SecureBlob& salt, |
| std::vector<brillo::SecureBlob*> gen_secrets) { |
| size_t total_len = 0; |
| for (auto& secret : gen_secrets) { |
| total_len += secret->size(); |
| } |
| |
| SecureBlob generated(total_len); |
| if (!Scrypt(passkey, salt, kDefaultScryptParams.n_factor, |
| kDefaultScryptParams.r_factor, kDefaultScryptParams.p_factor, |
| &generated)) { |
| LOG(ERROR) << "Failed to derive scrypt keys from passkey."; |
| return false; |
| } |
| |
| uint8_t* data = generated.data(); |
| for (auto& value : gen_secrets) { |
| value->assign(data, data + value->size()); |
| data += value->size(); |
| } |
| |
| return true; |
| } |
| |
| // static |
| bool CryptoLib::Scrypt(const brillo::SecureBlob& input, |
| const brillo::SecureBlob& salt, |
| int work_factor, |
| int block_size, |
| int parallel_factor, |
| brillo::SecureBlob* result) { |
| crypto::ScopedEVP_PKEY_CTX pctx(EVP_PKEY_CTX_new_id(EVP_PKEY_SCRYPT, NULL)); |
| if (EVP_PKEY_derive_init(pctx.get()) <= 0) |
| return false; |
| if (EVP_PKEY_CTX_set1_pbe_pass(pctx.get(), input.data(), input.size()) <= 0) |
| return false; |
| if (EVP_PKEY_CTX_set1_scrypt_salt(pctx.get(), salt.data(), salt.size()) <= 0) |
| return false; |
| if (EVP_PKEY_CTX_set_scrypt_N(pctx.get(), work_factor) <= 0) |
| return false; |
| if (EVP_PKEY_CTX_set_scrypt_r(pctx.get(), block_size) <= 0) |
| return false; |
| if (EVP_PKEY_CTX_set_scrypt_p(pctx.get(), parallel_factor) <= 0) |
| return false; |
| |
| size_t outlen = result->size(); |
| int rc = EVP_PKEY_derive(pctx.get(), result->data(), &outlen); |
| |
| return rc > 0 && outlen == result->size(); |
| } |
| |
| // static |
| bool CryptoLib::DeprecatedEncryptScryptBlob( |
| const brillo::SecureBlob& blob, |
| const brillo::SecureBlob& key_source, |
| brillo::SecureBlob* wrapped_blob) { |
| wrapped_blob->resize(blob.size() + kScryptMetadataSize); |
| |
| brillo::SecureBlob salt = |
| CryptoLib::CreateSecureRandomBlob(kLibScryptSaltSize); |
| brillo::SecureBlob derived_key(kLibScryptDerivedKeySize, '0'); |
| if (!Scrypt(key_source, salt, gScryptParams.n_factor, gScryptParams.r_factor, |
| gScryptParams.p_factor, &derived_key) != 0) { |
| LOG(ERROR) << "Failed to derive key with scrypt."; |
| return false; |
| } |
| |
| if (!LibScryptCompat::Encrypt(derived_key, salt, blob, gScryptParams, |
| wrapped_blob)) { |
| LOG(ERROR) << "Failed to generate encrypted data."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // static |
| bool CryptoLib::DeprecatedDecryptScryptBlob( |
| const brillo::SecureBlob& wrapped_blob, |
| const brillo::SecureBlob& key, |
| brillo::SecureBlob* blob, |
| CryptoError* error) { |
| DCHECK(blob->size() >= wrapped_blob.size()); |
| |
| ScryptParameters params; |
| brillo::SecureBlob salt; |
| if (!LibScryptCompat::ParseHeader(wrapped_blob, ¶ms, &salt)) { |
| LOG(ERROR) << "Failed to parse header."; |
| PopulateError(error, CryptoError::CE_SCRYPT_CRYPTO); |
| return false; |
| } |
| |
| // Generate the derived key. |
| brillo::SecureBlob derived_key(kLibScryptDerivedKeySize, 0); |
| if (!Scrypt(key, salt, params.n_factor, params.r_factor, params.p_factor, |
| &derived_key)) { |
| LOG(ERROR) << "scrypt failed"; |
| return false; |
| } |
| |
| if (!LibScryptCompat::Decrypt(wrapped_blob, derived_key, blob)) { |
| LOG(ERROR) << "Failed to decrypt output."; |
| PopulateError(error, CryptoError::CE_SCRYPT_CRYPTO); |
| return false; |
| } |
| |
| // Check if the plaintext is the right length. |
| if ((wrapped_blob.size() < kScryptMetadataSize) || |
| (blob->size() != (wrapped_blob.size() - kScryptMetadataSize))) { |
| LOG(ERROR) << "Blob Scrypt decryption output was the wrong length"; |
| PopulateError(error, CryptoError::CE_SCRYPT_CRYPTO); |
| return false; |
| } |
| return true; |
| } |
| |
| // static |
| void CryptoLib::AssertProductionScryptParams() { |
| // Always perform the check just in case. |
| CHECK_EQ(kDefaultScryptParams.n_factor, gScryptParams.n_factor); |
| CHECK_EQ(kDefaultScryptParams.r_factor, gScryptParams.r_factor); |
| CHECK_EQ(kDefaultScryptParams.p_factor, gScryptParams.p_factor); |
| } |
| |
| // static |
| void CryptoLib::SetScryptTestingParams() { |
| CryptoLib::gScryptParams = kTestScryptParams; |
| } |
| |
| } // namespace cryptohome |