You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by ti...@apache.org on 2018/05/18 22:28:10 UTC
mesos git commit: Added support for RS256 algo to JWT processing in
libprocess.
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 <cl...@gmail.com>
Authored: Sat May 19 00:26:04 2018 +0200
Committer: Till Toenshoff <to...@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);
+ }
}