Skip to content
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

replace hmac impl to use BIGNUM #38

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/jwt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ jobs:
cmake --build --preset ci-asan --target rsa-create-run
cmake --build --preset ci-asan --target rsa-verify-run
cmake --build --preset ci-asan --target jwks-verify-run
cmake --build --preset ci-asan --target es256k-run
cmake --build --preset ci-asan --target hs512-run
cmake --build --preset ci-asan --target jwt-cpp-test-run

ubsan:
Expand All @@ -92,4 +94,6 @@ jobs:
cmake --build --preset ci-ubsan --target rsa-create-run
cmake --build --preset ci-ubsan --target rsa-verify-run
cmake --build --preset ci-ubsan --target jwks-verify-run
cmake --build --preset ci-ubsan --target es256k-run
cmake --build --preset ci-ubsan --target hs512-run
cmake --build --preset ci-ubsan --target jwt-cpp-test-run
5 changes: 5 additions & 0 deletions example/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ add_custom_target(jwks-verify-run COMMAND jwks-verify)

add_executable(es256k es256k.cpp)
target_link_libraries(es256k jwt-cpp::jwt-cpp)
add_custom_target(es256k-run COMMAND es256k)

add_executable(hs512 hs512.cpp)
target_link_libraries(hs512 jwt-cpp::jwt-cpp)
add_custom_target(hs512-run COMMAND hs512)

add_executable(partial-claim-verifier partial-claim-verifier.cpp)
target_link_libraries(partial-claim-verifier jwt-cpp::jwt-cpp nlohmann_json::nlohmann_json)
60 changes: 60 additions & 0 deletions example/hs512.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/// @file hs512.cpp
#include <iostream>
#include <jwt-cpp/jwt.h>
#include <openssl/bn.h>
#include <openssl/rand.h>

BIGNUM* make_bn();

int main() {
BIGNUM* cipher = make_bn();

/* [use HMAC algo with BIGNUM] */
auto token = jwt::create()
.set_issuer("auth0")
.set_type("JWT")
.set_id("hs512-create-example")
.set_issued_now()
.set_expires_in(std::chrono::seconds{36000})
.set_payload_claim("sample", jwt::claim(std::string{"test"}))
.sign(jwt::algorithm::hs512(cipher));
/* [use HMAC algo with BIGNUM] */

std::cout << "token:\n" << token << std::endl;

auto verify = jwt::verify().allow_algorithm(jwt::algorithm::hs512(cipher)).with_issuer("auth0");

auto decoded = jwt::decode(token);

verify.verify(decoded);

for (auto& e : decoded.get_header_json())
std::cout << e.first << " = " << e.second << std::endl;
for (auto& e : decoded.get_payload_json())
std::cout << e.first << " = " << e.second << std::endl;

BN_free(cipher);
}

