mirror of
https://github.com/badaix/snapcast.git
synced 2025-05-03 12:16:36 +02:00
JWT improvements
This commit is contained in:
parent
e61c862510
commit
c1bbfdf167
3 changed files with 371 additions and 92 deletions
269
common/jwt.cpp
269
common/jwt.cpp
|
@ -23,6 +23,11 @@
|
||||||
#include "common/aixlog.hpp"
|
#include "common/aixlog.hpp"
|
||||||
#include "common/base64.h"
|
#include "common/base64.h"
|
||||||
#include "common/utils/string_utils.hpp"
|
#include "common/utils/string_utils.hpp"
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <exception>
|
||||||
|
#include <openssl/x509.h>
|
||||||
|
#include <optional>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
// 3rd party headers
|
// 3rd party headers
|
||||||
|
@ -45,7 +50,7 @@ static constexpr auto LOG_TAG = "JWT";
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
EVP_PKEY* createPrivate(const std::string& key)
|
EVP_PKEY* readKey(const std::string& key)
|
||||||
{
|
{
|
||||||
// Reads PEM information and retrieves some details
|
// Reads PEM information and retrieves some details
|
||||||
BIO* keybio = BIO_new_mem_buf((void*)key.c_str(), -1);
|
BIO* keybio = BIO_new_mem_buf((void*)key.c_str(), -1);
|
||||||
|
@ -83,120 +88,230 @@ EVP_PKEY* createPrivate(const std::string& key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool Sign(const std::string& pem_key, const std::string& msg, std::vector<unsigned char>& encoded)
|
EVP_PKEY* readCert(const std::string& key)
|
||||||
{
|
{
|
||||||
auto* key = createPrivate(pem_key);
|
// Reads PEM information and retrieves some details
|
||||||
EVP_MD_CTX* ctx = EVP_MD_CTX_create();
|
BIO* keybio = BIO_new_mem_buf((void*)key.c_str(), -1);
|
||||||
if (EVP_DigestSignInit(ctx, nullptr, EVP_sha384(), nullptr, key) <= 0)
|
if (keybio == nullptr)
|
||||||
|
{
|
||||||
|
LOG(ERROR, LOG_TAG) << "BIO_new_mem_buf failed\n";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* name = nullptr;
|
||||||
|
char* header = nullptr;
|
||||||
|
uint8_t* data = nullptr;
|
||||||
|
long datalen = 0;
|
||||||
|
if (PEM_read_bio(keybio, &name, &header, &data, &datalen) == 1)
|
||||||
|
{
|
||||||
|
// Copies the data pointer. D2I functions update it
|
||||||
|
const auto* data_pkey = reinterpret_cast<const uint8_t*>(data);
|
||||||
|
// Detects type and decodes the private key
|
||||||
|
X509* x509 = d2i_X509(nullptr, &data_pkey, datalen);
|
||||||
|
EVP_PKEY* pkey = X509_get_pubkey(x509);
|
||||||
|
if (pkey == nullptr)
|
||||||
|
{
|
||||||
|
LOG(ERROR, LOG_TAG) << "d2i_AutoPrivateKey failed\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free is only required after a PEM_bio_read successful return
|
||||||
|
if (name != nullptr)
|
||||||
|
OPENSSL_free(name);
|
||||||
|
if (header != nullptr)
|
||||||
|
OPENSSL_free(header);
|
||||||
|
if (data != nullptr)
|
||||||
|
OPENSSL_free(data);
|
||||||
|
return pkey;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sign(const std::string& pem_key, const std::string& msg, std::vector<unsigned char>& encoded)
|
||||||
|
{
|
||||||
|
auto* key = readKey(pem_key);
|
||||||
|
std::shared_ptr<EVP_MD_CTX> ctx(EVP_MD_CTX_create(), [](auto p) { EVP_MD_CTX_free(p); });
|
||||||
|
|
||||||
|
if (EVP_DigestSignInit(ctx.get(), nullptr, EVP_sha256(), nullptr, key) <= 0)
|
||||||
{
|
{
|
||||||
LOG(ERROR, LOG_TAG) << "EVP_DigestSignInit failed\n";
|
LOG(ERROR, LOG_TAG) << "EVP_DigestSignInit failed\n";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (EVP_DigestSignUpdate(ctx, msg.c_str(), msg.size()) <= 0)
|
if (EVP_DigestSignUpdate(ctx.get(), msg.c_str(), msg.size()) <= 0)
|
||||||
{
|
{
|
||||||
LOG(ERROR, LOG_TAG) << "EVP_DigestSignUpdate failed\n";
|
LOG(ERROR, LOG_TAG) << "EVP_DigestSignUpdate failed\n";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
size_t siglen;
|
size_t siglen;
|
||||||
if (EVP_DigestSignFinal(ctx, nullptr, &siglen) <= 0)
|
if (EVP_DigestSignFinal(ctx.get(), nullptr, &siglen) <= 0)
|
||||||
{
|
{
|
||||||
LOG(ERROR, LOG_TAG) << "EVP_DigestSignFinal failed\n";
|
LOG(ERROR, LOG_TAG) << "EVP_DigestSignFinal failed\n";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
encoded.resize(siglen);
|
encoded.resize(siglen);
|
||||||
if (EVP_DigestSignFinal(ctx, encoded.data(), &siglen) <= 0)
|
if (EVP_DigestSignFinal(ctx.get(), encoded.data(), &siglen) <= 0)
|
||||||
{
|
{
|
||||||
LOG(ERROR, LOG_TAG) << "EVP_DigestSignFinal failed\n";
|
LOG(ERROR, LOG_TAG) << "EVP_DigestSignFinal failed\n";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
EVP_MD_CTX_free(ctx);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool verifySignature(const std::string& pem_cert, unsigned char* MsgHash, size_t MsgHashLen, const char* Msg, size_t MsgLen, bool& Authentic)
|
||||||
|
{
|
||||||
|
Authentic = false;
|
||||||
|
auto* key = readCert(pem_cert);
|
||||||
|
EVP_MD_CTX* ctx = EVP_MD_CTX_create();
|
||||||
|
|
||||||
|
if (EVP_DigestVerifyInit(ctx, nullptr, EVP_sha256(), nullptr, key) <= 0)
|
||||||
|
{
|
||||||
|
LOG(ERROR, LOG_TAG) << "EVP_DigestVerifyInit failed\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (EVP_DigestVerifyUpdate(ctx, Msg, MsgLen) <= 0)
|
||||||
|
{
|
||||||
|
LOG(ERROR, LOG_TAG) << "EVP_DigestVerifyInit failed\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int authStatus = EVP_DigestVerifyFinal(ctx, MsgHash, MsgHashLen);
|
||||||
|
EVP_MD_CTX_free(ctx);
|
||||||
|
if (authStatus == 1)
|
||||||
|
{
|
||||||
|
Authentic = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (authStatus == 0)
|
||||||
|
{
|
||||||
|
Authentic = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
LOG(ERROR, LOG_TAG) << "EVP_DigestVerifyFinal failed: " << authStatus << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Jwt::Jwt()
|
Jwt::Jwt() : claims({})
|
||||||
{
|
{
|
||||||
// {
|
|
||||||
// "alg": "RS256",
|
|
||||||
// "typ": "JWT"
|
|
||||||
// }
|
|
||||||
json header = {{"alg", "RS384"}, {"typ", "JWT"}};
|
|
||||||
|
|
||||||
// {
|
|
||||||
// "sub": "1234567890",
|
|
||||||
// "name": "Johannes",
|
|
||||||
// "admin": true,
|
|
||||||
// "iat": 1516239022
|
|
||||||
// }
|
|
||||||
json payload = {{"sub", "1234567890"}, {"name", "Johannes"}, {"admin", true}, {"iat", 1516239022}};
|
|
||||||
|
|
||||||
LOG(INFO, LOG_TAG) << "Header: " << header << "\n";
|
|
||||||
LOG(INFO, LOG_TAG) << "Payload: " << payload << "\n";
|
|
||||||
std::string msg = base64url_encode(header.dump()) + "." + base64url_encode(payload.dump());
|
|
||||||
LOG(INFO, LOG_TAG) << "Encoded: " << msg << "\n";
|
|
||||||
|
|
||||||
auto key = "-----BEGIN PRIVATE KEY-----\n"
|
|
||||||
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwFxHHKvV6THj1\n"
|
|
||||||
"VjvZQJIW+OjIKF9MPfIN8wJTXn+4EkBJCoBy0NfKHG+FCb90YCPzvLrjL8jKcfhT\n"
|
|
||||||
"/jpaStrYYjABXA1sJS9P+9cwiV9RwTxTrfOiss8F7eZdyZI9UogrPmQa7YbevmwB\n"
|
|
||||||
"XeXIqKzdWQbtQsOJQgoL3vIR7qjUpVQrXPwAYQ6pc7AxS5RHFy8V2Y3sh/+i0aS9\n"
|
|
||||||
"bH/276OAKZNwVIfUIcSVVbBAPvrheR2ezUVoKSumUzek/2uq3cLb5YNF4XSe1Ikv\n"
|
|
||||||
"16lRSGTIQ2KflSYn3mJldFjEKL3sgADTmMhKes+TVveNr7eCvynyDCtHdiLanOKY\n"
|
|
||||||
"PyyOXFTdAgMBAAECggEAAjunU6UBZsBhrUzKEVZ5XnYKxP+xZlmyat0Iy8QFmcY4\n"
|
|
||||||
"z076iNo0o6u/efVWb/RIfcQILlmHXJKG7BEWg4Qc/oRPkwjW47xHULvtw0AOt85G\n"
|
|
||||||
"mfy5UPgfBMvQRs1c/87piqYt0KNFTqhQCCa9GKcTmsf7p5ZtPTLw8Sxt2e6H8LsK\n"
|
|
||||||
"60Jzc2Yw2t3unEb1NnjsTgshjPwFcdrppyRAa2B0Wk3f4ADA1i4vDmTt2+jTq/Hp\n"
|
|
||||||
"yFWup58Djs+lyn4RLnp7jFD2KS/q+qVQsTfGcPeXLMIWHHQDwfjfkzdA74zNi+Pn\n"
|
|
||||||
"C4e/iXIsC7VH4BJ8qrVH20WqTXRyuZ1uEF+32XbcSQKBgQDAtBXbnuGhu2llnfP2\n"
|
|
||||||
"dxJ5qtRjxTHedZs9UMEuy5pkLMi4JsIdjf072lqizpG7DE9kfiERwXJ/Spe4OMMh\n"
|
|
||||||
"MvWBpnJieTHnouAMpDVootVbSCpikOSGzClHZwpl6KU7pv9Q+Hv2xkSnTJLsakSt\n"
|
|
||||||
"qlOsG6cwK56kXiwYG0RsAn6lhQKBgQDp7gNE4J6wf9SZHErpyh+65VqqcF5+chNq\n"
|
|
||||||
"DTwFlb7U31WcDOA3hUHaQfrlwfblMAgnlVMofopdP4KIIgSWE31cCziBp8ZRy/25\n"
|
|
||||||
"2/aNkDoPEN59Ibk1RWLYsCzcQIAQrTjvfDMn3An1E9B3qYFzfdLKZItb8p3cxpO/\n"
|
|
||||||
"sMUwQRqFeQKBgDb7av0lwQUPXwwiXDhnUvsp9b2dxxPNBIUjJGuApkWMzZxVWq9q\n"
|
|
||||||
"EuXf8FphjA0Nfx2SK0dQpaWSF+X1NB+l1YyvfBWCtO19eGXC+IYpZ6zK02UaKEoZ\n"
|
|
||||||
"uHFqAfp/vZ1ekZx9uYj4myAM5iLUU1Iltgf2P+arm3EUeYpLRWN39sCtAoGBALZ0\n"
|
|
||||||
"egBC4gLv8TXqp1Np3w26zdiaBFnDR/kzkVkZztnhx7gLIuaq/Q3q4HJLsvJXYETf\n"
|
|
||||||
"ZxjyeaD5ZCohvkn/sYsVBWG7JieuX5uTQN5xW5dcpOwcXYR7Nfmkj5jKhhh7wyin\n"
|
|
||||||
"So8QRIPujG6IuvsFbF+HxFpXBWGpUJv2mBZm8PShAoGBALU/KNl1El0HpxTr3wgj\n"
|
|
||||||
"YY6eU3snn0Oa5Ci3k/FMWd1QTtVsf4Mym0demxshdC75L+qzCS0m5jAo9tm0keY9\n"
|
|
||||||
"A4F3VRlsuUyuAja+qDU2xMo3jnFKOIyMfN4mVSiFkqnq3eQ4xHgViyIEyr+8AbA4\n"
|
|
||||||
"ajjiCZsv+OITxQ+TTHeGDsdD\n"
|
|
||||||
"-----END PRIVATE KEY-----\n";
|
|
||||||
|
|
||||||
std::vector<unsigned char> encoded;
|
|
||||||
if (Sign(key, msg, encoded))
|
|
||||||
{
|
|
||||||
std::string signature = base64url_encode(encoded.data(), encoded.size());
|
|
||||||
LOG(INFO, LOG_TAG) << "Signature: " << signature << "\n";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOG(ERROR, LOG_TAG) << "Failed to sign\n";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<std::chrono::seconds> Jwt::getIat() const
|
||||||
|
{
|
||||||
|
if (!claims.contains("iat"))
|
||||||
|
return std::nullopt;
|
||||||
|
return std::chrono::seconds(claims.at("iat").get<int64_t>());
|
||||||
|
}
|
||||||
|
|
||||||
bool Jwt::decode(const std::string& token)
|
void Jwt::setIat(const std::optional<std::chrono::seconds>& iat)
|
||||||
|
{
|
||||||
|
if (iat.has_value())
|
||||||
|
claims["iat"] = iat->count();
|
||||||
|
else if (claims.contains("iat"))
|
||||||
|
claims.erase("iat");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::chrono::seconds> Jwt::getExp() const
|
||||||
|
{
|
||||||
|
if (!claims.contains("exp"))
|
||||||
|
return std::nullopt;
|
||||||
|
return std::chrono::seconds(claims.at("exp").get<int64_t>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Jwt::setExp(const std::optional<std::chrono::seconds>& exp)
|
||||||
|
{
|
||||||
|
if (exp.has_value())
|
||||||
|
claims["exp"] = exp->count();
|
||||||
|
else if (claims.contains("exp"))
|
||||||
|
claims.erase("exp");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> Jwt::getSub() const
|
||||||
|
{
|
||||||
|
if (!claims.contains("sub"))
|
||||||
|
return std::nullopt;
|
||||||
|
return claims.at("sub").get<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Jwt::setSub(const std::optional<std::string>& sub)
|
||||||
|
{
|
||||||
|
if (sub.has_value())
|
||||||
|
claims["sub"] = sub.value();
|
||||||
|
else if (claims.contains("sub"))
|
||||||
|
claims.erase("sub");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Jwt::parse(const std::string& token, const std::string& pem_cert)
|
||||||
{
|
{
|
||||||
std::vector<std::string> parts = utils::string::split(token, '.');
|
std::vector<std::string> parts = utils::string::split(token, '.');
|
||||||
if (parts.size() != 3)
|
if (parts.size() != 3)
|
||||||
{
|
{
|
||||||
|
LOG(ERROR, LOG_TAG) << "Token '" << token << "' not in the format <header>.<payload>.<signature>\n";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG(INFO, LOG_TAG) << "Header: " << base64url_decode(parts[0]) << "\n";
|
std::string header = base64url_decode(parts[0]);
|
||||||
LOG(INFO, LOG_TAG) << "Payload: " << base64url_decode(parts[1]) << "\n";
|
std::string payload = base64url_decode(parts[1]);
|
||||||
|
LOG(DEBUG, LOG_TAG) << "Header: " << header << ", payload: " << payload << "\n";
|
||||||
|
|
||||||
header_ = json::parse(base64url_decode(parts[0]));
|
try
|
||||||
payload_ = json::parse(base64url_decode(parts[1]));
|
{
|
||||||
|
json jheader = json::parse(header);
|
||||||
|
claims = json::parse(payload);
|
||||||
std::string signature = parts[2];
|
std::string signature = parts[2];
|
||||||
|
LOG(INFO, LOG_TAG) << "Header: " << jheader << "\n";
|
||||||
LOG(INFO, LOG_TAG) << "Header: " << header_ << "\n";
|
LOG(INFO, LOG_TAG) << "Payload: " << claims << "\n";
|
||||||
LOG(INFO, LOG_TAG) << "Payload: " << payload_ << "\n";
|
|
||||||
LOG(INFO, LOG_TAG) << "Signature: " << signature << "\n";
|
LOG(INFO, LOG_TAG) << "Signature: " << signature << "\n";
|
||||||
|
auto binary = base64url_decode(signature);
|
||||||
|
std::string msg = parts[0] + "." + parts[1];
|
||||||
|
bool auth;
|
||||||
|
if (!verifySignature(pem_cert, reinterpret_cast<unsigned char*>(binary.data()), binary.size(), msg.c_str(), msg.size(), auth))
|
||||||
|
{
|
||||||
|
LOG(ERROR, LOG_TAG) << "Failed to verify signature\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!auth)
|
||||||
|
{
|
||||||
|
LOG(ERROR, LOG_TAG) << "Wrong signature\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
|
LOG(ERROR, LOG_TAG) << "Error parsing JWT header or payload: " << e.what() << "\n";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::optional<std::string> Jwt::getToken(const std::string& pem_key) const
|
||||||
|
{
|
||||||
|
json header = {{"typ", "JWT"}};
|
||||||
|
if (pem_key.find("-----BEGIN PRIVATE KEY-----") == 0)
|
||||||
|
header["alg"] = "RS256";
|
||||||
|
// if (pem_key.find("-----BEGIN EC PRIVATE KEY-----") == 0)
|
||||||
|
// header["alg"] = "ES256";
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG(ERROR, LOG_TAG) << "PEM key must be an RSA key\n";
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(DEBUG, LOG_TAG) << "Header: " << header << ", payload: " << claims << "\n";
|
||||||
|
std::string msg = base64url_encode(header.dump()) + "." + base64url_encode(claims.dump());
|
||||||
|
LOG(DEBUG, LOG_TAG) << "Encoded: " << msg << "\n";
|
||||||
|
|
||||||
|
std::vector<unsigned char> encoded;
|
||||||
|
if (sign(pem_key, msg, encoded))
|
||||||
|
{
|
||||||
|
std::string signature = base64url_encode(encoded.data(), encoded.size());
|
||||||
|
LOG(DEBUG, LOG_TAG) << "Signature: " << signature << "\n";
|
||||||
|
auto token = msg + "." + signature;
|
||||||
|
LOG(DEBUG, LOG_TAG) << "Token: " << token << "\n";
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(ERROR, LOG_TAG) << "Failed to sign token\n";
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
#include "common/json.hpp"
|
#include "common/json.hpp"
|
||||||
|
|
||||||
// standard headers
|
// standard headers
|
||||||
|
#include <chrono>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,19 +55,63 @@ https://datatracker.ietf.org/doc/html/rfc7518#section-3
|
||||||
| none | No digital signature or MAC | Optional |
|
| none | No digital signature or MAC | Optional |
|
||||||
| | performed | |
|
| | performed | |
|
||||||
+--------------+-------------------------------+--------------------+
|
+--------------+-------------------------------+--------------------+
|
||||||
*/
|
|
||||||
|
|
||||||
|
https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-token-claims
|
||||||
|
|
||||||
|
Registered claims
|
||||||
|
The JWT specification defines seven reserved claims that are not required, but are recommended to allow interoperability with third-party applications. These
|
||||||
|
are:
|
||||||
|
- iss (issuer): Issuer of the JWT
|
||||||
|
- sub (subject): Subject of the JWT (the user)
|
||||||
|
- aud (audience): Recipient for which the JWT is intended
|
||||||
|
- exp (expiration time): Time after which the JWT expires
|
||||||
|
- nbf (not before time): Time before which the JWT must not be accepted for processing
|
||||||
|
- iat (issued at time): Time at which the JWT was issued; can be used to determine age of the JWT
|
||||||
|
- jti (JWT ID): Unique identifier; can be used to prevent the JWT from being replayed (allows a token to be used only once)
|
||||||
|
|
||||||
|
|
||||||
// https://techdocs.akamai.com/iot-token-access-control/docs/generate-jwt-rsa-keys
|
// https://techdocs.akamai.com/iot-token-access-control/docs/generate-jwt-rsa-keys
|
||||||
|
*/
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
|
||||||
|
/// Json Web Token in RS256 format
|
||||||
class Jwt
|
class Jwt
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/// c'tor
|
||||||
Jwt();
|
Jwt();
|
||||||
|
|
||||||
bool decode(const std::string& token);
|
/// Parse an base64 url encoded token of the form "<header>.<payload>.<signature>"
|
||||||
|
/// @param token The token
|
||||||
|
/// @param pem_cert Certificate in PEM format to verify the signature
|
||||||
|
/// @return true on success, else false
|
||||||
|
bool parse(const std::string& token, const std::string& pem_cert);
|
||||||
|
/// Create an base64 url encoded token of the form "<header>.<payload>.<signature>"
|
||||||
|
/// @param pem_key Private key in PEM format to sign the token
|
||||||
|
/// @return the token or nullopt if failed
|
||||||
|
std::optional<std::string> getToken(const std::string& pem_key) const;
|
||||||
|
|
||||||
private:
|
/// Get the iat "Issued at time" claim
|
||||||
json header_;
|
/// @return the claim or nullopt, if not present
|
||||||
json payload_;
|
std::optional<std::chrono::seconds> getIat() const;
|
||||||
|
/// Set the iat "Issued at time" claim, use nullopt to delete the iat
|
||||||
|
void setIat(const std::optional<std::chrono::seconds>& iat);
|
||||||
|
|
||||||
|
/// Get the exp "Expiration time" claim
|
||||||
|
/// @return the claim or nullopt, if not present
|
||||||
|
std::optional<std::chrono::seconds> getExp() const;
|
||||||
|
/// Set the exp "Expiration time" claim, use nullopt to delete the exp
|
||||||
|
void setExp(const std::optional<std::chrono::seconds>& exp);
|
||||||
|
|
||||||
|
/// Get the sub "Subject" claim
|
||||||
|
/// @return the claim or nullopt, if not present
|
||||||
|
std::optional<std::string> getSub() const;
|
||||||
|
/// Set the sub "Subject" claim, use nullopt to delete the sub
|
||||||
|
void setSub(const std::optional<std::string>& sub);
|
||||||
|
|
||||||
|
/// The token's raw payload (claims) in json format
|
||||||
|
json claims;
|
||||||
};
|
};
|
||||||
|
|
|
@ -47,14 +47,132 @@ TEST_CASE("String utils")
|
||||||
|
|
||||||
TEST_CASE("JWT")
|
TEST_CASE("JWT")
|
||||||
{
|
{
|
||||||
AixLog::Log::init<AixLog::SinkCout>(AixLog::Severity::debug);
|
#if 0
|
||||||
Jwt jwt;
|
/// ECDSA
|
||||||
|
{
|
||||||
|
const auto* key = "-----BEGIN EC PRIVATE KEY-----\n"
|
||||||
|
"MIHcAgEBBEIAarXYdFyTZIM2NGgbjPj8UBlBRYjDKrEfHIDuP1sYGLCJDqqx/GxH\n"
|
||||||
|
"DpP8mmpTL2dII8cZSbW7zqRv43ZcwK2dq3OgBwYFK4EEACOhgYkDgYYABADPIxdE\n"
|
||||||
|
"ubzaxsCtZAhbRnG3SJMZoD/0+sFO+r/5m9o4PPAz7cBnSEG3hBThsa+uBE+rfeah\n"
|
||||||
|
"RagSvgYzRyn85/N0YQBsE6V3htaOBrmoW4PBo/Lg0GwXe5qz7Fp98DuwpDC3OtXa\n"
|
||||||
|
"43Mgl0rbcwtQ6e0xcAxkLJ0tDfpomiM9Lj5gM+WT1g==\n"
|
||||||
|
"-----END EC PRIVATE KEY-----\n";
|
||||||
|
|
||||||
jwt.decode(
|
const auto* cert = "-----BEGIN CERTIFICATE-----\n"
|
||||||
"eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwiaWF0IjoxNTE2MjM5MDIyLCJuYW1lIjoiSm9oYW5uZXMiLCJzdWIiOiIxMjM0NTY3ODkwIn0."
|
"MIICVjCCAbigAwIBAgIUBrGbikLGcul0Cia5tBCsPDm7/P4wCgYIKoZIzj0EAwIw\n"
|
||||||
"QooKYcDKXwds9GF6Qgs5qTgXZyjPIf9OD_eBYIako7EoC18dqkvPgpwEar_Npck5wSGDPxcMCm9VxRHwv_LP4yelncf08BMu0JRK7vSqKQ8GGC2YoRILsOXD4nIf2mDUJ4CVo5fCbKuhwxE_"
|
"PTELMAkGA1UEBhMCREUxDDAKBgNVBAgMA05SVzEPMA0GA1UEBwwGQWFjaGVuMQ8w\n"
|
||||||
"lBdiQXFGF6NaQ-02LYTnoRub2x1wtHrQam5eYTaNPjaY2ANvSpRK8CCA6jWd6P5qqgedcPtE4J2HLDFR2GefFjhOYYaZP-LMhdbHDEoThk05-bQVyXXXg-7RCqCfzE_"
|
"DQYDVQQKDAZCYWRBaXgwHhcNMjQwNjAzMTk0NzEzWhcNMjUwNjAzMTk0NzEzWjA9\n"
|
||||||
"H5fx4w8t6JYudM8PsRoK3kE8vGsR7tHRGi0CBJqTMTxIYnjaQPQUd8OYFbux3TxEV0Kr9kQ");
|
"MQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQ8wDQYDVQQHDAZBYWNoZW4xDzAN\n"
|
||||||
|
"BgNVBAoMBkJhZEFpeDCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAM8jF0S5vNrG\n"
|
||||||
|
"wK1kCFtGcbdIkxmgP/T6wU76v/mb2jg88DPtwGdIQbeEFOGxr64ET6t95qFFqBK+\n"
|
||||||
|
"BjNHKfzn83RhAGwTpXeG1o4Guahbg8Gj8uDQbBd7mrPsWn3wO7CkMLc61drjcyCX\n"
|
||||||
|
"SttzC1Dp7TFwDGQsnS0N+miaIz0uPmAz5ZPWo1MwUTAdBgNVHQ4EFgQUy+1q8Nh5\n"
|
||||||
|
"qbrckn9hdMT7WXhha2gwHwYDVR0jBBgwFoAUy+1q8Nh5qbrckn9hdMT7WXhha2gw\n"
|
||||||
|
"DwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgOBiwAwgYcCQgCYarPzdeupTvEs\n"
|
||||||
|
"CC5OlWj9pzUJDY4R0kaEe94g+KwTsgyaY1OzhHKwmu4E2ggrU0BN/HTpRYrYhtFz\n"
|
||||||
|
"HnDpYIN5XAJBK9fgry78uAiN4RZHPfWBKJ/NMcCZO/dXLbxoFlh2+6FXD/TmABHW\n"
|
||||||
|
"XsA/SmQzK2kJOKkBu9jBwNPiFZUQrJJ5msE=\n"
|
||||||
|
"-----END CERTIFICATE-----\n";
|
||||||
|
|
||||||
|
Jwt jwt;
|
||||||
|
jwt.setIat(std::chrono::seconds(1516239022));
|
||||||
|
jwt.setSub("Badaix");
|
||||||
|
std::optional<std::string> token = jwt.getToken(key);
|
||||||
|
REQUIRE(token.has_value());
|
||||||
|
LOG(INFO, "TEST") << "Token: " << *token << "\n";
|
||||||
|
REQUIRE(token.value() == "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MTYyMzkwMjIsInN1YiI6IkJhZGFpeCJ9.MIGHAkEDxp53c--"
|
||||||
|
"MwMQAiTR5pnXlCunmqruZ8U6owKIfm06zgcAVswmTAhXFMIF28X9Yu2_F0o2LjgSWfBP9NEKg6BXy-gJCAevKxaKwxv1bbPOf7kGOeBzESuq8h_"
|
||||||
|
"pCJwGEA1WW9tflQgpsU52oBmIA-6fAHvl_EVPh4KIXoTQ71BRfacDMDzG2AA");
|
||||||
|
|
||||||
|
REQUIRE(jwt.parse(token.value(), cert));
|
||||||
|
REQUIRE(jwt.getSub().has_value());
|
||||||
|
REQUIRE(jwt.getSub().value() == "Badaix");
|
||||||
|
REQUIRE(jwt.getIat().has_value());
|
||||||
|
REQUIRE(jwt.getIat().value() == std::chrono::seconds(1516239022));
|
||||||
|
REQUIRE(!jwt.getExp().has_value());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// RSA keys
|
||||||
|
{
|
||||||
|
AixLog::Log::init<AixLog::SinkCout>(AixLog::Severity::debug);
|
||||||
|
const auto* key = "-----BEGIN PRIVATE KEY-----\n"
|
||||||
|
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwFxHHKvV6THj1\n"
|
||||||
|
"VjvZQJIW+OjIKF9MPfIN8wJTXn+4EkBJCoBy0NfKHG+FCb90YCPzvLrjL8jKcfhT\n"
|
||||||
|
"/jpaStrYYjABXA1sJS9P+9cwiV9RwTxTrfOiss8F7eZdyZI9UogrPmQa7YbevmwB\n"
|
||||||
|
"XeXIqKzdWQbtQsOJQgoL3vIR7qjUpVQrXPwAYQ6pc7AxS5RHFy8V2Y3sh/+i0aS9\n"
|
||||||
|
"bH/276OAKZNwVIfUIcSVVbBAPvrheR2ezUVoKSumUzek/2uq3cLb5YNF4XSe1Ikv\n"
|
||||||
|
"16lRSGTIQ2KflSYn3mJldFjEKL3sgADTmMhKes+TVveNr7eCvynyDCtHdiLanOKY\n"
|
||||||
|
"PyyOXFTdAgMBAAECggEAAjunU6UBZsBhrUzKEVZ5XnYKxP+xZlmyat0Iy8QFmcY4\n"
|
||||||
|
"z076iNo0o6u/efVWb/RIfcQILlmHXJKG7BEWg4Qc/oRPkwjW47xHULvtw0AOt85G\n"
|
||||||
|
"mfy5UPgfBMvQRs1c/87piqYt0KNFTqhQCCa9GKcTmsf7p5ZtPTLw8Sxt2e6H8LsK\n"
|
||||||
|
"60Jzc2Yw2t3unEb1NnjsTgshjPwFcdrppyRAa2B0Wk3f4ADA1i4vDmTt2+jTq/Hp\n"
|
||||||
|
"yFWup58Djs+lyn4RLnp7jFD2KS/q+qVQsTfGcPeXLMIWHHQDwfjfkzdA74zNi+Pn\n"
|
||||||
|
"C4e/iXIsC7VH4BJ8qrVH20WqTXRyuZ1uEF+32XbcSQKBgQDAtBXbnuGhu2llnfP2\n"
|
||||||
|
"dxJ5qtRjxTHedZs9UMEuy5pkLMi4JsIdjf072lqizpG7DE9kfiERwXJ/Spe4OMMh\n"
|
||||||
|
"MvWBpnJieTHnouAMpDVootVbSCpikOSGzClHZwpl6KU7pv9Q+Hv2xkSnTJLsakSt\n"
|
||||||
|
"qlOsG6cwK56kXiwYG0RsAn6lhQKBgQDp7gNE4J6wf9SZHErpyh+65VqqcF5+chNq\n"
|
||||||
|
"DTwFlb7U31WcDOA3hUHaQfrlwfblMAgnlVMofopdP4KIIgSWE31cCziBp8ZRy/25\n"
|
||||||
|
"2/aNkDoPEN59Ibk1RWLYsCzcQIAQrTjvfDMn3An1E9B3qYFzfdLKZItb8p3cxpO/\n"
|
||||||
|
"sMUwQRqFeQKBgDb7av0lwQUPXwwiXDhnUvsp9b2dxxPNBIUjJGuApkWMzZxVWq9q\n"
|
||||||
|
"EuXf8FphjA0Nfx2SK0dQpaWSF+X1NB+l1YyvfBWCtO19eGXC+IYpZ6zK02UaKEoZ\n"
|
||||||
|
"uHFqAfp/vZ1ekZx9uYj4myAM5iLUU1Iltgf2P+arm3EUeYpLRWN39sCtAoGBALZ0\n"
|
||||||
|
"egBC4gLv8TXqp1Np3w26zdiaBFnDR/kzkVkZztnhx7gLIuaq/Q3q4HJLsvJXYETf\n"
|
||||||
|
"ZxjyeaD5ZCohvkn/sYsVBWG7JieuX5uTQN5xW5dcpOwcXYR7Nfmkj5jKhhh7wyin\n"
|
||||||
|
"So8QRIPujG6IuvsFbF+HxFpXBWGpUJv2mBZm8PShAoGBALU/KNl1El0HpxTr3wgj\n"
|
||||||
|
"YY6eU3snn0Oa5Ci3k/FMWd1QTtVsf4Mym0demxshdC75L+qzCS0m5jAo9tm0keY9\n"
|
||||||
|
"A4F3VRlsuUyuAja+qDU2xMo3jnFKOIyMfN4mVSiFkqnq3eQ4xHgViyIEyr+8AbA4\n"
|
||||||
|
"ajjiCZsv+OITxQ+TTHeGDsdD\n"
|
||||||
|
"-----END PRIVATE KEY-----\n";
|
||||||
|
|
||||||
|
const auto* cert = "-----BEGIN CERTIFICATE-----\n"
|
||||||
|
"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\n"
|
||||||
|
"Iw==\n"
|
||||||
|
"-----END CERTIFICATE-----\n";
|
||||||
|
|
||||||
|
Jwt jwt;
|
||||||
|
jwt.setIat(std::chrono::seconds(1516239022));
|
||||||
|
jwt.setSub("Badaix");
|
||||||
|
std::optional<std::string> token = jwt.getToken(key);
|
||||||
|
REQUIRE(token.has_value());
|
||||||
|
REQUIRE(token.value() ==
|
||||||
|
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MTYyMzkwMjIsInN1YiI6IkJhZGFpeCJ9.LtKDGnT2OSgvWLECReajyMmUv7ApJeRu83MZhDM7d_"
|
||||||
|
"1t1zy2Z08BQZEEB58WzR1vZAtRGHDVrYytJaVCPzibQN4eZ1F4m0gDk83hxQPTAKsbwjtzi7pUJzvaBa1ni4ysc9POtoi_M1OtNk5xxziyk5VP1Ph-"
|
||||||
|
"TQbm9BCPfpA8bSUCx0LFrm5gyCD3irkww_W3RwDc2ghrjDCRNCyu4R9__lrCRGdx3Z8i0YMB_obuShcYzJXFSxG8adTSs3PQ_R4NXR94-vydVrvBxqe79apocFVrs_"
|
||||||
|
"c9Ub8TIFynzqp9L_s206nb2N3C1WfUkKeQ1E7gAgVq8b4SM0OZsmkERQ0P0w");
|
||||||
|
|
||||||
|
REQUIRE(jwt.parse(token.value(), cert));
|
||||||
|
REQUIRE(jwt.getSub().has_value());
|
||||||
|
REQUIRE(jwt.getSub().value() == "Badaix");
|
||||||
|
REQUIRE(jwt.getIat().has_value());
|
||||||
|
REQUIRE(jwt.getIat().value() == std::chrono::seconds(1516239022));
|
||||||
|
REQUIRE(!jwt.getExp().has_value());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue