|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.ssl; |
|
|
|
import java.io.ByteArrayInputStream; |
|
import java.io.IOException; |
|
import java.nio.ByteBuffer; |
|
import java.security.PublicKey; |
|
import java.security.cert.CertPathValidatorException; |
|
import java.security.cert.CertPathValidatorException.BasicReason; |
|
import java.security.cert.CertPathValidatorException.Reason; |
|
import java.security.cert.CertificateEncodingException; |
|
import java.security.cert.CertificateException; |
|
import java.security.cert.CertificateFactory; |
|
import java.security.cert.CertificateParsingException; |
|
import java.security.cert.X509Certificate; |
|
import java.text.MessageFormat; |
|
import java.util.ArrayList; |
|
import java.util.Arrays; |
|
import java.util.Collection; |
|
import java.util.Collections; |
|
import java.util.HashSet; |
|
import java.util.LinkedList; |
|
import java.util.List; |
|
import java.util.Locale; |
|
import javax.net.ssl.SSLEngine; |
|
import javax.net.ssl.SSLException; |
|
import javax.net.ssl.SSLProtocolException; |
|
import javax.net.ssl.SSLSocket; |
|
import javax.net.ssl.X509ExtendedTrustManager; |
|
import javax.net.ssl.X509TrustManager; |
|
import javax.security.auth.x500.X500Principal; |
|
import static sun.security.ssl.ClientAuthType.CLIENT_AUTH_REQUIRED; |
|
import sun.security.ssl.ClientHello.ClientHelloMessage; |
|
import sun.security.ssl.SSLHandshake.HandshakeMessage; |
|
import sun.security.ssl.X509Authentication.X509Credentials; |
|
import sun.security.ssl.X509Authentication.X509Possession; |
|
|
|
|
|
|
|
*/ |
|
final class CertificateMessage { |
|
static final SSLConsumer t12HandshakeConsumer = |
|
new T12CertificateConsumer(); |
|
static final HandshakeProducer t12HandshakeProducer = |
|
new T12CertificateProducer(); |
|
|
|
static final SSLConsumer t13HandshakeConsumer = |
|
new T13CertificateConsumer(); |
|
static final HandshakeProducer t13HandshakeProducer = |
|
new T13CertificateProducer(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static final class T12CertificateMessage extends HandshakeMessage { |
|
final List<byte[]> encodedCertChain; |
|
|
|
T12CertificateMessage(HandshakeContext handshakeContext, |
|
X509Certificate[] certChain) throws SSLException { |
|
super(handshakeContext); |
|
|
|
List<byte[]> encodedCerts = new ArrayList<>(certChain.length); |
|
for (X509Certificate cert : certChain) { |
|
try { |
|
encodedCerts.add(cert.getEncoded()); |
|
} catch (CertificateEncodingException cee) { |
|
|
|
throw handshakeContext.conContext.fatal( |
|
Alert.INTERNAL_ERROR, |
|
"Could not encode certificate (" + |
|
cert.getSubjectX500Principal() + ")", cee); |
|
} |
|
} |
|
|
|
this.encodedCertChain = encodedCerts; |
|
} |
|
|
|
T12CertificateMessage(HandshakeContext handshakeContext, |
|
ByteBuffer m) throws IOException { |
|
super(handshakeContext); |
|
|
|
int listLen = Record.getInt24(m); |
|
if (listLen > m.remaining()) { |
|
throw handshakeContext.conContext.fatal( |
|
Alert.ILLEGAL_PARAMETER, |
|
"Error parsing certificate message:no sufficient data"); |
|
} |
|
if (listLen > 0) { |
|
List<byte[]> encodedCerts = new LinkedList<>(); |
|
while (listLen > 0) { |
|
byte[] encodedCert = Record.getBytes24(m); |
|
listLen -= (3 + encodedCert.length); |
|
encodedCerts.add(encodedCert); |
|
if (encodedCerts.size() > SSLConfiguration.maxCertificateChainLength) { |
|
throw new SSLProtocolException( |
|
"The certificate chain length (" |
|
+ encodedCerts.size() |
|
+ ") exceeds the maximum allowed length (" |
|
+ SSLConfiguration.maxCertificateChainLength |
|
+ ")"); |
|
} |
|
|
|
} |
|
this.encodedCertChain = encodedCerts; |
|
} else { |
|
this.encodedCertChain = Collections.emptyList(); |
|
} |
|
} |
|
|
|
@Override |
|
public SSLHandshake handshakeType() { |
|
return SSLHandshake.CERTIFICATE; |
|
} |
|
|
|
@Override |
|
public int messageLength() { |
|
int msgLen = 3; |
|
for (byte[] encodedCert : encodedCertChain) { |
|
msgLen += (encodedCert.length + 3); |
|
} |
|
|
|
return msgLen; |
|
} |
|
|
|
@Override |
|
public void send(HandshakeOutStream hos) throws IOException { |
|
int listLen = 0; |
|
for (byte[] encodedCert : encodedCertChain) { |
|
listLen += (encodedCert.length + 3); |
|
} |
|
|
|
hos.putInt24(listLen); |
|
for (byte[] encodedCert : encodedCertChain) { |
|
hos.putBytes24(encodedCert); |
|
} |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
if (encodedCertChain.isEmpty()) { |
|
return "\"Certificates\": <empty list>"; |
|
} |
|
|
|
Object[] x509Certs = new Object[encodedCertChain.size()]; |
|
try { |
|
CertificateFactory cf = CertificateFactory.getInstance("X.509"); |
|
int i = 0; |
|
for (byte[] encodedCert : encodedCertChain) { |
|
Object obj; |
|
try { |
|
obj = (X509Certificate)cf.generateCertificate( |
|
new ByteArrayInputStream(encodedCert)); |
|
} catch (CertificateException ce) { |
|
obj = encodedCert; |
|
} |
|
x509Certs[i++] = obj; |
|
} |
|
} catch (CertificateException ce) { |
|
|
|
int i = 0; |
|
for (byte[] encodedCert : encodedCertChain) { |
|
x509Certs[i++] = encodedCert; |
|
} |
|
} |
|
|
|
MessageFormat messageFormat = new MessageFormat( |
|
"\"Certificates\": [\n" + |
|
"{0}\n" + |
|
"]", |
|
Locale.ENGLISH); |
|
Object[] messageFields = { |
|
SSLLogger.toString(x509Certs) |
|
}; |
|
|
|
return messageFormat.format(messageFields); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class T12CertificateProducer implements HandshakeProducer { |
|
|
|
private T12CertificateProducer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public byte[] produce(ConnectionContext context, |
|
HandshakeMessage message) throws IOException { |
|
|
|
HandshakeContext hc = (HandshakeContext)context; |
|
if (hc.sslConfig.isClientMode) { |
|
return onProduceCertificate( |
|
(ClientHandshakeContext)context, message); |
|
} else { |
|
return onProduceCertificate( |
|
(ServerHandshakeContext)context, message); |
|
} |
|
} |
|
|
|
private byte[] onProduceCertificate(ServerHandshakeContext shc, |
|
SSLHandshake.HandshakeMessage message) throws IOException { |
|
X509Possession x509Possession = null; |
|
for (SSLPossession possession : shc.handshakePossessions) { |
|
if (possession instanceof X509Possession) { |
|
x509Possession = (X509Possession)possession; |
|
break; |
|
} |
|
} |
|
|
|
if (x509Possession == null) { |
|
throw shc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
"No expected X.509 certificate for server authentication"); |
|
} |
|
|
|
shc.handshakeSession.setLocalPrivateKey( |
|
x509Possession.popPrivateKey); |
|
shc.handshakeSession.setLocalCertificates(x509Possession.popCerts); |
|
T12CertificateMessage cm = |
|
new T12CertificateMessage(shc, x509Possession.popCerts); |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Produced server Certificate handshake message", cm); |
|
} |
|
|
|
|
|
cm.write(shc.handshakeOutput); |
|
shc.handshakeOutput.flush(); |
|
|
|
|
|
return null; |
|
} |
|
|
|
private byte[] onProduceCertificate(ClientHandshakeContext chc, |
|
SSLHandshake.HandshakeMessage message) throws IOException { |
|
X509Possession x509Possession = null; |
|
for (SSLPossession possession : chc.handshakePossessions) { |
|
if (possession instanceof X509Possession) { |
|
x509Possession = (X509Possession)possession; |
|
break; |
|
} |
|
} |
|
|
|
// Report to the server if no appropriate cert was found. For |
|
// SSL 3.0, send a no_certificate alert; TLS 1.0/1.1/1.2 uses |
|
|
|
if (x509Possession == null) { |
|
if (chc.negotiatedProtocol.useTLS10PlusSpec()) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"No X.509 certificate for client authentication, " + |
|
"use empty Certificate message instead"); |
|
} |
|
|
|
x509Possession = |
|
new X509Possession(null, new X509Certificate[0]); |
|
} else { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"No X.509 certificate for client authentication, " + |
|
"send a no_certificate alert"); |
|
} |
|
|
|
chc.conContext.warning(Alert.NO_CERTIFICATE); |
|
return null; |
|
} |
|
} |
|
|
|
chc.handshakeSession.setLocalPrivateKey( |
|
x509Possession.popPrivateKey); |
|
if (x509Possession.popCerts != null && |
|
x509Possession.popCerts.length != 0) { |
|
chc.handshakeSession.setLocalCertificates( |
|
x509Possession.popCerts); |
|
} else { |
|
chc.handshakeSession.setLocalCertificates(null); |
|
} |
|
T12CertificateMessage cm = |
|
new T12CertificateMessage(chc, x509Possession.popCerts); |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Produced client Certificate handshake message", cm); |
|
} |
|
|
|
|
|
cm.write(chc.handshakeOutput); |
|
chc.handshakeOutput.flush(); |
|
|
|
|
|
return null; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
static final |
|
class T12CertificateConsumer implements SSLConsumer { |
|
|
|
private T12CertificateConsumer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public void consume(ConnectionContext context, |
|
ByteBuffer message) throws IOException { |
|
|
|
HandshakeContext hc = (HandshakeContext)context; |
|
|
|
|
|
hc.handshakeConsumers.remove(SSLHandshake.CERTIFICATE.id); |
|
|
|
T12CertificateMessage cm = new T12CertificateMessage(hc, message); |
|
if (hc.sslConfig.isClientMode) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Consuming server Certificate handshake message", cm); |
|
} |
|
onCertificate((ClientHandshakeContext)context, cm); |
|
} else { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Consuming client Certificate handshake message", cm); |
|
} |
|
onCertificate((ServerHandshakeContext)context, cm); |
|
} |
|
} |
|
|
|
private void onCertificate(ServerHandshakeContext shc, |
|
T12CertificateMessage certificateMessage )throws IOException { |
|
List<byte[]> encodedCerts = certificateMessage.encodedCertChain; |
|
if (encodedCerts == null || encodedCerts.isEmpty()) { |
|
// For empty Certificate messages, we should not expect |
|
|
|
shc.handshakeConsumers.remove( |
|
SSLHandshake.CERTIFICATE_VERIFY.id); |
|
if (shc.sslConfig.clientAuthType != |
|
ClientAuthType.CLIENT_AUTH_REQUESTED) { |
|
|
|
throw shc.conContext.fatal(Alert.BAD_CERTIFICATE, |
|
"Empty server certificate chain"); |
|
} else { |
|
return; |
|
} |
|
} |
|
|
|
X509Certificate[] x509Certs = |
|
new X509Certificate[encodedCerts.size()]; |
|
try { |
|
CertificateFactory cf = CertificateFactory.getInstance("X.509"); |
|
int i = 0; |
|
for (byte[] encodedCert : encodedCerts) { |
|
x509Certs[i++] = (X509Certificate)cf.generateCertificate( |
|
new ByteArrayInputStream(encodedCert)); |
|
} |
|
} catch (CertificateException ce) { |
|
throw shc.conContext.fatal(Alert.BAD_CERTIFICATE, |
|
"Failed to parse server certificates", ce); |
|
} |
|
|
|
checkClientCerts(shc, x509Certs); |
|
|
|
// |
|
// update |
|
|
|
shc.handshakeCredentials.add( |
|
new X509Credentials(x509Certs[0].getPublicKey(), x509Certs)); |
|
shc.handshakeSession.setPeerCertificates(x509Certs); |
|
} |
|
|
|
private void onCertificate(ClientHandshakeContext chc, |
|
T12CertificateMessage certificateMessage) throws IOException { |
|
List<byte[]> encodedCerts = certificateMessage.encodedCertChain; |
|
if (encodedCerts == null || encodedCerts.isEmpty()) { |
|
throw chc.conContext.fatal(Alert.BAD_CERTIFICATE, |
|
"Empty server certificate chain"); |
|
} |
|
|
|
X509Certificate[] x509Certs = |
|
new X509Certificate[encodedCerts.size()]; |
|
try { |
|
CertificateFactory cf = CertificateFactory.getInstance("X.509"); |
|
int i = 0; |
|
for (byte[] encodedCert : encodedCerts) { |
|
x509Certs[i++] = (X509Certificate)cf.generateCertificate( |
|
new ByteArrayInputStream(encodedCert)); |
|
} |
|
} catch (CertificateException ce) { |
|
throw chc.conContext.fatal(Alert.BAD_CERTIFICATE, |
|
"Failed to parse server certificates", ce); |
|
} |
|
|
|
// Allow server certificate change in client side during |
|
// renegotiation after a session-resumption abbreviated |
|
// initial handshake? |
|
// |
|
// DO NOT need to check allowUnsafeServerCertChange here. We only |
|
// reserve server certificates when allowUnsafeServerCertChange is |
|
|
|
if (chc.reservedServerCerts != null && |
|
!chc.handshakeSession.useExtendedMasterSecret) { |
|
// It is not necessary to check the certificate update if |
|
|
|
String identityAlg = chc.sslConfig.identificationProtocol; |
|
if ((identityAlg == null || identityAlg.isEmpty()) && |
|
!isIdentityEquivalent(x509Certs[0], |
|
chc.reservedServerCerts[0])) { |
|
throw chc.conContext.fatal(Alert.BAD_CERTIFICATE, |
|
"server certificate change is restricted " + |
|
"during renegotiation"); |
|
} |
|
} |
|
|
|
|
|
if (chc.staplingActive) { |
|
// Defer the certificate check until after we've received the |
|
// CertificateStatus message. If that message doesn't come in |
|
// immediately following this message we will execute the |
|
|
|
chc.deferredCerts = x509Certs; |
|
} else { |
|
|
|
checkServerCerts(chc, x509Certs); |
|
} |
|
|
|
// |
|
// update |
|
|
|
chc.handshakeCredentials.add( |
|
new X509Credentials(x509Certs[0].getPublicKey(), x509Certs)); |
|
chc.handshakeSession.setPeerCertificates(x509Certs); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static boolean isIdentityEquivalent(X509Certificate thisCert, |
|
X509Certificate prevCert) { |
|
if (thisCert.equals(prevCert)) { |
|
return true; |
|
} |
|
|
|
|
|
Collection<List<?>> thisSubjectAltNames = null; |
|
try { |
|
thisSubjectAltNames = thisCert.getSubjectAlternativeNames(); |
|
} catch (CertificateParsingException cpe) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("handshake")) { |
|
SSLLogger.fine( |
|
"Attempt to obtain subjectAltNames extension failed!"); |
|
} |
|
} |
|
|
|
Collection<List<?>> prevSubjectAltNames = null; |
|
try { |
|
prevSubjectAltNames = prevCert.getSubjectAlternativeNames(); |
|
} catch (CertificateParsingException cpe) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("handshake")) { |
|
SSLLogger.fine( |
|
"Attempt to obtain subjectAltNames extension failed!"); |
|
} |
|
} |
|
|
|
if (thisSubjectAltNames != null && prevSubjectAltNames != null) { |
|
// check the iPAddress field in subjectAltName extension |
|
// |
|
|
|
Collection<String> thisSubAltIPAddrs = |
|
getSubjectAltNames(thisSubjectAltNames, 7); |
|
Collection<String> prevSubAltIPAddrs = |
|
getSubjectAltNames(prevSubjectAltNames, 7); |
|
if (thisSubAltIPAddrs != null && prevSubAltIPAddrs != null && |
|
isEquivalent(thisSubAltIPAddrs, prevSubAltIPAddrs)) { |
|
return true; |
|
} |
|
|
|
// check the dNSName field in subjectAltName extension |
|
|
|
Collection<String> thisSubAltDnsNames = |
|
getSubjectAltNames(thisSubjectAltNames, 2); |
|
Collection<String> prevSubAltDnsNames = |
|
getSubjectAltNames(prevSubjectAltNames, 2); |
|
if (thisSubAltDnsNames != null && prevSubAltDnsNames != null && |
|
isEquivalent(thisSubAltDnsNames, prevSubAltDnsNames)) { |
|
return true; |
|
} |
|
} |
|
|
|
|
|
X500Principal thisSubject = thisCert.getSubjectX500Principal(); |
|
X500Principal prevSubject = prevCert.getSubjectX500Principal(); |
|
X500Principal thisIssuer = thisCert.getIssuerX500Principal(); |
|
X500Principal prevIssuer = prevCert.getIssuerX500Principal(); |
|
|
|
return (!thisSubject.getName().isEmpty() && |
|
!prevSubject.getName().isEmpty() && |
|
thisSubject.equals(prevSubject) && |
|
thisIssuer.equals(prevIssuer)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static Collection<String> getSubjectAltNames( |
|
Collection<List<?>> subjectAltNames, int type) { |
|
HashSet<String> subAltDnsNames = null; |
|
for (List<?> subjectAltName : subjectAltNames) { |
|
int subjectAltNameType = (Integer)subjectAltName.get(0); |
|
if (subjectAltNameType == type) { |
|
String subAltDnsName = (String)subjectAltName.get(1); |
|
if ((subAltDnsName != null) && !subAltDnsName.isEmpty()) { |
|
if (subAltDnsNames == null) { |
|
subAltDnsNames = |
|
new HashSet<>(subjectAltNames.size()); |
|
} |
|
subAltDnsNames.add(subAltDnsName); |
|
} |
|
} |
|
} |
|
|
|
return subAltDnsNames; |
|
} |
|
|
|
private static boolean isEquivalent(Collection<String> thisSubAltNames, |
|
Collection<String> prevSubAltNames) { |
|
for (String thisSubAltName : thisSubAltNames) { |
|
for (String prevSubAltName : prevSubAltNames) { |
|
// Only allow the exactly match. No wildcard character |
|
|
|
if (thisSubAltName.equalsIgnoreCase(prevSubAltName)) { |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static void checkServerCerts(ClientHandshakeContext chc, |
|
X509Certificate[] certs) throws IOException { |
|
|
|
X509TrustManager tm = chc.sslContext.getX509TrustManager(); |
|
|
|
// find out the key exchange algorithm used |
|
|
|
String keyExchangeString; |
|
if (chc.negotiatedCipherSuite.keyExchange == |
|
CipherSuite.KeyExchange.K_RSA_EXPORT || |
|
chc.negotiatedCipherSuite.keyExchange == |
|
CipherSuite.KeyExchange.K_DHE_RSA_EXPORT) { |
|
keyExchangeString = CipherSuite.KeyExchange.K_RSA.name; |
|
} else { |
|
keyExchangeString = chc.negotiatedCipherSuite.keyExchange.name; |
|
} |
|
|
|
try { |
|
if (tm instanceof X509ExtendedTrustManager) { |
|
if (chc.conContext.transport instanceof SSLEngine) { |
|
SSLEngine engine = (SSLEngine)chc.conContext.transport; |
|
((X509ExtendedTrustManager)tm).checkServerTrusted( |
|
certs.clone(), |
|
keyExchangeString, |
|
engine); |
|
} else { |
|
SSLSocket socket = (SSLSocket)chc.conContext.transport; |
|
((X509ExtendedTrustManager)tm).checkServerTrusted( |
|
certs.clone(), |
|
keyExchangeString, |
|
socket); |
|
} |
|
} else { |
|
// Unlikely to happen, because we have wrapped the old |
|
|
|
throw new CertificateException( |
|
"Improper X509TrustManager implementation"); |
|
} |
|
|
|
// Once the server certificate chain has been validated, set |
|
|
|
chc.handshakeSession.setPeerCertificates(certs); |
|
} catch (CertificateException ce) { |
|
throw chc.conContext.fatal(getCertificateAlert(chc, ce), ce); |
|
} |
|
} |
|
|
|
private static void checkClientCerts(ServerHandshakeContext shc, |
|
X509Certificate[] certs) throws IOException { |
|
X509TrustManager tm = shc.sslContext.getX509TrustManager(); |
|
|
|
|
|
PublicKey key = certs[0].getPublicKey(); |
|
String keyAlgorithm = key.getAlgorithm(); |
|
String authType; |
|
switch (keyAlgorithm) { |
|
case "RSA": |
|
case "DSA": |
|
case "EC": |
|
case "RSASSA-PSS": |
|
authType = keyAlgorithm; |
|
break; |
|
default: |
|
|
|
authType = "UNKNOWN"; |
|
} |
|
|
|
try { |
|
if (tm instanceof X509ExtendedTrustManager) { |
|
if (shc.conContext.transport instanceof SSLEngine) { |
|
SSLEngine engine = (SSLEngine)shc.conContext.transport; |
|
((X509ExtendedTrustManager)tm).checkClientTrusted( |
|
certs.clone(), |
|
authType, |
|
engine); |
|
} else { |
|
SSLSocket socket = (SSLSocket)shc.conContext.transport; |
|
((X509ExtendedTrustManager)tm).checkClientTrusted( |
|
certs.clone(), |
|
authType, |
|
socket); |
|
} |
|
} else { |
|
// Unlikely to happen, because we have wrapped the old |
|
|
|
throw new CertificateException( |
|
"Improper X509TrustManager implementation"); |
|
} |
|
} catch (CertificateException ce) { |
|
throw shc.conContext.fatal(Alert.CERTIFICATE_UNKNOWN, ce); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static Alert getCertificateAlert( |
|
ClientHandshakeContext chc, CertificateException cexc) { |
|
// The specific reason for the failure will determine how to |
|
|
|
Alert alert = Alert.CERTIFICATE_UNKNOWN; |
|
|
|
Throwable baseCause = cexc.getCause(); |
|
if (baseCause instanceof CertPathValidatorException) { |
|
CertPathValidatorException cpve = |
|
(CertPathValidatorException)baseCause; |
|
Reason reason = cpve.getReason(); |
|
if (reason == BasicReason.REVOKED) { |
|
alert = chc.staplingActive ? |
|
Alert.BAD_CERT_STATUS_RESPONSE : |
|
Alert.CERTIFICATE_REVOKED; |
|
} else if ( |
|
reason == BasicReason.UNDETERMINED_REVOCATION_STATUS) { |
|
alert = chc.staplingActive ? |
|
Alert.BAD_CERT_STATUS_RESPONSE : |
|
Alert.CERTIFICATE_UNKNOWN; |
|
} else if (reason == BasicReason.ALGORITHM_CONSTRAINED) { |
|
alert = Alert.UNSUPPORTED_CERTIFICATE; |
|
} else if (reason == BasicReason.EXPIRED) { |
|
alert = Alert.CERTIFICATE_EXPIRED; |
|
} else if (reason == BasicReason.INVALID_SIGNATURE || |
|
reason == BasicReason.NOT_YET_VALID) { |
|
alert = Alert.BAD_CERTIFICATE; |
|
} |
|
} |
|
|
|
return alert; |
|
} |
|
|
|
} |
|
|
|
|
|
|
|
*/ |
|
static final class CertificateEntry { |
|
final byte[] encoded; |
|
private final SSLExtensions extensions; |
|
|
|
CertificateEntry(byte[] encoded, SSLExtensions extensions) { |
|
this.encoded = encoded; |
|
this.extensions = extensions; |
|
} |
|
|
|
private int getEncodedSize() { |
|
int extLen = extensions.length(); |
|
if (extLen == 0) { |
|
extLen = 2; |
|
} |
|
return 3 + encoded.length + extLen; |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
MessageFormat messageFormat = new MessageFormat( |
|
"\n'{'\n" + |
|
"{0}\n" + |
|
" \"extensions\": '{'\n" + |
|
"{1}\n" + |
|
" '}'\n" + |
|
"'}',", Locale.ENGLISH); |
|
|
|
Object x509Certs; |
|
try { |
|
|
|
CertificateFactory cf = CertificateFactory.getInstance("X.509"); |
|
x509Certs = |
|
cf.generateCertificate(new ByteArrayInputStream(encoded)); |
|
} catch (CertificateException ce) { |
|
|
|
x509Certs = encoded; |
|
} |
|
|
|
Object[] messageFields = { |
|
SSLLogger.toString(x509Certs), |
|
Utilities.indent(extensions.toString(), " ") |
|
}; |
|
|
|
return messageFormat.format(messageFields); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
static final class T13CertificateMessage extends HandshakeMessage { |
|
private final byte[] requestContext; |
|
private final List<CertificateEntry> certEntries; |
|
|
|
T13CertificateMessage(HandshakeContext context, |
|
byte[] requestContext, X509Certificate[] certificates) |
|
throws SSLException, CertificateException { |
|
super(context); |
|
|
|
this.requestContext = requestContext.clone(); |
|
this.certEntries = new LinkedList<>(); |
|
for (X509Certificate cert : certificates) { |
|
byte[] encoded = cert.getEncoded(); |
|
SSLExtensions extensions = new SSLExtensions(this); |
|
certEntries.add(new CertificateEntry(encoded, extensions)); |
|
} |
|
} |
|
|
|
T13CertificateMessage(HandshakeContext handshakeContext, |
|
byte[] requestContext, List<CertificateEntry> certificates) { |
|
super(handshakeContext); |
|
|
|
this.requestContext = requestContext.clone(); |
|
this.certEntries = certificates; |
|
} |
|
|
|
T13CertificateMessage(HandshakeContext handshakeContext, |
|
ByteBuffer m) throws IOException { |
|
super(handshakeContext); |
|
|
|
// struct { |
|
// opaque certificate_request_context<0..2^8-1>; |
|
// CertificateEntry certificate_list<0..2^24-1>; |
|
|
|
if (m.remaining() < 4) { |
|
throw new SSLProtocolException( |
|
"Invalid Certificate message: " + |
|
"insufficient data (length=" + m.remaining() + ")"); |
|
} |
|
this.requestContext = Record.getBytes8(m); |
|
|
|
if (m.remaining() < 3) { |
|
throw new SSLProtocolException( |
|
"Invalid Certificate message: " + |
|
"insufficient certificate entries data (length=" + |
|
m.remaining() + ")"); |
|
} |
|
|
|
int listLen = Record.getInt24(m); |
|
if (listLen != m.remaining()) { |
|
throw new SSLProtocolException( |
|
"Invalid Certificate message: " + |
|
"incorrect list length (length=" + listLen + ")"); |
|
} |
|
|
|
SSLExtension[] enabledExtensions = |
|
handshakeContext.sslConfig.getEnabledExtensions( |
|
SSLHandshake.CERTIFICATE); |
|
List<CertificateEntry> certList = new LinkedList<>(); |
|
while (m.hasRemaining()) { |
|
|
|
byte[] encodedCert = Record.getBytes24(m); |
|
if (encodedCert.length == 0) { |
|
throw new SSLProtocolException( |
|
"Invalid Certificate message: empty cert_data"); |
|
} |
|
|
|
SSLExtensions extensions = |
|
new SSLExtensions(this, m, enabledExtensions); |
|
certList.add(new CertificateEntry(encodedCert, extensions)); |
|
if (certList.size() > SSLConfiguration.maxCertificateChainLength) { |
|
throw new SSLProtocolException( |
|
"The certificate chain length (" |
|
+ certList.size() |
|
+ ") exceeds the maximum allowed length (" |
|
+ SSLConfiguration.maxCertificateChainLength |
|
+ ")"); |
|
} |
|
} |
|
|
|
this.certEntries = Collections.unmodifiableList(certList); |
|
} |
|
|
|
@Override |
|
public SSLHandshake handshakeType() { |
|
return SSLHandshake.CERTIFICATE; |
|
} |
|
|
|
@Override |
|
public int messageLength() { |
|
int msgLen = 4 + requestContext.length; |
|
for (CertificateEntry entry : certEntries) { |
|
msgLen += entry.getEncodedSize(); |
|
} |
|
|
|
return msgLen; |
|
} |
|
|
|
@Override |
|
public void send(HandshakeOutStream hos) throws IOException { |
|
int entryListLen = 0; |
|
for (CertificateEntry entry : certEntries) { |
|
entryListLen += entry.getEncodedSize(); |
|
} |
|
|
|
hos.putBytes8(requestContext); |
|
hos.putInt24(entryListLen); |
|
for (CertificateEntry entry : certEntries) { |
|
hos.putBytes24(entry.encoded); |
|
|
|
if (entry.extensions.length() == 0) { |
|
hos.putInt16(0); |
|
} else { |
|
entry.extensions.send(hos); |
|
} |
|
} |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
MessageFormat messageFormat = new MessageFormat( |
|
"\"Certificate\": '{'\n" + |
|
" \"certificate_request_context\": \"{0}\",\n" + |
|
" \"certificate_list\": [{1}\n]\n" + |
|
"'}'", |
|
Locale.ENGLISH); |
|
|
|
StringBuilder builder = new StringBuilder(512); |
|
for (CertificateEntry entry : certEntries) { |
|
builder.append(entry.toString()); |
|
} |
|
|
|
Object[] messageFields = { |
|
Utilities.toHexString(requestContext), |
|
Utilities.indent(builder.toString()) |
|
}; |
|
|
|
return messageFormat.format(messageFields); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class T13CertificateProducer implements HandshakeProducer { |
|
|
|
private T13CertificateProducer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public byte[] produce(ConnectionContext context, |
|
HandshakeMessage message) throws IOException { |
|
|
|
HandshakeContext hc = (HandshakeContext)context; |
|
if (hc.sslConfig.isClientMode) { |
|
return onProduceCertificate( |
|
(ClientHandshakeContext)context, message); |
|
} else { |
|
return onProduceCertificate( |
|
(ServerHandshakeContext)context, message); |
|
} |
|
} |
|
|
|
private byte[] onProduceCertificate(ServerHandshakeContext shc, |
|
HandshakeMessage message) throws IOException { |
|
ClientHelloMessage clientHello = (ClientHelloMessage)message; |
|
|
|
SSLPossession pos = choosePossession(shc, clientHello); |
|
if (pos == null) { |
|
throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, |
|
"No available authentication scheme"); |
|
} |
|
|
|
if (!(pos instanceof X509Possession)) { |
|
throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, |
|
"No X.509 certificate for server authentication"); |
|
} |
|
|
|
X509Possession x509Possession = (X509Possession)pos; |
|
X509Certificate[] localCerts = x509Possession.popCerts; |
|
if (localCerts == null || localCerts.length == 0) { |
|
throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, |
|
"No X.509 certificate for server authentication"); |
|
} |
|
|
|
|
|
shc.handshakePossessions.add(x509Possession); |
|
shc.handshakeSession.setLocalPrivateKey( |
|
x509Possession.popPrivateKey); |
|
shc.handshakeSession.setLocalCertificates(localCerts); |
|
T13CertificateMessage cm; |
|
try { |
|
cm = new T13CertificateMessage(shc, (new byte[0]), localCerts); |
|
} catch (SSLException | CertificateException ce) { |
|
throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, |
|
"Failed to produce server Certificate message", ce); |
|
} |
|
|
|
// Check the OCSP stapling extensions and attempt |
|
// to get responses. If the resulting stapleParams is non |
|
|
|
shc.stapleParams = StatusResponseManager.processStapling(shc); |
|
shc.staplingActive = (shc.stapleParams != null); |
|
|
|
// Process extensions for each CertificateEntry. |
|
// Since there can be multiple CertificateEntries within a |
|
// single CT message, we will pin a specific CertificateEntry |
|
// into the ServerHandshakeContext so individual extension |
|
// producers know which X509Certificate it is processing in |
|
|
|
SSLExtension[] enabledCTExts = shc.sslConfig.getEnabledExtensions( |
|
SSLHandshake.CERTIFICATE, |
|
Arrays.asList(ProtocolVersion.PROTOCOLS_OF_13)); |
|
for (CertificateEntry certEnt : cm.certEntries) { |
|
shc.currentCertEntry = certEnt; |
|
certEnt.extensions.produce(shc, enabledCTExts); |
|
} |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine("Produced server Certificate message", cm); |
|
} |
|
|
|
|
|
cm.write(shc.handshakeOutput); |
|
shc.handshakeOutput.flush(); |
|
|
|
|
|
return null; |
|
} |
|
|
|
private static SSLPossession choosePossession( |
|
HandshakeContext hc, |
|
ClientHelloMessage clientHello) throws IOException { |
|
if (hc.peerRequestedCertSignSchemes == null || |
|
hc.peerRequestedCertSignSchemes.isEmpty()) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning( |
|
"No signature_algorithms(_cert) in ClientHello"); |
|
} |
|
return null; |
|
} |
|
|
|
Collection<String> checkedKeyTypes = new HashSet<>(); |
|
for (SignatureScheme ss : hc.peerRequestedCertSignSchemes) { |
|
if (checkedKeyTypes.contains(ss.keyAlgorithm)) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning( |
|
"Unsupported authentication scheme: " + ss.name); |
|
} |
|
continue; |
|
} |
|
|
|
// Don't select a signature scheme unless we will be able to |
|
|
|
if (SignatureScheme.getPreferableAlgorithm( |
|
hc.algorithmConstraints, |
|
hc.peerRequestedSignatureSchemes, |
|
ss, hc.negotiatedProtocol) == null) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning( |
|
"Unable to produce CertificateVerify for " + |
|
"signature scheme: " + ss.name); |
|
} |
|
checkedKeyTypes.add(ss.keyAlgorithm); |
|
continue; |
|
} |
|
|
|
SSLAuthentication ka = X509Authentication.valueOf(ss); |
|
if (ka == null) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning( |
|
"Unsupported authentication scheme: " + ss.name); |
|
} |
|
checkedKeyTypes.add(ss.keyAlgorithm); |
|
continue; |
|
} |
|
|
|
SSLPossession pos = ka.createPossession(hc); |
|
if (pos == null) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning( |
|
"Unavailable authentication scheme: " + ss.name); |
|
} |
|
continue; |
|
} |
|
|
|
return pos; |
|
} |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning("No available authentication scheme"); |
|
} |
|
return null; |
|
} |
|
|
|
private byte[] onProduceCertificate(ClientHandshakeContext chc, |
|
HandshakeMessage message) throws IOException { |
|
ClientHelloMessage clientHello = (ClientHelloMessage)message; |
|
SSLPossession pos = choosePossession(chc, clientHello); |
|
X509Certificate[] localCerts; |
|
if (pos == null) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine("No available client authentication scheme"); |
|
} |
|
localCerts = new X509Certificate[0]; |
|
} else { |
|
chc.handshakePossessions.add(pos); |
|
if (!(pos instanceof X509Possession)) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"No X.509 certificate for client authentication"); |
|
} |
|
localCerts = new X509Certificate[0]; |
|
} else { |
|
X509Possession x509Possession = (X509Possession)pos; |
|
localCerts = x509Possession.popCerts; |
|
chc.handshakeSession.setLocalPrivateKey( |
|
x509Possession.popPrivateKey); |
|
} |
|
} |
|
|
|
if (localCerts != null && localCerts.length != 0) { |
|
chc.handshakeSession.setLocalCertificates(localCerts); |
|
} else { |
|
chc.handshakeSession.setLocalCertificates(null); |
|
} |
|
|
|
T13CertificateMessage cm; |
|
try { |
|
cm = new T13CertificateMessage( |
|
chc, chc.certRequestContext, localCerts); |
|
} catch (SSLException | CertificateException ce) { |
|
throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, |
|
"Failed to produce client Certificate message", ce); |
|
} |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine("Produced client Certificate message", cm); |
|
} |
|
|
|
|
|
cm.write(chc.handshakeOutput); |
|
chc.handshakeOutput.flush(); |
|
|
|
|
|
return null; |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private static final class T13CertificateConsumer implements SSLConsumer { |
|
|
|
private T13CertificateConsumer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public void consume(ConnectionContext context, |
|
ByteBuffer message) throws IOException { |
|
|
|
HandshakeContext hc = (HandshakeContext)context; |
|
|
|
|
|
hc.handshakeConsumers.remove(SSLHandshake.CERTIFICATE.id); |
|
T13CertificateMessage cm = new T13CertificateMessage(hc, message); |
|
if (hc.sslConfig.isClientMode) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Consuming server Certificate handshake message", cm); |
|
} |
|
onConsumeCertificate((ClientHandshakeContext)context, cm); |
|
} else { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Consuming client Certificate handshake message", cm); |
|
} |
|
onConsumeCertificate((ServerHandshakeContext)context, cm); |
|
} |
|
} |
|
|
|
private void onConsumeCertificate(ServerHandshakeContext shc, |
|
T13CertificateMessage certificateMessage )throws IOException { |
|
if (certificateMessage.certEntries == null || |
|
certificateMessage.certEntries.isEmpty()) { |
|
// For empty Certificate messages, we should not expect |
|
|
|
shc.handshakeConsumers.remove( |
|
SSLHandshake.CERTIFICATE_VERIFY.id); |
|
if (shc.sslConfig.clientAuthType == CLIENT_AUTH_REQUIRED) { |
|
throw shc.conContext.fatal(Alert.BAD_CERTIFICATE, |
|
"Empty client certificate chain"); |
|
} else { |
|
|
|
return; |
|
} |
|
} |
|
|
|
|
|
X509Certificate[] cliCerts = |
|
checkClientCerts(shc, certificateMessage.certEntries); |
|
|
|
// |
|
// update |
|
|
|
shc.handshakeCredentials.add( |
|
new X509Credentials(cliCerts[0].getPublicKey(), cliCerts)); |
|
shc.handshakeSession.setPeerCertificates(cliCerts); |
|
} |
|
|
|
private void onConsumeCertificate(ClientHandshakeContext chc, |
|
T13CertificateMessage certificateMessage )throws IOException { |
|
if (certificateMessage.certEntries == null || |
|
certificateMessage.certEntries.isEmpty()) { |
|
throw chc.conContext.fatal(Alert.BAD_CERTIFICATE, |
|
"Empty server certificate chain"); |
|
} |
|
|
|
// Each CertificateEntry will have its own set of extensions |
|
|
|
SSLExtension[] enabledExtensions = |
|
chc.sslConfig.getEnabledExtensions(SSLHandshake.CERTIFICATE); |
|
for (CertificateEntry certEnt : certificateMessage.certEntries) { |
|
certEnt.extensions.consumeOnLoad(chc, enabledExtensions); |
|
} |
|
|
|
|
|
X509Certificate[] srvCerts = |
|
checkServerCerts(chc, certificateMessage.certEntries); |
|
|
|
// |
|
// update |
|
|
|
chc.handshakeCredentials.add( |
|
new X509Credentials(srvCerts[0].getPublicKey(), srvCerts)); |
|
chc.handshakeSession.setPeerCertificates(srvCerts); |
|
} |
|
|
|
private static X509Certificate[] checkClientCerts( |
|
ServerHandshakeContext shc, |
|
List<CertificateEntry> certEntries) throws IOException { |
|
X509Certificate[] certs = |
|
new X509Certificate[certEntries.size()]; |
|
try { |
|
CertificateFactory cf = CertificateFactory.getInstance("X.509"); |
|
int i = 0; |
|
for (CertificateEntry entry : certEntries) { |
|
certs[i++] = (X509Certificate)cf.generateCertificate( |
|
new ByteArrayInputStream(entry.encoded)); |
|
} |
|
} catch (CertificateException ce) { |
|
throw shc.conContext.fatal(Alert.BAD_CERTIFICATE, |
|
"Failed to parse server certificates", ce); |
|
} |
|
|
|
|
|
String keyAlgorithm = certs[0].getPublicKey().getAlgorithm(); |
|
String authType; |
|
switch (keyAlgorithm) { |
|
case "RSA": |
|
case "DSA": |
|
case "EC": |
|
case "RSASSA-PSS": |
|
authType = keyAlgorithm; |
|
break; |
|
default: |
|
|
|
authType = "UNKNOWN"; |
|
} |
|
|
|
try { |
|
X509TrustManager tm = shc.sslContext.getX509TrustManager(); |
|
if (tm instanceof X509ExtendedTrustManager) { |
|
if (shc.conContext.transport instanceof SSLEngine) { |
|
SSLEngine engine = (SSLEngine)shc.conContext.transport; |
|
((X509ExtendedTrustManager)tm).checkClientTrusted( |
|
certs.clone(), |
|
authType, |
|
engine); |
|
} else { |
|
SSLSocket socket = (SSLSocket)shc.conContext.transport; |
|
((X509ExtendedTrustManager)tm).checkClientTrusted( |
|
certs.clone(), |
|
authType, |
|
socket); |
|
} |
|
} else { |
|
// Unlikely to happen, because we have wrapped the old |
|
|
|
throw new CertificateException( |
|
"Improper X509TrustManager implementation"); |
|
} |
|
|
|
// Once the client certificate chain has been validated, set |
|
|
|
shc.handshakeSession.setPeerCertificates(certs); |
|
} catch (CertificateException ce) { |
|
throw shc.conContext.fatal(Alert.CERTIFICATE_UNKNOWN, ce); |
|
} |
|
|
|
return certs; |
|
} |
|
|
|
private static X509Certificate[] checkServerCerts( |
|
ClientHandshakeContext chc, |
|
List<CertificateEntry> certEntries) throws IOException { |
|
X509Certificate[] certs = |
|
new X509Certificate[certEntries.size()]; |
|
try { |
|
CertificateFactory cf = CertificateFactory.getInstance("X.509"); |
|
int i = 0; |
|
for (CertificateEntry entry : certEntries) { |
|
certs[i++] = (X509Certificate)cf.generateCertificate( |
|
new ByteArrayInputStream(entry.encoded)); |
|
} |
|
} catch (CertificateException ce) { |
|
throw chc.conContext.fatal(Alert.BAD_CERTIFICATE, |
|
"Failed to parse server certificates", ce); |
|
} |
|
|
|
// find out the types of server authentication used |
|
// |
|
// Note that the "UNKNOWN" authentication type is sufficient to |
|
|
|
String authType = "UNKNOWN"; |
|
|
|
try { |
|
X509TrustManager tm = chc.sslContext.getX509TrustManager(); |
|
if (tm instanceof X509ExtendedTrustManager) { |
|
if (chc.conContext.transport instanceof SSLEngine) { |
|
SSLEngine engine = (SSLEngine)chc.conContext.transport; |
|
((X509ExtendedTrustManager)tm).checkServerTrusted( |
|
certs.clone(), |
|
authType, |
|
engine); |
|
} else { |
|
SSLSocket socket = (SSLSocket)chc.conContext.transport; |
|
((X509ExtendedTrustManager)tm).checkServerTrusted( |
|
certs.clone(), |
|
authType, |
|
socket); |
|
} |
|
} else { |
|
// Unlikely to happen, because we have wrapped the old |
|
|
|
throw new CertificateException( |
|
"Improper X509TrustManager implementation"); |
|
} |
|
|
|
// Once the server certificate chain has been validated, set |
|
|
|
chc.handshakeSession.setPeerCertificates(certs); |
|
} catch (CertificateException ce) { |
|
throw chc.conContext.fatal(getCertificateAlert(chc, ce), ce); |
|
} |
|
|
|
return certs; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static Alert getCertificateAlert( |
|
ClientHandshakeContext chc, CertificateException cexc) { |
|
// The specific reason for the failure will determine how to |
|
|
|
Alert alert = Alert.CERTIFICATE_UNKNOWN; |
|
|
|
Throwable baseCause = cexc.getCause(); |
|
if (baseCause instanceof CertPathValidatorException) { |
|
CertPathValidatorException cpve = |
|
(CertPathValidatorException)baseCause; |
|
Reason reason = cpve.getReason(); |
|
if (reason == BasicReason.REVOKED) { |
|
alert = chc.staplingActive ? |
|
Alert.BAD_CERT_STATUS_RESPONSE : |
|
Alert.CERTIFICATE_REVOKED; |
|
} else if ( |
|
reason == BasicReason.UNDETERMINED_REVOCATION_STATUS) { |
|
alert = chc.staplingActive ? |
|
Alert.BAD_CERT_STATUS_RESPONSE : |
|
Alert.CERTIFICATE_UNKNOWN; |
|
} |
|
} |
|
|
|
return alert; |
|
} |
|
} |
|
} |