This is an automated email from the ASF dual-hosted git repository.
alexey pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kudu.git
The following commit(s) were added to refs/heads/master by this push:
new 98fa157bb jwt: add test for fetching JWKS via URL
98fa157bb is described below
commit 98fa157bb72939c285094152d83f822431347e7a
Author: Andrew Wong <[email protected]>
AuthorDate: Wed Apr 20 15:31:43 2022 -0700
jwt: add test for fetching JWKS via URL
Co-authored-by: Zoltan Chovan <[email protected]>
Change-Id: Ief272c813b62e789d747a88e1f3be8c406eed3f8
Reviewed-on: http://gerrit.cloudera.org:8080/18468
Tested-by: Alexey Serbin <[email protected]>
Reviewed-by: Alexey Serbin <[email protected]>
Reviewed-by: Wenzhe Zhou <[email protected]>
---
src/kudu/util/CMakeLists.txt | 3 +-
src/kudu/util/jwt-util-internal.h | 22 +--
src/kudu/util/jwt-util-test.cc | 113 +++++++++----
src/kudu/util/jwt-util.cc | 323 +++++++++++++++++++-------------------
4 files changed, 260 insertions(+), 201 deletions(-)
diff --git a/src/kudu/util/CMakeLists.txt b/src/kudu/util/CMakeLists.txt
index 3a1fab523..5223d9ef6 100644
--- a/src/kudu/util/CMakeLists.txt
+++ b/src/kudu/util/CMakeLists.txt
@@ -629,7 +629,8 @@ endif()
ADD_KUDU_TEST(jwt-util-test)
if(NOT NO_TESTS)
target_link_libraries(jwt-util-test
- kudu_jwt_util)
+ kudu_jwt_util
+ server_process)
endif()
#######################################
diff --git a/src/kudu/util/jwt-util-internal.h
b/src/kudu/util/jwt-util-internal.h
index 49b7b3dc9..38c499d24 100644
--- a/src/kudu/util/jwt-util-internal.h
+++ b/src/kudu/util/jwt-util-internal.h
@@ -19,6 +19,7 @@
#pragma once
+#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
@@ -44,8 +45,8 @@ typedef std::unordered_map<std::string, std::string>
JsonKVMap;
// This class represent cryptographic public key for JSON Web Token (JWT)
verification.
class JWTPublicKey {
public:
- JWTPublicKey(std::string algorithm, std::string pub_key)
- : verifier_(jwt::verify()), algorithm_(std::move(algorithm)),
public_key_(std::move(pub_key)) {}
+ JWTPublicKey(std::string algorithm, std::string pub_key)
+ : verifier_(jwt::verify()), algorithm_(std::move(algorithm)),
public_key_(std::move(pub_key)) {}
// Verify the given decoded token.
Status Verify(const DecodedJWT& decoded_jwt, const std::string& algorithm)
const;
@@ -205,13 +206,15 @@ class ES512JWTPublicKey : public JWTPublicKey {
// Construct a JWKPublicKey of HS from the JWK.
class HSJWTPublicKeyBuilder {
public:
- static Status CreateJWKPublicKey(const JsonKVMap& kv_map, JWTPublicKey**
pub_key_out);
+ static Status CreateJWKPublicKey(const JsonKVMap& kv_map,
+ std::unique_ptr<JWTPublicKey>* pub_key_out);
};
// Construct a JWKPublicKey of RSA from the JWK.
class RSAJWTPublicKeyBuilder {
public:
- static Status CreateJWKPublicKey(const JsonKVMap& kv_map, JWTPublicKey**
pub_key_out);
+ static Status CreateJWKPublicKey(const JsonKVMap& kv_map,
+ std::unique_ptr<JWTPublicKey>* pub_key_out);
private:
// Convert public key of RSA from JWK format to PEM encoded format by using
OpenSSL
@@ -223,7 +226,8 @@ class RSAJWTPublicKeyBuilder {
// Construct a JWKPublicKey of EC from the JWK.
class ECJWTPublicKeyBuilder {
public:
- static Status CreateJWKPublicKey(const JsonKVMap& kv_map, JWTPublicKey**
pub_key_out);
+ static Status CreateJWKPublicKey(const JsonKVMap& kv_map,
+ std::unique_ptr<JWTPublicKey>* pub_key_out);
private:
// Convert public key of EC from JWK format to PEM encoded format by using
OpenSSL
@@ -238,7 +242,7 @@ class ECJWTPublicKeyBuilder {
// and updates it atomically when the public keys in JWKS are changed. Clients
can obtain
// an immutable copy. Class instances can be created through the
implicitly-defined
// default and copy constructors.
-class JWKSSnapshot {
+class JWKSSnapshot final {
public:
JWKSSnapshot() = default;
JWKSSnapshot(const JWKSSnapshot&) = default;
@@ -288,11 +292,11 @@ class JWKSSnapshot {
// Following two functions are called inside Init().
// Add a RSA public key.
- void AddRSAPublicKey(const std::string& key_id, JWTPublicKey* jwk_pub_key);
+ void AddRSAPublicKey(const std::string& key_id,
std::unique_ptr<JWTPublicKey> jwk_pub_key);
// Add a HS key.
- void AddHSKey(const std::string& key_id, JWTPublicKey* jwk_pub_key);
+ void AddHSKey(const std::string& key_id, std::unique_ptr<JWTPublicKey>
jwk_pub_key);
// Add an EC public key.
- void AddECPublicKey(const std::string& key_id, JWTPublicKey* jwk_pub_key);
+ void AddECPublicKey(const std::string& key_id, std::unique_ptr<JWTPublicKey>
jwk_pub_key);
// Note: According to section 4.5 of RFC 7517 (JSON Web Key), different keys
might use
// the same "kid" value is if they have different "kty" (key type) values
but are
diff --git a/src/kudu/util/jwt-util-test.cc b/src/kudu/util/jwt-util-test.cc
index b035ad0ad..f9b4534f5 100644
--- a/src/kudu/util/jwt-util-test.cc
+++ b/src/kudu/util/jwt-util-test.cc
@@ -19,16 +19,13 @@
#include "kudu/util/jwt-util.h"
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-
#include <chrono>
-#include <cstdio> // file stuff
+#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <type_traits>
+#include <vector>
#include <gtest/gtest.h>
#include <jwt-cpp/jwt.h>
@@ -36,15 +33,21 @@
#include <jwt-cpp/traits/kazuho-picojson/traits.h>
#include "kudu/gutil/strings/substitute.h"
+#include "kudu/server/webserver.h"
+#include "kudu/server/webserver_options.h"
#include "kudu/util/env.h"
#include "kudu/util/jwt-util-internal.h"
+#include "kudu/util/net/sockaddr.h"
#include "kudu/util/slice.h"
#include "kudu/util/status.h"
#include "kudu/util/test_macros.h"
+#include "kudu/util/web_callback_registry.h"
namespace kudu {
using std::string;
+using std::unique_ptr;
+using std::vector;
using strings::Substitute;
const std::string kRsaPrivKeyPem = R"(-----BEGIN PRIVATE KEY-----
@@ -367,27 +370,21 @@ class TempTestDataFile {
// Creates a temporary file with the specified contents.
explicit TempTestDataFile(const std::string& contents);
- ~TempTestDataFile() { Delete(); }
-
/// Returns the absolute path to the file.
const std::string& Filename() const { return name_; }
private:
std::string name_;
- bool deleted_;
-
- // Delete this temporary file
- void Delete();
+ std::unique_ptr<WritableFile> tmp_file_;
};
TempTestDataFile::TempTestDataFile(const std::string& contents)
- : name_("/tmp/jwks_XXXXXX"), deleted_(false) {
- std::unique_ptr<WritableFile> tmp_file;
+ : name_("/tmp/jwks_XXXXXX") {
string created_filename;
WritableFileOptions opts;
opts.is_sensitive = false;
Status status;
- status = Env::Default()->NewTempWritableFile(opts, &name_[0],
&created_filename, &tmp_file);
+ status = Env::Default()->NewTempWritableFile(opts, &name_[0],
&created_filename, &tmp_file_);
if (!status.ok()) {
std::cerr << Substitute("Error creating temp file: $0", created_filename);
}
@@ -400,15 +397,6 @@ TempTestDataFile::TempTestDataFile(const std::string&
contents)
name_ = created_filename;
}
-void TempTestDataFile::Delete() {
- if (deleted_) return;
- deleted_ = true;
- if (remove(name_.c_str()) != 0) {
- std::cout << "Error deleting temp file; " << strerror(errno) << std::endl;
- abort();
- }
-}
-
TEST(JwtUtilTest, LoadJwksFile) {
// Load JWKS from file.
TempTestDataFile jwks_file(Substitute(kJwksRsaFileFormat, kKid1, "RS256",
@@ -1061,8 +1049,11 @@ TEST(JwtUtilTest, VerifyJwtTokenWithoutKeyId) {
// Create a JWT token without key ID.
auto token =
-
jwt::create().set_issuer("auth0").set_type("JWS").set_algorithm("RS256").sign(
- jwt::algorithm::rs256(kRsaPubKeyPem, kRsaPrivKeyPem, "", ""));
+ jwt::create()
+ .set_issuer("auth0")
+ .set_type("JWS")
+ .set_algorithm("RS256")
+ .sign(jwt::algorithm::rs256(kRsaPubKeyPem, kRsaPrivKeyPem, "", ""));
// Verify the token by trying each key in JWK set and there is one matched
key.
JWTHelper::UniqueJWTDecodedToken decoded_token;
status = JWTHelper::Decode(token, decoded_token);
@@ -1082,8 +1073,11 @@ TEST(JwtUtilTest, VerifyJwtFailTokenWithoutKeyId) {
// Create a JWT token without key ID.
auto token =
-
jwt::create().set_issuer("auth0").set_type("JWS").set_algorithm("RS512").sign(
- jwt::algorithm::rs512(kRsa512PubKeyPem, kRsa512PrivKeyPem, "", ""));
+ jwt::create()
+ .set_issuer("auth0")
+ .set_type("JWS")
+ .set_algorithm("RS512")
+ .sign(jwt::algorithm::rs512(kRsa512PubKeyPem, kRsa512PrivKeyPem, "",
""));
// Verify the token by trying each key in JWK set, but there is no matched
key.
JWTHelper::UniqueJWTDecodedToken decoded_token;
status = JWTHelper::Decode(token, decoded_token);
@@ -1145,4 +1139,69 @@ TEST(JwtUtilTest, VerifyJwtFailExpiredToken) {
<< " Actual error: " << status.ToString();
}
+namespace {
+
+void JWKSHandler(const Webserver::WebRequest& /*req*/,
+ Webserver::PrerenderedWebResponse* resp) {
+ resp->output <<
+ Substitute(kJwksRsaFileFormat, kKid1, "RS256",
+ kRsaPubKeyJwkN, kRsaPubKeyJwkE, kKid2, "RS256",
kRsaInvalidPubKeyJwkN,
+ kRsaPubKeyJwkE);
+ resp->status_code = HttpStatusCode::Ok;
+}
+
+class JWKSMockServer {
+ public:
+ Status Start() {
+ WebserverOptions opts;
+ opts.port = 0;
+ opts.bind_interface = "127.0.0.1";
+ webserver_.reset(new Webserver(opts));
+ webserver_->RegisterPrerenderedPathHandler("/jwks", "JWKS", JWKSHandler,
+ /*is_styled*/false,
/*is_on_nav_bar*/false);
+ RETURN_NOT_OK(webserver_->Start());
+ vector<Sockaddr> addrs;
+ RETURN_NOT_OK(webserver_->GetBoundAddresses(&addrs));
+ url_ = Substitute("http://$0/jwks", addrs[0].ToString());
+ return Status::OK();
+ }
+
+ const string& url() const {
+ return url_;
+ }
+ private:
+ unique_ptr<Webserver> webserver_;
+ string url_;
+};
+
+} // anonymous namespace
+
+TEST(JwtUtilTest, VerifyJWKSUrl) {
+ JWKSMockServer jwks_server;
+ ASSERT_OK(jwks_server.Start());
+
+ JWTHelper jwt_helper;
+ ASSERT_OK(jwt_helper.Init(jwks_server.url(), false));
+ auto encoded_token =
+ jwt::create()
+ .set_issuer("auth0")
+ .set_type("JWS")
+ .set_algorithm("RS256")
+ .set_key_id(kKid1)
+ .set_payload_claim("username", picojson::value("impala"))
+ .sign(jwt::algorithm::rs256(kRsaPubKeyPem, kRsaPrivKeyPem, "", ""));
+ ASSERT_EQ(
+
"eyJhbGciOiJSUzI1NiIsImtpZCI6InB1YmxpYzpjNDI0YjY3Yi1mZTI4LTQ1ZDctYjAxNS1mNzlkYTUwYj"
+
"ViMjEiLCJ0eXAiOiJKV1MifQ.eyJpc3MiOiJhdXRoMCIsInVzZXJuYW1lIjoiaW1wYWxhIn0.OW5H2SClL"
+
"lsotsCarTHYEbqlbRh43LFwOyo9WubpNTwE7hTuJDsnFoVrvHiWI02W69TZNat7DYcC86A_ogLMfNXagHj"
+
"lMFJaRnvG5Ekag8NRuZNJmHVqfX-qr6x7_8mpOdU554kc200pqbpYLhhuK4Qf7oT7y9mOrtNrUKGDCZ0Q2"
+
"y_mizlbY6SMg4RWqSz0RQwJbRgXIWSgcbZd0GbD_MQQ8x7WRE4nluU-5Fl4N2Wo8T9fNTuxALPiuVeIczO"
+
"25b5n4fryfKasSgaZfmk0CoOJzqbtmQxqiK9QNSJAiH2kaqMwLNgAdgn8fbd-lB1RAEGeyPH8Px8ipqcKs"
+ "Pk0bg",
+ encoded_token);
+ JWTHelper::UniqueJWTDecodedToken decoded_token;
+ ASSERT_OK(jwt_helper.Decode(encoded_token, decoded_token));
+ ASSERT_OK(jwt_helper.Verify(decoded_token.get()));
+}
+
} // namespace kudu
diff --git a/src/kudu/util/jwt-util.cc b/src/kudu/util/jwt-util.cc
index 31cc30069..bf512fd1b 100644
--- a/src/kudu/util/jwt-util.cc
+++ b/src/kudu/util/jwt-util.cc
@@ -19,7 +19,6 @@
#include "kudu/util/jwt-util.h"
-#include <errno.h>
#include <openssl/bn.h>
#include <openssl/crypto.h>
#include <openssl/ec.h>
@@ -27,13 +26,16 @@
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
-#include <stdint.h>
-#include <stdio.h>
+
#include <sys/stat.h>
+#include <cerrno>
+#include <cstdint>
+#include <cstdio>
#include <cstring>
#include <exception>
#include <functional>
+#include <iterator>
#include <mutex>
#include <ostream>
#include <stdexcept>
@@ -42,7 +44,6 @@
#include <unordered_map>
#include <utility>
-#include <boost/algorithm/string/case_conv.hpp>
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <jwt-cpp/jwt.h>
@@ -68,6 +69,7 @@
#include "kudu/util/thread.h"
using std::string;
+using std::unique_ptr;
using strings::Substitute;
DEFINE_int32(jwks_update_frequency_s, 60,
@@ -163,7 +165,7 @@ class JWKSetParser {
Status status = ParseKey(key);
if (!status.ok()) {
Status parse_status = Status::InvalidArgument(Substitute("parsing key
#$0, ", key_idx));
- return parse_status.CloneAndAppend(status.ToString());
+ return parse_status.CloneAndAppend(status.message());
}
}
return Status::OK();
@@ -178,41 +180,37 @@ class JWKSetParser {
member != json_key.MemberEnd(); ++member) {
key = string(member->name.GetString());
RETURN_NOT_OK(ReadKeyProperty(key, json_key, &value, /*required*/
false));
- if (kv_map.find(key) == kv_map.end()) {
- kv_map.insert(make_pair(key, value));
- } else {
+ if (!EmplaceIfNotPresent(&kv_map, key, value)) {
LOG(WARNING) << "Duplicate property of JWK: " << key;
}
}
- auto it_kty = kv_map.find("kty");
- if (it_kty == kv_map.end()) return Status::InvalidArgument("'kty' property
is required");
- auto it_kid = kv_map.find("kid");
- if (it_kid == kv_map.end()) return Status::InvalidArgument("'kid' property
is required");
- string key_id = it_kid->second;
- if (key_id.empty()) {
+ const auto* key_type = FindOrNull(kv_map, "kty");
+ if (!key_type) return Status::InvalidArgument("'kty' property is
required");
+ const auto* key_id = FindOrNull(kv_map, "kid");
+ if (!key_id) return Status::InvalidArgument("'kid' property is required");
+ if (key_id->empty()) {
return Status::InvalidArgument(Substitute("'kid' property must be a
non-empty string"));
}
- Status status;
- string key_type;
- ToLowerCase(it_kty->second, &key_type);
- if (key_type == "oct") {
- JWTPublicKey* jwt_pub_key;
- status = HSJWTPublicKeyBuilder::CreateJWKPublicKey(kv_map, &jwt_pub_key);
- if (status.ok()) jwks_->AddHSKey(key_id, jwt_pub_key);
- } else if (key_type == "rsa") {
- JWTPublicKey* jwt_pub_key;
- status = RSAJWTPublicKeyBuilder::CreateJWKPublicKey(kv_map,
&jwt_pub_key);
- if (status.ok()) jwks_->AddRSAPublicKey(key_id, jwt_pub_key);
- } else if (key_type == "ec") {
- JWTPublicKey* jwt_pub_key;
- status = ECJWTPublicKeyBuilder::CreateJWKPublicKey(kv_map, &jwt_pub_key);
- if (status.ok()) jwks_->AddECPublicKey(key_id, jwt_pub_key);
+ string key_type_lower;
+ ToLowerCase(*key_type, &key_type_lower);
+ if (key_type_lower == "oct") {
+ unique_ptr<JWTPublicKey> jwt_pub_key;
+ RETURN_NOT_OK(HSJWTPublicKeyBuilder::CreateJWKPublicKey(kv_map,
&jwt_pub_key));
+ jwks_->AddHSKey(*key_id, std::move(jwt_pub_key));
+ } else if (key_type_lower == "rsa") {
+ unique_ptr<JWTPublicKey> jwt_pub_key;
+ RETURN_NOT_OK(RSAJWTPublicKeyBuilder::CreateJWKPublicKey(kv_map,
&jwt_pub_key));
+ jwks_->AddRSAPublicKey(*key_id, std::move(jwt_pub_key));
+ } else if (key_type_lower == "ec") {
+ unique_ptr<JWTPublicKey> jwt_pub_key;
+ RETURN_NOT_OK(ECJWTPublicKeyBuilder::CreateJWKPublicKey(kv_map,
&jwt_pub_key));
+ jwks_->AddECPublicKey(*key_id, std::move(jwt_pub_key));
} else {
return Status::InvalidArgument(Substitute("Unsupported kty: '$0'",
key_type));
}
- return status;
+ return Status::OK();
}
// Reads a key property of the given name and assigns the property value to
the out
@@ -264,25 +262,24 @@ Status JWTPublicKey::Verify(
algorithm, algorithm_));
}
- Status status;
try {
// Call jwt-cpp API to verify token's signature.
verifier_.verify(decoded_jwt);
} catch (const jwt::error::rsa_exception& e) {
- status = Status::NotAuthorized(Substitute("RSA error: $0", e.what()));
+ return Status::NotAuthorized(Substitute("RSA error: $0", e.what()));
} catch (const jwt::error::token_verification_exception& e) {
- status = Status::NotAuthorized(
+ return Status::NotAuthorized(
Substitute("Verification failed, error: $0", e.what()));
} catch (const std::exception& e) {
- status = Status::NotAuthorized(
+ return Status::NotAuthorized(
Substitute("Verification failed, error: $0", e.what()));
}
- return status;
+ return Status::OK();
}
// Create a JWKPublicKey of HS from the JWK.
Status HSJWTPublicKeyBuilder::CreateJWKPublicKey(
- const JsonKVMap& kv_map, JWTPublicKey** pub_key_out) {
+ const JsonKVMap& kv_map, unique_ptr<JWTPublicKey>* pub_key_out) {
// Octet Sequence keys for HS256, HS384 or HS512.
// JWK Sample:
// {
@@ -305,30 +302,28 @@ Status HSJWTPublicKeyBuilder::CreateJWKPublicKey(
return Status::InvalidArgument(Substitute("'k' property must be a
non-empty string"));
}
- Status status;
- JWTPublicKey* jwt_pub_key = nullptr;
+ unique_ptr<JWTPublicKey> jwt_pub_key;
try {
if (algorithm == "hs256") {
- jwt_pub_key = new HS256JWTPublicKey(algorithm, it_k->second);
+ jwt_pub_key.reset(new HS256JWTPublicKey(algorithm, it_k->second));
} else if (algorithm == "hs384") {
- jwt_pub_key = new HS384JWTPublicKey(algorithm, it_k->second);
+ jwt_pub_key.reset(new HS384JWTPublicKey(algorithm, it_k->second));
} else if (algorithm == "hs512") {
- jwt_pub_key = new HS512JWTPublicKey(algorithm, it_k->second);
+ jwt_pub_key.reset(new HS512JWTPublicKey(algorithm, it_k->second));
} else {
return Status::InvalidArgument(Substitute("Invalid 'alg' property value:
'$0'", algorithm));
}
} catch (const std::exception& e) {
- status = Status::NotAuthorized(
+ return Status::NotAuthorized(
Substitute("Failed to initialize verifier, error: $0", e.what()));
}
- if (!status.ok()) return status;
- *pub_key_out = jwt_pub_key;
+ *pub_key_out = std::move(jwt_pub_key);
return Status::OK();
}
// Create a JWKPublicKey of RSA from the JWK.
Status RSAJWTPublicKeyBuilder::CreateJWKPublicKey(
- const JsonKVMap& kv_map, JWTPublicKey** pub_key_out) {
+ const JsonKVMap& kv_map, unique_ptr<JWTPublicKey>* pub_key_out) {
// JWK Sample:
// {
// "kty":"RSA",
@@ -339,7 +334,8 @@ Status RSAJWTPublicKeyBuilder::CreateJWKPublicKey(
// }
auto it_alg = kv_map.find("alg");
if (it_alg == kv_map.end()) return Status::InvalidArgument("'alg' property
is required");
- string algorithm = boost::algorithm::to_lower_copy(it_alg->second);
+ string algorithm;
+ ToLowerCase(it_alg->second, &algorithm);
if (algorithm.empty()) {
return Status::InvalidArgument(Substitute("'alg' property must be a
non-empty string"));
}
@@ -359,34 +355,31 @@ Status RSAJWTPublicKeyBuilder::CreateJWKPublicKey(
Substitute("Invalid public key 'n':'$0', 'e':'$1'", it_n->second,
it_e->second));
}
- Status status;
- JWTPublicKey* jwt_pub_key = nullptr;
+ unique_ptr<JWTPublicKey> jwt_pub_key;
try {
if (algorithm == "rs256") {
- jwt_pub_key = new RS256JWTPublicKey(algorithm, pub_key);
+ jwt_pub_key.reset(new RS256JWTPublicKey(algorithm, pub_key));
} else if (algorithm == "rs384") {
- jwt_pub_key = new RS384JWTPublicKey(algorithm, pub_key);
+ jwt_pub_key.reset(new RS384JWTPublicKey(algorithm, pub_key));
} else if (algorithm == "rs512") {
- jwt_pub_key = new RS512JWTPublicKey(algorithm, pub_key);
+ jwt_pub_key.reset(new RS512JWTPublicKey(algorithm, pub_key));
} else if (algorithm == "ps256") {
- jwt_pub_key = new PS256JWTPublicKey(algorithm, pub_key);
+ jwt_pub_key.reset(new PS256JWTPublicKey(algorithm, pub_key));
} else if (algorithm == "ps384") {
- jwt_pub_key = new PS384JWTPublicKey(algorithm, pub_key);
+ jwt_pub_key.reset(new PS384JWTPublicKey(algorithm, pub_key));
} else if (algorithm == "ps512") {
- jwt_pub_key = new PS512JWTPublicKey(algorithm, pub_key);
+ jwt_pub_key.reset(new PS512JWTPublicKey(algorithm, pub_key));
} else {
return Status::InvalidArgument(Substitute("Invalid 'alg' property value:
'$0'", algorithm));
}
} catch (const jwt::error::rsa_exception& e) {
- status = Status::NotAuthorized(Substitute("RSA error: $0", e.what()));
- RETURN_NOT_OK(status);
+ return Status::NotAuthorized(Substitute("RSA error: $0", e.what()));
} catch (const std::exception& e) {
- status = Status::NotAuthorized(
+ return Status::NotAuthorized(
Substitute("Failed to initialize verifier, error: $0", e.what()));
- RETURN_NOT_OK(status);
}
- *pub_key_out = jwt_pub_key;
+ *pub_key_out = std::move(jwt_pub_key);
return Status::OK();
}
@@ -398,38 +391,42 @@ bool RSAJWTPublicKeyBuilder::ConvertJwkToPem(
string str_e;
if (!strings::WebSafeBase64Unescape(base64_n, &str_n)) return false;
if (!strings::WebSafeBase64Unescape(base64_e, &str_e)) return false;
- BIGNUM* modul = BN_bin2bn(reinterpret_cast<const unsigned
char*>(str_n.c_str()),
- static_cast<int>(str_n.size()),
- nullptr);
- BIGNUM* expon = BN_bin2bn(reinterpret_cast<const unsigned
char*>(str_e.c_str()),
- static_cast<int>(str_e.size()),
- nullptr);
-
- RSA* rsa = RSA_new();
+ security::c_unique_ptr<BIGNUM> modul {
+ BN_bin2bn(reinterpret_cast<const unsigned char*>(str_n.c_str()),
+ static_cast<int>(str_n.size()),
+ nullptr),
+ &BN_free
+ };
+ security::c_unique_ptr<BIGNUM> expon {
+ BN_bin2bn(reinterpret_cast<const unsigned char*>(str_e.c_str()),
+ static_cast<int>(str_e.size()),
+ nullptr),
+ &BN_free
+ };
+
+ security::c_unique_ptr<RSA> rsa { RSA_new(), &RSA_free };
#if OPENSSL_VERSION_NUMBER < 0x10100000L
- rsa->n = modul;
- rsa->e = expon;
+ rsa->n = modul.get();
+ rsa->e = expon.get();
#else
// RSA_set0_key is a new API introduced in OpenSSL version 1.1
- RSA_set0_key(rsa, modul, expon, nullptr);
+ RSA_set0_key(rsa.get(), modul.release(), expon.release(), nullptr);
#endif
- unsigned char desc[1024];
- memset(desc, 0, 1024);
+ unsigned char desc[1024] = { 0 };
auto bio = security::ssl_make_unique(BIO_new(BIO_s_mem()));
- PEM_write_bio_RSA_PUBKEY(bio.get(), rsa);
- if (BIO_read(bio.get(), desc, 1024) > 0) {
+ PEM_write_bio_RSA_PUBKEY(bio.get(), rsa.get());
+ if (BIO_read(bio.get(), desc, std::size(desc) - 1) > 0) {
pub_key = reinterpret_cast<char*>(desc);
// Remove last '\n'.
if (pub_key.length() > 0 && pub_key[pub_key.length() - 1] == '\n')
pub_key.pop_back();
}
- RSA_free(rsa);
return !pub_key.empty();
}
// Create a JWKPublicKey of EC (ES256, ES384 or ES512) from the JWK.
Status ECJWTPublicKeyBuilder::CreateJWKPublicKey(
- const JsonKVMap& kv_map, JWTPublicKey** pub_key_out) {
+ const JsonKVMap& kv_map, unique_ptr<JWTPublicKey>* pub_key_out) {
// JWK Sample:
// {
// "kty":"EC",
@@ -442,7 +439,8 @@ Status ECJWTPublicKeyBuilder::CreateJWKPublicKey(
int eccgrp;
auto it_crv = kv_map.find("crv");
if (it_crv != kv_map.end()) {
- string curve = boost::algorithm::to_upper_copy(it_crv->second);
+ string curve;
+ ToUpperCase(it_crv->second, &curve);
if (curve == "P-256") {
algorithm = "es256";
eccgrp = NID_X9_62_prime256v1;
@@ -460,7 +458,7 @@ Status ECJWTPublicKeyBuilder::CreateJWKPublicKey(
if (it_alg == kv_map.end()) {
return Status::InvalidArgument("'alg' or 'crv' property is required");
}
- algorithm = boost::algorithm::to_lower_copy(it_alg->second);
+ ToLowerCase(it_alg->second, &algorithm);
if (algorithm.empty()) {
return Status::InvalidArgument(Substitute("'alg' property must be a
non-empty string"));
}
@@ -493,7 +491,6 @@ Status ECJWTPublicKeyBuilder::CreateJWKPublicKey(
Substitute("Invalid public key 'x':'$0', 'y':'$1'", it_x->second,
it_y->second));
}
- Status status;
JWTPublicKey* jwt_pub_key = nullptr;
try {
if (algorithm == "es256") {
@@ -505,13 +502,13 @@ Status ECJWTPublicKeyBuilder::CreateJWKPublicKey(
jwt_pub_key = new ES512JWTPublicKey(algorithm, pub_key);
}
} catch (const jwt::error::ecdsa_exception& e) {
- status = Status::NotAuthorized(Substitute("EC error: $0", e.what()));
+ return Status::NotAuthorized(Substitute("EC error: $0", e.what()));
} catch (const std::exception& e) {
- status = Status::NotAuthorized(
+ return Status::NotAuthorized(
Substitute("Failed to initialize verifier, error: $0", e.what()));
}
- if (!status.ok()) return status;
- *pub_key_out = jwt_pub_key;
+
+ pub_key_out->reset(jwt_pub_key);
return Status::OK();
}
@@ -523,23 +520,27 @@ bool ECJWTPublicKeyBuilder::ConvertJwkToPem(int eccgrp,
const std::string& base6
string ascii_y;
if (!strings::WebSafeBase64Unescape(base64_x, &ascii_x)) return false;
if (!strings::WebSafeBase64Unescape(base64_y, &ascii_y)) return false;
- BIGNUM* x = BN_bin2bn(reinterpret_cast<const unsigned
char*>(ascii_x.c_str()),
- static_cast<int>(ascii_x.size()),
- nullptr);
- BIGNUM* y = BN_bin2bn(reinterpret_cast<const unsigned
char*>(ascii_y.c_str()),
- static_cast<int>(ascii_y.size()),
- nullptr);
-
- BIO* bio = nullptr;
- EC_KEY* ecKey = EC_KEY_new_by_curve_name(eccgrp);
- EC_KEY_set_asn1_flag(ecKey, OPENSSL_EC_NAMED_CURVE);
- if (EC_KEY_set_public_key_affine_coordinates(ecKey, x, y) == 0) goto cleanup;
-
- unsigned char desc[1024];
- memset(desc, 0, 1024);
- bio = BIO_new(BIO_s_mem());
- if (PEM_write_bio_EC_PUBKEY(bio, ecKey) != 0) {
- if (BIO_read(bio, desc, 1024) > 0) {
+ security::c_unique_ptr<BIGNUM> x {
+ BN_bin2bn(reinterpret_cast<const unsigned char*>(ascii_x.c_str()),
+ static_cast<int>(ascii_x.size()),
+ nullptr),
+ &BN_free
+ };
+ security::c_unique_ptr<BIGNUM> y {
+ BN_bin2bn(reinterpret_cast<const unsigned char*>(ascii_y.c_str()),
+ static_cast<int>(ascii_y.size()),
+ nullptr),
+ &BN_free
+ };
+
+ security::c_unique_ptr<EC_KEY> ecKey { EC_KEY_new_by_curve_name(eccgrp),
&EC_KEY_free };
+ EC_KEY_set_asn1_flag(ecKey.get(), OPENSSL_EC_NAMED_CURVE);
+ if (EC_KEY_set_public_key_affine_coordinates(ecKey.get(), x.get(), y.get())
== 0) return false;
+
+ unsigned char desc[1024] = { 0 };
+ auto bio = security::ssl_make_unique(BIO_new(BIO_s_mem()));
+ if (PEM_write_bio_EC_PUBKEY(bio.get(), ecKey.get()) != 0) {
+ if (BIO_read(bio.get(), desc, std::size(desc) - 1) > 0) {
pub_key = reinterpret_cast<char*>(desc);
// Remove last '\n'.
if (pub_key.length() > 0 && pub_key[pub_key.length() - 1] == '\n') {
@@ -548,11 +549,6 @@ bool ECJWTPublicKeyBuilder::ConvertJwkToPem(int eccgrp,
const std::string& base6
}
}
-cleanup:
- if (bio != nullptr) BIO_free(bio);
- EC_KEY_free(ecKey);
- BN_free(x);
- BN_free(y);
return !pub_key.empty();
}
@@ -607,7 +603,7 @@ Status JWKSSnapshot::LoadKeysFromUrl(
const std::string& jwks_url, uint64_t cur_jwks_checksum, bool* is_changed)
{
kudu::EasyCurl curl;
kudu::faststring dst;
- Status status;
+ *is_changed = false;
curl.set_timeout(
kudu::MonoDelta::FromMilliseconds(static_cast<int64_t>(FLAGS_jwks_pulling_timeout_s)
* 1000));
@@ -618,8 +614,9 @@ Status JWKSSnapshot::LoadKeysFromUrl(
if (dst.size() > 0) {
// Verify if the checksum of the downloaded JWKS has been changed.
jwks_checksum_ = HashUtil::FastHash64(dst.data(), dst.size(), /*seed*/
0xcafebeef);
- if (jwks_checksum_ == cur_jwks_checksum) return Status::OK();
- *is_changed = true;
+ if (jwks_checksum_ == cur_jwks_checksum) {
+ return Status::OK();
+ }
// Append '\0' so that the in-memory object could be parsed as
StringStream.
dst.push_back('\0');
#ifndef NDEBUG
@@ -629,39 +626,46 @@ Status JWKSSnapshot::LoadKeysFromUrl(
Document jwks_doc;
jwks_doc.Parse(reinterpret_cast<char*>(dst.data()));
if (jwks_doc.HasParseError()) {
- status =
Status::InvalidArgument(GetParseError_En(jwks_doc.GetParseError()));
- } else if (!jwks_doc.IsObject()) {
- status = Status::InvalidArgument("root element must be a JSON Object");
- } else if (!jwks_doc.HasMember("keys")) {
- status = Status::InvalidArgument("keys is required");
- } else {
- // Load and initialize public keys.
- JWKSetParser jwks_parser(this);
- status = jwks_parser.Parse(jwks_doc);
+ return
Status::InvalidArgument(GetParseError_En(jwks_doc.GetParseError()));
}
+ if (!jwks_doc.IsObject()) {
+ return Status::InvalidArgument("root element must be a JSON Object");
+ }
+ if (!jwks_doc.HasMember("keys")) {
+ return Status::InvalidArgument("keys is required");
+ }
+
+ // Load and initialize public keys.
+ JWKSetParser jwks_parser(this);
+ RETURN_NOT_OK(jwks_parser.Parse(jwks_doc));
}
- return status;
+
+ *is_changed = true;
+ return Status::OK();
}
-void JWKSSnapshot::AddHSKey(const std::string& key_id, JWTPublicKey*
jwk_pub_key) {
+void JWKSSnapshot::AddHSKey(const std::string& key_id,
+ unique_ptr<JWTPublicKey> jwk_pub_key) {
if (hs_key_map_.find(key_id) == hs_key_map_.end()) {
- hs_key_map_[key_id].reset(jwk_pub_key);
+ hs_key_map_[key_id] = std::move(jwk_pub_key);
} else {
LOG(WARNING) << "Duplicate key ID of JWK for HS key: " << key_id;
}
}
-void JWKSSnapshot::AddRSAPublicKey(const std::string& key_id, JWTPublicKey*
jwk_pub_key) {
+void JWKSSnapshot::AddRSAPublicKey(const std::string& key_id,
+ unique_ptr<JWTPublicKey> jwk_pub_key) {
if (rsa_pub_key_map_.find(key_id) == rsa_pub_key_map_.end()) {
- rsa_pub_key_map_[key_id].reset(jwk_pub_key);
+ rsa_pub_key_map_[key_id] = std::move(jwk_pub_key);
} else {
LOG(WARNING) << "Duplicate key ID of JWK for RSA public key: " << key_id;
}
}
-void JWKSSnapshot::AddECPublicKey(const std::string& key_id, JWTPublicKey*
jwk_pub_key) {
+void JWKSSnapshot::AddECPublicKey(const std::string& key_id,
+ unique_ptr<JWTPublicKey> jwk_pub_key) {
if (ec_pub_key_map_.find(key_id) == ec_pub_key_map_.end()) {
- ec_pub_key_map_[key_id].reset(jwk_pub_key);
+ ec_pub_key_map_[key_id] = std::move(jwk_pub_key);
} else {
LOG(WARNING) << "Duplicate key ID of JWK for EC public key: " << key_id;
}
@@ -689,28 +693,20 @@ JWKSMgr::~JWKSMgr() {
}
Status JWKSMgr::Init(const std::string& jwks_uri, bool is_local_file) {
- Status status;
jwks_uri_ = jwks_uri;
std::shared_ptr<JWKSSnapshot> new_jwks = std::make_shared<JWKSSnapshot>();
if (is_local_file) {
- status = new_jwks->LoadKeysFromFile(jwks_uri);
- if (!status.ok()) {
- LOG(ERROR) << "Failed to load JWKS: " << status.ToString();
- return status;
- }
+ RETURN_NOT_OK_PREPEND(new_jwks->LoadKeysFromFile(jwks_uri), "Failed to
load JWKS");
SetJWKSSnapshot(new_jwks);
} else {
bool is_changed = false;
- status = new_jwks->LoadKeysFromUrl(jwks_uri, current_jwks_checksum_,
&is_changed);
- if (!status.ok()) {
- LOG(ERROR) << "Failed to load JWKS: " << status.ToString();
- return status;
- }
+ RETURN_NOT_OK_PREPEND(new_jwks->LoadKeysFromUrl(jwks_uri,
current_jwks_checksum_, &is_changed),
+ "Failed to load JWKS");
DCHECK(is_changed);
if (is_changed) SetJWKSSnapshot(new_jwks);
// Start a working thread to periodically check the JWKS URL for updates.
- RETURN_NOT_OK(Thread::Create("impala-server", "JWKS-mgr",
+ RETURN_NOT_OK(Thread::Create("JWT", "JWKS-mgr",
[this] { return UpdateJWKSThread(); }, &jwks_update_thread_));
}
@@ -733,7 +729,6 @@ void JWKSMgr::UpdateJWKSThread() {
break;
}
-
new_jwks = std::make_shared<JWKSSnapshot>();
bool is_changed = false;
Status status =
@@ -742,6 +737,9 @@ void JWKSMgr::UpdateJWKSThread() {
LOG(WARNING) << "Failed to update JWKS: " << status.ToString();
} else if (is_changed) {
SetJWKSSnapshot(new_jwks);
+ if (new_jwks->IsEmpty()) {
+ LOG(WARNING) << "New JWKS snapshot is empty.";
+ }
}
new_jwks.reset();
}
@@ -792,7 +790,6 @@ JWKSSnapshotPtr JWTHelper::GetJWKS() const {
// Decode the given JWT token.
Status JWTHelper::Decode(const string& token, UniqueJWTDecodedToken&
decoded_token_out) {
- Status status;
try {
// Call jwt-cpp API to decode the JWT token with default jwt::json_traits
// (jwt::picojson_traits).
@@ -810,18 +807,17 @@ Status JWTHelper::Decode(const string& token,
UniqueJWTDecodedToken& decoded_tok
VLOG(3) << msg.str();
#endif
} catch (const std::invalid_argument& e) {
- status = Status::NotAuthorized(
+ return Status::NotAuthorized(
Substitute("Token is not in correct format, error: $0", e.what()));
} catch (const std::runtime_error& e) {
- status = Status::NotAuthorized(
+ return Status::NotAuthorized(
Substitute("Base64 decoding failed or invalid json, error: $0",
e.what()));
}
- return status;
+ return Status::OK();
}
// Validate the token's signature with public key.
Status JWTHelper::Verify(const JWTDecodedToken* decoded_token) const {
- Status status;
DCHECK(initialized_);
const auto& decoded_jwt = decoded_token->decoded_jwt_;
@@ -841,8 +837,8 @@ Status JWTHelper::Verify(const JWTDecodedToken*
decoded_token) const {
}
try {
- string algorithm =
- boost::algorithm::to_lower_copy(decoded_jwt.get_algorithm());
+ string algorithm;
+ ToLowerCase(decoded_jwt.get_algorithm(), &algorithm);
string prefix = algorithm.substr(0, 2);
if (decoded_jwt.has_key_id()) {
// Get key id from token's header and use it to retrieve the public key
from JWKS.
@@ -863,7 +859,7 @@ Status JWTHelper::Verify(const JWTDecodedToken*
decoded_token) const {
return Status::NotAuthorized("Invalid JWK ID in the JWT token");
}
// Use the public key to verify the token's signature.
- status = pub_key->Verify(decoded_jwt, algorithm);
+ RETURN_NOT_OK(pub_key->Verify(decoded_jwt, algorithm));
} else {
// According to RFC 7517 (JSON Web Key), 'kid' is OPTIONAL so it's
possible there
// is no key id in the token's header. In this case, get all of public
keys from
@@ -882,52 +878,51 @@ Status JWTHelper::Verify(const JWTDecodedToken*
decoded_token) const {
if (key_map->empty()) {
return Status::NotAuthorized("Verification failed, no matching key");
}
+ Status status;
// Try each key with matching algorithm util the signature is verified.
for (const auto& key : *key_map) {
status = key.second->Verify(decoded_jwt, algorithm);
if (status.ok()) return status;
}
+ return status;
}
} catch (const std::bad_cast& e) {
- status = Status::NotAuthorized(
+ return Status::NotAuthorized(
Substitute("Claim was present but not a string, error: $0", e.what()));
} catch (const jwt::error::claim_not_present_exception& e) {
- status = Status::NotAuthorized(
+ return Status::NotAuthorized(
Substitute("Claim not present in JWT token, error $0", e.what()));
} catch (const std::exception& e) {
- status = Status::NotAuthorized(
+ return Status::NotAuthorized(
Substitute("Token verification failed, error: $0", e.what()));
}
- return status;
+ return Status::OK();
}
Status JWTHelper::GetCustomClaimUsername(const JWTDecodedToken* decoded_token,
const string& jwt_custom_claim_username, string& username) {
DCHECK(!jwt_custom_claim_username.empty());
- Status status;
const auto& decoded_jwt = decoded_token->decoded_jwt_;
try {
// Get value of custom claim 'username' from the token payload.
- if (decoded_jwt.has_payload_claim(jwt_custom_claim_username)) {
- // Assume the claim data type of 'username' is string.
- username.assign(
- decoded_jwt.get_payload_claim(jwt_custom_claim_username)
- .to_json()
- .to_str());
- if (username.empty()) {
- status = Status::NotAuthorized(
- Substitute("Claim '$0' is empty", jwt_custom_claim_username));
- }
- } else {
- status = Status::NotAuthorized(
+ if (!decoded_jwt.has_payload_claim(jwt_custom_claim_username)) {
+ return Status::NotAuthorized(
Substitute("Claim '$0' was not present", jwt_custom_claim_username));
}
+
+ username =
decoded_jwt.get_payload_claim(jwt_custom_claim_username).to_json().to_str();
+
+ if (username.empty()) {
+ return Status::NotAuthorized(
+ Substitute("Claim '$0' is empty", jwt_custom_claim_username));
+ }
+
} catch (const std::runtime_error& e) {
- status = Status::NotAuthorized(
+ return Status::NotAuthorized(
Substitute("Claim '$0' was not present, error: $1",
jwt_custom_claim_username,
e.what()));
}
- return status;
+ return Status::OK();
}
} // namespace kudu