339150 Validate client certificate when it is used for authentication

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@2882 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
Michael Gorovoy 2011-03-12 02:01:01 +00:00
parent 31d8e8b3dc
commit 626ef8271b
7 changed files with 818 additions and 174 deletions

View File

@ -1,8 +1,10 @@
jetty-7.3.2-SNAPSHOT
+ 338819 Externally control Deployment Manager application lifecycle
+ Ensure generated fragment names are unique
+ 339187 In the OSGi manifest of the jetty-all-server aggregate, mark javax.annotation as optional
+ 337685 Update websocket API in preparation for draft -07
+ 338819 Externally control Deployment Manager application lifecycle
+ 339150 Validate client certificate when it is used for authentication
+ 339187 In the OSGi manifest of the jetty-all-server aggregate, mark javax.annotation as optional
+ 339543 Add configuration options for Certificate Revocation checking
+ Ensure generated fragment names are unique
jetty-7.3.1.v20110307 7 March 2011
+ 316382 Support a more strict SSL option with certificates

View File

@ -0,0 +1,171 @@
// ========================================================================
// Copyright (c) 2009-2009 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.client;
import java.security.Principal;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.security.auth.Subject;
import org.eclipse.jetty.http.security.Constraint;
import org.eclipse.jetty.http.security.Credential;
import org.eclipse.jetty.http.ssl.SslContextFactory;
import org.eclipse.jetty.security.Authenticator;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.IdentityService;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.MappedLoginService.KnownUser;
import org.eclipse.jetty.security.authentication.ClientCertAuthenticator;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.log.Log;
public class SslCertSecuredExchangeTest extends ContentExchangeTest
{
// certificate is valid until Jan 1, 2050
private String _keypath = MavenTestingUtils.getTargetFile("test-policy/validation/jetty-valid.keystore").getAbsolutePath();
private String _trustpath = MavenTestingUtils.getTargetFile("test-policy/validation/jetty-trust.keystore").getAbsolutePath();
private String _clientpath = MavenTestingUtils.getTargetFile("test-policy/validation/jetty-client.keystore").getAbsolutePath();
private String _crlpath = MavenTestingUtils.getTargetFile("test-policy/validation/crlfile.pem").getAbsolutePath();
private String _password = "OBF:1wnl1sw01ta01z0f1tae1svy1wml";
protected void configureServer(Server server)
throws Exception
{
setProtocol("https");
SslSelectChannelConnector connector = new SslSelectChannelConnector();
SslContextFactory cf = connector.getSslContextFactory();
cf.setValidateCerts(true);
cf.setCrlPath(_crlpath);
cf.setNeedClientAuth(true);
cf.setKeyStore(_keypath);
cf.setKeyStorePassword(_password);
cf.setKeyManagerPassword(_password);
cf.setTrustStore(_trustpath);
cf.setTrustStorePassword(_password);
server.addConnector(connector);
LoginService loginService = new LoginService() {
public String getName()
{
return "MyLoginService";
}
public UserIdentity login(String username, Object credentials)
{
return new UserIdentity() {
public Subject getSubject()
{
Subject subject = new Subject();
subject.getPrincipals().add(getUserPrincipal());
subject.setReadOnly();
return subject;
}
public Principal getUserPrincipal()
{
return new KnownUser("client", new Credential() {
@Override
public boolean check(Object credentials)
{
return true;
}
});
}
public boolean isUserInRole(String role, Scope scope) { return true; }
};
}
public boolean validate(UserIdentity user) { return true; }
public IdentityService getIdentityService() { return null; }
public void setIdentityService(IdentityService service) {}
public void logout(UserIdentity user) {}
};
server.addBean(loginService);
ConstraintSecurityHandler security = new ConstraintSecurityHandler();
server.setHandler(security);
Constraint constraint = new Constraint();
constraint.setName("auth");
constraint.setAuthenticate( true );
constraint.setRoles(new String[]{"user", "admin"});
ConstraintMapping mapping = new ConstraintMapping();
mapping.setPathSpec( "/*" );
mapping.setConstraint( constraint );
Set<String> knownRoles = new HashSet<String>();
knownRoles.add("user");
knownRoles.add("admin");
security.setConstraintMappings(Collections.singletonList(mapping), knownRoles);
security.setLoginService(loginService);
ClientCertAuthenticator auth = new ClientCertAuthenticator();
auth.setValidateCerts(true);
auth.setCrlPath(_crlpath);
auth.setTrustStore(_trustpath);
auth.setTrustStorePassword(_password);
security.setAuthenticator(auth);
security.setAuthMethod(auth.getAuthMethod());
security.setRealmName("MyRealm");
security.setStrict(true);
ServletContextHandler root = new ServletContextHandler();
root.setContextPath("/");
root.setResourceBase(getBasePath());
ServletHolder servletHolder = new ServletHolder( new DefaultServlet() );
servletHolder.setInitParameter( "gzip", "true" );
root.addServlet( servletHolder, "/*" );
Handler handler = new TestHandler(getBasePath());
HandlerCollection handlers = new HandlerCollection();
handlers.setHandlers(new Handler[]{handler, root});
security.setHandler(handlers);
}
@Override
protected void configureClient(HttpClient client) throws Exception
{
SslContextFactory cf = client.getSslContextFactory();
cf.setValidateCerts(true);
cf.setCrlPath(_crlpath);
cf.setCertAlias("client");
cf.setKeyStore(_clientpath);
cf.setKeyStorePassword(_password);
cf.setKeyManagerPassword(_password);
cf.setTrustStore(_trustpath);
cf.setTrustStorePassword(_password);
}
}

