feat(CIE): Implement OIDC RP Service with login, callback, and session management
This commit is contained in:
@@ -1,7 +1,13 @@
|
||||
#include "common/Crypto.h"
|
||||
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -10,6 +16,12 @@ namespace visione::crypto
|
||||
{
|
||||
namespace
|
||||
{
|
||||
using BnPtr = std::unique_ptr<BIGNUM, decltype(&BN_free)>;
|
||||
using RsaPtr = std::unique_ptr<RSA, decltype(&RSA_free)>;
|
||||
using EvpKeyPtr = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>;
|
||||
using BioPtr = std::unique_ptr<BIO, decltype(&BIO_free)>;
|
||||
using BnCtxPtr = std::unique_ptr<BN_CTX, decltype(&BN_CTX_free)>;
|
||||
|
||||
std::string toHex(const unsigned char *data, size_t size)
|
||||
{
|
||||
std::ostringstream stream;
|
||||
@@ -20,6 +32,39 @@ std::string toHex(const unsigned char *data, size_t size)
|
||||
}
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string base64UrlDecode(std::string value)
|
||||
{
|
||||
while (value.size() % 4 != 0)
|
||||
{
|
||||
value.push_back('=');
|
||||
}
|
||||
for (char &ch : value)
|
||||
{
|
||||
if (ch == '-')
|
||||
{
|
||||
ch = '+';
|
||||
}
|
||||
else if (ch == '_')
|
||||
{
|
||||
ch = '/';
|
||||
}
|
||||
}
|
||||
return drogon::utils::base64Decode(value);
|
||||
}
|
||||
|
||||
BnPtr bnFromBase64Url(const std::string &value)
|
||||
{
|
||||
const auto decoded = base64UrlDecode(value);
|
||||
BIGNUM *raw = BN_bin2bn(reinterpret_cast<const unsigned char *>(decoded.data()),
|
||||
static_cast<int>(decoded.size()),
|
||||
nullptr);
|
||||
if (!raw)
|
||||
{
|
||||
throw std::runtime_error("invalid_jwk_bn");
|
||||
}
|
||||
return {raw, &BN_free};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::string sha256(const std::string &value)
|
||||
@@ -47,8 +92,151 @@ std::string randomToken()
|
||||
return drogon::utils::getUuid() + drogon::utils::getUuid();
|
||||
}
|
||||
|
||||
std::string randomBytes(size_t size)
|
||||
{
|
||||
return drogon::utils::genRandomString(size);
|
||||
}
|
||||
|
||||
std::string hashSecret(const std::string &secret)
|
||||
{
|
||||
return sha256("visione:" + secret);
|
||||
}
|
||||
|
||||
std::string pemFromRsaJwk(const std::string &n,
|
||||
const std::string &e,
|
||||
const std::string &d,
|
||||
const std::string &p,
|
||||
const std::string &q)
|
||||
{
|
||||
RsaPtr rsa(RSA_new(), &RSA_free);
|
||||
if (!rsa)
|
||||
{
|
||||
throw std::runtime_error("rsa_new_failed");
|
||||
}
|
||||
|
||||
auto nBn = bnFromBase64Url(n);
|
||||
auto eBn = bnFromBase64Url(e);
|
||||
BIGNUM *nRaw = nBn.release();
|
||||
BIGNUM *eRaw = eBn.release();
|
||||
if (RSA_set0_key(rsa.get(), nRaw, eRaw, nullptr) != 1)
|
||||
{
|
||||
BN_free(nRaw);
|
||||
BN_free(eRaw);
|
||||
throw std::runtime_error("rsa_set_public_failed");
|
||||
}
|
||||
|
||||
const bool includePrivate = !d.empty();
|
||||
if (includePrivate)
|
||||
{
|
||||
auto dBn = bnFromBase64Url(d);
|
||||
BIGNUM *dRaw = dBn.release();
|
||||
if (RSA_set0_key(rsa.get(), nullptr, nullptr, dRaw) != 1)
|
||||
{
|
||||
BN_free(dRaw);
|
||||
throw std::runtime_error("rsa_set_private_failed");
|
||||
}
|
||||
|
||||
if (p.empty() || q.empty())
|
||||
{
|
||||
throw std::runtime_error("missing_private_jwk_fields");
|
||||
}
|
||||
|
||||
auto pBn = bnFromBase64Url(p);
|
||||
auto qBn = bnFromBase64Url(q);
|
||||
BIGNUM *pRaw = pBn.release();
|
||||
BIGNUM *qRaw = qBn.release();
|
||||
if (RSA_set0_factors(rsa.get(), pRaw, qRaw) != 1)
|
||||
{
|
||||
BN_free(pRaw);
|
||||
BN_free(qRaw);
|
||||
throw std::runtime_error("rsa_set_factors_failed");
|
||||
}
|
||||
|
||||
const BIGNUM *rsaD = nullptr;
|
||||
const BIGNUM *rsaP = nullptr;
|
||||
const BIGNUM *rsaQ = nullptr;
|
||||
const BIGNUM *rsaE = nullptr;
|
||||
RSA_get0_key(rsa.get(), nullptr, &rsaE, &rsaD);
|
||||
RSA_get0_factors(rsa.get(), &rsaP, &rsaQ);
|
||||
|
||||
BnCtxPtr bnCtx(BN_CTX_new(), &BN_CTX_free);
|
||||
if (!bnCtx)
|
||||
{
|
||||
throw std::runtime_error("bn_ctx_new_failed");
|
||||
}
|
||||
|
||||
BIGNUM *dp = BN_new();
|
||||
BIGNUM *dq = BN_new();
|
||||
BIGNUM *qi = BN_new();
|
||||
BIGNUM *pMinus1 = BN_dup(rsaP);
|
||||
BIGNUM *qMinus1 = BN_dup(rsaQ);
|
||||
if (!dp || !dq || !qi || !pMinus1 || !qMinus1)
|
||||
{
|
||||
BN_free(dp);
|
||||
BN_free(dq);
|
||||
BN_free(qi);
|
||||
BN_free(pMinus1);
|
||||
BN_free(qMinus1);
|
||||
throw std::runtime_error("bn_alloc_failed");
|
||||
}
|
||||
|
||||
BN_sub_word(pMinus1, 1);
|
||||
BN_sub_word(qMinus1, 1);
|
||||
if (BN_mod(dp, rsaD, pMinus1, bnCtx.get()) != 1 ||
|
||||
BN_mod(dq, rsaD, qMinus1, bnCtx.get()) != 1 ||
|
||||
BN_mod_inverse(qi, rsaQ, rsaP, bnCtx.get()) == nullptr)
|
||||
{
|
||||
BN_free(dp);
|
||||
BN_free(dq);
|
||||
BN_free(qi);
|
||||
BN_free(pMinus1);
|
||||
BN_free(qMinus1);
|
||||
throw std::runtime_error("rsa_crt_params_failed");
|
||||
}
|
||||
BN_free(pMinus1);
|
||||
BN_free(qMinus1);
|
||||
|
||||
if (RSA_set0_crt_params(rsa.get(), dp, dq, qi) != 1)
|
||||
{
|
||||
BN_free(dp);
|
||||
BN_free(dq);
|
||||
BN_free(qi);
|
||||
throw std::runtime_error("rsa_set_crt_failed");
|
||||
}
|
||||
}
|
||||
|
||||
EVP_PKEY *rawPkey = EVP_PKEY_new();
|
||||
if (!rawPkey)
|
||||
{
|
||||
throw std::runtime_error("evp_pkey_new_failed");
|
||||
}
|
||||
EvpKeyPtr pkey(rawPkey, &EVP_PKEY_free);
|
||||
if (EVP_PKEY_assign_RSA(pkey.get(), rsa.release()) != 1)
|
||||
{
|
||||
throw std::runtime_error("evp_assign_rsa_failed");
|
||||
}
|
||||
|
||||
BioPtr bio(BIO_new(BIO_s_mem()), &BIO_free);
|
||||
if (!bio)
|
||||
{
|
||||
throw std::runtime_error("bio_new_failed");
|
||||
}
|
||||
|
||||
const int ok = includePrivate
|
||||
? PEM_write_bio_PrivateKey(
|
||||
bio.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr)
|
||||
: PEM_write_bio_PUBKEY(bio.get(), pkey.get());
|
||||
if (ok != 1)
|
||||
{
|
||||
throw std::runtime_error("pem_write_failed");
|
||||
}
|
||||
|
||||
BUF_MEM *buffer = nullptr;
|
||||
BIO_get_mem_ptr(bio.get(), &buffer);
|
||||
if (!buffer || !buffer->data || buffer->length == 0)
|
||||
{
|
||||
throw std::runtime_error("pem_buffer_failed");
|
||||
}
|
||||
return {buffer->data, buffer->length};
|
||||
}
|
||||
} // namespace visione::crypto
|
||||
|
||||
Reference in New Issue
Block a user