mesos-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ti...@apache.org
Subject mesos git commit: Added support for RS256 algo to JWT processing in libprocess.
Date Fri, 18 May 2018 22:28:10 GMT
Repository: mesos
Updated Branches:
  refs/heads/master 7ecafb126 -> 9fa72c549


Added support for RS256 algo to JWT processing in libprocess.

Review: https://reviews.apache.org/r/66621/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/9fa72c54
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/9fa72c54
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/9fa72c54

Branch: refs/heads/master
Commit: 9fa72c54981127b438b088cc67e58267f28e401c
Parents: 7ecafb1
Author: Clement Michaud <clement.michaud34@gmail.com>
Authored: Sat May 19 00:26:04 2018 +0200
Committer: Till Toenshoff <toenshoff@me.com>
Committed: Sat May 19 00:26:15 2018 +0200

----------------------------------------------------------------------
 3rdparty/libprocess/include/process/jwt.hpp     |  38 +-
 .../include/process/ssl/utilities.hpp           |  54 +++
 3rdparty/libprocess/src/jwt.cpp                 |  90 +++-
 3rdparty/libprocess/src/ssl/utilities.cpp       | 115 ++++-
 3rdparty/libprocess/src/tests/jwt_keys.hpp      |  74 ++++
 3rdparty/libprocess/src/tests/jwt_tests.cpp     | 417 +++++++++++++++----
 6 files changed, 696 insertions(+), 92 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/9fa72c54/3rdparty/libprocess/include/process/jwt.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/include/process/jwt.hpp b/3rdparty/libprocess/include/process/jwt.hpp
index 768cbf6..62bf5c2 100644
--- a/3rdparty/libprocess/include/process/jwt.hpp
+++ b/3rdparty/libprocess/include/process/jwt.hpp
@@ -16,6 +16,8 @@
 #include <ostream>
 #include <string>
 
+#include <openssl/rsa.h>
+
 #include <stout/json.hpp>
 #include <stout/option.hpp>
 #include <stout/try.hpp>
@@ -45,7 +47,7 @@ public:
  * A JSON Web Token (JWT) implementation.
  * @see <a href="https://tools.ietf.org/html/rfc7519">RFC 7519</a>
  *
- * This implementation supports the 'none' and 'HS256' algorithms.
+ * This implementation supports the 'none', 'RS256' and 'HS256' algorithms.
  * Header parameters other than 'alg' and 'typ' aren't parsed. To comply
  * with RFC 7515, headers with 'crit' parameter are invalid.
  * Currently, only the 'exp' standard claim is validated. Applications
@@ -58,7 +60,8 @@ public:
   enum class Alg
   {
     None,
-    HS256
+    HS256,
+    RS256
   };
 
   struct Header
@@ -90,6 +93,19 @@ public:
       const std::string& secret);
 
   /**
+   * Parse a JWT and validate its RS256 signature.
+   *
+   * @param token The JWT to parse.
+   * @param publicKey The public key to validate the signature with.
+   *
+   * @return The validated JWT representation if successful otherwise an
+   *     Error.
+   */
+  static Try<JWT, JWTError> parse(
+      const std::string& token,
+      std::shared_ptr<RSA> publicKey);
+
+  /**
    * Create an unsecured JWT.
    *
    * @param payload The payload of the JWT.
@@ -105,7 +121,7 @@ public:
    * When creating a payload keep in mind that of the standard claims
    * currently only 'exp' is validated during parsing.
    *
-   * @param payload The payload of the JWT
+   * @param payload The payload of the JWT.
    * @param secret The secret to sign the JWT with.
    *
    * @return The signed JWT representation if successful otherwise an
@@ -115,6 +131,22 @@ public:
       const JSON::Object& payload,
       const std::string& secret);
 
+  /**
+   * Create a JWT with a RS256 signature.
+   *
+   * When creating a payload keep in mind that of the standard claims
+   * currently only 'exp' is validated during parsing.
+   *
+   * @param payload The payload of the JWT.
+   * @param privateKey The private key to sign the JWT with.
+   *
+   * @return The signed JWT representation if successful otherwise an
+   *     Error.
+   */
+  static Try<JWT, JWTError> create(
+      const JSON::Object& payload,
+      std::shared_ptr<RSA> privateKey);
+
   const Header header;
   const JSON::Object payload;
   const Option<std::string> signature;

http://git-wip-us.apache.org/repos/asf/mesos/blob/9fa72c54/3rdparty/libprocess/include/process/ssl/utilities.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/include/process/ssl/utilities.hpp b/3rdparty/libprocess/include/process/ssl/utilities.hpp
index b7cc31c..9f1a520 100644
--- a/3rdparty/libprocess/include/process/ssl/utilities.hpp
+++ b/3rdparty/libprocess/include/process/ssl/utilities.hpp
@@ -130,6 +130,60 @@ Try<std::string> generate_hmac_sha256(
     const std::string& message,
     const std::string& key);
 
