io.netty.handler.ssl.ResumptionController Maven / Gradle / Ivy
/*
* Copyright 2024 The Netty Project
*
* The Netty Project 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:
*
* https://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 io.netty.handler.ssl;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.SuppressJava6Requirement;
import java.net.Socket;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;
final class ResumptionController {
private final Set confirmedValidations;
private final AtomicReference resumableTm;
ResumptionController() {
confirmedValidations = Collections.synchronizedSet(
Collections.newSetFromMap(new WeakHashMap()));
resumableTm = new AtomicReference();
}
@SuppressJava6Requirement(reason = "Guarded by version check")
public TrustManager wrapIfNeeded(TrustManager tm) {
if (tm instanceof ResumableX509ExtendedTrustManager) {
if (PlatformDependent.javaVersion() < 7 || !(tm instanceof X509ExtendedTrustManager)) {
throw new IllegalStateException("ResumableX509ExtendedTrustManager implementation must be a " +
"subclass of X509ExtendedTrustManager, found: " + (tm == null ? null : tm.getClass()));
}
if (!resumableTm.compareAndSet(null, (ResumableX509ExtendedTrustManager) tm)) {
throw new IllegalStateException(
"Only one ResumableX509ExtendedTrustManager can be configured for resumed sessions");
}
return new X509ExtendedWrapTrustManager((X509ExtendedTrustManager) tm, confirmedValidations);
}
return tm;
}
public void remove(SSLEngine engine) {
if (resumableTm.get() != null) {
confirmedValidations.remove(unwrapEngine(engine));
}
}
public boolean validateResumeIfNeeded(SSLEngine engine)
throws CertificateException, SSLPeerUnverifiedException {
ResumableX509ExtendedTrustManager tm;
boolean valid = engine.getSession().isValid();
if (valid && (tm = resumableTm.get()) != null) {
Certificate[] peerCertificates = engine.getSession().getPeerCertificates();
// Unwrap JdkSslEngines because they add their inner JDK SSLEngine objects to the set.
engine = unwrapEngine(engine);
if (!confirmedValidations.remove(engine)) {
// This is a resumed session.
if (engine.getUseClientMode()) {
// We are the client, resuming a session trusting the server
tm.resumeServerTrusted(chainOf(peerCertificates), engine);
} else {
// We are the server, resuming a session trusting the client
tm.resumeClientTrusted(chainOf(peerCertificates), engine);
}
return true;
}
}
return false;
}
private static SSLEngine unwrapEngine(SSLEngine engine) {
if (engine instanceof JdkSslEngine) {
return ((JdkSslEngine) engine).getWrappedEngine();
}
return engine;
}
private static X509Certificate[] chainOf(Certificate[] peerCertificates) {
if (peerCertificates instanceof X509Certificate[]) {
//noinspection SuspiciousArrayCast
return (X509Certificate[]) peerCertificates;
}
X509Certificate[] chain = new X509Certificate[peerCertificates.length];
for (int i = 0; i < peerCertificates.length; i++) {
Certificate cert = peerCertificates[i];
if (cert instanceof X509Certificate || cert == null) {
chain[i] = (X509Certificate) cert;
} else {
throw new IllegalArgumentException("Only X509Certificates are supported, found: " + cert.getClass());
}
}
return chain;
}
@SuppressJava6Requirement(reason = "Guarded by version check")
private static final class X509ExtendedWrapTrustManager extends X509ExtendedTrustManager {
private final X509ExtendedTrustManager trustManager;
private final Set confirmedValidations;
X509ExtendedWrapTrustManager(X509ExtendedTrustManager trustManager, Set confirmedValidations) {
this.trustManager = trustManager;
this.confirmedValidations = confirmedValidations;
}
private static void unsupported() throws CertificateException {
throw new CertificateException(
new UnsupportedOperationException("Resumable trust managers require the SSLEngine parameter"));
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
throws CertificateException {
unsupported();
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket)
throws CertificateException {
unsupported();
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
unsupported();
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
unsupported();
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
throws CertificateException {
trustManager.checkClientTrusted(chain, authType, engine);
confirmedValidations.add(engine);
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
throws CertificateException {
trustManager.checkServerTrusted(chain, authType, engine);
confirmedValidations.add(engine);
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return trustManager.getAcceptedIssuers();
}
}
}