Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
cb49fc2
IGNITE-29209 Add fresh SSL contexts with certificate metadata
anton-vinogradov Oct 26, 2026
ae42041
IGNITE-29209 Preserve peer addresses in metadata wrappers
anton-vinogradov Oct 26, 2026
0802865
IGNITE-29209 Name SSLEngine peer parameters host and port
anton-vinogradov Oct 26, 2026
cf8143f
IGNITE-29209 Allow factories to implement only createKeyMaterial
anton-vinogradov Oct 26, 2026
42d5fe4
IGNITE-29209 Verify missing-key metadata and clarify wrapper construc…
anton-vinogradov Oct 26, 2026
fdb2a29
IGNITE-29209 Keep legacy key-manager loading outside KeyMaterial
anton-vinogradov Oct 26, 2026
78053e9
IGNITE-29209 Keep legacy adaptation in createKeyMaterial
anton-vinogradov Oct 26, 2026
bda9a61
IGNITE-29209 Format SSL context metadata delegation.
anton-vinogradov Oct 26, 2026
65c8098
IGNITE-29209 Test SSL metadata and legacy factory guarantees.
anton-vinogradov Oct 26, 2026
f723631
IGNITE-29209 Generate explicit key material for SSL metadata tests.
anton-vinogradov Oct 26, 2026
9716fd9
IGNITE-29209 Use JKS for generated certificate stores.
anton-vinogradov Oct 26, 2026
893a520
IGNITE-29209 Make metadata test premises and expectations explicit.
anton-vinogradov Oct 26, 2026
856866a
IGNITE-29209 Make certificate test inputs explicit and reusable
anton-vinogradov Oct 26, 2026
fbf5c51
IGNITE-29209 Let expiry tests specify dates without an unrelated subject
anton-vinogradov Oct 26, 2026
cbef7aa
IGNITE-29209 Expose key-store contents and hook calls in tests
anton-vinogradov Oct 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@
package org.apache.ignite.ssl;

import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.concurrent.atomic.AtomicReference;
import javax.cache.configuration.Factory;
import javax.net.ssl.KeyManager;
Expand All @@ -28,11 +33,11 @@
import javax.net.ssl.TrustManager;
import org.apache.ignite.IgniteException;
import org.apache.ignite.internal.util.typedef.internal.A;
import org.jetbrains.annotations.Nullable;

