/******************************************************************************* * Copyright (c) 2000, 2005 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.update.internal.security; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipException; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.osgi.util.NLS; import org.eclipse.update.core.ContentReference; import org.eclipse.update.core.IFeature; import org.eclipse.update.core.IVerificationResult; import org.eclipse.update.core.IVerifier; import org.eclipse.update.core.InstallMonitor; import org.eclipse.update.core.JarContentReference; import org.eclipse.update.core.Utilities; import org.eclipse.update.core.Verifier; import org.eclipse.update.internal.core.Messages; import org.eclipse.update.internal.core.UpdateCore; import org.eclipse.update.internal.core.connection.ConnectionFactory; /** * The JarVerifier will check the integrity of the JAR. * If the Jar is signed and the integrity is validated, * it will check if one of the certificate of each file * is in one of the keystore. * */ public class JarVerifier extends Verifier { private static final String MANIFEST = "META-INF"; //$NON-NLS-1$ private JarVerificationResult result; private List /*of CertificatePair*/ trustedCertificates; private boolean acceptUnsignedFiles; private List /* of KeyStore */ listOfKeystores; private IProgressMonitor monitor; private File jarFile; private static byte[] buffer = new byte[8192]; /* * Default Constructor */ public JarVerifier() { initialize(); } /* * Returns the list of the keystores. */ private List getKeyStores() throws CoreException { if (listOfKeystores == null) { listOfKeystores = new ArrayList(0); KeyStores listOfKeystoreHandles = new KeyStores(); InputStream in = null; KeyStore keystore = null; KeystoreHandle handle = null; while (listOfKeystoreHandles.hasNext()) { try { handle = listOfKeystoreHandles.next(); in = ConnectionFactory.get(handle.getLocation()).getInputStream(); try { keystore = KeyStore.getInstance(handle.getType()); keystore.load(in, null); // no password } catch (NoSuchAlgorithmException e) { throw Utilities.newCoreException(NLS.bind(Messages.JarVerifier_UnableToFindEncryption, (new String[] { handle.getLocation().toExternalForm() })), e); } catch (CertificateException e) { throw Utilities.newCoreException(NLS.bind(Messages.JarVerifier_UnableToLoadCertificate, (new String[] { handle.getLocation().toExternalForm() })), e); } catch (KeyStoreException e) { throw Utilities.newCoreException(NLS.bind(Messages.JarVerifier_UnableToFindProviderForKeystore, (new String[] { handle.getType() })), e); } finally { if (in != null) { try { in.close(); } catch (IOException e) { } // nothing } } // try loading a keyStore // keystore was loaded listOfKeystores.add(keystore); } catch (IOException e) { // nothing... if the keystore doesn't exist, continue } } // while all key stores } return listOfKeystores; } /* * */ private void initialize() { result = null; trustedCertificates = null; acceptUnsignedFiles = false; listOfKeystores = null; } /* * init */ private void init(IFeature feature, ContentReference contentRef) throws CoreException { jarFile = null; if (contentRef instanceof JarContentReference) { JarContentReference jarReference = (JarContentReference) contentRef; try { jarFile = jarReference.asFile(); if (UpdateCore.DEBUG && UpdateCore.DEBUG_SHOW_INSTALL) UpdateCore.debug("Attempting to read JAR file:"+jarFile); //$NON-NLS-1$ // # of entries if (!jarFile.exists()) throw new IOException(); JarFile jar = new JarFile(jarFile); if (jar !=null){ try { jar.close(); } catch (IOException ex) { // unchecked } } } catch (ZipException e){ throw Utilities.newCoreException(NLS.bind(Messages.JarVerifier_InvalidJar, (new String[] { jarReference.toString() })), e); } catch (IOException e) { throw Utilities.newCoreException(NLS.bind(Messages.JarVerifier_UnableToAccessJar, (new String[] { jarReference.toString() })), e); } } result = new JarVerificationResult(); result.setVerificationCode(IVerificationResult.UNKNOWN_ERROR); result.setResultException(null); result.setFeature(feature); result.setContentReference(contentRef); } /* * Returns true if one of the certificate exists in the keystore */ private boolean existsInKeystore(Certificate cert) throws CoreException { try { List keyStores = getKeyStores(); if (!keyStores.isEmpty()) { Iterator listOfKeystores = keyStores.iterator(); while (listOfKeystores.hasNext()) { KeyStore keystore = (KeyStore) listOfKeystores.next(); if (keystore.getCertificateAlias(cert) != null) { return true; } } } } catch (KeyStoreException e) { throw Utilities.newCoreException(Messages.JarVerifier_KeyStoreNotLoaded, e); } return false; } /* * */ private List readJarFile(JarFile jarFile, String identifier) throws IOException, InterruptedException { List list = new ArrayList(); Enumeration entries = jarFile.entries(); JarEntry currentEntry = null; InputStream in = null; if (monitor != null) monitor.setTaskName(NLS.bind(Messages.JarVerifier_Verify, (new String[] { identifier == null ? jarFile.getName(): identifier }))); try { while (entries.hasMoreElements()) { currentEntry = (JarEntry) entries.nextElement(); list.add(currentEntry); in = jarFile.getInputStream(currentEntry); while ((in.read(buffer, 0, buffer.length)) != -1) { // Security error thrown if tempered } if (in!=null) in.close(); } } catch (IOException e) { result.setVerificationCode(IVerificationResult.UNKNOWN_ERROR); result.setResultException(e); } finally { try { if (in != null) in.close(); } catch (IOException e1) { // ignore } } return list; } /* * @param newMonitor org.eclipse.core.runtime.IProgressMonitor */ public void setMonitor(IProgressMonitor newMonitor) { monitor = newMonitor; } /* * @see IVerifier#verify(IFeature,ContentReference,boolean, InstallMonitor) */ public IVerificationResult verify( IFeature feature, ContentReference reference, boolean isFeatureVerification, InstallMonitor monitor) throws CoreException { if (reference == null) return result; // if parent knows how to verify, ask the parent first if (getParent() != null) { IVerificationResult vr = getParent().verify(feature, reference, isFeatureVerification, monitor); if (vr.getVerificationCode() != IVerificationResult.TYPE_ENTRY_UNRECOGNIZED) return vr; } // the parent couldn't verify setMonitor(monitor); init(feature, reference); result.isFeatureVerification(isFeatureVerification); if (jarFile!=null) { result = verify(jarFile.getAbsolutePath(), reference.getIdentifier()); } else { result.setVerificationCode(IVerificationResult.TYPE_ENTRY_UNRECOGNIZED); } return result; } /* * */ private JarVerificationResult verify(String file, String identifier) { try { // verify integrity verifyIntegrity(file, identifier); // do not close input stream // as verifyIntegrity already did it //if user already said yes result.alreadySeen(alreadyValidated()); // verify source certificate if (result.getVerificationCode() == IVerificationResult.TYPE_ENTRY_SIGNED_UNRECOGNIZED) { verifyAuthentication(); } // save the fact the file is not signed, so the user will not be prompted again if (result.getVerificationCode() == IVerificationResult.TYPE_ENTRY_NOT_SIGNED) { acceptUnsignedFiles = true; } } catch (Exception e) { result.setVerificationCode(IVerificationResult.UNKNOWN_ERROR); result.setResultException(e); } if (monitor != null) { monitor.worked(1); if (monitor.isCanceled()) { result.setVerificationCode(IVerificationResult.VERIFICATION_CANCELLED); } } return result; } /* * Verifies that each file has at least one certificate * valid in the keystore * * At least one certificate from each Certificate Array * of the Jar file must be found in the known Certificates */ private void verifyAuthentication() throws CoreException { CertificatePair[] entries = result.getRootCertificates(); boolean certificateFound = false; // If all the certificate of an entry are // not found in the list of known certifcate // the certificate is not trusted by any keystore. for (int i = 0; i < entries.length; i++) { certificateFound = existsInKeystore(entries[i].getRoot()); if (certificateFound) { result.setVerificationCode(IVerificationResult.TYPE_ENTRY_SIGNED_RECOGNIZED); result.setFoundCertificate(entries[i]); return; } } } /* * Verifies the integrity of the JAR */ private void verifyIntegrity(String file, String identifier) { JarFile jarFile = null; try { // If the JAR is signed and not valid // a security exception will be thrown // while reading it jarFile = new JarFile(file, true); List filesInJar = readJarFile(jarFile, identifier); // you have to read all the files once // before getting the certificates if (jarFile.getManifest() != null) { Iterator iter = filesInJar.iterator(); boolean certificateFound = false; while (iter.hasNext()) { JarEntry currentJarEntry = (JarEntry) iter.next(); Certificate[] certs = currentJarEntry.getCertificates(); if ((certs != null) && (certs.length != 0)) { certificateFound = true; result.addCertificates(certs); } else { String jarEntryName = currentJarEntry.getName(); if (!jarEntryName.toUpperCase().startsWith(MANIFEST) && !currentJarEntry.isDirectory()) { // if the jarEntry is not in MANIFEST, consider the whole file unsigned break; } } } if (certificateFound) result.setVerificationCode(IVerificationResult.TYPE_ENTRY_SIGNED_UNRECOGNIZED); else result.setVerificationCode(IVerificationResult.TYPE_ENTRY_NOT_SIGNED); } else { Exception e = new Exception(NLS.bind(Messages.JarVerifier_InvalidFile, (new String[] { file }))); result.setResultException(e); result.setVerificationCode(IVerificationResult.TYPE_ENTRY_NOT_SIGNED); UpdateCore.warn(null,e); } } catch (SecurityException e) { // Jar file is signed // but content has changed since signed result.setVerificationCode(IVerificationResult.TYPE_ENTRY_CORRUPTED); } catch (InterruptedException e) { result.setVerificationCode(IVerificationResult.VERIFICATION_CANCELLED); } catch (Exception e) { result.setVerificationCode(IVerificationResult.UNKNOWN_ERROR); result.setResultException(e); } finally { if (jarFile!=null){ try {jarFile.close();} catch (IOException e){} } } } /* * */ private boolean alreadyValidated() { if (result.getVerificationCode() == IVerificationResult.TYPE_ENTRY_NOT_SIGNED) return (acceptUnsignedFiles); if (getTrustedCertificates() != null) { Iterator iter = getTrustedCertificates().iterator(); CertificatePair[] jarPairs = result.getRootCertificates(); // check if this is not a user accepted certificate for this feature while (iter.hasNext()) { CertificatePair trustedCertificate = (CertificatePair) iter.next(); for (int i = 0; i < jarPairs.length; i++) { if (trustedCertificate.equals(jarPairs[i])) { return true; } } } // if certificate pair not found in trusted add it for next time for (int i = 0; i < jarPairs.length; i++) { addTrustedCertificate(jarPairs[i]); } } return false; } /* * */ private void addTrustedCertificate(CertificatePair pair) { if (trustedCertificates == null) trustedCertificates = new ArrayList(); if (pair != null) trustedCertificates.add(pair); } /* * */ private List getTrustedCertificates() { if (trustedCertificates == null) trustedCertificates = new ArrayList(); return trustedCertificates; } /** * @see IVerifier#setParent(IVerifier) */ public void setParent(IVerifier parentVerifier) { super.setParent(parentVerifier); initialize(); } }