Skip to content

Commit

Permalink
FISH-1520 verify if JWT is encrypted as required
Browse files Browse the repository at this point in the history
Make the private and public key locations customizable via MP properties.
Introduce JWT specific exception to unify error processing.
Improve error messages.
Remove unused MockTokenParser.
  • Loading branch information
aubi authored and Pandrex247 committed Aug 5, 2021
1 parent 93ecc43 commit 5c2a592
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 149 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,14 @@ public JWTAuthenticationMechanism() {
// https://download.eclipse.org/microprofile/microprofile-jwt-auth-1.2/microprofile-jwt-auth-spec-1.2.html#_jwt_and_http_headers
Optional<Properties> properties = SignedJWTIdentityStore.readVendorProperties();
Config config = ConfigProvider.getConfig();
configJwtTokenHeader = readConfig(Names.TOKEN_HEADER, properties, config, CONFIG_TOKEN_HEADER_AUTHORIZATION);
configJwtTokenHeader = SignedJWTIdentityStore.readConfig(Names.TOKEN_HEADER, properties, config, CONFIG_TOKEN_HEADER_AUTHORIZATION);
if ((!CONFIG_TOKEN_HEADER_AUTHORIZATION.equals(configJwtTokenHeader))
&& (!CONFIG_TOKEN_HEADER_COOKIE.equals(configJwtTokenHeader))) {
throw new DeploymentException("Configuration " + Names.TOKEN_HEADER + " must be either "
+ CONFIG_TOKEN_HEADER_AUTHORIZATION + " or " + CONFIG_TOKEN_HEADER_COOKIE
+ ", but is " + configJwtTokenHeader);
}
configJwtTokenCookie = readConfig(Names.TOKEN_COOKIE, properties, config, "Bearer");
configJwtTokenCookie = SignedJWTIdentityStore.readConfig(Names.TOKEN_COOKIE, properties, config, "Bearer");
}

@Override
Expand Down Expand Up @@ -139,16 +139,4 @@ private SignedJWTCredential createSignedJWTCredential(String token) {

return null;
}

