-
-
Notifications
You must be signed in to change notification settings - Fork 243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How were the JWKS and token generated for the jwks-verify.cpp
example?
#160
Comments
I am assuming the original author
Sadly not yet, however if you are interested in contributing... I did some work on this and I can share my notes |
Makes sense. Normally I generate my keys with OpenSSH//OpenSSL in terminal and if needed encode in PEM but hadn't worked with JWKS before. Was mostly curious as to whether they had been generated in code using this repo. Thanks for the answer.
Yeah feel free to share. Would be interesting to look at. I'd be happy to explore further. |
I have a test "pem to pem" which is for the RSA JWK scenario making sure I can remake a public key from the modulus and exponent (since The bulk should just be parsing/formatting the structure
std::unique_ptr<BIGNUM, decltype(&BN_free)> base64DecodeBigNum(const std::string& base64bignum)
{
const auto decode = jwt::base::decode<jwt::alphabet::base64url>(jwt::base::pad<jwt::alphabet::base64url>(base64bignum));
return jwt::helper::raw2bn(decode);
}
// https://gist.github.com/polesen/2855098
// https://stackoverflow.com/a/10903704/8480874
std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> RsaPubKeyFromModExp(const std::string& modulus_b64, const std::string& exp_b64)
{
auto n = base64DecodeBigNum(modulus_b64);
auto e = base64DecodeBigNum(exp_b64);
if (e && n)
{
EVP_PKEY* pRsaKey = EVP_PKEY_new();
RSA* rsa = RSA_new();
RSA_set0_key(rsa, n.release(), e.release(), nullptr);
EVP_PKEY_assign_RSA(pRsaKey, rsa);
return std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>(pRsaKey, EVP_PKEY_free);
}
return std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>(nullptr, EVP_PKEY_free);
}
std::tuple<std::string, std::string> Auth::extractModulusAndExp(const std::string& publicKey)
{
const auto pkey = jwt::helper::load_public_key_from_string(publicKey);
RSA* rsa = EVP_PKEY_get1_RSA(pkey.get());
const BIGNUM* n = RSA_get0_n(rsa);
const BIGNUM* e = RSA_get0_e(rsa);
const auto modulus = jwt::base::trim<jwt::alphabet::base64url>(jwt::base::encode<jwt::alphabet::base64url>(jwt::helper::bn2raw(n)));
const auto exp = jwt::base::trim<jwt::alphabet::base64url>(jwt::base::encode<jwt::alphabet::base64url>(jwt::helper::bn2raw(e)));
return { modulus, exp };
} |
Thanks! I was able to verify the resulting token using the public key without needing to convert to a JWKS. Will definitely explore this when time permits. |
The jwks part in jwt-cpp is very much in it's early stages, which is one of the reasons its not really announced as a feature anywhere yet. Whats there should work, but there simply isn't much yet. In particular there is no real support for writing/building jwks, the reading part does very little verification and (as you have discovered) there is no built in way to convert the jwks to something you can use for signing/verifying yet. All of these will get implemented at some point though. |
jwks-verify.cpp
example?
Now suspect, it was just copied off the internet from an tutorial 👓 While trying to fix the example (in #307) I needed to make new values and in my research doing this purely from the CLI with openssl was very challenging because outputting the "raw binary" was not possible to then be encoded correctly so I ended up writing a bunch of code as a result this still needs
#include <iostream>
#include <jwt-cpp/jwt.h>
#include <openssl/rand.h>
std::string write_bio_to_string(std::unique_ptr<BIO, decltype(&BIO_free_all)>& bio_out) {
char* ptr = nullptr;
auto len = BIO_get_mem_data(bio_out.get(), &ptr);
if (len <= 0 || ptr == nullptr) { throw std::exception(); }
return {ptr, static_cast<size_t>(len)};
}
int main() {
EVP_PKEY* pkey = NULL;
#if defined(JWT_OPENSSL_3_0)
EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
EVP_PKEY_keygen_init(pctx);
// https://www.openssl.org/docs/man3.1/man3/EVP_PKEY_keygen_init.html
EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, 4096);
EVP_PKEY_generate(pctx, &pkey);
#else
pkey = EVP_PKEY_new(); // https://stackoverflow.com/questions/5313855/rsa-sign-openssl
// https://www.dynamsoft.com/codepool/how-to-use-openssl-generate-rsa-keys-cc.html
BIGNUM* bne = BN_new();
BN_set_word(bne, RSA_F4);
RSA* rsa = RSA_new();
RSA_generate_key_ex(rsa, 4096, bne, NULL);
EVP_PKEY_set1_RSA(pkey, rsa);
#endif
std::string pem_public_key = [&]() {
auto bio_out = jwt::helper::make_mem_buf_bio();
PEM_write_bio_PUBKEY(bio_out.get(), pkey);
const auto pub_key = write_bio_to_string(bio_out);
std::cout << pub_key << std::endl;
return pub_key;
}();
// https://stackoverflow.com/questions/69179822/jwk-key-creation-with-x5c-and-x5t-parameters
// https://stackoverflow.com/questions/256405/programmatically-create-x509-certificate-using-openssl
std::unique_ptr<X509, decltype(&X509_free)> cert{X509_new(), X509_free};
ASN1_INTEGER* serial_number = X509_get_serialNumber(cert.get());
ASN1_INTEGER_set(serial_number, 1); // serial number
#if defined(JWT_OPENSSL_1_0_0)
auto x509_not_before = &X509_get_notBefore;
auto x509_not_after = &X509_get_notAfter;
#else
auto x509_not_before = &X509_getm_notBefore;
auto x509_not_after = &X509_getm_notAfter;
#endif
X509_gmtime_adj(x509_not_before(cert.get()), 0); // now
X509_gmtime_adj(x509_not_after(cert.get()), 10 * 365 * 24 * 3600); // accepts secs
X509_set_pubkey(cert.get(), pkey);
X509_NAME* name = X509_get_subject_name(cert.get());
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (const unsigned char*)"US", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (const unsigned char*)"JWT-CPP", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const unsigned char*)"localhost", -1, -1, 0);
X509_set_issuer_name(cert.get(), name);
X509_sign(cert.get(), pkey, EVP_sha256()); // some hash type here
std::string base64_x5c = [&]() {
// PEM_write_bio_X509(certFile.get(), cert.get());
// PEM_write_bio_PrivateKey(keyFile.get(), pkey, nullptr, nullptr, 0, nullptr, nullptr);
auto bio_out = jwt::helper::make_mem_buf_bio();
i2d_X509_bio(bio_out.get(), cert.get());
const auto der_cert = write_bio_to_string(bio_out);
const auto b64_der_cert = jwt::base::encode<jwt::alphabet::base64>(der_cert);
std::cout << b64_der_cert << std::endl;
return b64_der_cert;
}();
// https://stackoverflow.com/questions/8135209/open-ssl-certificate-fingerprint-in-c
// std::string base64_x5c = [&](){
// auto bio_out = jwt::helper::make_mem_buf_bio();
// i2d_PUBKEY_bio(bio_out.get(), pkey);
// const auto der_pub_key = write_bio_to_string(bio_out);
// const auto x5c = jwt::base::encode<jwt::alphabet::base64>(der_pub_key);
// std::cout << x5c << std::endl;
// return x5c;
// }();
std::string pem_priv_key = [&]() {
auto bio_out = jwt::helper::make_mem_buf_bio();
PEM_write_bio_PrivateKey(bio_out.get(), pkey, NULL, NULL, 0, 0, (void*)"");
const auto priv_key = write_bio_to_string(bio_out);
std::cout << priv_key << std::endl;
return priv_key;
}();
#if defined(JWT_OPENSSL_3_0)
EVP_PKEY_CTX_free(pctx);
#else
RSA_free(rsa);
BN_free(bne);
#endif
#if defined(JWT_OPENSSL_3_0)
BIGNUM* n = nullptr;
EVP_PKEY_get_bn_param(pkey, "n", &n);
BIGNUM* e = nullptr;
EVP_PKEY_get_bn_param(pkey, "e", &e);
#elif defined(JWT_OPENSSL_1_1_1) && !defined(LIBWOLFSSL_VERSION_HEX) && !defined(LIBRESSL_VERSION_NUMBER)
// wolfSSL is missing RSA_get0_n and needs RSA_get0_key
RSA* r = EVP_PKEY_get1_RSA(pkey);
const BIGNUM* n = RSA_get0_n(r);
const BIGNUM* e = RSA_get0_e(r);
#elif defined(JWT_OPENSSL_1_1_0) || defined(LIBWOLFSSL_VERSION_HEX) || defined(LIBRESSL_VERSION_NUMBER)
const BIGNUM* n = nullptr;
const BIGNUM* e = nullptr;
RSA* r = EVP_PKEY_get1_RSA(pkey);
RSA_get0_key(r, &n, &e, nullptr);
#elif defined(JWT_OPENSSL_1_0_0)
RSA* r = EVP_PKEY_get1_RSA(pkey);
BIGNUM* n = r->n;
BIGNUM* e = r->e;
#endif
EVP_PKEY_free(pkey);
const auto modulus =
jwt::base::trim<jwt::alphabet::base64url>(jwt::base::encode<jwt::alphabet::base64url>(jwt::helper::bn2raw(n)));
const auto exp =
jwt::base::trim<jwt::alphabet::base64url>(jwt::base::encode<jwt::alphabet::base64url>(jwt::helper::bn2raw(e)));
#if defined(JWT_OPENSSL_3_0)
BN_free(n);
BN_free(e);
#endif
std::cout << modulus << std::endl;
std::cout << exp << std::endl;
// https://stackoverflow.com/a/30138974
unsigned char nonce[24];
RAND_bytes(nonce, sizeof(nonce));
std::string jti = jwt::base::encode<jwt::alphabet::base64url>(std::string{(const char*)nonce, sizeof(nonce)});
std::string raw_jwks =
R"({"keys": [{
"kid":"internal-gateway-jwt.api.sc.net",
"alg": "RS256",
"kty": "RSA",
"use": "sig",
"x5c": [
")" +
base64_x5c + R"("
],
"n": ")" +
modulus + R"(",
"e": "AQAB"
},
{
"kid":"internal-123456",
"use":"sig",
"x5c":["MIIG1TCCBL2gAwIBAgIIFvMVGp6t\/cMwDQYJKoZIhvcNAQELBQAwZjELMAkGA1UEBhMCR0IxIDAeBgNVBAoMF1N0YW5kYXJkIENoYXJ0ZXJlZCBCYW5rMTUwMwYDVQQDDCxTdGFuZGFyZCBDaGFydGVyZWQgQmFuayBTaWduaW5nIENBIEcxIC0gU0hBMjAeFw0xODEwMTAxMTI2MzVaFw0yMjEwMTAxMTI2MzVaMIG9MQswCQYDVQQGEwJTRzESMBAGA1UECAwJU2luZ2Fwb3JlMRIwEAYDVQQHDAlTaW5nYXBvcmUxIDAeBgNVBAoMF1N0YW5kYXJkIENoYXJ0ZXJlZCBCYW5rMRwwGgYDVQQLDBNGb3VuZGF0aW9uIFNlcnZpY2VzMSgwJgYDVQQDDB9pbnRlcm5hbC1nYXRld2F5LWp3dC5hcGkuc2MubmV0MRwwGgYJKoZIhvcNAQkBFg1BUElQU1NAc2MuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArVWBoIi3IJ4nOWXu7\/SDxczqMou1B+c4c2FdQrOXrK31HxAaz4WEtma9BLXFdFHJ5mCCPIvdUcVxxnCynqhMOkZ\/a7acQbUD9cDzI8isMB9JL7VooDw0CctxHxffjqQQVIEhC2Q7zsM1pQayR7cl+pbBlvHIoRxq2n1B0fFvfoiosjf4kDiCpgHdM+v5Hw9aVYmUbroHxmQWqhB0iRTJQPPLZqqQVC50A1Q\/96gkwoODyotc46Uy9wYEpdGrtDG\/thWay3fmMsjpWR0U25xFIrxTrfCGBblYpD7juukWWml2E9rtE2rHgUxbymxXjEw7xrMwcGrhOGyqwoBqJy1JVwIDAQABo4ICLTCCAikwZAYIKwYBBQUHAQEEWDBWMFQGCCsGAQUFBzABhkhodHRwOi8vY29yZW9jc3AuZ2xvYmFsLnN0YW5kYXJkY2hhcnRlcmVkLmNvbS9lamJjYS9wdWJsaWN3ZWIvc3RhdHVzL29jc3AwHQYDVR0OBBYEFIinW4BNDeVEFcuLf8YjZjtySoW9MAwGA1UdEwEB\/wQCMAAwHwYDVR0jBBgwFoAUfNZMoZi33nKrcmVU3TFVQnuEi\/4wggFCBgNVHR8EggE5MIIBNTCCATGggcKggb+GgbxodHRwOi8vY29yZWNybC5nbG9iYWwuc3RhbmRhcmRjaGFydGVyZWQuY29tL2VqYmNhL3B1YmxpY3dlYi93ZWJkaXN0L2NlcnRkaXN0P2NtZD1jcmwmaXNzdWVyPUNOPVN0YW5kYXJkJTIwQ2hhcnRlcmVkJTIwQmFuayUyMFNpZ25pbmclMjBDQSUyMEcxJTIwLSUyMFNIQTIsTz1TdGFuZGFyZCUyMENoYXJ0ZXJlZCUyMEJhbmssQz1HQqJqpGgwZjE1MDMGA1UEAwwsU3RhbmRhcmQgQ2hhcnRlcmVkIEJhbmsgU2lnbmluZyBDQSBHMSAtIFNIQTIxIDAeBgNVBAoMF1N0YW5kYXJkIENoYXJ0ZXJlZCBCYW5rMQswCQYDVQQGEwJHQjAOBgNVHQ8BAf8EBAMCBsAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMA0GCSqGSIb3DQEBCwUAA4ICAQBtsoRlDHuOTDChcWdfdVUtRgP0U0ijDSeJi8vULN1rgYnqqJc4PdJno50aiu9MGlxY02O7HW7ZVD6QEG\/pqHmZ0sbWpb\/fumMgZSjP65IcGuS53zgcNtLYnyXyEv+v5T\/CK3bk4Li6tUW3ScJPUwVWwP1E0\/u6aBSb5k\/h4lTwS1o88ybS5pJOg6XutXByp991QQrrs7tp7fKNynjNZbFuG3J1e09X+zTfJOpjaDUofQTkt8IyMRI6Cs4wI1eZA+dAIL8B0n8ze1mRl1FOJqgdZrAQjoqZkCTnc0Il5VY\/dUXxGVg6D9e5pfck3FWT107K9\/5EZoxytpqYXFCjMXi5hx4YjK17OUgm82mZhvqkNdzF8Yq2vFuB3LPfyelESq99xFLykvinrVm1NtZKeDTT1Jq\/VvZt6stO\/tovq1RfJJcznpYcwOzxlnhGR6E+hxuBx7aDJzGf0JaoRxQILH1B2XV9WDI3HPYQsP7XtriX+QUJ\/aly28QkV48RmaGYCsly43YZu1MKudSsw+dhnbZzRsg\/aes3dzGW2x137bQPtux7k2LCSpsTXgedhOys28YoGlsoe8kUv0myAU4Stt+I3mrwO3BKUn+tJggvlDiiiyT1tg2HiklyU\/2FxQkZRMeB0eRrXTpg3l9x2mpF+dDFxOMKszxwD2kgoEZgo6o58A=="],
"n":"nr9UsxnPVd21iuiGcIJ_Qli2XVlAZe5VbELA1hO2-L4k5gI4fjHZ3ysUcautLpbOYogOQgsnlpsLrCmvNDvBDVzVp2nMbpguJlt12vHSP1fRJJpipGQ8qU-VaXsC4OjOQf3H9ojAU5Vfnl5gZ7kVCd8g4M29l-IRyNpxE-Ccxc2Y7molsCHT6GHLMMBVsd11JIOXMICJf4hz2YYkQ1t7C8SaB2RFRPuGO5Mn6mfAnwdmRera4TBz6_pIPPCgCbN8KOdJItWkr9F7Tjv_0nhh-ZVlQvbQ9PXHyKTj00g3IYUlbZIWHm0Ley__fzNZk2dyAAVjNA2QSzTZJc33MQx1pQ",
"e":"AQAB",
"x5t":"-qC0akuyiHTV5aFsKVWM9da7lzq6DLrj09I",
"alg":"RS256",
"kty":"RSA"
}
]})";
std::string token = jwt::create()
.set_issuer("auth0")
.set_type("JWT")
.set_id(jti)
.set_key_id("internal-gateway-jwt.api.sc.net")
.set_subject("jwt-cpp.example.localhost")
.set_issued_at(std::chrono::system_clock::now())
.set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{36000})
.set_payload_claim("sample", jwt::claim(std::string{"test"}))
.sign(jwt::algorithm::rs256("", pem_priv_key, "", ""));
std::cout << token << std::endl;
auto decoded_jwt = jwt::decode(token);
auto jwks = jwt::parse_jwks(raw_jwks);
auto jwk = jwks.get_jwk(decoded_jwt.get_key_id());
auto issuer = decoded_jwt.get_issuer();
auto x5c = jwk.get_x5c_key_value();
if (!x5c.empty() && !issuer.empty()) {
auto verifier =
jwt::verify()
.allow_algorithm(jwt::algorithm::rs256(jwt::helper::convert_base64_der_to_pem(x5c), "", "", ""))
.with_issuer(issuer)
.leeway(60UL); // value in seconds, add some to compensate timeout
verifier.verify(decoded_jwt);
}
// else if the optional 'x5c' was not present
{
const auto modulus = jwk.get_jwk_claim("n").as_string();
const auto exponent = jwk.get_jwk_claim("e").as_string();
auto verifier = jwt::verify()
.allow_algorithm(jwt::algorithm::rs256(
jwt::helper::create_public_key_from_rsa_components(modulus, exponent)))
.with_issuer(issuer)
.leeway(60UL); // value in seconds, add some to compensate timeout
verifier.verify(decoded_jwt);
}
} |
Can a JWK be generated from an RSA public key using this repo?
The text was updated successfully, but these errors were encountered: