blob: c50dcfcf28e0ca395042976065031f6e5dfad0e5 [file] [log] [blame]
Karl Pauls36407322008-03-07 00:37:30 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19package org.apache.felix.framework.security.verifier;
20
21import java.io.IOException;
22import java.io.InputStream;
23import java.lang.reflect.Method;
24import java.security.cert.CRL;
25import java.security.cert.Certificate;
26import java.security.cert.CertificateException;
27import java.security.cert.X509Certificate;
28import java.util.ArrayList;
29import java.util.HashMap;
30import java.util.Iterator;
31import java.util.List;
32import java.util.Map;
33import java.util.jar.JarEntry;
34import java.util.jar.JarInputStream;
35
36import org.apache.felix.framework.cache.BundleRevision;
37import org.apache.felix.framework.security.util.BundleInputStream;
38import org.apache.felix.framework.security.util.TrustManager;
39import org.apache.felix.moduleloader.IContent;
40import org.apache.felix.moduleloader.IContentLoader;
41
42public final class BundleDNParser
43{
44 private static final Method m_getCodeSigners;
45 private static final Method m_getSignerCertPath;
46 private static final Method m_getCertificates;
47
48 static
49 {
50 Method getCodeSigners = null;
51 Method getSignerCertPath = null;
52 Method getCertificates = null;
53 try
54 {
55 getCodeSigners =
56 Class.forName("java.util.jar.JarEntry").getMethod(
57 "getCodeSigners", null);
58 getSignerCertPath =
59 Class.forName("java.security.CodeSigner").getMethod(
60 "getSignerCertPath", null);
61 getCertificates =
62 Class.forName("java.security.cert.CertPath").getMethod(
63 "getCertificates", null);
64 }
65 catch (Exception ex)
66 {
67 ex.printStackTrace();
68 getCodeSigners = null;
69 getSignerCertPath = null;
70 getCertificates = null;
71 }
72 m_getCodeSigners = getCodeSigners;
73 m_getSignerCertPath = getSignerCertPath;
74 m_getCertificates = getCertificates;
75 }
76
77 private final Map m_cache = new HashMap();
78 private final TrustManager m_manager;
79
80 public BundleDNParser(TrustManager manager)
81 {
82 m_manager = manager;
83 }
84
85 public Map getCache()
86 {
87 synchronized (m_cache)
88 {
89 return new HashMap(m_cache);
90 }
91 }
92
93 public void put(String root, String[] dnChains)
94 {
95 synchronized (m_cache)
96 {
97 m_cache.put(root, dnChains);
98 }
99 }
100
101 public void checkDNChains(String root, IContentLoader contentLoader) throws Exception
102 {
103 synchronized (m_cache)
104 {
105 if (m_cache.containsKey(root))
106 {
107 String[] result = (String[]) m_cache.get(root);
108 if ((result != null) && (result.length == 0))
109 {
110 throw new IOException("Bundle not properly signed");
111 }
112 return;
113 }
114 }
115
116 String[] result = new String[0];
117 Exception org = null;
118 try
119 {
120 result = _getDNChains(root, contentLoader.getContent());
121 }
122 catch (Exception ex)
123 {
124 org = ex;
125 }
126
127 synchronized (m_cache)
128 {
129 m_cache.put(root, result);
130 }
131
132 if (org != null)
133 {
134 throw org;
135 }
136 }
137
138 public String[] getDNChains(String root, BundleRevision bundleRevision)
139 {
140 synchronized (m_cache)
141 {
142 if (m_cache.containsKey(root))
143 {
144 String[] result = (String[]) m_cache.get(root);
145 if ((result != null) && (result.length == 0))
146 {
147 return null;
148 }
149 return result;
150 }
151 }
152
153 String[] result = new String[0];
154
155 IContent content = null;
156 try
157 {
158 content = bundleRevision.getContent();
159 content.open();
160 result = _getDNChains(root, content);
161 }
162 catch (Exception ex)
163 {
164 // Ignore
165 }
166 if (content != null)
167 {
168 try
169 {
170 content.close();
171 }
172 catch (Exception ex)
173 {
174 // Ignore
175 }
176 }
177
178 synchronized (m_cache)
179 {
180 m_cache.put(root, result);
181 }
182
183 return result;
184 }
185
186 private String[] _getDNChains(String root, IContent content)
187 throws IOException
188 {
189 X509Certificate[] certificates = null;
190
191 certificates = getCertificates(new BundleInputStream(content));
192
193 if (certificates == null)
194 {
195 return null;
196 }
197
198 List rootChains = new ArrayList();
199
200 getRootChains(certificates, rootChains);
201
202 List result = new ArrayList();
203
204 SubjectDNParser parser = new SubjectDNParser();
205
206 for (Iterator rootIter = rootChains.iterator(); rootIter.hasNext();)
207 {
208 StringBuffer buffer = new StringBuffer();
209
210 List chain = (List) rootIter.next();
211
212 Iterator iter = chain.iterator();
213
214 X509Certificate current = (X509Certificate) iter.next();
215
216 try
217 {
218 buffer.append(parser
219 .parseSubjectDN(current.getTBSCertificate()));
220
221 while (iter.hasNext())
222 {
223 buffer.append(';');
224
225 current = (X509Certificate) iter.next();
226
227 buffer.append(parser.parseSubjectDN(current
228 .getTBSCertificate()));
229 }
230
231 result.add(buffer.toString());
232
233 }
234 catch (Exception ex)
235 {
236 // something went wrong during parsing -
237 // it might be that the cert contained an unsupported OID
238 // TODO: log this or something
239 ex.printStackTrace();
240 }
241 }
242
243 if (!result.isEmpty())
244 {
245 return (String[]) result.toArray(new String[result.size()]);
246 }
247
248 throw new IOException();
249 }
250
251 private X509Certificate[] getCertificates(InputStream input)
252 throws IOException
253 {
254 JarInputStream bundle = new JarInputStream(input, true);
255
256 if (bundle.getManifest() == null)
257 {
258 return null;
259 }
260
261 List certificateChains = new ArrayList();
262
263 int count = certificateChains.size();
264
265 // This is tricky: jdk1.3 doesn't say anything about what is happening
266 // if a bad sig is detected on an entry - later jdk's do say that they
267 // will throw a security Exception. The below should cater for both
268 // behaviors.
269 for (JarEntry entry = bundle.getNextJarEntry(); entry != null; entry =
270 bundle.getNextJarEntry())
271 {
272
273 if (entry.isDirectory() || entry.getName().startsWith("META-INF"))
274 {
275 continue;
276 }
277
278 for (byte[] tmp = new byte[4096]; bundle.read(tmp, 0, tmp.length) != -1;)
279 {
280 }
281
282 Certificate[] certificates = entry.getCertificates();
283
284 // Workaround stupid bug in the sun jdk 1.5.x - getCertificates()
285 // returns null there even if there are valid certificates.
286 // This is a regression bug that has been fixed in 1.6.
287 //
288 // We use reflection to see whether we have a SignerCertPath
289 // for the entry (available >= 1.5) and if so check whether
290 // there are valid certificates - don't try this at home.
291 if ((certificates == null) && (m_getCodeSigners != null))
292 {
293 try
294 {
295 Object[] signers =
296 (Object[]) m_getCodeSigners.invoke(entry, null);
297
298 if (signers != null)
299 {
300 List certChains = new ArrayList();
301
302 for (int i = 0; i < signers.length; i++)
303 {
304 Object path =
305 m_getSignerCertPath.invoke(signers[i], null);
306
307 certChains.addAll((List) m_getCertificates.invoke(
308 path, null));
309 }
310
311 certificates =
312 (Certificate[]) certChains
313 .toArray(new Certificate[certChains.size()]);
314 }
315 }
316 catch (Exception ex)
317 {
318 ex.printStackTrace();
319 // Not much we can do - probably we are not on >= 1.5
320 }
321 }
322
323 if ((certificates == null) || (certificates.length == 0))
324 {
325 return null;
326 }
327
328 List chains = new ArrayList();
329
330 getRootChains(certificates, chains);
331
332 if (certificateChains.isEmpty())
333 {
334 certificateChains.addAll(chains);
335 count = certificateChains.size();
336 }
337 else
338 {
339 for (Iterator iter2 = certificateChains.iterator(); iter2
340 .hasNext();)
341 {
342 X509Certificate cert =
343 (X509Certificate) ((List) iter2.next()).get(0);
344 boolean found = false;
345 for (Iterator iter3 = chains.iterator(); iter3.hasNext();)
346 {
347 X509Certificate cert2 =
348 (X509Certificate) ((List) iter3.next()).get(0);
349
350 if (cert.getSubjectDN().equals(cert2.getSubjectDN())
351 && cert.equals(cert2))
352 {
353 found = true;
354 break;
355 }
356 }
357 if (!found)
358 {
359 iter2.remove();
360 }
361 }
362 }
363
364 if (certificateChains.isEmpty())
365 {
366 if (count > 0)
367 {
368 throw new IOException("Bad signers");
369 }
370 return null;
371 }
372 }
373
374 List result = new ArrayList();
375
376 for (Iterator iter = certificateChains.iterator(); iter.hasNext();)
377 {
378 result.addAll((List) iter.next());
379 }
380
381 return (X509Certificate[]) result.toArray(new X509Certificate[result
382 .size()]);
383 }
384
385 private boolean isRevoked(Certificate certificate)
386 {
387 for (Iterator iter = m_manager.getCRLs().iterator(); iter.hasNext();)
388 {
389 if (((CRL) iter.next()).isRevoked(certificate))
390 {
391 return true;
392 }
393 }
394
395 return false;
396 }
397
398 private void getRootChains(Certificate[] certificates, List chains)
399 {
400 List chain = new ArrayList();
401
402 boolean revoked = false;
403
404 for (int i = 0; i < certificates.length - 1; i++)
405 {
406 X509Certificate certificate = (X509Certificate) certificates[i];
407
408 if (!revoked && isRevoked(certificate))
409 {
410 revoked = true;
411 }
412 else if (!revoked)
413 {
414 try
415 {
416 certificate.checkValidity();
417
418 chain.add(certificate);
419 }
420 catch (CertificateException ex)
421 {
422 // TODO: log this or something
423 revoked = true;
424 }
425 }
426
427 if (!((X509Certificate) certificates[i + 1]).getSubjectDN().equals(
428 certificate.getIssuerDN()))
429 {
430 if (!revoked && trusted(certificate))
431 {
432 chains.add(chain);
433 }
434
435 revoked = false;
436
437 if (!chain.isEmpty())
438 {
439 chain = new ArrayList();
440 }
441 }
442 }
443 // The final entry in the certs array is always
444 // a "root" certificate
445 if (!revoked)
446 {
447 chain.add(certificates[certificates.length - 1]);
448 if (trusted((X509Certificate) certificates[certificates.length - 1]))
449 {
450 chains.add(chain);
451 }
452 }
453 }
454
455 private boolean trusted(X509Certificate cert)
456 {
457 if (m_manager.getCaCerts().isEmpty() || isRevoked(cert))
458 {
459 return false;
460 }
461
462 for (Iterator iter = m_manager.getCaCerts().iterator(); iter.hasNext();)
463 {
464 X509Certificate trustedCaCert = (X509Certificate) iter.next();
465
466 if (isRevoked(trustedCaCert))
467 {
468 continue;
469 }
470
471 // If the cert has the same SubjectDN
472 // as a trusted CA, check whether
473 // the two certs are the same.
474 if (cert.getSubjectDN().equals(trustedCaCert.getSubjectDN()))
475 {
476 if (cert.equals(trustedCaCert))
477 {
478 try
479 {
480 cert.checkValidity();
481 trustedCaCert.checkValidity();
482 return true;
483 }
484 catch (CertificateException ex)
485 {
486 // Not much we can do
487 // TODO: log this or something
488 }
489 }
490 }
491 }
492
493 // cert issued by any of m_trustedCaCerts ? return true : return false
494 for (Iterator iter = m_manager.getCaCerts().iterator(); iter.hasNext();)
495 {
496 X509Certificate trustedCaCert = (X509Certificate) iter.next();
497
498 if (isRevoked(trustedCaCert))
499 {
500 continue;
501 }
502
503 if (cert.getIssuerDN().equals(trustedCaCert.getSubjectDN()))
504 {
505 try
506 {
507 cert.verify(trustedCaCert.getPublicKey());
508 cert.checkValidity();
509 trustedCaCert.checkValidity();
510 return true;
511 }
512 catch (Exception ex)
513 {
514 // TODO: log this or something
515 }
516 }
517 }
518
519 return false;
520 }
521}