| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.felix.framework.security.verifier; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.reflect.Method; |
| import java.security.cert.CRL; |
| import java.security.cert.Certificate; |
| import java.security.cert.CertificateException; |
| import java.security.cert.X509Certificate; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.WeakHashMap; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarInputStream; |
| |
| import org.apache.felix.framework.BundleRevisionImpl; |
| import org.apache.felix.framework.Logger; |
| import org.apache.felix.framework.security.util.BundleInputStream; |
| import org.apache.felix.framework.security.util.TrustManager; |
| /* |
| import org.apache.felix.moduleloader.IContent; |
| import org.apache.felix.moduleloader.IModule; |
| */ |
| import org.apache.felix.framework.cache.Content; |
| |
| |
| import org.osgi.framework.Bundle; |
| |
| public final class BundleDNParser |
| { |
| private static final Method m_getCodeSigners; |
| private static final Method m_getSignerCertPath; |
| private static final Method m_getCertificates; |
| |
| static |
| { |
| Method getCodeSigners = null; |
| Method getSignerCertPath = null; |
| Method getCertificates = null; |
| try |
| { |
| getCodeSigners = Class.forName("java.util.jar.JarEntry").getMethod( |
| "getCodeSigners", null); |
| getSignerCertPath = Class.forName("java.security.CodeSigner") |
| .getMethod("getSignerCertPath", null); |
| getCertificates = Class.forName("java.security.cert.CertPath") |
| .getMethod("getCertificates", null); |
| } |
| catch (Exception ex) |
| { |
| ex.printStackTrace(); |
| getCodeSigners = null; |
| getSignerCertPath = null; |
| getCertificates = null; |
| } |
| m_getCodeSigners = getCodeSigners; |
| m_getSignerCertPath = getSignerCertPath; |
| m_getCertificates = getCertificates; |
| } |
| |
| private final Logger m_logger; |
| private final Map m_cache = new WeakHashMap(); |
| private final Map m_allCache = new WeakHashMap(); |
| |
| private final TrustManager m_manager; |
| |
| public BundleDNParser(TrustManager manager, Logger logger) |
| { |
| m_manager = manager; |
| m_logger = logger; |
| } |
| |
| public Map getCache() |
| { |
| synchronized (m_cache) |
| { |
| return new HashMap(m_cache); |
| } |
| } |
| |
| public void put(String root, X509Certificate[] dnChains) |
| { |
| synchronized (m_cache) |
| { |
| m_cache.put(root, dnChains); |
| } |
| } |
| |
| public void checkDNChains(BundleRevisionImpl root, Content content, int signersType) |
| throws Exception |
| { |
| if (signersType == Bundle.SIGNERS_TRUSTED) |
| { |
| synchronized (m_cache) |
| { |
| if (m_cache.containsKey(root)) |
| { |
| Map result = (Map) m_cache.get(root); |
| if ((result != null) && (result.isEmpty())) |
| { |
| throw new IOException("Bundle not properly signed"); |
| } |
| return; |
| } |
| } |
| } |
| else |
| { |
| synchronized (m_allCache) |
| { |
| if (m_allCache.containsKey(root)) |
| { |
| Map result = (Map) m_allCache.get(root); |
| if ((result != null) && (result.isEmpty())) |
| { |
| throw new IOException("Bundle not properly signed"); |
| } |
| return; |
| } |
| } |
| } |
| |
| Map result = null; |
| Exception org = null; |
| try |
| { |
| result = _getDNChains(content, |
| signersType == Bundle.SIGNERS_TRUSTED); |
| } |
| catch (Exception ex) |
| { |
| org = ex; |
| } |
| |
| if (signersType == Bundle.SIGNERS_TRUSTED) |
| { |
| synchronized (m_cache) |
| { |
| m_cache.put(root, result); |
| } |
| } |
| else |
| { |
| synchronized (m_allCache) |
| { |
| m_allCache.put(root, result); |
| } |
| } |
| |
| if (org != null) |
| { |
| throw org; |
| } |
| } |
| |
| public Map getDNChains(BundleRevisionImpl root, Content bundleRevision, |
| int signersType) |
| { |
| if (signersType == Bundle.SIGNERS_TRUSTED) |
| { |
| synchronized (m_cache) |
| { |
| if (m_cache.containsKey(root)) |
| { |
| Map result = (Map) m_cache.get(root); |
| return (result == null) ? new HashMap() : new HashMap( |
| result); |
| } |
| } |
| } |
| else |
| { |
| synchronized (m_allCache) |
| { |
| if (m_allCache.containsKey(root)) |
| { |
| Map result = (Map) m_allCache.get(root); |
| return (result == null) ? new HashMap() : new HashMap( |
| result); |
| } |
| } |
| } |
| |
| Map result = null; |
| |
| try |
| { |
| result = _getDNChains(bundleRevision, |
| signersType == Bundle.SIGNERS_TRUSTED); |
| } |
| catch (Exception ex) |
| { |
| // Ignore |
| } |
| |
| if (signersType == Bundle.SIGNERS_TRUSTED) |
| { |
| synchronized (m_cache) |
| { |
| m_cache.put(root, result); |
| } |
| } |
| else |
| { |
| synchronized (m_allCache) |
| { |
| m_allCache.put(root, result); |
| } |
| } |
| |
| return (result == null) ? new HashMap() : new HashMap(result); |
| } |
| |
| private Map _getDNChains(Content content, boolean check) |
| throws IOException |
| { |
| X509Certificate[] certificates = null; |
| |
| certificates = getCertificates(new BundleInputStream(content), check); |
| |
| if (certificates == null) |
| { |
| return null; |
| } |
| |
| List rootChains = new ArrayList(); |
| |
| getRootChains(certificates, rootChains, check); |
| |
| Map result = new HashMap(); |
| |
| for (Iterator rootIter = rootChains.iterator(); rootIter.hasNext();) |
| { |
| StringBuffer buffer = new StringBuffer(); |
| |
| List chain = (List) rootIter.next(); |
| |
| Iterator iter = chain.iterator(); |
| |
| X509Certificate current = (X509Certificate) iter.next(); |
| |
| result.put(current, chain); |
| } |
| |
| if (!result.isEmpty()) |
| { |
| return result; |
| } |
| |
| throw new IOException(); |
| } |
| |
| private X509Certificate[] getCertificates(InputStream input, boolean check) |
| throws IOException |
| { |
| JarInputStream bundle = new JarInputStream(input, true); |
| |
| if (bundle.getManifest() == null) |
| { |
| return null; |
| } |
| |
| List certificateChains = new ArrayList(); |
| |
| int count = certificateChains.size(); |
| |
| // This is tricky: jdk1.3 doesn't say anything about what is happening |
| // if a bad sig is detected on an entry - later jdk's do say that they |
| // will throw a security Exception. The below should cater for both |
| // behaviors. |
| for (JarEntry entry = bundle.getNextJarEntry(); entry != null; entry = bundle |
| .getNextJarEntry()) |
| { |
| |
| if (entry.isDirectory() || |
| (entry.getName().startsWith("META-INF/") && |
| (entry.getName().indexOf('/', "META-INF/".length()) < 0))) |
| { |
| continue; |
| } |
| |
| for (byte[] tmp = new byte[4096]; bundle.read(tmp, 0, tmp.length) != -1;) |
| { |
| } |
| |
| Certificate[] certificates = entry.getCertificates(); |
| |
| // Workaround stupid bug in the sun jdk 1.5.x - getCertificates() |
| // returns null there even if there are valid certificates. |
| // This is a regression bug that has been fixed in 1.6. |
| // |
| // We use reflection to see whether we have a SignerCertPath |
| // for the entry (available >= 1.5) and if so check whether |
| // there are valid certificates - don't try this at home. |
| if ((certificates == null) && (m_getCodeSigners != null)) |
| { |
| try |
| { |
| Object[] signers = (Object[]) m_getCodeSigners.invoke( |
| entry, null); |
| |
| if (signers != null) |
| { |
| List certChains = new ArrayList(); |
| |
| for (int i = 0; i < signers.length; i++) |
| { |
| Object path = m_getSignerCertPath.invoke( |
| signers[i], null); |
| |
| certChains.addAll((List) m_getCertificates.invoke( |
| path, null)); |
| } |
| |
| certificates = (Certificate[]) certChains |
| .toArray(new Certificate[certChains.size()]); |
| } |
| } |
| catch (Exception ex) |
| { |
| ex.printStackTrace(); |
| // Not much we can do - probably we are not on >= 1.5 |
| } |
| } |
| |
| if ((certificates == null) || (certificates.length == 0)) |
| { |
| return null; |
| } |
| |
| List chains = new ArrayList(); |
| |
| getRootChains(certificates, chains, check); |
| |
| if (certificateChains.isEmpty()) |
| { |
| certificateChains.addAll(chains); |
| count = certificateChains.size(); |
| } |
| else |
| { |
| for (Iterator iter2 = certificateChains.iterator(); iter2 |
| .hasNext();) |
| { |
| X509Certificate cert = (X509Certificate) ((List) iter2 |
| .next()).get(0); |
| boolean found = false; |
| for (Iterator iter3 = chains.iterator(); iter3.hasNext();) |
| { |
| X509Certificate cert2 = (X509Certificate) ((List) iter3 |
| .next()).get(0); |
| |
| if (cert.getSubjectDN().equals(cert2.getSubjectDN()) |
| && cert.equals(cert2)) |
| { |
| found = true; |
| break; |
| } |
| } |
| if (!found) |
| { |
| iter2.remove(); |
| } |
| } |
| } |
| |
| if (certificateChains.isEmpty()) |
| { |
| if (count > 0) |
| { |
| throw new IOException("Bad signers"); |
| } |
| return null; |
| } |
| } |
| |
| List result = new ArrayList(); |
| |
| for (Iterator iter = certificateChains.iterator(); iter.hasNext();) |
| { |
| result.addAll((List) iter.next()); |
| } |
| |
| return (X509Certificate[]) (!result.isEmpty() ? result.toArray(new X509Certificate[result |
| .size()]) : null); |
| } |
| |
| private boolean isRevoked(Certificate certificate) |
| { |
| for (Iterator iter = m_manager.getCRLs().iterator(); iter.hasNext();) |
| { |
| if (((CRL) iter.next()).isRevoked(certificate)) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| private void getRootChains(Certificate[] certificates, List chains, |
| boolean check) |
| { |
| List chain = new ArrayList(); |
| |
| boolean revoked = false; |
| |
| for (int i = 0; i < certificates.length - 1; i++) |
| { |
| X509Certificate certificate = (X509Certificate) certificates[i]; |
| |
| if (!revoked && isRevoked(certificate)) |
| { |
| revoked = true; |
| } |
| if (!check || !revoked) |
| { |
| try |
| { |
| if (check) |
| { |
| certificate.checkValidity(); |
| } |
| |
| chain.add(certificate); |
| } |
| catch (CertificateException ex) |
| { |
| m_logger.log(Logger.LOG_WARNING, "Invalid Certificate", ex); |
| revoked = true; |
| } |
| } |
| |
| if (!((X509Certificate) certificates[i + 1]).getSubjectDN().equals( |
| certificate.getIssuerDN())) |
| { |
| if (!check || (!revoked && trusted(certificate))) |
| { |
| chains.add(chain); |
| } |
| |
| revoked = false; |
| |
| if (!chain.isEmpty()) |
| { |
| chain = new ArrayList(); |
| } |
| } |
| } |
| // The final entry in the certs array is always |
| // a "root" certificate |
| if (!check || !revoked) |
| { |
| chain.add(certificates[certificates.length - 1]); |
| if (!check |
| || trusted((X509Certificate) certificates[certificates.length - 1])) |
| { |
| chains.add(chain); |
| } |
| } |
| } |
| |
| private boolean trusted(X509Certificate cert) |
| { |
| if (m_manager.getCaCerts().isEmpty() || isRevoked(cert)) |
| { |
| return false; |
| } |
| |
| for (Iterator iter = m_manager.getCaCerts().iterator(); iter.hasNext();) |
| { |
| X509Certificate trustedCaCert = (X509Certificate) iter.next(); |
| |
| if (isRevoked(trustedCaCert)) |
| { |
| continue; |
| } |
| |
| // If the cert has the same SubjectDN |
| // as a trusted CA, check whether |
| // the two certs are the same. |
| if (cert.getSubjectDN().equals(trustedCaCert.getSubjectDN())) |
| { |
| if (cert.equals(trustedCaCert)) |
| { |
| try |
| { |
| cert.checkValidity(); |
| trustedCaCert.checkValidity(); |
| return true; |
| } |
| catch (CertificateException ex) |
| { |
| // Not much we can do |
| m_logger.log(Logger.LOG_WARNING, "Invalid Certificate", ex); |
| } |
| } |
| } |
| } |
| |
| // cert issued by any of m_trustedCaCerts ? return true : return false |
| for (Iterator iter = m_manager.getCaCerts().iterator(); iter.hasNext();) |
| { |
| X509Certificate trustedCaCert = (X509Certificate) iter.next(); |
| |
| if (isRevoked(trustedCaCert)) |
| { |
| continue; |
| } |
| |
| if (cert.getIssuerDN().equals(trustedCaCert.getSubjectDN())) |
| { |
| try |
| { |
| cert.verify(trustedCaCert.getPublicKey()); |
| cert.checkValidity(); |
| trustedCaCert.checkValidity(); |
| return true; |
| } |
| catch (Exception ex) |
| { |
| m_logger.log(Logger.LOG_WARNING, "Invalid Certificate", ex); |
| } |
| } |
| } |
| |
| return false; |
| } |
| } |