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