View File

@ -12,7 +12,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
public abstract class SslValidationTestBase extends SslContentExchangeTest
public abstract class SslValidationTestBase extends ContentExchangeTest
{
protected static Class<? extends SslConnector> __klass;
protected static int __konnector;
@ -20,6 +20,7 @@ public abstract class SslValidationTestBase extends SslContentExchangeTest
// certificate is valid until Jan 1, 2050
private String _keypath = MavenTestingUtils.getTargetFile("test-policy/validation/jetty-valid.keystore").getAbsolutePath();
private String _trustpath = MavenTestingUtils.getTargetFile("test-policy/validation/jetty-trust.keystore").getAbsolutePath();
private String _clientpath = MavenTestingUtils.getTargetFile("test-policy/validation/jetty-client.keystore").getAbsolutePath();
private String _crlpath = MavenTestingUtils.getTargetFile("test-policy/validation/crlfile.pem").getAbsolutePath();
private String _password = "OBF:1wnl1sw01ta01z0f1tae1svy1wml";
@ -31,12 +32,15 @@ public abstract class SslValidationTestBase extends SslContentExchangeTest
SslContextFactory srvFactory = new SslContextFactory();
srvFactory.setValidateCerts(true);
srvFactory.setCrlPath(_crlpath);
srvFactory.setNeedClientAuth(true);
srvFactory.setKeyStore(_keypath);
srvFactory.setKeyStorePassword(_password);
srvFactory.setKeyManagerPassword(_password);
srvFactory.setTrustStore(_trustpath);
srvFactory.setTrustStorePassword(_password);
srvFactory.setCrlPath(_crlpath);
Constructor<? extends SslConnector> constructor = __klass.getConstructor(SslContextFactory.class);
SslConnector connector = constructor.newInstance(srvFactory);
@ -64,6 +68,13 @@ public abstract class SslValidationTestBase extends SslContentExchangeTest
client.setConnectorType(__konnector);
SslContextFactory cf = client.getSslContextFactory();
cf.setValidateCerts(true);
cf.setCrlPath(_crlpath);
cf.setKeyStore(_clientpath);
cf.setKeyStorePassword(_password);
cf.setKeyManagerPassword(_password);
cf.setTrustStore(_trustpath);
cf.setTrustStorePassword(_password);
}

View File