/**
* Read configuration from Vendor or server or return default value.
*/
private String readConfig(String key, Optional<Properties> properties, Config config, String defaultValue) {
Optional<String> valueOpt = properties.map(props -> props.getProperty(key));
if (!valueOpt.isPresent()) {
valueOpt = config.getOptionalValue(key, String.class);
}
return valueOpt.orElse(defaultValue);
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2017-2019 Payara Foundation and/or its affiliates. All rights reserved.
* Copyright (c) [2021] Payara Foundation and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
Expand Down Expand Up @@ -37,33 +37,26 @@
* only if the new code is made subject to such option by the copyright
* holder.
*/
package fish.payara.microprofile.jwtauth.tck;

import java.security.PublicKey;
import org.eclipse.microprofile.jwt.JsonWebToken;
import fish.payara.microprofile.jwtauth.jwt.JwtTokenParser;
package fish.payara.microprofile.jwtauth.eesecurity;

/**
*
* * This implements the artefact mandated by the MP-JWT TCK for offline
* (outside container) testing
* of the token parser.
*
* @author Arjan Tijms
*
* Exception during processing of JWT.
*/
public class MockTokenParser {
public class JWTProcessingException extends Exception {

public JWTProcessingException() {
}

private final JwtTokenParser jwtTokenParser = new JwtTokenParser();
public JWTProcessingException(String message) {
super(message);
}

public JsonWebToken parse(String bearerToken, String issuer, PublicKey signedBy) throws Exception {
try {
jwtTokenParser.parse(bearerToken);
return jwtTokenParser.verify(issuer, signedBy);
} catch (Exception e) {
throw new IllegalStateException("", e);
}
public JWTProcessingException(String message, Throwable cause) {
super(message, cause);
}

}
public JWTProcessingException(Throwable cause) {
super(cause);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,6 @@

package fish.payara.microprofile.jwtauth.eesecurity;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;

import javax.enterprise.inject.spi.DeploymentException;
import javax.json.JsonArray;
import javax.json.JsonObject;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PrivateKey;
Expand All @@ -55,14 +49,19 @@
import java.util.Base64;
import java.util.Optional;
import java.util.function.Supplier;

import javax.enterprise.inject.spi.DeploymentException;
import javax.json.JsonArray;
import javax.json.JsonObject;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import static org.eclipse.microprofile.jwt.config.Names.DECRYPTOR_KEY_LOCATION;

class JwtPrivateKeyStore {
public class JwtPrivateKeyStore {

private final Config config;
private final Supplier<Optional<String>> cacheSupplier;
private final Duration defaultCacheTTL;
private String keyLocation = "/privateKey.pem";

/**
* @param defaultCacheTTL Private key cache TTL
Expand All @@ -73,8 +72,17 @@ public JwtPrivateKeyStore(Duration defaultCacheTTL) {
this.cacheSupplier = new KeyLoadingCache(this::readRawPrivateKey)::get;
}

/**
* @param defaultCacheTTL Private key cache TTL
* @param keyLocation location of the private key
*/
public JwtPrivateKeyStore(Duration defaultCacheTTL, Optional<String> keyLocation) {
this(defaultCacheTTL);
this.keyLocation = keyLocation.orElse(this.keyLocation);
}

private CacheableString readRawPrivateKey() {
CacheableString privateKey = JwtKeyStoreUtils.readKeyFromLocation("/privateKey.pem", defaultCacheTTL);
CacheableString privateKey = JwtKeyStoreUtils.readKeyFromLocation(keyLocation, defaultCacheTTL);

if (!privateKey.isPresent()) {
privateKey = JwtKeyStoreUtils.readMPKeyFromLocation(config, DECRYPTOR_KEY_LOCATION, defaultCacheTTL);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) [2017-2020] Payara Foundation and/or its affiliates. All rights reserved.
* Copyright (c) [2017-2021] Payara Foundation and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
Expand Down Expand Up @@ -39,18 +39,6 @@
*/
package fish.payara.microprofile.jwtauth.eesecurity;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;

import javax.enterprise.inject.spi.DeploymentException;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.JsonValue;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringReader;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.KeyFactory;
Expand All @@ -64,15 +52,18 @@
import java.security.spec.X509EncodedKeySpec;
import java.time.Duration;
import java.util.Base64;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.logging.Logger;

import javax.enterprise.inject.spi.DeploymentException;
import javax.json.JsonArray;
import javax.json.JsonObject;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import static org.eclipse.microprofile.jwt.config.Names.VERIFIER_PUBLIC_KEY;
import static org.eclipse.microprofile.jwt.config.Names.VERIFIER_PUBLIC_KEY_LOCATION;

class JwtPublicKeyStore {
public class JwtPublicKeyStore {

private static final Logger LOGGER = Logger.getLogger(JwtPublicKeyStore.class.getName());
private static final String RSA_ALGORITHM = "RSA";
Expand All @@ -82,6 +73,7 @@ class JwtPublicKeyStore {
private final Config config;
private final Supplier<Optional<String>> cacheSupplier;
private final Duration defaultCacheTTL;
private String keyLocation = "/publicKey.pem";

/**
* @param defaultCacheTTL Public key cache TTL
Expand All @@ -92,6 +84,15 @@ public JwtPublicKeyStore(Duration defaultCacheTTL) {
this.cacheSupplier = new KeyLoadingCache(this::readRawPublicKey)::get;
}

/**
* @param defaultCacheTTL Public key cache TTL
* @param keyLocation location of the public key
*/
public JwtPublicKeyStore(Duration defaultCacheTTL, Optional<String> keyLocation) {
this(defaultCacheTTL);
this.keyLocation = keyLocation.orElse(this.keyLocation);
}

/**
*
* @param keyID The JWT key ID or null if no key ID was provided
Expand All @@ -105,7 +106,7 @@ public PublicKey getPublicKey(String keyID) {
}

private CacheableString readRawPublicKey() {
CacheableString publicKey = JwtKeyStoreUtils.readKeyFromLocation("/publicKey.pem", defaultCacheTTL);
CacheableString publicKey = JwtKeyStoreUtils.readKeyFromLocation(keyLocation, defaultCacheTTL);

if (!publicKey.isPresent()) {
publicKey = readMPEmbeddedPublicKey();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
import java.io.IOException;
import static java.lang.Thread.currentThread;
import java.net.URL;
import java.security.PublicKey;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
Expand Down Expand Up @@ -83,6 +82,8 @@ public class SignedJWTIdentityStore implements IdentityStore {
private final JwtPublicKeyStore publicKeyStore;
private final JwtPrivateKeyStore privateKeyStore;

private final boolean isEncryptionRequired;

public SignedJWTIdentityStore() {
config = ConfigProvider.getConfig();

Expand All @@ -99,29 +100,23 @@ public SignedJWTIdentityStore() {
enabledNamespace = readEnabledNamespace(properties);
customNamespace = readCustomNamespace(properties);
disableTypeVerification = readDisableTypeVerification(properties);
publicKeyStore = new JwtPublicKeyStore(readPublicKeyCacheTTL(properties));
privateKeyStore = new JwtPrivateKeyStore(readPublicKeyCacheTTL(properties));
Optional<String> publicKeyLocation = readConfigOptional(Names.VERIFIER_PUBLIC_KEY_LOCATION, properties, config); //mp.jwt.verifyAndParseEncryptedJWT.publickey.location
Optional<String> publicKey = readConfigOptional(Names.VERIFIER_PUBLIC_KEY, properties, config); //mp.jwt.verifyAndParseEncryptedJWT.publickey
Optional<String> decryptKeyLocation = readConfigOptional(Names.DECRYPTOR_KEY_LOCATION, properties, config); //mp.jwt.decrypt.key.location
publicKeyStore = new JwtPublicKeyStore(readPublicKeyCacheTTL(properties), publicKeyLocation);
privateKeyStore = new JwtPrivateKeyStore(readPublicKeyCacheTTL(properties), decryptKeyLocation);

// Signing is required by default, it doesn't parse if not signed
isEncryptionRequired = decryptKeyLocation.isPresent();
}

public CredentialValidationResult validate(SignedJWTCredential signedJWTCredential) {
final JwtTokenParser jwtTokenParser = new JwtTokenParser(enabledNamespace, customNamespace, disableTypeVerification);
try {
jwtTokenParser.parse(signedJWTCredential.getSignedJWT());
String keyID = jwtTokenParser.getKeyID();

PublicKey publicKey = publicKeyStore.getPublicKey(keyID);
JsonWebTokenImpl jsonWebToken = null;
try {
jsonWebToken = jwtTokenParser.verify(acceptedIssuer, publicKey);
} catch (IllegalStateException illegalStateException) {
if (illegalStateException.getMessage().equals("No parsed SignedJWT.")) {
jsonWebToken = jwtTokenParser.verify(acceptedIssuer, publicKey, privateKeyStore.getPrivateKey(keyID));
} else {
throw illegalStateException;
}
}
JsonWebTokenImpl jsonWebToken = jwtTokenParser.parse(signedJWTCredential.getSignedJWT(),
isEncryptionRequired, publicKeyStore, acceptedIssuer, privateKeyStore);

// verify audience
// verifyAndParseEncryptedJWT audience
final Set<String> recipientsOfThisJWT = jsonWebToken.getAudience();
// find if any recipient is in the allowed audience
Boolean recipientInAudience = allowedAudience
Expand Down Expand Up @@ -188,4 +183,19 @@ private Optional<String> readAudience(Optional<Properties> properties) {
return properties.isPresent() ? Optional.ofNullable(properties.get().getProperty(Names.AUDIENCES)) : Optional.empty();
}

/**
* Read configuration from Vendor or server or return default value.
*/
public static String readConfig(String key, Optional<Properties> properties, Config config, String defaultValue) {
Optional<String> valueOpt = readConfigOptional(key, properties, config);
return valueOpt.orElse(defaultValue);
}

public static Optional<String> readConfigOptional(String key, Optional<Properties> properties, Config config) {
Optional<String> valueOpt = properties.map(props -> props.getProperty(key));
if (!valueOpt.isPresent()) {
valueOpt = config.getOptionalValue(key, String.class);
}
return valueOpt;
}
}
Loading

0 comments on commit 5c2a592

Please sign in to comment.