+
+/**
+ * Helper function converting a PEM representation of a private key
+ * into a RSA private key usable by openssl.
+ *
+ * @param pem The PEM representation of the private key.
+ *
+ * @return A shared pointer to an openssl-compatible private key if
+ *     successful otherwise an Error.
+ */
+Try<std::shared_ptr<RSA>> pem_to_rsa_private_key(const std::string& pem);
+
+
+/**
+ * Helper function converting a PEM representation of a public key
+ * into a RSA private key usable by openssl.
+ *
+ * @param pem The PEM representation of the public key.
+ *
+ * @return A shared pointer to an openssl-compatible private key if
+ *     successful otherwise an Error.
+ */
+Try<std::shared_ptr<RSA>> pem_to_rsa_public_key(const std::string& pem);
+
+
+/**
+ * Create a signature of a message with a private key and the RSA SHA256
+ * algorithm.
+ *
+ * @param message The message to sign.
+ * @param private_key The private key used to sign the message.
+ *
+ * @return The signature of message if successful otherwise an Error.
+ */
+Try<std::string> sign_rsa_sha256(
+    const std::string& message,
+    std::shared_ptr<RSA> private_key);
+
+
+/**
+ * Verify the signature of a message with a public key and the RSA SHA256
+ * algorithm.
+ *
+ * @param message The message to verify signature of.
+ * @param signature The signature to verify.
+ * @param public_key The public key used to verify the signature.
+ *
+ * @return Nothing if the signature is valid otherwise an Error.
+ */
+Try<Nothing> verify_rsa_sha256(
+    const std::string& message,
+    const std::string& signature,
+    std::shared_ptr<RSA> public_key);
+
 } // namespace openssl {
 } // namespace network {
 } // namespace process {

http://git-wip-us.apache.org/repos/asf/mesos/blob/9fa72c54/3rdparty/libprocess/src/jwt.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/src/jwt.cpp b/3rdparty/libprocess/src/jwt.cpp
index 921031e..4477ddd 100644
--- a/3rdparty/libprocess/src/jwt.cpp
+++ b/3rdparty/libprocess/src/jwt.cpp
@@ -12,6 +12,7 @@
 
 #include <process/jwt.hpp>
 
+#include <memory>
 #include <vector>
 
 #include <process/clock.hpp>
@@ -28,12 +29,14 @@ namespace authentication {
 using process::Clock;
 
 using process::network::openssl::generate_hmac_sha256;
+using process::network::openssl::sign_rsa_sha256;
+using process::network::openssl::verify_rsa_sha256;
 
 using std::ostream;
+using std::shared_ptr;
 using std::string;
 using std::vector;
 
-
 namespace {
 
 Try<JSON::Object> decode(const string& component)
@@ -100,6 +103,8 @@ Try<JWT::Header> parse_header(const string& component)
     alg = JWT::Alg::None;
   } else if (alg_value == "HS256") {
     alg = JWT::Alg::HS256;
+  } else if (alg_value == "RS256") {
+    alg = JWT::Alg::RS256;
   } else {
     return Error("Unsupported token algorithm: " + alg_value);
   }
@@ -257,6 +262,60 @@ Try<JWT, JWTError> JWT::parse(const string& token, const string&
secret)
 }
 
 