BIGNUM* make_bn() {
// https://stackoverflow.com/a/70790564
unsigned char n1[64];
unsigned char n2[64];

RAND_bytes(n1, sizeof n1);
RAND_bytes(n2, sizeof n2);

BIGNUM* bn1 = BN_bin2bn(n1, sizeof n1, NULL);
BIGNUM* bn2 = BN_bin2bn(n2, sizeof n2, NULL);
BIGNUM* bn3 = BN_new();

// create context
BN_CTX* ctx = BN_CTX_new();
BN_mul(bn3, bn1, bn2, ctx);
BN_CTX_free(ctx);

BN_free(bn1);
BN_free(bn2);

return bn3;
}
55 changes: 50 additions & 5 deletions include/jwt-cpp/jwt.h
Original file line number Diff line number Diff line change
Expand Up @@ -1132,13 +1132,31 @@ namespace jwt {
struct hmacsha {
/**
* Construct new hmac algorithm
*
*
* \deprecated Using a character is not recommended and hardened applications should use BIGNUM
* \param key Key to use for HMAC
* \param md Pointer to hash function
* \param name Name of the algorithm
*/
hmacsha(std::string key, const EVP_MD* (*md)(), std::string name)
: secret(std::move(key)), md(md), alg_name(std::move(name)) {}
: secret(helper::raw2bn(key).release()), md(md), alg_name(std::move(name)) {}
/**
* Construct new hmac algorithm
*
* \param key Key to use for HMAC
* \param md Pointer to hash function
* \param name Name of the algorithm
*/
hmacsha(const BIGNUM* key, const EVP_MD* (*md)(), std::string name)
: secret(BN_dup(key)), md(md), alg_name(std::move(name)) {}
hmacsha(const hmacsha& other) : secret(BN_dup(other.secret)), md(other.md), alg_name(other.alg_name) {}
hmacsha(hmacsha&& other) : secret(nullptr), md(std::move(other.md)), alg_name(std::move(other.alg_name)) {
if (BN_copy(other.secret, secret) == nullptr) throw std::runtime_error("failed to copy BN");
other.secret = nullptr;
}
~hmacsha() { BN_free(secret); }
hmacsha& operator=(const hmacsha& other)= delete;
hmacsha& operator=(hmacsha&& other) = delete;
/**
* Sign jwt data
*
Expand All @@ -1150,8 +1168,13 @@ namespace jwt {
ec.clear();
std::string res(static_cast<size_t>(EVP_MAX_MD_SIZE), '\0');
auto len = static_cast<unsigned int>(res.size());
if (HMAC(md(), secret.data(), static_cast<int>(secret.size()),
reinterpret_cast<const unsigned char*>(data.data()), static_cast<int>(data.size()),

std::vector<unsigned char> buffer(BN_num_bytes(secret), '\0');
const auto buffer_size = BN_bn2bin(secret, buffer.data());
buffer.resize(buffer_size);

if (HMAC(md(), buffer.data(), buffer_size, reinterpret_cast<const unsigned char*>(data.data()),
static_cast<int>(data.size()),
(unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast`
&len) == nullptr) {
ec = error::signature_generation_error::hmac_failed;
Expand Down Expand Up @@ -1190,7 +1213,7 @@ namespace jwt {

private:
/// HMAC secret
const std::string secret;
BIGNUM* secret;
/// HMAC hash generator
const EVP_MD* (*md)();
/// algorithm's name
Expand Down Expand Up @@ -1787,29 +1810,51 @@ namespace jwt {
struct hs256 : public hmacsha {
/**
* Construct new instance of algorithm
* \deprecated Using a character is not recommended and hardened applications should use BIGNUM
* \param key HMAC signing key
*/
explicit hs256(std::string key) : hmacsha(std::move(key), EVP_sha256, "HS256") {}
/**
* Construct new instance of algorithm
* \param key HMAC signing key
*/
explicit hs256(const BIGNUM* key) : hmacsha(key, EVP_sha256, "HS256") {}
};
/**
* HS384 algorithm
*/
struct hs384 : public hmacsha {
/**
* Construct new instance of algorithm
* \deprecated Using a character is not recommended and hardened applications should use BIGNUM
* \param key HMAC signing key
*/
explicit hs384(std::string key) : hmacsha(std::move(key), EVP_sha384, "HS384") {}
/**
* Construct new instance of algorithm
* \param key HMAC signing key
*/
explicit hs384(const BIGNUM* key) : hmacsha(key, EVP_sha384, "HS384") {}
};
/**
* HS512 algorithm
*/
struct hs512 : public hmacsha {
/**
* Construct new instance of algorithm
* \deprecated Using a character is not recommended and hardened applications should use BIGNUM
* \param key HMAC signing key
*/
explicit hs512(std::string key) : hmacsha(std::move(key), EVP_sha512, "HS512") {}
/**
* Construct new instance of algorithm
*
* This can be used to sign and verify tokens.
* \snippet{trimleft} hs512.cpp use HMAC algo with BIGNUM
*
* \param key HMAC signing key
*/
explicit hs512(const BIGNUM* key) : hmacsha(key, EVP_sha512, "HS512") {}
};
/**
* RS256 algorithm.
Expand Down
26 changes: 26 additions & 0 deletions tests/TokenTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@ TEST(TokenTest, CreateTokenHS256) {
token);
}

TEST(TokenTest, CreateTokenHS256Bytes) {
// https://stackoverflow.com/a/70790564
const char bytes[] = "1234567891234578912345678912345678912345678912345789123456789123456789";
BIGNUM *cipher = nullptr;
ASSERT_NE(0, BN_dec2bn(&cipher, bytes));
ASSERT_NE(nullptr, cipher);
auto token = jwt::create().set_issuer("auth0").set_type("JWS").sign(jwt::algorithm::hs256{cipher});
ASSERT_EQ("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.iXeab-Ef-S-JlVH5zxpqR4BIrz7DiUNH-0EljbYaf68",
token);
}

TEST(TokenTest, CreateTokenRS256) {
auto token = jwt::create().set_issuer("auth0").set_type("JWS").sign(
jwt::algorithm::rs256(rsa_pub_key, rsa_priv_key, "", ""));
Expand Down Expand Up @@ -359,6 +370,21 @@ TEST(TokenTest, VerifyTokenHS256) {
verify.verify(decoded_token);
}

TEST(TokenTest, VerifyTokenHS256Bytes) {
std::string token =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.iXeab-Ef-S-JlVH5zxpqR4BIrz7DiUNH-0EljbYaf68";


const char bytes[] = "1234567891234578912345678912345678912345678912345789123456789123456789";
BIGNUM *cipher = nullptr;
ASSERT_NE(0, BN_dec2bn(&cipher, bytes));
ASSERT_NE(nullptr, cipher);
auto verify = jwt::verify().allow_algorithm(jwt::algorithm::hs256{cipher}).with_issuer("auth0");

auto decoded_token = jwt::decode(token);
verify.verify(decoded_token);
}

TEST(TokenTest, VerifyTokenHS256Fail) {
std::string token =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
Expand Down
Loading