You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@impala.apache.org by wz...@apache.org on 2021/07/16 01:30:53 UTC

[impala] branch master updated (474e022 -> de8397e)

This is an automated email from the ASF dual-hosted git repository.

wzhou pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git.


    from 474e022  IMPALA-10626: Add support for Iceberg's Catalogs API
     new 36d8e67  IMPALA-10763: Min/max filters should be enabled on Z-order sorted columns
     new de8397e  IMPALA-10489 part2: Support RSASSA-PSS and EC Algorithms for JWT

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 be/src/service/query-options.cc                    |   2 +-
 be/src/util/jwt-util-internal.h                    |  95 ++++-
 be/src/util/jwt-util-test.cc                       | 441 +++++++++++++++++++++
 be/src/util/jwt-util.cc                            | 181 ++++++++-
 .../java/org/apache/impala/catalog/FeFsTable.java  |  34 +-
 .../org/apache/impala/planner/HdfsScanNode.java    |  43 +-
 .../overlap_min_max_filters_on_sorted_columns.test |  95 +++++
 7 files changed, 847 insertions(+), 44 deletions(-)

[impala] 02/02: IMPALA-10489 part2: Support RSASSA-PSS and EC Algorithms for JWT

Posted by wz...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

wzhou pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git

commit de8397ec171ffd9bb911fbbc5967858edf75e4c0
Author: wzhou-code <wz...@cloudera.com>
AuthorDate: Thu Jul 8 18:27:31 2021 -0700

    IMPALA-10489 part2: Support RSASSA-PSS and EC Algorithms for JWT
    
    This patch added additional JSON Web Algorithms (JWA) for JWT
    authentication, including RSASSA-PSS algorithms - PS256/PS384/PS512,
    and Elliptic Curve family of algorithms - ES256/ES384/ES512.
    JWTs can be signed using a public/private key pair using RSA or ECDSA.
    
    Added BE unit-tests for PS256, PS384, PS512, ES256, ES384, and ES512
    algorithms.
    
    Testing:
     - Passed core run.
     - Passed BE test jwt-util-test with ASAN build.
    
    Change-Id: Ib4dc30c51f503b609dd311ce4387080abc5a0832
    Reviewed-on: http://gerrit.cloudera.org:8080/17663
    Reviewed-by: Impala Public Jenkins <im...@cloudera.com>
    Tested-by: Impala Public Jenkins <im...@cloudera.com>
---
 be/src/util/jwt-util-internal.h |  95 ++++++++-
 be/src/util/jwt-util-test.cc    | 441 ++++++++++++++++++++++++++++++++++++++++
 be/src/util/jwt-util.cc         | 181 +++++++++++++++--
 3 files changed, 701 insertions(+), 16 deletions(-)

diff --git a/be/src/util/jwt-util-internal.h b/be/src/util/jwt-util-internal.h
index 568326d..1d559bd 100644
--- a/be/src/util/jwt-util-internal.h
+++ b/be/src/util/jwt-util-internal.h
@@ -141,6 +141,73 @@ class RS512JWTPublicKey : public JWTPublicKey {
   }
 };
 
+/// JWT Public Key for PS256.
+/// PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256.
+/// RSASSA-PSS is the probabilistic version of RSA.
+class PS256JWTPublicKey : public JWTPublicKey {
+ public:
+  /// Throw JWT exception if failed to initialize the verifier.
+  PS256JWTPublicKey(std::string algorithm, std::string pub_key)
+    : JWTPublicKey(algorithm, pub_key) {
+    verifier_.allow_algorithm(jwt::algorithm::ps256(pub_key, "", "", ""));
+  }
+};
+
+/// JWT Public Key for PS384.
+/// PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384.
+class PS384JWTPublicKey : public JWTPublicKey {
+ public:
+  /// Throw exception if failed to initialize the JWT verifier.
+  PS384JWTPublicKey(std::string algorithm, std::string pub_key)
+    : JWTPublicKey(algorithm, pub_key) {
+    verifier_.allow_algorithm(jwt::algorithm::ps384(pub_key, "", "", ""));
+  }
+};
+
+/// JWT Public Key for PS512.
+/// PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512.
+class PS512JWTPublicKey : public JWTPublicKey {
+ public:
+  /// Throw JWT exception if failed to initialize the verifier.
+  PS512JWTPublicKey(std::string algorithm, std::string pub_key)
+    : JWTPublicKey(algorithm, pub_key) {
+    verifier_.allow_algorithm(jwt::algorithm::ps512(pub_key, "", "", ""));
+  }
+};
+
+/// JWT Public Key for ES256.
+/// ES256: ECDSA using P-256 and SHA-256.
+class ES256JWTPublicKey : public JWTPublicKey {
+ public:
+  /// Throw JWT exception if failed to initialize the verifier.
+  ES256JWTPublicKey(std::string algorithm, std::string pub_key)
+    : JWTPublicKey(algorithm, pub_key) {
+    verifier_.allow_algorithm(jwt::algorithm::es256(pub_key, "", "", ""));
+  }
+};
+
+/// JWT Public Key for ES384.
+/// ES384: ECDSA using P-384 and SHA-384.
+class ES384JWTPublicKey : public JWTPublicKey {
+ public:
+  /// Throw exception if failed to initialize the JWT verifier.
+  ES384JWTPublicKey(std::string algorithm, std::string pub_key)
+    : JWTPublicKey(algorithm, pub_key) {
+    verifier_.allow_algorithm(jwt::algorithm::es384(pub_key, "", "", ""));
+  }
+};
+
+/// JWT Public Key for ES512.
+/// ES512: ECDSA using P-521 and SHA-512.
+class ES512JWTPublicKey : public JWTPublicKey {
+ public:
+  /// Throw JWT exception if failed to initialize the verifier.
+  ES512JWTPublicKey(std::string algorithm, std::string pub_key)
+    : JWTPublicKey(algorithm, pub_key) {
+    verifier_.allow_algorithm(jwt::algorithm::es512(pub_key, "", "", ""));
+  }
+};
+
 /// Construct a JWKPublicKey of HS from the JWK.
 class HSJWTPublicKeyBuilder {
  public:
@@ -159,6 +226,18 @@ class RSAJWTPublicKeyBuilder {
       const std::string& base64_n, const std::string& base64_e, std::string& pub_key);
 };
 
+/// Construct a JWKPublicKey of EC from the JWK.
+class ECJWTPublicKeyBuilder {
+ public:
+  static Status CreateJWKPublicKey(JsonKVMap& kv_map, JWTPublicKey** pub_key_out);
+
+ private:
+  /// Convert public key of EC from JWK format to PEM encoded format by using OpenSSL
+  /// APIs.
+  static bool ConvertJwkToPem(int eccgrp, const std::string& base64_x,
+      const std::string& base64_y, std::string& pub_key);
+};
+
 /// JSON Web Key Set (JWKS) conveys the public keys used by the signing party to the
 /// clients that need to validate signatures. It represents a cryptographic key set in
 /// JSON data structure.
