Skip to content

Commit

Permalink
jwt-auth: JWT lib interface: split verification function into another…
Browse files Browse the repository at this point in the history
… class (envoyproxy#455)

* create EVP_MD object in JwtVerifier constructor

* split verification function into another class

* update merged part according to this PR

* add TODO comment
  • Loading branch information
mtakigiku authored and qiwzhang committed Sep 8, 2017
1 parent d4d12e1 commit 3bd1beb
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 88 deletions.
10 changes: 6 additions & 4 deletions src/envoy/auth/http_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,7 @@ std::string JwtVerificationFilter::Verify(HeaderMap& headers) {
kAuthorizationHeaderTokenPrefix.length()) != 0) {
return "AUTHORIZATION_HEADER_BAD_FORMAT";
}
Auth::JwtVerifier jwt(value.c_str() +
kAuthorizationHeaderTokenPrefix.length());
Auth::Jwt jwt(value.c_str() + kAuthorizationHeaderTokenPrefix.length());
if (jwt.GetStatus() != Auth::Status::OK) {
// Invalid JWT
return Auth::StatusToString(jwt.GetStatus());
Expand All @@ -165,6 +164,7 @@ std::string JwtVerificationFilter::Verify(HeaderMap& headers) {
}

bool iss_aud_matched = false;
Auth::Verifier v;
for (const auto& iss : config_->issuers_) {
if (iss->failed_ || iss->pkey_->GetStatus() != Auth::Status::OK) {
continue;
Expand All @@ -178,7 +178,8 @@ std::string JwtVerificationFilter::Verify(HeaderMap& headers) {
}
iss_aud_matched = true;

if (jwt.Verify(*iss->pkey_)) {
iss_aud_matched = true;
if (v.Verify(jwt, *iss->pkey_)) {
// verification succeeded
std::string str_to_add;
switch (config_->user_info_type_) {
Expand All @@ -199,7 +200,8 @@ std::string JwtVerificationFilter::Verify(HeaderMap& headers) {
return "OK";
}
}
return iss_aud_matched ? "INVALID_SIGNATURE" : "ISS_AUD_UNMATCH";
return iss_aud_matched ? Auth::StatusToString(v.GetStatus())
: "ISS_AUD_UNMATCH";
}

void JwtVerificationFilter::CompleteVerification(HeaderMap& headers) {
Expand Down
93 changes: 40 additions & 53 deletions src/envoy/auth/jwt.cc
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ class EvpPkeyGetter : public WithStatus {

} // namespace

JwtVerifier::JwtVerifier(const std::string &jwt) {
Jwt::Jwt(const std::string &jwt) {
// jwt must have exactly 2 dots
if (std::count(jwt.begin(), jwt.end(), '.') != 2) {
UpdateStatus(Status::JWT_BAD_FORMAT);
Expand Down Expand Up @@ -234,6 +234,17 @@ JwtVerifier::JwtVerifier(const std::string &jwt) {
return;
}

// Prepare EVP_MD object.
if (alg_ == "RS256") {
// may use
// EVP_sha384() if alg == "RS384" and
// EVP_sha512() if alg == "RS512"
md_ = EVP_sha256();
} else {
UpdateStatus(Status::ALG_NOT_IMPLEMENTED);
return;
}

// Header may contain "kid", which should be a string if exists.
try {
kid_ = header_->getString("kid", "");
Expand Down Expand Up @@ -265,49 +276,28 @@ JwtVerifier::JwtVerifier(const std::string &jwt) {
}
}

const EVP_MD *JwtVerifier::EvpMdFromAlg(const std::string &alg) {
// may use
// EVP_sha384() if alg == "RS384" and
// EVP_sha512() if alg == "RS512"
if (alg == "RS256") {
return EVP_sha256();
} else {
return nullptr;
}
}

bool JwtVerifier::VerifySignature(EVP_PKEY *key, const std::string &alg,
const uint8_t *signature,
size_t signature_len,
const uint8_t *signed_data,
size_t signed_data_len) {
bool Verifier::VerifySignature(EVP_PKEY *key, const EVP_MD *md,
const uint8_t *signature, size_t signature_len,
const uint8_t *signed_data,
size_t signed_data_len) {
bssl::UniquePtr<EVP_MD_CTX> md_ctx(EVP_MD_CTX_create());
const EVP_MD *md = EvpMdFromAlg(alg);

if (!md) {
UpdateStatus(Status::ALG_NOT_IMPLEMENTED);
return false;
}
EVP_DigestVerifyInit(md_ctx.get(), nullptr, md, nullptr, key);
EVP_DigestVerifyUpdate(md_ctx.get(), signed_data, signed_data_len);
return (EVP_DigestVerifyFinal(md_ctx.get(), signature, signature_len) == 1);
}

bool JwtVerifier::VerifySignature(EVP_PKEY *key, const std::string &alg,
const std::string &signature,
const std::string &signed_data) {
return VerifySignature(key, alg, CastToUChar(signature), signature.length(),
bool Verifier::VerifySignature(EVP_PKEY *key, const EVP_MD *md,
const std::string &signature,
const std::string &signed_data) {
return VerifySignature(key, md, CastToUChar(signature), signature.length(),
CastToUChar(signed_data), signed_data.length());
}

bool JwtVerifier::VerifySignature(EVP_PKEY *key) {
std::string signed_data = jwt_split[0] + '.' + jwt_split[1];
return VerifySignature(key, alg_, signature_, signed_data);
}

bool JwtVerifier::Verify(const Pubkeys &pubkeys) {
// If setup is not successfully done, return false.
if (GetStatus() != Status::OK) {
bool Verifier::Verify(const Jwt &jwt, const Pubkeys &pubkeys) {
// If JWT status is not OK, inherits its status and return false.
if (jwt.GetStatus() != Status::OK) {
UpdateStatus(jwt.GetStatus());
return false;
}

Expand All @@ -317,23 +307,24 @@ bool JwtVerifier::Verify(const Pubkeys &pubkeys) {
return false;
}

std::string kid_jwt = Kid();
std::string signed_data = jwt.jwt_split[0] + '.' + jwt.jwt_split[1];
bool kid_alg_matched = false;
for (auto &pubkey : pubkeys.keys_) {
// If kid is specified in JWT, JWK with the same kid is used for
// verification.
// If kid is not specified in JWT, try all JWK.
if (kid_jwt != "" && pubkey->kid_ != kid_jwt) {
if (jwt.kid_ != "" && pubkey->kid_ != jwt.kid_) {
continue;
}

// The same alg must be used.
if (pubkey->alg_specified_ && pubkey->alg_ != Alg()) {
if (pubkey->alg_specified_ && pubkey->alg_ != jwt.alg_) {
continue;
}
kid_alg_matched = true;

if (VerifySignature(pubkey->key_.get())) {
if (VerifySignature(pubkey->key_.get(), jwt.md_, jwt.signature_,
signed_data)) {
// Verification succeeded.
return true;
}
Expand All @@ -349,25 +340,21 @@ bool JwtVerifier::Verify(const Pubkeys &pubkeys) {
}

// Returns the parsed header.
Json::ObjectSharedPtr JwtVerifier::Header() { return header_; }
Json::ObjectSharedPtr Jwt::Header() { return header_; }

const std::string &JwtVerifier::HeaderStr() { return header_str_; }
const std::string &JwtVerifier::HeaderStrBase64Url() {
return header_str_base64url_;
}
const std::string &JwtVerifier::Alg() { return alg_; }
const std::string &JwtVerifier::Kid() { return kid_; }
const std::string &Jwt::HeaderStr() { return header_str_; }
const std::string &Jwt::HeaderStrBase64Url() { return header_str_base64url_; }
const std::string &Jwt::Alg() { return alg_; }
const std::string &Jwt::Kid() { return kid_; }

// Returns payload JSON.
Json::ObjectSharedPtr JwtVerifier::Payload() { return payload_; }
Json::ObjectSharedPtr Jwt::Payload() { return payload_; }

const std::string &JwtVerifier::PayloadStr() { return payload_str_; }
const std::string &JwtVerifier::PayloadStrBase64Url() {
return payload_str_base64url_;
}
const std::string &JwtVerifier::Iss() { return iss_; }
const std::string &JwtVerifier::Aud() { return aud_; }
int64_t JwtVerifier::Exp() { return exp_; }
const std::string &Jwt::PayloadStr() { return payload_str_; }
const std::string &Jwt::PayloadStrBase64Url() { return payload_str_base64url_; }
const std::string &Jwt::Iss() { return iss_; }
const std::string &Jwt::Aud() { return aud_; }
int64_t Jwt::Exp() { return exp_; }

void Pubkeys::CreateFromPemCore(const std::string &pkey_pem) {
keys_.clear();
Expand Down
71 changes: 45 additions & 26 deletions src/envoy/auth/jwt.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,33 +105,56 @@ class WithStatus {
};

class Pubkeys;
class Jwt;

// JWT Verifier class.
//
// Usage example:
// JwtVerifier v(jwt);
// Verifier v;
// Jwt jwt(jwt_string);
// std::unique_ptr<Pubkeys> pubkey = ...
// if (v.Verify(*pubkey)) {
// auto payload = v.Payload();
// if (v.Verify(jwt, *pubkey)) {
// auto payload = jwt.Payload();
// ...
// } else {
// Status s = v.GetStatus();
// ...
// }
class JwtVerifier : public WithStatus {
class Verifier : public WithStatus {
public:
// This function verifies JWT signature.
// If verification failed, GetStatus() returns the failture reason.
// When the given JWT has a format error, this verification always fails and
// the JWT's status is handed over to Verifier.
// When pubkeys.GetStatus() is not equal to Status::OK, this verification
// always fails and the public key's status is handed over to Verifier.
bool Verify(const Jwt& jwt, const Pubkeys& pubkeys);

private:
// Functions to verify with single public key.
// (Note: Pubkeys object passed to Verify() may contains multiple public keys)
// When verification fails, UpdateStatus() is NOT called.
bool VerifySignature(EVP_PKEY* key, const EVP_MD* md,
const uint8_t* signature, size_t signature_len,
const uint8_t* signed_data, size_t signed_data_len);
bool VerifySignature(EVP_PKEY* key, const EVP_MD* md,
const std::string& signature,
const std::string& signed_data);
};

// Class to parse and a hold a JWT.
// It also holds the failure reason if parse failed.
//
// Usage example:
// Jwt jwt(jwt_string);
// if(jwt.GetStatus() == Status::OK) { ... }
class Jwt : public WithStatus {
public:
// This constructor parses the given JWT and prepares for verification.
// You can check if the setup was successfully done by seeing if GetStatus()
// == Status::OK. When the given JWT has a format error, GetStatus() returns
// the error detail.
JwtVerifier(const std::string& jwt);

// This function verifies JWT signature.
// If verification failed, GetStatus() returns the failture reason.
// When the given JWT has a format error, this verification always fails.
// When pubkeys.GetStatus() is not equal to Status::OK, this verification
// always fails and the public key's status is handed over to JwtVerifier.
bool Verify(const Pubkeys& pubkeys);
Jwt(const std::string& jwt);

// It returns a pointer to a JSON object of the header of the given JWT.
// When the given JWT has a format error, it returns nullptr.
Expand Down Expand Up @@ -173,19 +196,7 @@ class JwtVerifier : public WithStatus {
int64_t Exp();

private:
const EVP_MD* EvpMdFromAlg(const std::string& alg);

// Functions to verify with single public key.
// (Note: Pubkeys object passed to Verify() may contains multiple public keys)
// When verification fails, UpdateStatus(Status::JWT_INVALID_SIGNATURE) is NOT
// called.
bool VerifySignature(EVP_PKEY* key);
bool VerifySignature(EVP_PKEY* key, const std::string& alg,
const uint8_t* signature, size_t signature_len,
const uint8_t* signed_data, size_t signed_data_len);
bool VerifySignature(EVP_PKEY* key, const std::string& alg,
const std::string& signature,
const std::string& signed_data);
const EVP_MD* md_;

std::vector<std::string> jwt_split;
Json::ObjectSharedPtr header_;
Expand All @@ -200,6 +211,11 @@ class JwtVerifier : public WithStatus {
std::string iss_;
std::string aud_;
int64_t exp_;

/*
* TODO: try not to use friend function
*/
friend bool Verifier::Verify(const Jwt& jwt, const Pubkeys& pubkeys);
};

// Class to parse and a hold public key(s).
Expand Down Expand Up @@ -231,7 +247,10 @@ class Pubkeys : public WithStatus {
};
std::vector<std::unique_ptr<Pubkey> > keys_;

friend bool JwtVerifier::Verify(const Pubkeys& pubkeys);
/*
* TODO: try not to use friend function
*/
friend bool Verifier::Verify(const Jwt& jwt, const Pubkeys& pubkeys);
};

} // Auth
Expand Down
11 changes: 6 additions & 5 deletions src/envoy/auth/jwt_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -344,9 +344,10 @@ bool EqJson(Json::ObjectSharedPtr p1, Json::ObjectSharedPtr p2) {

class JwtTest : public testing::Test {
protected:
void DoTest(std::string jwt, std::string pkey, std::string pkey_type,
void DoTest(std::string jwt_str, std::string pkey, std::string pkey_type,
bool verified, Status status, Json::ObjectSharedPtr payload) {
JwtVerifier v = JwtVerifier(jwt);
Jwt jwt(jwt_str);
Verifier v;
std::unique_ptr<Pubkeys> key;
if (pkey_type == "pem") {
key = Pubkeys::CreateFrom(pkey, Pubkeys::Type::PEM);
Expand All @@ -355,11 +356,11 @@ class JwtTest : public testing::Test {
} else {
ASSERT_TRUE(0);
}
EXPECT_EQ(verified, v.Verify(*key));
EXPECT_EQ(verified, v.Verify(jwt, *key));
EXPECT_EQ(status, v.GetStatus());
if (verified) {
ASSERT_TRUE(v.Payload());
EXPECT_TRUE(EqJson(payload, v.Payload()));
ASSERT_TRUE(jwt.Payload());
EXPECT_TRUE(EqJson(payload, jwt.Payload()));
}
}
};
Expand Down

0 comments on commit 3bd1beb

Please sign in to comment.