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 <aw...@cloudera.com>
AuthorDate: Wed Apr 20 15:31:43 2022 -0700

    jwt: add test for fetching JWKS via URL
    
    Co-authored-by: Zoltan Chovan <zcho...@cloudera.com>
    
    Change-Id: Ief272c813b62e789d747a88e1f3be8c406eed3f8
    Reviewed-on: http://gerrit.cloudera.org:8080/18468
    Tested-by: Alexey Serbin <ale...@apache.org>
    Reviewed-by: Alexey Serbin <ale...@apache.org>
    Reviewed-by: Wenzhe Zhou <wz...@cloudera.com>
---
 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


Reply via email to