+Try<JWT, JWTError> JWT::parse(const string& token, shared_ptr<RSA> publicKey)
+{
+  CHECK_NOTNULL(publicKey.get());
+
+  const vector<string> components = strings::split(token, ".");
+
+  if (components.size() != 3) {
+    return JWTError(
+        "Expected 3 components in token, got " + stringify(components.size()),
+        JWTError::Type::INVALID_TOKEN);
+  }
+
+  Try<JWT::Header> header = parse_header(components[0]);
+
+  if (header.isError()) {
+    return JWTError(header.error(), JWTError::Type::INVALID_TOKEN);
+  }
+
+  if (header->alg != JWT::Alg::RS256) {
+    return JWTError(
+        "Token 'alg' value \"" + stringify(header->alg) +
+        "\" does not match, expected \"RS256\"",
+        JWTError::Type::INVALID_TOKEN);
+  }
+
+  Try<JSON::Object> payload = parse_payload(components[1]);
+
+  if (payload.isError()) {
+    return JWTError(payload.error(), JWTError::Type::INVALID_TOKEN);
+  }
+
+  const Try<string> signature = base64::decode_url_safe(components[2]);
+
+  if (signature.isError()) {
+    return JWTError(
+        "Failed to base64url-decode token signature: " + signature.error(),
+        JWTError::Type::INVALID_TOKEN);
+  }
+
+  // Validate RSA SHA-256 signature.
+
+  const Try<Nothing> valid = verify_rsa_sha256(
+      components[0] + "." + components[1], signature.get(), publicKey);
+
+  if (valid.isError()) {
+    return JWTError(
+        "Failed to verify token: " + valid.error(),
+        JWTError::Type::INVALID_TOKEN);
+  }
+
+  return JWT(header.get(), payload.get(), signature.get());
+}
+
+
 Try<JWT, JWTError> JWT::create(const JSON::Object& payload)
 {
   const Header header{Alg::None, "JWT"};
@@ -286,6 +345,32 @@ Try<JWT, JWTError> JWT::create(
 }
 
 
+Try<JWT, JWTError> JWT::create(
+    const JSON::Object& payload,
+    shared_ptr<RSA> privateKey)
+{
+  CHECK_NOTNULL(privateKey.get());
+
+  const Header header{Alg::RS256, "JWT"};
+
+  const string message = base64::encode_url_safe(stringify(header), false) +
+    "." + base64::encode_url_safe(stringify(payload), false);
+
+  const Try<string> signature = sign_rsa_sha256(message, privateKey);
+
+  if (signature.isError()) {
+    return JWTError(
+        "Failed to generate RSA signature: " + signature.error(),
+        JWTError::Type::UNKNOWN);
+  }
+
+  return JWT(
+    header,
+    payload,
+    base64::encode_url_safe(signature.get(), false));
+}
+
+
 JWT::JWT(
     const Header& _header,
     const JSON::Object& _payload,
@@ -302,6 +387,9 @@ ostream& operator<<(ostream& stream, const JWT::Alg&
alg)
     case JWT::Alg::HS256:
       stream << "HS256";
       break;
+    case JWT::Alg::RS256:
+      stream << "RS256";
+      break;
   }
 
   return stream;

http://git-wip-us.apache.org/repos/asf/mesos/blob/9fa72c54/3rdparty/libprocess/src/ssl/utilities.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/src/ssl/utilities.cpp b/3rdparty/libprocess/src/ssl/utilities.cpp
index 4d3727d..36a4cb1 100644
--- a/3rdparty/libprocess/src/ssl/utilities.cpp
+++ b/3rdparty/libprocess/src/ssl/utilities.cpp
@@ -12,6 +12,10 @@
 
 #include <process/ssl/utilities.hpp>
 
+#include <memory>
+#include <string>
+#include <vector>
+
 #include <openssl/err.h>
 #include <openssl/evp.h>
 #include <openssl/hmac.h>
@@ -19,8 +23,6 @@
 #include <openssl/x509.h>
 #include <openssl/x509v3.h>
 
-#include <string>
-
 #include <stout/check.hpp>
 #include <stout/net.hpp>
 #include <stout/stringify.hpp>
@@ -32,6 +34,10 @@ namespace process {
 namespace network {
 namespace openssl {
 
+using std::shared_ptr;
+using std::string;
+using std::vector;
+
 Try<EVP_PKEY*> generate_private_rsa_key(int bits, unsigned long _exponent)
 {
   // Allocate the in-memory structure for the private key.
@@ -92,7 +98,7 @@ Try<X509*> generate_x509(
     const Option<X509*>& parent_certificate,
     int serial,
     int days,
-    Option<std::string> hostname,
+    Option<string> hostname,
     const Option<net::IP>& ip)
 {
   Option<X509_NAME*> issuer_name = None();
@@ -147,7 +153,7 @@ Try<X509*> generate_x509(
 
   // Figure out our hostname if one was not provided.
   if (hostname.isNone()) {
-    const Try<std::string> _hostname = net::hostname();
+    const Try<string> _hostname = net::hostname();
     if (_hostname.isError()) {
       X509_free(x509);
       return Error("Failed to determine hostname");
@@ -344,9 +350,9 @@ Try<Nothing> write_certificate_file(X509* x509, const Path&
path)
 }
 
 
-Try<std::string> generate_hmac_sha256(
-  const std::string& message,
-  const std::string& key)
+Try<string> generate_hmac_sha256(
+  const string& message,
+  const string& key)
 {
   unsigned int md_len = 0;
 
@@ -363,10 +369,101 @@ Try<std::string> generate_hmac_sha256(
     const char* reason = ERR_reason_error_string(ERR_get_error());
 
     return Error(
-        "HMAC failed" + (reason == nullptr ? "" : ": " + std::string(reason)));
+        "HMAC failed" + (reason == nullptr ? "" : ": " + string(reason)));
+  }
+
+  return string(reinterpret_cast<char*>(rc), md_len);
+}
+
+
+template<typename Reader>
+Try<shared_ptr<RSA>> pem_to_rsa(const string& pem, Reader reader)
+{
+  BIO *bio = BIO_new_mem_buf(pem.c_str(), -1);
+  if (bio == nullptr) {
+    return Error("Failed to create RSA key bio");
+  }
+  RSA *rsa = reader(bio, nullptr, nullptr, nullptr);
+  BIO_free(bio);
+  if (rsa == nullptr) {
+    return Error("Failed to create RSA from key bio");
+  }
+  return shared_ptr<RSA>(rsa, RSA_free);
+}
+
+
+Try<shared_ptr<RSA>> pem_to_rsa_private_key(const string& pem)
+{
+  return pem_to_rsa(pem, PEM_read_bio_RSAPrivateKey);
+}
+
+
+Try<shared_ptr<RSA>> pem_to_rsa_public_key(const string& pem)
+{
+  return pem_to_rsa(pem, PEM_read_bio_RSA_PUBKEY);
+}
+
+
+Try<string> sign_rsa_sha256(
+    const string& message,
+    shared_ptr<RSA> private_key)
+{
+  vector<unsigned char> signatureData;
+  signatureData.reserve(RSA_size(private_key.get()));
+  unsigned int signatureLength;
+  unsigned char hash[SHA256_DIGEST_LENGTH];
+
+  SHA256(
+    reinterpret_cast<const unsigned char*>(message.c_str()),
+    message.size(),
+    hash);
+
+  int success = RSA_sign(
+    NID_sha256,
+    hash,
+    SHA256_DIGEST_LENGTH,
+    signatureData.data(),
+    &signatureLength,
+    private_key.get());
+
+  if (success == 0) {
+    const char* reason = ERR_reason_error_string(ERR_get_error());
+    return Error("Failed to sign the message" +
+      (reason == nullptr ? "" : ": " + string(reason)));
   }
 
-  return std::string(reinterpret_cast<char*>(rc), md_len);
+  return string(
+    reinterpret_cast<char*>(signatureData.data()),
+    signatureLength);
+}
+
+
+Try<Nothing> verify_rsa_sha256(
+    const string& message,
+    const string& signature,
+    shared_ptr<RSA> public_key)
+{
+  unsigned char hash[SHA256_DIGEST_LENGTH];
+
+  SHA256(
+    reinterpret_cast<const unsigned char*>(message.c_str()),
+    message.size(),
+    hash);
+
+  int success = RSA_verify(
+    NID_sha256,
+    hash,
+    SHA256_DIGEST_LENGTH,
+    reinterpret_cast<const unsigned char*>(signature.data()),
+    signature.size(),
+    public_key.get());
+
+  if (success == 0) {
+    const char* reason = ERR_reason_error_string(ERR_get_error());
+    return Error("Failed to verify message signature" +
+      (reason == nullptr ? "" : ": " + string(reason)));
+  }
+  return Nothing();
 }
 
 } // namespace openssl {

http://git-wip-us.apache.org/repos/asf/mesos/blob/9fa72c54/3rdparty/libprocess/src/tests/jwt_keys.hpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/src/tests/jwt_keys.hpp b/3rdparty/libprocess/src/tests/jwt_keys.hpp
new file mode 100644
index 0000000..0fb9a98
--- /dev/null
+++ b/3rdparty/libprocess/src/tests/jwt_keys.hpp
@@ -0,0 +1,74 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License
+
+#ifndef __JWT_KEYS_HPP__
+#define __JWT_KEYS_HPP__
+
+/**
+ * Private and public keys used for JWT tests.
+ * They have been generated by openssl.
+ */
+
+const char PRIVATE_KEY[] =
+    "-----BEGIN PRIVATE KEY-----\n"
+    "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC7Fzlng3c3SGcH\n"
+    "W0Icz5na8hb1dRg9G5jdi2+sfcssn05zbTl54y7Jl1SxEYqyu6RMvU/eWNeYowtO\n"
+    "lgeyTHGdy9kCLYTwapRh4wCSQdpY5cLwWhoVV5VzR4v/fTfViNFFOZArKUy3juBg\n"
+    "0iHcGETS4/8mpWKRJbfuzrjo+zxqFtGlFvj0knALVW9DKAd4DsgdAfelyAng5nC/\n"
+    "w1rKzCoEfPDTkZp3CA5dr0chImfHDc0jIn8xQpfzOZiH8oxobbAEPZ/POcvaodwQ\n"
+    "9aE1Pwp2cBGZ9PewpDbg+K9T+mtEnaQmJfCOqalKme0nIOy8peJWAT4hyhrChj6q\n"
+    "Ls/+hw5nAgMBAAECggEAXatnBjh/+6G4U6qREzOtaD1E2Wfi1tV2V5V0N/xTOOgZ\n"
+    "sxjAahIgXrXxpSWPN6VSwUkXL89zQex/wLzE5bP4PnTNFZYMtQHngIrSwmdOFqwS\n"
+    "SZwS6xSKssjjgusChVqWy/3h/HoU+uIB3PfYFAXij2OvX332N42W5W2CjsMaoFIp\n"
+    "Fe0i/PkPDoSCYok/DOzMTbXUITUb3fp/Fnlk3onk2+AOVw/jxL/u5teYEFMH8NX9\n"
+    "l3t0Bon3X98mq179nJGgKRSrRglhbHkObqudC2Trz+HoRrvNjQjYEKvloQjd4Fgy\n"
+    "3xV3nFAUI9WgT6OHp5uHDPoDybqHDSp/gn/p+T8JoQKBgQDkQm+0DAglSlMLazeO\n"
+    "NxYcg6TLRwZkJAfJwoBzd5/l4UhwAhLKlpGrCPeiTVtVS1+8/sQ4pjy94vQyzWDz\n"
+    "7YA/9nzsVxj4+U1K/jD3vhZWj9xXbpedW1093zEavlV/F0/enkcgsIwnQ6mWg9sY\n"
+    "ziKyOT/yAqFRWvDjmPcB/miQswKBgQDR0/PgvsCQCgonXqLovk3j2QMHrUwr+I1o\n"
+    "yLQBwjiRHTS8Ts9GjmMyn+jOHb90kjpzB24iE5LtvdSvZDP2s2uLPv4dpjesGvPM\n"
+    "huCoaYF2+emkUZgNlfvwmaN0ZtgEymuc10rVWKwAI8Ll93kzr1KLhFOQUDtJfCsu\n"
+    "NqQX1Gt9fQKBgC6GVBpQsYBYS+Dx85rrI3igZICCc40JwwSevmvKoC7M4mTiJ05f\n"
+    "rkU8SK0uM0WJXXQ6QWiCibLyhW+taOuPJyriZMgPYKmuttBoSzbT6d2u6OxxQDn0\n"
+    "m2a3DV00Gl0TNVZc0IabNZXzNqfVLF079tp4zM3ZN2RLsvnQ/dfMMSf9AoGADVAD\n"
+    "QxkXIoxghIrujxGz42bbWFtYX9nPLvy83vexmxNdSy083V8fUBDxNlKQ2RaF+tJX\n"
+    "3HWddtP6cH5NBbPweM8wVDU9hv/Ww/0yt7yp6CCHAFPk78e6SlOVGUeFIRiupy7J\n"
+    "oquTjha2wNxopizTceKdYqSUfl8QZkg1NQXXJAkCgYAbObXvDbWEpvem/9YQXW8C\n"
+    "Onts+PKx5dqpUx5vWiJyIIh04cXlHQIeu/P673NPTiORMRO/IFulk6jjS5XE8Aah\n"
+    "p4D755HSFUgQHG+5AF5SMq4cI4Agt97awyH7RHYVoSealSN0yKV4ON7Cs5mRX4Ze\n"
+    "f8lvmXYQjTzoJ1pPjwJE1Q==\n"
+    "-----END PRIVATE KEY-----\n";
+
+const char PUBLIC_KEY[] =
+    "-----BEGIN PUBLIC KEY-----\n"
+    "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuxc5Z4N3N0hnB1tCHM+Z\n"
+    "2vIW9XUYPRuY3YtvrH3LLJ9Oc205eeMuyZdUsRGKsrukTL1P3ljXmKMLTpYHskxx\n"
+    "ncvZAi2E8GqUYeMAkkHaWOXC8FoaFVeVc0eL/3031YjRRTmQKylMt47gYNIh3BhE\n"
+    "0uP/JqVikSW37s646Ps8ahbRpRb49JJwC1VvQygHeA7IHQH3pcgJ4OZwv8Nayswq\n"
+    "BHzw05GadwgOXa9HISJnxw3NIyJ/MUKX8zmYh/KMaG2wBD2fzznL2qHcEPWhNT8K\n"
+    "dnARmfT3sKQ24PivU/prRJ2kJiXwjqmpSpntJyDsvKXiVgE+IcoawoY+qi7P/ocO\n"
+    "ZwIDAQAB\n"
+    "-----END PUBLIC KEY-----\n";
+
+// This public key is not linked to the private key above.
+const char WRONG_PUBLIC_KEY[] =
+    "-----BEGIN PUBLIC KEY-----\n"
+    "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuFD5lVAz2AjFEjzIWMwB\n"
+    "jtLrQNxaXy2wk9Yiwg8UOeYJevkG3/+UWXAEszurqJ7lq/4uSPAbTECbtyiE4dTv\n"
+    "ESFmqHqfwKYk+VFvP8Ty4Vh9brt4/RiZ1xL10MFif/vu38mLsvRPbDraTusT+9ni\n"
+    "TQyhJCdiRGrq5hmiW3BoA7/elpvp0+i/0e1FxMREcNOUqQ1HhYMFn3MskqjZRs2g\n"
+    "igmknQPVPoJBdBWQVGIYVb52d34hmLhvYPD3f4hJHS+mZvS5W6jekCXK4HAheDNx\n"
+    "OvBE4V1dtILdJPJEsx3Ua91z8IEaYa8iwbOV3yejdZVZ3NHSxmtrARYBpo5K9Bw4\n"
+    "jQIDAQAB\n"
+    "-----END PUBLIC KEY-----";
+
+#endif // __JWT_KEYS_HPP__

http://git-wip-us.apache.org/repos/asf/mesos/blob/9fa72c54/3rdparty/libprocess/src/tests/jwt_tests.cpp
----------------------------------------------------------------------
diff --git a/3rdparty/libprocess/src/tests/jwt_tests.cpp b/3rdparty/libprocess/src/tests/jwt_tests.cpp
index eb36a9a..581438d 100644
--- a/3rdparty/libprocess/src/tests/jwt_tests.cpp
+++ b/3rdparty/libprocess/src/tests/jwt_tests.cpp
@@ -23,11 +23,17 @@
 #include <stout/stringify.hpp>
 #include <stout/strings.hpp>
 
+#include "jwt_keys.hpp"
+
 using process::http::authentication::JWT;
 using process::http::authentication::JWTError;
 
 using process::network::openssl::generate_hmac_sha256;
+using process::network::openssl::pem_to_rsa_private_key;
+using process::network::openssl::pem_to_rsa_public_key;
+using process::network::openssl::sign_rsa_sha256;
 
+using std::shared_ptr;
 using std::string;
 
 
@@ -35,7 +41,17 @@ TEST(JWTTest, Parse)
 {
   const string secret = "secret";
 
-  auto create_token = [secret](string header, string payload) {
+  shared_ptr<RSA> privateKey = pem_to_rsa_private_key(PRIVATE_KEY).get();
+  CHECK_NOTNULL(privateKey.get());
+
+  shared_ptr<RSA> publicKey = pem_to_rsa_public_key(PUBLIC_KEY).get();
+  CHECK_NOTNULL(publicKey.get());
+
+  shared_ptr<RSA> wrongPublicKey =
+    pem_to_rsa_public_key(WRONG_PUBLIC_KEY).get();
+  CHECK_NOTNULL(wrongPublicKey.get());
+
+  auto create_hs256_token = [secret](string header, string payload) {
     header = base64::encode_url_safe(header, false);
     payload = base64::encode_url_safe(payload, false);
 
@@ -47,91 +63,206 @@ TEST(JWTTest, Parse)
     return strings::join(".", header, payload, signature);
   };
 
+  auto create_rs256_token = [privateKey](string header, string payload) {
+    header = base64::encode_url_safe(header, false);
+    payload = base64::encode_url_safe(payload, false);
+
+    const string rawSignature = sign_rsa_sha256(
+      strings::join(".", header, payload), privateKey).get();
+
+    const string signature = base64::encode_url_safe(rawSignature, false);
+
+    return strings::join(".", header, payload, signature);
+  };
+
   // Invalid token header.
   {
-    const string token = create_token(
-        "NOT A VALID HEADER",
-        "{\"exp\":9999999999,\"sub\":\"foo\"}");
+    // HS256.
+    {
+      const string token = create_hs256_token(
+          "NOT A VALID HEADER",
+          "{\"exp\":9999999999,\"sub\":\"foo\"}");
 
-    const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
+      const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
 
-    EXPECT_ERROR(jwt);
+      EXPECT_ERROR(jwt);
+    }
+
+    // RS256.
+    {
+      const string token = create_rs256_token(
+          "NOT A VALID HEADER",
+          "{\"exp\":9999999999,\"sub\":\"foo\"}");
+
+      const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey);
+
+      EXPECT_ERROR(jwt);
+    }
   }
 
   // Invalid token payload.
   {
-    const string token = create_token(
-        "{\"alg\":\"HS256\",\"typ\":\"JWT\"}",
-        "INVALID PAYLOAD");
+    // HS256.
+    {
+      const string token = create_hs256_token(
+          "{\"alg\":\"HS256\",\"typ\":\"JWT\"}",
+          "INVALID PAYLOAD");
 
-    const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
+      const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
 
-    EXPECT_ERROR(jwt);
+      EXPECT_ERROR(jwt);
+    }
+
+    // RS256.
+    {
+      const string token = create_rs256_token(
+          "{\"alg\":\"RS256\",\"typ\":\"JWT\"}",
+          "INVALID PAYLOAD");
+
+      const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey);
+
+      EXPECT_ERROR(jwt);
+    }
   }
 
   // Unsupported token alg.
   {
-    const string token = create_token(
-        "{\"alg\":\"RS256\",\"typ\":\"JWT\"}",
-        "{\"exp\":9999999999,\"sub\":\"foo\"}");
+    // HS256.
+    {
+      const string token = create_hs256_token(
+          "{\"alg\":\"RS512\",\"typ\":\"JWT\"}",
+          "{\"exp\":9999999999,\"sub\":\"foo\"}");
 
-    const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
+      const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
 
-    EXPECT_ERROR(jwt);
+      EXPECT_ERROR(jwt);
+    }
+
+    // RS256.
+    {
+      const string token = create_rs256_token(
+          "{\"alg\":\"RS512\",\"typ\":\"JWT\"}",
+          "{\"exp\":9999999999,\"sub\":\"foo\"}");
+
+      const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey);
+
+      EXPECT_ERROR(jwt);
+    }
   }
 
   // Unknown token alg.
   {
-    const string token = create_token(
-        "{\"alg\":\"NOT A VALID ALG\",\"typ\":\"JWT\"}",
-        "{\"exp\":9999999999,\"sub\":\"foo\"}");
+    // HS256.
+    {
+      const string token = create_hs256_token(
+          "{\"alg\":\"NOT A VALID ALG\",\"typ\":\"JWT\"}",
+          "{\"exp\":9999999999,\"sub\":\"foo\"}");
 
-    const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
+      const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
 
-    EXPECT_ERROR(jwt);
+      EXPECT_ERROR(jwt);
+    }
+
+    // RS256.
+    {
+      const string token = create_rs256_token(
+          "{\"alg\":\"NOT A VALID ALG\",\"typ\":\"JWT\"}",
+          "{\"exp\":9999999999,\"sub\":\"foo\"}");
+
+      const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey);
+
+      EXPECT_ERROR(jwt);
+    }
   }
 
   // 'crit' in header.
   {
-    const string token = create_token(
-        "{\"alg\":\"HS256\",\"crit\":[\"exp\"],\"exp\":99,\"typ\":\"JWT\"}",
-        "{\"exp\":9999999999,\"sub\":\"foo\"}");
+    // HS256.
+    {
+      const string token = create_hs256_token(
+          "{\"alg\":\"HS256\",\"crit\":[\"exp\"],\"exp\":99,\"typ\":\"JWT\"}",
+          "{\"exp\":9999999999,\"sub\":\"foo\"}");
 
-    const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
+      const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
 
-    EXPECT_ERROR(jwt);
+      EXPECT_ERROR(jwt);
+    }
+
+    // RS256.
+    {
+      const string token = create_rs256_token(
+          "{\"alg\":\"RS256\",\"crit\":[\"exp\"],\"exp\":99,\"typ\":\"JWT\"}",
+          "{\"exp\":9999999999,\"sub\":\"foo\"}");
+
+      const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey);
+
+      EXPECT_ERROR(jwt);
+    }
   }
 
   // Missing signature.
   {
-    const string token =
-      base64::encode_url_safe("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", false) +
-      "." +
-      base64::encode_url_safe("{\"exp\":9999999999,\"sub\":\"foo\"}", false) +
-      ".";
-
-    const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
-
-    EXPECT_ERROR(jwt);
+    // HS256.
+    {
+      const string token =
+        base64::encode_url_safe("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", false) +
+        "." +
+        base64::encode_url_safe("{\"exp\":9999999999,\"sub\":\"foo\"}", false) +
+        ".";
+
+      const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
+
+      EXPECT_ERROR(jwt);
+    }
+
+    // RS256.
+    {
+      const string token =
+        base64::encode_url_safe("{\"alg\":\"RS256\",\"typ\":\"JWT\"}", false) +
+        "." +
+        base64::encode_url_safe("{\"exp\":9999999999,\"sub\":\"foo\"}", false) +
+        ".";
+
+      const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey);
+
+      EXPECT_ERROR(jwt);
+    }
   }
 
   // Wrong signature.
   {
-    const string token =
-      base64::encode_url_safe("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", false) +
-      "." +
-      base64::encode_url_safe("{\"exp\":9999999999,\"sub\":\"foo\"}", false) +
-      "." +
-      "rm4sQe5q4sdHIJgt7mjKsnuZeP4eRquoZuncSsscqbQ";
-
-    const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
-
-    EXPECT_ERROR(jwt);
+    // HS256.
+    {
+      const string token =
+        base64::encode_url_safe("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", false) +
+        "." +
+        base64::encode_url_safe("{\"exp\":9999999999,\"sub\":\"foo\"}", false) +
+        "." +
+        "rm4sQe5q4sdHIJgt7mjKsnuZeP4eRquoZuncSsscqbQ";
+
+      const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
+
+      EXPECT_ERROR(jwt);
+    }
+
+    // RS256.
+    {
+      const string token =
+        base64::encode_url_safe("{\"alg\":\"RS256\",\"typ\":\"JWT\"}", false) +
+        "." +
+        base64::encode_url_safe("{\"exp\":9999999999,\"sub\":\"foo\"}", false) +
+        "." +
+        "rm4sQe5q4sdHIJgt7mjKsnuZeP4eRquoZuncSsscqbQ";
+
+      const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey);
+
+      EXPECT_ERROR(jwt);
+    }
   }
 
   // 'none' alg with signature.
   {
-    const string token = create_token(
+    const string token = create_hs256_token(
         "{\"alg\":\"none\",\"typ\":\"JWT\"}",
         "{\"exp\":9999999999,\"sub\":\"foo\"}");
 
@@ -154,37 +285,92 @@ TEST(JWTTest, Parse)
 
   // Expiration date is not a number.
   {
-    const string token = create_token(
-        "{\"alg\":\"HS256\",\"typ\":\"JWT\"}",
-        "{\"exp\":\"NOT A NUMBER\",\"sub\":\"foo\"}");
+    // HS256.
+    {
+      const string token = create_hs256_token(
+          "{\"alg\":\"HS256\",\"typ\":\"JWT\"}",
+          "{\"exp\":\"NOT A NUMBER\",\"sub\":\"foo\"}");
 
-    const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
+      const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
 
-    EXPECT_ERROR(jwt);
+      EXPECT_ERROR(jwt);
+    }
+
+    // RS256.
+    {
+      const string token = create_rs256_token(
+          "{\"alg\":\"RS256\",\"typ\":\"JWT\"}",
+          "{\"exp\":\"NOT A NUMBER\",\"sub\":\"foo\"}");
+
+      const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey);
+
+      EXPECT_ERROR(jwt);
+    }
   }
 
   // Expiration date expired.
   {
-    const string token = create_token(
-        "{\"alg\":\"HS256\",\"typ\":\"JWT\"}",
-        "{\"exp\":0,\"sub\":\"foo\"}");
+    // HS256.
+    {
+      const string token = create_hs256_token(
+          "{\"alg\":\"HS256\",\"typ\":\"JWT\"}",
+          "{\"exp\":0,\"sub\":\"foo\"}");
 
-    const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
+      const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
 
-    EXPECT_ERROR(jwt);
+      EXPECT_ERROR(jwt);
+    }
+
+    // RS256.
+    {
+      const string token = create_rs256_token(
+          "{\"alg\":\"RS256\",\"typ\":\"JWT\"}",
+          "{\"exp\":0,\"sub\":\"foo\"}");
+
+      const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey);
+
+      EXPECT_ERROR(jwt);
+    }
   }
 
   // Expiration date not set.
   {
-    const string token = create_token(
-        "{\"alg\":\"HS256\",\"typ\":\"JWT\"}",
-        "{\"sub\":\"foo\"}");
+    // HS256.
+    {
+      const string token = create_hs256_token(
+          "{\"alg\":\"HS256\",\"typ\":\"JWT\"}",
+          "{\"sub\":\"foo\"}");
+
+      const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
+
+      // TODO(nfnt): Change this to `EXPECT_SOME(jwt)`
+      // once MESOS-7220 is resolved.
+      EXPECT_TRUE(jwt.isSome());
+    }
+
+    // RS256.
+    {
+      const string token = create_rs256_token(
+          "{\"alg\":\"RS256\",\"typ\":\"JWT\"}",
+          "{\"sub\":\"foo\"}");
+
+      const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey);
+
+      // TODO(nfnt): Change this to `EXPECT_SOME(jwt)`
+      // once MESOS-7220 is resolved.
+      EXPECT_TRUE(jwt.isSome());
+    }
+  }
 
