blob: 11ada99a971e2a8c74c3a1060a25955cc0bdeac5 [file] [log] [blame]
// 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
#include <gtest/gtest.h>
#include <string>
#include <process/jwt.hpp>
#include <process/ssl/utilities.hpp>
#include <stout/base64.hpp>
#include <stout/gtest.hpp>
#include <stout/stringify.hpp>
#include <stout/strings.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;
// Private and public keys generated by openssl.
static 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";
static 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.
static 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-----";
TEST(JWTTest, Parse)
{
const string secret = "secret";
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);
const string mac =
generate_hmac_sha256(strings::join(".", header, payload), secret).get();
const string signature = base64::encode_url_safe(mac, false);
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.
{
// HS256.
{
const string token = create_hs256_token(
"NOT A VALID HEADER",
"{\"exp\":9999999999,\"sub\":\"foo\"}");
const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
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.
{
// HS256.
{
const string token = create_hs256_token(
"{\"alg\":\"HS256\",\"typ\":\"JWT\"}",
"INVALID PAYLOAD");
const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
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.
{
// HS256.
{
const string token = create_hs256_token(
"{\"alg\":\"RS512\",\"typ\":\"JWT\"}",
"{\"exp\":9999999999,\"sub\":\"foo\"}");
const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
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.
{
// 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);
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.
{
// 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);
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.
{
// 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.
{
// 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_hs256_token(
"{\"alg\":\"none\",\"typ\":\"JWT\"}",
"{\"exp\":9999999999,\"sub\":\"foo\"}");
const Try<JWT, JWTError> jwt = JWT::parse(token);
EXPECT_ERROR(jwt);
}
// 'none' alg missing dot.
{
const string token =
base64::encode_url_safe("{\"alg\":\"none\",\"typ\":\"JWT\"}", false) +
"." +
base64::encode_url_safe("{\"exp\":9999999999,\"sub\":\"foo\"}", false);
const Try<JWT, JWTError> jwt = JWT::parse(token);
EXPECT_ERROR(jwt);
}
// Expiration date is not a number.
{
// 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);
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.
{
// HS256.
{
const string token = create_hs256_token(
"{\"alg\":\"HS256\",\"typ\":\"JWT\"}",
"{\"exp\":0,\"sub\":\"foo\"}");
const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
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.
{
// HS256.
{
const string token = create_hs256_token(
"{\"alg\":\"HS256\",\"typ\":\"JWT\"}",
"{\"sub\":\"foo\"}");
const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
EXPECT_SOME(jwt);
}
// RS256.
{
const string token = create_rs256_token(
"{\"alg\":\"RS256\",\"typ\":\"JWT\"}",
"{\"sub\":\"foo\"}");
const Try<JWT, JWTError> jwt = JWT::parse(token, publicKey);
EXPECT_SOME(jwt);
}
}
// Wrong public key when verifying RS256 token.
{
const string token = create_rs256_token(
"{\"alg\":\"RS256\",\"typ\":\"JWT\"}",
"{\"exp\":9999999999,\"sub\":\"foo\"}");
const Try<JWT, JWTError> jwt = JWT::parse(token, wrongPublicKey);
EXPECT_ERROR(jwt);
}
// Valid unsecure token.
{
const string token =
base64::encode_url_safe("{\"alg\":\"none\",\"typ\":\"JWT\"}", false) +
"." +
base64::encode_url_safe("{\"exp\":9999999999,\"sub\":\"foo\"}", false) +
".";
const Try<JWT, JWTError> jwt = JWT::parse(token);
EXPECT_SOME(jwt);
}
// Valid HS256 token.
{
const string token = create_hs256_token(
"{\"alg\":\"HS256\",\"typ\":\"JWT\"}",
"{\"exp\":9999999999,\"sub\":\"foo\"}");
const Try<JWT, JWTError> jwt = JWT::parse(token, secret);
EXPECT_SOME(jwt);
}
// 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_SOME(jwt);
}
}
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_hs256_signature = [secret](const JSON::Object& payload) {
const string message = strings::join(
".",
base64::encode_url_safe("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", false),
base64::encode_url_safe(stringify(payload), false));
const string mac = generate_hmac_sha256(message, secret).get();
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";
// HS256 signed JWT.
{
const Try<JWT, JWTError> jwt = JWT::create(payload, secret);
EXPECT_SOME(jwt);
EXPECT_EQ(JWT::Alg::HS256, jwt->header.alg);
EXPECT_SOME_EQ("JWT", jwt->header.typ);
EXPECT_EQ(payload, jwt->payload);
EXPECT_SOME_EQ(create_hs256_signature(payload), jwt->signature);
}
// RS256 signed JWT.
{
const Try<JWT, JWTError> jwt = JWT::create(payload, privateKey);
ASSERT_SOME(jwt);
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.
{
const Try<JWT, JWTError> jwt = JWT::create(payload);
EXPECT_SOME(jwt);
EXPECT_EQ(JWT::Alg::None, jwt->header.alg);
EXPECT_SOME_EQ("JWT", jwt->header.typ);
EXPECT_EQ(payload, jwt->payload);
EXPECT_NONE(jwt->signature);
}
}
TEST(JWTTest, Stringify)
{
// HS256.
{
JSON::Object payload;
payload.values["exp"] = 9999999999;
payload.values["sub"] = "foo";
const Try<JWT, JWTError> jwt = JWT::create(payload, "secret");
ASSERT_SOME(jwt);
const string token = stringify(jwt.get());
EXPECT_EQ(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
"eyJleHAiOjk5OTk5OTk5OTksInN1YiI6ImZvbyJ9."
"7dwSK1mIRKqJTPQT8-AGnI-r8nnefw2hhai3kgBg7bs",
token);
}
// RS256.
{
shared_ptr<RSA> privateKey = pem_to_rsa_private_key(PRIVATE_KEY).get();
CHECK_NOTNULL(privateKey.get());
JSON::Object payload;
payload.values["exp"] = 9999999999;
payload.values["sub"] = "foo";
const Try<JWT, JWTError> jwt = JWT::create(payload, privateKey);
ASSERT_SOME(jwt);
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);
}
}