@@ -183,19 +262,26 @@ class JsonWebKeySet {
   /// successful, otherwise return nullptr.
   const JWTPublicKey* LookupRSAPublicKey(const std::string& kid) const;
   const JWTPublicKey* LookupHSKey(const std::string& kid) const;
+  const JWTPublicKey* LookupECPublicKey(const std::string& kid) const;
 
   /// Return number of keys for each family of algorithms.
   int GetHSKeyNum() const { return hs_key_map_.size(); }
   /// Return number of keys for RSA.
   int GetRSAPublicKeyNum() const { return rsa_pub_key_map_.size(); }
+  /// Return number of keys for EC.
+  int GetECPublicKeyNum() const { return ec_pub_key_map_.size(); }
 
   /// Return all keys for HS.
   const JWTPublicKeyMap* GetAllHSKeys() const { return &hs_key_map_; }
   /// Return all keys for RSA.
   const JWTPublicKeyMap* GetAllRSAPublicKeys() const { return &rsa_pub_key_map_; }
+  /// Return all keys for EC.
+  const JWTPublicKeyMap* GetAllECPublicKeys() const { return &ec_pub_key_map_; }
 
   /// Return TRUE if there is no key.
-  bool IsEmpty() const { return hs_key_map_.empty() && rsa_pub_key_map_.empty(); }
+  bool IsEmpty() const {
+    return hs_key_map_.empty() && rsa_pub_key_map_.empty() && ec_pub_key_map_.empty();
+  }
 
  private:
   friend class JWKSetParser;
@@ -205,6 +291,8 @@ class JsonWebKeySet {
   void AddRSAPublicKey(std::string key_id, JWTPublicKey* jwk_pub_key);
   /// Add a HS key.
   void AddHSKey(std::string key_id, JWTPublicKey* jwk_pub_key);
+  /// Add a EC public key.
+  void AddECPublicKey(std::string key_id, 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
@@ -214,9 +302,12 @@ class JsonWebKeySet {
   /// Octet Sequence keys for HS256 (HMAC using SHA-256), HS384 and HS512.
   /// kty (key type): oct.
   JWTPublicKeyMap hs_key_map_;
-  /// Public keys for RSA family of algorithms: RS256, RS384, RS512.
+  /// Public keys for RSA family of algorithms: RS256, RS384, RS512, PS256, PS384, PS512.
   /// kty (key type): RSA.
   JWTPublicKeyMap rsa_pub_key_map_;
+  /// Public keys for EC family of algorithms: ES256, ES384, ES512.
+  /// kty (key type): EC.
+  JWTPublicKeyMap ec_pub_key_map_;
 };
 
 } // namespace impala
diff --git a/be/src/util/jwt-util-test.cc b/be/src/util/jwt-util-test.cc
index c45db48..1bb6412 100644
--- a/be/src/util/jwt-util-test.cc
+++ b/be/src/util/jwt-util-test.cc
@@ -65,6 +65,8 @@ WXI9C+yjHztqyL2h8P6mlThPY9E9ue2fCqdgixfTFIF9Dm4SLHbphUS2iw7w1JgT
 AziMCxS+VrRPDM+zfvpIJg3JljAh3PJHDiLu902v9w+Iplu1WyoB2aPfitxEhRN0
 YwIDAQAB
 -----END PUBLIC KEY-----)";
+// The public keys in JWK format were converted from PEM formatted crypto keys with
+// pem-to-jwk tool at https://hub.docker.com/r/danedmunds/pem-to-jwk/
 std::string rsa_pub_key_jwk_n =
     "uGbXWiK3dQTyCbX5xdE4yCuYp0AF2d15Qq1JSXT_lx8CEcXb9RbDddl8jGDv-sp"
     "i5qPa8qEHiK7FwV2KpRE983wGPnYsAm9BxLFb4YrLYcDFOIGULuk2FtrPS512Qe"
@@ -115,6 +117,205 @@ std::string rsa512_invalid_pub_key_jwk_n =
     "CTmsa2Ysf712rl57SlH0Wz_Mr3F7aM9YpErzeYLrl0GhQr9BVJxOvXcVd4kmY-X"
     "kiCcrkyS1cnghnllh-LCwQu1sYw";
 
+std::string rsa1024_priv_key_pem = R"(-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDT+6sb2SvN69NB+6Zg78B7mdke0tC91CTfixzCSn7wS8JUvvZK
+AO1uMgnrCQdDr2TNeRYr6urawIOCDB1Ybz1+cBSNxouVdt/aT9+cw27kzVQE59NA
+PMpQyLtXaAOR6rD8xzyIgAV12QFmc1kHFl7Sjobwmsu5ZWRqYTwdXvFXIQIDAQAB
+AoGBAJKDLxBgWVZJ2AmS1LvK+U50VwxmyL9rENEwZQAkXPfYZMgN9EvRuEihbRl1
+c//kCde6CQjxpMDsrfgER4QH3odypQWT9A5uXKcdfu/z+xKNtB813rSrew3Q9pXe
+wlOb0q7EcS7XHMrcPxj4gvn2yKqB40vF3TIY6oiSeZbFLUvBAkEA9NaTrGB1+FZj
++3lIAs7UtYbxNggX53OEcXlstDbqhG3O9SzAHiccMbGu2lDBcAAghmtg9poT0Uo6
+V3VCJcnfNwJBAN2lppZFVWAXOLD2k8OMCp4jc9pRHIUtPU6kWoflU8O6kuDNNamD
+AeNMhdHX+Ed/Js3ig75eAGxsd9q+CFp/uGcCQQDFfGb0/YFqZFSVPMhm62oLWeMq
+T/DoEfdciDK0Ui9rzh7HB+eW6rkFJGsDUWwV6SRTCD3X64PcpuDUNpK6ZFCVAkEA
+oaBgAAiDH1UPpAvK6LfALl0P6E1pjLvWjvhOg/Z4xKvS21cJIJlF0ShGFSV2CTzx
+YQUiqLkHegkGxV353XRxVQJAZaW5O2BI5jKy2hK0EoAx3pSnp2X4CmkWrXsSeOgC
+Zz+jDkn8QzPbRwb8cyks/IHc2CBvaFStLFKO2VQj1THDhw==
+-----END RSA PRIVATE KEY-----)";
+std::string rsa1024_pub_key_pem = R"(-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDT+6sb2SvN69NB+6Zg78B7mdke
+0tC91CTfixzCSn7wS8JUvvZKAO1uMgnrCQdDr2TNeRYr6urawIOCDB1Ybz1+cBSN
+xouVdt/aT9+cw27kzVQE59NAPMpQyLtXaAOR6rD8xzyIgAV12QFmc1kHFl7Sjobw
+msu5ZWRqYTwdXvFXIQIDAQAB
+-----END PUBLIC KEY-----)";
+std::string rsa1024_pub_key_jwk_n =
+    "0_urG9krzevTQfumYO_Ae5nZHtLQvdQk34scwkp-8EvCVL72SgDtbjIJ6wkHQ69"
+    "kzXkWK-rq2sCDggwdWG89fnAUjcaLlXbf2k_fnMNu5M1UBOfTQDzKUMi7V2gDke"
+    "qw_Mc8iIAFddkBZnNZBxZe0o6G8JrLuWVkamE8HV7xVyE";
+std::string rsa1024_pub_key_jwk_e = "AQAB";
+
+std::string rsa2048_priv_key_pem = R"(-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA0jCHomsNIaRYVlsemWg9yBx3od1B9Fd9RUslk9IVE7IU+QYZ
++T4NvRVPAMjpzuurvPnN4uBVPREycXOgEcWHiJDDQEhlQD4F69W8MFE7SXpdcBih
+zcj5qPYtTFP/52s6Vg7Y8SAUkBDyr0B442ONR1SBD8qEMAxpiLMH1Q/Yap+etvIj
+D1r2zQkQke53An9LvVl7OKkM8KGOcE/0tJRmc7x8ZlLqogPczkXvW6T+YAkwA8Xw
+ginZw0xBzfpoOEnajqm4Yikck0gJ0HwdlYFIp72ih7uozne7PYLVGb9X97cL0H1X
+DiA/SXJiFKo1AKXihcOdIRiw49eo9rzsoWPygQIDAQABAoIBAADe2BT1XgojYNqc
+s9P9UUeEof80Mst6WEe4RQknb9RozVBEX55Ut4sEAqjVbC3MnpBgtXhTfFmNem4W
+BUCa7DyFzZ/fcjc8T9sh7mQB1h3FXraHN5ZUrH9auPsjBuvfBGW/rSjUfJlQefzS
+psgu950Rwxtnt+PuDTrWc6QaKx0ylvESKPIaVoticc11Kcts5Fe/RQ2Az2epDDM7
+ptZamvtzptozPPq5YUIvpSnKCJfzOczAQT4omVewJV/7nbo/MdCALExrqHcIqXFp
+2uMpHV1QhqZ160Bzf1O+iDRCxT3rd4OZ5Y68x/fYV8dRqrqPA5BFep6ukf17cnWM
+svDqsaUCgYEA+Z5RbadUKteAM3v1Deu9RG7TucnxyoNSofpEuwMoVxo3+z+dS44v
+UpC7/MJhx1FBf15yKSPIgtjt5o/LanApcJEZVyghucsNvqy11db027P63NkIL/ic
+AgB04odLvxpgLHNv/qEWy7zHBLHhcazajzDHW+a/xBXrtJa3i2G+poUCgYEA15Ap
+OJPafAx/BPMbrYthpd5pVX5AMExXTur7rMIPi4/wh0O0vqGtulwgX3FiS0X4bAzK
+tNJ23/V2RR0F16IAIVZQqt16pIvmhx52iC55EPp3bZWkGhZ33/8Dxzkbe+rlwECa
+wRK4dOyA9hwsnlRuEb8OHva6sr+EusOxmeN6Us0CgYEAg4O/QTe057GM0RNRJFl8
+6a4+jRdx9hHEmqTCS4m5WlLtBcoZdLJgCm9JLD25yIruKE45daVtwkrK5PwD33ti
+yfUY1cvGIR5zim9yikzry0mDNZJ/ds7UW1WkP6mq5e/elezoJ871tLgsXzPdJMg+
+iszXbHshtA0cl5QE9kG0cgUCgYEAzZf3WLjbxzh75RKhMVIgnfyU5i91tRr6opBH
+3atw/CEavUf8GV1GvtmjHqSbpUNk/ljs9K1PJ6eLV7uomNMv4JvccDqxAENWaUTK
+tHPukBzyzxfL3f3T81XcGqUC65tL6aM0djUOrKXtEc4pWBEasd5Q74NO6bD0PNTs
+jOODBXkCgYEAhaD3gZUCWU+ZA6QmxPotfe9L0tzjmUjsLo0QUgIHJa2VaoHzdnWC
+ClvP3tFFkv2dlD6UW+g0JJFTVWcv+HEiC9WUnD/C6dXK/qA3fRvBhRKy8FTwvOis
+zSVeYds6mvDJwFe+2mk0KQiKnxlx22B4PcYbbN7mZ2ClBFTFrp0+Id4=
+-----END RSA PRIVATE KEY-----)";
+std::string rsa2048_pub_key_pem = R"(-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0jCHomsNIaRYVlsemWg9
+yBx3od1B9Fd9RUslk9IVE7IU+QYZ+T4NvRVPAMjpzuurvPnN4uBVPREycXOgEcWH
+iJDDQEhlQD4F69W8MFE7SXpdcBihzcj5qPYtTFP/52s6Vg7Y8SAUkBDyr0B442ON
+R1SBD8qEMAxpiLMH1Q/Yap+etvIjD1r2zQkQke53An9LvVl7OKkM8KGOcE/0tJRm
+c7x8ZlLqogPczkXvW6T+YAkwA8XwginZw0xBzfpoOEnajqm4Yikck0gJ0HwdlYFI
+p72ih7uozne7PYLVGb9X97cL0H1XDiA/SXJiFKo1AKXihcOdIRiw49eo9rzsoWPy
+gQIDAQAB
+-----END PUBLIC KEY-----)";
+std::string rsa2048_pub_key_jwk_n =
+    "0jCHomsNIaRYVlsemWg9yBx3od1B9Fd9RUslk9IVE7IU-QYZ-T4NvRVPAMjpzuu"
+    "rvPnN4uBVPREycXOgEcWHiJDDQEhlQD4F69W8MFE7SXpdcBihzcj5qPYtTFP_52"
+    "s6Vg7Y8SAUkBDyr0B442ONR1SBD8qEMAxpiLMH1Q_Yap-etvIjD1r2zQkQke53A"
+    "n9LvVl7OKkM8KGOcE_0tJRmc7x8ZlLqogPczkXvW6T-YAkwA8XwginZw0xBzfpo"
+    "OEnajqm4Yikck0gJ0HwdlYFIp72ih7uozne7PYLVGb9X97cL0H1XDiA_SXJiFKo"
+    "1AKXihcOdIRiw49eo9rzsoWPygQ";
+std::string rsa2048_pub_key_jwk_e = "AQAB";
+
+std::string rsa4096_priv_key_pem = R"(-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----)";
+std::string rsa4096_pub_key_pem = R"(-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtxmYsvs6ZfhTCFKCHQBW
+/W3iRfh8wZN+/XPXaOiIx9SXYSFrb/WRaTn8UOvflYuRnPYMaRGr5gVTS6/WFVvt
+NuZVIDOQBgEOBt5MQ0BeM0yPiM6qacP15couRwxbJx45ODQNyh5jNF4SdzqThNFT
+CFHtWakL1qrkGNSKdowIMaM59dm58liMHxp9h9yTmqM4ZgiZkoF6Vy4KYrg9ChVq
+UZze4KMiyow8Xv6ESM7Eg2ncTbRevuedbtYv7OVlotyozt1geFkWm/8ZUA6Z68lf
+tYMySq0/yjAmjql0DXP1+vPL9k5sKGr5lpIUlB7a9JWbXdepNjvy1vslFuLjcM50
+9d1E8e70C5VF61XthKlk3rRIBWK80XupWR7o6clJsMYKxeF+ImBbzDbWrIkMxe0v
+TbikS6S4CLPVlYx4sMWAWu4UBpxZquw4cVxyiKoZ2j5yTMAD1xiHI/b2/psFzW3q
+XcgQWTY7dpIP1BInhepCzHlcLSEiHtmMoCXNC3+i9ZXxiJ1u8avYfGjH8RrJW8dv
+Vw7BS7zlH9s7rCn001VBJCJcXtkGaykw9Zd1E+Jh7IKQJn8gydsQ0enlMmtwsJO/
+tEvYBojFXbl4XecMWADTiExjXobX1y7u9ZTn0KRNkPpX9GTgY3oR0ei+rwOr4d+k
+2CrUdkMTGfjnfcDHKjHh3LMCAwEAAQ==
+-----END PUBLIC KEY-----)";
+std::string rsa4096_pub_key_jwk_n =
+    "txmYsvs6ZfhTCFKCHQBW_W3iRfh8wZN-_XPXaOiIx9SXYSFrb_WRaTn8UOvflYu"
+    "RnPYMaRGr5gVTS6_WFVvtNuZVIDOQBgEOBt5MQ0BeM0yPiM6qacP15couRwxbJx"
+    "45ODQNyh5jNF4SdzqThNFTCFHtWakL1qrkGNSKdowIMaM59dm58liMHxp9h9yTm"
+    "qM4ZgiZkoF6Vy4KYrg9ChVqUZze4KMiyow8Xv6ESM7Eg2ncTbRevuedbtYv7OVl"
+    "otyozt1geFkWm_8ZUA6Z68lftYMySq0_yjAmjql0DXP1-vPL9k5sKGr5lpIUlB7"
+    "a9JWbXdepNjvy1vslFuLjcM509d1E8e70C5VF61XthKlk3rRIBWK80XupWR7o6c"
+    "lJsMYKxeF-ImBbzDbWrIkMxe0vTbikS6S4CLPVlYx4sMWAWu4UBpxZquw4cVxyi"
+    "KoZ2j5yTMAD1xiHI_b2_psFzW3qXcgQWTY7dpIP1BInhepCzHlcLSEiHtmMoCXN"
+    "C3-i9ZXxiJ1u8avYfGjH8RrJW8dvVw7BS7zlH9s7rCn001VBJCJcXtkGaykw9Zd"
+    "1E-Jh7IKQJn8gydsQ0enlMmtwsJO_tEvYBojFXbl4XecMWADTiExjXobX1y7u9Z"
+    "Tn0KRNkPpX9GTgY3oR0ei-rwOr4d-k2CrUdkMTGfjnfcDHKjHh3LM";
+std::string rsa4096_pub_key_jwk_e = "AQAB";
+
+std::string ecdsa521_priv_key_pem = R"(-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIAuZxTZjLIZM5hxgZX+JRrqt5FKpAEg/meZ7m9aSE3XbRITqtfz1Uy
+h2Srn7o8+4j/jQpwHTTHZThy10u5jMjaR+mgBwYFK4EEACOhgYkDgYYABAFFah0k
+6m4ddp/tUN/ObrKKwSCp4QUZdiAMaC9eY1HyNBPuuEsH5qCfeY5lmeJwSUpzCosn
+rgW8M2hQ4Kr5V9OXrgHLA5WVtH6//sSkUY2/xYuqc7/Ln8gI5ddtr1qG64Xtgs05
+/CNajSjFZeLm76llakvYiBTTH/ii8hIfrwukW9IP7Q==
+-----END EC PRIVATE KEY-----)";
+std::string ecdsa521_pub_key_pem = R"(-----BEGIN PUBLIC KEY-----
+MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBRWodJOpuHXaf7VDfzm6yisEgqeEF
+GXYgDGgvXmNR8jQT7rhLB+agn3mOZZnicElKcwqLJ64FvDNoUOCq+VfTl64BywOV
+lbR+v/7EpFGNv8WLqnO/y5/ICOXXba9ahuuF7YLNOfwjWo0oxWXi5u+pZWpL2IgU
+0x/4ovISH68LpFvSD+0=
+-----END PUBLIC KEY-----)";
+std::string ecdsa521_pub_key_jwk_x =
+    "AUVqHSTqbh12n-1Q385usorBIKnhBRl2IAxoL15jUfI0E-64SwfmoJ95jmWZ4nB"
+    "JSnMKiyeuBbwzaFDgqvlX05eu";
+std::string ecdsa521_pub_key_jwk_y =
+    "AcsDlZW0fr_-xKRRjb_Fi6pzv8ufyAjl122vWobrhe2CzTn8I1qNKMVl4ubvqWV"
+    "qS9iIFNMf-KLyEh-vC6Rb0g_t";
+
+std::string ecdsa384_priv_key_pem = R"(-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDCrPXJDgQDtNRpM0qNUW/zN1vrCvOVH1CsItVZ+1NeGB+w/2whnIXJQ
+K7U5C1ETPHagBwYFK4EEACKhZANiAAR0JjvVJXc3u1I/7vt5mxzPtAIi1VIqxCwN
+wgISZVySTYZQzyicW2GfhMlFCow28LzqTwH/eCymAvnTAmpK/P1hXhNcnxDBZNOU
+WMbMLFcQrg2wwpIb/k/IXobNwjNPRBo=
+-----END EC PRIVATE KEY-----)";
+std::string ecdsa384_pub_key_pem = R"(-----BEGIN PUBLIC KEY-----
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEdCY71SV3N7tSP+77eZscz7QCItVSKsQs
+DcICEmVckk2GUM8onFthn4TJRQqMNvC86k8B/3gspgL50wJqSvz9YV4TXJ8QwWTT
+lFjGzCxXEK4NsMKSG/5PyF6GzcIzT0Qa
+-----END PUBLIC KEY-----)";
+std::string ecdsa384_pub_key_jwk_x =
+    "dCY71SV3N7tSP-77eZscz7QCItVSKsQsDcICEmVckk2GUM8onFthn4TJRQqMNvC8";
+std::string ecdsa384_pub_key_jwk_y =
+    "6k8B_3gspgL50wJqSvz9YV4TXJ8QwWTTlFjGzCxXEK4NsMKSG_5PyF6GzcIzT0Qa";
+
+std::string ecdsa256_priv_key_pem = R"(-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPGJGAm4X1fvBuC1z
+SpO/4Izx6PXfNMaiKaS5RUkFqEGhRANCAARCBvmeksd3QGTrVs2eMrrfa7CYF+sX
+sjyGg+Bo5mPKGH4Gs8M7oIvoP9pb/I85tdebtKlmiCZHAZE5w4DfJSV6
+-----END PRIVATE KEY-----)";
+std::string ecdsa256_pub_key_pem = R"(-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQgb5npLHd0Bk61bNnjK632uwmBfr
+F7I8hoPgaOZjyhh+BrPDO6CL6D/aW/yPObXXm7SpZogmRwGROcOA3yUleg==
+-----END PUBLIC KEY-----)";
+std::string ecdsa256_pub_key_jwk_x = "Qgb5npLHd0Bk61bNnjK632uwmBfrF7I8hoPgaOZjyhg";
+std::string ecdsa256_pub_key_jwk_y = "fgazwzugi-g_2lv8jzm115u0qWaIJkcBkTnDgN8lJXo";
+
 std::string kid_1 = "public:c424b67b-fe28-45d7-b015-f79da50b5b21";
 std::string kid_2 = "public:9b9d0b47-b9ed-4ba6-9180-52fc5b161a3a";
 
