Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 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
2c9b01f
IGNITE-29209 Build fresh SSL bundles from loaded key material
anton-vinogradov Oct 27, 2026
be95316
IGNITE-29209 Clarify fresh bundle creation contract
anton-vinogradov Oct 27, 2026
7ab7635
IGNITE-29209 Make metadata test aliases explicit
anton-vinogradov Oct 27, 2026
72a76a4
IGNITE-29209 Keep key material loading internal to the file factory
anton-vinogradov Oct 27, 2026
4c40951
Merge remote-tracking branch 'refs/remotes/upstream/master' into igni…
anton-vinogradov Oct 27, 2026
a360d09
IGNITE-29209 Leave unrelated wrapper wording unchanged
anton-vinogradov Oct 27, 2026
b672056
IGNITE-29209 Isolate legacy context creation from SSL bundles
anton-vinogradov Oct 27, 2026
65a208e
IGNITE-29209 Keep legacy SSL creation independent of bundles
anton-vinogradov Oct 27, 2026
fecbd9e
IGNITE-29209 Preserve legacy key manager initialization
anton-vinogradov Oct 27, 2026
166027d
IGNITE-29209 Build fresh SSL bundles without legacy factory state
anton-vinogradov Oct 27, 2026
f1449e9
IGNITE-29209 Reuse the communication SSL context across connections
anton-vinogradov Oct 27, 2026
e11f5b2
IGNITE-29209 Remove unused SSL context import
anton-vinogradov Oct 27, 2026
e0dd39e
IGNITE-29209 Clarify key material ownership and factory customization
anton-vinogradov Oct 27, 2026
5fa46cb
IGNITE-29209 State that keys without an X.509 chain are never presented
anton-vinogradov Oct 27, 2026
c7f4aae
IGNITE-29209 Make SSL API tests state one guarantee each
anton-vinogradov Oct 27, 2026
8a99c99
IGNITE-29209 State how JKS lists a key without a chain
anton-vinogradov Oct 27, 2026
a1bb15f
IGNITE-29209 Preserve aliases of keys with empty certificate chains
anton-vinogradov Oct 28, 2026
5f0c03f
IGNITE-29209 Report only nonempty X.509 key chains
anton-vinogradov Oct 28, 2026
f7e62f6
IGNITE-29209 Remove optional key manager hook
anton-vinogradov Oct 28, 2026
89d0e7e
IGNITE-29209 Preserve SSL diagnostics without an extra exception
anton-vinogradov Oct 29, 2026
6459a46
IGNITE-29209 Allow factory details in .NET SSL error assertions
anton-vinogradov Oct 29, 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 @@ -655,9 +655,7 @@ public void testUnsupportedSslProtocol() throws Exception {
}
}

