475927 - SecureRequestCustomizer fails to match host.
Moved host and wildcard to new X509 class
This commit is contained in:
parent
c07f11a1fd
commit
8070ce61f3
|
@ -35,6 +35,7 @@ import org.eclipse.jetty.util.log.Log;
|
|||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.ssl.SniX509ExtendedKeyManager;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.util.ssl.X509;
|
||||
|
||||
/**
|
||||
* <p>Customizer that extracts the attribute from an {@link SSLContext}
|
||||
|
@ -105,23 +106,17 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
|||
{
|
||||
String name = request.getServerName();
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> hosts = (List<String>)sslSession.getValue(SniX509ExtendedKeyManager.SNI_HOSTS);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> wilds = (List<String>)sslSession.getValue(SniX509ExtendedKeyManager.SNI_WILDS);
|
||||
|
||||
if (hosts != null && !hosts.stream().anyMatch(host -> host.equalsIgnoreCase(name)))
|
||||
X509 x509 = (X509)sslSession.getValue(SniX509ExtendedKeyManager.SNI_X509);
|
||||
|
||||
if (x509!=null && !x509.matches(name))
|
||||
{
|
||||
// Browsers may reuse the same connection if they can prove that two domains
|
||||
// have the same certificate and IP, for example domain.com and www.domain.com.
|
||||
if (wilds == null || !wilds.stream().anyMatch(wild -> isDomainOrSubDomain(name, wild)))
|
||||
{
|
||||
LOG.warn("Host {} does not match SNI hosts: {}/{}",name,hosts,wilds);
|
||||
throw new BadMessageException(400,"Host does not match SNI");
|
||||
}
|
||||
LOG.warn("Host {} does not match SNI {}",name,x509);
|
||||
throw new BadMessageException(400,"Host does not match SNI");
|
||||
}
|
||||
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Host {} matched SNI hosts: {}/{}",name,hosts,wilds);
|
||||
LOG.debug("Host {} matched SNI {}",name,x509);
|
||||
}
|
||||
|
||||
try
|
||||
|
@ -161,15 +156,6 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean isDomainOrSubDomain(String name, String domain)
|
||||
{
|
||||
if (!name.endsWith(domain))
|
||||
return false;
|
||||
if (name.length() == domain.length())
|
||||
return true;
|
||||
return name.charAt(name.length() - domain.length() - 1) == '.';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.server;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class SecureRequestCustomizerTest
|
||||
{
|
||||
@Test
|
||||
public void testDomain()
|
||||
{
|
||||
assertTrue(SecureRequestCustomizer.isDomainOrSubDomain("foo.com", "foo.com"));
|
||||
assertTrue(SecureRequestCustomizer.isDomainOrSubDomain("www.foo.com", "foo.com"));
|
||||
assertFalse(SecureRequestCustomizer.isDomainOrSubDomain("bad_foo.com", "foo.com"));
|
||||
assertFalse(SecureRequestCustomizer.isDomainOrSubDomain("foo.com", "bar.com"));
|
||||
assertFalse(SecureRequestCustomizer.isDomainOrSubDomain("www.foo.com","bar.com"));
|
||||
}
|
||||
}
|
|
@ -24,7 +24,6 @@ import java.security.PrivateKey;
|
|||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.net.ssl.SNIMatcher;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
@ -42,8 +41,7 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
*/
|
||||
public class SniX509ExtendedKeyManager extends X509ExtendedKeyManager
|
||||
{
|
||||
public static final String SNI_HOSTS = "org.eclipse.jetty.util.ssl.snihosts";
|
||||
public static final String SNI_WILDS = "org.eclipse.jetty.util.ssl.sniwilds";
|
||||
public static final String SNI_X509 = "org.eclipse.jetty.util.ssl.snix509";
|
||||
private static final String NO_MATCHERS = "no_matchers";
|
||||
private static final Logger LOG = Log.getLogger(SniX509ExtendedKeyManager.class);
|
||||
|
||||
|
@ -75,9 +73,7 @@ public class SniX509ExtendedKeyManager extends X509ExtendedKeyManager
|
|||
|
||||
// Look for the SNI information.
|
||||
String host=null;
|
||||
String alias=null;
|
||||
List<String> hosts=null;
|
||||
List<String> wilds=null;
|
||||
X509 x509=null;
|
||||
if (matchers!=null)
|
||||
{
|
||||
for (SNIMatcher m : matchers)
|
||||
|
@ -86,27 +82,24 @@ public class SniX509ExtendedKeyManager extends X509ExtendedKeyManager
|
|||
{
|
||||
SslContextFactory.AliasSNIMatcher matcher = (SslContextFactory.AliasSNIMatcher)m;
|
||||
host=matcher.getHost();
|
||||
alias=matcher.getAlias();
|
||||
hosts=matcher.getHosts();
|
||||
wilds=matcher.getWilds();
|
||||
x509=matcher.getX509();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Matched {} with alias {}/{}/{} from {}",host,alias,hosts,wilds,Arrays.asList(aliases));
|
||||
LOG.debug("Matched {} with {} from {}",host,x509,Arrays.asList(aliases));
|
||||
|
||||
// Check if the SNI selected alias is allowable
|
||||
if (alias!=null)
|
||||
if (x509!=null)
|
||||
{
|
||||
for (String a:aliases)
|
||||
{
|
||||
if (a.equals(alias))
|
||||
if (a.equals(x509.getAlias()))
|
||||
{
|
||||
session.putValue(SNI_HOSTS,hosts);
|
||||
session.putValue(SNI_WILDS,wilds);
|
||||
return alias;
|
||||
session.putValue(SNI_X509,x509);
|
||||
return a;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -38,7 +38,6 @@ import java.util.Arrays;
|
|||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
|
@ -47,10 +46,7 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.naming.ldap.LdapName;
|
||||
import javax.naming.ldap.Rdn;
|
||||
import javax.net.ssl.CertPathTrustManagerParameters;
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
|
@ -71,8 +67,8 @@ import javax.net.ssl.TrustManager;
|
|||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
@ -111,18 +107,6 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
|
||||
static final Logger LOG = Log.getLogger(SslContextFactory.class);
|
||||
|
||||
/*
|
||||
* @see {@link X509Certificate#getKeyUsage()}
|
||||
*/
|
||||
private static final int KEY_USAGE__KEY_CERT_SIGN=5;
|
||||
|
||||
/*
|
||||
*
|
||||
* @see {@link X509Certificate#getSubjectAlternativeNames()}
|
||||
*/
|
||||
private static final int SUBJECT_ALTERNATIVE_NAMES__DNS_NAME=2;
|
||||
|
||||
|
||||
public static final String DEFAULT_KEYMANAGERFACTORY_ALGORITHM =
|
||||
(Security.getProperty("ssl.KeyManagerFactory.algorithm") == null ?
|
||||
KeyManagerFactory.getDefaultAlgorithm() : Security.getProperty("ssl.KeyManagerFactory.algorithm"));
|
||||
|
@ -168,8 +152,9 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
|
||||
/** SSL certificate alias */
|
||||
private String _certAlias;
|
||||
private final Map<String,String> _certHosts = new HashMap<>();
|
||||
private final Map<String,String> _certWilds = new HashMap<>();
|
||||
private final Map<String,X509> _aliasX509 = new HashMap<>();
|
||||
private final Map<String,X509> _certHosts = new HashMap<>();
|
||||
private final Map<String,X509> _certWilds = new HashMap<>();
|
||||
|
||||
/** Truststore path */
|
||||
private Resource _trustStoreResource;
|
||||
|
@ -305,6 +290,16 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
_cipherComparator = cipherComparator;
|
||||
}
|
||||
|
||||
public Set<String> getAliases()
|
||||
{
|
||||
return Collections.unmodifiableSet(_aliasX509.keySet());
|
||||
}
|
||||
|
||||
public X509 getX509(String alias)
|
||||
{
|
||||
return _aliasX509.get(alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the SSLContext object and start the lifecycle
|
||||
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
|
||||
|
@ -344,33 +339,6 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
|
||||
Collection<? extends CRL> crls = loadCRL(_crlPath);
|
||||
|
||||
if (_validateCerts && keyStore != null)
|
||||
{
|
||||
if (_certAlias==null)
|
||||
{
|
||||
for (Enumeration<String> e=keyStore.aliases(); _certAlias==null && e.hasMoreElements(); )
|
||||
{
|
||||
String alias=e.nextElement();
|
||||
Certificate c =keyStore.getCertificate(alias);
|
||||
if (c!=null && "X.509".equals(c.getType()))
|
||||
_certAlias=alias;
|
||||
}
|
||||
}
|
||||
|
||||
Certificate cert = _certAlias == null?null:keyStore.getCertificate(_certAlias);
|
||||
if (cert==null || !"X.509".equals(cert.getType()))
|
||||
{
|
||||
throw new Exception("No X.509 certificate in the keystore" + (_certAlias==null ? "":" for alias " + _certAlias));
|
||||
}
|
||||
|
||||
CertificateValidator validator = new CertificateValidator(trustStore, crls);
|
||||
validator.setMaxCertPathLength(_maxCertPathLength);
|
||||
validator.setEnableCRLDP(_enableCRLDP);
|
||||
validator.setEnableOCSP(_enableOCSP);
|
||||
validator.setOcspResponderURL(_ocspResponderURL);
|
||||
validator.validate(keyStore, cert);
|
||||
}
|
||||
|
||||
// Look for X.509 certificates to create alias map
|
||||
_certHosts.clear();
|
||||
if (keyStore!=null)
|
||||
|
@ -380,72 +348,38 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
Certificate certificate = keyStore.getCertificate(alias);
|
||||
if (certificate!=null && "X.509".equals(certificate.getType()))
|
||||
{
|
||||
X509Certificate x509 = (X509Certificate)certificate;
|
||||
X509Certificate x509C = (X509Certificate)certificate;
|
||||
|
||||
// Exclude certificates with special uses
|
||||
if (x509.getKeyUsage()!=null)
|
||||
if (X509.isCertSign(x509C))
|
||||
{
|
||||
boolean[] b=x509.getKeyUsage();
|
||||
if (b[KEY_USAGE__KEY_CERT_SIGN])
|
||||
continue;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Skipping "+x509C);
|
||||
continue;
|
||||
}
|
||||
X509 x509 = new X509(alias,x509C);
|
||||
_aliasX509.put(alias,x509);
|
||||
|
||||
if (_validateCerts)
|
||||
{
|
||||
CertificateValidator validator = new CertificateValidator(trustStore, crls);
|
||||
validator.setMaxCertPathLength(_maxCertPathLength);
|
||||
validator.setEnableCRLDP(_enableCRLDP);
|
||||
validator.setEnableOCSP(_enableOCSP);
|
||||
validator.setOcspResponderURL(_ocspResponderURL);
|
||||
validator.validate(keyStore, x509C); // TODO what about truststore?
|
||||
}
|
||||
|
||||
// Look for alternative name extensions
|
||||
boolean named=false;
|
||||
Collection<List<?>> altNames = x509.getSubjectAlternativeNames();
|
||||
if (altNames!=null)
|
||||
{
|
||||
for (List<?> list : altNames)
|
||||
{
|
||||
if (((Number)list.get(0)).intValue() == SUBJECT_ALTERNATIVE_NAMES__DNS_NAME)
|
||||
{
|
||||
String cn = list.get(1).toString();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Certificate SAN alias={} cn={} in {}",alias,cn,this);
|
||||
if (cn!=null)
|
||||
{
|
||||
named=true;
|
||||
_certHosts.put(cn,alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no names found, look up the cn from the subject
|
||||
if (!named)
|
||||
{
|
||||
LdapName name=new LdapName(x509.getSubjectX500Principal().getName(X500Principal.RFC2253));
|
||||
for (Rdn rdn : name.getRdns())
|
||||
{
|
||||
if (rdn.getType().equalsIgnoreCase("cn"))
|
||||
{
|
||||
String cn = rdn.getValue().toString();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Certificate cn alias={} cn={} in {}",alias,cn,this);
|
||||
if (cn!=null && cn.contains(".") && !cn.contains(" "))
|
||||
_certHosts.put(cn,alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG.info("x509={} for {}",x509,this);
|
||||
|
||||
for (String h:x509.getHosts())
|
||||
_certHosts.put(h,x509);
|
||||
for (String w:x509.getWilds())
|
||||
_certWilds.put(w,x509);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find wild aliases
|
||||
_certWilds.clear();
|
||||
for (Iterator<Map.Entry<String, String>> iterator = _certHosts.entrySet().iterator(); iterator.hasNext();)
|
||||
{
|
||||
Map.Entry<String, String> entry = iterator.next();
|
||||
String host = entry.getKey();
|
||||
if (host.startsWith("*."))
|
||||
{
|
||||
_certWilds.put(host.substring(2), entry.getValue());
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
LOG.info("x509={} wild={} alias={} for {}",_certHosts,_certWilds,_certAlias,this);
|
||||
|
||||
// Instantiate key and trust managers
|
||||
KeyManager[] keyManagers = getKeyManagers(keyStore);
|
||||
TrustManager[] trustManagers = getTrustManagers(trustStore,crls);
|
||||
|
@ -470,9 +404,8 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
LOG.debug("Selected Protocols {} of {}",Arrays.asList(_selectedProtocols),Arrays.asList(sslEngine.getSupportedProtocols()));
|
||||
LOG.debug("Selected Ciphers {} of {}",Arrays.asList(_selectedCipherSuites),Arrays.asList(sslEngine.getSupportedCipherSuites()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doStop() throws Exception
|
||||
{
|
||||
|
@ -480,6 +413,7 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
super.doStop();
|
||||
_certHosts.clear();
|
||||
_certWilds.clear();
|
||||
_aliasX509.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1735,8 +1669,6 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
_trustStoreResource);
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected class Factory
|
||||
{
|
||||
final KeyStore _keyStore;
|
||||
|
@ -1761,15 +1693,11 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
class AliasSNIMatcher extends SNIMatcher
|
||||
{
|
||||
private String _host;
|
||||
private String _alias;
|
||||
private List<String> _hosts;
|
||||
private List<String> _wilds;
|
||||
private X509 _x509;
|
||||
|
||||
AliasSNIMatcher()
|
||||
{
|
||||
super(StandardConstants.SNI_HOST_NAME);
|
||||
_hosts = Collections.emptyList();
|
||||
_wilds = Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1781,54 +1709,38 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
if (serverName instanceof SNIHostName)
|
||||
{
|
||||
String host = _host = ((SNIHostName)serverName).getAsciiName();
|
||||
|
||||
// If we don't have a SNI host, or didn't see any certificate aliases,
|
||||
// just say true as it will either somehow work or fail elsewhere.
|
||||
if (_certHosts.isEmpty())
|
||||
return true;
|
||||
|
||||
host=StringUtil.asciiToLowerCase(host);
|
||||
|
||||
// Try an exact match
|
||||
_alias = _certHosts.get(host);
|
||||
if (_alias!=null)
|
||||
_x509 = _certHosts.get(host);
|
||||
|
||||
// Else try an exact wild match
|
||||
if (_x509==null)
|
||||
{
|
||||
_hosts = _certHosts.entrySet().stream()
|
||||
.filter(entry -> _alias.equals(entry.getValue()))
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("SNI matched {}->{}->{}",host,_alias,_hosts);
|
||||
}
|
||||
|
||||
// Try wildcard matches.
|
||||
if (_alias==null)
|
||||
{
|
||||
_alias = _certWilds.get(host);
|
||||
if (_alias==null)
|
||||
_x509 = _certWilds.get(host);
|
||||
|
||||
// Else try an 1 deep wild match
|
||||
if (_x509==null)
|
||||
{
|
||||
int dot=host.indexOf('.');
|
||||
if (dot>=0)
|
||||
{
|
||||
host=host.substring(dot+1);
|
||||
_alias = _certWilds.get(host);
|
||||
String domain=host.substring(dot+1);
|
||||
_x509 = _certWilds.get(domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_alias!=null)
|
||||
{
|
||||
_wilds = _certWilds.entrySet().stream()
|
||||
.filter(entry -> _alias.equals(entry.getValue()))
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("SNI wild match {}->{}->{}",host,_alias,_wilds);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("SNI matched {}->{}",host,_x509);
|
||||
|
||||
}
|
||||
else if (LOG.isDebugEnabled())
|
||||
LOG.debug("SNI no match for {}", serverName);
|
||||
|
||||
// Return true and allow the KeyManager to accept or reject when choosing a certificate.
|
||||
// If we don't have a SNI host, or didn't see any certificate aliases,
|
||||
// just say true as it will either somehow work or fail elsewhere.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1837,19 +1749,9 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
return _host;
|
||||
}
|
||||
|
||||
public String getAlias()
|
||||
public X509 getX509()
|
||||
{
|
||||
return _alias;
|
||||
}
|
||||
|
||||
public List<String> getHosts()
|
||||
{
|
||||
return _hosts;
|
||||
}
|
||||
|
||||
public List<String> getWilds()
|
||||
{
|
||||
return _wilds;
|
||||
return _x509;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.util.ssl;
|
||||
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.naming.InvalidNameException;
|
||||
import javax.naming.ldap.LdapName;
|
||||
import javax.naming.ldap.Rdn;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class X509
|
||||
{
|
||||
static final Logger LOG = Log.getLogger(X509.class);
|
||||
|
||||
/*
|
||||
* @see {@link X509Certificate#getKeyUsage()}
|
||||
*/
|
||||
private static final int KEY_USAGE__KEY_CERT_SIGN=5;
|
||||
|
||||
/*
|
||||
*
|
||||
* @see {@link X509Certificate#getSubjectAlternativeNames()}
|
||||
*/
|
||||
private static final int SUBJECT_ALTERNATIVE_NAMES__DNS_NAME=2;
|
||||
|
||||
|
||||
public static boolean isCertSign(X509Certificate x509)
|
||||
{
|
||||
boolean[] key_usage=x509.getKeyUsage();
|
||||
return key_usage!=null && key_usage[KEY_USAGE__KEY_CERT_SIGN];
|
||||
}
|
||||
|
||||
|
||||
private final X509Certificate _x509;
|
||||
private final String _alias;
|
||||
private final List<String> _hosts=new ArrayList<>();
|
||||
private final List<String> _wilds=new ArrayList<>();
|
||||
|
||||
|
||||
public X509(String alias,X509Certificate x509) throws CertificateParsingException, InvalidNameException
|
||||
{
|
||||
_alias=alias;
|
||||
_x509 = x509;
|
||||
|
||||
|
||||
// Look for alternative name extensions
|
||||
boolean named=false;
|
||||
Collection<List<?>> altNames = x509.getSubjectAlternativeNames();
|
||||
if (altNames!=null)
|
||||
{
|
||||
for (List<?> list : altNames)
|
||||
{
|
||||
if (((Number)list.get(0)).intValue() == SUBJECT_ALTERNATIVE_NAMES__DNS_NAME)
|
||||
{
|
||||
String cn = list.get(1).toString();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Certificate SAN alias={} cn={} in {}",alias,cn,this);
|
||||
if (cn!=null)
|
||||
{
|
||||
named=true;
|
||||
addName(cn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no names found, look up the cn from the subject
|
||||
if (!named)
|
||||
{
|
||||
LdapName name=new LdapName(x509.getSubjectX500Principal().getName(X500Principal.RFC2253));
|
||||
for (Rdn rdn : name.getRdns())
|
||||
{
|
||||
if (rdn.getType().equalsIgnoreCase("cn"))
|
||||
{
|
||||
String cn = rdn.getValue().toString();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Certificate cn alias={} cn={} in {}",alias,cn,this);
|
||||
if (cn!=null && cn.contains(".") && !cn.contains(" "))
|
||||
addName(cn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void addName(String cn)
|
||||
{
|
||||
cn=StringUtil.asciiToLowerCase(cn);
|
||||
cn.toLowerCase();
|
||||
if (cn.startsWith("*."))
|
||||
_wilds.add(cn.substring(2));
|
||||
else
|
||||
_hosts.add(cn);
|
||||
}
|
||||
|
||||
public String getAlias()
|
||||
{
|
||||
return _alias;
|
||||
}
|
||||
|
||||
public X509Certificate getCertificate()
|
||||
{
|
||||
return _x509;
|
||||
}
|
||||
|
||||
public Set<String> getHosts()
|
||||
{
|
||||
return new HashSet<>(_hosts);
|
||||
}
|
||||
|
||||
public Set<String> getWilds()
|
||||
{
|
||||
return new HashSet<>(_wilds);
|
||||
}
|
||||
|
||||
public boolean matches(String host)
|
||||
{
|
||||
host=StringUtil.asciiToLowerCase(host);
|
||||
if (_hosts.contains(host) || _wilds.contains(host))
|
||||
return true;
|
||||
|
||||
int dot = host.indexOf('.');
|
||||
if (dot>=0)
|
||||
{
|
||||
String domain=host.substring(dot+1);
|
||||
if (_wilds.contains(domain))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x(%s,h=%s,w=%s)",
|
||||
getClass().getSimpleName(),
|
||||
hashCode(),
|
||||
_alias,
|
||||
_hosts,
|
||||
_wilds);
|
||||
}
|
||||
}
|
|
@ -19,7 +19,9 @@
|
|||
package org.eclipse.jetty.util.ssl;
|
||||
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
@ -221,4 +223,45 @@ public class SslContextFactoryTest
|
|||
assertNotNull(cf.getExcludeCipherSuites());
|
||||
assertNotNull(cf.getIncludeCipherSuites());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSNICertificates() throws Exception
|
||||
{
|
||||
Resource keystoreResource = Resource.newSystemResource("snikeystore");
|
||||
|
||||
cf.setKeyStoreResource(keystoreResource);
|
||||
cf.setKeyStorePassword("storepwd");
|
||||
cf.setKeyManagerPassword("keypwd");
|
||||
|
||||
cf.start();
|
||||
|
||||
assertThat(cf.getAliases(),containsInAnyOrder("jetty","other","san","wild"));
|
||||
|
||||
assertThat(cf.getX509("jetty").getHosts(),containsInAnyOrder("jetty.eclipse.org"));
|
||||
assertTrue(cf.getX509("jetty").getWilds().isEmpty());
|
||||
assertTrue(cf.getX509("jetty").matches("JETTY.Eclipse.Org"));
|
||||
assertFalse(cf.getX509("jetty").matches("m.jetty.eclipse.org"));
|
||||
assertFalse(cf.getX509("jetty").matches("eclipse.org"));
|
||||
|
||||
assertThat(cf.getX509("other").getHosts(),containsInAnyOrder("www.example.com"));
|
||||
assertTrue(cf.getX509("other").getWilds().isEmpty());
|
||||
assertTrue(cf.getX509("other").matches("www.example.com"));
|
||||
assertFalse(cf.getX509("other").matches("eclipse.org"));
|
||||
|
||||
assertThat(cf.getX509("san").getHosts(),containsInAnyOrder("www.san.com","m.san.com"));
|
||||
assertTrue(cf.getX509("san").getWilds().isEmpty());
|
||||
assertTrue(cf.getX509("san").matches("www.san.com"));
|
||||
assertTrue(cf.getX509("san").matches("m.san.com"));
|
||||
assertFalse(cf.getX509("san").matches("other.san.com"));
|
||||
assertFalse(cf.getX509("san").matches("san.com"));
|
||||
assertFalse(cf.getX509("san").matches("eclipse.org"));
|
||||
|
||||
assertTrue(cf.getX509("wild").getHosts().isEmpty());
|
||||
assertThat(cf.getX509("wild").getWilds(),containsInAnyOrder("domain.com"));
|
||||
assertTrue(cf.getX509("wild").matches("domain.com"));
|
||||
assertTrue(cf.getX509("wild").matches("www.domain.com"));
|
||||
assertTrue(cf.getX509("wild").matches("other.domain.com"));
|
||||
assertFalse(cf.getX509("wild").matches("foo.bar.domain.com"));
|
||||
assertFalse(cf.getX509("wild").matches("other.com"));
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue