Skip to content

Commit

Permalink
Added support for PS256, PS384 and PS512
Browse files Browse the repository at this point in the history
Added support for RSA-PSS signatures
closes #2
  • Loading branch information
Thalhammer committed Oct 7, 2018
1 parent df0509d commit 1357588
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 9 deletions.
67 changes: 59 additions & 8 deletions TokenTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,8 @@ TEST(TokenTest, CreateTokenPS256) {
.set_type("JWS")
.sign(jwt::algorithm::ps256(rsa_pub_key, rsa_priv_key, "", ""));

ASSERT_EQ(
"eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.yrqybvOkt6z2REIrSVIYoTA-sg-SOW1ZdMM0HksnIBy"
"wk9535ugb_nLfCld2r30RjZzkuT875FmFOsjvvUL6WcZq5X0D8y81mmoe7cJ7g-ZVEtEf5HkK8ITGXwaBkPmOJgv-dxUl1KAi9mzz"
"OmjOsbwYU_rxJv-kcnmUYimtvv8", token);
// TODO: Find a better way to check if generated signature is valid
// Can't do simple check for equal since pss adds random salt.
}

TEST(TokenTest, CreateTokenPS384) {
Expand All @@ -93,10 +91,18 @@ TEST(TokenTest, CreateTokenPS384) {
.set_type("JWS")
.sign(jwt::algorithm::ps384(rsa_pub_key, rsa_priv_key, "", ""));

ASSERT_EQ(
"eyJhbGciOiJQUzM4NCIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.lGXMcgseUWc_U5F7A5HI-EymAz9WYJbM4-OHjO0rzz4"
"1oevoFkQu4HfJrEWfrnxXkYjpkbmgWWYakISNVK2EYvPCQqMKEpzUsVCnQjTWF0uowABrCXOjzCprK1_Wtkwmnacbko7L4y68BOlR"
"zQFYMnQS7EviYV1LF6fWNP5cRk4", token);
// TODO: Find a better way to check if generated signature is valid
// Can't do simple check for equal since pss adds random salt.
}

TEST(TokenTest, CreateTokenPS512) {
auto token = jwt::create()
.set_issuer("auth0")
.set_type("JWS")
.sign(jwt::algorithm::ps512(rsa_pub_key, rsa_priv_key, "", ""));

// TODO: Find a better way to check if generated signature is valid
// Can't do simple check for equal since pss adds random salt.
}

TEST(TokenTest, CreateTokenES256) {
Expand Down Expand Up @@ -266,6 +272,51 @@ TEST(TokenTest, VerifyTokenES256Fail) {
ASSERT_THROW(verify.verify(decoded_token), jwt::signature_verification_exception);
}

TEST(TokenTest, VerifyTokenPS256) {
std::string token = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.CJ4XjVWdbV6vXGZkD4GdJbtYc80SN9cmPOqRhZBRzOyDRqTFE"
"4MsbdKyQuhAWcvuMOjn-24qOTjVMR_P_uTC1uG6WPLcucxZyLnbb56zbKnEklW2SX0mQnCGewr-93a_vDaFT6Cp45MsF_OwFPRCMaS5CJg-"
"N5KY67UrVSr3s9nkuK9ZTQkyODHfyEUh9F_FhRCATGrb5G7_qHqBYvTvaPUXqzhhpCjN855Tocg7A24Hl0yMwM-XdasucW5xNdKjG_YCkis"
"HX7ax--JiF5GNYCO61eLFteO4THUg-3Z0r4OlGqlppyWo5X5tjcxOZCvBh7WDWfkxA48KFZPRv0nlKA";

auto verify = jwt::verify()
.allow_algorithm(jwt::algorithm::ps256(rsa_pub_key, rsa_priv_key, "", ""))
.with_issuer("auth0");

auto decoded_token = jwt::decode(token);

verify.verify(decoded_token);
}

TEST(TokenTest, VerifyTokenPS256PublicOnly) {
std::string token = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.CJ4XjVWdbV6vXGZkD4GdJbtYc80SN9cmPOqRhZBRzOyDRqTFE"
"4MsbdKyQuhAWcvuMOjn-24qOTjVMR_P_uTC1uG6WPLcucxZyLnbb56zbKnEklW2SX0mQnCGewr-93a_vDaFT6Cp45MsF_OwFPRCMaS5CJg-"
"N5KY67UrVSr3s9nkuK9ZTQkyODHfyEUh9F_FhRCATGrb5G7_qHqBYvTvaPUXqzhhpCjN855Tocg7A24Hl0yMwM-XdasucW5xNdKjG_YCkis"
"HX7ax--JiF5GNYCO61eLFteO4THUg-3Z0r4OlGqlppyWo5X5tjcxOZCvBh7WDWfkxA48KFZPRv0nlKA";

auto verify = jwt::verify()
.allow_algorithm(jwt::algorithm::ps256(rsa_pub_key, "", "", ""))
.with_issuer("auth0");

auto decoded_token = jwt::decode(token);

verify.verify(decoded_token);
}

TEST(TokenTest, VerifyTokenPS256Fail) {
std::string token = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.CJ4XjVWdbV6vXGZkD4GdJbtYc80SN9cmPOqRhZBRzOyDRqTFE"
"4MsbdKyQuhAWcvuMOjn-24qOTjVMR_P_uTC1uG6WPLcucxZyLnbb56zbKnEklW2SX0mQnCGewr-93a_vDaFT6Cp45MsF_OwFPRCMaS5CJg-"
"N5KY67UrVSr3s9nkuK9ZTQkyODHfyEUh9F_FhRCATGrb5G7_qHqBYvTvaPUXqzhhpCjN855Tocg7A24Hl0yMwM-XdasucW5xNdKjG_YCkis"
"HX7ax--JiF5GNYCO61eLFteO4THUg-3Z0r4OlGqlppyWo5X5tjcxOZCvBh7WDWfkxA48KFZPRv0nlKA";

auto verify = jwt::verify()
.allow_algorithm(jwt::algorithm::ps256(rsa_pub_key_invalid, "", "", ""))
.with_issuer("auth0");

auto decoded_token = jwt::decode(token);

ASSERT_THROW(verify.verify(decoded_token), jwt::signature_verification_exception);
}

namespace {
std::string rsa_priv_key = R"(-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC4ZtdaIrd1BPIJ
Expand Down
98 changes: 97 additions & 1 deletion include/jwt-cpp/jwt.h
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ namespace jwt {
throw signature_generation_exception("EVP_DigestUpdate failed");
unsigned int len = 0;
std::string res;
res.resize(EVP_MD_CTX_block_size(ctx.get()));
res.resize(EVP_MD_CTX_size(ctx.get()));
if(EVP_DigestFinal(ctx.get(), (unsigned char*)res.data(), &len) == 0)
throw signature_generation_exception("EVP_DigestFinal failed");
res.resize(len);
Expand All @@ -322,6 +322,86 @@ namespace jwt {
const std::string alg_name;
};

struct pss {
pss(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, const std::string& private_key_password, const EVP_MD*(*md)(), const std::string& name)
: md(md), alg_name(name)
{
std::unique_ptr<BIO, decltype(&BIO_free_all)> pubkey_bio(BIO_new(BIO_s_mem()), BIO_free_all);
if ((size_t)BIO_write(pubkey_bio.get(), public_key.data(), public_key.size()) != public_key.size())
throw rsa_exception("failed to load public key: bio_write failed");
pkey.reset(PEM_read_bio_PUBKEY(pubkey_bio.get(), nullptr, nullptr, (void*)public_key_password.c_str()), EVP_PKEY_free);
if (!pkey)
throw rsa_exception("failed to load public key: PEM_read_bio_PUBKEY failed");

if (!private_key.empty()) {
std::unique_ptr<BIO, decltype(&BIO_free_all)> privkey_bio(BIO_new(BIO_s_mem()), BIO_free_all);
if ((size_t)BIO_write(privkey_bio.get(), private_key.data(), private_key.size()) != private_key.size())
throw rsa_exception("failed to load private key: bio_write failed");
RSA* privkey = PEM_read_bio_RSAPrivateKey(privkey_bio.get(), nullptr, nullptr, (void*)private_key_password.c_str());
if (privkey == nullptr)
throw rsa_exception("failed to load private key: PEM_read_bio_RSAPrivateKey failed");
if (EVP_PKEY_assign_RSA(pkey.get(), privkey) == 0) {
RSA_free(privkey);
throw rsa_exception("failed to load private key: EVP_PKEY_assign_RSA failed");
}
}
}
std::string sign(const std::string& data) const {
auto hash = this->generate_hash(data);

std::unique_ptr<RSA, decltype(&RSA_free)> key(EVP_PKEY_get1_RSA(pkey.get()), RSA_free);
const int size = RSA_size(key.get());

std::string padded(size, 0x00);
if (!RSA_padding_add_PKCS1_PSS_mgf1(key.get(), (unsigned char*)padded.data(), (const unsigned char*)hash.data(), md(), md(), -1))
throw signature_generation_exception("failed to create signature: RSA_padding_add_PKCS1_PSS_mgf1 failed");

std::string res(size, 0x00);
if (RSA_private_encrypt(size, (const unsigned char*)padded.data(), (unsigned char*)res.data(), key.get(), RSA_NO_PADDING) < 0)
throw signature_generation_exception("failed to create signature: RSA_private_encrypt failed");
return res;
}
void verify(const std::string& data, const std::string& signature) const {
auto hash = this->generate_hash(data);

std::unique_ptr<RSA, decltype(&RSA_free)> key(EVP_PKEY_get1_RSA(pkey.get()), RSA_free);
const int size = RSA_size(key.get());

std::string sig(size, 0x00);
if(!RSA_public_decrypt(signature.size(), (const unsigned char*)signature.data(), (unsigned char*)sig.data(), key.get(), RSA_NO_PADDING))
throw signature_verification_exception("Invalid signature");

if(!RSA_verify_PKCS1_PSS_mgf1(key.get(), (const unsigned char*)hash.data(), md(), md(), (const unsigned char*)sig.data(), -1))
throw signature_verification_exception("Invalid signature");
}
std::string name() const {
return alg_name;
}
private:
std::string generate_hash(const std::string& data) const {
#ifdef OPENSSL10
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_destroy)> ctx(EVP_MD_CTX_create(), &EVP_MD_CTX_destroy);
#else
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
#endif
if(EVP_DigestInit(ctx.get(), md()) == 0)
throw signature_generation_exception("EVP_DigestInit failed");
if(EVP_DigestUpdate(ctx.get(), data.data(), data.size()) == 0)
throw signature_generation_exception("EVP_DigestUpdate failed");
unsigned int len = 0;
std::string res;
res.resize(EVP_MD_CTX_size(ctx.get()));
if(EVP_DigestFinal(ctx.get(), (unsigned char*)res.data(), &len) == 0)
throw signature_generation_exception("EVP_DigestFinal failed");
res.resize(len);
return res;
}

std::shared_ptr<EVP_PKEY> pkey;
const EVP_MD*(*md)();
const std::string alg_name;
};

struct hs256 : public hmacsha {
hs256(std::string key)
: hmacsha(std::move(key), EVP_sha256, "HS256")
Expand Down Expand Up @@ -367,6 +447,22 @@ namespace jwt {
: ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "ES512")
{}
};

struct ps256 : public pss {
ps256(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "")
: pss(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "PS256")
{}
};
struct ps384 : public pss {
ps384(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "")
: pss(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "PS384")
{}
};
struct ps512 : public pss {
ps512(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "")
: pss(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "PS512")
{}
};
}

class claim {
Expand Down

0 comments on commit 1357588

Please sign in to comment.