blob: 75c7ba494d48e21550adf4ac37e0e8409aaa4cc1 [file] [log] [blame]
/*
* 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.conscrypt;
import org.conscrypt.util.EmptyArray;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.crypto.SecretKey;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.x500.X500Principal;
/**
* The instances of this class encapsulate all the info
* about enabled cipher suites and protocols,
* as well as the information about client/server mode of
* ssl socket, whether it require/want client authentication or not,
* and controls whether new SSL sessions may be established by this
* socket or not.
*/
public class SSLParametersImpl implements Cloneable {
// default source of X.509 certificate based authentication keys
private static volatile X509KeyManager defaultX509KeyManager;
// default source of X.509 certificate based authentication trust decisions
private static volatile X509TrustManager defaultX509TrustManager;
// default source of random numbers
private static volatile SecureRandom defaultSecureRandom;
// default SSL parameters
private static volatile SSLParametersImpl defaultParameters;
// client session context contains the set of reusable
// client-side SSL sessions
private final ClientSessionContext clientSessionContext;
// server session context contains the set of reusable
// server-side SSL sessions
private final ServerSessionContext serverSessionContext;
// source of X.509 certificate based authentication keys or null if not provided
private final X509KeyManager x509KeyManager;
// source of Pre-Shared Key (PSK) authentication keys or null if not provided.
private final PSKKeyManager pskKeyManager;
// source of X.509 certificate based authentication trust decisions or null if not provided
private final X509TrustManager x509TrustManager;
// source of random numbers
private SecureRandom secureRandom;
// protocols enabled for SSL connection
private String[] enabledProtocols;
// cipher suites enabled for SSL connection
private String[] enabledCipherSuites;
// if the peer with this parameters tuned to work in client mode
private boolean client_mode = true;
// if the peer with this parameters tuned to require client authentication
private boolean need_client_auth = false;
// if the peer with this parameters tuned to request client authentication
private boolean want_client_auth = false;
// if the peer with this parameters allowed to cteate new SSL session
private boolean enable_session_creation = true;
private String endpointIdentificationAlgorithm;
byte[] npnProtocols;
byte[] alpnProtocols;
boolean useSessionTickets;
private Boolean useSni;
/**
* Whether the TLS Channel ID extension is enabled. This field is
* server-side only.
*/
boolean channelIdEnabled;
/**
* Initializes the parameters. Naturally this constructor is used
* in SSLContextImpl.engineInit method which directly passes its
* parameters. In other words this constructor holds all
* the functionality provided by SSLContext.init method.
* See {@link javax.net.ssl.SSLContext#init(KeyManager[],TrustManager[],
* SecureRandom)} for more information
*/
protected SSLParametersImpl(KeyManager[] kms, TrustManager[] tms,
SecureRandom sr, ClientSessionContext clientSessionContext,
ServerSessionContext serverSessionContext, String[] protocols)
throws KeyManagementException {
this.serverSessionContext = serverSessionContext;
this.clientSessionContext = clientSessionContext;
// initialize key managers
if (kms == null) {
x509KeyManager = getDefaultX509KeyManager();
// There's no default PSK key manager
pskKeyManager = null;
} else {
x509KeyManager = findFirstX509KeyManager(kms);
pskKeyManager = findFirstPSKKeyManager(kms);
}
// initialize x509TrustManager
if (tms == null) {
x509TrustManager = getDefaultX509TrustManager();
} else {
x509TrustManager = findFirstX509TrustManager(tms);
}
// initialize secure random
// We simply use the SecureRandom passed in by the caller. If it's
// null, we don't replace it by a new instance. The native code below
// then directly accesses /dev/urandom. Not the most elegant solution,
// but faster than going through the SecureRandom object.
secureRandom = sr;
// initialize the list of cipher suites and protocols enabled by default
enabledProtocols = NativeCrypto.checkEnabledProtocols(
protocols == null ? NativeCrypto.DEFAULT_PROTOCOLS : protocols).clone();
boolean x509CipherSuitesNeeded = (x509KeyManager != null) || (x509TrustManager != null);
boolean pskCipherSuitesNeeded = pskKeyManager != null;
enabledCipherSuites = getDefaultCipherSuites(
x509CipherSuitesNeeded, pskCipherSuitesNeeded);
}
protected static SSLParametersImpl getDefault() throws KeyManagementException {
SSLParametersImpl result = defaultParameters;
if (result == null) {
// single-check idiom
defaultParameters = result = new SSLParametersImpl(null,
null,
null,
new ClientSessionContext(),
new ServerSessionContext(),
null);
}
return (SSLParametersImpl) result.clone();
}
/**
* Returns the appropriate session context.
*/
public AbstractSessionContext getSessionContext() {
return client_mode ? clientSessionContext : serverSessionContext;
}
/**
* @return server session context
*/
protected ServerSessionContext getServerSessionContext() {
return serverSessionContext;
}
/**
* @return client session context
*/
protected ClientSessionContext getClientSessionContext() {
return clientSessionContext;
}
/**
* @return X.509 key manager or {@code null} for none.
*/
protected X509KeyManager getX509KeyManager() {
return x509KeyManager;
}
/**
* @return Pre-Shared Key (PSK) key manager or {@code null} for none.
*/
protected PSKKeyManager getPSKKeyManager() {
return pskKeyManager;
}
/**
* @return X.509 trust manager or {@code null} for none.
*/
protected X509TrustManager getX509TrustManager() {
return x509TrustManager;
}
/**
* @return secure random
*/
protected SecureRandom getSecureRandom() {
if (secureRandom != null) {
return secureRandom;
}
SecureRandom result = defaultSecureRandom;
if (result == null) {
// single-check idiom
defaultSecureRandom = result = new SecureRandom();
}
secureRandom = result;
return secureRandom;
}
/**
* @return the secure random member reference, even it is null
*/
protected SecureRandom getSecureRandomMember() {
return secureRandom;
}
/**
* @return the names of enabled cipher suites
*/
protected String[] getEnabledCipherSuites() {
return enabledCipherSuites.clone();
}
/**
* Sets the enabled cipher suites after filtering through OpenSSL.
*/
protected void setEnabledCipherSuites(String[] cipherSuites) {
enabledCipherSuites = NativeCrypto.checkEnabledCipherSuites(cipherSuites).clone();
}
/**
* @return the set of enabled protocols
*/
protected String[] getEnabledProtocols() {
return enabledProtocols.clone();
}
/**
* Sets the set of available protocols for use in SSL connection.
* @param protocols String[]
*/
protected void setEnabledProtocols(String[] protocols) {
enabledProtocols = NativeCrypto.checkEnabledProtocols(protocols).clone();
}
/**
* Tunes the peer holding this parameters to work in client mode.
* @param mode if the peer is configured to work in client mode
*/
protected void setUseClientMode(boolean mode) {
client_mode = mode;
}
/**
* Returns the value indicating if the parameters configured to work
* in client mode.
*/
protected boolean getUseClientMode() {
return client_mode;
}
/**
* Tunes the peer holding this parameters to require client authentication
*/
protected void setNeedClientAuth(boolean need) {
need_client_auth = need;
// reset the want_client_auth setting
want_client_auth = false;
}
/**
* Returns the value indicating if the peer with this parameters tuned
* to require client authentication
*/
protected boolean getNeedClientAuth() {
return need_client_auth;
}
/**
* Tunes the peer holding this parameters to request client authentication
*/
protected void setWantClientAuth(boolean want) {
want_client_auth = want;
// reset the need_client_auth setting
need_client_auth = false;
}
/**
* Returns the value indicating if the peer with this parameters
* tuned to request client authentication
*/
protected boolean getWantClientAuth() {
return want_client_auth;
}
/**
* Allows/disallows the peer holding this parameters to
* create new SSL session
*/
protected void setEnableSessionCreation(boolean flag) {
enable_session_creation = flag;
}
/**
* Returns the value indicating if the peer with this parameters
* allowed to cteate new SSL session
*/
protected boolean getEnableSessionCreation() {
return enable_session_creation;
}
/**
* Whether connections using this SSL connection should use the TLS
* extension Server Name Indication (SNI).
*/
protected void setUseSni(boolean flag) {
useSni = Boolean.valueOf(flag);
}
/**
* Returns whether connections using this SSL connection should use the TLS
* extension Server Name Indication (SNI).
*/
protected boolean getUseSni() {
return useSni != null ? useSni.booleanValue() : isSniEnabledByDefault();
}
static byte[][] encodeIssuerX509Principals(X509Certificate[] certificates)
throws CertificateEncodingException {
byte[][] principalBytes = new byte[certificates.length][];
for (int i = 0; i < certificates.length; i++) {
principalBytes[i] = certificates[i].getIssuerX500Principal().getEncoded();
}
return principalBytes;
}
/**
* Return a possibly null array of X509Certificates given the possibly null
* array of DER encoded bytes.
*/
private static OpenSSLX509Certificate[] createCertChain(long[] certificateRefs)
throws IOException {
if (certificateRefs == null) {
return null;
}
OpenSSLX509Certificate[] certificates = new OpenSSLX509Certificate[certificateRefs.length];
for (int i = 0; i < certificateRefs.length; i++) {
certificates[i] = new OpenSSLX509Certificate(certificateRefs[i]);
}
return certificates;
}
OpenSSLSessionImpl getSessionToReuse(long sslNativePointer, String hostname, int port)
throws SSLException {
final OpenSSLSessionImpl sessionToReuse;
if (client_mode) {
// look for client session to reuse
sessionToReuse = getCachedClientSession(clientSessionContext, hostname, port);
if (sessionToReuse != null) {
NativeCrypto.SSL_set_session(sslNativePointer,
sessionToReuse.sslSessionNativePointer);
}
} else {
sessionToReuse = null;
}
return sessionToReuse;
}
void setTlsChannelId(long sslNativePointer, OpenSSLKey channelIdPrivateKey)
throws SSLHandshakeException, SSLException {
// TLS Channel ID
if (channelIdEnabled) {
if (client_mode) {
// Client-side TLS Channel ID
if (channelIdPrivateKey == null) {
throw new SSLHandshakeException("Invalid TLS channel ID key specified");
}
NativeCrypto.SSL_set1_tls_channel_id(sslNativePointer,
channelIdPrivateKey.getNativeRef());
} else {
// Server-side TLS Channel ID
NativeCrypto.SSL_enable_tls_channel_id(sslNativePointer);
}
}
}
void setCertificate(long sslNativePointer, String alias) throws CertificateEncodingException,
SSLException {
if (alias == null) {
return;
}
X509KeyManager keyManager = getX509KeyManager();
if (keyManager == null) {
return;
}
PrivateKey privateKey = keyManager.getPrivateKey(alias);
if (privateKey == null) {
return;
}
X509Certificate[] certificates = keyManager.getCertificateChain(alias);
if (certificates == null) {
return;
}
PublicKey publicKey = (certificates.length > 0) ? certificates[0].getPublicKey() : null;
/*
* Make sure we keep a reference to the OpenSSLX509Certificate by using
* this array. Otherwise, if they're not OpenSSLX509Certificate
* instances originally, they may be garbage collected before we
* complete our JNI calls.
*/
OpenSSLX509Certificate[] openSslCerts = new OpenSSLX509Certificate[certificates.length];
long[] x509refs = new long[certificates.length];
for (int i = 0; i < certificates.length; i++) {
OpenSSLX509Certificate openSslCert = OpenSSLX509Certificate
.fromCertificate(certificates[i]);
openSslCerts[i] = openSslCert;
x509refs[i] = openSslCert.getContext();
}
// Note that OpenSSL says to use SSL_use_certificate before
// SSL_use_PrivateKey.
NativeCrypto.SSL_use_certificate(sslNativePointer, x509refs);
final OpenSSLKey key;
try {
key = OpenSSLKey.fromPrivateKeyForTLSStackOnly(privateKey, publicKey);
NativeCrypto.SSL_use_PrivateKey(sslNativePointer, key.getNativeRef());
} catch (InvalidKeyException e) {
throw new SSLException(e);
}
// We may not have access to all the information to check the private key
// if it's a wrapped platform key, so skip this check.
if (!key.isWrapped()) {
// Makes sure the set PrivateKey and X509Certificate refer to the same
// key by comparing the public values.
NativeCrypto.SSL_check_private_key(sslNativePointer);
}
}
void setSSLParameters(long sslCtxNativePointer, long sslNativePointer, AliasChooser chooser,
PSKCallbacks pskCallbacks, String sniHostname) throws SSLException, IOException {
if (npnProtocols != null) {
NativeCrypto.SSL_CTX_enable_npn(sslCtxNativePointer);
}
if (client_mode && alpnProtocols != null) {
NativeCrypto.SSL_set_alpn_protos(sslNativePointer, alpnProtocols);
}
NativeCrypto.setEnabledProtocols(sslNativePointer, enabledProtocols);
NativeCrypto.setEnabledCipherSuites(sslNativePointer, enabledCipherSuites);
// setup server certificates and private keys.
// clients will receive a call back to request certificates.
if (!client_mode) {
Set<String> keyTypes = new HashSet<String>();
for (long sslCipherNativePointer : NativeCrypto.SSL_get_ciphers(sslNativePointer)) {
String keyType = getServerX509KeyType(sslCipherNativePointer);
if (keyType != null) {
keyTypes.add(keyType);
}
}
X509KeyManager keyManager = getX509KeyManager();
if (keyManager != null) {
for (String keyType : keyTypes) {
try {
setCertificate(sslNativePointer,
chooser.chooseServerAlias(x509KeyManager, keyType));
} catch (CertificateEncodingException e) {
throw new IOException(e);
}
}
}
}
// Enable Pre-Shared Key (PSK) key exchange if requested
PSKKeyManager pskKeyManager = getPSKKeyManager();
if (pskKeyManager != null) {
boolean pskEnabled = false;
for (String enabledCipherSuite : enabledCipherSuites) {
if ((enabledCipherSuite != null) && (enabledCipherSuite.contains("PSK"))) {
pskEnabled = true;
break;
}
}
if (pskEnabled) {
if (client_mode) {
NativeCrypto.set_SSL_psk_client_callback_enabled(sslNativePointer, true);
} else {
NativeCrypto.set_SSL_psk_server_callback_enabled(sslNativePointer, true);
String identityHint = pskCallbacks.chooseServerPSKIdentityHint(pskKeyManager);
NativeCrypto.SSL_use_psk_identity_hint(sslNativePointer, identityHint);
}
}
}
if (useSessionTickets) {
NativeCrypto.SSL_clear_options(sslNativePointer, NativeConstants.SSL_OP_NO_TICKET);
}
if (getUseSni() && AddressUtils.isValidSniHostname(sniHostname)) {
NativeCrypto.SSL_set_tlsext_host_name(sslNativePointer, sniHostname);
}
// BEAST attack mitigation (1/n-1 record splitting for CBC cipher suites
// with TLSv1 and SSLv3).
NativeCrypto.SSL_set_mode(sslNativePointer, NativeConstants.SSL_MODE_CBC_RECORD_SPLITTING);
boolean enableSessionCreation = getEnableSessionCreation();
if (!enableSessionCreation) {
NativeCrypto.SSL_set_session_creation_enabled(sslNativePointer, enableSessionCreation);
}
}
/**
* Returns whether Server Name Indication (SNI) is enabled by default for
* sockets. For more information on SNI, see RFC 6066 section 3.
*/
private boolean isSniEnabledByDefault() {
String enableSNI = System.getProperty("jsse.enableSNIExtension", "true");
if ("true".equalsIgnoreCase(enableSNI)) {
return true;
} else if ("false".equalsIgnoreCase(enableSNI)) {
return false;
} else {
throw new RuntimeException(
"Can only set \"jsse.enableSNIExtension\" to \"true\" or \"false\"");
}
}
void setCertificateValidation(long sslNativePointer) throws IOException {
// setup peer certificate verification
if (!client_mode) {
// needing client auth takes priority...
boolean certRequested;
if (getNeedClientAuth()) {
NativeCrypto.SSL_set_verify(sslNativePointer,
NativeCrypto.SSL_VERIFY_PEER
| NativeCrypto.SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
certRequested = true;
// ... over just wanting it...
} else if (getWantClientAuth()) {
NativeCrypto.SSL_set_verify(sslNativePointer, NativeCrypto.SSL_VERIFY_PEER);
certRequested = true;
// ... and we must disable verification if we don't want client auth.
} else {
NativeCrypto.SSL_set_verify(sslNativePointer, NativeCrypto.SSL_VERIFY_NONE);
certRequested = false;
}
if (certRequested) {
X509TrustManager trustManager = getX509TrustManager();
X509Certificate[] issuers = trustManager.getAcceptedIssuers();
if (issuers != null && issuers.length != 0) {
byte[][] issuersBytes;
try {
issuersBytes = encodeIssuerX509Principals(issuers);
} catch (CertificateEncodingException e) {
throw new IOException("Problem encoding principals", e);
}
NativeCrypto.SSL_set_client_CA_list(sslNativePointer, issuersBytes);
}
}
}
}
OpenSSLSessionImpl setupSession(long sslSessionNativePointer, long sslNativePointer,
final OpenSSLSessionImpl sessionToReuse, String hostname, int port,
boolean handshakeCompleted) throws IOException {
OpenSSLSessionImpl sslSession = null;
byte[] sessionId = NativeCrypto.SSL_SESSION_session_id(sslSessionNativePointer);
if (sessionToReuse != null && Arrays.equals(sessionToReuse.getId(), sessionId)) {
sslSession = sessionToReuse;
sslSession.lastAccessedTime = System.currentTimeMillis();
NativeCrypto.SSL_SESSION_free(sslSessionNativePointer);
} else {
if (!getEnableSessionCreation()) {
// Should have been prevented by
// NativeCrypto.SSL_set_session_creation_enabled
throw new IllegalStateException("SSL Session may not be created");
}
X509Certificate[] localCertificates = createCertChain(NativeCrypto
.SSL_get_certificate(sslNativePointer));
X509Certificate[] peerCertificates = createCertChain(NativeCrypto
.SSL_get_peer_cert_chain(sslNativePointer));
sslSession = new OpenSSLSessionImpl(sslSessionNativePointer, localCertificates,
peerCertificates, hostname, port, getSessionContext());
// if not, putSession later in handshakeCompleted() callback
if (handshakeCompleted) {
getSessionContext().putSession(sslSession);
}
}
return sslSession;
}
void chooseClientCertificate(byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals,
long sslNativePointer, AliasChooser chooser) throws SSLException,
CertificateEncodingException {
Set<String> keyTypesSet = getSupportedClientKeyTypes(keyTypeBytes);
String[] keyTypes = keyTypesSet.toArray(new String[keyTypesSet.size()]);
X500Principal[] issuers;
if (asn1DerEncodedPrincipals == null) {
issuers = null;
} else {
issuers = new X500Principal[asn1DerEncodedPrincipals.length];
for (int i = 0; i < asn1DerEncodedPrincipals.length; i++) {
issuers[i] = new X500Principal(asn1DerEncodedPrincipals[i]);
}
}
X509KeyManager keyManager = getX509KeyManager();
String alias = (keyManager != null) ? chooser.chooseClientAlias(keyManager, issuers,
keyTypes) : null;
setCertificate(sslNativePointer, alias);
}
/**
* @see NativeCrypto.SSLHandshakeCallbacks#clientPSKKeyRequested(String, byte[], byte[])
*/
int clientPSKKeyRequested(
String identityHint, byte[] identityBytesOut, byte[] key, PSKCallbacks pskCallbacks) {
PSKKeyManager pskKeyManager = getPSKKeyManager();
if (pskKeyManager == null) {
return 0;
}
String identity = pskCallbacks.chooseClientPSKIdentity(pskKeyManager, identityHint);
// Store identity in NULL-terminated modified UTF-8 representation into ientityBytesOut
byte[] identityBytes;
if (identity == null) {
identity = "";
identityBytes = EmptyArray.BYTE;
} else if (identity.isEmpty()) {
identityBytes = EmptyArray.BYTE;
} else {
try {
identityBytes = identity.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 encoding not supported", e);
}
}
if (identityBytes.length + 1 > identityBytesOut.length) {
// Insufficient space in the output buffer
return 0;
}
if (identityBytes.length > 0) {
System.arraycopy(identityBytes, 0, identityBytesOut, 0, identityBytes.length);
}
identityBytesOut[identityBytes.length] = 0;
SecretKey secretKey = pskCallbacks.getPSKKey(pskKeyManager, identityHint, identity);
byte[] secretKeyBytes = secretKey.getEncoded();
if (secretKeyBytes == null) {
return 0;
} else if (secretKeyBytes.length > key.length) {
// Insufficient space in the output buffer
return 0;
}
System.arraycopy(secretKeyBytes, 0, key, 0, secretKeyBytes.length);
return secretKeyBytes.length;
}
/**
* @see NativeCrypto.SSLHandshakeCallbacks#serverPSKKeyRequested(String, String, byte[])
*/
int serverPSKKeyRequested(
String identityHint, String identity, byte[] key, PSKCallbacks pskCallbacks) {
PSKKeyManager pskKeyManager = getPSKKeyManager();
if (pskKeyManager == null) {
return 0;
}
SecretKey secretKey = pskCallbacks.getPSKKey(pskKeyManager, identityHint, identity);
byte[] secretKeyBytes = secretKey.getEncoded();
if (secretKeyBytes == null) {
return 0;
} else if (secretKeyBytes.length > key.length) {
return 0;
}
System.arraycopy(secretKeyBytes, 0, key, 0, secretKeyBytes.length);
return secretKeyBytes.length;
}
/**
* Gets the suitable session reference from the session cache container.
*/
OpenSSLSessionImpl getCachedClientSession(ClientSessionContext sessionContext, String hostName,
int port) {
if (hostName == null) {
return null;
}
OpenSSLSessionImpl session = (OpenSSLSessionImpl) sessionContext.getSession(hostName, port);
if (session == null) {
return null;
}
String protocol = session.getProtocol();
boolean protocolFound = false;
for (String enabledProtocol : enabledProtocols) {
if (protocol.equals(enabledProtocol)) {
protocolFound = true;
break;
}
}
if (!protocolFound) {
return null;
}
String cipherSuite = session.getCipherSuite();
boolean cipherSuiteFound = false;
for (String enabledCipherSuite : enabledCipherSuites) {
if (cipherSuite.equals(enabledCipherSuite)) {
cipherSuiteFound = true;
break;
}
}
if (!cipherSuiteFound) {
return null;
}
return session;
}
/**
* For abstracting the X509KeyManager calls between
* {@link X509KeyManager#chooseClientAlias(String[], java.security.Principal[], java.net.Socket)}
* and
* {@link X509ExtendedKeyManager#chooseEngineClientAlias(String[], java.security.Principal[], javax.net.ssl.SSLEngine)}
*/
public interface AliasChooser {
String chooseClientAlias(X509KeyManager keyManager, X500Principal[] issuers,
String[] keyTypes);
String chooseServerAlias(X509KeyManager keyManager, String keyType);
}
/**
* For abstracting the {@code PSKKeyManager} calls between those taking an {@code SSLSocket} and
* those taking an {@code SSLEngine}.
*/
public interface PSKCallbacks {
String chooseServerPSKIdentityHint(PSKKeyManager keyManager);
String chooseClientPSKIdentity(PSKKeyManager keyManager, String identityHint);
SecretKey getPSKKey(PSKKeyManager keyManager, String identityHint, String identity);
}
/**
* Returns the clone of this object.
* @return the clone.
*/
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
private static X509KeyManager getDefaultX509KeyManager() throws KeyManagementException {
X509KeyManager result = defaultX509KeyManager;
if (result == null) {
// single-check idiom
defaultX509KeyManager = result = createDefaultX509KeyManager();
}
return result;
}
private static X509KeyManager createDefaultX509KeyManager() throws KeyManagementException {
try {
String algorithm = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
kmf.init(null, null);
KeyManager[] kms = kmf.getKeyManagers();
X509KeyManager result = findFirstX509KeyManager(kms);
if (result == null) {
throw new KeyManagementException("No X509KeyManager among default KeyManagers: "
+ Arrays.toString(kms));
}
return result;
} catch (NoSuchAlgorithmException e) {
throw new KeyManagementException(e);
} catch (KeyStoreException e) {
throw new KeyManagementException(e);
} catch (UnrecoverableKeyException e) {
throw new KeyManagementException(e);
}
}
/**
* Finds the first {@link X509KeyManager} element in the provided array.
*
* @return the first {@code X509KeyManager} or {@code null} if not found.
*/
private static X509KeyManager findFirstX509KeyManager(KeyManager[] kms) {
for (KeyManager km : kms) {
if (km instanceof X509KeyManager) {
return (X509KeyManager)km;
}
}
return null;
}
/**
* Finds the first {@link PSKKeyManager} element in the provided array.
*
* @return the first {@code PSKKeyManager} or {@code null} if not found.
*/
private static PSKKeyManager findFirstPSKKeyManager(KeyManager[] kms) {
for (KeyManager km : kms) {
if (km instanceof PSKKeyManager) {
return (PSKKeyManager)km;
} else if (km != null) {
try {
return DuckTypedPSKKeyManager.getInstance(km);
} catch (NoSuchMethodException ignored) {}
}
}
return null;
}
/**
* Gets the default X.509 trust manager.
* <p>
* TODO: Move this to a published API under dalvik.system.
*/
public static X509TrustManager getDefaultX509TrustManager()
throws KeyManagementException {
X509TrustManager result = defaultX509TrustManager;
if (result == null) {
// single-check idiom
defaultX509TrustManager = result = createDefaultX509TrustManager();
}
return result;
}
private static X509TrustManager createDefaultX509TrustManager()
throws KeyManagementException {
try {
String algorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
tmf.init((KeyStore) null);
TrustManager[] tms = tmf.getTrustManagers();
X509TrustManager trustManager = findFirstX509TrustManager(tms);
if (trustManager == null) {
throw new KeyManagementException(
"No X509TrustManager in among default TrustManagers: "
+ Arrays.toString(tms));
}
return trustManager;
} catch (NoSuchAlgorithmException e) {
throw new KeyManagementException(e);
} catch (KeyStoreException e) {
throw new KeyManagementException(e);
}
}
/**
* Finds the first {@link X509TrustManager} element in the provided array.
*
* @return the first {@code X509TrustManager} or {@code null} if not found.
*/
private static X509TrustManager findFirstX509TrustManager(TrustManager[] tms) {
for (TrustManager tm : tms) {
if (tm instanceof X509TrustManager) {
return (X509TrustManager) tm;
}
}
return null;
}
public String getEndpointIdentificationAlgorithm() {
return endpointIdentificationAlgorithm;
}
public void setEndpointIdentificationAlgorithm(String endpointIdentificationAlgorithm) {
this.endpointIdentificationAlgorithm = endpointIdentificationAlgorithm;
}
/** Key type: RSA certificate. */
private static final String KEY_TYPE_RSA = "RSA";
/** Key type: Diffie-Hellman certificate signed by issuer with RSA signature. */
private static final String KEY_TYPE_DH_RSA = "DH_RSA";
/** Key type: Elliptic Curve certificate. */
private static final String KEY_TYPE_EC = "EC";
/** Key type: Elliptic Curve certificate signed by issuer with ECDSA signature. */
private static final String KEY_TYPE_EC_EC = "EC_EC";
/** Key type: Elliptic Curve certificate signed by issuer with RSA signature. */
private static final String KEY_TYPE_EC_RSA = "EC_RSA";
/**
* Returns key type constant suitable for calling X509KeyManager.chooseServerAlias or
* X509ExtendedKeyManager.chooseEngineServerAlias. Returns {@code null} for key exchanges that
* do not use X.509 for server authentication.
*/
private static String getServerX509KeyType(long sslCipherNative) throws SSLException {
String kx_name = NativeCrypto.SSL_CIPHER_get_kx_name(sslCipherNative);
if (kx_name.equals("RSA") || kx_name.equals("DHE_RSA") || kx_name.equals("ECDHE_RSA")) {
return KEY_TYPE_RSA;
} else if (kx_name.equals("ECDHE_ECDSA")) {
return KEY_TYPE_EC;
} else if (kx_name.equals("ECDH_RSA")) {
return KEY_TYPE_EC_RSA;
} else if (kx_name.equals("ECDH_ECDSA")) {
return KEY_TYPE_EC_EC;
} else if (kx_name.equals("DH_RSA")) {
return KEY_TYPE_DH_RSA;
} else {
return null;
}
}
/**
* Similar to getServerKeyType, but returns value given TLS
* ClientCertificateType byte values from a CertificateRequest
* message for use with X509KeyManager.chooseClientAlias or
* X509ExtendedKeyManager.chooseEngineClientAlias.
* <p>
* Visible for testing.
*/
public static String getClientKeyType(byte clientCertificateType) {
// See also http://www.ietf.org/assignments/tls-parameters/tls-parameters.xml
switch (clientCertificateType) {
case NativeConstants.TLS_CT_RSA_SIGN:
return KEY_TYPE_RSA; // RFC rsa_sign
case NativeConstants.TLS_CT_RSA_FIXED_DH:
return KEY_TYPE_DH_RSA; // RFC rsa_fixed_dh
case NativeConstants.TLS_CT_ECDSA_SIGN:
return KEY_TYPE_EC; // RFC ecdsa_sign
case NativeConstants.TLS_CT_RSA_FIXED_ECDH:
return KEY_TYPE_EC_RSA; // RFC rsa_fixed_ecdh
case NativeConstants.TLS_CT_ECDSA_FIXED_ECDH:
return KEY_TYPE_EC_EC; // RFC ecdsa_fixed_ecdh
default:
return null;
}
}
/**
* Gets the supported key types for client certificates based on the
* {@code ClientCertificateType} values provided by the server.
*
* @param clientCertificateTypes {@code ClientCertificateType} values provided by the server.
* See https://www.ietf.org/assignments/tls-parameters/tls-parameters.xml.
* @return supported key types that can be used in {@code X509KeyManager.chooseClientAlias} and
* {@code X509ExtendedKeyManager.chooseEngineClientAlias}.
*
* Visible for testing.
*/
public static Set<String> getSupportedClientKeyTypes(byte[] clientCertificateTypes) {
Set<String> result = new HashSet<String>(clientCertificateTypes.length);
for (byte keyTypeCode : clientCertificateTypes) {
String keyType = getClientKeyType(keyTypeCode);
if (keyType == null) {
// Unsupported client key type -- ignore
continue;
}
result.add(keyType);
}
return result;
}
private static String[] getDefaultCipherSuites(
boolean x509CipherSuitesNeeded,
boolean pskCipherSuitesNeeded) {
if (x509CipherSuitesNeeded) {
// X.509 based cipher suites need to be listed.
if (pskCipherSuitesNeeded) {
// Both X.509 and PSK based cipher suites need to be listed. Because TLS-PSK is not
// normally used, we assume that when PSK cipher suites are requested here they
// should be preferred over other cipher suites. Thus, we give PSK cipher suites
// higher priority than X.509 cipher suites.
// NOTE: There are cipher suites that use both X.509 and PSK (e.g., those based on
// RSA_PSK key exchange). However, these cipher suites are not currently supported.
return concat(
NativeCrypto.DEFAULT_PSK_CIPHER_SUITES,
NativeCrypto.DEFAULT_X509_CIPHER_SUITES,
new String[] {NativeCrypto.TLS_EMPTY_RENEGOTIATION_INFO_SCSV});
} else {
// Only X.509 cipher suites need to be listed.
return concat(
NativeCrypto.DEFAULT_X509_CIPHER_SUITES,
new String[] {NativeCrypto.TLS_EMPTY_RENEGOTIATION_INFO_SCSV});
}
} else if (pskCipherSuitesNeeded) {
// Only PSK cipher suites need to be listed.
return concat(
NativeCrypto.DEFAULT_PSK_CIPHER_SUITES,
new String[] {NativeCrypto.TLS_EMPTY_RENEGOTIATION_INFO_SCSV});
} else {
// Neither X.509 nor PSK cipher suites need to be listed.
return new String[] {NativeCrypto.TLS_EMPTY_RENEGOTIATION_INFO_SCSV};
}
}
private static String[] concat(String[]... arrays) {
int resultLength = 0;
for (String[] array : arrays) {
resultLength += array.length;
}
String[] result = new String[resultLength];
int resultOffset = 0;
for (String[] array : arrays) {
System.arraycopy(array, 0, result, resultOffset, array.length);
resultOffset += array.length;
}
return result;
}
}