blob: 2ead38781624174e2c2c6f7340710bcc3cf683ba [file] [log] [blame]
Jake Slack03928ae2014-05-13 18:41:56 -07001//
2// ========================================================================
3// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4// ------------------------------------------------------------------------
5// All rights reserved. This program and the accompanying materials
6// are made available under the terms of the Eclipse Public License v1.0
7// and Apache License v2.0 which accompanies this distribution.
8//
9// The Eclipse Public License is available at
10// http://www.eclipse.org/legal/epl-v10.html
11//
12// The Apache License v2.0 is available at
13// http://www.opensource.org/licenses/apache2.0.php
14//
15// You may elect to redistribute this code under either of these licenses.
16// ========================================================================
17//
18
19package org.eclipse.jetty.util.security;
20
21import java.security.GeneralSecurityException;
22import java.security.InvalidParameterException;
23import java.security.KeyStore;
24import java.security.KeyStoreException;
25import java.security.Security;
26import java.security.cert.CRL;
27import java.security.cert.CertPathBuilder;
28import java.security.cert.CertPathBuilderResult;
29import java.security.cert.CertPathValidator;
30import java.security.cert.CertStore;
31import java.security.cert.Certificate;
32import java.security.cert.CertificateException;
33import java.security.cert.CollectionCertStoreParameters;
34import java.security.cert.PKIXBuilderParameters;
35import java.security.cert.X509CertSelector;
36import java.security.cert.X509Certificate;
37import java.util.ArrayList;
38import java.util.Collection;
39import java.util.Enumeration;
40import java.util.concurrent.atomic.AtomicLong;
41
42import org.eclipse.jetty.util.log.Log;
43import org.eclipse.jetty.util.log.Logger;
44
45/**
46 * Convenience class to handle validation of certificates, aliases and keystores
47 *
48 * Allows specifying Certificate Revocation List (CRL), as well as enabling
49 * CRL Distribution Points Protocol (CRLDP) certificate extension support,
50 * and also enabling On-Line Certificate Status Protocol (OCSP) support.
51 *
52 * IMPORTANT: at least one of the above mechanisms *MUST* be configured and
53 * operational, otherwise certificate validation *WILL FAIL* unconditionally.
54 */
55public class CertificateValidator
56{
57 private static final Logger LOG = Log.getLogger(CertificateValidator.class);
58 private static AtomicLong __aliasCount = new AtomicLong();
59
60 private KeyStore _trustStore;
61 private Collection<? extends CRL> _crls;
62
63 /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
64 private int _maxCertPathLength = -1;
65 /** CRL Distribution Points (CRLDP) support */
66 private boolean _enableCRLDP = false;
67 /** On-Line Certificate Status Protocol (OCSP) support */
68 private boolean _enableOCSP = false;
69 /** Location of OCSP Responder */
70 private String _ocspResponderURL;
71
72 /**
73 * creates an instance of the certificate validator
74 *
75 * @param trustStore
76 * @param crls
77 */
78 public CertificateValidator(KeyStore trustStore, Collection<? extends CRL> crls)
79 {
80 if (trustStore == null)
81 {
82 throw new InvalidParameterException("TrustStore must be specified for CertificateValidator.");
83 }
84
85 _trustStore = trustStore;
86 _crls = crls;
87 }
88
89 /**
90 * validates all aliases inside of a given keystore
91 *
92 * @param keyStore
93 * @throws CertificateException
94 */
95 public void validate( KeyStore keyStore ) throws CertificateException
96 {
97 try
98 {
99 Enumeration<String> aliases = keyStore.aliases();
100
101 for ( ; aliases.hasMoreElements(); )
102 {
103 String alias = aliases.nextElement();
104
105 validate(keyStore,alias);
106 }
107
108 }
109 catch ( KeyStoreException kse )
110 {
111 throw new CertificateException("Unable to retrieve aliases from keystore", kse);
112 }
113 }
114
115
116 /**
117 * validates a specific alias inside of the keystore being passed in
118 *
119 * @param keyStore
120 * @param keyAlias
121 * @return the keyAlias if valid
122 * @throws CertificateException
123 */
124 public String validate(KeyStore keyStore, String keyAlias) throws CertificateException
125 {
126 String result = null;
127
128 if (keyAlias != null)
129 {
130 try
131 {
132 validate(keyStore, keyStore.getCertificate(keyAlias));
133 }
134 catch (KeyStoreException kse)
135 {
136 LOG.debug(kse);
137 throw new CertificateException("Unable to validate certificate" +
138 " for alias [" + keyAlias + "]: " + kse.getMessage(), kse);
139 }
140 result = keyAlias;
141 }
142
143 return result;
144 }
145
146 /**
147 * validates a specific certificate inside of the keystore being passed in
148 *
149 * @param keyStore
150 * @param cert
151 * @throws CertificateException
152 */
153 public void validate(KeyStore keyStore, Certificate cert) throws CertificateException
154 {
155 Certificate[] certChain = null;
156
157 if (cert != null && cert instanceof X509Certificate)
158 {
159 ((X509Certificate)cert).checkValidity();
160
161 String certAlias = null;
162 try
163 {
164 if (keyStore == null)
165 {
166 throw new InvalidParameterException("Keystore cannot be null");
167 }
168
169 certAlias = keyStore.getCertificateAlias((X509Certificate)cert);
170 if (certAlias == null)
171 {
172 certAlias = "JETTY" + String.format("%016X",__aliasCount.incrementAndGet());
173 keyStore.setCertificateEntry(certAlias, cert);
174 }
175
176 certChain = keyStore.getCertificateChain(certAlias);
177 if (certChain == null || certChain.length == 0)
178 {
179 throw new IllegalStateException("Unable to retrieve certificate chain");
180 }
181 }
182 catch (KeyStoreException kse)
183 {
184 LOG.debug(kse);
185 throw new CertificateException("Unable to validate certificate" +
186 (certAlias == null ? "":" for alias [" +certAlias + "]") + ": " + kse.getMessage(), kse);
187 }
188
189 validate(certChain);
190 }
191 }
192
193 public void validate(Certificate[] certChain) throws CertificateException
194 {
195 try
196 {
197 ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
198 for (Certificate item : certChain)
199 {
200 if (item == null)
201 continue;
202
203 if (!(item instanceof X509Certificate))
204 {
205 throw new IllegalStateException("Invalid certificate type in chain");
206 }
207
208 certList.add((X509Certificate)item);
209 }
210
211 if (certList.isEmpty())
212 {
213 throw new IllegalStateException("Invalid certificate chain");
214
215 }
216
217 X509CertSelector certSelect = new X509CertSelector();
218 certSelect.setCertificate(certList.get(0));
219
220 // Configure certification path builder parameters
221 PKIXBuilderParameters pbParams = new PKIXBuilderParameters(_trustStore, certSelect);
222 pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList)));
223
224 // Set maximum certification path length
225 pbParams.setMaxPathLength(_maxCertPathLength);
226
227 // Enable revocation checking
228 pbParams.setRevocationEnabled(true);
229
230 // Set static Certificate Revocation List
231 if (_crls != null && !_crls.isEmpty())
232 {
233 pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(_crls)));
234 }
235
236 // Enable On-Line Certificate Status Protocol (OCSP) support
237 if (_enableOCSP)
238 {
239 Security.setProperty("ocsp.enable","true");
240 }
241 // Enable Certificate Revocation List Distribution Points (CRLDP) support
242 if (_enableCRLDP)
243 {
244 System.setProperty("com.sun.security.enableCRLDP","true");
245 }
246
247 // Build certification path
248 CertPathBuilderResult buildResult = CertPathBuilder.getInstance("PKIX").build(pbParams);
249
250 // Validate certification path
251 CertPathValidator.getInstance("PKIX").validate(buildResult.getCertPath(),pbParams);
252 }
253 catch (GeneralSecurityException gse)
254 {
255 LOG.debug(gse);
256 throw new CertificateException("Unable to validate certificate: " + gse.getMessage(), gse);
257 }
258 }
259
260 public KeyStore getTrustStore()
261 {
262 return _trustStore;
263 }
264
265 public Collection<? extends CRL> getCrls()
266 {
267 return _crls;
268 }
269
270 /**
271 * @return Maximum number of intermediate certificates in
272 * the certification path (-1 for unlimited)
273 */
274 public int getMaxCertPathLength()
275 {
276 return _maxCertPathLength;
277 }
278
279 /* ------------------------------------------------------------ */
280 /**
281 * @param maxCertPathLength
282 * maximum number of intermediate certificates in
283 * the certification path (-1 for unlimited)
284 */
285 public void setMaxCertPathLength(int maxCertPathLength)
286 {
287 _maxCertPathLength = maxCertPathLength;
288 }
289
290 /* ------------------------------------------------------------ */
291 /**
292 * @return true if CRL Distribution Points support is enabled
293 */
294 public boolean isEnableCRLDP()
295 {
296 return _enableCRLDP;
297 }
298
299 /* ------------------------------------------------------------ */
300 /** Enables CRL Distribution Points Support
301 * @param enableCRLDP true - turn on, false - turns off
302 */
303 public void setEnableCRLDP(boolean enableCRLDP)
304 {
305 _enableCRLDP = enableCRLDP;
306 }
307
308 /* ------------------------------------------------------------ */
309 /**
310 * @return true if On-Line Certificate Status Protocol support is enabled
311 */
312 public boolean isEnableOCSP()
313 {
314 return _enableOCSP;
315 }
316
317 /* ------------------------------------------------------------ */
318 /** Enables On-Line Certificate Status Protocol support
319 * @param enableOCSP true - turn on, false - turn off
320 */
321 public void setEnableOCSP(boolean enableOCSP)
322 {
323 _enableOCSP = enableOCSP;
324 }
325
326 /* ------------------------------------------------------------ */
327 /**
328 * @return Location of the OCSP Responder
329 */
330 public String getOcspResponderURL()
331 {
332 return _ocspResponderURL;
333 }
334
335 /* ------------------------------------------------------------ */
336 /** Set the location of the OCSP Responder.
337 * @param ocspResponderURL location of the OCSP Responder
338 */
339 public void setOcspResponderURL(String ocspResponderURL)
340 {
341 _ocspResponderURL = ocspResponderURL;
342 }
343}