@ -26,7 +26,6 @@ import java.security.Security;
import java.security.cert.CRL;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.X509CertSelector;
@ -50,7 +49,7 @@ import javax.net.ssl.X509TrustManager;
import org.eclipse.jetty.http.security.Password;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.security.CertificateUtils;
import org.eclipse.jetty.util.security.CertificateValidator;
@ -110,8 +109,6 @@ public class SslContextFactory extends AbstractLifeCycle
private boolean _needClientAuth = false;
/** Set to true if client certificate authentication is desired */
private boolean _wantClientAuth = false;
/** Set to true if SSL certificate validation is required */
private boolean _validateCerts;
/** Set to true if renegotiation is allowed */
private boolean _allowRenegotiate = false;
@ -134,11 +131,19 @@ public class SslContextFactory extends AbstractLifeCycle
/** TrustManager factory algorithm */
private String _trustManagerFactoryAlgorithm = DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM;
/** Path to file that contains Certificate Revocation List */
private String _crlPath;
/** Set to true if SSL certificate validation is required */
private boolean _validateCerts;
/** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
private int _maxCertPathLength = -1;
/** Path to file that contains Certificate Revocation List */
private String _crlPath;
/** Set to true to enable CRL Distribution Points (CRLDP) support */
private boolean _enableCRLDP = false;
/** Set to true to enable On-Line Certificate Status Protocol (OCSP) support */
private boolean _enableOCSP = false;
/** Location of OCSP Responder */
private String _ocspResponderURL;
/** SSL context */
private SSLContext _context;
@ -154,11 +159,11 @@ public class SslContextFactory extends AbstractLifeCycle
/* ------------------------------------------------------------ */
/**
* Construct an instance of SslContextFactory
* @param keystorePath default keystore location
* @param keyStorePath default keystore location
*/
public SslContextFactory(String keystorePath)
public SslContextFactory(String keyStorePath)
{
_keyStorePath = keystorePath;
_keyStorePath = keyStorePath;
}
/* ------------------------------------------------------------ */
@ -258,14 +263,14 @@ public class SslContextFactory extends AbstractLifeCycle
/* ------------------------------------------------------------ */
/**
* @param keystore
* @param keyStorePath
* The file or URL of the SSL Key store.
*/
public void setKeyStore(String keystore)
public void setKeyStore(String keyStorePath)
{
checkStarted();
_keyStorePath = keystore;
_keyStorePath = keyStorePath;
}
/* ------------------------------------------------------------ */
@ -279,14 +284,14 @@ public class SslContextFactory extends AbstractLifeCycle
/* ------------------------------------------------------------ */
/**
* @param keystoreProvider
* @param keyStoreProvider
* The provider of the key store
*/
public void setKeyStoreProvider(String keystoreProvider)
public void setKeyStoreProvider(String keyStoreProvider)
{
checkStarted();
_keyStoreProvider = keystoreProvider;
_keyStoreProvider = keyStoreProvider;
}
/* ------------------------------------------------------------ */
@ -300,14 +305,14 @@ public class SslContextFactory extends AbstractLifeCycle
/* ------------------------------------------------------------ */
/**
* @param keystoreType
* @param keyStoreType
* The type of the key store (default "JKS")
*/
public void setKeyStoreType(String keystoreType)
public void setKeyStoreType(String keyStoreType)
{
checkStarted();
_keyStoreType = keystoreType;
_keyStoreType = keyStoreType;
}
/* ------------------------------------------------------------ */
@ -323,13 +328,13 @@ public class SslContextFactory extends AbstractLifeCycle
/* ------------------------------------------------------------ */
/** Set the keyStoreInputStream.
* @param keystoreInputStream the InputStream to the KeyStore
* @param keyStoreInputStream the InputStream to the KeyStore
*/
public void setKeyStoreInputStream(InputStream keystoreInputStream)
public void setKeyStoreInputStream(InputStream keyStoreInputStream)
{
checkStarted();
_keyStoreInputStream = keystoreInputStream;
_keyStoreInputStream = keyStoreInputStream;
}
/* ------------------------------------------------------------ */
@ -364,14 +369,14 @@ public class SslContextFactory extends AbstractLifeCycle
/* ------------------------------------------------------------ */
/**
* @param truststore
* @param trustStorePath
* The file name or URL of the trust store location
*/
public void setTrustStore(String truststore)
public void setTrustStore(String trustStorePath)
{
checkStarted();
_trustStorePath = truststore;
_trustStorePath = trustStorePath;
}
/* ------------------------------------------------------------ */
@ -385,14 +390,14 @@ public class SslContextFactory extends AbstractLifeCycle
/* ------------------------------------------------------------ */
/**
* @param truststoreProvider
* @param trustStoreProvider
* The provider of the trust store
*/
public void setTrustStoreProvider(String truststoreProvider)
public void setTrustStoreProvider(String trustStoreProvider)
{
checkStarted();
_trustStoreProvider = truststoreProvider;
_trustStoreProvider = trustStoreProvider;
}
/* ------------------------------------------------------------ */
@ -406,14 +411,14 @@ public class SslContextFactory extends AbstractLifeCycle
/* ------------------------------------------------------------ */
/**
* @param truststoreType
* @param trustStoreType
* The type of the trust store (default "JKS")
*/
public void setTrustStoreType(String truststoreType)
public void setTrustStoreType(String trustStoreType)
{
checkStarted();
_trustStoreType = truststoreType;
_trustStoreType = trustStoreType;
}
/* ------------------------------------------------------------ */
@ -429,13 +434,13 @@ public class SslContextFactory extends AbstractLifeCycle
/* ------------------------------------------------------------ */
/** Set the _trustStoreInputStream.
* @param truststoreInputStream the InputStream to the TrustStore
* @param trustStoreInputStream the InputStream to the TrustStore
*/
public void setTrustStoreInputStream(InputStream truststoreInputStream)
public void setTrustStoreInputStream(InputStream trustStoreInputStream)
{
checkStarted();
_trustStoreInputStream = truststoreInputStream;
_trustStoreInputStream = trustStoreInputStream;
}
/* ------------------------------------------------------------ */
@ -487,12 +492,23 @@ public class SslContextFactory extends AbstractLifeCycle
/* ------------------------------------------------------------ */
/**
* @return true if SSL certificate has to be validated
* @deprecated
*/
@Deprecated
public boolean getValidateCerts()
{
return _validateCerts;
}
/* ------------------------------------------------------------ */
/**
* @return true if SSL certificate has to be validated
*/
public boolean isValidateCerts()
{
return _validateCerts;
}
/* ------------------------------------------------------------ */
/**
* @param validateCerts
@ -754,11 +770,11 @@ public class SslContextFactory extends AbstractLifeCycle
// parameters are set up correctly
checkConfig();
KeyStore keyStore = getKeyStore(_keyStoreInputStream, _keyStorePath, _keyStoreType,
KeyStore keyStore = CertificateUtils.getKeyStore(_keyStoreInputStream, _keyStorePath, _keyStoreType,
_keyStoreProvider, _keyStorePassword==null? null: _keyStorePassword.toString());
KeyStore trustStore = getKeyStore(_trustStoreInputStream, _trustStorePath, _trustStoreType,
KeyStore trustStore = CertificateUtils.getKeyStore(_trustStoreInputStream, _trustStorePath, _trustStoreType,
_trustStoreProvider, _trustStorePassword==null? null: _trustStorePassword.toString());
Collection<? extends CRL> crls = loadCRL(_crlPath);
Collection<? extends CRL> crls = CertificateUtils.loadCRL(_crlPath);
if (_validateCerts && keyStore != null)
{
@ -774,7 +790,11 @@ public class SslContextFactory extends AbstractLifeCycle
throw new Exception("No certificate found in the keystore" + (_certAlias==null ? "":" for alias " + _certAlias));
}
CertificateValidator validator = new CertificateValidator(trustStore,crls);
CertificateValidator validator = new CertificateValidator(trustStore, crls);
validator.setMaxCertPathLength(_maxCertPathLength);
validator.setEnableCRLDP(_enableCRLDP);
validator.setEnableOCSP(_enableOCSP);
validator.setOcspResponderURL(_ocspResponderURL);
validator.validate(keyStore, cert);
}
@ -823,22 +843,34 @@ public class SslContextFactory extends AbstractLifeCycle
{
PKIXBuilderParameters pbParams = new PKIXBuilderParameters(trustStore,new X509CertSelector());
// Enable revocation checking
pbParams.setRevocationEnabled(true);
// Set maximum certification path length
pbParams.setMaxPathLength(_maxCertPathLength);
// Make sure revocation checking is enabled
pbParams.setRevocationEnabled(true);
if (crls != null && !crls.isEmpty())
{
pbParams.addCertStore(CertStore.getInstance("Collection",new CollectionCertStoreParameters(crls)));
}
// Enable On-Line Certificate Status Protocol (OCSP) support
Security.setProperty("ocsp.enable","true");
if (_enableCRLDP)
{
// Enable Certificate Revocation List Distribution Points (CRLDP) support
System.setProperty("com.sun.security.enableCRLDP","true");
}
// Enable Certificate Revocation List Distribution Points (CRLDP) support
System.setProperty("com.sun.security.enableCRLDP","true");
if (_enableOCSP)
{
// Enable On-Line Certificate Status Protocol (OCSP) support
Security.setProperty("ocsp.enable","true");
if (_ocspResponderURL != null)
{
// Override location of OCSP Responder
Security.setProperty("ocsp.responderURL", _ocspResponderURL);
}
}
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm);
trustManagerFactory.init(new CertPathTrustManagerParameters(pbParams));
@ -857,69 +889,6 @@ public class SslContextFactory extends AbstractLifeCycle
return managers;
}
/* ------------------------------------------------------------ */
protected KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception
{
KeyStore keystore = null;
if (storeStream != null || storePath != null)
{
InputStream inStream = storeStream;
try
{
if (inStream == null)
{
inStream = Resource.newResource(storePath).getInputStream();
}
if (storeProvider != null)
{
keystore = KeyStore.getInstance(storeType, storeProvider);
}
else
{
keystore = KeyStore.getInstance(storeType);
}
keystore.load(inStream, storePassword == null ? null : storePassword.toCharArray());
}
finally
{
if (inStream != null)
{
inStream.close();
}
}
}
return keystore;
}
/* ------------------------------------------------------------ */
protected Collection<? extends CRL> loadCRL(String crlPath) throws Exception
{
Collection<? extends CRL> crlList = null;
if (crlPath != null)
{
InputStream in = null;
try
{
in = Resource.newResource(crlPath).getInputStream();
crlList = CertificateFactory.getInstance("X.509").generateCRLs(in);
}
finally
{
if (in != null)
{
in.close();
}
}
}
return crlList;
}
/* ------------------------------------------------------------ */
/**
* Check configuration. Ensures that if keystore has been
@ -1032,4 +1001,58 @@ public class SslContextFactory extends AbstractLifeCycle
throw new IllegalStateException("Cannot modify configuration after SslContextFactory was started");
}
}
/* ------------------------------------------------------------ */
/**
* @return true if CRL Distribution Points support is enabled
*/
public boolean isEnableCRLDP()
{
return _enableCRLDP;
}
/* ------------------------------------------------------------ */
/** Enables CRL Distribution Points Support
* @param enableCRLDP true - turn on, false - turns off
*/
public void setEnableCRLDP(boolean enableCRLDP)
{
_enableCRLDP = enableCRLDP;
}
/* ------------------------------------------------------------ */
/**
* @return true if On-Line Certificate Status Protocol support is enabled
*/
public boolean isEnableOCSP()
{
return _enableOCSP;
}
/* ------------------------------------------------------------ */
/** Enables On-Line Certificate Status Protocol support
* @param enableOCSP true - turn on, false - turn off
*/
public void setEnableOCSP(boolean enableOCSP)
{
_enableOCSP = enableOCSP;
}
/* ------------------------------------------------------------ */
/**
* @return Location of the OCSP Responder
*/
public String getOcspResponderURL()
{
return _ocspResponderURL;
}
/* ------------------------------------------------------------ */
/** Set the location of the OCSP Responder.
* @param ocspResponderURL location of the OCSP Responder
*/
public void setOcspResponderURL(String ocspResponderURL)
{
_ocspResponderURL = ocspResponderURL;
}
}

