GitHub Viewer
/*
* Crypto.cpp
*
* Copyright (C) 2009-19 by RStudio, PBC
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/
#include
#include
#ifdef _MSC_VER
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// openssl calls on lion are are all marked as deprecated
#ifdef __clang__
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif
// openssl encrypt/decrypt constants
#define kEncrypt 1
#define kDecrypt 0
using namespace rstudio::core;
namespace rstudio {
namespace core {
namespace system {
namespace crypto {
namespace {
// NOTE: we've never see the error codepath in spite of trying to get it to
// return an error by tweaking params -- NULL params caused a crash rather
// than returning an error). this is presumably because we are calling
// such low level functions. we may want to look at the src code to see
// if there is a way to test/engineer an error (or remove the error
// checking if there is no way to get one)
Error lastCryptoError(const ErrorLocation& location)
{
// get the error code
unsigned long ec = ::ERR_get_error();
if (ec == 0)
{
LOG_WARNING_MESSAGE("lastCrytpoError called with no pending error");
return systemError(boost::system::errc::not_supported,
"lastCrytpoError called with no pending error",
location);
}
// get the error message (docs say max len is 120)
const int ERR_BUFF_SIZE = 250;
char errorBuffer[ERR_BUFF_SIZE];
::ERR_error_string_n(ec, errorBuffer, ERR_BUFF_SIZE);
// return the error
return systemError(boost::system::errc::bad_message,
errorBuffer,
location);
}
class BIOFreeAllScope : boost::noncopyable
{
public:
BIOFreeAllScope(BIO* pMem)
: pMem_(pMem)
{
}
~BIOFreeAllScope()
{
try
{
::BIO_free_all(pMem_);
}
catch(...)
{
}
}
private:
BIO* pMem_;
};
} // anonymous namespace
void initialize()
{
#if OPENSSL_VERSION_NUMBER < 0x10100000L
// load global error string table
::ERR_load_crypto_strings();
#endif
}
Error HMAC_SHA2(const std::string& data,
const std::string& key,
std::vector* pHMAC)
{
// copy data into vector
std::vector keyVector;
std::copy(key.begin(), key.end(), std::back_inserter(keyVector));
// call core
return HMAC_SHA2(data, keyVector, pHMAC);
}
Error HMAC_SHA2(const std::string& data,
const std::vector& key,
std::vector* pHMAC)
{
// copy data into data vector
std::vector dataVector;
std::copy(data.begin(), data.end(), std::back_inserter(dataVector));
// perform the hash
unsigned int md_len = 0;
pHMAC->resize(EVP_MAX_MD_SIZE);
unsigned char* pResult = ::HMAC(EVP_sha256(),
&(key[0]),
gsl::narrow_cast(key.size()),
&(dataVector[0]),
dataVector.size(),
&(pHMAC->operator[](0)),
&md_len);
if (pResult != nullptr)
{
pHMAC->resize(md_len);
return Success();
}
else
{
return lastCryptoError(ERROR_LOCATION);
}
}
Error sha256(const std::string& message,
std::string* pHash)
{
SHA256_CTX shaCtx;
int ret = SHA256_Init(&shaCtx);
if (ret != 1)
return lastCryptoError(ERROR_LOCATION);
ret = SHA256_Update(&shaCtx, message.c_str(), message.size());
if (ret != 1)
return lastCryptoError(ERROR_LOCATION);
unsigned char hash[SHA256_DIGEST_LENGTH];
ret = SHA256_Final(hash, &shaCtx);
if (ret != 1)
return lastCryptoError(ERROR_LOCATION);
*pHash = std::string((const char*)hash, SHA256_DIGEST_LENGTH);
return Success();
}
Error base64Encode(const std::vector& data,
std::string* pEncoded)
{
return base64Encode(&(data[0]), gsl::narrow_cast(data.size()), pEncoded);
}
Error base64Encode(const unsigned char* pData,
int len,
std::string* pEncoded)
{
// allocate BIO
BIO* pB64 = ::BIO_new(BIO_f_base64());
if (pB64 == nullptr)
return lastCryptoError(ERROR_LOCATION);
// no newlines
BIO_set_flags(pB64, BIO_FLAGS_BASE64_NO_NL);
// make sure it is freed prior to exit from the function
BIOFreeAllScope freeB64Scope(pB64);
// allocate memory stream
BIO* pMem = ::BIO_new(BIO_s_mem());
if (pMem == nullptr)
return lastCryptoError(ERROR_LOCATION);
// tie the stream to the b64 stream
pB64 = ::BIO_push(pB64, pMem);
// perform the encoding
int written = ::BIO_write(pB64, pData, len);
if (written != len)
return lastCryptoError(ERROR_LOCATION);
// flush all writes
int result = BIO_flush(pB64);
if (result assign(buffer.begin(), buffer.end());
// return success
return Success();
}
Error base64Decode(const std::string& data,
std::vector* pDecoded)
{
// allocate b64 BIO
BIO* pB64 = ::BIO_new(BIO_f_base64());
if (pB64 == nullptr)
return lastCryptoError(ERROR_LOCATION);
// no newlines
BIO_set_flags(pB64, BIO_FLAGS_BASE64_NO_NL);
// make sure it is freed prior to exit from the function
BIOFreeAllScope freeB64Scope(pB64);
// allocate buffer
BIO* pMem = BIO_new_mem_buf((void*)data.data(), gsl::narrow_cast(data.length()));
if (pMem == nullptr)
return lastCryptoError(ERROR_LOCATION);
// tie the stream to the b64 stream
pB64 = ::BIO_push(pB64, pMem);
// reserve adequate memory in the decoded buffer and read into it
pDecoded->clear();
pDecoded->resize(data.length());
int bytesRead = ::BIO_read(pB64,
&(pDecoded->operator[](0)),
gsl::narrow_cast(pDecoded->size()));
if (bytesRead < 0)
return lastCryptoError(ERROR_LOCATION);
// resize the out buffer to the number of bytes actually read
pDecoded->resize(bytesRead);
// return success
return Success();
}
Error aesEncrypt(const std::vector& data,
const std::vector& key,
const std::vector& iv,
std::vector* pEncrypted)
{
// allow enough space in output buffer for additional block
pEncrypted->resize(data.size() + EVP_MAX_BLOCK_LENGTH);
int outlen = 0;
int bytesEncrypted = 0;
EVP_CIPHER_CTX *ctx;
ctx = EVP_CIPHER_CTX_new();
EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), nullptr, &key[0], &iv[0], kEncrypt);
// perform the encryption
if(!EVP_CipherUpdate(ctx, &(pEncrypted->operator[](0)), &outlen, &data[0], gsl::narrow_cast(data.size())))
{
EVP_CIPHER_CTX_free(ctx);
return lastCryptoError(ERROR_LOCATION);
}
bytesEncrypted += outlen;
// perform final flush including left-over padding
if(!EVP_CipherFinal_ex(ctx, &(pEncrypted->operator[](outlen)), &outlen))
{
EVP_CIPHER_CTX_free(ctx);
return lastCryptoError(ERROR_LOCATION);
}
bytesEncrypted += outlen;
EVP_CIPHER_CTX_free(ctx);
// resize the container to the amount of actual bytes encrypted (including padding)
pEncrypted->resize(bytesEncrypted);
return Success();
}
Error aesDecrypt(const std::vector& data,
const std::vector& key,
const std::vector& iv,
std::vector* pDecrypted)
{
pDecrypted->resize(data.size());
int outlen = 0;
int bytesDecrypted = 0;
EVP_CIPHER_CTX *ctx;
ctx = EVP_CIPHER_CTX_new();
EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), nullptr, &key[0], &iv[0], kDecrypt);
// perform the decryption
if(!EVP_CipherUpdate(ctx, &(pDecrypted->operator[](0)), &outlen, &data[0], gsl::narrow_cast(data.size())))
{
EVP_CIPHER_CTX_free(ctx);
return lastCryptoError(ERROR_LOCATION);
}
bytesDecrypted += outlen;
// perform final flush
if(!EVP_CipherFinal_ex(ctx, &(pDecrypted->operator[](outlen)), &outlen))
{
EVP_CIPHER_CTX_free(ctx);
return lastCryptoError(ERROR_LOCATION);
}
bytesDecrypted += outlen;
EVP_CIPHER_CTX_free(ctx);
// resize the container to the amount of actual bytes decrypted (padding is removed)
pDecrypted->resize(bytesDecrypted);
return Success();
}
Error random(uint32_t numBytes,
std::vector* pOut)
{
pOut->resize(numBytes);
if (!RAND_bytes(&(pOut->operator[](0)), numBytes))
{
return lastCryptoError(ERROR_LOCATION);
}
return Success();
}
Error rsaSign(const std::string& message,
const std::string& pemPrivateKey,
std::string* pOutSignature)
{
// create a sha256 hash of the message first which is what we will sign
// this prevents attackers from being able to back into creating a valid message
std::string hash;
Error error = sha256(message, &hash);
if (error)
return error;
// convert the key into an RSA structure
std::unique_ptr pKeyBuff(BIO_new_mem_buf(const_cast(pemPrivateKey.c_str()),
gsl::narrow_cast(pemPrivateKey.size())),
BIO_free);
if (!pKeyBuff)
return systemError(boost::system::errc::not_enough_memory, ERROR_LOCATION);
std::unique_ptr pRsa(PEM_read_bio_RSAPrivateKey(pKeyBuff.get(), nullptr, nullptr, nullptr),
RSA_free);
if (!pRsa)
return systemError(boost::system::errc::not_enough_memory, ERROR_LOCATION);
// sign the message hash
std::unique_ptr pSignature((unsigned char*)malloc(RSA_size(pRsa.get())),
free);
if (!pSignature)
return systemError(boost::system::errc::not_enough_memory, ERROR_LOCATION);
unsigned int sigLen = 0;
int ret = RSA_sign(NID_sha256, (const unsigned char*)hash.c_str(),
static_cast(hash.size()), pSignature.get(), &sigLen, pRsa.get());
if (ret != 1)
return lastCryptoError(ERROR_LOCATION);
// store signature in output param
*pOutSignature = std::string((const char*)pSignature.get(), sigLen);
return Success();
}
Error rsaVerify(const std::string& message,
const std::string& signature,
const std::string& pemPublicKey)
{
// create a sha256 hash of the message first which is what we will verify
// this prevents attackers from being able to back into creating a valid message
std::string hash;
Error error = sha256(message, &hash);
if (error)
return error;
// convert the key into an RSA structure
std::unique_ptr pKeyBuff(
BIO_new_mem_buf(const_cast(pemPublicKey.c_str()),
gsl::narrow_cast(pemPublicKey.size())),
BIO_free);
if (!pKeyBuff)
return systemError(boost::system::errc::not_enough_memory, ERROR_LOCATION);
std::unique_ptr pRsa(PEM_read_bio_RSA_PUBKEY(pKeyBuff.get(), nullptr, nullptr, nullptr),
RSA_free);
if (!pRsa)
return systemError(boost::system::errc::not_enough_memory, ERROR_LOCATION);
// verify the message hash
int ret = RSA_verify(NID_sha256, (const unsigned char*)hash.c_str(), static_cast(hash.size()),
(const unsigned char*)signature.c_str(), static_cast(signature.size()), pRsa.get());
if (ret != 1)
return lastCryptoError(ERROR_LOCATION);
return Success();
}
namespace {
Error generateRsa(const std::unique_ptr& pBioPub,
const std::unique_ptr& pBioPem)
{
std::unique_ptr pRsa(RSA_new(), RSA_free);
if (!pRsa)
return systemError(boost::system::errc::not_enough_memory, ERROR_LOCATION);
std::unique_ptr pBigNum(BN_new(), BN_free);
if (!pBigNum)
return systemError(boost::system::errc::not_enough_memory, ERROR_LOCATION);
int ret = BN_set_word(pBigNum.get(), RSA_F4);
if (ret != 1)
return lastCryptoError(ERROR_LOCATION);
ret = RSA_generate_key_ex(pRsa.get(), 2048, pBigNum.get(), nullptr);
if (ret != 1)
return lastCryptoError(ERROR_LOCATION);
// Convert RSA to PKEY
std::unique_ptr pKey(EVP_PKEY_new(), EVP_PKEY_free);
if (!pKey)
return systemError(boost::system::errc::not_enough_memory, ERROR_LOCATION);
ret = EVP_PKEY_set1_RSA(pKey.get(), pRsa.get());
if (ret != 1)
return lastCryptoError(ERROR_LOCATION);
// Write public key in PEM format
ret = PEM_write_bio_PUBKEY(pBioPub.get(), pKey.get());
if (ret != 1)
return lastCryptoError(ERROR_LOCATION);
// Write private key in PEM format
ret = PEM_write_bio_PrivateKey(pBioPem.get(), pKey.get(), nullptr, nullptr, 0, nullptr, nullptr);
if (ret != 1)
return lastCryptoError(ERROR_LOCATION);
return Success();
}
} // anonymous namespace
Error generateRsaKeyFiles(const FilePath& publicKeyPath,
const FilePath& privateKeyPath)
{
std::unique_ptr pBioPub(BIO_new_file(publicKeyPath.getAbsolutePath().c_str(), "w"),
BIO_free);
if (!pBioPub)
return systemError(boost::system::errc::not_enough_memory, ERROR_LOCATION);
std::unique_ptr pBioPem(BIO_new_file(privateKeyPath.getAbsolutePath().c_str(), "w"),
BIO_free);
if (!pBioPem)
return systemError(boost::system::errc::not_enough_memory, ERROR_LOCATION);
return generateRsa(pBioPub, pBioPem);
}
Error generateRsaKeyPair(std::string* pOutPublicKey,
std::string* pOutPrivateKey)
{
std::unique_ptr pBioPub(BIO_new(BIO_s_mem()),
BIO_free);
if (!pBioPub)
return systemError(boost::system::errc::not_enough_memory, ERROR_LOCATION);
pOutPrivateKey->reserve(4096);
std::unique_ptr pBioPem(BIO_new(BIO_s_mem()),
BIO_free);
if (!pBioPem)
return systemError(boost::system::errc::not_enough_memory, ERROR_LOCATION);
Error error = generateRsa(pBioPub, pBioPem);
if (error)
return error;
// extract the underlying character buffers from the memory BIOs
// note - these will be freed automatically when the BIOs are freed
BUF_MEM* pubPtr;
BUF_MEM* pemPtr;
BIO_get_mem_ptr(pBioPub.get(), &pubPtr);
BIO_get_mem_ptr(pBioPem.get(), &pemPtr);
*pOutPublicKey = std::string(pubPtr->data, pubPtr->length);
*pOutPrivateKey = std::string(pemPtr->data, pemPtr->length);
return Success();
}
namespace {
RSA* s_pRSA;
std::string s_modulo;
std::string s_exponent;
}
core::Error rsaInit()
{
const int KEY_SIZE = 2048;
const int ENTROPY_BYTES = 4096;
const BIGNUM *bn_n;
const BIGNUM *bn_e;
int rnd = ::open("/dev/urandom", O_RDONLY);
if (rnd == -1)
return systemError(errno, ERROR_LOCATION);
char entropy[ENTROPY_BYTES];
if (-1 == ::read(rnd, entropy, ENTROPY_BYTES))
{
::close(rnd);
return systemError(errno, ERROR_LOCATION);
}
::close(rnd);
RAND_seed(entropy, ENTROPY_BYTES);
#if OPENSSL_VERSION_NUMBER < 0x10100000L
s_pRSA = ::RSA_generate_key(KEY_SIZE, 0x10001, nullptr, nullptr);
if (!s_pRSA)
return lastCryptoError(ERROR_LOCATION);
bn_n = s_pRSA->n;
bn_e = s_pRSA->e;
#else
BIGNUM *bn = BN_new();
BN_set_word(bn, RSA_F4);
s_pRSA = RSA_new();
int rc = ::RSA_generate_key_ex(s_pRSA, KEY_SIZE, bn, nullptr);
BN_clear_free(bn);
if (rc != 1) {
RSA_free(s_pRSA);
return lastCryptoError(ERROR_LOCATION);
}
RSA_get0_key(s_pRSA, &bn_n, &bn_e, nullptr);
#endif
char* n = BN_bn2hex(bn_n);
s_modulo = n;
OPENSSL_free(n);
char* e = BN_bn2hex(bn_e);
s_exponent = e;
OPENSSL_free(e);
return Success();
}
void rsaPublicKey(std::string* pExponent, std::string* pModulo)
{
pModulo->assign(s_modulo.begin(), s_modulo.end());
pExponent->assign(s_exponent.begin(), s_exponent.end());
}
core::Error rsaPrivateDecrypt(const std::string& cipherText, std::string* pPlainText)
{
std::vector cipherTextBytes;
Error error = base64Decode(cipherText, &cipherTextBytes);
if (error)
return error;
int size = RSA_size(s_pRSA);
std::vector plainTextBytes(size);
int bytesRead = RSA_private_decrypt(gsl::narrow_cast(cipherTextBytes.size()),
&cipherTextBytes[0],
&plainTextBytes[0],
s_pRSA,
RSA_PKCS1_PADDING);
if (bytesRead == -1)
return lastCryptoError(ERROR_LOCATION);
plainTextBytes.resize(bytesRead);
pPlainText->assign(plainTextBytes.begin(), plainTextBytes.end());
return Success();
}
Error encryptDataAsBase64EncodedString(const std::string& input,
const std::string& keyStr,
std::string* pIv,
std::string* pEncrypted)
{
// copy data into vector
std::vector data;
std::copy(input.begin(), input.end(), std::back_inserter(data));
// copy key into vector
std::vector key;
std::copy(keyStr.begin(), keyStr.end(), std::back_inserter(key));
// create a random initialization vector for a little added security
std::vector iv;
Error error = core::system::crypto::random(256, &iv);
if (error)
return error;
// encrypt the input
std::vector encrypted;
error = core::system::crypto::aesEncrypt(data, key, iv, &encrypted);
if (error)
return error;
// base 64 encode the IV used for encryption
error = core::system::crypto::base64Encode(iv, pIv);
if (error)
return error;
// base 64 encode encrypted result
return core::system::crypto::base64Encode(encrypted, pEncrypted);
}
Error decryptBase64EncodedString(const std::string& input,
const std::string& keyStr,
const std::string& ivStr,
std::string* pDecrypted)
{
// copy key into vector
std::vector key;
std::copy(keyStr.begin(), keyStr.end(), std::back_inserter(key));
// decode initialization vector
std::vector iv;
Error error = core::system::crypto::base64Decode(ivStr, &iv);
if (error)
return error;
// decode encrypted input
std::vector decoded;
error = core::system::crypto::base64Decode(input, &decoded);
if (error)
return error;
// decrypt decoded input
std::vector decrypted;
error = core::system::crypto::aesDecrypt(decoded, key, iv, &decrypted);
if (error)
return error;
// covert the decrypted bytes into the original string
pDecrypted->reserve(decrypted.size());
std::copy(decrypted.begin(), decrypted.end(), std::back_inserter(*pDecrypted));
return Success();
}
} // namespace crypto
} // namespace system
} // namespace core
} // namespace rstudio