/**
* Represents abstract implementation of SSL Context Factory that caches the result of the first successful
* attempt to create an {@link SSLContext} and always returns it as a result of further invocations of the
* {@link AbstractSslContextFactory#create()}} method.
* Base SSL context factory that caches the result of the first successful {@link #create()} call.
* Use {@link #createNew()} to build another context without changing the cached one.
*/
public abstract class AbstractSslContextFactory implements Factory<SSLContext> {
/** */
Expand Down Expand Up @@ -118,26 +123,29 @@ public void setProtocols(String... protocols) {
private SSLContext createSslContext() throws SSLException {
checkParameters();

KeyManager[] keyMgrs = createKeyManagers();
KeyMaterial material = createKeyMaterial();

TrustManager[] trustMgrs = createTrustManagers();

try {
SSLContext ctx = SSLContext.getInstance(proto);

SSLParameters sslParameters = null;

if (cipherSuites != null || protocols != null) {
SSLParameters sslParameters = new SSLParameters();
sslParameters = new SSLParameters();

if (cipherSuites != null)
sslParameters.setCipherSuites(cipherSuites);

if (protocols != null)
sslParameters.setProtocols(protocols);

ctx = new SSLContextWrapper(ctx, sslParameters);
}

ctx.init(keyMgrs, trustMgrs, null);
if (sslParameters != null || material.chain != null || material.privateKeyMissing)
ctx = new SSLContextWrapper(ctx, sslParameters, material.chain, material.privateKeyMissing);

ctx.init(material.keyMgrs, trustMgrs, null);

return ctx;
}
Expand Down Expand Up @@ -167,10 +175,27 @@ protected void checkNullParameter(Object param, String name) throws SSLException
protected abstract void checkParameters() throws SSLException;

/**
* @return Created Key Managers.
* @throws SSLException If Key Managers could not be created.
* Creates key managers without certificate metadata.
*
* @return Created key managers.
* @throws SSLException If this method is not overridden or key managers cannot be created.
* @deprecated Override {@link #createKeyMaterial()} to return key managers with their certificate chain.
*/
@Deprecated
protected KeyManager[] createKeyManagers() throws SSLException {
throw new SSLException("Subclass must override createKeyMaterial() or createKeyManagers()");
}

/**
* Creates key managers and their certificate metadata from the same load. New subclasses should override this method.
* The default adapts {@link #createKeyManagers()} for existing subclasses and leaves the certificate chain unknown.
*
* @return Loaded key material.
* @throws SSLException If the key material could not be loaded.
*/
protected abstract KeyManager[] createKeyManagers() throws SSLException;
protected KeyMaterial createKeyMaterial() throws SSLException {
return new KeyMaterial(createKeyManagers(), null);
}

/**
* @return Created Trust Managers.
Expand All @@ -196,4 +221,109 @@ protected void checkNullParameter(Object param, String name) throws SSLException

return ctx;
}

/**
* Builds a new SSL context from the current factory settings without reading or replacing the context cached by
* {@link #create()}.
* <p>
* A subclass that builds its context in its own {@link #create()} must override this method too and build the context from
* the same sources. This implementation calls {@link #checkParameters()}, {@link #createKeyMaterial()} and
* {@link #createTrustManagers()}; it does not call {@link #create()}.
* <p>
* A subclass that only changes the key source can override {@link #createKeyMaterial()} instead. When building a context
* through another mechanism, attach its loaded certificate chain, if known, using
* {@link SSLContextWrapper#SSLContextWrapper(SSLContext, SSLParameters, X509Certificate[])}.
*
* @return New initialized SSL context.
* @throws IgniteException If the context could not be created.
*/
public SSLContext createNew() {
try {
return createSslContext();
}
catch (SSLException e) {
throw new IgniteException(e);
}
}

/** Key managers and the certificate chain loaded with them. */
protected static final class KeyMaterial {
/** */
private final KeyManager[] keyMgrs;

/** */
private final X509Certificate[] chain;

/** Whether the loaded store is known to contain no private key. */
private final boolean privateKeyMissing;

/**
* @param keyMgrs Key managers, or {@code null} to use the provider's defaults.
* @param chain Chain of the single private key, own certificate first, or {@code null} if unknown.
*/
public KeyMaterial(@Nullable KeyManager[] keyMgrs, @Nullable X509Certificate[] chain) {
this(keyMgrs, chain, false);
}

/**
* @param keyMgrs Key managers.
* @param chain Certificate chain, if known.
* @param privateKeyMissing Whether the loaded store contains no private key.
*/
private KeyMaterial(@Nullable KeyManager[] keyMgrs, @Nullable X509Certificate[] chain, boolean privateKeyMissing) {
this.keyMgrs = keyMgrs == null ? null : keyMgrs.clone();
this.chain = chain == null || chain.length == 0 ? null : chain.clone();
this.privateKeyMissing = privateKeyMissing;
}

/** @return Key managers, or {@code null} to use the provider's defaults. */
public @Nullable KeyManager[] keyManagers() {
return keyMgrs == null ? null : keyMgrs.clone();
}

/**
* Takes the chain from the store used to create the key managers. A store with one private key and an X.509 chain
* supplies that chain. More than one private key, or a chain containing a non-X.509 certificate, leaves the chain
* unknown. A store without a private key records that fact separately from an unknown chain.
*
* @param keyMgrs Key managers initialized from the store.
* @param store Already loaded key store.
* @return Key managers and the chain of the single private key, if known.
* @throws KeyStoreException If the store cannot be read.
*/
public static KeyMaterial fromKeyStore(KeyManager[] keyMgrs, KeyStore store) throws KeyStoreException {
String keyAlias = null;

for (Enumeration<String> aliases = store.aliases(); aliases.hasMoreElements(); ) {
String alias = aliases.nextElement();

if (!store.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class))
continue;

if (keyAlias != null)
return new KeyMaterial(keyMgrs, null);

keyAlias = alias;
}

if (keyAlias == null)
return new KeyMaterial(keyMgrs, null, true);

Certificate[] certs = store.getCertificateChain(keyAlias);

if (certs == null || certs.length == 0)
return new KeyMaterial(keyMgrs, null);

X509Certificate[] chain = new X509Certificate[certs.length];

for (int i = 0; i < certs.length; i++) {
if (!(certs[i] instanceof X509Certificate))
return new KeyMaterial(keyMgrs, null);

chain[i] = (X509Certificate)certs[i];
}

return new KeyMaterial(keyMgrs, chain);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,13 @@ class DelegatingSSLContextSpi extends SSLContextSpi {

/** {@inheritDoc} */
@Override protected SSLSocketFactory engineGetSocketFactory() {
return new SSLSocketFactoryWrapper(delegate.getSocketFactory(), parameters);
return parameters == null ? delegate.getSocketFactory() : new SSLSocketFactoryWrapper(delegate.getSocketFactory(), parameters);
}

/** {@inheritDoc} */
@Override protected SSLServerSocketFactory engineGetServerSocketFactory() {
return new SSLServerSocketFactoryWrapper(delegate.getServerSocketFactory(), parameters);
return parameters == null ? delegate.getServerSocketFactory()
: new SSLServerSocketFactoryWrapper(delegate.getServerSocketFactory(), parameters);
}

/** {@inheritDoc} */
Expand All @@ -70,8 +71,8 @@ class DelegatingSSLContextSpi extends SSLContextSpi {
}

/** {@inheritDoc} */
@Override protected SSLEngine engineCreateSSLEngine(String s, int i) {
final SSLEngine engine = delegate.createSSLEngine();
@Override protected SSLEngine engineCreateSSLEngine(String host, int port) {
final SSLEngine engine = delegate.createSSLEngine(host, port);

if (parameters != null)
engine.setSSLParameters(parameters);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,75 @@

package org.apache.ignite.ssl;

import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import org.jetbrains.annotations.Nullable;

/**
* Wrapper for {@link SSLContext} that extend source context with custom SSL parameters.
* Wrapper for {@link SSLContext} with custom SSL parameters and the certificate chain loaded with the context.
*/
public class SSLContextWrapper extends SSLContext {
/** Chain loaded with the delegate, or {@code null} if unknown. */
private final X509Certificate[] chain;

/** Whether the loaded key material is known to contain no private key. */
private final boolean privateKeyMissing;

/**
* Wraps a context with custom SSL parameters. If the delegate is an {@code SSLContextWrapper}, copies its
* certificate chain and missing-private-key flag.
*
* @param delegate Wrapped SSL context.
* @param sslParameters Extended SSL parameters.
*/
public SSLContextWrapper(SSLContext delegate, SSLParameters sslParameters) {
this(delegate, sslParameters,
delegate instanceof SSLContextWrapper ? ((SSLContextWrapper)delegate).getCertificateChain() : null,
delegate instanceof SSLContextWrapper && ((SSLContextWrapper)delegate).isPrivateKeyMissing());
}

/**
* Wraps a context with its loaded certificate chain. The chain describes the key material, not the outcome of a TLS
* handshake. The delegate must not be reinitialized with different key material after wrapping.
* This constructor uses the supplied chain and sets the missing-private-key flag to {@code false}.
* It does not copy metadata from the delegate.
*
* @param delegate Wrapped SSL context.
* @param sslParameters Extended SSL parameters, or {@code null} to keep the delegate's parameters.
* @param chain Chain of the single private key, own certificate first, or {@code null} if unknown.
*/
public SSLContextWrapper(SSLContext delegate, @Nullable SSLParameters sslParameters, @Nullable X509Certificate[] chain) {
this(delegate, sslParameters, chain, false);
}

/**
* Wraps a context with its loaded certificate chain and records whether that key material has no private key.
* Both metadata values come from the arguments, even if the delegate is an {@code SSLContextWrapper}.
*
* @param delegate Wrapped SSL context.
* @param sslParameters Extended SSL parameters, or {@code null} to keep the delegate's parameters.
* @param chain Chain of the single private key, own certificate first, or {@code null} if unknown.
* @param privateKeyMissing {@code true} only if the loaded material is known to contain no private key. Do not pass
* {@code true} just because the chain is unknown: the material may contain several keys or use custom key managers.
*/
public SSLContextWrapper(SSLContext delegate, @Nullable SSLParameters sslParameters, @Nullable X509Certificate[] chain,
boolean privateKeyMissing) {
super(new DelegatingSSLContextSpi(delegate, sslParameters),
delegate.getProvider(),
delegate.getProtocol());

this.chain = chain == null || chain.length == 0 ? null : chain.clone();
this.privateKeyMissing = privateKeyMissing;
}

/** @return Chain loaded with the context, own certificate first, or {@code null} if unknown. */
public @Nullable X509Certificate[] getCertificateChain() {
return chain == null ? null : chain.clone();
}

/** @return {@code true} only if the loaded material is known to contain no private key. */
public boolean isPrivateKeyMissing() {
return privateKeyMissing;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -258,16 +258,26 @@ public static TrustManager getDisabledTrustManager() {
return new DisabledX509TrustManager();
}

/** {@inheritDoc} */
/**
* {@inheritDoc}
*
* @deprecated Use {@link #createKeyMaterial()} to retain certificate metadata.
*/
@Deprecated
@Override protected final KeyManager[] createKeyManagers() throws SSLException {
return createKeyMaterial().keyManagers();
}

/** {@inheritDoc} */
@Override protected final KeyMaterial createKeyMaterial() throws SSLException {
try {
KeyManagerFactory keyMgrFactory = KeyManagerFactory.getInstance(keyAlgorithm);

KeyStore keyStore = loadKeyStore(keyStoreType, keyStoreFilePath, keyStorePwd);

keyMgrFactory.init(keyStore, keyStorePwd);

return keyMgrFactory.getKeyManagers();
return KeyMaterial.fromKeyStore(keyMgrFactory.getKeyManagers(), keyStore);
}
catch (NoSuchAlgorithmException e) {
throw new SSLException("Unsupported key algorithm: " + keyAlgorithm, e);
Expand Down
7 changes: 7 additions & 0 deletions modules/core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,13 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>1.86</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.eclipse.jetty.ee11</groupId>
<artifactId>jetty-ee11-servlets</artifactId>
Expand Down
Loading
Loading