blob: 04ebefa6364403cd31d5b5f332b86ce9f91f31d5 [file] [log] [blame]
/*
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.ssl;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.AlgorithmConstraints;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.function.BiFunction;
import javax.crypto.KeyGenerator;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SNIMatcher;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import sun.security.action.GetIntegerAction;
import sun.security.action.GetPropertyAction;
import sun.security.ssl.SSLExtension.ClientExtensions;
import sun.security.ssl.SSLExtension.ServerExtensions;
/**
* SSL/(D)TLS configuration.
*/
final class SSLConfiguration implements Cloneable {
// configurations with SSLParameters
AlgorithmConstraints userSpecifiedAlgorithmConstraints;
List<ProtocolVersion> enabledProtocols;
List<CipherSuite> enabledCipherSuites;
ClientAuthType clientAuthType;
String identificationProtocol;
List<SNIServerName> serverNames;
Collection<SNIMatcher> sniMatchers;
String[] applicationProtocols;
boolean preferLocalCipherSuites;
boolean enableRetransmissions;
int maximumPacketSize;
// The configured signature schemes for "signature_algorithms" and
// "signature_algorithms_cert" extensions
List<SignatureScheme> signatureSchemes;
// the maximum protocol version of enabled protocols
ProtocolVersion maximumProtocolVersion;
// Configurations per SSLSocket or SSLEngine instance.
boolean isClientMode;
boolean enableSessionCreation;
// the application layer protocol negotiation configuration
BiFunction<SSLSocket, List<String>, String> socketAPSelector;
BiFunction<SSLEngine, List<String>, String> engineAPSelector;
@SuppressWarnings("removal")
HashMap<HandshakeCompletedListener, AccessControlContext>
handshakeListeners;
boolean noSniExtension;
boolean noSniMatcher;
// To switch off the extended_master_secret extension.
static final boolean useExtendedMasterSecret;
// Allow session resumption without Extended Master Secret extension.
static final boolean allowLegacyResumption =
Utilities.getBooleanProperty("jdk.tls.allowLegacyResumption", true);
// Allow full handshake without Extended Master Secret extension.
static final boolean allowLegacyMasterSecret =
Utilities.getBooleanProperty("jdk.tls.allowLegacyMasterSecret", true);
// Allow full handshake without Extended Master Secret extension.
static final boolean useCompatibilityMode = Utilities.getBooleanProperty(
"jdk.tls.client.useCompatibilityMode", true);
// Respond a close_notify alert if receiving close_notify alert.
static final boolean acknowledgeCloseNotify = Utilities.getBooleanProperty(
"jdk.tls.acknowledgeCloseNotify", false);
// Set the max size limit for Handshake Message to 2^15
static final int maxHandshakeMessageSize = GetIntegerAction.privilegedGetProperty(
"jdk.tls.maxHandshakeMessageSize", 32768);
// Set the max certificate chain length to 10
static final int maxCertificateChainLength = GetIntegerAction.privilegedGetProperty(
"jdk.tls.maxCertificateChainLength", 10);
// Is the extended_master_secret extension supported?
static {
boolean supportExtendedMasterSecret = Utilities.getBooleanProperty(
"jdk.tls.useExtendedMasterSecret", true);
if (supportExtendedMasterSecret) {
try {
KeyGenerator.getInstance("SunTlsExtendedMasterSecret");
} catch (NoSuchAlgorithmException nae) {
supportExtendedMasterSecret = false;
}
}
useExtendedMasterSecret = supportExtendedMasterSecret;
}
SSLConfiguration(SSLContextImpl sslContext, boolean isClientMode) {
// Configurations with SSLParameters, default values.
this.userSpecifiedAlgorithmConstraints =
SSLAlgorithmConstraints.DEFAULT;
this.enabledProtocols =
sslContext.getDefaultProtocolVersions(!isClientMode);
this.enabledCipherSuites =
sslContext.getDefaultCipherSuites(!isClientMode);
this.clientAuthType = ClientAuthType.CLIENT_AUTH_NONE;
this.identificationProtocol = null;
this.serverNames = Collections.emptyList();
this.sniMatchers = Collections.emptyList();
this.preferLocalCipherSuites = true;
this.applicationProtocols = new String[0];
this.enableRetransmissions = sslContext.isDTLS();
this.maximumPacketSize = 0; // please reset it explicitly later
this.signatureSchemes = isClientMode ?
CustomizedClientSignatureSchemes.signatureSchemes :
CustomizedServerSignatureSchemes.signatureSchemes;
this.maximumProtocolVersion = ProtocolVersion.NONE;
for (ProtocolVersion pv : enabledProtocols) {
if (pv.compareTo(maximumProtocolVersion) > 0) {
this.maximumProtocolVersion = pv;
}
}
// Configurations per SSLSocket or SSLEngine instance.
this.isClientMode = isClientMode;
this.enableSessionCreation = true;
this.socketAPSelector = null;
this.engineAPSelector = null;
this.handshakeListeners = null;
this.noSniExtension = false;
this.noSniMatcher = false;
}
SSLParameters getSSLParameters() {
SSLParameters params = new SSLParameters();
params.setAlgorithmConstraints(this.userSpecifiedAlgorithmConstraints);
params.setProtocols(ProtocolVersion.toStringArray(enabledProtocols));
params.setCipherSuites(CipherSuite.namesOf(enabledCipherSuites));
switch (this.clientAuthType) {
case CLIENT_AUTH_REQUIRED:
params.setNeedClientAuth(true);
break;
case CLIENT_AUTH_REQUESTED:
params.setWantClientAuth(true);
break;
default:
params.setWantClientAuth(false);
}
params.setEndpointIdentificationAlgorithm(this.identificationProtocol);
if (serverNames.isEmpty() && !noSniExtension) {
// 'null' indicates none has been set
params.setServerNames(null);
} else {
params.setServerNames(this.serverNames);
}
if (sniMatchers.isEmpty() && !noSniMatcher) {
// 'null' indicates none has been set
params.setSNIMatchers(null);
} else {
params.setSNIMatchers(this.sniMatchers);
}
params.setApplicationProtocols(this.applicationProtocols);
params.setUseCipherSuitesOrder(this.preferLocalCipherSuites);
params.setEnableRetransmissions(this.enableRetransmissions);
params.setMaximumPacketSize(this.maximumPacketSize);
return params;
}
void setSSLParameters(SSLParameters params) {
AlgorithmConstraints ac = params.getAlgorithmConstraints();
if (ac != null) {
this.userSpecifiedAlgorithmConstraints = ac;
} // otherwise, use the default value
String[] sa = params.getCipherSuites();
if (sa != null) {
this.enabledCipherSuites = CipherSuite.validValuesOf(sa);
} // otherwise, use the default values
sa = params.getProtocols();
if (sa != null) {
this.enabledProtocols = ProtocolVersion.namesOf(sa);
this.maximumProtocolVersion = ProtocolVersion.NONE;
for (ProtocolVersion pv : enabledProtocols) {
if (pv.compareTo(maximumProtocolVersion) > 0) {
this.maximumProtocolVersion = pv;
}
}
} // otherwise, use the default values
if (params.getNeedClientAuth()) {
this.clientAuthType = ClientAuthType.CLIENT_AUTH_REQUIRED;
} else if (params.getWantClientAuth()) {
this.clientAuthType = ClientAuthType.CLIENT_AUTH_REQUESTED;
} else {
this.clientAuthType = ClientAuthType.CLIENT_AUTH_NONE;
}
String s = params.getEndpointIdentificationAlgorithm();
if (s != null) {
this.identificationProtocol = s;
} // otherwise, use the default value
List<SNIServerName> sniNames = params.getServerNames();
if (sniNames != null) {
this.noSniExtension = sniNames.isEmpty();
this.serverNames = sniNames;
} // null if none has been set
Collection<SNIMatcher> matchers = params.getSNIMatchers();
if (matchers != null) {
this.noSniMatcher = matchers.isEmpty();
this.sniMatchers = matchers;
} // null if none has been set
sa = params.getApplicationProtocols();
if (sa != null) {
this.applicationProtocols = sa;
} // otherwise, use the default values
this.preferLocalCipherSuites = params.getUseCipherSuitesOrder();
this.enableRetransmissions = params.getEnableRetransmissions();
this.maximumPacketSize = params.getMaximumPacketSize();
}
// SSLSocket only
@SuppressWarnings("removal")
void addHandshakeCompletedListener(
HandshakeCompletedListener listener) {
if (handshakeListeners == null) {
handshakeListeners = new HashMap<>(4);
}
handshakeListeners.put(listener, AccessController.getContext());
}
// SSLSocket only
void removeHandshakeCompletedListener(
HandshakeCompletedListener listener) {
if (handshakeListeners == null) {
throw new IllegalArgumentException("no listeners");
}
if (handshakeListeners.remove(listener) == null) {
throw new IllegalArgumentException("listener not registered");
}
if (handshakeListeners.isEmpty()) {
handshakeListeners = null;
}
}
/**
* Return true if the extension is available.
*/
boolean isAvailable(SSLExtension extension) {
for (ProtocolVersion protocolVersion : enabledProtocols) {
if (extension.isAvailable(protocolVersion)) {
if (isClientMode ?
ClientExtensions.defaults.contains(extension) :
ServerExtensions.defaults.contains(extension)) {
return true;
}
}
}
return false;
}
/**
* Return true if the extension is available for the specific protocol.
*/
boolean isAvailable(SSLExtension extension,
ProtocolVersion protocolVersion) {
return extension.isAvailable(protocolVersion) &&
(isClientMode ? ClientExtensions.defaults.contains(extension) :
ServerExtensions.defaults.contains(extension));
}
/**
* Get the enabled extensions for the specific handshake message.
*
* Used to consume handshake extensions.
*/
SSLExtension[] getEnabledExtensions(SSLHandshake handshakeType) {
List<SSLExtension> extensions = new ArrayList<>();
for (SSLExtension extension : SSLExtension.values()) {
if (extension.handshakeType == handshakeType) {
if (isAvailable(extension)) {
extensions.add(extension);
}
}
}
return extensions.toArray(new SSLExtension[0]);
}
/**
* Get the enabled extensions for the specific handshake message, excluding
* the specified extensions.
*
* Used to consume handshake extensions.
*/
SSLExtension[] getExclusiveExtensions(SSLHandshake handshakeType,
List<SSLExtension> excluded) {
List<SSLExtension> extensions = new ArrayList<>();
for (SSLExtension extension : SSLExtension.values()) {
if (extension.handshakeType == handshakeType) {
if (isAvailable(extension) && !excluded.contains(extension)) {
extensions.add(extension);
}
}
}
return extensions.toArray(new SSLExtension[0]);
}
/**
* Get the enabled extensions for the specific handshake message
* and the specific protocol version.
*
* Used to produce handshake extensions after handshake protocol
* version negotiation.
*/
SSLExtension[] getEnabledExtensions(
SSLHandshake handshakeType, ProtocolVersion protocolVersion) {
return getEnabledExtensions(handshakeType, List.of(protocolVersion));
}
/**
* Get the enabled extensions for the specific handshake message
* and the specific protocol versions.
*
* Used to produce ClientHello extensions before handshake protocol
* version negotiation.
*/
SSLExtension[] getEnabledExtensions(
SSLHandshake handshakeType, List<ProtocolVersion> activeProtocols) {
List<SSLExtension> extensions = new ArrayList<>();
for (SSLExtension extension : SSLExtension.values()) {
if (extension.handshakeType == handshakeType) {
if (!isAvailable(extension)) {
continue;
}
for (ProtocolVersion protocolVersion : activeProtocols) {
if (extension.isAvailable(protocolVersion)) {
extensions.add(extension);
break;
}
}
}
}
return extensions.toArray(new SSLExtension[0]);
}
void toggleClientMode() {
this.isClientMode ^= true;
// reset the signature schemes
this.signatureSchemes = isClientMode ?
CustomizedClientSignatureSchemes.signatureSchemes :
CustomizedServerSignatureSchemes.signatureSchemes;
}
@Override
@SuppressWarnings({"removal","unchecked", "CloneDeclaresCloneNotSupported"})
public Object clone() {
// Note that only references to the configurations are copied.
try {
SSLConfiguration config = (SSLConfiguration)super.clone();
if (handshakeListeners != null) {
config.handshakeListeners =
(HashMap<HandshakeCompletedListener, AccessControlContext>)
handshakeListeners.clone();
}
return config;
} catch (CloneNotSupportedException cnse) {
// unlikely
}
return null; // unlikely
}
// lazy initialization holder class idiom for static default parameters
//
// See Effective Java Second Edition: Item 71.
private static final class CustomizedClientSignatureSchemes {
private static final List<SignatureScheme> signatureSchemes =
getCustomizedSignatureScheme("jdk.tls.client.SignatureSchemes");
}
// lazy initialization holder class idiom for static default parameters
//
// See Effective Java Second Edition: Item 71.
private static final class CustomizedServerSignatureSchemes {
private static final List<SignatureScheme> signatureSchemes =
getCustomizedSignatureScheme("jdk.tls.server.SignatureSchemes");
}
/*
* Get the customized signature schemes specified by the given
* system property.
*/
private static List<SignatureScheme> getCustomizedSignatureScheme(
String propertyName) {
String property = GetPropertyAction.privilegedGetProperty(propertyName);
if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) {
SSLLogger.fine(
"System property " + propertyName + " is set to '" +
property + "'");
}
if (property != null && !property.isEmpty()) {
// remove double quote marks from beginning/end of the property
if (property.length() > 1 && property.charAt(0) == '"' &&
property.charAt(property.length() - 1) == '"') {
property = property.substring(1, property.length() - 1);
}
}
if (property != null && !property.isEmpty()) {
String[] signatureSchemeNames = property.split(",");
List<SignatureScheme> signatureSchemes =
new ArrayList<>(signatureSchemeNames.length);
for (int i = 0; i < signatureSchemeNames.length; i++) {
signatureSchemeNames[i] = signatureSchemeNames[i].trim();
if (signatureSchemeNames[i].isEmpty()) {
continue;
}
SignatureScheme scheme =
SignatureScheme.nameOf(signatureSchemeNames[i]);
if (scheme != null && scheme.isAvailable) {
signatureSchemes.add(scheme);
} else {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) {
SSLLogger.fine(
"The current installed providers do not " +
"support signature scheme: " +
signatureSchemeNames[i]);
}
}
}
return signatureSchemes;
}
return Collections.emptyList();
}
}