Skip to content
Snippets Groups Projects
rsa.cc 4.59 KiB
#include <vereign/crypto/rsa.hh>

#include <vereign/bytes/view.hh>
#include <vereign/crypto/errors.hh>
#include <vereign/bytes/buffer.hh>
#include <vereign/crypto/bio.hh>

#include <openssl/base.h>
#include <openssl/bn.h>
#include <openssl/rsa.h>
#include <openssl/mem.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/pem.h>

namespace vereign::crypto::rsa {

auto GenerateKey(int bits) -> bssl::UniquePtr<EVP_PKEY> {
  bssl::UniquePtr<BIGNUM> bn{BN_new()};
  if (!bn) {
    throw OpenSSLError("rsa key generation failed");
  }

  auto r = BN_set_word(bn.get(), RSA_F4);
  if (r != 1) {
    throw OpenSSLError("rsa key generation failed");
  }

  bssl::UniquePtr<RSA> rsa{RSA_new()};
  r = RSA_generate_key_ex(rsa.get(), bits, bn.get(), nullptr);
  if (r != 1) {
    throw OpenSSLError("rsa key generation failed");
  }

  bssl::UniquePtr<EVP_PKEY> key(EVP_PKEY_new());
  if (key == nullptr) {
    throw OpenSSLError("creating key failed");
  }

  r = EVP_PKEY_assign_RSA(key.get(), rsa.release());
  if (r != 1) {
    throw OpenSSLError("rsa key assign to evp failed");
  }

  return key;
}

void PublicKeyEncrypt(EVP_PKEY* key, bytes::View src, bytes::Buffer& encrypted) {
  bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new(key, nullptr));
  if (!ctx) {
    throw OpenSSLError("creating evp ctx failed");
  }

  auto r = EVP_PKEY_encrypt_init(ctx.get());
  if (r != 1) {
    throw OpenSSLError("encrypt init failed");
  }

  r = EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING);
  if (r != 1) {
    throw OpenSSLError("pkey padding init failed");
  }

  std::size_t outlen = 0;
  r = EVP_PKEY_encrypt(ctx.get(), nullptr, &outlen, src.Data(), src.Size());
  if (r != 1) {
    throw OpenSSLError("determining ciphertext size failed");
  }

  encrypted.Reserve(outlen);

  r = EVP_PKEY_encrypt(ctx.get(), encrypted.end(), &outlen, src.Data(), src.Size());
  if (r != 1) {
    throw OpenSSLError("encrypting failed");
  }

  encrypted.IncSize(outlen);
}

void PrivateKeyDecrypt(EVP_PKEY* key, bytes::View src, bytes::Buffer& decrypted) {
  bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new(key, nullptr));
  if (!ctx) {
    throw OpenSSLError("creating evp ctx failed");
  }

  auto r = EVP_PKEY_decrypt_init(ctx.get());
  if (r != 1) {
    throw OpenSSLError("decrypt init failed");
  }

  r = EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING);
  if (r != 1) {
    throw OpenSSLError("init pkey padding failed");
  }

  std::size_t outlen = 0;
  r = EVP_PKEY_decrypt(ctx.get(), nullptr, &outlen, src.Data(), src.Size());
  if (r != 1) {
    throw OpenSSLError("determining decrypted buffer size failed");
  }

  decrypted.Reserve(outlen);

  r = EVP_PKEY_decrypt(ctx.get(), decrypted.end(), &outlen, src.Data(), src.Size());
  if (r != 1) {
    throw OpenSSLError("decrypting failed");
  }

  decrypted.IncSize(outlen);
}

auto ExportPublicKeyToPEM(EVP_PKEY* key) -> bssl::UniquePtr<BIO> {
  bssl::UniquePtr<BIO> mem(BIO_new(BIO_s_mem()));
  if (!mem) {
    throw OpenSSLError("creating memory buffer failed");
  }

  auto r = PEM_write_bio_PUBKEY(mem.get(), key);
  if (r != 1) {
    throw OpenSSLError("exporting public key to PEM failed");
  }

  return mem;
}

auto ImportPublicKeyFromPEM(bytes::View pem) -> bssl::UniquePtr<EVP_PKEY> {
  bssl::UniquePtr<BIO> mem(BIO_new_mem_buf(pem.Data(), pem.Size()));
  if (mem == nullptr) {
    throw OpenSSLError("creating memory buffer failed");
  }

  bssl::UniquePtr<EVP_PKEY> key(PEM_read_bio_PUBKEY(mem.get(), nullptr, nullptr, nullptr));
  if (key == nullptr) {
    throw OpenSSLError("importing public key from PEM failed");
  }

  return key;
}

auto ExportPrivateKeyToPEM(EVP_PKEY* key) -> bssl::UniquePtr<BIO> {
  bssl::UniquePtr<BIO> mem(BIO_new(BIO_s_mem()));
  if (!mem) {
    throw OpenSSLError("creating memory buffer failed");
  }

  auto r = PEM_write_bio_PrivateKey(mem.get(), key, nullptr, nullptr, 0, nullptr, nullptr);
  if (r != 1) {
    throw OpenSSLError("exporting private key to PEM failed");
  }

  return mem;
}

auto ExportPrivateKeyToPEMString(EVP_PKEY* key) -> std::string {
  auto key_bio = ExportPublicKeyToPEM(key);

  return std::string{crypto::bio::View(key_bio.get()).String()};
}

auto ImportPrivateKeyFromPEM(bytes::View pem) -> bssl::UniquePtr<EVP_PKEY> {
  bssl::UniquePtr<BIO> mem(BIO_new_mem_buf(pem.Data(), pem.Size()));
  if (mem == nullptr) {
    throw OpenSSLError("creating memory buffer failed");
  }

  auto key_ptr = PEM_read_bio_PrivateKey(mem.get(), nullptr, nullptr, nullptr);
  if (key_ptr == nullptr) {
    throw OpenSSLError("importing private key from PEM failed");
  }

  return bssl::UniquePtr<EVP_PKEY>(key_ptr);
}

} // vereign::crypto::rsa