All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nimbusds.jose.jwk.loader.JWKSetLoader Maven / Gradle / Ivy

package com.nimbusds.jose.jwk.loader;


import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.*;
import com.nimbusds.jose.util.Base64URL;
import com.nimbusds.jose.util.IOUtils;
import net.jcip.annotations.ThreadSafe;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.Provider;
import java.security.Security;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;


/**
 * JSON Web Key (JWK) set loader, with PKCS#11 support.
 */
@ThreadSafe
public class JWKSetLoader {
	
	
	/**
	 * The default name of the JWK set file.
	 */
	public static final String DEFAULT_JWK_SET_FILENAME = "/WEB-INF/jwkSet.json";
	
	
	/**
	 * The default name of the JWK set Java property.
	 */
	public static final String DEFAULT_JWK_SET_PROPERTY_NAME = "jose.jwkSet";
	
	
	/**
	 * Loads the keys from the specified PKCS#11 key store into a JWK set.
	 *
	 * @param pkcs11Config The PKCS#11 configuration, inline, or as file
	 *                     path. Must not be {@code null}.
	 * @param keyStorePin  The PKCS#11 key store pin (password).
	 *
	 * @return The JWK set.
	 *
	 * @throws JOSEException If loading failed.
	 */
	public static JWKSet loadPKCS11Keys(final String pkcs11Config, final char[] keyStorePin)
		throws JOSEException {
		
		Provider pkcs11Provider = Security.getProvider("SunPKCS11");
		pkcs11Provider = pkcs11Provider.configure(pkcs11Config);
		
		Loggers.MAIN_LOG.info("[SE1006] Loaded PKCS#11 provider {}", pkcs11Provider.getName());
		
		final KeyStore keyStore;
		try {
			keyStore = KeyStore.getInstance("PKCS11", pkcs11Provider);
			keyStore.load(null, keyStorePin);
			Loggers.MAIN_LOG.info("[SE1007] Loaded PKCS#11 key store with {} entries", keyStore.size());
		} catch (Exception e) {
			throw new JOSEException("Couldn't load PKCS#11 key store: " + e.getMessage(), e);
		}
		
		JWKSet pkcsKeys;
		try {
			pkcsKeys = JWKSet.load(keyStore, null);
			Loggers.MAIN_LOG.info("[SE1009] Extracted JWK set with {} keys from PKCS#11 key store {}",
				pkcsKeys.getKeys().size(),
				keyStore.getProvider().getName());
		} catch (KeyStoreException e) {
			throw new JOSEException("Couldn't load JWK set from PKCS#11 key store: " + e.getMessage(), e);
		}
		
		
		// Process keys
		List pkcsKeyList = new ArrayList<>();
		
		for (JWK jwk: pkcsKeys.getKeys()) {
			// Default key use to signature
			if (jwk instanceof RSAKey && jwk.getKeyUse() == null) {
				Loggers.MAIN_LOG.warn("[SE1010] Assuming signature key use for RSA PKCS#11 JWK with ID {}", jwk.getKeyID());
				pkcsKeyList.add(new RSAKey.Builder((RSAKey)jwk).keyUse(KeyUse.SIGNATURE).build());
			} else if (jwk instanceof ECKey && jwk.getKeyUse() == null) {
				Loggers.MAIN_LOG.warn("[SE1011] Assuming signature key use for EC PKCS#11 JWK with ID {}", jwk.getKeyID());
				pkcsKeyList.add(new ECKey.Builder((ECKey)jwk).keyUse(KeyUse.SIGNATURE).build());
			} else {
				pkcsKeyList.add(jwk); // Add without modification
			}
		}
		
		assert pkcsKeys.getKeys().size() == pkcsKeyList.size();
		
		pkcsKeys = new JWKSet(pkcsKeyList);
		
		return pkcsKeys;
	}
	
	
	/**
	 * Loads a JWK set from the {@link #DEFAULT_JWK_SET_PROPERTY_NAME}
	 * system property. The JWK set string can be alternatively encoded in
	 * BASE64URL format. The system property is not cleared after
	 * retrieval.
	 *
	 * @return The JWK set, {@code null} if the system property is not set.
	 *
	 * @throws JOSEException If parsing of the JWK set failed.
	 */
	public static JWKSet loadFromDefaultSystemProperty()
		throws JOSEException {
		
		return loadFromDefaultProperty(System.getProperties());
	}
	
	
	/**
	 * Loads a JWK set from the {@link #DEFAULT_JWK_SET_PROPERTY_NAME}
	 * property. The JWK set string can be alternatively encoded in
	 * BASE64URL format. The system property is not cleared after
	 * retrieval.
	 *
	 * @return The JWK set, {@code null} if the system property is not set.
	 *
	 * @throws JOSEException If parsing of the JWK set failed.
	 */
	public static JWKSet loadFromDefaultProperty(final Properties properties)
		throws JOSEException {
		
		return loadFromProperty(properties, DEFAULT_JWK_SET_PROPERTY_NAME);
	}
	
	
	/**
	 * Loads a JWK set from a property. The JWK set string can be
	 * alternatively encoded in BASE64URL format.
	 *
	 * @param properties   The properties. Must not be {@code null}.
	 * @param propertyName The property name. Must not be {@code null}.
	 *
	 * @return The JWK set, {@code null} if the property is not set.
	 *
	 * @throws JOSEException If parsing of the JWK set failed.
	 */
	public static JWKSet loadFromProperty(final Properties properties, final String propertyName)
		throws JOSEException {
		
		String value = properties.getProperty(propertyName);
		
		if (null == value || value.trim().isEmpty()) {
			return null;
		}
		
		if (! value.trim().startsWith("{")) {
			// Attempt base64-decode
			value = new Base64URL(value).decodeToString();
		}
		
		try {
			return JWKSet.parse(value);
		} catch (ParseException e) {
			throw new JOSEException("Invalid JWK set: " + e.getMessage(), e);
		}
	}
	
	
	/**
	 * Loads a JWK set from the specified input stream.
	 *
	 * @param inputStream The input stream, {@code null} if none.
	 *
	 * @return The JWK set, {@code null} if no input stream.
	 *
	 * @throws JOSEException If parsing of the JWK set failed.
	 */
	public static JWKSet loadFromInputStream(final InputStream inputStream)
		throws JOSEException {
		
		if (inputStream == null) {
			return null;
		}
		
		try {
			return JWKSet.parse(IOUtils.readInputStreamToString(inputStream, StandardCharsets.UTF_8));
		} catch (IOException | ParseException e) {
			throw new JOSEException(e.getMessage(), e);
		}
	}
	
	
	/**
	 * Loads a JWK set with {@link #DEFAULT_JWK_SET_PROPERTY_NAME} system
	 * property override and optional PKCS#11 key store support. Requires a
	 * {@link JOSEConfiguration JOSE and PKCS#11 configuration to be
	 * present}.
	 *
	 * @param fisSource The files input stream source. Must not be
	 *                  {@code null}.
	 *
	 * @return The JWK set.
	 *
	 * @throws RuntimeException If loading failed.
	 */
	public static JWKSet loadWithDefaultSystemPropertyOverrideAndPKCS11Support(final FileInputStreamSource fisSource) {
		
		return loadWithSystemPropertyOverrideAndPKCS11Support(fisSource, DEFAULT_JWK_SET_FILENAME, DEFAULT_JWK_SET_PROPERTY_NAME);
	}
	
	
	/**
	 * Loads a JWK set with system property override and optional PKCS#11
	 * key store support. Requires a {@link JOSEConfiguration JOSE and
	 * PKCS#11 configuration to be present}.
	 *
	 * @param fisSource      The files input stream source. Must not be
	 *                       {@code null}.
	 * @param jwkSetFileName The JWK set file name. Must not be
	 *                       {@code null}.
	 * @param propertyName   The system property name. Must not be
	 *                       {@code null}.
	 *
	 * @return The JWK set.
	 *
	 * @throws RuntimeException If loading failed.
	 */
	public static JWKSet loadWithSystemPropertyOverrideAndPKCS11Support(final FileInputStreamSource fisSource,
									    final String jwkSetFileName,
									    final String propertyName) {
		
		// Try to load the JWK set from system property first
		JWKSet jwkSet;
		try {
			jwkSet = loadFromProperty(System.getProperties(), propertyName);
		} catch (JOSEException e) {
			String msg = "Couldn't load JWK set from system property: " + propertyName + ": " + e.getMessage();
			Loggers.MAIN_LOG.fatal("[SE1020] {}", msg, e);
			throw new RuntimeException(msg, e);
		}
		
		final boolean loadedFromSysProp = jwkSet != null;
		
		if (! loadedFromSysProp) {
			// Try to load the JWK set from file resource
			try {
				jwkSet = loadFromInputStream(fisSource.getInputSteam(jwkSetFileName));
			} catch (JOSEException e) {
				String msg = "Couldn't load JWK set file: " + jwkSetFileName + ": " + e.getMessage();
				Loggers.MAIN_LOG.fatal("[SE1000] {}", msg, e);
				throw new RuntimeException(msg, e);
			}
			
			if (jwkSet == null) {
				// No file, set to empty JWK set
				jwkSet = new JWKSet();
			}
		}
		
		Loggers.MAIN_LOG.info("[SE1021] Loaded JWK set from {} {} with {} keys",
			loadedFromSysProp ? propertyName : jwkSetFileName,
			loadedFromSysProp ? "system property" : "file resource",
			jwkSet.getKeys().size()
		);
		
		// Load the JOSE and PKCS#11 config
		JOSEConfiguration joseConfig;
		try {
			joseConfig = JOSEConfiguration.load(fisSource);
		} catch (IOException e) {
			String msg = "Couldn't load PKCS#11 configuration: " + e.getMessage();
			Loggers.MAIN_LOG.fatal("[SE1001] {}", msg, e);
			throw new RuntimeException(msg, e);
		}
		
		joseConfig.log(Loggers.MAIN_LOG);
		
		// Load the optional PKCS#11 keys
		if (joseConfig.isPKCS11Enabled()) {
			
			String inlineConfig = PKCS11ProviderConfigurationLoader.load(joseConfig.getPKCS11ConfigurationFile(), fisSource);
			
			if (inlineConfig.trim().isEmpty()) {
				String msg = "Couldn't find PKCS#11 configuration: " + joseConfig.getPKCS11ConfigurationFile();
				Loggers.MAIN_LOG.fatal("[SE1005] {}", msg);
				throw new RuntimeException(msg);
			}
			
			JWKSet pkcsKeys;
			
			try {
				pkcsKeys = loadPKCS11Keys(inlineConfig, joseConfig.getPKCS11KeyStorePassword());
			} catch (JOSEException e) {
				Loggers.MAIN_LOG.fatal("[SE1002] {}", e.getMessage(), e);
				throw new RuntimeException(e.getMessage(), e);
			}

			if (! joseConfig.getPKCS11KeyIDs().isEmpty()) {
				// Apply key ID filter
				pkcsKeys = pkcsKeys.filter(
					new JWKMatcher.Builder()
						.keyIDs(joseConfig.getPKCS11KeyIDs().toArray(new String[0]))
						.build());
			}

			jwkSet = JWKSetMerge.merge(jwkSet, pkcsKeys);
			
			Loggers.MAIN_LOG.info("[SE1003] Merged PKCS#11 based JWK set with {} keys", pkcsKeys.getKeys().size());
		}
		
		JWKMetaLogger.log(jwkSet);
		
		List idsOfWeakKeys = WeakRSAKeyDetector.findWeakRSAKeys(jwkSet);
		
		if (! idsOfWeakKeys.isEmpty()) {
			
			String msg = "Found weak RSA key(s) shorter than 2048 bits with IDs: " + idsOfWeakKeys;
			
			if (! joseConfig.isAllowWeakKeys()) {
				Loggers.MAIN_LOG.fatal("[SE1030] {}", msg);
				throw new RuntimeException(msg);
			}
			
			Loggers.MAIN_LOG.warn("[SE1030] {}", msg);
		}
		
		return jwkSet;
	}
	
	
	/**
	 * Prevents public instantiation.
	 */
	private JWKSetLoader() {}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy