Skip to content

Commit

Permalink
crypto: Added support for asymmetric keys
Browse files Browse the repository at this point in the history
Support loading PEM encoded RSA public and private keys and
RSA public keys via modulus and public exponent.

Signed-off-by: Michael Boquard <[email protected]>
  • Loading branch information
michael-redpanda committed Mar 5, 2024
1 parent d1eceeb commit 774bb60
Show file tree
Hide file tree
Showing 10 changed files with 482 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/v/crypto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ v_cc_library(
crypto.cc
digest.cc
hmac.cc
key.cc
ssl_utils.cc
DEPS
absl::node_hash_set
Expand Down
34 changes: 34 additions & 0 deletions src/v/crypto/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,37 @@ crypto::hmac_ctx ctx(crypto::digest_type::SHA256, key);
ctx.update(msg);
auto sig = std::move(ctx).final();
```

## Asymmetric Key Handling

Currently, this library only supports RSA keys. There are two ways of loading a
key from a buffer.

First, if the key is held within a buffer in PKCS8 format, you can use
`crypto::load_key`:

```c++
#include "crypto/crypto.h"

bytes rsa_priv_key {...};

auto key = crypto::key::load_key(
rsa_priv_key,
crypto::format_type::PEM,
crypto::is_private_key_t::yes);
```
The above function can determine the type of key held in the buffer but the
caller is responsible for indicating the format the key is in (PEM or DER) and
whether or not it's the public half of the key or the private key.
The other way of loading a key is by its parts. Currently only RSA public key
loading is available:
```c++
#include "crypto/crypto.h"
bytes modulus {...};
bytes public_exponent {...};
auto key = crypto::key::load_rsa_public_key(modulus, public_exponent);
```
16 changes: 16 additions & 0 deletions src/v/crypto/crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,20 @@ std::ostream& operator<<(std::ostream& os, digest_type type) {

return os;
}

std::ostream& operator<<(std::ostream& os, key_type type) {
switch (type) {
case key_type::RSA:
return os << "RSA";
}
}

std::ostream& operator<<(std::ostream& os, format_type type) {
switch (type) {
case format_type::PEM:
return os << "PEM";
case format_type::DER:
return os << "DER";
}
}
} // namespace crypto
48 changes: 48 additions & 0 deletions src/v/crypto/include/crypto/crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,52 @@ struct hmac_ctx final {
*/
bytes hmac(digest_type type, bytes_view key, bytes_view msg);
bytes hmac(digest_type type, std::string_view key, std::string_view msg);

///////////////////////////////////////////////////////////////////////////////
/// Asymmetric key operations
///////////////////////////////////////////////////////////////////////////////
/**
* Structure that holds the key implementation
*/
struct key {
public:
/**
* Attempts to load a key from a buffer
*
* @param key The key buffer
* @param fmt The format of the buffer
* @param is_private_key Whether or not the key is a private key
* @return key The loaded key
* @throws crypto::exception If unable to load the key contained in @p key
* or if @p key contains an unsupported key
*/
static key
load_key(bytes_view key, format_type fmt, is_private_key_t is_private_key);
static key load_key(
std::string_view key, format_type fmt, is_private_key_t is_private_key);
/**
* Loads an RSA public key from its parts
*
* @param n The modulus
* @param e The public exponent
* @return key The loaded key
* @throws crypto::exception If there is an error loading the key
*/
static key load_rsa_public_key(bytes_view n, bytes_view e);
static key load_rsa_public_key(std::string_view n, std::string_view e);
~key() noexcept;
key(const key&) = delete;
key& operator=(const key&) = delete;
key(key&&) noexcept;
key& operator=(key&&) noexcept;

key_type get_type() const;
is_private_key_t is_private_key() const;

private:
class impl;
std::unique_ptr<impl> _impl;

explicit key(std::unique_ptr<impl>&& impl);
};
} // namespace crypto
12 changes: 12 additions & 0 deletions src/v/crypto/include/crypto/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,21 @@

#pragma once

#include "base/seastarx.h"

#include <seastar/util/bool_class.hh>

#include <iosfwd>

namespace crypto {
enum class digest_type { MD5, SHA256, SHA512 };
std::ostream& operator<<(std::ostream&, digest_type);

enum class key_type { RSA };
std::ostream& operator<<(std::ostream&, key_type);

enum class format_type { PEM, DER };
std::ostream& operator<<(std::ostream&, format_type);

using is_private_key_t = ss::bool_class<struct is_private_key_tag>;
} // namespace crypto
200 changes: 200 additions & 0 deletions src/v/crypto/key.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
* Copyright 2024 Redpanda Data, Inc.
*
* Use of this software is governed by the Business Source License
* included in the file licenses/BSL.md
*
* As of the Change Date specified in that file, in accordance with
* the Business Source License, use of this software will be governed
* by the Apache License, Version 2.0
*/

#include "key.h"

#include "crypto/crypto.h"
#include "crypto/exceptions.h"
#include "crypto/internal.h"
#include "crypto/ssl_utils.h"
#include "crypto/types.h"

#include <absl/container/flat_hash_set.h>
#include <openssl/bio.h>
#include <openssl/bn.h>
#include <openssl/evp.h>
#include <openssl/param_build.h>
#include <openssl/pem.h>

namespace crypto {

namespace internal {
EVP_PKEY_ptr load_public_pem_key(bytes_view key) {
BIO_ptr key_bio(BIO_new_mem_buf(key.data(), static_cast<int>(key.size())));
EVP_PKEY_ptr pkey(
PEM_read_bio_PUBKEY(key_bio.get(), nullptr, nullptr, nullptr));
if (!pkey) {
throw ossl_error("Failed to load PEM public key");
}

return pkey;
}

EVP_PKEY_ptr load_private_pem_key(bytes_view key) {
BIO_ptr key_bio(BIO_new_mem_buf(key.data(), static_cast<int>(key.size())));
EVP_PKEY_ptr pkey(
PEM_read_bio_PrivateKey(key_bio.get(), nullptr, nullptr, nullptr));
if (!pkey) {
throw ossl_error("Failed to load PEM private key");
}

return pkey;
}

// NOLINTNEXTLINE
static const absl::flat_hash_set<int> supported_key_types{EVP_PKEY_RSA};
} // namespace internal

key::~key() noexcept = default;
key::key(key&&) noexcept = default;
key& key::operator=(key&&) noexcept = default;

static_assert(
std::is_nothrow_move_constructible_v<key>,
"key should be nothrow move constructible");
static_assert(
std::is_nothrow_move_assignable_v<key>,
"key should be nothrow move assignable");

key::impl::~impl() noexcept = default;

key::impl::impl(
_private, internal::EVP_PKEY_ptr pkey, is_private_key_t is_private_key)
: _pkey(std::move(pkey))
, _is_private_key(is_private_key) {
auto key_type = EVP_PKEY_get_base_id(_pkey.get());

if (!internal::supported_key_types.contains(key_type)) {
throw exception(fmt::format("Unsupported key type {}", key_type));
}
}

key_type key::impl::get_key_type() const {
auto key_type = EVP_PKEY_get_base_id(_pkey.get());

// NOLINTNEXTLINE: For when we add more supported key types
switch (key_type) {
case EVP_PKEY_RSA:
return key_type::RSA;
}

vassert(false, "Unsupported key type {}", key_type);
}

key_type key::get_type() const { return _impl->get_key_type(); }
is_private_key_t key::is_private_key() const { return _impl->is_private_key(); }

std::unique_ptr<key::impl>
key::impl::load_pem_key(bytes_view key, is_private_key_t is_private_key) {
auto key_ptr = is_private_key == is_private_key_t::yes
? internal::load_private_pem_key(key)
: internal::load_public_pem_key(key);

return std::make_unique<key::impl>(
key::impl::_private{}, std::move(key_ptr), is_private_key);
}

std::unique_ptr<key::impl>
key::impl::load_rsa_public_key(bytes_view n, bytes_view e) {
auto n_bn = internal::BN_ptr(
BN_bin2bn(n.data(), static_cast<int>(n.size()), nullptr));
if (!n_bn) {
throw internal::ossl_error("Failed to load exponent bignum value");
}
auto e_bn = internal::BN_ptr(
BN_bin2bn(e.data(), static_cast<int>(e.size()), nullptr));
if (!e_bn) {
throw internal::ossl_error(
"Failed to load public exponent bignum value");
}
auto param_bld = internal::OSSL_PARAM_BLD_ptr(OSSL_PARAM_BLD_new());
if (!param_bld) {
throw internal::ossl_error(
"Failed to create param builder for loading RSA public key");
}
if (
!OSSL_PARAM_BLD_push_BN(param_bld.get(), "n", n_bn.get())
|| !OSSL_PARAM_BLD_push_BN(param_bld.get(), "e", e_bn.get())) {
throw internal::ossl_error("Failed to set E or N to parameters");
}

auto params = internal::OSSL_PARAM_ptr(
OSSL_PARAM_BLD_to_param(param_bld.get()));
if (!params) {
throw internal::ossl_error("Failed to great parameters from builder");
}

auto ctx = internal::EVP_PKEY_CTX_ptr(
EVP_PKEY_CTX_new_from_name(nullptr, "RSA", nullptr));
if (!ctx) {
throw internal::ossl_error("Failed to create RSA PKEY context");
}

if (EVP_PKEY_fromdata_init(ctx.get()) != 1) {
throw internal::ossl_error("Failed to initialize EVP PKEY from data");
}

EVP_PKEY* pkey = nullptr;

if (
1
!= EVP_PKEY_fromdata(
ctx.get(), &pkey, EVP_PKEY_PUBLIC_KEY, params.get())) {
throw internal::ossl_error(
"Failed to load RSA public key from parameters");
}

auto pkey_ptr = internal::EVP_PKEY_ptr(pkey);
{
// For some reason, even though the original PKEY_CTX was used to load
// the key, it's not 'saved' within that ctx so we need to create a
// _new_ one to verify that the key was good
auto verify_ctx = internal::EVP_PKEY_CTX_ptr(
EVP_PKEY_CTX_new_from_pkey(nullptr, pkey_ptr.get(), nullptr));
if (1 != EVP_PKEY_public_check(verify_ctx.get())) {
throw internal::ossl_error("Failed RSA public key validation");
}
}

return std::make_unique<key::impl>(
_private{}, std::move(pkey_ptr), is_private_key_t::no);
}

key::key(std::unique_ptr<impl>&& impl)
: _impl(std::move(impl)) {}

key key::load_key(
bytes_view key_buffer, format_type fmt, is_private_key_t is_private_key) {
auto key_ptr = fmt == format_type::PEM
? key::impl::load_pem_key(key_buffer, is_private_key)
: nullptr;

return key{std::move(key_ptr)};
}

key key::load_key(
std::string_view key_buffer,
format_type fmt,
is_private_key_t is_private_key) {
return key::load_key(
internal::string_view_to_bytes_view(key_buffer), fmt, is_private_key);
}

key key::load_rsa_public_key(bytes_view n, bytes_view e) {
return key{key::impl::load_rsa_public_key(n, e)};
}

key key::load_rsa_public_key(std::string_view n, std::string_view e) {
return key::load_rsa_public_key(
internal::string_view_to_bytes_view(n),
internal::string_view_to_bytes_view(e));
}
} // namespace crypto
48 changes: 48 additions & 0 deletions src/v/crypto/key.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2024 Redpanda Data, Inc.
*
* Use of this software is governed by the Business Source License
* included in the file licenses/BSL.md
*
* As of the Change Date specified in that file, in accordance with
* the Business Source License, use of this software will be governed
* by the Apache License, Version 2.0
*/

#pragma once

#include "bytes/bytes.h"
#include "crypto/crypto.h"
#include "crypto/types.h"
#include "ssl_utils.h"

namespace crypto {
class key::impl {
private:
struct _private {};

public:
impl(
_private p, internal::EVP_PKEY_ptr pkey, is_private_key_t is_private_key);
~impl() noexcept;
impl(const impl&) = delete;
impl& operator=(const impl&) = delete;
impl(impl&&) = default;
impl& operator=(impl&&) = default;
static std::unique_ptr<impl>
load_pem_key(bytes_view key, is_private_key_t is_private_key);
static std::unique_ptr<impl>
load_der_key(bytes_view key, is_private_key_t is_private_key);
static std::unique_ptr<impl>
load_rsa_public_key(bytes_view n, bytes_view e);

key_type get_key_type() const;
is_private_key_t is_private_key() const { return _is_private_key; }

EVP_PKEY* get_pkey() const { return _pkey.get(); }

private:
internal::EVP_PKEY_ptr _pkey;
is_private_key_t _is_private_key;
};
} // namespace crypto
Loading

0 comments on commit 774bb60

Please sign in to comment.