-    const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
+  // Wrong public key when verifying RS256 token.
+  {
+    const string token = create_rs256_token(
+        "{\"alg\":\"RS256\",\"typ\":\"JWT\"}",
+        "{\"exp\":9999999999,\"sub\":\"foo\"}");
 
-    // TODO(nfnt): Change this to `EXPECT_SOME(jwt)`
-    // once MESOS-7220 is resolved.
-    EXPECT_TRUE(jwt.isSome());
+    const Try<JWT, JWTError> jwt = JWT::parse(token, wrongPublicKey);
+
+    EXPECT_TRUE(jwt.isError());
   }
 
   // Valid unsecure token.
@@ -204,7 +390,7 @@ TEST(JWTTest, Parse)
 
   // Valid HS256 token.
   {
-    const string token = create_token(
+    const string token = create_hs256_token(
         "{\"alg\":\"HS256\",\"typ\":\"JWT\"}",
         "{\"exp\":9999999999,\"sub\":\"foo\"}");
 
@@ -214,14 +400,27 @@ TEST(JWTTest, Parse)
     // once MESOS-7220 is resolved.
     EXPECT_TRUE(jwt.isSome());
   }
+
+  // Valid RS256 token.
+  {
+    const string token = create_rs256_token(
+        "{\"alg\":\"RS256\",\"typ\":\"JWT\"}",
+        "{\"exp\":9999999999,\"sub\":\"foo\"}");
+
+    const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey);
+
+    EXPECT_TRUE(jwt.isSome());
+  }
 }
 
 
 TEST(JWTTest, Create)
 {
   const string secret = "secret";
+  shared_ptr<RSA> privateKey = pem_to_rsa_private_key(PRIVATE_KEY).get();
+  CHECK_NOTNULL(privateKey.get());
 
-  auto create_signature = [secret](const JSON::Object& payload) {
+  auto create_hs256_signature = [secret](const JSON::Object& payload) {
     const string message = strings::join(
         ".",
         base64::encode_url_safe("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", false),
@@ -232,6 +431,17 @@ TEST(JWTTest, Create)
     return base64::encode_url_safe(mac, false);
   };
 
+  auto create_rs256_signature = [privateKey](const JSON::Object& payload) {
+    const string message = strings::join(
+      ".",
+      base64::encode_url_safe("{\"alg\":\"RS256\",\"typ\":\"JWT\"}", false),
+      base64::encode_url_safe(stringify(payload), false));
+
+    const string mac = sign_rsa_sha256(message, privateKey).get();
+
+    return base64::encode_url_safe(mac, false);
+  };
+
   JSON::Object payload;
   payload.values["exp"] = 9999999999;
   payload.values["sub"] = "foo";
@@ -249,7 +459,23 @@ TEST(JWTTest, Create)
 
     EXPECT_EQ(payload, jwt->payload);
 
-    EXPECT_SOME_EQ(create_signature(payload), jwt->signature);
+    EXPECT_SOME_EQ(create_hs256_signature(payload), jwt->signature);
+  }
+
+  // RS256 signed JWT.
+  {
+    const Try<JWT, JWTError> jwt = JWT::create(payload, privateKey);
+
+    // TODO(nfnt): Change this to `EXPECT_SOME(jwt)`
+    // once MESOS-7220 is resolved.
+    EXPECT_TRUE(jwt.isSome());
+
+    EXPECT_EQ(JWT::Alg::RS256, jwt->header.alg);
+    EXPECT_SOME_EQ("JWT", jwt->header.typ);
+
+    EXPECT_EQ(payload, jwt->payload);
+
+    EXPECT_SOME_EQ(create_rs256_signature(payload), jwt->signature);
   }
 
   // Unsecured JWT.
@@ -272,21 +498,54 @@ TEST(JWTTest, Create)
 
 TEST(JWTTest, Stringify)
 {
-  JSON::Object payload;
-  payload.values["exp"] = 9999999999;
-  payload.values["sub"] = "foo";
+  // HS256.
+  {
+    JSON::Object payload;
+    payload.values["exp"] = 9999999999;
+    payload.values["sub"] = "foo";
+
+    const Try<JWT, JWTError> jwt = JWT::create(payload, "secret");
+
+    // TODO(nfnt): Change this to `EXPECT_SOME(jwt)`
+    // once MESOS-7220 is resolved.
+    EXPECT_TRUE(jwt.isSome());
 
-  const Try<JWT, JWTError> jwt = JWT::create(payload, "secret");
+    const string token = stringify(jwt.get());
 
-  // TODO(nfnt): Change this to `EXPECT_SOME(jwt)`
-  // once MESOS-7220 is resolved.
-  EXPECT_TRUE(jwt.isSome());
+    EXPECT_EQ(
+        "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
+        "eyJleHAiOjk5OTk5OTk5OTksInN1YiI6ImZvbyJ9."
+        "7dwSK1mIRKqJTPQT8-AGnI-r8nnefw2hhai3kgBg7bs",
+        token);
+  }
 
-  const string token = stringify(jwt.get());
+  // RS256.
+  {
+    shared_ptr<RSA> privateKey = pem_to_rsa_private_key(PRIVATE_KEY).get();
+    CHECK_NOTNULL(privateKey.get());
 
-  EXPECT_EQ(
-      "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
-      "eyJleHAiOjk5OTk5OTk5OTksInN1YiI6ImZvbyJ9."
-      "7dwSK1mIRKqJTPQT8-AGnI-r8nnefw2hhai3kgBg7bs",
-      token);
+    JSON::Object payload;
+    payload.values["exp"] = 9999999999;
+    payload.values["sub"] = "foo";
+
+    const Try<JWT, JWTError> jwt = JWT::create(payload, privateKey);
+
+    // TODO(nfnt): Change this to `EXPECT_SOME(jwt)`
+    // once MESOS-7220 is resolved.
+    EXPECT_TRUE(jwt.isSome());
+
+    const string token = stringify(jwt.get());
+
+    EXPECT_EQ(
+        "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9."
+        "eyJleHAiOjk5OTk5OTk5OTksInN1YiI6ImZvbyJ9."
+        "kNVM5tQWMHCsKzxgoUYrQ-oNrRDaTdnXXT01_Kf3DG9rGAWegQ1GC9H"
+        "iKJr0Nces_C7kDg3xhg0TAKc4sumlRHnQf40Y6P6NGAw__71qTvCptb"
+        "NS97sQypPeI7iFGcZGg-WfO2e1u0ztbZZi0PnrSO_5TL4qPXNE0UZTw"
+        "Si3f8nOPbBoIDdXHZKBWDVbP7evgcsSTeg26i0kwNI3SMLFa0nUt3rw"
+        "BVflxaAPK2PDD16s6hEmg0EB9MXHXYQGmh2Q01G5o7XKWsAe5H46CWD"
+        "LnJFpU3NN4iGd4EkbN_wPjOQ0FjlzypCTqF0QRM0Stf219qwVIw4_rt"
+        "j8V4bZUdp-wg",
+        token);
+  }
 }


Mime
View raw message