@@ -133,6 +334,13 @@ std::string jwks_rsa_file_format = R"(
   ]
 })";
 
+std::string jwks_ec_file_format = R"(
+{
+  "keys": [
+    { "kty": "EC", "kid": "$0", "crv": "$1", "x": "$2", "y": "$3" }
+  ]
+})";
+
 /// Utility class for creating a file that will be automatically deleted upon test
 /// completion.
 class TempTestDataFile {
@@ -527,6 +735,239 @@ TEST(JwtUtilTest, VerifyJwtRS512) {
   ASSERT_EQ("impala", username);
 }
 
+TEST(JwtUtilTest, VerifyJwtPS256) {
+  // Cryptographic algorithm: PS256.
+  TempTestDataFile jwks_file(Substitute(jwks_rsa_file_format, kid_1, "PS256",
+      rsa1024_pub_key_jwk_n, rsa1024_pub_key_jwk_e, kid_2, "PS256",
+      rsa_invalid_pub_key_jwk_n, rsa_pub_key_jwk_e));
+  JWTHelper jwt_helper;
+  Status status = jwt_helper.Init(jwks_file.Filename());
+  EXPECT_OK(status);
+  const JsonWebKeySet* jwks = jwt_helper.GetJWKS();
+  ASSERT_EQ(2, jwks->GetRSAPublicKeyNum());
+
+  const JWTPublicKey* key1 = jwks->LookupRSAPublicKey(kid_1);
+  ASSERT_TRUE(key1 != nullptr);
+  ASSERT_EQ(rsa1024_pub_key_pem, key1->get_key());
+
+  // Create a JWT token and sign it with PS256.
+  auto token =
+      jwt::create()
+          .set_issuer("auth0")
+          .set_type("JWS")
+          .set_algorithm("PS256")
+          .set_key_id(kid_1)
+          .set_payload_claim("username", picojson::value("impala"))
+          .sign(jwt::algorithm::ps256(rsa1024_pub_key_pem, rsa1024_priv_key_pem, "", ""));
+
+  // Verify the JWT token with our wrapper class which use public key retrieved from JWKS,
+  // and read username from the token.
+  JWTHelper::UniqueJWTDecodedToken decoded_token;
+  status = JWTHelper::Decode(token, decoded_token);
+  EXPECT_OK(status);
+  status = jwt_helper.Verify(decoded_token.get());
+  EXPECT_OK(status);
+  string username;
+  status = JWTHelper::GetCustomClaimUsername(decoded_token.get(), "username", username);
+  EXPECT_OK(status);
+  ASSERT_EQ("impala", username);
+}
+
+TEST(JwtUtilTest, VerifyJwtPS384) {
+  // Cryptographic algorithm: PS384.
+  TempTestDataFile jwks_file(Substitute(jwks_rsa_file_format, kid_1, "PS384",
+      rsa2048_pub_key_jwk_n, rsa2048_pub_key_jwk_e, kid_2, "PS384",
+      rsa_invalid_pub_key_jwk_n, rsa_pub_key_jwk_e));
+  JWTHelper jwt_helper;
+  Status status = jwt_helper.Init(jwks_file.Filename());
+  EXPECT_OK(status);
+  const JsonWebKeySet* jwks = jwt_helper.GetJWKS();
+  ASSERT_EQ(2, jwks->GetRSAPublicKeyNum());
+
+  const JWTPublicKey* key1 = jwks->LookupRSAPublicKey(kid_1);
+  ASSERT_TRUE(key1 != nullptr);
+  ASSERT_EQ(rsa2048_pub_key_pem, key1->get_key());
+
+  // Create a JWT token and sign it with PS384.
+  auto token =
+      jwt::create()
+          .set_issuer("auth0")
+          .set_type("JWS")
+          .set_algorithm("PS384")
+          .set_key_id(kid_1)
+          .set_payload_claim("username", picojson::value("impala"))
+          .sign(jwt::algorithm::ps384(rsa2048_pub_key_pem, rsa2048_priv_key_pem, "", ""));
+
+  // Verify the JWT token with our wrapper class which use public key retrieved from JWKS,
+  // and read username from the token.
+  JWTHelper::UniqueJWTDecodedToken decoded_token;
+  status = JWTHelper::Decode(token, decoded_token);
+  EXPECT_OK(status);
+  status = jwt_helper.Verify(decoded_token.get());
+  EXPECT_OK(status);
+  string username;
+  status = JWTHelper::GetCustomClaimUsername(decoded_token.get(), "username", username);
+  EXPECT_OK(status);
+  ASSERT_EQ("impala", username);
+}
+
+TEST(JwtUtilTest, VerifyJwtPS512) {
+  // Cryptographic algorithm: PS512.
+  TempTestDataFile jwks_file(Substitute(jwks_rsa_file_format, kid_1, "PS512",
+      rsa4096_pub_key_jwk_n, rsa4096_pub_key_jwk_e, kid_2, "PS512",
+      rsa_invalid_pub_key_jwk_n, rsa_pub_key_jwk_e));
+  JWTHelper jwt_helper;
+  Status status = jwt_helper.Init(jwks_file.Filename());
+  EXPECT_OK(status);
+  const JsonWebKeySet* jwks = jwt_helper.GetJWKS();
+  ASSERT_EQ(2, jwks->GetRSAPublicKeyNum());
+
+  const JWTPublicKey* key1 = jwks->LookupRSAPublicKey(kid_1);
+  ASSERT_TRUE(key1 != nullptr);
+  ASSERT_EQ(rsa4096_pub_key_pem, key1->get_key());
+
+  // Create a JWT token and sign it with PS512.
+  auto token =
+      jwt::create()
+          .set_issuer("auth0")
+          .set_type("JWS")
+          .set_algorithm("PS512")
+          .set_key_id(kid_1)
+          .set_payload_claim("username", picojson::value("impala"))
+          .sign(jwt::algorithm::ps512(rsa4096_pub_key_pem, rsa4096_priv_key_pem, "", ""));
+
+  // Verify the JWT token with our wrapper class which use public key retrieved from JWKS,
+  // and read username from the token.
+  JWTHelper::UniqueJWTDecodedToken decoded_token;
+  status = JWTHelper::Decode(token, decoded_token);
+  EXPECT_OK(status);
+  status = jwt_helper.Verify(decoded_token.get());
+  EXPECT_OK(status);
+  string username;
+  status = JWTHelper::GetCustomClaimUsername(decoded_token.get(), "username", username);
+  EXPECT_OK(status);
+  ASSERT_EQ("impala", username);
+}
+
+TEST(JwtUtilTest, VerifyJwtES256) {
+  // Cryptographic algorithm: ES256.
+  TempTestDataFile jwks_file(Substitute(jwks_ec_file_format, kid_1, "P-256",
+      ecdsa256_pub_key_jwk_x, ecdsa256_pub_key_jwk_y));
+  JWTHelper jwt_helper;
+  Status status = jwt_helper.Init(jwks_file.Filename());
+  EXPECT_OK(status);
+  const JsonWebKeySet* jwks = jwt_helper.GetJWKS();
+  ASSERT_EQ(1, jwks->GetECPublicKeyNum());
+
+  const JWTPublicKey* key1 = jwks->LookupECPublicKey(kid_1);
+  ASSERT_TRUE(key1 != nullptr);
+  ASSERT_EQ(ecdsa256_pub_key_pem, key1->get_key());
+
+  // Create a JWT token and sign it with ES256.
+  auto token = jwt::create()
+                   .set_issuer("auth0")
+                   .set_type("JWS")
+                   .set_algorithm("ES256")
+                   .set_key_id(kid_1)
+                   .set_payload_claim("username", picojson::value("impala"))
+                   .sign(jwt::algorithm::es256(
+                       ecdsa256_pub_key_pem, ecdsa256_priv_key_pem, "", ""));
+
+  // Verify the JWT token with jwt-cpp APIs directly.
+  auto jwt_decoded_token = jwt::decode(token);
+  auto verifier =
+      jwt::verify()
+          .allow_algorithm(jwt::algorithm::es256(ecdsa256_pub_key_pem, "", "", ""))
+          .with_issuer("auth0");
+  verifier.verify(jwt_decoded_token);
+
+  // Verify the JWT token with our wrapper class which use public key retrieved from JWKS,
+  // and read username from the token.
+  JWTHelper::UniqueJWTDecodedToken decoded_token;
+  status = JWTHelper::Decode(token, decoded_token);
+  EXPECT_OK(status);
+  status = jwt_helper.Verify(decoded_token.get());
+  EXPECT_OK(status);
+  string username;
+  status = JWTHelper::GetCustomClaimUsername(decoded_token.get(), "username", username);
+  EXPECT_OK(status);
+  ASSERT_EQ("impala", username);
+}
+
+TEST(JwtUtilTest, VerifyJwtES384) {
+  // Cryptographic algorithm: ES384.
+  TempTestDataFile jwks_file(Substitute(jwks_ec_file_format, kid_1, "P-384",
+      ecdsa384_pub_key_jwk_x, ecdsa384_pub_key_jwk_y));
+  JWTHelper jwt_helper;
+  Status status = jwt_helper.Init(jwks_file.Filename());
+  EXPECT_OK(status);
+  const JsonWebKeySet* jwks = jwt_helper.GetJWKS();
+  ASSERT_EQ(1, jwks->GetECPublicKeyNum());
+
+  const JWTPublicKey* key1 = jwks->LookupECPublicKey(kid_1);
+  ASSERT_TRUE(key1 != nullptr);
+  ASSERT_EQ(ecdsa384_pub_key_pem, key1->get_key());
+
+  // Create a JWT token and sign it with ES384.
+  auto token = jwt::create()
+                   .set_issuer("auth0")
+                   .set_type("JWS")
+                   .set_algorithm("ES384")
+                   .set_key_id(kid_1)
+                   .set_payload_claim("username", picojson::value("impala"))
+                   .sign(jwt::algorithm::es384(
+                       ecdsa384_pub_key_pem, ecdsa384_priv_key_pem, "", ""));
+
+  // Verify the JWT token with our wrapper class which use public key retrieved from JWKS,
+  // and read username from the token.
+  JWTHelper::UniqueJWTDecodedToken decoded_token;
+  status = JWTHelper::Decode(token, decoded_token);
+  EXPECT_OK(status);
+  status = jwt_helper.Verify(decoded_token.get());
+  EXPECT_OK(status);
+  string username;
+  status = JWTHelper::GetCustomClaimUsername(decoded_token.get(), "username", username);
+  EXPECT_OK(status);
+  ASSERT_EQ("impala", username);
+}
+
+TEST(JwtUtilTest, VerifyJwtES512) {
+  // Cryptographic algorithm: ES512.
+  TempTestDataFile jwks_file(Substitute(jwks_ec_file_format, kid_1, "P-521",
+      ecdsa521_pub_key_jwk_x, ecdsa521_pub_key_jwk_y));
+  JWTHelper jwt_helper;
+  Status status = jwt_helper.Init(jwks_file.Filename());
+  EXPECT_OK(status);
+  const JsonWebKeySet* jwks = jwt_helper.GetJWKS();
+  ASSERT_EQ(1, jwks->GetECPublicKeyNum());
+
+  const JWTPublicKey* key1 = jwks->LookupECPublicKey(kid_1);
+  ASSERT_TRUE(key1 != nullptr);
+  ASSERT_EQ(ecdsa521_pub_key_pem, key1->get_key());
+
+  // Create a JWT token and sign it with ES512.
+  auto token = jwt::create()
+                   .set_issuer("auth0")
+                   .set_type("JWS")
+                   .set_algorithm("ES512")
+                   .set_key_id(kid_1)
+                   .set_payload_claim("username", picojson::value("impala"))
+                   .sign(jwt::algorithm::es512(
+                       ecdsa521_pub_key_pem, ecdsa521_priv_key_pem, "", ""));
+
+  // Verify the JWT token with our wrapper class which use public key retrieved from JWKS,
+  // and read username from the token.
+  JWTHelper::UniqueJWTDecodedToken decoded_token;
+  status = JWTHelper::Decode(token, decoded_token);
+  EXPECT_OK(status);
+  status = jwt_helper.Verify(decoded_token.get());
+  EXPECT_OK(status);
+  string username;
+  status = JWTHelper::GetCustomClaimUsername(decoded_token.get(), "username", username);
+  EXPECT_OK(status);
+  ASSERT_EQ("impala", username);
+}
+
 TEST(JwtUtilTest, VerifyJwtNotVerifySignature) {
   // Create a JWT token and sign it with RS256.
   auto token =
diff --git a/be/src/util/jwt-util.cc b/be/src/util/jwt-util.cc
index 27c4a68..d1c27c1 100644
--- a/be/src/util/jwt-util.cc
+++ b/be/src/util/jwt-util.cc
@@ -149,14 +149,18 @@ class JWKSetParser {
 
     Status status;
     string key_type = boost::algorithm::to_lower_copy(it_kty->second);
-    if (key_type.compare("oct") == 0) {
+    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.compare("rsa") == 0) {
+    } 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);
     } else {
       return Status(Substitute("Unsupported kty: '$0'", key_type));
     }
@@ -253,11 +257,11 @@ Status HSJWTPublicKeyBuilder::CreateJWKPublicKey(
   Status status;
   JWTPublicKey* jwt_pub_key = nullptr;
   try {
-    if (algorithm.compare("hs256") == 0) {
+    if (algorithm == "hs256") {
       jwt_pub_key = new HS256JWTPublicKey(algorithm, it_k->second);
-    } else if (algorithm.compare("hs384") == 0) {
+    } else if (algorithm == "hs384") {
       jwt_pub_key = new HS384JWTPublicKey(algorithm, it_k->second);
-    } else if (algorithm.compare("hs512") == 0) {
+    } else if (algorithm == "hs512") {
       jwt_pub_key = new HS512JWTPublicKey(algorithm, it_k->second);
     } else {
       return Status(Substitute("Invalid 'alg' property value: '$0'", algorithm));
@@ -306,12 +310,18 @@ Status RSAJWTPublicKeyBuilder::CreateJWKPublicKey(
   Status status;
   JWTPublicKey* jwt_pub_key = nullptr;
   try {
-    if (algorithm.compare("rs256") == 0) {
+    if (algorithm == "rs256") {
       jwt_pub_key = new RS256JWTPublicKey(algorithm, pub_key);
-    } else if (algorithm.compare("rs384") == 0) {
+    } else if (algorithm == "rs384") {
       jwt_pub_key = new RS384JWTPublicKey(algorithm, pub_key);
-    } else if (algorithm.compare("rs512") == 0) {
+    } else if (algorithm == "rs512") {
       jwt_pub_key = new RS512JWTPublicKey(algorithm, pub_key);
+    } else if (algorithm == "ps256") {
+      jwt_pub_key = new PS256JWTPublicKey(algorithm, pub_key);
+    } else if (algorithm == "ps384") {
+      jwt_pub_key = new PS384JWTPublicKey(algorithm, pub_key);
+    } else if (algorithm == "ps512") {
+      jwt_pub_key = new PS512JWTPublicKey(algorithm, pub_key);
     } else {
       return Status(Substitute("Invalid 'alg' property value: '$0'", algorithm));
     }
@@ -356,8 +366,129 @@ bool RSAJWTPublicKeyBuilder::ConvertJwkToPem(
   }
   BIO_free(bio);
   RSA_free(rsa);
-  if (pub_key.empty()) return false;
-  return true;
+  return !pub_key.empty();
+}
+
+// Create a JWKPublicKey of EC (ES256, ES384 or ES512) from the JWK.
+Status ECJWTPublicKeyBuilder::CreateJWKPublicKey(
+    JsonKVMap& kv_map, JWTPublicKey** pub_key_out) {
+  // JWK Sample:
+  // {
+  //   "kty":"EC",
+  //   "crv":"P-256",
+  //   "x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
+  //   "y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
+  //   "kid":"Id that can be uniquely Identified"
+  // }
+  string algorithm;
+  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);
+    if (curve == "P-256") {
+      algorithm = "es256";
+      eccgrp = NID_X9_62_prime256v1;
+    } else if (curve == "P-384") {
+      algorithm = "es384";
+      eccgrp = NID_secp384r1;
+    } else if (curve == "P-521") {
+      algorithm = "es512";
+      eccgrp = NID_secp521r1;
+    } else {
+      return Status(Substitute("Unsupported crv: '$0'", curve));
+    }
+  } else {
+    auto it_alg = kv_map.find("alg");
+    if (it_alg == kv_map.end()) {
+      return Status("'alg' or 'crv' property is required");
+    }
+    algorithm = boost::algorithm::to_lower_copy(it_alg->second);
+    if (algorithm.empty()) {
+      return Status(Substitute("'alg' property must be a non-empty string"));
+    } else if (algorithm == "es256") {
+      // ECDSA using P-256 and SHA-256 (OBJ_txt2nid("prime256v1")).
+      eccgrp = NID_X9_62_prime256v1;
+    } else if (algorithm == "es384") {
+      // ECDSA using P-384 and SHA-384 (OBJ_txt2nid("secp384r1")).
+      eccgrp = NID_secp384r1;
+    } else if (algorithm == "es512") {
+      // ECDSA using P-521 and SHA-512 (OBJ_txt2nid("secp521r1")).
+      eccgrp = NID_secp521r1;
+    } else {
+      return Status(Substitute("Unsupported alg: '$0'", algorithm));
+    }
+  }
+
+  auto it_x = kv_map.find("x");
+  auto it_y = kv_map.find("y");
+  if (it_x == kv_map.end() || it_y == kv_map.end()) {
+    return Status("'x' and 'y' properties are required");
+  } else if (it_x->second.empty() || it_y->second.empty()) {
+    return Status("'x' and 'y' properties must be a non-empty string");
+  }
+  // Converts public key to PEM encoded form.
+  string pub_key;
+  if (!ConvertJwkToPem(eccgrp, it_x->second, it_y->second, pub_key)) {
+    return Status(
+        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") {
+      jwt_pub_key = new ES256JWTPublicKey(algorithm, pub_key);
+    } else if (algorithm == "es384") {
+      jwt_pub_key = new ES384JWTPublicKey(algorithm, pub_key);
+    } else {
+      DCHECK(algorithm == "es512");
+      jwt_pub_key = new ES512JWTPublicKey(algorithm, pub_key);
+    }
+  } catch (const jwt::error::ecdsa_exception& e) {
+    status = Status(TErrorCode::JWT_VERIFY_FAILED, Substitute("EC error: $0", e.what()));
+  } catch (const std::exception& e) {
+    status = Status(TErrorCode::JWT_VERIFY_FAILED,
+        Substitute("Failed to initialize verifier, error: $0", e.what()));
+  }
+  if (!status.ok()) return status;
+  *pub_key_out = jwt_pub_key;
+  return Status::OK();
+}
+
+// Convert public key of EC from JWK format to PEM encoded format by using OpenSSL APIs.
+bool ECJWTPublicKeyBuilder::ConvertJwkToPem(int eccgrp, const std::string& base64_x,
+    const std::string& base64_y, std::string& pub_key) {
+  pub_key.clear();
+  string ascii_x, ascii_y;
+  if (!WebSafeBase64Unescape(base64_x, &ascii_x)) return false;
+  if (!WebSafeBase64Unescape(base64_y, &ascii_y)) return false;
+  BIGNUM* x = BN_bin2bn((const unsigned char*)ascii_x.c_str(), ascii_x.size(), nullptr);
+  BIGNUM* y = BN_bin2bn((const unsigned char*)ascii_y.c_str(), 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) {
+      pub_key = (char*)desc;
+      // Remove last '\n'.
+      if (pub_key.length() > 0 && pub_key[pub_key.length() - 1] == '\n') {
+        pub_key.pop_back();
+      }
+    }
+  }
+
+cleanup:
+  if (bio != nullptr) BIO_free(bio);
+  EC_KEY_free(ecKey);
+  BN_free(x);
+  BN_free(y);
+  return !pub_key.empty();
 }
 
 //
@@ -422,6 +553,14 @@ void JsonWebKeySet::AddRSAPublicKey(std::string key_id, JWTPublicKey* jwk_pub_ke
   }
 }
 
+void JsonWebKeySet::AddECPublicKey(std::string key_id, 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);
+  } else {
+    LOG(WARNING) << "Duplicate key ID of JWK for EC public key: " << key_id;
+  }
+}
+
 const JWTPublicKey* JsonWebKeySet::LookupHSKey(const std::string& kid) const {
   auto find_it = hs_key_map_.find(kid);
   if (find_it == hs_key_map_.end()) {
@@ -440,6 +579,15 @@ const JWTPublicKey* JsonWebKeySet::LookupRSAPublicKey(const std::string& kid) co
   return find_it->second.get();
 }
 
+const JWTPublicKey* JsonWebKeySet::LookupECPublicKey(const std::string& kid) const {
+  auto find_it = ec_pub_key_map_.find(kid);
+  if (find_it == ec_pub_key_map_.end()) {
+    // Could not find key for the given key ID.
+    return nullptr;
+  }
+  return find_it->second.get();
+}
+
 //
 // JWTHelper member functions.
 //
@@ -509,15 +657,18 @@ Status JWTHelper::Verify(const JWTDecodedToken* decoded_token) const {
   try {
     string algorithm =
         boost::algorithm::to_lower_copy(decoded_token->decoded_jwt_.get_algorithm());
+    string prefix = algorithm.substr(0, 2);
     if (decoded_token->decoded_jwt_.has_key_id()) {
       // Get key id from token's header and use it to retrieve the public key from JWKS.
       std::string key_id = decoded_token->decoded_jwt_.get_key_id();
 
       const JWTPublicKey* pub_key = nullptr;
-      if (algorithm.substr(0, 2).compare("hs") == 0) {
+      if (prefix == "hs") {
         pub_key = jwks_->LookupHSKey(key_id);
-      } else if (algorithm.substr(0, 2).compare("rs") == 0) {
+      } else if (prefix == "rs" || prefix == "ps") {
         pub_key = jwks_->LookupRSAPublicKey(key_id);
+      } else if (prefix == "es") {
+        pub_key = jwks_->LookupECPublicKey(key_id);
       } else {
         return Status(TErrorCode::JWT_VERIFY_FAILED,
             Substitute("Unsupported cryptographic algorithm '$0' for JWT", algorithm));
@@ -532,10 +683,12 @@ Status JWTHelper::Verify(const JWTDecodedToken* decoded_token) const {
       // is no key id in the token's header. In this case, get all of public keys from
       // JWKS for the family of algorithms.
       const JsonWebKeySet::JWTPublicKeyMap* key_map = nullptr;
-      if (algorithm.substr(0, 2).compare("hs") == 0) {
+      if (prefix == "hs") {
         key_map = jwks_->GetAllHSKeys();
-      } else if (algorithm.substr(0, 2).compare("rs") == 0) {
+      } else if (prefix == "rs" || prefix == "ps") {
         key_map = jwks_->GetAllRSAPublicKeys();
+      } else if (prefix == "es") {
+        key_map = jwks_->GetAllECPublicKeys();
       } else {
         return Status(TErrorCode::JWT_VERIFY_FAILED,
             Substitute("Unsupported cryptographic algorithm '$0' for JWT", algorithm));

[impala] 01/02: IMPALA-10763: Min/max filters should be enabled on Z-order sorted columns

Posted by wz...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

wzhou pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git

commit 36d8e6766e3f1ce133a86b3edc5fee34e5e71cc7
Author: Qifan Chen <qc...@cloudera.com>
AuthorDate: Thu Jun 24 20:18:50 2021 -0400

    IMPALA-10763: Min/max filters should be enabled on Z-order sorted columns
    
    This patch enables min/max filtering on any Z-order sort-by columns
    by default.
    
    Since the column stats for a row group or a page is computed from the
    column values stored in the row group or the page, the current
    infrastructure for min/max filtering works for the Z-order out of box.
    The fact that these column values are ordered by Z-order is
    orthogonal to the work of min/max filtering.
    
    By default, the new feature is enabled. Set the existing control knob
    minmax_filter_sorted_columns to false to turn it off.
    
    Testing
      1. Added new z-order related sort column tests in
         overlap_min_max_filters_on_sorted_columns.test;
      2. Ran core-test.
    
    Change-Id: I2a528ffbd0e333721ef38b4be7d4ddcdbf188adf
    Reviewed-on: http://gerrit.cloudera.org:8080/17635
    Reviewed-by: Impala Public Jenkins <im...@cloudera.com>
    Tested-by: Impala Public Jenkins <im...@cloudera.com>
---
 be/src/service/query-options.cc                    |  2 +-
 .../java/org/apache/impala/catalog/FeFsTable.java  | 34 ++++++--
 .../org/apache/impala/planner/HdfsScanNode.java    | 43 +++++-----
 .../overlap_min_max_filters_on_sorted_columns.test | 95 ++++++++++++++++++++++
 4 files changed, 146 insertions(+), 28 deletions(-)

diff --git a/be/src/service/query-options.cc b/be/src/service/query-options.cc
index e92e81f..4e431a2 100644
--- a/be/src/service/query-options.cc
+++ b/be/src/service/query-options.cc
@@ -1097,7 +1097,7 @@ Status impala::SetQueryOption(const string& key, const string& value,
       }
       case TImpalaQueryOptions::DELETE_STATS_IN_TRUNCATE: {
         query_options->__set_delete_stats_in_truncate(IsTrue(value));
-         break;
+        break;
       }
       case TImpalaQueryOptions::MINMAX_FILTER_SORTED_COLUMNS: {
         query_options->__set_minmax_filter_sorted_columns(IsTrue(value));
diff --git a/fe/src/main/java/org/apache/impala/catalog/FeFsTable.java b/fe/src/main/java/org/apache/impala/catalog/FeFsTable.java
index 74a539b..0aadb73 100644
--- a/fe/src/main/java/org/apache/impala/catalog/FeFsTable.java
+++ b/fe/src/main/java/org/apache/impala/catalog/FeFsTable.java
@@ -290,19 +290,37 @@ public interface FeFsTable extends FeTable {
   ListMap<TNetworkAddress> getHostIndex();
 
   /**
-   * Check if 'col_name' names the leading sort by column by searching the 'sort.columns'
-   * table property.
+   * Check if 'col_name' appears in the list of sort-by columns by searching the
+   * 'sort.columns' table property and return the index in the list if so. Return
+   * -1 otherwise.
    */
-  default boolean isLeadingSortByColumn(String col_name) {
+  default int getSortByColumnIndex(String col_name) {
     // Get the names of all sort by columns (specified in the SORT BY clause in
     // CREATE TABLE DDL) from TBLPROPERTIES.
     Map<String, String> parameters = getMetaStoreTable().getParameters();
-    if (parameters == null) return false;
+    if (parameters == null) return -1;
     String sort_by_columns_string = parameters.get("sort.columns");
-    if (sort_by_columns_string == null) return false;
-    String[] sort_by_columns = sort_by_columns_string.split(",", -1);
-    if (sort_by_columns == null) return false;
-    return sort_by_columns.length > 0 && sort_by_columns[0].equals(col_name);
+    if (sort_by_columns_string == null) return -1;
+    String[] sort_by_columns = sort_by_columns_string.split(",");
+    if (sort_by_columns == null) return -1;
+    for (int i = 0; i < sort_by_columns.length; i++) {
+      if (sort_by_columns[i].equals(col_name)) return i;
+    }
+    return -1;
+  }
+
+  /**
+   * Check if 'col_name' names the leading sort-by column.
+   */
+  default boolean isLeadingSortByColumn(String col_name) {
+    return getSortByColumnIndex(col_name) == 0;
+  }
+
+  /**
+   * Check if 'col_name' appears in the list of sort-by columns.
+   */
+  default boolean isSortByColumn(String col_name) {
+    return getSortByColumnIndex(col_name) >= 0;
   }
 
   /**
diff --git a/fe/src/main/java/org/apache/impala/planner/HdfsScanNode.java b/fe/src/main/java/org/apache/impala/planner/HdfsScanNode.java
index 9ef52bb..00af40e 100644
--- a/fe/src/main/java/org/apache/impala/planner/HdfsScanNode.java
+++ b/fe/src/main/java/org/apache/impala/planner/HdfsScanNode.java
@@ -91,6 +91,7 @@ import org.apache.impala.thrift.TScanRange;
 import org.apache.impala.thrift.TScanRangeLocation;
 import org.apache.impala.thrift.TScanRangeLocationList;
 import org.apache.impala.thrift.TScanRangeSpec;
+import org.apache.impala.thrift.TSortingOrder;
 import org.apache.impala.thrift.TTableStats;
 import org.apache.impala.util.BitUtil;
 import org.apache.impala.util.ExecutorMembershipSnapshot;
@@ -725,21 +726,34 @@ public class HdfsScanNode extends ScanNode {
   /**
    * Determine if a runtime filter should be allowed given the relevant query options.
    */
-  private boolean allowRuntimeFilter(TQueryOptions queryOptions,
-      boolean isBoundByPartitionColumns, boolean isLeadingLexicalSortedColumn) {
+  private boolean allowMinMaxFilter(FeTable table, Column column,
+      TQueryOptions queryOptions, boolean isBoundByPartitionColumns) {
+    if (column == null || table == null || !(table instanceof FeFsTable)) return false;
+    FeFsTable feFsTable = (FeFsTable) table;
+
     boolean minmaxOnPartitionColumns = queryOptions.isMinmax_filter_partition_columns();
     boolean minmaxOnSortedColumns = queryOptions.isMinmax_filter_sorted_columns();
 
+    TSortingOrder sortOrder = feFsTable.getSortOrderForSortByColumn();
+    if (sortOrder != null) {
+      // The table is sorted.
+      if (sortOrder == TSortingOrder.LEXICAL) {
+        // If the table is sorted in lexical order, allow it if the column is a
+        // leading sort-by column and filtering on sorted column is enabled.
+        return feFsTable.isLeadingSortByColumn(column.getName()) && minmaxOnSortedColumns;
+      } else {
+        // Must be Z-order. Allow it if it is one of the sort-by columns and filtering
+        // on sorted column is enabled.
+        Preconditions.checkState(sortOrder == TSortingOrder.ZORDER);
+        return feFsTable.isSortByColumn(column.getName()) && minmaxOnSortedColumns;
+      }
+    }
+
     // Allow min/max filters on partition columns only when enabled.
     if (isBoundByPartitionColumns) {
       return minmaxOnPartitionColumns;
     }
 
-    // Allow min/max filters on sorted columns only when enabled.
-    if (isLeadingLexicalSortedColumn) {
-      return minmaxOnSortedColumns;
-    }
-
     // Allow min/max filters if the threshold value > 0.0.
     return queryOptions.getMinmax_filter_threshold() > 0.0;
   }
@@ -766,19 +780,10 @@ public class HdfsScanNode extends ScanNode {
         return false;
     }
 
-    boolean isLeadingLexicalSortedColumn = false;
     Column column = slotRefInScan.getDesc().getColumn();
-    if (column != null) {
-      TupleDescriptor tDesc = slotRefInScan.getDesc().getParent();
-      FeTable table = tDesc.getTable();
-      if (table != null && table instanceof FeFsTable) {
-        isLeadingLexicalSortedColumn =
-            ((FeFsTable) table).isLeadingSortByColumn(column.getName())
-            && ((FeFsTable) table).IsLexicalSortByColumn();
-      }
-    }
-    if (!allowRuntimeFilter(analyzer.getQueryOptions(), isBoundByPartitionColumns,
-            isLeadingLexicalSortedColumn)) {
+    FeTable table = slotRefInScan.getDesc().getParent().getTable();
+    if (!allowMinMaxFilter(
+            table, column, analyzer.getQueryOptions(), isBoundByPartitionColumns)) {
       return false;
     }
 
diff --git a/testdata/workloads/functional-query/queries/QueryTest/overlap_min_max_filters_on_sorted_columns.test b/testdata/workloads/functional-query/queries/QueryTest/overlap_min_max_filters_on_sorted_columns.test
index ffb1a18..d30af02 100644
--- a/testdata/workloads/functional-query/queries/QueryTest/overlap_min_max_filters_on_sorted_columns.test
+++ b/testdata/workloads/functional-query/queries/QueryTest/overlap_min_max_filters_on_sorted_columns.test
@@ -252,3 +252,98 @@ where a.a = b.float_col;
 ---- RUNTIME_PROFILE
 aggregation(SUM, NumRuntimeFilteredPages): 0
 ====
+---- QUERY
+###################################################
+# Create a version of store_sales with z-order sort
+# on primary key and load it with 27587 rows.
+###################################################
+drop table if exists store_sales_zorder;
+create table store_sales_zorder (
+ss_sold_time_sk INT,
+ss_item_sk BIGINT,
+ss_customer_sk INT,
+ss_cdemo_sk INT,
+ss_hdemo_sk INT,
+ss_addr_sk INT,
+ss_store_sk INT,
+ss_promo_sk INT,
+ss_ticket_number BIGINT,
+ss_quantity INT,
+ss_wholesale_cost DECIMAL(7,2),
+ss_list_price DECIMAL(7,2),
+ss_sales_price DECIMAL(7,2),
+ss_ext_discount_amt DECIMAL(7,2),
+ss_ext_sales_price DECIMAL(7,2),
+ss_ext_wholesale_cost DECIMAL(7,2),
+ss_ext_list_price DECIMAL(7,2),
+ss_ext_tax DECIMAL(7,2),
+ss_coupon_amt DECIMAL(7,2),
+ss_net_paid DECIMAL(7,2),
+ss_net_paid_inc_tax DECIMAL(7,2),
+ss_net_profit DECIMAL(7,2),
+PRIMARY KEY (ss_item_sk, ss_ticket_number)
+)
+PARTITIONED BY (ss_sold_date_sk INT)
+sort by zorder(ss_item_sk, ss_ticket_number)
+STORED AS PARQUET;
+set PARQUET_PAGE_ROW_COUNT_LIMIT=1000;
+insert into store_sales_zorder partition (ss_sold_date_sk)
+select * from tpcds_parquet.store_sales;
+====
+---- QUERY
+###################################################
+# A minmax filter should be generated for the
+# z-order column ss_item_sk out of box.
+###################################################
+set explain_level=3;
+explain select straight_join a.ss_sold_time_sk from
+store_sales_zorder a join [SHUFFLE] tpcds_parquet.store_sales b
+on a.ss_item_sk = b.ss_item_sk
+where b.ss_customer_sk < 10 and b.ss_addr_sk < 20;
+---- RESULTS: VERIFY_IS_SUBSET
+row_regex:.* RF001\[min_max\] -. .\.ss_item_sk.*
+====
+---- QUERY
+###################################################
+# A minmax filter should be generated for the
+# z-order column ss_ticket_number out of box.
+###################################################
+set explain_level=3;
+explain select straight_join a.ss_sold_time_sk from
+store_sales_zorder a join [SHUFFLE] tpcds_parquet.store_sales b
+on a.ss_ticket_number = b.ss_ticket_number
+where b.ss_customer_sk < 10 and b.ss_addr_sk < 20;
+---- RESULTS: VERIFY_IS_SUBSET
+row_regex:.* RF001\[min_max\] -. .\.ss_ticket_number.*
+====
+---- QUERY
+###################################################
+# A minmax filter should not be generated for
+# column ss_item_sk which is not z-ordered.
+###################################################
+set explain_level=3;
+explain select straight_join a.ss_sold_time_sk from
+store_sales_zorder a join [SHUFFLE] tpcds_parquet.store_sales b
+on a.ss_cdemo_sk = b.ss_cdemo_sk
+where b.ss_customer_sk < 10 and b.ss_addr_sk < 20;
+---- RESULTS: VERIFY_IS_NOT_IN
+row_regex:.* RF001\[min_max\] -. .\.ss_cdemo_sk.*
+====
+---- QUERY
+###################################################
+# Run a query that demonstrates the min/max filter
+# helps reduces # of pages:
+# sum(NumRuntimeFilteredPages) = 28
+###################################################
+set minmax_filtering_level=page;
+set minmax_filter_threshold=0.9;
+SET RUNTIME_FILTER_WAIT_TIME_MS=$RUNTIME_FILTER_WAIT_TIME_MS;
+select straight_join count(a.ss_sold_time_sk) from
+store_sales_zorder a join [SHUFFLE] tpcds_parquet.item b
+on a.ss_item_sk = b.i_item_sk
+where i_manufact_id = 1 and i_current_price < 1.0;
+---- RESULTS
+540
+---- RUNTIME_PROFILE
+aggregation(SUM, NumRuntimeFilteredPages): 28
+====