diff --git a/console/CMakeLists.txt b/console/CMakeLists.txt
index 96c0ce9f7..90a9f98d2 100644
--- a/console/CMakeLists.txt
+++ b/console/CMakeLists.txt
@@ -21,6 +21,8 @@
# * along with this program. If not, see .*
# ************************************************************************
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/commands/helpers/)
+
#-------------------------------------------------------------------------------
# eos executable
#-------------------------------------------------------------------------------
diff --git a/console/commands/com_scitoken.cc b/console/commands/com_scitoken.cc
index ddc2e8d8e..f49536c35 100644
--- a/console/commands/com_scitoken.cc
+++ b/console/commands/com_scitoken.cc
@@ -31,10 +31,11 @@ com_scitoken(char* arg1)
}
#else
+#include "console/ConsoleMain.hh"
+#include "console/commands/helpers/jwk_generator/jwk_generator.hpp"
#include "common/Mapping.hh"
#include "common/StringTokenizer.hh"
#include "common/Utils.hh"
-#include "console/ConsoleMain.hh"
#include
#include
#include
@@ -282,91 +283,105 @@ com_scitoken(char* arg1)
}
if (subcommand == "create-keys") {
- struct stat buf;
+ do {
+ const char* o = subtokenizer.GetTokenUnquoted();
+ const char* v = subtokenizer.GetTokenUnquoted();
- if (::stat("/sbin/eos-jwker", &buf)) {
- std::cerr << "error: couldn't find /sbin/eos-jwker" << std::endl;
- global_retc = EOPNOTSUPP;
+ if (o && !v) {
+ goto com_scitoken_usage;
+ }
+
+ if (!o && !v) {
+ break;
+ }
+
+ option = o;
+ value = v;
+
+ if (option == "--keyid") {
+ keyid = value;
+ }
+ } while (option.length());
+
+ std::string prefix;
+
+ if (!keyid.empty()) {
+ prefix = "/etc/xrootd/";
} else {
- do {
- const char* o = subtokenizer.GetTokenUnquoted();
- const char* v = subtokenizer.GetTokenUnquoted();
+ keyid = "default";
+ const size_t size = 1024;
+ char buffer[size];
- if (o && !v) {
- goto com_scitoken_usage;
- }
+ if (getcwd(buffer, size) == nullptr) {
+ std::cerr << "error: can not get CWD" << std::endl;
+ global_retc = errno;
+ return 0;
+ }
- if (!o && !v) {
- break;
- }
+ prefix = buffer;
+ }
- option = o;
- value = v;
+ if (*prefix.rbegin() != '/') {
+ prefix += '/';
+ }
- if (option == "--keyid") {
- keyid = value;
- }
+ // If the public/private key files exist then we use them to generate
+ // the jwk file, otherwise we generate new keys
+ bool store_keys = false;
+ std::string fn_public = SSTR(prefix << keyid << "-pkey.pem").c_str();
+ std::string fn_private = SSTR(prefix << keyid << "-key.pem").c_str();
+ std::string jwk_file;
+ struct stat buf;
- if (option == "--jwk") {
- jwk = value;
- }
- } while (option.length());
+ if (::stat(fn_public.c_str(), &buf) ||
+ ::stat(fn_private.c_str(), &buf)) {
+ // We generate new keys
+ fn_public = "";
+ fn_private = "";
+ store_keys = true;
}
- system("openssl ecparam -genkey -name prime256v1 > /tmp/.eossci.ec "
- "2>/dev/null");
- system("openssl pkcs8 -topk8 -nocrypt -in /tmp/.eossci.ec -out "
- "/tmp/.eossci-key.pem 2>/dev/null");
- system("cat /tmp/.eossci.ec | openssl ec -pubout > /tmp/.eossci-pkey.pem "
- "2>/dev/null");
- system(
- "/sbin/eos-jwker /tmp/.eossci-pkey.pem | json_pp > /tmp/.eossci.jwk ");
- {
- Json::Value json;
- Json::Value root;
- std::string errs;
- Json::CharReaderBuilder reader;
- std::ifstream configfile("/tmp/.eossci.jwk", std::ifstream::binary);
- bool ok = parseFromStream(reader, configfile, &root, &errs);
-
- if (ok) {
- // store the keyid into the JSON document
- root["kid"] = keyid.length() ? keyid : "default";
- } else {
- global_retc = EIO;
- return (0);
- }
+ using namespace jwk_generator;
+ JwkGenerator jwk(keyid, fn_public, fn_private);
+ std::cout << "JWK:\n" << jwk.to_pretty_string()
+ << std::endl << std::endl;
+
+ if (store_keys) {
+ fn_public = SSTR(prefix << keyid << "-pkey.pem").c_str();
+ fn_private = SSTR(prefix << keyid << "-key.pem").c_str();
+ jwk_file = SSTR(prefix << keyid << "-sci.jwk").c_str();
+
+ for (auto pair : std::list> {
+ {fn_public, jwk.public_to_pem()},
+ {fn_private, jwk.private_to_pem()},
+ {jwk_file, jwk.to_pretty_string()}
+ }) {
+ std::ofstream file(pair.first);
+
+ if (!file.is_open()) {
+ std::cerr << "error: failed to open public key file "
+ << pair.first << std::endl;
+ global_retc = EINVAL;
+ return 0;
+ }
- json["keys"].append(root);
- std::cout << SSTR(json) << std::endl;
+ file << pair.second << std::endl;
+ file.close();
+ }
}
- std::string prefix;
- if (keyid.length()) {
- prefix = "/etc/xrootd/";
- } else {
- keyid = "default";
+ if (!fn_public.empty() && !fn_private.empty()) {
+ std::cerr << (store_keys ? "Wrote" : "Used") << " public key : "
+ << fn_public << std::endl
+ << (store_keys ? "Wrote" : "Used") << " private key: "
+ << fn_private << std::endl;
+
+ if (!jwk_file.empty()) {
+ std::cerr << "Wrote JWK file : " << jwk_file << std::endl;
+ }
}
- std::string s;
- s = "mv /tmp/.eossci-pkey.pem ";
- s += prefix;
- s += keyid;
- s += "-pkey.pem";
- system(s.c_str());
- ::unlink("/tmp/.eossci-pkey.pem");
- s = "mv /tmp/.eossci-key.pem ";
- s += prefix;
- s += keyid;
- s += "-key.pem";
- system(s.c_str());
- ::unlink("/tmp/.eossci-key.pem");
- ::unlink("/tmp/.eossci.ec");
- std::cerr << "# private key : " << prefix << keyid << "-key.pem"
- << std::endl;
- std::cerr << "# public key : " << prefix << keyid << "-pkey.pem"
- << std::endl;
- return (0);
+ return 0;
}
com_scitoken_usage:
@@ -399,7 +414,7 @@ com_scitoken(char* arg1)
<< " eos scitoken create --issuer eos.cern.ch --keyid eos "
<< "profile wlcg --claim sub=foo --claim scope=storage.read:/eos\n"
<< " eos scitoken dump eyJhb ...\n"
- << " eos scitoken create-keys --keyid eos > /etc/xrootrd/eos.json\n";
+ << " eos scitoken create-keys --keyid eos > /etc/xrootd/eos.jwk\n";
std::cerr << oss.str().c_str() << std::endl;
global_retc = EINVAL;
return 0;
diff --git a/console/commands/helpers/jwk_generator/c_resource.hpp b/console/commands/helpers/jwk_generator/c_resource.hpp
new file mode 100644
index 000000000..6374736c8
--- /dev/null
+++ b/console/commands/helpers/jwk_generator/c_resource.hpp
@@ -0,0 +1,53 @@
+#pragma once
+#include
+#include
+#include
+#include
+
+template
+class c_resource
+{
+private:
+ std::unique_ptr ptr;
+public:
+ c_resource(std::function creater) : ptr{creater(), deleter} {}
+ c_resource(T* ptr) : ptr{ptr, deleter} {}
+ c_resource() : ptr {nullptr, deleter} {}
+
+ static c_resource allocate()
+ {
+ static_assert(not std::is_null_pointer_v,
+ "allocate should not be used without a defined allocator");
+ return c_resource(allocator);
+ }
+
+ operator bool() const
+ {
+ return ptr;
+ }
+
+ auto release()
+ {
+ return ptr.release();
+ }
+
+ auto get()
+ {
+ return ptr.get();
+ }
+
+ auto get() const
+ {
+ return ptr.get();
+ }
+
+ operator T* ()
+ {
+ return get();
+ }
+
+ operator const T* () const
+ {
+ return get();
+ }
+};
diff --git a/console/commands/helpers/jwk_generator/errors.hpp b/console/commands/helpers/jwk_generator/errors.hpp
new file mode 100644
index 000000000..f46a6a4af
--- /dev/null
+++ b/console/commands/helpers/jwk_generator/errors.hpp
@@ -0,0 +1,22 @@
+#pragma once
+#include
+#include
+#include
+
+namespace jwk_generator
+{
+struct openssl_error: public std::runtime_error {
+private:
+ static inline std::string open_ssl_last_error()
+ {
+ int err = ERR_get_error();
+ char errStr[256];
+ ERR_error_string(err, errStr);
+ return std::string(errStr);
+ }
+public:
+ openssl_error(std::string what) : std::runtime_error(what +
+ open_ssl_last_error()) {}
+};
+};
+
diff --git a/console/commands/helpers/jwk_generator/jwk_generator.hpp b/console/commands/helpers/jwk_generator/jwk_generator.hpp
new file mode 100644
index 000000000..be5bb75cb
--- /dev/null
+++ b/console/commands/helpers/jwk_generator/jwk_generator.hpp
@@ -0,0 +1,215 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "jwk_generator//libs/base64_url.hpp"
+#include "jwk_generator/libs/uuid.hpp"
+#include "jwk_generator/libs/json.hpp"
+#include "jwk_generator/errors.hpp"
+#include "jwk_generator/keyspecs/ec_key.hpp"
+#include "jwk_generator/keyspecs/rsa_key.hpp"
+
+namespace jwk_generator
+{
+template
+class JwkGenerator
+{
+public:
+ KeySpec key;
+ std::string kid;
+
+private:
+ std::string to_pem(std::function writeKeyToBIO) const
+ {
+ using namespace detail;
+ auto pemKeyBIO = std::shared_ptr(BIO_new(BIO_s_secmem()), BIO_free);
+
+ if (!pemKeyBIO) {
+ throw openssl_error("Unable to retrieve public key: ");
+ }
+
+ EVP_PKEY* tmpEVP = key.keyPair.get();
+ int result = writeKeyToBIO(pemKeyBIO.get(), tmpEVP);
+
+ if (!result) {
+ throw openssl_error("Unable to convert key to pem: ");
+ }
+
+ char* buffer;
+ auto len = BIO_get_mem_data(pemKeyBIO.get(), &buffer);
+
+ if (!len) {
+ throw openssl_error("Unable to retrieve key from bio: ");
+ }
+
+ std::string pem;
+ pem.resize(len);
+ std::memcpy(pem.data(), buffer, len);
+ return pem;
+ }
+
+public:
+ JwkGenerator(const JwkGenerator&) = delete;
+ JwkGenerator& operator = (const JwkGenerator&) = delete;
+ JwkGenerator(JwkGenerator&&) = default;
+ JwkGenerator& operator = (JwkGenerator&&) = default;
+
+ JwkGenerator()
+ {
+ kid = detail::generate_uuid_v4();
+ }
+
+ JwkGenerator(const std::string& kid_uuid,
+ const std::string& fn_public = "",
+ const std::string& fn_private = ""):
+ kid(kid_uuid)
+ {
+ if (!fn_public.empty() && !fn_private.empty()) {
+ key = KeySpec(fn_public, fn_private);
+ }
+ }
+
+ std::string private_to_pem() const
+ {
+ return to_pem([](auto bio, auto key) {
+ return PEM_write_bio_PrivateKey(bio, key, NULL, NULL, 0, 0, NULL);
+ });
+ }
+
+ std::string public_to_pem() const
+ {
+ return to_pem([](auto bio, auto key) {
+ return PEM_write_bio_PUBKEY(bio, key);
+ });
+ }
+
+ nlohmann::json to_json() const
+ {
+ nlohmann::json json;
+ key.insert_json(json);
+ json["kid"] = kid;
+ return json;
+ }
+
+ std::string to_pretty_string() const
+ {
+ return to_json().dump(true);
+ }
+
+ operator std::string() const
+ {
+ return to_json().dump();
+ }
+
+ friend std::ostream& operator<< (std::ostream& out, const JwkGenerator& e)
+ {
+ out << std::string(e);
+ return out;
+ }
+};
+
+template
+class JwkSetGenerator
+{
+private:
+ JwkSetGenerator(JwkSetGenerator&) = delete;
+
+public:
+ JwkSetGenerator() = default;
+ JwkSetGenerator(std::tuple...>&& keys) : keys{keys} { }
+
+ const std::tuple...> keys;
+
+ nlohmann::json to_json() const
+ {
+ using namespace nlohmann;
+ nlohmann::json jwks;
+ std::apply([&jwks](const auto & ... jwk) {
+ jwks["keys"] = std::array < nlohmann::json, std::tuple_size {} > {jwk.to_json()...};
+ }, keys);
+ return jwks;
+ }
+
+ template
+ const auto& get() const
+ {
+ return std::get(keys);
+ }
+
+ operator std::string() const
+ {
+ return to_json().dump();
+ }
+
+ friend std::ostream& operator<< (std::ostream& out, const JwkSetGenerator& e)
+ {
+ out << std::string(e);
+ return out;
+ }
+};
+
+template class Container, class ... Args >
+class JwkSetSingleSpecGenerator
+{
+private:
+ JwkSetSingleSpecGenerator(JwkSetSingleSpecGenerator&) = delete;
+public:
+ JwkSetSingleSpecGenerator() = default;
+ JwkSetSingleSpecGenerator(Container, Args...>&& keys) :
+ keys{std::move(keys)} { }
+ Container, Args...> keys;
+
+ nlohmann::json to_json() const
+ {
+ using namespace nlohmann;
+ nlohmann::json jwks;
+ jwks["keys"] = json::array();
+
+ for (const auto& jwk : keys) {
+ jwks["keys"].push_back(jwk.to_json());
+ }
+
+ return jwks;
+ }
+
+ const JwkGenerator& operator[](size_t idx) const
+ {
+ return keys[idx];
+ }
+
+ template
+ const JwkGenerator& get() const
+ {
+ return keys[idx];
+ }
+
+ operator std::string() const
+ {
+ return to_json().dump();
+ }
+
+ friend std::ostream& operator<< (std::ostream& out,
+ const JwkSetSingleSpecGenerator& e)
+ {
+ out << std::string(e);
+ return out;
+ }
+};
+
+template
+static auto make_jwks(size_t nKeys)
+{
+ std::vector> keys;
+ keys.resize(nKeys);
+ return JwkSetSingleSpecGenerator(std::move(keys));
+}
+
+};
diff --git a/console/commands/helpers/jwk_generator/keyspecs/ec_key.hpp b/console/commands/helpers/jwk_generator/keyspecs/ec_key.hpp
new file mode 100644
index 000000000..86fe2b2f6
--- /dev/null
+++ b/console/commands/helpers/jwk_generator/keyspecs/ec_key.hpp
@@ -0,0 +1,332 @@
+#pragma once
+
+#include "jwk_generator/libs/json.hpp"
+#include "jwk_generator/errors.hpp"
+#include "jwk_generator/openssl_wrapper.hpp"
+
+namespace
+{
+//----------------------------------------------------------------------------
+//! Load public/private key in the corresponding data structure
+//!
+//! @param key EVP_PKEY data structure to be populated
+//! @param fn_public path to the file containing the public key
+//! @param fn_private path to the file containing the private key
+//!
+//! @return true if successful, otherwise false
+//----------------------------------------------------------------------------
+bool load_key_from_file(EVP_PKEY*& priv_key,
+ const std::string& fn_public,
+ const std::string& fn_private)
+{
+ // Load the private key
+ FILE* priv_file = fopen(fn_private.c_str(), "r");
+
+ if (!priv_file) {
+ std::cerr << "error: failed to open file " << fn_private << std::endl;
+ return false;
+ }
+
+ // Read the private keydata
+ priv_key = PEM_read_PrivateKey(priv_file, nullptr, nullptr, nullptr);
+ fclose(priv_file);
+
+ if (!priv_key) {
+ std::cerr << "error: failed to read private key" << std::endl;
+ return false;
+ }
+
+ // Ensure the key is of type EC
+ if (EVP_PKEY_base_id(priv_key) != EVP_PKEY_EC) {
+ std::cerr << "error: private key is not an EC key." << std::endl;
+ EVP_PKEY_free(priv_key);
+ return false;
+ }
+
+ // Load the public key
+ FILE* pub_file = fopen(fn_public.c_str(), "r");
+
+ if (!pub_file) {
+ std::cerr << "error: failed to open file " << fn_public << std::endl;
+ return false;
+ }
+
+ // Read the public key
+ EVP_PKEY* pub_key = PEM_read_PUBKEY(pub_file, nullptr, nullptr, nullptr);
+ fclose(pub_file);
+
+ if (!pub_key) {
+ std::cerr << "error: failed to read public key" << std::endl;
+ EVP_PKEY_free(pub_key);
+ return false;
+ }
+
+ // Ensure the public key if of type EC
+ if (EVP_PKEY_base_id(pub_key) != EVP_PKEY_EC) {
+ std::cerr << "erro: public key is not EC type" << std::endl;
+ EVP_PKEY_free(pub_key);
+ EVP_PKEY_free(priv_key);
+ return false;
+ }
+
+ // Extract the EC_KEY from EVP_PKEY structures
+ EC_KEY* ec_priv_key = EVP_PKEY_get1_EC_KEY(priv_key);
+ EC_KEY* ec_pub_key = EVP_PKEY_get1_EC_KEY(pub_key);
+
+ if (!ec_priv_key || !ec_pub_key) {
+ std::cerr << "error: failed to extract EC_KEY from pub/priv key" << std::endl;
+ EVP_PKEY_free(pub_key);
+ EVP_PKEY_free(priv_key);
+ return false;
+ }
+
+ const EC_POINT* pub_point = EC_KEY_get0_public_key(ec_pub_key);
+
+ if (!pub_point) {
+ std::cerr << "error: failed to retrieve public key or group" << std::endl;
+ EC_KEY_free(ec_priv_key);
+ EC_KEY_free(ec_pub_key);
+ EVP_PKEY_free(pub_key);
+ EVP_PKEY_free(priv_key);
+ return false;
+ }
+
+ // Associate the public key with the private key
+ if (EC_KEY_set_public_key(ec_priv_key, pub_point) != 1) {
+ std::cerr << "error: failed to set the public key" << std::endl;
+ EVP_PKEY_free(priv_key);
+ EC_KEY_free(ec_priv_key);
+ EVP_PKEY_free(pub_key);
+ return false;
+ }
+
+ // Clean up
+ EC_KEY_free(ec_pub_key);
+ EC_KEY_free(ec_priv_key);
+ EVP_PKEY_free(pub_key);
+ EVP_cleanup();
+ return true;
+}
+}
+
+namespace jwk_generator
+{
+template
+class ECKey
+{
+private:
+#ifdef JWKGEN_OPENSSL_3_0
+ static constexpr const char* ecdsa_bit_to_curve()
+ {
+ switch (shaBits) {
+ case 256: {
+ return SN_X9_62_prime256v1;
+ }
+
+ case 512: {
+ return SN_secp521r1;
+ }
+
+ case 384: {
+ return SN_secp384r1;
+ }
+ }
+
+ throw std::runtime_error("Unsupported EC algorithm");
+ }
+#else
+ static constexpr int ecdsa_bit_to_curve()
+ {
+ switch (shaBits) {
+ case 256: {
+ return NID_X9_62_prime256v1;
+ }
+
+ case 512: {
+ return NID_secp521r1;
+ }
+
+ case 384: {
+ return NID_secp384r1;
+ }
+ }
+
+ throw std::runtime_error("Unsupported EC algorithm");
+ }
+#endif
+
+ static constexpr size_t bits_to_point_size()
+ {
+ switch (shaBits) {
+ case 256: {
+ return 32;
+ }
+
+ case 512: {
+ return 66;
+ }
+
+ case 384: {
+ return 48;
+ }
+ }
+
+ throw std::runtime_error("Unsupported EC algorithm");
+ }
+public:
+ static constexpr size_t pointSize = bits_to_point_size();
+ openssl::EVPPKey keyPair;
+ std::string pointX;
+ std::string pointY;
+
+ ECKey(const ECKey&) = delete;
+ ECKey& operator = (const ECKey&) = delete;
+ ECKey(ECKey&&) = default;
+ ECKey& operator = (ECKey&&) = default;
+
+ //------------------------------------------------------------------------
+ //! Constructor reading public and private key part from files
+ //!
+ //! @param fn_public file holding the public key
+ //! @param fn_private file holding the private key
+ //!
+ //------------------------------------------------------------------------
+ ECKey(const std::string& fn_public, const std::string& fn_private)
+ {
+ using namespace detail;
+ EVP_PKEY* key = nullptr;
+
+ if (!load_key_from_file(key, fn_public, fn_private)) {
+ throw std::runtime_error("error: failed to load keys from file");
+ }
+
+ keyPair = openssl::EVPPKey(key);
+#ifdef JWKGEN_OPENSSL_3_0
+ BIGNUM* xBN = nullptr;
+
+ if (!EVP_PKEY_get_bn_param(key, OSSL_PKEY_PARAM_EC_PUB_X, &xBN)) {
+ throw openssl_error("Unable to extract coordinates key: ");
+ }
+
+ BIGNUM* yBN = nullptr;
+
+ if (!EVP_PKEY_get_bn_param(key, OSSL_PKEY_PARAM_EC_PUB_Y, &yBN)) {
+ throw openssl_error("Unable extract coordinates from key: ");
+ }
+
+ std::vector xBin;
+ xBin.resize(pointSize);
+ BN_bn2binpad(xBN, xBin.data(), pointSize);
+ pointX = base64_url_encode(xBin);
+ std::vector yBin;
+ yBin.resize(pointSize);
+ BN_bn2binpad(yBN, yBin.data(), pointSize);
+ pointY = base64_url_encode(yBin);
+#else
+ std::cout << "warning: do extraction with openssl < 3.0.0" << std::endl;
+#endif
+ }
+
+
+ ECKey()
+ {
+ using namespace detail;
+#ifdef JWKGEN_OPENSSL_3_0
+ keyPair = {[]()
+ {
+ return EVP_EC_gen(ecdsa_bit_to_curve());
+ }
+ };
+
+ if (!keyPair) {
+ throw openssl_error("Unable to generate ec key: ");
+ }
+
+ BIGNUM* xBN = nullptr;
+
+ if (!EVP_PKEY_get_bn_param(keyPair, OSSL_PKEY_PARAM_EC_PUB_X, &xBN)) {
+ throw openssl_error("Unable to extract coordinates key: ");
+ }
+
+ BIGNUM* yBN = nullptr;
+
+ if (!EVP_PKEY_get_bn_param(keyPair, OSSL_PKEY_PARAM_EC_PUB_Y, &yBN)) {
+ throw openssl_error("Unable extract coordinates from key: ");
+ }
+
+#else
+ auto group = openssl::ECGroup([]() {
+ return EC_GROUP_new_by_curve_name(ecdsa_bit_to_curve());
+ });
+ auto ec = openssl::ECKey::allocate();
+
+ if (!EC_KEY_set_group(ec, group)) {
+ throw openssl_error("Unable to generate ec key: ");
+ }
+
+ if (!EC_KEY_generate_key(ec)) {
+ throw openssl_error("Unable to generate ec key: ");
+ }
+
+ keyPair = openssl::EVPPKey::allocate();
+
+ if (!keyPair) {
+ throw openssl_error("Unable to generate ec key: ");
+ }
+
+ // release it now as ownership transfers to the key pair
+ EC_KEY* e cPtr = ec.release();
+
+ if (!EVP_PKEY_assign_EC_KEY(keyPair, ecPtr)) {
+ throw openssl_error("Unable to generate ec key: ");
+ }
+
+ openssl::BigNum xBN = openssl::BigNum::allocate();
+
+ if (!xBN) {
+ throw openssl_error("Unable to allocate BN: ");
+ }
+
+ openssl::BigNum yBN = openssl::BigNum::allocate();
+
+ if (!yBN) {
+ throw openssl_error("Unable to allocate BN: ");
+ }
+
+ auto point = EC_KEY_get0_public_key(ecPtr);
+
+ if (!EC_POINT_get_affine_coordinates(group, point, xBN, yBN, NULL)) {
+ throw openssl_error("Unable to extract coordinates from key: ");
+ }
+
+#endif
+ std::vector xBin;
+ xBin.resize(pointSize);
+ BN_bn2binpad(xBN, xBin.data(), pointSize);
+ pointX = base64_url_encode(xBin);
+ std::vector yBin;
+ yBin.resize(pointSize);
+ BN_bn2binpad(yBN, yBin.data(), pointSize);
+ pointY = base64_url_encode(yBin);
+ }
+
+ void insert_json(nlohmann::json& json) const
+ {
+ std::string crv = std::string("P-") + std::to_string(shaBits);
+
+ if (shaBits == 512) {
+ crv = "P-521";
+ }
+
+ json["alg"] = "ES" + std::to_string(shaBits);
+ json["kty"] = "EC";
+ json["x"] = pointX;
+ json["y"] = pointY;
+ json["crv"] = crv;
+ }
+};
+
+using ES256 = ECKey<256>;
+using ES384 = ECKey<384>;
+using ES512 = ECKey<512>;
+};
diff --git a/console/commands/helpers/jwk_generator/keyspecs/rsa_key.hpp b/console/commands/helpers/jwk_generator/keyspecs/rsa_key.hpp
new file mode 100644
index 000000000..ea51f6989
--- /dev/null
+++ b/console/commands/helpers/jwk_generator/keyspecs/rsa_key.hpp
@@ -0,0 +1,99 @@
+#pragma once
+
+#include "jwk_generator/libs/json.hpp"
+#include "jwk_generator/errors.hpp"
+#include "jwk_generator/openssl_wrapper.hpp"
+
+namespace jwk_generator
+{
+template
+struct RSAKey {
+public:
+ static constexpr const size_t nBits = 2048;
+ openssl::EVPPKey keyPair;
+ std::string modulous;
+ std::string exponent;
+
+ RSAKey(const RSAKey&) = delete;
+ RSAKey& operator = (const RSAKey&) = delete;
+ RSAKey(RSAKey&&) = default;
+ RSAKey& operator = (RSAKey&&) = default;
+ RSAKey()
+ {
+ using namespace detail;
+#ifdef JWKGEN_OPENSSL_3_0
+ keyPair = {[]()
+ {
+ return EVP_RSA_gen(nBits);
+ }
+ };
+
+ if (!keyPair) {
+ throw openssl_error("Unable to generate rsa key: ");
+ }
+
+ BIGNUM* modBN = nullptr;
+
+ if (!EVP_PKEY_get_bn_param(keyPair, OSSL_PKEY_PARAM_RSA_N, &modBN)) {
+ throw openssl_error("Unable to retrieve public key: ");
+ }
+
+ BIGNUM* exBN = nullptr;
+
+ if (!EVP_PKEY_get_bn_param(keyPair, OSSL_PKEY_PARAM_RSA_E, &exBN)) {
+ throw openssl_error("Unable to retrieve public key: ");
+ }
+
+#else
+ auto exBN = openssl::BigNum::allocate();
+
+ if (!exBN) {
+ throw openssl_error("Unable to allocate BN: ");
+ }
+
+ BN_set_word(exBN, 65537);
+ auto rsa = openssl::RSA::allocate();
+
+ if (!RSA_generate_key_ex(rsa, nBits, exBN, NULL)) {
+ throw openssl_error("Unable to generate rsa key: ");
+ }
+
+ keyPair = openssl::EVPPKey::allocate();
+
+ if (!keyPair) {
+ throw openssl_error("Unable to generate rsa key: ");
+ }
+
+ RSA* rsaPtr = rsa.release();
+
+ if (!EVP_PKEY_assign_RSA(keyPair, rsaPtr)) {
+ throw openssl_error("Unable to generate rsa key: ");
+ }
+
+ const BIGNUM* modBN = RSA_get0_n(rsaPtr);
+#endif
+ size_t len = BN_num_bytes(modBN);
+ std::vector modBin;
+ modBin.resize(len);
+ BN_bn2bin(modBN, modBin.data());
+ modulous = base64_url_encode(modBin);
+ len = BN_num_bytes(exBN);
+ std::vector exBin;
+ exBin.resize(len);
+ BN_bn2bin(exBN, exBin.data());
+ exponent = base64_url_encode(exBin);
+ }
+
+ void insert_json(nlohmann::json& json) const
+ {
+ json["alg"] = "RS" + std::to_string(shaBits);
+ json["kty"] = "RSA";
+ json["e"] = exponent;
+ json["n"] = modulous;
+ }
+};
+
+using RS256 = RSAKey<256>;
+using RS384 = RSAKey<384>;
+using RS512 = RSAKey<512>;
+};
diff --git a/console/commands/helpers/jwk_generator/libs/base64_url.hpp b/console/commands/helpers/jwk_generator/libs/base64_url.hpp
new file mode 100644
index 000000000..85883a4e2
--- /dev/null
+++ b/console/commands/helpers/jwk_generator/libs/base64_url.hpp
@@ -0,0 +1,134 @@
+// altered slightly from the original
+// https://stackoverflow.com/a/180949
+
+#pragma once
+
+#include
+#include
+#include
+
+namespace jwk_generator
+{
+namespace detail
+{
+static const std::string base64_chars =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789-_";
+
+
+static inline bool is_base64(uint8_t c)
+{
+ return (isalnum(c) || (c == '-') || (c == '_'));
+}
+
+static inline std::string base64_url_encode(std::vector data,
+ bool pad = false)
+{
+ uint8_t const* buf = data.data();
+ unsigned int bufLen = data.size();
+ std::string ret;
+ int i = 0;
+ int j = 0;
+ uint8_t char_array_3[3];
+ uint8_t char_array_4[4];
+
+ while (bufLen--) {
+ char_array_3[i++] = *(buf++);
+
+ if (i == 3) {
+ char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+ char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >>
+ 4);
+ char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >>
+ 6);
+ char_array_4[3] = char_array_3[2] & 0x3f;
+
+ for (i = 0; (i < 4) ; i++) {
+ ret += base64_chars[char_array_4[i]];
+ }
+
+ i = 0;
+ }
+ }
+
+ if (i) {
+ for (j = i; j < 3; j++) {
+ char_array_3[j] = '\0';
+ }
+
+ char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+ char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >>
+ 4);
+ char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >>
+ 6);
+ char_array_4[3] = char_array_3[2] & 0x3f;
+
+ for (j = 0; (j < i + 1); j++) {
+ ret += base64_chars[char_array_4[j]];
+ }
+
+ while (pad && (i++ < 3)) {
+ ret += '=';
+ }
+ }
+
+ return ret;
+}
+
+static inline std::vector base64_url_decode(std::string const&
+ encoded_string)
+{
+ int in_len = encoded_string.size();
+ int i = 0;
+ int j = 0;
+ int in_ = 0;
+ uint8_t char_array_4[4], char_array_3[3];
+ std::vector ret;
+
+ while (in_len-- && (encoded_string[in_] != '=') &&
+ is_base64(encoded_string[in_])) {
+ char_array_4[i++] = encoded_string[in_];
+ in_++;
+
+ if (i == 4) {
+ for (i = 0; i < 4; i++) {
+ char_array_4[i] = base64_chars.find(char_array_4[i]);
+ }
+
+ char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
+ char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >>
+ 2);
+ char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
+
+ for (i = 0; (i < 3); i++) {
+ ret.push_back(char_array_3[i]);
+ }
+
+ i = 0;
+ }
+ }
+
+ if (i) {
+ for (j = i; j < 4; j++) {
+ char_array_4[j] = 0;
+ }
+
+ for (j = 0; j < 4; j++) {
+ char_array_4[j] = base64_chars.find(char_array_4[j]);
+ }
+
+ char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
+ char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >>
+ 2);
+ char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
+
+ for (j = 0; (j < i - 1); j++) {
+ ret.push_back(char_array_3[j]);
+ }
+ }
+
+ return ret;
+}
+};
+};
diff --git a/console/commands/helpers/jwk_generator/libs/json.hpp b/console/commands/helpers/jwk_generator/libs/json.hpp
new file mode 100644
index 000000000..d7e1caaf4
--- /dev/null
+++ b/console/commands/helpers/jwk_generator/libs/json.hpp
@@ -0,0 +1,24025 @@
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.11.2
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+/****************************************************************************\
+ * Note on documentation: The source files contain links to the online *
+ * documentation of the public API at https://json.nlohmann.me. This URL *
+ * contains the most recent documentation and should also be applicable to *
+ * previous versions; documentation for deprecated functions is not *
+ * removed, but marked deprecated. See "Generate documentation" section in *
+ * file docs/README.md. *
+\****************************************************************************/
+
+#ifndef INCLUDE_NLOHMANN_JSON_HPP_
+#define INCLUDE_NLOHMANN_JSON_HPP_
+
+#include // all_of, find, for_each
+#include // nullptr_t, ptrdiff_t, size_t
+#include // hash, less
+#include // initializer_list
+#ifndef JSON_NO_IO
+#include // istream, ostream
+#endif // JSON_NO_IO
+#include // random_access_iterator_tag
+#include // unique_ptr
+#include // accumulate
+#include // string, stoi, to_string
+#include // declval, forward, move, pair, swap
+#include // vector
+
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.11.2
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+
+
+#include
+
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.11.2
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+
+
+// This file contains all macro definitions affecting or depending on the ABI
+
+#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK
+#if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)
+#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 2
+#warning "Already included a different version of the library!"
+#endif
+#endif
+#endif
+
+#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum)
+#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum)
+#define NLOHMANN_JSON_VERSION_PATCH 2 // NOLINT(modernize-macro-to-enum)
+
+#ifndef JSON_DIAGNOSTICS
+#define JSON_DIAGNOSTICS 0
+#endif
+
+#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
+#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
+#endif
+
+#if JSON_DIAGNOSTICS
+#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
+#else
+#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS
+#endif
+
+#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
+#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp
+#else
+#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
+#endif
+
+#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
+#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
+#endif
+
+// Construct the namespace ABI tags component
+#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b
+#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \
+ NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b)
+
+#define NLOHMANN_JSON_ABI_TAGS \
+ NLOHMANN_JSON_ABI_TAGS_CONCAT( \
+ NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
+ NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON)
+
+// Construct the namespace version component
+#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
+ _v ## major ## _ ## minor ## _ ## patch
+#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \
+ NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)
+
+#if NLOHMANN_JSON_NAMESPACE_NO_VERSION
+#define NLOHMANN_JSON_NAMESPACE_VERSION
+#else
+#define NLOHMANN_JSON_NAMESPACE_VERSION \
+ NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \
+ NLOHMANN_JSON_VERSION_MINOR, \
+ NLOHMANN_JSON_VERSION_PATCH)
+#endif
+
+// Combine namespace components
+#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b
+#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \
+ NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)
+
+#ifndef NLOHMANN_JSON_NAMESPACE
+#define NLOHMANN_JSON_NAMESPACE \
+ nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \
+ NLOHMANN_JSON_ABI_TAGS, \
+ NLOHMANN_JSON_NAMESPACE_VERSION)
+#endif
+
+#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN
+#define NLOHMANN_JSON_NAMESPACE_BEGIN \
+ namespace nlohmann \
+ { \
+ inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \
+ NLOHMANN_JSON_ABI_TAGS, \
+ NLOHMANN_JSON_NAMESPACE_VERSION) \
+ {
+#endif
+
+#ifndef NLOHMANN_JSON_NAMESPACE_END
+#define NLOHMANN_JSON_NAMESPACE_END \
+ } /* namespace (inline namespace) NOLINT(readability/namespace) */ \
+ } // namespace nlohmann
+#endif
+
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.11.2
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+
+
+#include // transform
+#include // array
+#include // forward_list
+#include // inserter, front_inserter, end
+#include