/**
* @throws Exception If failed.
*/
/** An unsupported key-manager algorithm is reported together with the affected key store. */
@Test
public void testInvalidKeyAlgorithm() throws Exception {
setSslCtxFactoryToCli = true;
Expand All @@ -666,7 +664,7 @@ public void testInvalidKeyAlgorithm() throws Exception {
startGrids(1);

try {
GridTestUtils.assertThrows(log, new Callable<Object>() {
Throwable err = GridTestUtils.assertThrows(log, new Callable<Object>() {
@Override public Object call() throws Exception {
Connection c = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/?sslMode=require" +
"&sslKeyAlgorithm=INVALID" +
Expand All @@ -678,6 +676,8 @@ public void testInvalidKeyAlgorithm() throws Exception {
return null;
}
}, SQLException.class, "Unsupported key algorithm: INVALID");

assertTrue(err.getMessage().contains("keyStoreFile=" + CLI_KEY_STORE_PATH));
}
finally {
stopAllGrids();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.atomic.AtomicReference;
import javax.cache.configuration.Factory;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
Expand All @@ -30,9 +29,8 @@
import org.apache.ignite.internal.util.typedef.internal.A;

/**
* 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.
* Builds a fresh SSL context and its certificate metadata on each call. The component using the context is responsible for
* retaining it between connections.
*/
public abstract class AbstractSslContextFactory implements Factory<SSLContext> {
/** */
Expand All @@ -50,9 +48,6 @@ public abstract class AbstractSslContextFactory implements Factory<SSLContext> {
/** Enabled protocols. */
protected String[] protocols;

/** Cached instance of an {@link SSLContext}. */
protected final AtomicReference<SSLContext> sslCtx = new AtomicReference<>();

/**
* Gets protocol for secure transport.
*
Expand Down Expand Up @@ -110,16 +105,31 @@ public void setProtocols(String... protocols) {
}

/**
* Creates SSL context based on factory settings.
* Checks the factory parameters, loads its key material once, and builds a fresh context with the certificate chains from
* that material. The factory does not retain the result.
*
* @return Initialized SSL context.
* @throws SSLException If SSL context could not be created.
* @return Fresh context and the certificate metadata available to this factory.
* @throws IgniteException If the context could not be created.
*/
private SSLContext createSslContext() throws SSLException {
checkParameters();
public SslBundle createSslBundle() {
try {
checkParameters();

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

return new SslBundle(createSslContext(material.keyManagers()), material.certificateChains());
}
catch (SSLException e) {
throw new IgniteException(e.getMessage() + " [factory=" + this + ']', e);
}
}

/**
* @param keyMgrs Key managers for the loaded material.
* @return Initialized context.
* @throws SSLException If trust managers or the context cannot be created.
*/
private SSLContext createSslContext(KeyManager[] keyMgrs) throws SSLException {
TrustManager[] trustMgrs = createTrustManagers();

try {
Expand Down Expand Up @@ -167,33 +177,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.
* Loads the key store and the settings used to read its keys. Called once per context, after the parameters are checked.
* For a context without private keys, return material over an empty store initialized with {@code KeyStore.load(null, null)}.
*
* @return Loaded key material, never {@code null}.
* @throws SSLException If the key material cannot be loaded.
*/
protected abstract KeyManager[] createKeyManagers() throws SSLException;
protected abstract KeyMaterial loadKeyMaterial() throws SSLException;

/**
* @return Created Trust Managers.
* @throws SSLException If Trust Managers could not be created.
*/
protected abstract TrustManager[] createTrustManagers() throws SSLException;

/** {@inheritDoc} */
@Override public SSLContext create() {
SSLContext ctx = sslCtx.get();

if (ctx == null) {
try {
ctx = createSslContext();

if (!sslCtx.compareAndSet(null, ctx))
ctx = sslCtx.get();
}
catch (SSLException e) {
throw new IgniteException(e);
}
}

return ctx;
/**
* Builds a fresh context through {@link #createSslBundle()}. Override that method to customize both entry points.
*
* @return Fresh initialized context.
* @throws IgniteException If the context could not be created.
*/
@Override public final SSLContext create() {
return createSslBundle().context();
}
}
135 changes: 135 additions & 0 deletions modules/commons/src/main/java/org/apache/ignite/ssl/KeyMaterial.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.ignite.ssl;

import java.security.GeneralSecurityException;
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.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLException;
import org.apache.ignite.internal.util.typedef.internal.A;
import org.jetbrains.annotations.Nullable;

/**
* A loaded key store and the settings needed to create its key managers. Both managers and certificate metadata are read from
* this store without loading it again. Do not modify the store while any context created from it is in use: key managers
* such as PKIX read it during handshakes. Load replacement material into a separate store.
*/
public final class KeyMaterial {
/** */
private final KeyStore store;

/** */
private final char[] keyPwd;

/** */
private final String algorithm;

/**
* @param store Loaded key store, never {@code null}. For a context without private keys, initialize an empty store with
* {@code KeyStore.load(null, null)}.
* @param keyPwd Password for its private keys, or {@code null} if the store requires no password.
* @param algorithm Key manager algorithm.
*/
public KeyMaterial(KeyStore store, @Nullable char[] keyPwd, String algorithm) {
A.notNull(store, "store", algorithm, "algorithm");

this.store = store;
this.keyPwd = keyPwd == null ? null : keyPwd.clone();
this.algorithm = algorithm;
}

/**
* @return Key managers initialized from the loaded store.
* @throws SSLException If the algorithm is unavailable or the keys cannot be read.
*/
public KeyManager[] keyManagers() throws SSLException {
try {
KeyManagerFactory factory = KeyManagerFactory.getInstance(algorithm);

factory.init(store, keyPwd);

return factory.getKeyManagers();
}
catch (NoSuchAlgorithmException e) {
throw new SSLException("Unsupported key algorithm: " + algorithm, e);
}
catch (GeneralSecurityException e) {
throw new SSLException("Failed to initialize Key Manager (security exception occurred)", e);
}
}

/**
* @return Immutable, nonempty X.509 chains of key entries, ordered by alias and with each key's own certificate first.
* Entries without a chain or with a non-X.509 certificate are omitted. An empty map means no such chains were found.
* Reading the chains does not recover the keys.
* @throws SSLException If the loaded store cannot be read.
*/
public Map<String, List<X509Certificate>> certificateChains() throws SSLException {
try {
Map<String, List<X509Certificate>> chains = new TreeMap<>();

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

if (!store.isKeyEntry(alias))
continue;

List<X509Certificate> chain = certificateChain(store.getCertificateChain(alias));

if (!chain.isEmpty())
chains.put(alias, chain);
}

return Collections.unmodifiableMap(chains);
}
catch (KeyStoreException e) {
throw new SSLException("Failed to read certificate chains from key store", e);
}
}

/**
* @param certs Chain from the store, if present.
* @return Immutable X.509 chain, or an empty list if it is absent or contains another certificate type.
*/
private static List<X509Certificate> certificateChain(@Nullable Certificate[] certs) {
if (certs == null)
return Collections.emptyList();

List<X509Certificate> chain = new ArrayList<>(certs.length);

for (Certificate cert : certs) {
if (!(cert instanceof X509Certificate))
return Collections.emptyList();

chain.add((X509Certificate)cert);
}

return Collections.unmodifiableList(chain);
}
}
79 changes: 79 additions & 0 deletions modules/commons/src/main/java/org/apache/ignite/ssl/SslBundle.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.ignite.ssl;

import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.net.ssl.SSLContext;
import org.apache.ignite.internal.util.typedef.internal.A;
import org.jetbrains.annotations.Nullable;

/**
* An initialized SSL context and a snapshot of the certificate chains loaded with it. The chains describe its key material,
* not the certificate selected for a particular connection. Do not reinitialize the context after creating the bundle.
*/
public final class SslBundle {
/** */
private final SSLContext ctx;

/** */
private final Map<String, List<X509Certificate>> chains;

/**
* @param ctx Initialized SSL context.
* @param chains Nonempty X.509 chains of key entries, by alias, own certificate first. {@code null} means certificate
* metadata is unavailable; an empty map means there are no chains. Trusted certificate entries and secret keys
* must not be included.
* @throws IllegalArgumentException If a chain is empty.
*/
public SslBundle(SSLContext ctx, @Nullable Map<String, List<X509Certificate>> chains) {
A.notNull(ctx, "ctx");

this.ctx = ctx;

if (chains == null)
this.chains = null;
else {
Map<String, List<X509Certificate>> copy = new TreeMap<>();

chains.forEach((alias, chain) -> {
A.ensure(!chain.isEmpty(), "Certificate chain must not be empty [alias=" + alias + ']');

copy.put(alias, List.copyOf(chain));
});

this.chains = Collections.unmodifiableMap(copy);
}
}

/** @return Initialized context. */
public SSLContext context() {
return ctx;
}

/**
* @return Immutable, nonempty chains ordered by alias, or {@code null} if certificate metadata is unavailable.
* An empty map means there are no chains.
*/
public @Nullable Map<String, List<X509Certificate>> certificateChains() {
return chains;
}
}
Loading
Loading