1 /*
2 * Copyright 2002-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26 package sun.security.validator;
27
28 import java.util;
29
30 import java.security;
31 import java.security.cert;
32
33 import javax.security.auth.x500.X500Principal;
34
35 /**
36 * Validator implementation built on the PKIX CertPath API. This
37 * implementation will be emphasized going forward.<p>
38 *
39 * Note that the validate() implementation tries to use a PKIX validator
40 * if that appears possible and a PKIX builder otherwise. This increases
41 * performance and currently also leads to better exception messages
42 * in case of failures.
43 *
44 * @author Andreas Sterbenz
45 */
46 public final class PKIXValidator extends Validator {
47
48 // enable use of the validator if possible
49 private final static boolean TRY_VALIDATOR = true;
50
51 private final Set<X509Certificate> trustedCerts;
52 private final PKIXBuilderParameters parameterTemplate;
53 private int certPathLength = -1;
54
55 // needed only for the validator
56 private Map<X500Principal, X509Certificate> trustedSubjects;
57 private CertificateFactory factory;
58
59 private boolean plugin = false;
60
61 PKIXValidator(String variant, Collection<X509Certificate> trustedCerts) {
62 super(TYPE_PKIX, variant);
63 if (trustedCerts instanceof Set) {
64 this.trustedCerts = (Set<X509Certificate>)trustedCerts;
65 } else {
66 this.trustedCerts = new HashSet<X509Certificate>(trustedCerts);
67 }
68 Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>();
69 for (X509Certificate cert : trustedCerts) {
70 trustAnchors.add(new TrustAnchor(cert, null));
71 }
72 try {
73 parameterTemplate = new PKIXBuilderParameters(trustAnchors, null);
74 } catch (InvalidAlgorithmParameterException e) {
75 throw new RuntimeException("Unexpected error: " + e.toString(), e);
76 }
77 setDefaultParameters(variant);
78 initCommon();
79 }
80
81 PKIXValidator(String variant, PKIXBuilderParameters params) {
82 super(TYPE_PKIX, variant);
83 trustedCerts = new HashSet<X509Certificate>();
84 for (TrustAnchor anchor : params.getTrustAnchors()) {
85 X509Certificate cert = anchor.getTrustedCert();
86 if (cert != null) {
87 trustedCerts.add(cert);
88 }
89 }
90 parameterTemplate = params;
91 initCommon();
92 }
93
94 private void initCommon() {
95 if (TRY_VALIDATOR == false) {
96 return;
97 }
98 trustedSubjects = new HashMap<X500Principal, X509Certificate>();
99 for (X509Certificate cert : trustedCerts) {
100 trustedSubjects.put(cert.getSubjectX500Principal(), cert);
101 }
102 try {
103 factory = CertificateFactory.getInstance("X.509");
104 } catch (CertificateException e) {
105 throw new RuntimeException("Internal error", e);
106 }
107 plugin = variant.equals(VAR_PLUGIN_CODE_SIGNING);
108 }
109
110 public Collection<X509Certificate> getTrustedCertificates() {
111 return trustedCerts;
112 }
113
114 /**
115 * Returns the length of the last certification path that is validated by
116 * CertPathValidator. This is intended primarily as a callback mechanism
117 * for PKIXCertPathCheckers to determine the length of the certification
118 * path that is being validated. It is necessary since engineValidate()
119 * may modify the length of the path.
120 *
121 * @return the length of the last certification path passed to
122 * CertPathValidator.validate, or -1 if it has not been invoked yet
123 */
124 public int getCertPathLength() {
125 return certPathLength;
126 }
127
128 /**
129 * Set J2SE global default PKIX parameters. Currently, hardcoded to disable
130 * revocation checking. In the future, this should be configurable.
131 */
132 private void setDefaultParameters(String variant) {
133 parameterTemplate.setRevocationEnabled(false);
134 }
135
136 /**
137 * Return the PKIX parameters used by this instance. An application may
138 * modify the parameters but must make sure not to perform any concurrent
139 * validations.
140 */
141 public PKIXBuilderParameters getParameters() {
142 return parameterTemplate;
143 }
144
145 X509Certificate[] engineValidate(X509Certificate[] chain,
146 Collection<X509Certificate> otherCerts, Object parameter)
147 throws CertificateException {
148 if ((chain == null) || (chain.length == 0)) {
149 throw new CertificateException
150 ("null or zero-length certificate chain");
151 }
152 if (TRY_VALIDATOR) {
153 // check if chain contains trust anchor
154 for (int i = 0; i < chain.length; i++) {
155 if (trustedCerts.contains(chain[i])) {
156 if (i == 0) {
157 return new X509Certificate[] {chain[0]};
158 }
159 // Remove and call validator
160 X509Certificate[] newChain = new X509Certificate[i];
161 System.arraycopy(chain, 0, newChain, 0, i);
162 return doValidate(newChain);
163 }
164 }
165
166 // not self issued and apparently issued by trust anchor?
167 X509Certificate last = chain[chain.length - 1];
168 X500Principal issuer = last.getIssuerX500Principal();
169 X500Principal subject = last.getSubjectX500Principal();
170 if (trustedSubjects.containsKey(issuer) && !issuer.equals(subject)
171 && isSignatureValid(trustedSubjects.get(issuer), last)) {
172 return doValidate(chain);
173 }
174
175 // don't fallback to builder if called from plugin/webstart
176 if (plugin) {
177 // Validate chain even if no trust anchor is found. This
178 // allows plugin/webstart to make sure the chain is
179 // otherwise valid
180 if (chain.length > 1) {
181 X509Certificate[] newChain =
182 new X509Certificate[chain.length-1];
183 System.arraycopy(chain, 0, newChain, 0, newChain.length);
184 // temporarily set last cert as sole trust anchor
185 PKIXBuilderParameters params =
186 (PKIXBuilderParameters) parameterTemplate.clone();
187 try {
188 params.setTrustAnchors
189 (Collections.singleton(new TrustAnchor
190 (chain[chain.length-1], null)));
191 } catch (InvalidAlgorithmParameterException iape) {
192 // should never occur, but ...
193 throw new CertificateException(iape);
194 }
195 doValidate(newChain, params);
196 }
197 // if the rest of the chain is valid, throw exception
198 // indicating no trust anchor was found
199 throw new ValidatorException
200 (ValidatorException.T_NO_TRUST_ANCHOR);
201 }
202 // otherwise, fall back to builder
203 }
204
205 return doBuild(chain, otherCerts);
206 }
207
208 private boolean isSignatureValid(X509Certificate iss, X509Certificate sub) {
209 if (plugin) {
210 try {
211 sub.verify(iss.getPublicKey());
212 } catch (Exception ex) {
213 return false;
214 }
215 return true;
216 }
217 return true; // only check if PLUGIN is set
218 }
219
220 private static X509Certificate[] toArray(CertPath path, TrustAnchor anchor)
221 throws CertificateException {
222 List<? extends java.security.cert.Certificate> list =
223 path.getCertificates();
224 X509Certificate[] chain = new X509Certificate[list.size() + 1];
225 list.toArray(chain);
226 X509Certificate trustedCert = anchor.getTrustedCert();
227 if (trustedCert == null) {
228 throw new ValidatorException
229 ("TrustAnchor must be specified as certificate");
230 }
231 chain[chain.length - 1] = trustedCert;
232 return chain;
233 }
234
235 /**
236 * Set the check date (for debugging).
237 */
238 private void setDate(PKIXBuilderParameters params) {
239 Date date = validationDate;
240 if (date != null) {
241 params.setDate(date);
242 }
243 }
244
245 private X509Certificate[] doValidate(X509Certificate[] chain)
246 throws CertificateException {
247 PKIXBuilderParameters params =
248 (PKIXBuilderParameters)parameterTemplate.clone();
249 return doValidate(chain, params);
250 }
251
252 private X509Certificate[] doValidate(X509Certificate[] chain,
253 PKIXBuilderParameters params) throws CertificateException {
254 try {
255 setDate(params);
256
257 // do the validation
258 CertPathValidator validator = CertPathValidator.getInstance("PKIX");
259 CertPath path = factory.generateCertPath(Arrays.asList(chain));
260 certPathLength = chain.length;
261 PKIXCertPathValidatorResult result =
262 (PKIXCertPathValidatorResult)validator.validate(path, params);
263
264 return toArray(path, result.getTrustAnchor());
265 } catch (GeneralSecurityException e) {
266 throw new ValidatorException
267 ("PKIX path validation failed: " + e.toString(), e);
268 }
269 }
270
271 private X509Certificate[] doBuild(X509Certificate[] chain,
272 Collection<X509Certificate> otherCerts) throws CertificateException {
273
274 try {
275 PKIXBuilderParameters params =
276 (PKIXBuilderParameters)parameterTemplate.clone();
277 setDate(params);
278
279 // setup target constraints
280 X509CertSelector selector = new X509CertSelector();
281 selector.setCertificate(chain[0]);
282 params.setTargetCertConstraints(selector);
283
284 // setup CertStores
285 Collection<X509Certificate> certs =
286 new ArrayList<X509Certificate>();
287 certs.addAll(Arrays.asList(chain));
288 if (otherCerts != null) {
289 certs.addAll(otherCerts);
290 }
291 CertStore store = CertStore.getInstance("Collection",
292 new CollectionCertStoreParameters(certs));
293 params.addCertStore(store);
294
295 // do the build
296 CertPathBuilder builder = CertPathBuilder.getInstance("PKIX");
297 PKIXCertPathBuilderResult result =
298 (PKIXCertPathBuilderResult)builder.build(params);
299
300 return toArray(result.getCertPath(), result.getTrustAnchor());
301 } catch (GeneralSecurityException e) {
302 throw new ValidatorException
303 ("PKIX path building failed: " + e.toString(), e);
304 }
305 }
306
307 }