View File

@ -13,9 +13,11 @@
package org.eclipse.jetty.security.authentication;
import java.io.IOException;
import java.security.KeyStore;
import java.security.Principal;
import java.security.cert.CRL;
import java.security.cert.X509Certificate;
import java.util.Collection;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
@ -23,18 +25,46 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.security.Constraint;
import org.eclipse.jetty.http.security.Password;
import org.eclipse.jetty.security.ServerAuthException;
import org.eclipse.jetty.security.UserAuthentication;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.Authentication.User;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.security.CertificateUtils;
import org.eclipse.jetty.util.security.CertificateValidator;
/**
* @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
*/
public class ClientCertAuthenticator extends LoginAuthenticator
{
/** String name of keystore password property. */
private static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password";
/** Truststore path */
private String _trustStorePath;
/** Truststore provider name */
private String _trustStoreProvider;
/** Truststore type */
private String _trustStoreType = "JKS";
/** Truststore password */
private transient Password _trustStorePassword;
/** Set to true if SSL certificate validation is required */
private boolean _validateCerts;
/** Path to file that contains Certificate Revocation List */
private String _crlPath;
/** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
private int _maxCertPathLength = -1;
/** CRL Distribution Points (CRLDP) support */
private boolean _enableCRLDP = false;
/** On-Line Certificate Status Protocol (OCSP) support */
private boolean _enableOCSP = false;
/** Location of OCSP Responder */
private String _ocspResponderURL;
public ClientCertAuthenticator()
{
super();
@ -63,10 +93,22 @@ public class ClientCertAuthenticator extends LoginAuthenticator
// Need certificates.
if (certs != null && certs.length > 0)
{
if (_validateCerts)
{
KeyStore trustStore = CertificateUtils.getKeyStore(null,
_trustStorePath, _trustStoreType, _trustStoreProvider,
_trustStorePassword == null ? null :_trustStorePassword.toString());
Collection<? extends CRL> crls = CertificateUtils.loadCRL(_crlPath);
CertificateValidator validator = new CertificateValidator(trustStore, crls);
validator.validate(certs);
}
for (X509Certificate cert: certs)
{
if (cert==null)
continue;
Principal principal = cert.getSubjectDN();
if (principal == null) principal = cert.getIssuerDN();
final String username = principal == null ? "clientcert" : principal.getName();
@ -90,7 +132,7 @@ public class ClientCertAuthenticator extends LoginAuthenticator
return Authentication.UNAUTHENTICATED;
}
catch (IOException e)
catch (Exception e)
{
throw new ServerAuthException(e.getMessage());
}
@ -100,4 +142,182 @@ public class ClientCertAuthenticator extends LoginAuthenticator
{
return true;
}
/* ------------------------------------------------------------ */
/**
* @return true if SSL certificate has to be validated
*/
public boolean isValidateCerts()
{
return _validateCerts;
}
/* ------------------------------------------------------------ */
/**
* @param validateCerts
* true if SSL certificates have to be validated
*/
public void setValidateCerts(boolean validateCerts)
{
_validateCerts = validateCerts;
}
/* ------------------------------------------------------------ */
/**
* @return The file name or URL of the trust store location
*/
public String getTrustStore()
{
return _trustStorePath;
}
/* ------------------------------------------------------------ */
/**
* @param trustStorePath
* The file name or URL of the trust store location
*/
public void setTrustStore(String trustStorePath)
{
_trustStorePath = trustStorePath;
}
/* ------------------------------------------------------------ */
/**
* @return The provider of the trust store
*/
public String getTrustStoreProvider()
{
return _trustStoreProvider;
}
/* ------------------------------------------------------------ */
/**
* @param trustStoreProvider
* The provider of the trust store
*/
public void setTrustStoreProvider(String trustStoreProvider)
{
_trustStoreProvider = trustStoreProvider;
}
/* ------------------------------------------------------------ */
/**
* @return The type of the trust store (default "JKS")
*/
public String getTrustStoreType()
{
return _trustStoreType;
}
/* ------------------------------------------------------------ */
/**
* @param trustStoreType
* The type of the trust store (default "JKS")
*/
public void setTrustStoreType(String trustStoreType)
{
_trustStoreType = trustStoreType;
}
/* ------------------------------------------------------------ */
/**
* @param password
* The password for the trust store
*/
public void setTrustStorePassword(String password)
{
_trustStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
}
/* ------------------------------------------------------------ */
/** Get the crlPath.
* @return the crlPath
*/
public String getCrlPath()
{
return _crlPath;
}
/* ------------------------------------------------------------ */
/** Set the crlPath.
* @param crlPath the crlPath to set
*/
public void setCrlPath(String crlPath)
{
_crlPath = crlPath;
}
/**
* @return Maximum number of intermediate certificates in
* the certification path (-1 for unlimited)
*/
public int getMaxCertPathLength()
{
return _maxCertPathLength;
}
/* ------------------------------------------------------------ */
/**
* @param maxCertPathLength
* maximum number of intermediate certificates in
* the certification path (-1 for unlimited)
*/
public void setMaxCertPathLength(int maxCertPathLength)
{
_maxCertPathLength = maxCertPathLength;
}
/* ------------------------------------------------------------ */
/**
* @return true if CRL Distribution Points support is enabled
*/
public boolean isEnableCRLDP()
{
return _enableCRLDP;
}
/* ------------------------------------------------------------ */
/** Enables CRL Distribution Points Support
* @param enableCRLDP true - turn on, false - turns off
*/
public void setEnableCRLDP(boolean enableCRLDP)
{
_enableCRLDP = enableCRLDP;
}
/* ------------------------------------------------------------ */
/**
* @return true if On-Line Certificate Status Protocol support is enabled
*/
public boolean isEnableOCSP()
{
return _enableOCSP;
}
/* ------------------------------------------------------------ */
/** Enables On-Line Certificate Status Protocol support
* @param enableOCSP true - turn on, false - turn off
*/
public void setEnableOCSP(boolean enableOCSP)
{
_enableOCSP = enableOCSP;
}
/* ------------------------------------------------------------ */
/**
* @return Location of the OCSP Responder
*/
public String getOcspResponderURL()
{
return _ocspResponderURL;
}
/* ------------------------------------------------------------ */
/** Set the location of the OCSP Responder.
* @param ocspResponderURL location of the OCSP Responder
*/
public void setOcspResponderURL(String ocspResponderURL)
{
_ocspResponderURL = ocspResponderURL;
}
}

View File

@ -0,0 +1,92 @@
//========================================================================
//Copyright (c) Webtide LLC
//------------------------------------------------------------------------
//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.apache.org/licenses/LICENSE-2.0.txt
//
//You may elect to redistribute this code under either of these licenses.
//========================================================================
package org.eclipse.jetty.util.security;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.cert.CRL;
import java.security.cert.CertificateFactory;
import java.util.Collection;
import org.eclipse.jetty.util.resource.Resource;
public class CertificateUtils
{
/* ------------------------------------------------------------ */
public static KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception
{
KeyStore keystore = null;
if (storeStream != null || storePath != null)
{
InputStream inStream = storeStream;
try
{
if (inStream == null)
{
inStream = Resource.newResource(storePath).getInputStream();
}
if (storeProvider != null)
{
keystore = KeyStore.getInstance(storeType, storeProvider);
}
else
{
keystore = KeyStore.getInstance(storeType);
}
keystore.load(inStream, storePassword == null ? null : storePassword.toCharArray());
}
finally
{
if (inStream != null)
{
inStream.close();
}
}
}
return keystore;
}
/* ------------------------------------------------------------ */
public static Collection<? extends CRL> loadCRL(String crlPath) throws Exception
{
Collection<? extends CRL> crlList = null;
if (crlPath != null)
{
InputStream in = null;
try
{
in = Resource.newResource(crlPath).getInputStream();
crlList = CertificateFactory.getInstance("X.509").generateCRLs(in);
}
finally
{
if (in != null)
{
in.close();
}
}
}
return crlList;
}
}

View File

@ -16,8 +16,11 @@ package org.eclipse.jetty.util.security;
//You may elect to redistribute this code under either of these licenses.
//========================================================================
import java.security.GeneralSecurityException;
import java.security.InvalidParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.Security;
import java.security.cert.CRL;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertPathBuilderResult;
@ -32,22 +35,35 @@ import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.util.log.Log;
/**
* Convenience class to handle validation of certificates, aliases and keystores
*
* Currently handles certificate revocation lists, should evolve to handle ocsp as well
* Allows specifying Certificate Revocation List (CRL), as well as enabling
* CRL Distribution Points Protocol (CRLDP) certificate extension support,
* and also enabling On-Line Certificate Status Protocol (OCSP) support.
*
* TODO: consider the case of a null trust store, is that important?
* TODO: add what support for ocsp is needed, if any
* IMPORTANT: at least one of the above mechanisms *MUST* be configured and
* operational, otherwise certificate validation *WILL FAIL* unconditionally.
*/
public class CertificateValidator
{
private static AtomicLong __aliasCount = new AtomicLong();
private KeyStore _trustStore;
private Collection<? extends CRL> _crls;
/** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
private int _maxCertPathLength = -1;
/** CRL Distribution Points (CRLDP) support */
private boolean _enableCRLDP = false;
/** On-Line Certificate Status Protocol (OCSP) support */
private boolean _enableOCSP = false;
/** Location of OCSP Responder */
private String _ocspResponderURL;
/**
* creates an instance of the certificate validator
@ -57,6 +73,11 @@ public class CertificateValidator
*/
public CertificateValidator(KeyStore trustStore, Collection<? extends CRL> crls)
{
if (trustStore == null)
{
throw new InvalidParameterException("TrustStore must be specified for CertificateValidator.");
}
_trustStore = trustStore;
_crls = crls;
}
@ -83,7 +104,7 @@ public class CertificateValidator
}
catch ( KeyStoreException kse )
{
throw new CertificateException("error obtaining aliases", kse);
throw new CertificateException("Unable to retrieve aliases from keystore", kse);
}
}
@ -106,11 +127,11 @@ public class CertificateValidator
{
validate(keyStore, keyStore.getCertificate(keyAlias));
}
catch (KeyStoreException ex)
catch (KeyStoreException kse)
{
Log.debug(ex);
throw new CertificateException("Unable to validate certificate for alias [" +
keyAlias + "]: " + ex.getMessage());
Log.debug(kse);
throw new CertificateException("Unable to validate certificate" +
" for alias [" + keyAlias + "]: " + kse.getMessage(), kse);
}
result = keyAlias;
}
@ -127,74 +148,104 @@ public class CertificateValidator
*/
public void validate(KeyStore keyStore, Certificate cert) throws CertificateException
{
Certificate[] certChain = null;
if (cert != null && cert instanceof X509Certificate)
{
((X509Certificate)cert).checkValidity();
String certAlias = "[none]";
String certAlias = null;
try
{
if (keyStore == null)
{
throw new InvalidParameterException("Keystore cannot be null");
}
certAlias = keyStore.getCertificateAlias((X509Certificate)cert);
Certificate[] certChain = keyStore.getCertificateChain(certAlias);
ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
for (Certificate item : certChain)
if (certAlias == null)
{
if (!(item instanceof X509Certificate))
{
throw new CertificateException("Invalid certificate type in chain");
}
certList.add((X509Certificate)item);
certAlias = "JETTY" + String.format("%016X",__aliasCount.incrementAndGet());
keyStore.setCertificateEntry(certAlias, cert);
}
if (certList.isEmpty())
{
throw new CertificateException("Invalid certificate chain");
}
X509CertSelector certSelect = new X509CertSelector();
certSelect.setCertificate(certList.get(0));
// Configure certification path builder parameters
PKIXBuilderParameters pbParams = new PKIXBuilderParameters(_trustStore, certSelect);
pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList)));
// Set static Certificate Revocation List
if (_crls != null && !_crls.isEmpty())
certChain = keyStore.getCertificateChain(certAlias);
if (certChain == null || certChain.length == 0)
{
pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(_crls)));
throw new IllegalStateException("Unable to retrieve certificate chain");
}
// Enable revocation checking
pbParams.setRevocationEnabled(true);
// Set maximum certification path length
pbParams.setMaxPathLength(_maxCertPathLength);
// Build certification path
CertPathBuilderResult buildResult = CertPathBuilder.getInstance("PKIX").build(pbParams);
// Validate certification path
CertPathValidator.getInstance("PKIX").validate(buildResult.getCertPath(),pbParams);
}
catch (Exception ex)
catch (KeyStoreException kse)
{
Log.debug(ex);
throw new CertificateException("Unable to validate certificate for alias [" +
certAlias + "]: " + ex.getMessage());
Log.debug(kse);
throw new CertificateException("Unable to validate certificate" +
(certAlias == null ? "":" for alias [" +certAlias + "]") + ": " + kse.getMessage(), kse);
}
validate(certChain);
}
}
public int getMaxCertPathLength()
public void validate(Certificate[] certChain) throws CertificateException
{
return _maxCertPathLength;
}
public void setMaxCertPathLength(int maxCertPathLength)
{
_maxCertPathLength = maxCertPathLength;
try
{
ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
for (Certificate item : certChain)
{
if (item == null)
continue;
if (!(item instanceof X509Certificate))
{
throw new IllegalStateException("Invalid certificate type in chain");
}
certList.add((X509Certificate)item);
}
if (certList.isEmpty())
{
throw new IllegalStateException("Invalid certificate chain");
}
X509CertSelector certSelect = new X509CertSelector();
certSelect.setCertificate(certList.get(0));
// Configure certification path builder parameters
PKIXBuilderParameters pbParams = new PKIXBuilderParameters(_trustStore, certSelect);
pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList)));
// Set maximum certification path length
pbParams.setMaxPathLength(_maxCertPathLength);
// Enable revocation checking
pbParams.setRevocationEnabled(true);
// Set static Certificate Revocation List
if (_crls != null && !_crls.isEmpty())
{
pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(_crls)));
}
// Enable On-Line Certificate Status Protocol (OCSP) support
Security.setProperty("ocsp.enable","true");
// Enable Certificate Revocation List Distribution Points (CRLDP) support
System.setProperty("com.sun.security.enableCRLDP","true");
// Build certification path
CertPathBuilderResult buildResult = CertPathBuilder.getInstance("PKIX").build(pbParams);
// Validate certification path
CertPathValidator.getInstance("PKIX").validate(buildResult.getCertPath(),pbParams);
}
catch (GeneralSecurityException gse)
{
Log.debug(gse);
throw new CertificateException("Unable to validate certificate: " + gse.getMessage(), gse);
}
}
public KeyStore getTrustStore()
@ -206,4 +257,78 @@ public class CertificateValidator
{
return _crls;
}
/**
* @return Maximum number of intermediate certificates in
* the certification path (-1 for unlimited)
*/
public int getMaxCertPathLength()
{
return _maxCertPathLength;
}
/* ------------------------------------------------------------ */
/**
* @param maxCertPathLength
* maximum number of intermediate certificates in
* the certification path (-1 for unlimited)
*/
public void setMaxCertPathLength(int maxCertPathLength)
{
_maxCertPathLength = maxCertPathLength;
}
/* ------------------------------------------------------------ */
/**
* @return true if CRL Distribution Points support is enabled
*/
public boolean isEnableCRLDP()
{
return _enableCRLDP;
}
/* ------------------------------------------------------------ */
/** Enables CRL Distribution Points Support
* @param enableCRLDP true - turn on, false - turns off
*/
public void setEnableCRLDP(boolean enableCRLDP)
{
_enableCRLDP = enableCRLDP;
}
/* ------------------------------------------------------------ */
/**
* @return true if On-Line Certificate Status Protocol support is enabled
*/
public boolean isEnableOCSP()
{
return _enableOCSP;
}
/* ------------------------------------------------------------ */
/** Enables On-Line Certificate Status Protocol support
* @param enableOCSP true - turn on, false - turn off
*/
public void setEnableOCSP(boolean enableOCSP)
{
_enableOCSP = enableOCSP;
}
/* ------------------------------------------------------------ */
/**
* @return Location of the OCSP Responder
*/
public String getOcspResponderURL()
{
return _ocspResponderURL;
}
/* ------------------------------------------------------------ */
/** Set the location of the OCSP Responder.
* @param ocspResponderURL location of the OCSP Responder
*/
public void setOcspResponderURL(String ocspResponderURL)
{
_ocspResponderURL = ocspResponderURL;
}
}