Skip to content

Commit

Permalink
WFCORE-6802 [Preview] OCSP stapling support
Browse files Browse the repository at this point in the history
  • Loading branch information
Prarthona Paul committed Jul 4, 2024
1 parent 5365664 commit 3acf1d9
Show file tree
Hide file tree
Showing 11 changed files with 784 additions and 35 deletions.
1 change: 1 addition & 0 deletions elytron/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@
<exclude>jacc-with-providers.xml</exclude>
<exclude>legacy*.xml</exclude>
<exclude>elytron-subsystem-community*.xml</exclude>
<exclude>elytron-subsystem-preview*.xml</exclude>
</excludes>
<systemId>src/main/resources/schema/wildfly-elytron_18_0.xsd</systemId>
</validationSet>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
interface ElytronDescriptionConstants {

String ACCEPT_OCSP_STAPLING = "accept-ocsp-stapling";
String ACCOUNT_KEY = "account-key";
String ACTION = "action";
String ACTIVE_SESSION_COUNT = "active-session-count";
Expand Down Expand Up @@ -73,6 +74,8 @@ interface ElytronDescriptionConstants {
String BCRYPT_MAPPER = "bcrypt-mapper";

String CAA_IDENTITIES = "caa-identities";
String CACHE_SIZE = "cache-size";
String CACHE_LIFETIME = "cache-lifetime";
String CACHING_REALM = "caching-realm";
String CASE_PRINCIPAL_TRANSFORMER = "case-principal-transformer";
String CALLBACK_HANDLER = "callback-handler";
Expand Down Expand Up @@ -246,6 +249,7 @@ interface ElytronDescriptionConstants {
String IDENTITY_MAPPING = "identity-mapping";
String IDENTITY_REALM = "identity-realm";
String IGNORE_UNAVAILABLE_REALMS = "ignore-unavailable-realms";
String IGNORE_EXTENSIONS = "ignore-extensions";
String IMPLEMENTATION = "implementation";
String IMPLEMENTATION_PROPERTIES = "implementation-properties";
String IMPORT_CERTIFICATE = "import-certificate";
Expand Down Expand Up @@ -366,6 +370,8 @@ interface ElytronDescriptionConstants {
String OBTAIN_CERTIFICATE = "obtain-certificate";
String OBTAIN_KERBEROS_TICKET = "obtain-kerberos-ticket";
String OCSP = "ocsp";
String OCSP_STAPLING = "ocsp-stapling";
String OCSP_STAPLING_SOFT_FAIL = "ocsp-stapling-soft-fail";
String OID = "oid";
String ONLY_LEAF_CERT = "only-leaf-cert";
String OPERATIONS = "operations";
Expand Down Expand Up @@ -467,6 +473,9 @@ interface ElytronDescriptionConstants {
String RESPONDER = "responder";
String RESPONDER_CERTIFICATE = "responder-certificate";
String RESPONDER_KEYSTORE = "responder-keystore";
String RESPONDER_OVERRIDE = "responder-override";
String RESPONDER_URI = "responder-uri";
String RESPONSE_TIMEOUT = "response-timeout";
String REVERSE = "reverse";
String REVOKE_CERTIFICATE = "revoke-certificate";
String RIGHT = "right";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,9 @@ private void addCredentialStoreParser(PersistentResourceXMLDescription.Persisten

private void addTlsParser(PersistentResourceXMLDescription.PersistentResourceXMLBuilder builder) {
TlsParser tlsParser = new TlsParser();
if (this.since(ElytronSubsystemSchema.VERSION_18_0_COMMUNITY) && this.enables(getDynamicClientSSLContextDefinition())) {
if (this.since(ElytronSubsystemSchema.VERSION_18_0_PREVIEW) && this.enables(SSLDefinitions.OCSP_STAPLING)) {
builder.addChild(tlsParser.tlsParserPreview_18_0);
} else if (this.since(ElytronSubsystemSchema.VERSION_18_0_COMMUNITY) && this.enables(getDynamicClientSSLContextDefinition())) {
builder.addChild(tlsParser.tlsParserCommunity_18_0);
} else if (this.since(ElytronSubsystemSchema.VERSION_14_0)) {
builder.addChild(tlsParser.tlsParser_14_0);
Expand Down
193 changes: 164 additions & 29 deletions elytron/src/main/java/org/wildfly/extension/elytron/SSLDefinitions.java
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,18 @@ class SSLDefinitions {
//.setDefaultValue(new ModelNode(CipherSuiteSelector.OPENSSL_DEFAULT_CIPHER_SUITE_NAMES))
.build();

static final SimpleAttributeDefinition ACCEPT_OCSP_STAPLING = new SimpleAttributeDefinitionBuilder(ElytronDescriptionConstants.ACCEPT_OCSP_STAPLING, ModelType.BOOLEAN, true)
.setAllowExpression(true)
.setRestartAllServices()
.setDefaultValue(ModelNode.FALSE)
.build();

static final SimpleAttributeDefinition OCSP_STAPLING_SOFT_FAIL = new SimpleAttributeDefinitionBuilder(ElytronDescriptionConstants.OCSP_STAPLING_SOFT_FAIL, ModelType.BOOLEAN, true)
.setAllowExpression(true)
.setRestartAllServices()
.setDefaultValue(ModelNode.TRUE)
.build();

private static final String[] ALLOWED_PROTOCOLS = { "SSLv2", "SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" };

static final StringListAttributeDefinition PROTOCOLS = new StringListAttributeDefinition.Builder(ElytronDescriptionConstants.PROTOCOLS)
Expand Down Expand Up @@ -398,6 +410,55 @@ class SSLDefinitions {
.setRestartAllServices()
.build();

static final SimpleAttributeDefinition RESPONSE_TIMEOUT = new SimpleAttributeDefinitionBuilder(ElytronDescriptionConstants.RESPONSE_TIMEOUT, ModelType.INT, true)
.setValidator(new IntRangeValidator(1))
.setDefaultValue(new ModelNode(5000))
.setAllowExpression(true)
.setRestartAllServices()
.setStability(Stability.PREVIEW)
.build();

static final SimpleAttributeDefinition CACHE_SIZE = new SimpleAttributeDefinitionBuilder(ElytronDescriptionConstants.CACHE_SIZE, ModelType.INT, true)
.setDefaultValue(new ModelNode(256))
.setAllowExpression(true)
.setRestartAllServices()
.setStability(Stability.PREVIEW)
.build();

static final SimpleAttributeDefinition CACHE_LIFETIME = new SimpleAttributeDefinitionBuilder(ElytronDescriptionConstants.CACHE_LIFETIME, ModelType.INT, true)
.setDefaultValue(new ModelNode(3600))
.setAllowExpression(true)
.setRestartAllServices()
.setStability(Stability.PREVIEW)
.build();

static final SimpleAttributeDefinition RESPONDER_URI = new SimpleAttributeDefinitionBuilder(ElytronDescriptionConstants.RESPONDER_URI, ModelType.STRING, true)
.setRequires(ElytronDescriptionConstants.RESPONDER_OVERRIDE)
.setAllowExpression(true)
.setRestartAllServices()
.setStability(Stability.PREVIEW)
.build();

static final SimpleAttributeDefinition RESPONDER_OVERRIDE = new SimpleAttributeDefinitionBuilder(ElytronDescriptionConstants.RESPONDER_OVERRIDE, ModelType.BOOLEAN, true)
.setDefaultValue(ModelNode.FALSE)
.setAllowExpression(true)
.setRestartAllServices()
.setStability(Stability.PREVIEW)
.build();

static final SimpleAttributeDefinition IGNORE_EXTENSIONS = new SimpleAttributeDefinitionBuilder(ElytronDescriptionConstants.IGNORE_EXTENSIONS, ModelType.BOOLEAN, true)
.setDefaultValue(ModelNode.FALSE)
.setAllowExpression(true)
.setRestartAllServices()
.setStability(Stability.PREVIEW)
.build();

static final ObjectTypeAttributeDefinition OCSP_STAPLING = new ObjectTypeAttributeDefinition.Builder(ElytronDescriptionConstants.OCSP_STAPLING, RESPONSE_TIMEOUT, CACHE_SIZE, CACHE_LIFETIME, RESPONDER_URI, RESPONDER_OVERRIDE, IGNORE_EXTENSIONS)
.setRequired(false)
.setRestartAllServices()
.setStability(Stability.PREVIEW)
.build();

/*
* Runtime Attributes
*/
Expand Down Expand Up @@ -941,31 +1002,6 @@ private File resolveFileLocation(String path, String relativeTo, InjectedValue<P
}
return resolvedPath;
}

private TrustManagerFactory createTrustManagerFactory(Provider[] providers, String providerName, String algorithm) throws StartException {
TrustManagerFactory trustManagerFactory = null;

if (providers != null) {
for (Provider current : providers) {
if (providerName == null || providerName.equals(current.getName())) {
try {
// TODO - We could check the Services within each Provider to check there is one of the required type/algorithm
// However the same loop would need to remain as it is still possible a specific provider can't create it.
return TrustManagerFactory.getInstance(algorithm, current);
} catch (NoSuchAlgorithmException ignored) {
}
}
}
if (trustManagerFactory == null)
throw ROOT_LOGGER.unableToCreateManagerFactory(TrustManagerFactory.class.getSimpleName(), algorithm);
}

try {
return TrustManagerFactory.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
throw new StartException(e);
}
}
};

ResourceDescriptionResolver resolver = ElytronExtension.getResourceDescriptionResolver(ElytronDescriptionConstants.TRUST_MANAGER);
Expand Down Expand Up @@ -1288,7 +1324,7 @@ static ResourceDefinition getServerSSLContextDefinition(boolean serverOrHostCont
SECURITY_DOMAIN, WANT_CLIENT_AUTH, NEED_CLIENT_AUTH, AUTHENTICATION_OPTIONAL,
USE_CIPHER_SUITES_ORDER, MAXIMUM_SESSION_CACHE_SIZE, SESSION_TIMEOUT, WRAP, keyManagerDefinition, TRUST_MANAGER,
PRE_REALM_PRINCIPAL_TRANSFORMER, POST_REALM_PRINCIPAL_TRANSFORMER, FINAL_PRINCIPAL_TRANSFORMER, REALM_MAPPER,
providersDefinition, PROVIDER_NAME};
providersDefinition, PROVIDER_NAME, OCSP_STAPLING};

AbstractAddStepHandler add = new TrivialAddHandler<SSLContext>(SSLContext.class, ServiceController.Mode.ACTIVE, ServiceController.Mode.PASSIVE, attributes, SSL_CONTEXT_RUNTIME_CAPABILITY) {

Expand Down Expand Up @@ -1316,7 +1352,29 @@ protected ValueSupplier<SSLContext> getValueSupplier(ServiceBuilder<SSLContext>
final int maximumSessionCacheSize = MAXIMUM_SESSION_CACHE_SIZE.resolveModelAttribute(context, model).asInt();
final int sessionTimeout = SESSION_TIMEOUT.resolveModelAttribute(context, model).asInt();
final boolean wrap = WRAP.resolveModelAttribute(context, model).asBoolean();

final String ocspStapling = OCSP_STAPLING.resolveModelAttribute(context, model).asStringOrNull();
final int responseTimeout;
final int cacheSize;
final int cacheLifetime;
final String responderURI;
final boolean responderOverride;
final boolean ignoreExtensions;

if (ocspStapling != null) {
responseTimeout = RESPONSE_TIMEOUT.resolveModelAttribute(context, model).asInt();
cacheSize = CACHE_SIZE.resolveModelAttribute(context, model).asInt();
cacheLifetime = CACHE_LIFETIME.resolveModelAttribute(context, model).asInt();
responderURI = RESPONDER_URI.resolveModelAttribute(context, model).asString();
responderOverride = RESPONDER_OVERRIDE.resolveModelAttribute(context, model).asBoolean();
ignoreExtensions = IGNORE_EXTENSIONS.resolveModelAttribute(context, model).asBoolean();
} else {
responseTimeout = 0;
cacheSize = 0;
cacheLifetime = 0;
responderURI = null;
responderOverride = false;
ignoreExtensions = false;
}
return () -> {
SecurityDomain securityDomain = securityDomainInjector.getOptionalValue();
X509ExtendedKeyManager keyManager = getX509KeyManager(keyManagerInjector.getOptionalValue());
Expand Down Expand Up @@ -1366,6 +1424,15 @@ protected ValueSupplier<SSLContext> getValueSupplier(ServiceBuilder<SSLContext>
.setSessionTimeout(sessionTimeout)
.setWrap(wrap);

if (ocspStapling != null) {
builder.setResponseTimeout(responseTimeout)
.setCacheSize(cacheSize)
.setCacheLifetime(cacheLifetime)
.setResponderURI(responderURI)
.setResponderOverride(responderOverride)
.setIgnoreExtensions(ignoreExtensions);
}

if (ROOT_LOGGER.isTraceEnabled()) {
ROOT_LOGGER.tracef(
"ServerSSLContext supplying: securityDomain = %s keyManager = %s trustManager = %s "
Expand Down Expand Up @@ -1461,7 +1528,7 @@ static ResourceDefinition getClientSSLContextDefinition(boolean serverOrHostCont
.build();

AttributeDefinition[] attributes = new AttributeDefinition[]{CIPHER_SUITE_FILTER, CIPHER_SUITE_NAMES, PROTOCOLS,
KEY_MANAGER, TRUST_MANAGER, providersDefinition, PROVIDER_NAME};
KEY_MANAGER, TRUST_MANAGER, providersDefinition, PROVIDER_NAME, ACCEPT_OCSP_STAPLING, OCSP_STAPLING_SOFT_FAIL};

AbstractAddStepHandler add = new TrivialAddHandler<SSLContext>(SSLContext.class, attributes, SSL_CONTEXT_RUNTIME_CAPABILITY) {
@Override
Expand All @@ -1475,15 +1542,31 @@ protected ValueSupplier<SSLContext> getValueSupplier(ServiceBuilder<SSLContext>
final List<String> protocols = PROTOCOLS.unwrap(context, model);
final String cipherSuiteFilter = CIPHER_SUITE_FILTER.resolveModelAttribute(context, model).asString(); // has default value, can't be null
final String cipherSuiteNames = CIPHER_SUITE_NAMES.resolveModelAttribute(context, model).asStringOrNull(); // doesn't have a default value yet since we are disabling TLS 1.3 by default
final boolean acceptOCSPStapling = ACCEPT_OCSP_STAPLING.resolveModelAttribute(context, model).asBoolean();
final boolean softFail = OCSP_STAPLING_SOFT_FAIL.resolveModelAttribute(context, model).asBoolean();
final String trustManagerName = TRUST_MANAGER.resolveModelAttribute(context,model).asString();

return () -> {
X509ExtendedKeyManager keyManager = getX509KeyManager(keyManagerInjector.getOptionalValue());
X509ExtendedTrustManager trustManager = getX509TrustManager(trustManagerInjector.getOptionalValue());
Provider[] providers = filterProviders(providersInjector.getOptionalValue(), providerName);

SSLContextBuilder builder = new SSLContextBuilder();
if (keyManager != null) builder.setKeyManager(keyManager);
if (acceptOCSPStapling) {
TrustManagerFactory trustManagerFactory = createTrustManagerFactory(providersInjector.getOptionalValue(), providerName, TrustManagerFactory.getDefaultAlgorithm());
X509RevocationTrustManager.Builder revocationBuilder = X509RevocationTrustManager.builder();
revocationBuilder.setTrustManagerFactory(trustManagerFactory);
revocationBuilder.setTrustStore(getModifiableTrustManagerService(context, trustManagerName).getModifiableValue());

revocationBuilder.setCheckRevocation(true);
revocationBuilder.setSoftFail(softFail);
trustManager = revocationBuilder.build();
}

if (trustManager != null) builder.setTrustManager(trustManager);
if (providers != null) builder.setProviderSupplier(() -> providers);

builder.setCipherSuiteSelector(CipherSuiteSelector.aggregate(cipherSuiteNames != null ? CipherSuiteSelector.fromNamesString(cipherSuiteNames) : null, CipherSuiteSelector.fromString(cipherSuiteFilter)));
if (!protocols.isEmpty()) {
List<Protocol> list = new ArrayList<>();
Expand All @@ -1496,7 +1579,8 @@ protected ValueSupplier<SSLContext> getValueSupplier(ServiceBuilder<SSLContext>
));
}
builder.setClientMode(true)
.setWrap(false);
.setWrap(false)
.setAcceptOCSPStapling(acceptOCSPStapling);

if (ROOT_LOGGER.isTraceEnabled()) {
ROOT_LOGGER.tracef(
Expand Down Expand Up @@ -1689,4 +1773,55 @@ public InjectedValue<PathManager> getPathManagerInjector() {
}
}

private static TrustManagerFactory createTrustManagerFactory(Provider[] providers, String providerName, String algorithm) throws StartException {
TrustManagerFactory trustManagerFactory = null;

if (providers != null) {
for (Provider current : providers) {
if (providerName == null || providerName.equals(current.getName())) {
try {
// TODO - We could check the Services within each Provider to check there is one of the required type/algorithm
// However the same loop would need to remain as it is still possible a specific provider can't create it.
return TrustManagerFactory.getInstance(algorithm, current);
} catch (NoSuchAlgorithmException ignored) {
}
}
}
if (trustManagerFactory == null)
throw ROOT_LOGGER.unableToCreateManagerFactory(TrustManagerFactory.class.getSimpleName(), algorithm);
}

try {
return TrustManagerFactory.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
throw new StartException(e);
}
}

public static ModifiableKeyStoreService getModifiableTrustManagerService(OperationContext context, String trustManagerName) throws OperationFailedException {
ServiceRegistry serviceRegistry = context.getServiceRegistry(false);
RuntimeCapability<Void> runtimeCapability = TRUST_MANAGER_RUNTIME_CAPABILITY.fromBaseCapability(trustManagerName);
ServiceName serviceName = runtimeCapability.getCapabilityServiceName();

ServiceController<TrustManager> serviceContainer = getRequiredService(serviceRegistry, serviceName, TrustManager.class);
ServiceController.State serviceState = serviceContainer.getState();
if (serviceState != ServiceController.State.UP) {
throw ROOT_LOGGER.requiredServiceNotUp(serviceName, serviceState);
}

String keyStoreName = null;
Set<ServiceName> serviceNames = serviceContainer.requires();
for(ServiceName name : serviceNames) {
if (name.getCanonicalName().contains(KEY_STORE_CAPABILITY)) {
keyStoreName = (name).getCanonicalName().substring(KEY_STORE_CAPABILITY.length() + 1);
}
}

if (keyStoreName == null) {
throw ROOT_LOGGER.unableToLoadKeystoreCapabilityService();
} else {
return getModifiableKeyStoreService(context, keyStoreName);
}
}

}
Loading

0 comments on commit 3acf1d9

Please sign in to comment.