diff options
author | Thomas Watson | 2007-12-17 20:01:53 +0000 |
---|---|---|
committer | Thomas Watson | 2007-12-17 20:01:53 +0000 |
commit | 0dc68cdfc27a0112b071bade9737965855ff7834 (patch) | |
tree | e325956db65682338a70d5f9b3c0b95b3c034cb2 | |
parent | b2c60c044ec9a85aaa715fcb0f81f85bdc74f33b (diff) | |
download | rt.equinox.framework-20071217.tar.gz rt.equinox.framework-20071217.tar.xz rt.equinox.framework-20071217.zip |
44 files changed, 3082 insertions, 1850 deletions
diff --git a/bundles/org.eclipse.osgi/.classpath b/bundles/org.eclipse.osgi/.classpath index 4ff5649aa..66c14f41a 100644 --- a/bundles/org.eclipse.osgi/.classpath +++ b/bundles/org.eclipse.osgi/.classpath @@ -9,6 +9,7 @@ <classpathentry kind="src" path="eclipseAdaptor/src"/> <classpathentry kind="src" path="resolver/src"/> <classpathentry kind="src" path="jarverifier"/> + <classpathentry kind="src" path="security/src"/> <classpathentry kind="lib" path="osgi/exceptions.jar"/> <classpathentry kind="lib" path="osgi/ee.minimum.jar"/> <classpathentry kind="lib" path="osgi/xmlParserAPIs.jar"/> diff --git a/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF b/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF index 9fa7f1395..1d2ea10c8 100644 --- a/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF @@ -11,7 +11,9 @@ Export-Package: org.eclipse.osgi.event;version="1.0", org.eclipse.osgi.service.pluginconversion;version="1.0", org.eclipse.osgi.service.resolver;version="1.2", org.eclipse.osgi.service.runnable;version="1.1", + org.eclipse.osgi.service.security; version="1.0", org.eclipse.osgi.service.urlconversion;version="1.0", + org.eclipse.osgi.signedcontent; version="1.0", org.eclipse.osgi.storagemanager;version="1.0", org.eclipse.osgi.util;version="1.1", org.osgi.framework;version="1.4", @@ -42,8 +44,9 @@ Export-Package: org.eclipse.osgi.event;version="1.0", org.eclipse.osgi.internal.module;x-internal:=true, org.eclipse.osgi.internal.profile;x-internal:=true, org.eclipse.osgi.internal.resolver;x-internal:=true, - org.eclipse.osgi.internal.verifier;x-internal:=true, - org.eclipse.osgi.internal.provisional.verifier;x-friends:="org.eclipse.update.core,org.eclipse.ui.workbench,org.eclipse.equinox.p2.artifact.repository" + org.eclipse.osgi.internal.provisional.verifier;x-friends:="org.eclipse.update.core,org.eclipse.ui.workbench,org.eclipse.equinox.p2.artifact.repository", + org.eclipse.osgi.internal.service.security; x-internal:=true, + org.eclipse.osgi.internal.signedcontent; x-internal:=true Export-Service: org.osgi.service.packageadmin.PackageAdmin, org.osgi.service.permissionadmin.PermissionAdmin, org.osgi.service.startlevel.StartLevel, diff --git a/bundles/org.eclipse.osgi/build.properties b/bundles/org.eclipse.osgi/build.properties index ad876e0a1..734768372 100644 --- a/bundles/org.eclipse.osgi/build.properties +++ b/bundles/org.eclipse.osgi/build.properties @@ -27,6 +27,7 @@ source.. = osgi/src,\ eclipseAdaptor/src/,\ console/src/,\ supplement/src/,\ - jarverifier/ + jarverifier/,\ + security/src/ output.. = bin/ jre.compilation.profile = J2SE-1.4 diff --git a/bundles/org.eclipse.osgi/core/adaptor/org/eclipse/osgi/service/resolver/DisabledInfo.java b/bundles/org.eclipse.osgi/core/adaptor/org/eclipse/osgi/service/resolver/DisabledInfo.java index 3e730075e..dbe1274a3 100644 --- a/bundles/org.eclipse.osgi/core/adaptor/org/eclipse/osgi/service/resolver/DisabledInfo.java +++ b/bundles/org.eclipse.osgi/core/adaptor/org/eclipse/osgi/service/resolver/DisabledInfo.java @@ -23,8 +23,7 @@ package org.eclipse.osgi.service.resolver; * </p> *@see State */ -public class DisabledInfo { - +public final class DisabledInfo { private final String policyName; private final String message; private final BundleDescription bundle; diff --git a/bundles/org.eclipse.osgi/core/adaptor/org/eclipse/osgi/service/resolver/State.java b/bundles/org.eclipse.osgi/core/adaptor/org/eclipse/osgi/service/resolver/State.java index 382903e4f..845888bcd 100644 --- a/bundles/org.eclipse.osgi/core/adaptor/org/eclipse/osgi/service/resolver/State.java +++ b/bundles/org.eclipse.osgi/core/adaptor/org/eclipse/osgi/service/resolver/State.java @@ -470,6 +470,7 @@ public interface State { * in the system. Use {@link #getDisabledInfos(BundleDescription)} to interrogate the reason that * each bundle is disabled. * @return the array of disabled bundles. An empty array is returned if no bundles are disabled. + * @see DisabledInfo */ public BundleDescription[] getDisabledBundles(); diff --git a/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/internal/baseadaptor/BaseStorage.java b/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/internal/baseadaptor/BaseStorage.java index 60ba6eedb..5a30f9e7b 100644 --- a/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/internal/baseadaptor/BaseStorage.java +++ b/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/internal/baseadaptor/BaseStorage.java @@ -1095,11 +1095,15 @@ public class BaseStorage implements SynchronousBundleListener { BundleDescription newDescription = null; switch (type) { case BundleEvent.UPDATED : - oldDescription = systemState.removeBundle(bundleData.getBundleID()); // fall through to INSTALLED case BundleEvent.INSTALLED : + if (type == BundleEvent.UPDATED) + oldDescription = systemState.getBundle(bundleData.getBundleID()); newDescription = stateManager.getFactory().createBundleDescription(systemState, bundleData.getManifest(), bundleData.getLocation(), bundleData.getBundleID()); - systemState.addBundle(newDescription); + if (oldDescription == null) + systemState.addBundle(newDescription); + else + systemState.updateBundle(newDescription); break; case BundleEvent.UNINSTALLED : systemState.removeBundle(bundleData.getBundleID()); @@ -1114,10 +1118,10 @@ public class BaseStorage implements SynchronousBundleListener { verified = true; } finally { if (!verified) { - if (newDescription != null) - systemState.removeBundle(newDescription); if (oldDescription != null) - systemState.addBundle(oldDescription); + systemState.updateBundle(oldDescription); + else + systemState.removeBundle(newDescription); } } } diff --git a/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/internal/baseadaptor/DefaultClassLoader.java b/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/internal/baseadaptor/DefaultClassLoader.java index 61770b076..f7cc29304 100644 --- a/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/internal/baseadaptor/DefaultClassLoader.java +++ b/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/internal/baseadaptor/DefaultClassLoader.java @@ -24,8 +24,9 @@ import org.eclipse.osgi.baseadaptor.loader.*; import org.eclipse.osgi.framework.adaptor.BundleData; import org.eclipse.osgi.framework.adaptor.ClassLoaderDelegate; import org.eclipse.osgi.framework.debug.Debug; -import org.eclipse.osgi.internal.provisional.verifier.CertificateChain; -import org.eclipse.osgi.internal.provisional.verifier.CertificateVerifier; +import org.eclipse.osgi.framework.internal.core.FrameworkProperties; +import org.eclipse.osgi.signedcontent.SignedContent; +import org.eclipse.osgi.signedcontent.SignerInfo; /** * The default implemention of <code>BaseClassLoader</code>. This implementation extends @@ -38,7 +39,10 @@ public class DefaultClassLoader extends ClassLoader implements BaseClassLoader { * A PermissionCollection for AllPermissions; shared across all ProtectionDomains when security is disabled */ protected static final PermissionCollection ALLPERMISSIONS; + private final static String CLASS_CERTIFICATE_SUPPORT = "osgi.support.class.certificate"; //$NON-NLS-1$ + private static final boolean CLASS_CERTIFICATE; static { + CLASS_CERTIFICATE = Boolean.valueOf(FrameworkProperties.getProperty(CLASS_CERTIFICATE_SUPPORT, "true")).booleanValue(); //$NON-NLS-1$ AllPermission allPerm = new AllPermission(); ALLPERMISSIONS = allPerm.newPermissionCollection(); if (ALLPERMISSIONS != null) @@ -219,9 +223,11 @@ public class DefaultClassLoader extends ClassLoader implements BaseClassLoader { // this is done just incase someone sets the security manager later permissions = ALLPERMISSIONS; Certificate[] certs = null; - if (bundlefile instanceof CertificateVerifier) { - CertificateChain[] chains = ((CertificateVerifier) bundlefile).getChains(); - certs = chains == null || chains.length == 0 ? null : chains[0].getCertificates(); + SignedContent signedContent = (bundlefile instanceof SignedContent) ? (SignedContent) bundlefile : null; + if (CLASS_CERTIFICATE && signedContent != null && signedContent.isSigned()) { + SignerInfo[] signers = signedContent.getSignerInfos(); + if (signers.length > 0) + certs = signers[0].getCertificateChain(); } return new ProtectionDomain(new CodeSource(bundlefile.getBaseFile().toURL(), certs), permissions); } catch (MalformedURLException e) { diff --git a/bundles/org.eclipse.osgi/hookconfigurators.properties b/bundles/org.eclipse.osgi/hookconfigurators.properties index b8f618700..bb97ef345 100644 --- a/bundles/org.eclipse.osgi/hookconfigurators.properties +++ b/bundles/org.eclipse.osgi/hookconfigurators.properties @@ -18,5 +18,5 @@ hook.configurators= \ org.eclipse.core.runtime.internal.adaptor.EclipseClassLoadingHook,\ org.eclipse.core.runtime.internal.adaptor.EclipseLazyStarter,\ org.eclipse.core.runtime.internal.stats.StatsManager,\ - org.eclipse.osgi.internal.verifier.SignedBundleHook + org.eclipse.osgi.internal.signedcontent.SignedBundleHook builtin.hooks = true diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/DefaultTrustAuthority.java b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/DefaultTrustAuthority.java deleted file mode 100644 index 6df72728d..000000000 --- a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/DefaultTrustAuthority.java +++ /dev/null @@ -1,48 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2006 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.osgi.internal.verifier; - -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import org.eclipse.osgi.internal.provisional.verifier.CertificateTrustAuthority; -import org.eclipse.osgi.util.NLS; - -public class DefaultTrustAuthority implements CertificateTrustAuthority { - // the KeyStores that we determine trust from. This only gets intialized the - // supportFlags include the VERIFY_TRUST flag - private KeyStores keyStores; - // used to indicate if we should check the KeyStores object for trust. - private int supportFlags; - public DefaultTrustAuthority(int supportFlags) { - this.supportFlags = supportFlags; - } - public void checkTrust(Certificate[] certChain) throws CertificateException { - if (certChain == null || certChain.length == 0) { - throw new IllegalArgumentException(JarVerifierMessages.Cert_Verifier_Illegal_Args); - } - KeyStores stores = getKeyStores(); - // stores == null when the supportFlags includes the VERIFY_TRUST flag - if (stores != null && !stores.isTrusted(certChain[certChain.length - 1])) { - throw new CertificateException(NLS.bind(JarVerifierMessages.Cert_Verifier_Not_Trusted, new String[] {certChain[0].toString()})); - } - } - - private synchronized KeyStores getKeyStores() { - if (((supportFlags & SignedBundleHook.VERIFY_TRUST) == 0) || keyStores != null) - return keyStores; - keyStores = new KeyStores(); - return keyStores; - } - public void addTrusted(Certificate[] certs) throws CertificateException { - // do nothing for now ... - } - -} diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/JarVerifierMessages.properties b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/JarVerifierMessages.properties deleted file mode 100644 index 95e1c5186..000000000 --- a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/JarVerifierMessages.properties +++ /dev/null @@ -1,46 +0,0 @@ -############################################################################### -# Copyright (c) 2006 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 -############################################################################### - -file_is_removed_from_jar = A file {0} has been removed from the bundle: {1} -jar_is_tampered = The Jar {0} file has been tampered because a file has been removed! - -Signature_Not_Verify = Signature doesn't verify! - - -Signature_Not_Verify_1 = The signature cannot be verified for this signer {0} in this bundle: {1} - -# Jar file tampered error messages -File_In_Jar_Is_Tampered = The {0} in the {1} has been tampered! -Security_File_Is_Tampered = Either the manfiest file or the signature file has been tampered in this bundle: {0} - -# Jar file parsing -SF_File_Parsing_Error = Error occurs parsing the .SF file to find out the digest algorithm in this bundle: {0} - -# PKCS7 parsing errors -PKCS7_SignerInfo_Version_Not_Supported = The SignerInfo version other than 1 is not supported! -PKCS7_Cert_Excep = CertificateException occurs when parsing the signature block file from this bundle: {0} file!\n {1} -PKCS7_No_Such_Algorithm = NoSuchAlgorithmException occurs when parsing the signature block file from this bundle: {0} + " file!\n {1} -PKCS7_Parse_Signing_Time = CertificateException is thrown when parsing signing time -PKCS7_Parse_Signing_Time_1 = timestamp in the pkcs7file cannot be parsed properly! - -# Validate chain of certificate -Validate_Certs_Certificate_Exception = CertificateException is being thrown during validation of certificates! \n {0} - -# Security Exceptions -Algorithm_Not_Supported = {0} digest algorithm is not supported! -No_Such_Algorithm_Excep = NoSuchAlgorithmException is being thrown \n {0} -No_Such_Provider_Excep = NoSuchProviderException is being thrown! \n {0} -Invalid_Key_Exception = InvalidKeyException is being thrown during verification of {0} file!\n {1} - -# Certs Trust Determination -Cert_Verifier_Illegal_Args = certChain parmeter is either null or zero-length! -Cert_Verifier_Not_Trusted = The following certificate is not trusted: {0} -Cert_Verifier_Add_Certs = Error occurs when adding certs to the certs trust store! diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/KeyStores.java b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/KeyStores.java deleted file mode 100644 index e07a66867..000000000 --- a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/KeyStores.java +++ /dev/null @@ -1,194 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2006 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.osgi.internal.verifier; - -import java.io.*; -import java.net.*; -import java.security.*; -import java.security.cert.Certificate; -import java.util.*; -import org.eclipse.osgi.framework.internal.core.FrameworkProperties; -import org.eclipse.osgi.framework.log.FrameworkLogEntry; - -/** - * Class to manage the different KeyStores we should check for certificates of - * Signed JAR - */ -public class KeyStores { - /** - * java.policy files properties of the java.security file - */ - private static final String JAVA_POLICY_URL = "policy.url."; //$NON-NLS-1$ - /** - * Default keystore type in java.security file - */ - private static final String DEFAULT_KEYSTORE_TYPE = "keystore.type"; //$NON-NLS-1$ - /** - * List of KeyStores - */ - private List /* of Keystore */keyStores; - - /** - * KeyStores constructor comment. - */ - public KeyStores() { - super(); - initializeDefaultKeyStores(); - } - - private void processKeyStore(String urlSpec, String type, URL rootURL) { - if (type == null) - type = KeyStore.getDefaultType(); - InputStream in = null; - try { - URL url; - try { - url = new URL(urlSpec); - } catch (MalformedURLException mue) { - url = new URL(rootURL, urlSpec); - } - KeyStore ks = KeyStore.getInstance(type); - try { - in = url.openStream(); - } catch (IOException ioe) { - // ignore this; the file probably does not exist - } - if (in != null) { - ks.load(in, null); - keyStores.add(ks); - } - } catch (Exception e) { - SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.WARNING, e); - } finally { - if (in != null) - try { - in.close(); - } catch (IOException e){ - // do nothing - } - } - } - - /** - * populate the list of Keystores should be done with Dialog with - * Cancel/Skip button if the connection to the URL is down... - */ - private void initializeDefaultKeyStores() { - keyStores = new ArrayList(5); - // get JRE cacerts - String defaultType = Security.getProperty(DEFAULT_KEYSTORE_TYPE); - String urlSpec = "file:" + FrameworkProperties.getProperty("java.home") + File.separator + "lib" + File.separator + "security" + File.separator + "cacerts"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ - processKeyStore(urlSpec, defaultType, null); - - // get java.home .keystore - urlSpec = "file:" + FrameworkProperties.getProperty("user.home") + File.separator + ".keystore"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - processKeyStore(urlSpec, defaultType, null); - - // get osgi.framework.keystore keystore - urlSpec = FrameworkProperties.getProperty("osgi.framework.keystore"); //$NON-NLS-1$ - if (urlSpec != null) - processKeyStore(urlSpec, defaultType, null); - - // get KeyStores from policy files... - int index = 1; - String java_policy = Security.getProperty(JAVA_POLICY_URL + index); - while (java_policy != null) { - // retrieve keystore url from java.policy - // also retrieve keystore type - processKeystoreFromLocation(java_policy); - index++; - java_policy = Security.getProperty(JAVA_POLICY_URL + index); - } - } - - /** - * retrieve the keystore from java.policy file - */ - private void processKeystoreFromLocation(String location) { - InputStream in = null; - char[] buff = new char[4096]; - int indexOf$ = location.indexOf("${"); //$NON-NLS-1$ - int indexOfCurly = location.indexOf('}', indexOf$); - if (indexOf$ != -1 && indexOfCurly != -1) { - String prop = FrameworkProperties.getProperty(location.substring(indexOf$ + 2, indexOfCurly)); - String location2 = location.substring(0, indexOf$); - location2 += prop; - location2 += location.substring(indexOfCurly + 1); - location = location2; - } - try { - URL url = new URL(location); - //System.out.println("getKeystoreFromLocation: location is: " +location); - in = url.openStream(); - Reader reader = new InputStreamReader(in); - int result = reader.read(buff); - StringBuffer contentBuff = new StringBuffer(); - while (result != -1) { - contentBuff.append(buff, 0, result); - result = reader.read(buff); - } - if (contentBuff.length() > 0) { - String content = new String(contentBuff.toString()); - int indexOfKeystore = content.indexOf("keystore"); //$NON-NLS-1$ - if (indexOfKeystore != -1) { - int indexOfSemiColumn = content.indexOf(';', indexOfKeystore); - processKeystoreFromString(content.substring(indexOfKeystore, indexOfSemiColumn), url); - return; - } - } - } catch (MalformedURLException e) { - SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.WARNING, e); - } catch (IOException e) { - // do nothing it is likely that the file does not exist - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - // do nothing - } - } - } - } - - /** - * retrieve the keystore from java.policy file - */ - private void processKeystoreFromString(String content, URL rootURL) { - String keyStoreType = null; - int indexOfSpace = content.indexOf(' '); - if (indexOfSpace == -1) - return; - int secondSpace = content.lastIndexOf(','); - if (secondSpace == -1) { - secondSpace = content.length(); - } else { - keyStoreType = content.substring(secondSpace + 1, content.length()).trim(); - } - processKeyStore(content.substring(indexOfSpace, secondSpace), keyStoreType, rootURL); - } - - public boolean isTrusted(Certificate cert) { - Iterator it = keyStores.iterator(); - while (it.hasNext()) { - KeyStore ks = (KeyStore) it.next(); - try { - if (ks.getCertificateAlias(cert) != null) { - return true; - } - } catch (KeyStoreException e) { - SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.WARNING, e); - } - } - return false; - } -}
\ No newline at end of file diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/PKCS7DateParser.java b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/PKCS7DateParser.java deleted file mode 100644 index 86ce83323..000000000 --- a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/PKCS7DateParser.java +++ /dev/null @@ -1,65 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2006, 2007 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.osgi.internal.verifier; - -import java.io.IOException; -import java.security.*; -import java.security.cert.CertificateException; -import java.util.*; -import org.eclipse.osgi.framework.log.FrameworkLogEntry; - -public class PKCS7DateParser { - - static Date parseDate(PKCS7Processor pkcs7Processor) throws IOException { - return hasTimeStamp(pkcs7Processor); - } - - private static Date hasTimeStamp(PKCS7Processor pkcs7) throws IOException { - Map unsignedAttrs = pkcs7.getUnsignedAttrs(); - if (unsignedAttrs != null) { - // get the timestamp constrcut - byte[] timeStampConstruct = retrieveTimeStampConstruct(unsignedAttrs); - - // there is a timestamp in the signer info - if (timeStampConstruct != null) { - - try { - PKCS7Processor timestampProcess = new PKCS7Processor(timeStampConstruct, 0, timeStampConstruct.length); - timestampProcess.validateCerts(); - pkcs7.setTSACertificates(timestampProcess.getCertificates()); - return timestampProcess.getSigningTime(); - } catch (CertificateException e) { - SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); - throw new IOException(JarVerifierMessages.PKCS7_Parse_Signing_Time); - } catch (NoSuchAlgorithmException e) { - SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); - throw new SecurityException(JarVerifierMessages.No_Such_Algorithm_Excep); - } catch (InvalidKeyException e) { - throw new IOException("InvalidKeyException occurs when verifying the certs from tsa certificates: " + e.getMessage()); //$NON-NLS-1$ - } catch (SignatureException e) { - throw new IOException(JarVerifierMessages.Signature_Not_Verify); - } - - } - } - return null; - } - - private static byte[] retrieveTimeStampConstruct(Map unsignedAttrs) { - Set objIDs = unsignedAttrs.keySet(); - Iterator iter = objIDs.iterator(); - while (iter.hasNext()) { - int[] objID = (int[]) iter.next(); - if (Arrays.equals(JarVerifierConstant.TIMESTAMP_OID, objID)) { - return (byte[]) unsignedAttrs.get(objID); - } - } - return null; - } -} diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/SignedBundleFile.java b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/SignedBundleFile.java deleted file mode 100644 index eae205001..000000000 --- a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/SignedBundleFile.java +++ /dev/null @@ -1,779 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2006, 2007 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.osgi.internal.verifier; - -import java.io.*; -import java.net.URL; -import java.security.*; -import java.security.cert.*; -import java.util.*; -import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry; -import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile; -import org.eclipse.osgi.framework.log.FrameworkLogEntry; -import org.eclipse.osgi.internal.provisional.verifier.*; -import org.eclipse.osgi.util.NLS; - -/** - * This class wraps a Repository of classes and resources to check and enforce - * signatures. It requires full signing of the manifest by all signers. If no - * signatures are found, the classes and resources are retrieved without checks. - */ -public class SignedBundleFile extends BundleFile implements CertificateVerifier, JarVerifierConstant { - private static DefaultTrustAuthority trustAllAuthority = new DefaultTrustAuthority(0); - private BundleFile bundleFile; - CertificateChain[] chains; - - /** - * The key of the hashtable will be the name of the entry (type String). The - * value will be MessageDigest algorithm to use. - */ - Hashtable digests4entries; - /** - * The key of the hashtable will be the name of the entry (type String). The - * value will be byte[] which is an array of one MessageDigest result. - */ - Hashtable results4entries; - String manifestSHAResult = null; - String manifestMD5Result = null; - boolean certsInitialized = false; - - SignedBundleFile() { - // default constructor - } - - SignedBundleFile(CertificateChain[] chains, Hashtable digests4entries, Hashtable results4entries, String manifestMD5Result, String manifestSHAResult) { - this.chains = chains; - this.digests4entries = digests4entries; - this.results4entries = results4entries; - this.manifestMD5Result = manifestMD5Result; - this.manifestSHAResult = manifestSHAResult; - certsInitialized = true; - // isSigned = true; - } - - /** - * Verify the digest listed in each entry in the .SF file with corresponding section in the manifest - */ - private void verifyManifestAndSingatureFile(byte[] manifestBytes, byte[] sfBytes) { - - String sf = new String(sfBytes); - sf = stripContinuations(sf); - - // check if there -Digest-Manfiest: header in the file - int off = sf.indexOf(digestManifestSearch); - if (off != -1) { - int start = sf.lastIndexOf('\n', off); - String manfiestDigest = null; - if (start != -1) { - // Signature-Version has to start the file, so there - // should always be a newline at the start of - // Digest-Manifest - String digestName = sf.substring(start + 1, off); - if (digestName.equalsIgnoreCase(MD5_STR)) { - if (manifestMD5Result == null) - manifestMD5Result = calculateDigest(getMessageDigest(MD5_STR), manifestBytes); - manfiestDigest = manifestMD5Result; - } else if (digestName.equalsIgnoreCase(SHA1_STR)) { - if (manifestSHAResult == null) - manifestSHAResult = calculateDigest(getMessageDigest(SHA1_STR), manifestBytes); - manfiestDigest = manifestSHAResult; - } - off += digestManifestSearchLen; - - // find out the index of first '\n' after the -Digest-Manifest: - int nIndex = sf.indexOf('\n', off); - String digestValue = sf.substring(off, nIndex - 1); - - // check if the the computed digest value of manifest file equals to the digest value in the .sf file - if (!manfiestDigest.equals(digestValue)) { - Exception e = new SecurityException(NLS.bind(JarVerifierMessages.Security_File_Is_Tampered, new String[] {bundleFile.getBaseFile().toString()})); - SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); - throw (SecurityException) e; - } - } - } - - } - - /** - * Read the .SF file abd assuming that same digest algorithm will be used through out the whole - * .SF file. That digest algorithm name in the last entry will be returned. - * - * @param SFBuf a .SF file in bytes - * @return the digest algorithm name used in the .SF file - */ - private String getDigAlgFromSF(byte SFBuf[]) { - String rtvValue = null; - - // need to make a string from the MF file data bytes - String mfStr = new String(SFBuf); - String entryStr = null; - - // start parsing each entry in the MF String - int entryStartOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME); - int length = mfStr.length(); - - while ((entryStartOffset != -1) && (entryStartOffset < length)) { - - // get the start of the next 'entry', i.e. the end of this entry - int entryEndOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME, entryStartOffset + 1); - if (entryEndOffset == -1) { - // if there is no next entry, then the end of the string - // is the end of this entry - entryEndOffset = mfStr.length(); - } - - // get the string for this entry only, since the entryStartOffset - // points to the '\n' befor the 'Name: ' we increase it by 1 - // this is guaranteed to not go past end-of-string and be less - // then entryEndOffset. - entryStr = mfStr.substring(entryStartOffset + 1, entryEndOffset); - entryStr = stripContinuations(entryStr); - break; - } - - if (entryStr != null) { - // process the entry to retrieve the digest algorith name - String digestLine = getDigestLine(entryStr, null); - - // throw parsing - rtvValue = getMessageDigestName(digestLine); - } - - return rtvValue; - } - - /** - * @param mfBuf the data from an MF file of a JAR archive - * - * This method will populate the "digest type & result" hashtables - * with whatever entries it can correctly parse from the MF file and with the same digest algorithm. - * it will 'skip' incorrect entries (TODO: should the correct behavior - * be to throw an exception, or return an error code?)... - * - * - */ - private void populateManifest(byte mfBuf[], String digAlg) { - // need to make a string from the MF file data bytes - String mfStr = new String(mfBuf); - - // start parsing each entry in the MF String - int entryStartOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME); - int length = mfStr.length(); - - while ((entryStartOffset != -1) && (entryStartOffset < length)) { - - // get the start of the next 'entry', i.e. the end of this entry - int entryEndOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME, entryStartOffset + 1); - if (entryEndOffset == -1) { - // if there is no next entry, then the end of the string - // is the end of this entry - entryEndOffset = mfStr.length(); - } - - // get the string for this entry only, since the entryStartOffset - // points to the '\n' befor the 'Name: ' we increase it by 1 - // this is guaranteed to not go past end-of-string and be less - // then entryEndOffset. - String entryStr = mfStr.substring(entryStartOffset + 1, entryEndOffset); - entryStr = stripContinuations(entryStr); - - // entry points to the start of the next 'entry' - String entryName = getEntryFileName(entryStr); - - // if we could retrieve an entry name, then we will extract - // digest type list, and the digest value list - if (entryName != null) { - - String aDigestLine = getDigestLine(entryStr, digAlg); - - if (aDigestLine != null) { - String msgDigestAlgorithm = getDigestAlgorithmFromString(aDigestLine); - byte digestResultsList[] = getDigestResultsList(aDigestLine); - - // - // only insert this entry into the table if its - // "well-formed", - // i.e. only if we could extract its name, digest types, and - // digest-results - // - // sanity check, if the 2 lists are non-null, then their - // counts must match - // - // if ((msgDigestObj != null) && (digestResultsList != null) - // && (1 != digestResultsList.length)) { - // throw new RuntimeException( - // "Errors occurs when parsing the manifest file stream!"); //$NON-NLS-1$ - // } - - if (digests4entries == null) { - digests4entries = new Hashtable(10); - results4entries = new Hashtable(10); - } - // TODO throw exception if duplicate entry?? - // no need, in theory, there is impossible to have two - // duplicate entries unless the manifest file - // is tampered - if (!digests4entries.contains(entryName)) { - digests4entries.put(entryName, msgDigestAlgorithm); - results4entries.put(entryName, digestResultsList); - } - - } // could get lines of digest entries in this MF file entry - - } // could retrieve entry name - - // increment the offset to the ending entry... - entryStartOffset = entryEndOffset; - } - } - - private String stripContinuations(String entry) { - if (entry.indexOf("\n ") < 0) //$NON-NLS-1$ - return entry; - StringBuffer buffer = new StringBuffer(entry.length()); - int cont = entry.indexOf("\n "); //$NON-NLS-1$ - int start = 0; - while (cont >= 0) { - buffer.append(entry.substring(start, cont - 1)); - start = cont + 2; - cont = cont + 2 < entry.length() ? entry.indexOf("\n ", cont + 2) : -1; //$NON-NLS-1$ - } - // get the last one continuation - if (start < entry.length()) - buffer.append(entry.substring(start)); - return buffer.toString(); - } - - private String getEntryFileName(String manifestEntry) { - // get the beginning of the name - int nameStart = manifestEntry.indexOf(MF_ENTRY_NAME); - if (nameStart == -1) { - return null; - } - // check where the name ends - int nameEnd = manifestEntry.indexOf('\n', nameStart); - if (nameEnd == -1) { - return null; - } - // if there is a '\r' before the '\n', then we'll strip it - if (manifestEntry.charAt(nameEnd - 1) == '\r') { - nameEnd--; - } - // get to the beginning of the actual name... - nameStart += MF_ENTRY_NAME.length(); - if (nameStart >= nameEnd) { - return null; - } - return manifestEntry.substring(nameStart, nameEnd); - } - - /** - * - * @param manifestEntry contains a single MF file entry of the format - * "Name: foo" - * "MD5-Digest: [base64 encoded MD5 digest data]" - * "SHA1-Digest: [base64 encoded SHA1 digest dat]" - * - * @param desireDigestAlg a string representing the desire digest value to be returned if there are - * multiple digest lines. - * If this value is null, return whatever digest value is in the entry. - * - * @return this function returns a digest line based on the desire digest algorithm value - * (since only MD5 and SHA1 are recognized here), - * or a 'null' will be returned if none of the digest algorithms - * were recognized. - */ - private String getDigestLine(String manifestEntry, String desireDigestAlg) { - String rtvValue = null; - - // find the first digest line - int indexDigest = manifestEntry.indexOf(MF_DIGEST_PART); - // if we didn't find any digests at all, then we are done - if (indexDigest == -1) - return null; - - // while we continue to find digest entries - // note: in the following loop we bail if any of the lines - // look malformed... - while (indexDigest != -1) { - // see where this digest line begins (look to left) - int indexStart = manifestEntry.lastIndexOf('\n', indexDigest); - if (indexStart == -1) - return null; - // see where it ends (look to right) - int indexEnd = manifestEntry.indexOf('\n', indexDigest); - if (indexEnd == -1) - return null; - // strip off ending '\r', if any - int indexEndToUse = indexEnd; - if (manifestEntry.charAt(indexEndToUse - 1) == '\r') - indexEndToUse--; - // indexStart points to the '\n' before this digest line - int indexStartToUse = indexStart + 1; - if (indexStartToUse >= indexEndToUse) - return null; - - // now this may be a valid digest line, parse it a bit more - // to see if this is a preferred digest algorithm - String digestLine = manifestEntry.substring(indexStartToUse, indexEndToUse); - String digAlg = getMessageDigestName(digestLine); - if (desireDigestAlg != null && desireDigestAlg.equalsIgnoreCase(digAlg)) { - rtvValue = digestLine; - break; - } - - // desireDigestAlg is null, always return the - rtvValue = digestLine; - - // iterate to next digest line in this entry - indexDigest = manifestEntry.indexOf(MF_DIGEST_PART, indexEnd); - } - - // if we couldn't find any digest lines, then we are done - return rtvValue; - } - - private String getDigestAlgorithmFromString(String digestLines) { - if (digestLines != null) { - // String sDigestLine = digestLines[i]; - int indexDigest = digestLines.indexOf(MF_DIGEST_PART); - String sDigestAlgType = digestLines.substring(0, indexDigest); - if (sDigestAlgType.equalsIgnoreCase(MD5_STR)) { - // remember the "algorithm type" - return MD5_STR; - } else if (sDigestAlgType.equalsIgnoreCase(SHA1_STR)) { - // remember the "algorithm type" object - return SHA1_STR; - } else { - // unknown algorithm type, we will stop processing this entry - // break; - throw new SecurityException(NLS.bind(JarVerifierMessages.Algorithm_Not_Supported, sDigestAlgType)); - } - } - return null; - } - - /** - * Return the Message Digest name - * - * @param digLine the message digest line is in the following format. That is in the - * following format: - * DIGEST_NAME-digest: digest value - * @return a string representing a message digest. - */ - private String getMessageDigestName(String digLine) { - String rtvValue = null; - if (digLine != null) { - int indexDigest = digLine.indexOf(MF_DIGEST_PART); - if (indexDigest != -1) { - rtvValue = digLine.substring(0, indexDigest); - } - } - return rtvValue; - } - - private byte[] getDigestResultsList(String digestLines) { - byte resultsList[] = null; - if (digestLines != null) { - // for each digest-line retrieve the digest result - // for (int i = 0; i < digestLines.length; i++) { - String sDigestLine = digestLines; - int indexDigest = sDigestLine.indexOf(MF_DIGEST_PART); - indexDigest += MF_DIGEST_PART.length(); - // if there is no data to extract for this digest value - // then we will fail... - if (indexDigest >= sDigestLine.length()) { - resultsList = null; - // break; - } - // now attempt to base64 decode the result - String sResult = sDigestLine.substring(indexDigest); - try { - resultsList = Base64.decode(sResult.getBytes()); - } catch (Throwable t) { - // malformed digest result, no longer processing this entry - resultsList = null; - } - } - return resultsList; - } - - static private int readFully(InputStream is, byte b[]) throws IOException { - int count = b.length; - int offset = 0; - int rc; - while ((rc = is.read(b, offset, count)) > 0) { - count -= rc; - offset += rc; - } - return offset; - } - - byte[] readIntoArray(BundleEntry be) throws IOException { - int size = (int) be.getSize(); - InputStream is = be.getInputStream(); - byte b[] = new byte[size]; - int rc = readFully(is, b); - if (rc != size) { - throw new IOException("Couldn't read all of " + be.getName() + ": " + rc + " != " + size); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - } - return b; - } - - /** - * Sets the BundleFile for this singed bundle. It will extract - * signatures and digests from the bundle file and validate input streams - * before using them from the bundle file. - * - * @param bundleFile the BundleFile to extract elements from. - * @param supportFlags the support flags for this signed bundle - * @throws IOException - */ - void setBundleFile(BundleFile bundleFile, int supportFlags) throws IOException { - this.bundleFile = bundleFile; - if (certsInitialized) - return; - BundleEntry be = bundleFile.getEntry(META_INF_MANIFEST_MF); - if (be == null) - return; - - // read all the signature block file names into a list - Enumeration en = bundleFile.getEntryPaths(META_INF); - List signers = new ArrayList(2); - while (en.hasMoreElements()) { - String name = (String) en.nextElement(); - if ((name.endsWith(DOT_DSA) || name.endsWith(DOT_RSA)) && name.indexOf('/') == name.lastIndexOf('/')) - signers.add(name); - } - - // this means the jar is not signed - if (signers.size() == 0) - return; - - byte manifestBytes[] = readIntoArray(be); - // determine the signer to be used for later - String latestSigner = findLatestSigner(bundleFile, signers); - // process the signers - try { - ArrayList processors = new ArrayList(signers.size()); - Iterator iSigners = signers.iterator(); - for (int i = 0; iSigners.hasNext(); i++) { - String signer = (String) iSigners.next(); - PKCS7Processor processor = processSigner(bundleFile, manifestBytes, signer, latestSigner, supportFlags); - boolean error = false; - try { - processor.validateCerts(); - determineCertsTrust(processor, supportFlags); - } catch (CertificateExpiredException e) { - // ignore - } catch (CertificateNotYetValidException e) { - // ignore - } catch (InvalidKeyException e) { - error = true; - } - if (!error) { - // make sure the latestSigner is the first in the list - if (signer == latestSigner) - processors.add(0, processor); - else - processors.add(processor); - } - } - chains = processors.size() == 0 ? null : (CertificateChain[]) processors.toArray(new CertificateChain[processors.size()]); - } catch (SignatureException e) { - throw new SecurityException(NLS.bind(JarVerifierMessages.Signature_Not_Verify_1, new String[] {latestSigner, bundleFile.toString()})); - } - } - - private void determineCertsTrust(PKCS7Processor signerPKCS7, int supportFlags) { - CertificateTrustAuthority trustAuthority; - if ((supportFlags & SignedBundleHook.VERIFY_TRUST) != 0) - trustAuthority = SignedBundleHook.getTrustAuthority(); - else - trustAuthority = trustAllAuthority; - if (trustAuthority != null) - signerPKCS7.determineTrust(trustAuthority); - } - - private PKCS7Processor processSigner(BundleFile bf, byte[] manifestBytes, String signer, String latestSigner, int supportFlags) throws IOException, SignatureException { - BundleEntry be = bf.getEntry(signer); - byte pkcs7Bytes[] = readIntoArray(be); - int dotIndex = signer.lastIndexOf('.'); - be = bf.getEntry(signer.substring(0, dotIndex) + DOT_SF); - byte sfBytes[] = readIntoArray(be); - - // Step 1, verify the .SF file is signed by the private key that corresponds to the public key - // in the .RSA/.DSA file - PKCS7Processor chain = null; - try { - - chain = new PKCS7Processor(pkcs7Bytes, 0, pkcs7Bytes.length); - - // call the Step 1 in the Jar File Verification algorithm - chain.verifySFSignature(sfBytes, 0, sfBytes.length); - - // algorithm used - String digAlg = getDigAlgFromSF(sfBytes); - if (digAlg == null) { - throw new SecurityException(NLS.bind(JarVerifierMessages.SF_File_Parsing_Error, new String[] {bf.toString()})); - } - // populate the two digest hashtable instance variable based on the used Message Digest algorithm - if (latestSigner == signer) { // only do this if it is the latest signer - - // Process the Step 2 in the Jar File Verification algorithm - // Get the manifest out of the signature file and make sure - // it matches MANIFEST.MF - verifyManifestAndSingatureFile(manifestBytes, sfBytes); - - // only populate the manifests if we are verifying content at runtime - if ((supportFlags & SignedBundleHook.VERIFY_RUNTIME) != 0) - populateManifest(manifestBytes, digAlg); - } - - // process the Step 3 - // read each file in the JAR file that has an entry in the .SF file. - // also determine the - // verifySFEntriesDigest(bf, sfBytes, manifestBytes); - } catch (InvalidKeyException e) { - SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); - throw new SecurityException(NLS.bind(JarVerifierMessages.Invalid_Key_Exception, new String[] {bf.getBaseFile().toString(), e.getMessage()})); - } catch (CertificateException e) { - SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); - throw new SecurityException(NLS.bind(JarVerifierMessages.PKCS7_Cert_Excep, new String[] {bf.getBaseFile().toString(), e.getMessage()})); - } catch (NoSuchAlgorithmException e) { - SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); - throw new SecurityException(NLS.bind(JarVerifierMessages.PKCS7_No_Such_Algorithm, new String[] {bf.getBaseFile().toString(), e.getMessage()})); - } - - return chain; - } - - private String findLatestSigner(BundleFile bf, List names) { - String result = null; - long latestTime = Long.MIN_VALUE; - for (Iterator iNames = names.iterator(); iNames.hasNext();) { - String name = (String) iNames.next(); - BundleEntry entry = bf.getEntry(name); - if (entry.getTime() > latestTime) { - result = name; - latestTime = entry.getTime(); - } - } - return result; - } - - /** - * Returns the Base64 encoded digest of the passed set of bytes. - */ - private String calculateDigest(MessageDigest digest, byte[] bytes) { - return new String(Base64.encode(digest.digest(bytes))); - } - - public File getFile(String path, boolean nativeCode) { - return bundleFile.getFile(path, nativeCode); - } - - public BundleEntry getEntry(String path) { - // strip off leading slashes so we can ensure the path matches the one provided in the manifest. - if (path.length() > 0 && path.charAt(0) == '/') - path = path.substring(1); - BundleEntry be = bundleFile.getEntry(path); - if (digests4entries == null) - return be; - if (be == null) { - if (digests4entries.get(path) == null) - return null; - throw new SecurityException(NLS.bind(JarVerifierMessages.file_is_removed_from_jar, getBaseFile().toString(), path)); - } - if (be.getName().startsWith(META_INF)) - return be; - if (!isSigned()) - // If there is no signatures, we just return the regular bundle entry - return be; - return new SignedBundleEntry(be); - } - - public Enumeration getEntryPaths(String path) { - return bundleFile.getEntryPaths(path); - } - - public void close() throws IOException { - bundleFile.close(); - } - - public void open() throws IOException { - bundleFile.open(); - } - - public boolean containsDir(String dir) { - return bundleFile.containsDir(dir); - } - - boolean matchDNChain(String pattern) { - CertificateChain[] matchChains = getChains(); - for (int i = 0; i < matchChains.length; i++) - if (matchChains[i].isTrusted() && DNChainMatching.match(matchChains[i].getChain(), pattern)) - return true; - return false; - } - - public File getBaseFile() { - return bundleFile.getBaseFile(); - } - - class SignedBundleEntry extends BundleEntry { - BundleEntry nestedEntry; - - SignedBundleEntry(BundleEntry nestedEntry) { - this.nestedEntry = nestedEntry; - } - - public InputStream getInputStream() throws IOException { - String name = getName(); - String digest = digests4entries == null ? null : (String) digests4entries.get(name); - if (digest == null) - // the digest does not exist; this must be a corrupted jar - throw new IOException("Corrupted file: the digest does not exist for the file " + name); //$NON-NLS-1$ - byte results[] = (byte[]) results4entries.get(name); - // TODO ELI: it is probbaly best to decode the value of digest into Base64 base. This will only optimize the bad jar case. - return new DigestedInputStream(nestedEntry.getInputStream(), digest, results, nestedEntry.getSize()); - } - - public long getSize() { - return nestedEntry.getSize(); - } - - public String getName() { - return nestedEntry.getName(); - } - - public long getTime() { - return nestedEntry.getTime(); - } - - public URL getLocalURL() { - return nestedEntry.getLocalURL(); - } - - public URL getFileURL() { - return nestedEntry.getFileURL(); - } - - } - - public void checkContent() throws CertificateException, CertificateExpiredException, SignatureException { - if (!isSigned() || digests4entries == null) - return; - - for (Enumeration entries = digests4entries.keys(); entries.hasMoreElements();) { - String name = (String) entries.nextElement(); - BundleEntry entry = getEntry(name); - if (entry == null) { - throw new SecurityException(NLS.bind(JarVerifierMessages.Jar_Is_Tampered, bundleFile.getBaseFile().getName())); - } - try { - entry.getBytes(); - } catch (IOException e) { - SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); - throw new SecurityException(NLS.bind(JarVerifierMessages.File_In_Jar_Is_Tampered, new String[] {name, bundleFile.getBaseFile().toString()})); - } - } - - // validate the certs chain and determine the trust - for (int i = 0; i < chains.length; i++) { - PKCS7Processor signerPKCS7 = (PKCS7Processor) chains[i]; - try { - signerPKCS7.validateCerts(); - } catch (InvalidKeyException e) { - throw new CertificateException(e.getMessage()); - } - // determine the trust of certificates - determineCertsTrust(signerPKCS7, SignedBundleHook.VERIFY_ALL); - } - } - - public String[] verifyContent() { - if (!isSigned() || digests4entries == null) - return EMPTY_STRING; - ArrayList corrupted = new ArrayList(0); // be optimistic - for (Enumeration entries = digests4entries.keys(); entries.hasMoreElements();) { - String name = (String) entries.nextElement(); - BundleEntry entry = getEntry(name); - if (entry == null) - corrupted.add(name); // we expected the entry to be here - else - try { - entry.getBytes(); - } catch (IOException e) { - // must be invalid - corrupted.add(name); - } - } - return corrupted.size() == 0 ? EMPTY_STRING : (String[]) corrupted.toArray(new String[corrupted.size()]); - } - - public CertificateChain[] getChains() { - if (!isSigned()) - return new CertificateChain[0]; - return chains; - } - - public boolean isSigned() { - return chains != null; - } - - static synchronized MessageDigest getMessageDigest(String algorithm) { - try { - return MessageDigest.getInstance(algorithm); - } catch (NoSuchAlgorithmException e) { - SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); - } - return null; - } - - // static public void main(String args[]) throws IOException { - // - // ZipBundleFile jf = new ZipBundleFile(new File(args[0]), null); - // SignedBundleFile sr = new SignedBundleFile(); - // - // sr.setBundleFile(jf); - // - // // read the first level directory entries - // Enumeration en = sr.getEntryPaths("/"); //$NON-NLS-1$ - // while (en.hasMoreElements()) { - // String filePath = (String) en.nextElement(); - // System.out.println("main(): " + filePath); //$NON-NLS-1$ - // - // // if this file is not a directory file - // // then we'll get its input stream for testing - // if (filePath.indexOf('/') == -1) { - // BundleEntry be = sr.getEntry(filePath); - // InputStream is = be.getInputStream(); - // is.skip(be.getSize()); - // is.read(); - // is.close(); - // } - // } - // - // if (!sr.isSigned()) { - // System.out.println("No signers present"); //$NON-NLS-1$ - // } else { - // CertificateChain[] chains = sr.getChains(); - // for (int i = 0; i < chains.length; i++) { - // System.out.println(chains[i].getChain()); - // } - // } - // - // System.out.println("Done"); //$NON-NLS-1$ - // } -} diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/SignedBundleHook.java b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/SignedBundleHook.java deleted file mode 100644 index 5bfece0b1..000000000 --- a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/SignedBundleHook.java +++ /dev/null @@ -1,221 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2006, 2007 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.osgi.internal.verifier; - -import java.io.File; -import java.io.IOException; -import java.net.URLConnection; -import java.security.Security; -import java.util.Hashtable; -import java.util.Properties; -import org.eclipse.osgi.baseadaptor.*; -import org.eclipse.osgi.baseadaptor.bundlefile.*; -import org.eclipse.osgi.baseadaptor.hooks.AdaptorHook; -import org.eclipse.osgi.baseadaptor.hooks.BundleFileWrapperFactoryHook; -import org.eclipse.osgi.framework.adaptor.BundleData; -import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor; -import org.eclipse.osgi.framework.internal.core.AbstractBundle; -import org.eclipse.osgi.framework.internal.core.FrameworkProperties; -import org.eclipse.osgi.framework.log.FrameworkLog; -import org.eclipse.osgi.framework.log.FrameworkLogEntry; -import org.eclipse.osgi.internal.provisional.verifier.*; -import org.eclipse.osgi.util.ManifestElement; -import org.osgi.framework.*; -import org.osgi.util.tracker.ServiceTracker; - -/** - * Implements signed bundle hook support for the framework - */ -public class SignedBundleHook implements AdaptorHook, BundleFileWrapperFactoryHook, HookConfigurator, CertificateVerifierFactory { - static final int VERIFY_CERTIFICATE = 0x01; - static final int VERIFY_TRUST = 0x02; - static final int VERIFY_RUNTIME = 0x04; - static final int VERIFY_ALL = VERIFY_CERTIFICATE | VERIFY_TRUST | VERIFY_RUNTIME; - private static String SUPPORT_CERTIFICATE = "certificate"; //$NON-NLS-1$ - private static String SUPPORT_TRUST = "trust"; //$NON-NLS-1$ - private static String SUPPORT_RUNTIME = "runtime"; //$NON-NLS-1$ - private static String SUPPORT_ALL = "all"; //$NON-NLS-1$ - private static String SUPPORT_TRUE = "true"; //$NON-NLS-1$ - private static ServiceTracker trustAuthorityTracker; - private static BaseAdaptor ADAPTOR; - private static String SIGNED_BUNDLE_SUPPORT = "osgi.support.signature.verify"; //$NON-NLS-1$ - private static int supportSignedBundles; - private static CertificateTrustAuthority trustAuthority = new DefaultTrustAuthority(VERIFY_ALL); - private ServiceRegistration certVerifierReg; - private ServiceRegistration trustAuthorityReg; - - public boolean matchDNChain(String pattern, String dnChain[]) { - boolean satisfied = false; - if (dnChain != null) { - for (int i = 0; i < dnChain.length; i++) - if (DNChainMatching.match(dnChain[i], pattern)) { - satisfied = true; - break; - } - } - return satisfied; - } - - public void initialize(BaseAdaptor adaptor) { - SignedBundleHook.ADAPTOR = adaptor; - } - - public void frameworkStart(BundleContext context) throws BundleException { - certVerifierReg = context.registerService(CertificateVerifierFactory.class.getName(), this, null); - Hashtable properties = new Hashtable(7); - properties.put(Constants.SERVICE_RANKING, new Integer(Integer.MIN_VALUE)); - properties.put(JarVerifierConstant.TRUST_AUTHORITY, JarVerifierConstant.DEFAULT_TRUST_AUTHORITY); - trustAuthorityReg = context.registerService(CertificateTrustAuthority.class.getName(), trustAuthority, properties); - } - - public void frameworkStop(BundleContext context) throws BundleException { - if (certVerifierReg != null) { - certVerifierReg.unregister(); - certVerifierReg = null; - } - if (trustAuthorityReg != null) { - trustAuthorityReg.unregister(); - trustAuthorityReg = null; - } - if (trustAuthorityTracker != null) { - trustAuthorityTracker.close(); - trustAuthorityTracker = null; - } - } - - public void frameworkStopping(BundleContext context) { - // do nothing - } - - public void addProperties(Properties properties) { - // do nothing - } - - public URLConnection mapLocationToURLConnection(String location) throws IOException { - return null; - } - - public void handleRuntimeError(Throwable error) { - // do nothing - } - - public FrameworkLog createFrameworkLog() { - return null; - } - - public BundleFile wrapBundleFile(BundleFile bundleFile, Object content, BaseData data, boolean base) { - try { - if (bundleFile != null) { - SignedStorageHook hook = (SignedStorageHook) data.getStorageHook(SignedStorageHook.KEY); - SignedBundleFile signedBaseFile; - if (base && hook != null) { - if (hook.signedBundleFile == null) - hook.signedBundleFile = new SignedBundleFile(); - signedBaseFile = hook.signedBundleFile; - } else - signedBaseFile = new SignedBundleFile(); - signedBaseFile.setBundleFile(bundleFile, supportSignedBundles); - if (signedBaseFile.isSigned()) // only use the signed file if there are certs - bundleFile = signedBaseFile; - else if (base) // if the base is not signed null out the hook.signedBundleFile - hook.signedBundleFile = null; - } - } catch (IOException e) { - // do nothing; its not your responsibility the error will be addressed later - } - return bundleFile; - } - - public void addHooks(HookRegistry hookRegistry) { - hookRegistry.addAdaptorHook(this); - String[] support = ManifestElement.getArrayFromList(FrameworkProperties.getProperty(SIGNED_BUNDLE_SUPPORT), ","); //$NON-NLS-1$ - for (int i = 0; i < support.length; i++) { - if (SUPPORT_CERTIFICATE.equals(support[i])) - supportSignedBundles |= VERIFY_CERTIFICATE; - else if (SUPPORT_TRUST.equals(support[i])) - supportSignedBundles |= VERIFY_CERTIFICATE | VERIFY_TRUST; - else if (SUPPORT_RUNTIME.equals(support[i])) - supportSignedBundles |= VERIFY_CERTIFICATE | VERIFY_RUNTIME; - else if (SUPPORT_TRUE.equals(support[i]) || SUPPORT_ALL.equals(support[i])) - supportSignedBundles |= VERIFY_ALL; - } - if ((supportSignedBundles & VERIFY_CERTIFICATE) != 0) { - hookRegistry.addStorageHook(new SignedStorageHook()); - hookRegistry.addBundleFileWrapperFactoryHook(this); - } - } - - public CertificateVerifier getVerifier(File content) throws IOException { - if (content == null) - throw new IllegalArgumentException("null content"); //$NON-NLS-1$ - BundleFile contentBundleFile; - if (content.isDirectory()) - contentBundleFile = new DirBundleFile(content); - else - contentBundleFile = new ZipBundleFile(content, null); - SignedBundleFile result = new SignedBundleFile(); - result.setBundleFile(contentBundleFile, VERIFY_ALL); - return result; - } - - public CertificateVerifier getVerifier(Bundle bundle) throws IOException { - BundleData data = ((AbstractBundle) bundle).getBundleData(); - if (!(data instanceof BaseData)) - throw new IllegalArgumentException("Invalid bundle object. No BaseData found."); //$NON-NLS-1$ - SignedStorageHook hook = (SignedStorageHook) ((BaseData)data).getStorageHook(SignedStorageHook.KEY); - SignedBundleFile signedBundle = hook != null ? hook.signedBundleFile : null; - if (signedBundle != null) - return signedBundle; // just reuse the verifier from the bundle file - return getVerifier(((BaseData)data).getBundleFile().getBaseFile()); // must create a new verifier using the raw file - } - - static void log(String msg, int severity, Throwable t) { - if (SignedBundleHook.ADAPTOR == null) { - System.err.println(msg); - t.printStackTrace(); - return; - } - FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, severity, 0, msg, 0, t, null); - SignedBundleHook.ADAPTOR.getFrameworkLog().log(entry); - } - - static BundleContext getContext() { - if (ADAPTOR == null) - return null; - return ADAPTOR.getContext(); - } - - static CertificateTrustAuthority getTrustAuthority() { - // read the certs chain security property and open the service tracker if not null - BundleContext context = SignedBundleHook.getContext(); - if (context == null) - return trustAuthority; - if (trustAuthorityTracker == null) { - // read the trust provider security property - String trustAuthorityProp = Security.getProperty(JarVerifierConstant.TRUST_AUTHORITY); - Filter filter = null; - if (trustAuthorityProp != null) - try { - filter = FrameworkUtil.createFilter("(&(" + Constants.OBJECTCLASS + "=" + CertificateTrustAuthority.class.getName() + ")(" + JarVerifierConstant.TRUST_AUTHORITY + "=" + trustAuthorityProp + "))"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$//$NON-NLS-5$ - } catch (InvalidSyntaxException e) { - e.printStackTrace(); - // do nothing just use no filter TODO we may want to log something - } - if (filter != null) { - trustAuthorityTracker = new ServiceTracker(context, filter, null); - } - else - trustAuthorityTracker = new ServiceTracker(context, CertificateTrustAuthority.class.getName(), null); - trustAuthorityTracker.open(); - } - return (CertificateTrustAuthority) trustAuthorityTracker.getService(); - } -} diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/SignedStorageHook.java b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/SignedStorageHook.java deleted file mode 100644 index d1beab8db..000000000 --- a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/SignedStorageHook.java +++ /dev/null @@ -1,235 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2006, 2007 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.osgi.internal.verifier; - -import java.io.*; -import java.security.cert.*; -import java.util.*; -import org.eclipse.osgi.baseadaptor.BaseData; -import org.eclipse.osgi.baseadaptor.hooks.StorageHook; -import org.eclipse.osgi.framework.util.KeyedElement; -import org.eclipse.osgi.internal.provisional.verifier.CertificateChain; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleException; - -public class SignedStorageHook implements StorageHook { - static final String KEY = SignedStorageHook.class.getName(); - static final int HASHCODE = KEY.hashCode(); - private static final int STORAGE_VERSION = 2; - private static ArrayList saveChainCache = new ArrayList(5); - private static long firstIDSaved = -1; - private static long lastIDSaved = -1; - private static ArrayList loadChainCache = new ArrayList(5); - private static long lastIDLoaded; - - private BaseData bundledata; - SignedBundleFile signedBundleFile; - - public int getStorageVersion() { - return STORAGE_VERSION; - } - - public StorageHook create(BaseData bundledata) throws BundleException { - SignedStorageHook hook = new SignedStorageHook(); - hook.bundledata = bundledata; - return hook; - } - - public void initialize(Dictionary manifest) throws BundleException { - // do nothing - } - - public StorageHook load(BaseData target, DataInputStream is) throws IOException { - if (lastIDLoaded > target.getBundleID()) - loadChainCache.clear(); - lastIDLoaded = target.getBundleID(); - SignedStorageHook hook = new SignedStorageHook(); - hook.bundledata = target; - boolean signed = is.readBoolean(); - if (!signed) - return hook; - int numChains = is.readInt(); - CertificateChain[] chains = new CertificateChain[numChains]; - for (int i = 0; i < numChains; i++) { - int chainIdx = is.readInt(); - if (chainIdx >= 0) { - chains[i] = (CertificateChain) loadChainCache.get(chainIdx); - if (chains[i] == null) - throw new IOException("Invalid chain cache."); //$NON-NLS-1$ - continue; - } - String chain = is.readUTF(); - - boolean trusted = is.readBoolean(); - int numCerts = is.readInt(); - byte[][] certsBytes = new byte[numCerts][]; - for (int j = 0; j < certsBytes.length; j++) { - int numBytes = is.readInt(); - certsBytes[j] = new byte[numBytes]; - is.readFully(certsBytes[j]); - } - long signingTime = is.readLong(); - try { - chains[i] = new PKCS7Processor(chain, trusted, certsBytes, signingTime); - } catch (CertificateException e) { - throw new IOException(e.getMessage()); - } - loadChainCache.add(chains[i]); - } - int numEntries = is.readInt(); - Hashtable digests = null; - Hashtable results = null; - if (numEntries >= 0) { - digests = new Hashtable(numEntries); - results = new Hashtable(numEntries); - for (int i = 0; i < numEntries; i++) { - String entry = is.readUTF(); - String md; - byte[] result; - if (is.readInt() == 0) - md = JarVerifierConstant.MD5_STR; - else - md = JarVerifierConstant.SHA1_STR; - result = new byte[is.readInt()]; - is.readFully(result); - digests.put(entry, md); - results.put(entry, result); - } - } - String md5Result = null; - String shaResult = null; - if (is.readBoolean()) - md5Result = is.readUTF(); - if (is.readBoolean()) - shaResult = is.readUTF(); - hook.signedBundleFile = new SignedBundleFile(chains, digests, results, md5Result, shaResult); - return hook; - } - - public void save(DataOutputStream os) throws IOException { - getFirstLastID(); - if (firstIDSaved == bundledata.getBundleID()) - saveChainCache.clear(); - if (lastIDSaved == bundledata.getBundleID()) - firstIDSaved = lastIDSaved = -1; - SignedBundleFile signedFile = signedBundleFile; - CertificateChain[] chains = null; - String md5Result = null; - String shaResult = null; - Hashtable digests = null; - Hashtable results = null; - if (signedFile != null) { - chains = signedFile.chains; - md5Result = signedFile.manifestMD5Result; - shaResult = signedFile.manifestSHAResult; - digests = signedFile.digests4entries; - results = signedFile.results4entries; - } - os.writeBoolean(chains != null); - if (chains == null) - return; - os.writeInt(chains.length); - for (int i = 0; i < chains.length; i++) { - int cacheIdx = saveChainCache.indexOf(chains[i]); - os.writeInt(cacheIdx); - if (cacheIdx >= 0) - continue; - saveChainCache.add(chains[i]); - os.writeUTF(chains[i].getChain()); - os.writeBoolean(chains[i].isTrusted()); - Certificate[] certs = chains[i].getCertificates(); - os.writeInt(certs == null ? 0 : certs.length); - if (certs != null) - for (int j = 0; j < certs.length; j++) { - byte[] certBytes; - try { - certBytes = certs[j].getEncoded(); - } catch (CertificateEncodingException e) { - throw new IOException(e.getMessage()); - } - os.writeInt(certBytes.length); - os.write(certBytes); - } - os.writeLong(chains[i].getSigningTime() != null ? chains[i].getSigningTime().getTime() : Long.MIN_VALUE); - } - if (digests == null) - os.writeInt(-1); - else { - os.writeInt(digests.size()); - for (Enumeration entries = digests.keys(); entries.hasMoreElements();) { - String entry = (String) entries.nextElement(); - String md = (String) digests.get(entry); - byte[] result = (byte[]) results.get(entry); - os.writeUTF(entry); - if (md == JarVerifierConstant.MD2_STR) - os.writeInt(0); - else - os.writeInt(1); - os.writeInt(result.length); - os.write(result); - } - } - os.writeBoolean(md5Result != null); - if (md5Result != null) - os.writeUTF(md5Result); - os.writeBoolean(shaResult != null); - if (shaResult != null) - os.writeUTF(shaResult); - } - - private void getFirstLastID() { - if (firstIDSaved >= 0) - return; - Bundle[] bundles = bundledata.getAdaptor().getContext().getBundles(); - if (bundles.length > 1) { - firstIDSaved = bundles[1].getBundleId(); - lastIDSaved = bundles[bundles.length - 1].getBundleId(); - } - } - - public void copy(StorageHook storageHook) { - // do nothing - } - - public void validate() throws IllegalArgumentException { - // do nothing - } - - public Dictionary getManifest(boolean firstLoad) throws BundleException { - // do nothing - return null; - } - - public boolean forgetStatusChange(int status) { - // do nothing - return false; - } - - public boolean forgetStartLevelChange(int startlevel) { - // do nothing - return false; - } - - public boolean matchDNChain(String pattern) { - return signedBundleFile == null ? false : signedBundleFile.matchDNChain(pattern); - } - - public int getKeyHashCode() { - return HASHCODE; - } - - public boolean compare(KeyedElement other) { - return other.getKey() == KEY; - } - - public Object getKey() { - return KEY; - } - -} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/service/security/DefaultAuthorizationEngine.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/service/security/DefaultAuthorizationEngine.java new file mode 100644 index 000000000..8785edc86 --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/service/security/DefaultAuthorizationEngine.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2007 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.osgi.internal.service.security; + +import java.security.cert.CertificateException; +import org.eclipse.osgi.framework.internal.core.FrameworkProperties; +import org.eclipse.osgi.internal.baseadaptor.DevClassPathHelper; +import org.eclipse.osgi.service.resolver.*; +import org.eclipse.osgi.service.security.AuthorizationEngine; +import org.eclipse.osgi.service.security.AuthorizationEvent; +import org.eclipse.osgi.signedcontent.SignedContent; +import org.eclipse.osgi.signedcontent.SignerInfo; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; + +public class DefaultAuthorizationEngine extends AuthorizationEngine { + + private final State systemState; + + public DefaultAuthorizationEngine(BundleContext context, State systemState) { + super(context); + this.systemState = systemState; + } + + private static final int ENFORCE_NONE = 0x0000; + private static final int ENFORCE_SIGNED = 0x0001; + private static final int ENFORCE_TRUSTED = 0x0002; + private static final int ENFORCE_VALIDITY = 0x0004; + + private static final String STR_ENFORCE_NONE = "any"; //$NON-NLS-1$ + private static final String STR_ENFORCE_SIGNED = "signed"; //$NON-NLS-1$ + private static final String STR_ENFORCE_TRUSTED = "trusted"; //$NON-NLS-1$ + private static final String STR_ENFORCE_VALIDITY = "validity"; //$NON-NLS-1$ + + private static final String POLICY_NAME = "org.eclipse.equinox.security"; //$NON-NLS-1$ + private static final String POLICY = "osgi.signedcontent.authorization.engine.policy"; //$NON-NLS-1$ + private static int enforceFlags = 0; + + static { + String policy = FrameworkProperties.getProperty(POLICY); + if (policy == null || STR_ENFORCE_NONE.equals(policy)) + enforceFlags = ENFORCE_NONE; + else if (STR_ENFORCE_TRUSTED.equals(policy)) + enforceFlags = ENFORCE_TRUSTED | ENFORCE_SIGNED; + else if (STR_ENFORCE_SIGNED.equals(policy)) + enforceFlags = ENFORCE_SIGNED; + else if (STR_ENFORCE_VALIDITY.equals(policy)) + enforceFlags = ENFORCE_TRUSTED | ENFORCE_SIGNED | ENFORCE_VALIDITY; + } + + protected AuthorizationEvent doAuthorize(SignedContent content, Object context) { + boolean enabled = isEnabled(content, context); + AuthorizationEvent event = null; + if (context instanceof Bundle) { + BundleDescription desc = systemState.getBundle(((Bundle) context).getBundleId()); + if (!enabled) { + DisabledInfo info = new DisabledInfo(POLICY_NAME, null, desc); // TODO add an error message + systemState.addDisabledInfo(info); + event = new AuthorizationEvent(AuthorizationEvent.DENIED, content, context, 0); // TODO severity?? + } else { + DisabledInfo info = systemState.getDisabledInfo(desc, POLICY_NAME); + if (info != null) { + systemState.removeDisabledInfo(info); + } + event = new AuthorizationEvent(AuthorizationEvent.ALLOWED, content, context, 0); + } + } + return event; + } + + private boolean isEnabled(SignedContent content, Object context) { + if (context instanceof Bundle && DevClassPathHelper.inDevelopmentMode()) { + String[] devClassPath = DevClassPathHelper.getDevClassPath(((Bundle) context).getSymbolicName()); + if (devClassPath != null && devClassPath.length > 0) + return true; // always enabled bundles from workspace; they never are signed + } + if ((0 != (enforceFlags & ENFORCE_SIGNED)) && ((content == null) || !content.isSigned())) + return false; + + SignerInfo[] signerInfos = content == null ? new SignerInfo[0] : content.getSignerInfos(); + for (int i = 0; i < signerInfos.length; i++) { + if ((0 != (enforceFlags & ENFORCE_TRUSTED)) && !signerInfos[i].isTrusted()) + return false; + if ((0 != (enforceFlags & ENFORCE_VALIDITY))) + try { + content.checkValidity(signerInfos[i]); + } catch (CertificateException e) { + return false; + } + } + return true; + } + + protected int doGetSeverity() { + return 0; + } +} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/service/security/KeyStoreTrustEngine.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/service/security/KeyStoreTrustEngine.java new file mode 100644 index 000000000..c435b9b4b --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/service/security/KeyStoreTrustEngine.java @@ -0,0 +1,259 @@ +/******************************************************************************* + * Copyright (c) 2007 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.osgi.internal.service.security; + +import java.io.*; +import java.security.*; +import java.security.cert.*; +import java.security.cert.Certificate; +import java.util.ArrayList; +import java.util.Enumeration; +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.eclipse.osgi.internal.signedcontent.SignedBundleHook; +import org.eclipse.osgi.internal.signedcontent.SignedContentMessages; +import org.eclipse.osgi.service.security.TrustEngine; +import org.eclipse.osgi.util.NLS; + +//*potential enhancements* +// 1. reloading from the backing file when it changes +// 3. methods to support lock/unlock +// 3a. Using a callback handler to collect the password +// 3b. managing lock/unlock between multiple threads. dealing with SWT UI thread +// 4. methods to support changing password, etc +// 5. methods to support export, etc +// 6. 'friendly-name' generator +// 7. Listeners for change events +public class KeyStoreTrustEngine extends TrustEngine { + + private KeyStore keyStore; + + private String type; + private String path; + private char[] password; + + /** + * Create a new KeyStoreTrustEngine that is backed by a KeyStore + * @param path - path to the keystore + * @param type - the type of keystore at the path location + * @param password - the password required to unlock the keystore + */ + public KeyStoreTrustEngine(String path, String type, char[] password) { //TODO: This should be a *CallbackHandler* + this.path = path; + this.type = type; + this.password = password; + } + + /** + * Return the type + * @return type - the type for the KeyStore being managed + */ + private String getType() { + return type; + } + + /** + * Return the path + * @return - the path for the KeyStore being managed + */ + private String getPath() { + return path; + } + + /** + * Return the password + * @return password - the password as a char[] + */ + private char[] getPassword() { + return password; + } + + /** + * Return the KeyStore managed + * @return keystore - the KeyStore instance, initialized and loaded + * @throws KeyStoreException + */ + private synchronized KeyStore getKeyStore() throws IOException, GeneralSecurityException { + if (null == keyStore) { + keyStore = KeyStore.getInstance(getType()); + loadStore(keyStore, getInputStream()); + } + + if (keyStore == null) + throw new KeyStoreException(NLS.bind(SignedContentMessages.Default_Trust_Keystore_Load_Failed, getPath())); + + return keyStore; + } + + public Certificate findTrustAnchor(Certificate[] certChain) throws IOException { + + if (certChain == null || certChain.length == 0) + throw new IllegalArgumentException("Certificate chain is required"); //$NON-NLS-1$ + + try { + KeyStore store = getKeyStore(); + for (int i = 0; i < certChain.length; i++) { + if (i == certChain.length - 1) { + certChain[i].verify(certChain[i].getPublicKey()); + } else { + X509Certificate nextX509Cert = (X509Certificate) certChain[i + 1]; + certChain[i].verify(nextX509Cert.getPublicKey()); + } + + synchronized (store) { + String alias = store.getCertificateAlias(certChain[i]); + if (alias != null) { + return store.getCertificate(alias); + } + } + } + } catch (KeyStoreException e) { + throw new IOException(e.getMessage()); + } catch (GeneralSecurityException e) { + SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.WARNING, e); + return null; + } + return null; + } + + protected String doAddTrustAnchor(Certificate cert, String alias) throws IOException, GeneralSecurityException { + if (isReadOnly()) + throw new IOException(SignedContentMessages.Default_Trust_Read_Only); + if (cert == null) { + throw new IllegalArgumentException("Certificate must be specified"); //$NON-NLS-1$ + } + try { + KeyStore store = getKeyStore(); + synchronized (store) { + store.setCertificateEntry(alias, cert); + saveStore(store, getOutputStream()); + } + } catch (KeyStoreException ke) { + throw new CertificateException(ke.getMessage()); + } + return alias; + } + + protected void doRemoveTrustAnchor(Certificate cert) throws IOException, GeneralSecurityException { + if (isReadOnly()) + throw new IOException(SignedContentMessages.Default_Trust_Read_Only); + if (cert == null) { + throw new IllegalArgumentException("Certificate must be specified"); //$NON-NLS-1$ + } + try { + KeyStore store = getKeyStore(); + synchronized (store) { + String alias = store.getCertificateAlias(cert); + if (alias == null) { + throw new CertificateException(SignedContentMessages.Default_Trust_Cert_Not_Found); + } + removeTrustAnchor(alias); + } + } catch (KeyStoreException ke) { + throw new CertificateException(ke.getMessage()); + } + } + + protected void doRemoveTrustAnchor(String alias) throws IOException, GeneralSecurityException { + + if (alias == null) { + throw new IllegalArgumentException("Alias must be specified"); //$NON-NLS-1$ + } + try { + KeyStore store = getKeyStore(); + synchronized (store) { + store.deleteEntry(alias); + saveStore(store, getOutputStream()); + } + } catch (KeyStoreException ke) { + throw new CertificateException(ke.getMessage()); + } + } + + public Certificate getTrustAnchor(String alias) throws IOException, GeneralSecurityException { + + if (alias == null) { + throw new IllegalArgumentException("Alias must be specified"); //$NON-NLS-1$ + } + + try { + KeyStore store = getKeyStore(); + synchronized (store) { + return store.getCertificate(alias); + } + } catch (KeyStoreException ke) { + throw new CertificateException(ke.getMessage()); + } + } + + public String[] getAliases() throws IOException, GeneralSecurityException { + + ArrayList returnList = new ArrayList(); + try { + KeyStore store = getKeyStore(); + synchronized (store) { + for (Enumeration aliases = store.aliases(); aliases.hasMoreElements();) { + String currentAlias = (String) aliases.nextElement(); + if (store.isCertificateEntry(currentAlias)) { + returnList.add(currentAlias); + } + } + } + } catch (KeyStoreException ke) { + throw new CertificateException(ke.getMessage()); + } + return (String[]) returnList.toArray(new String[] {}); + } + + /** + * Load using the current password + */ + private void loadStore(KeyStore store, InputStream is) throws IOException, GeneralSecurityException { + store.load(is, getPassword()); + } + + /** + * Save using the current password + */ + private void saveStore(KeyStore store, OutputStream os) throws IOException, GeneralSecurityException { + store.store(os, getPassword()); + } + + /** + * Get an input stream for the KeyStore managed + * @return inputstream - the stream + * @throws KeyStoreException + */ + private InputStream getInputStream() throws IOException { + return new FileInputStream(new File(getPath())); + } + + /** + * Get an output stream for the KeyStore managed + * @return outputstream - the stream + * @throws KeyStoreException + */ + private OutputStream getOutputStream() throws IOException { + + File file = new File(getPath()); + if (!file.exists()) + file.createNewFile(); + + return new FileOutputStream(file); + } + + public boolean isReadOnly() { + return getPassword() == null || !(new File(path).canWrite()); + } + + public String getName() { + return "System"; //$NON-NLS-1$ + } +} diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/BERProcessor.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/BERProcessor.java index 1c9210d40..57b985226 100644 --- a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/BERProcessor.java +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/BERProcessor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2006 IBM Corporation and others. + * Copyright (c) 2006, 2007 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 @@ -9,9 +9,10 @@ * IBM Corporation - initial API and implementation *******************************************************************************/ -package org.eclipse.osgi.internal.verifier; +package org.eclipse.osgi.internal.signedcontent; import java.math.BigInteger; +import java.security.SignatureException; /** * This is a simple class that processes BER structures. This class @@ -82,8 +83,9 @@ public class BERProcessor { * @param buffer the buffer containing the BER structures. * @param offset the offset into <code>buffer</code> to the start of the first structure. * @param len the length of the BER structure. + * @throws SignatureException */ - public BERProcessor(byte buffer[], int offset, int len) { + public BERProcessor(byte buffer[], int offset, int len) throws SignatureException { this.buffer = buffer; this.offset = offset; lastOffset = len + offset; @@ -96,7 +98,7 @@ public class BERProcessor { * <code>offset</code> is modified outside of those methods, this method will need to * be invoked. */ - public void processStructure() { + public void processStructure() throws SignatureException { // Don't process if we are at the end if (offset == -1) return; @@ -111,7 +113,7 @@ public class BERProcessor { tag = tagNumber; endOffset = offset + 1; } else { - throw new IllegalArgumentException("Can't handle tags > 32"); //$NON-NLS-1$ + throw new SignatureException("Can't handle tags > 32"); //$NON-NLS-1$ } if ((buffer[endOffset] & 0x80) == 0) { // section 8.1.3.4 (doing the short form of the length) @@ -121,7 +123,7 @@ public class BERProcessor { // section 8.1.3.5 (doing the long form of the length) int octetCount = buffer[endOffset] & 0x7f; if (octetCount > 3) - throw new ArrayIndexOutOfBoundsException("ContentLength octet count too large: " + octetCount); //$NON-NLS-1$ + throw new SignatureException("ContentLength octet count too large: " + octetCount); //$NON-NLS-1$ contentLength = 0; endOffset++; for (int i = 0; i < octetCount; i++) { @@ -137,7 +139,7 @@ public class BERProcessor { if (contentLength != -1) endOffset += contentLength; if (endOffset > lastOffset) - throw new ArrayIndexOutOfBoundsException(endOffset + " > " + lastOffset); //$NON-NLS-1$ + throw new SignatureException("Content length too large: " + endOffset + " > " + lastOffset); //$NON-NLS-1$ //$NON-NLS-2$ } /** @@ -190,12 +192,13 @@ public class BERProcessor { /** * Returns a BERProcessor for the content of the current structure. + * @throws SignatureException */ - public BERProcessor stepInto() { + public BERProcessor stepInto() throws SignatureException { return new BERProcessor(buffer, contentOffset, contentLength); } - public void stepOver() { + public void stepOver() throws SignatureException { offset = endOffset; if (endOffset >= lastOffset) { offset = -1; diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/Base64.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/Base64.java index 130192b1c..32d09834f 100644 --- a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/Base64.java +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/Base64.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2006 IBM Corporation and others. + * Copyright (c) 2006, 2007 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 @@ -8,7 +8,7 @@ * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ -package org.eclipse.osgi.internal.verifier; +package org.eclipse.osgi.internal.signedcontent; public class Base64 { @@ -195,4 +195,4 @@ public class Base64 { } return result; } -}
\ No newline at end of file +} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/BundleInstallListener.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/BundleInstallListener.java new file mode 100644 index 000000000..23148a7da --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/BundleInstallListener.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2007 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.osgi.internal.signedcontent; + +import org.eclipse.osgi.baseadaptor.BaseData; +import org.eclipse.osgi.framework.internal.core.AbstractBundle; +import org.eclipse.osgi.service.security.AuthorizationEngine; +import org.eclipse.osgi.signedcontent.SignedContent; +import org.osgi.framework.*; + +public class BundleInstallListener implements SynchronousBundleListener { + + public void bundleChanged(BundleEvent event) { + Bundle bundle = event.getBundle(); + switch (event.getType()) { + case BundleEvent.UPDATED : + // fall through to INSTALLED + case BundleEvent.INSTALLED : + TrustEngineListener listener = TrustEngineListener.getInstance(); + AuthorizationEngine authEngine = listener == null ? null : listener.getAuthorizationEngine(); + if (authEngine != null) { + BaseData baseData = (BaseData) ((AbstractBundle) bundle).getBundleData(); + SignedStorageHook hook = (SignedStorageHook) baseData.getStorageHook(SignedStorageHook.KEY); + SignedContent signedContent = hook != null ? hook.signedContent : null; + authEngine.authorize(signedContent, bundle); + } + break; + default : + break; + } + } +} diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/DNChainMatching.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/DNChainMatching.java index c79dfa086..ab208c54f 100644 --- a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/DNChainMatching.java +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/DNChainMatching.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2006 IBM Corporation and others. + * Copyright (c) 2006, 2007 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 @@ -8,7 +8,7 @@ * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ -package org.eclipse.osgi.internal.verifier; +package org.eclipse.osgi.internal.signedcontent; import java.util.ArrayList; import javax.security.auth.x500.X500Principal; diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/DigestedInputStream.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/DigestedInputStream.java index b2626c5b0..d244c1681 100644 --- a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/DigestedInputStream.java +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/DigestedInputStream.java @@ -9,10 +9,16 @@ * IBM Corporation - initial API and implementation *******************************************************************************/ -package org.eclipse.osgi.internal.verifier; +package org.eclipse.osgi.internal.signedcontent; -import java.io.*; +import java.io.FilterInputStream; +import java.io.IOException; import java.security.MessageDigest; +import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry; +import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile; +import org.eclipse.osgi.signedcontent.InvalidContentException; +import org.eclipse.osgi.signedcontent.SignerInfo; +import org.eclipse.osgi.util.NLS; /** * This InputStream will calculate the digest of bytes as they are read. At the @@ -20,9 +26,11 @@ import java.security.MessageDigest; * if the calculated digest do not match the expected digests. */ class DigestedInputStream extends FilterInputStream { - MessageDigest digest; - byte result[]; - long remaining; + private final MessageDigest digests[]; + private final byte result[][]; + private final BundleEntry entry; + private final BundleFile bundleFile; + private long remaining; /** * Constructs an InputStream that uses another InputStream as a source and @@ -30,14 +38,19 @@ class DigestedInputStream extends FilterInputStream { * thrown if the calculated digest doesn't match the passed digest. * * @param in the stream to use as an input source. - * @param digestAlgorithm the MessageDigest algorithm to use. - * @param result the expected digest. + * @param signerInfos the signers. + * @param results the expected digest. + * @throws IOException */ - DigestedInputStream(InputStream in, String digestAlgorithm, byte result[], long size) { - super(in); + DigestedInputStream(BundleEntry entry, BundleFile bundleFile, SignerInfo[] signerInfos, byte results[][], long size) throws IOException { + super(entry.getInputStream()); + this.entry = entry; + this.bundleFile = bundleFile; this.remaining = size; - this.digest = SignedBundleFile.getMessageDigest(digestAlgorithm); - this.result = result; + this.digests = new MessageDigest[signerInfos.length]; + for (int i = 0; i < signerInfos.length; i++) + this.digests[i] = SignatureBlockProcessor.getMessageDigest(signerInfos[i].getMessageDigestAlgorithm()); + this.result = results; } /** @@ -69,7 +82,8 @@ class DigestedInputStream extends FilterInputStream { return -1; int c = super.read(); if (c != -1) { - digest.update((byte) c); + for (int i = 0; i < digests.length; i++) + digests[i].update((byte) c); remaining--; } else { // We hit eof so set remaining to zero @@ -80,11 +94,13 @@ class DigestedInputStream extends FilterInputStream { return c; } - private void verifyDigests() throws IOException { + private void verifyDigests() throws InvalidContentException { // Check the digest at end of file - byte rc[] = digest.digest(); - if (!MessageDigest.isEqual(result, rc)) - throw new IOException("Corrupted file: the digest is valid for " + digest.getAlgorithm()); //$NON-NLS-1$ + for (int i = 0; i < digests.length; i++) { + byte rc[] = digests[i].digest(); + if (!MessageDigest.isEqual(result[i], rc)) + throw new InvalidContentException(NLS.bind(SignedContentMessages.File_In_Jar_Is_Tampered, entry.getName(), bundleFile.getBaseFile()), null); + } } /** @@ -102,7 +118,8 @@ class DigestedInputStream extends FilterInputStream { return -1; int rc = super.read(b, off, len); if (rc != -1) { - digest.update(b, off, rc); + for (int i = 0; i < digests.length; i++) + digests[i].update(b, off, rc); remaining -= rc; } else { // We hit eof so set remaining to zero @@ -140,4 +157,4 @@ class DigestedInputStream extends FilterInputStream { } return count; } -}
\ No newline at end of file +} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/LegacyVerifierFactory.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/LegacyVerifierFactory.java new file mode 100644 index 000000000..be9106879 --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/LegacyVerifierFactory.java @@ -0,0 +1,138 @@ +/******************************************************************************* + * Copyright (c) 2007 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.osgi.internal.signedcontent; + +import java.io.File; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.cert.*; +import java.util.ArrayList; +import java.util.Date; +import org.eclipse.osgi.internal.provisional.verifier.*; +import org.eclipse.osgi.signedcontent.*; +import org.osgi.framework.Bundle; + +public class LegacyVerifierFactory implements CertificateVerifierFactory { + private final SignedContentFactory signedContentFactory; + + public LegacyVerifierFactory(SignedContentFactory signedContentFactory) { + this.signedContentFactory = signedContentFactory; + } + + public CertificateVerifier getVerifier(File content) throws IOException { + try { + return new LegacyVerifier(signedContentFactory.getSignedContent(content)); + } catch (GeneralSecurityException e) { + throw new IOException(e.getMessage()); + } + } + + public CertificateVerifier getVerifier(Bundle bundle) throws IOException { + try { + return new LegacyVerifier(signedContentFactory.getSignedContent(bundle)); + } catch (GeneralSecurityException e) { + throw new IOException(e.getMessage()); + } + } + + static class LegacyVerifier implements CertificateVerifier { + private final SignedContent signedContent; + + public LegacyVerifier(SignedContent signedContent) { + this.signedContent = signedContent; + } + + public void checkContent() throws CertificateException, CertificateExpiredException { + SignedContentEntry[] entries = signedContent.getSignedEntries(); + for (int i = 0; i < entries.length; i++) { + try { + entries[i].verify(); + } catch (InvalidContentException e) { + throw new SecurityException(e.getMessage()); + } catch (IOException e) { + throw new SecurityException(e.getMessage()); + } + } + SignerInfo[] infos = signedContent.getSignerInfos(); + for (int i = 0; i < infos.length; i++) + signedContent.checkValidity(infos[i]); + } + + public CertificateChain[] getChains() { + SignerInfo infos[] = signedContent.getSignerInfos(); + CertificateChain[] chains = new CertificateChain[infos.length]; + for (int i = 0; i < chains.length; i++) + chains[i] = new LegacyChain(infos[i], signedContent); + return chains; + } + + public boolean isSigned() { + return signedContent.isSigned(); + } + + public String[] verifyContent() { + ArrayList invalidContent = new ArrayList(0); + SignedContentEntry[] entries = signedContent.getSignedEntries(); + for (int i = 0; i < entries.length; i++) { + try { + entries[i].verify(); + } catch (InvalidContentException e) { + invalidContent.add(entries[i].getName()); + } catch (IOException e) { + invalidContent.add(entries[i].getName()); + } + } + return (String[]) invalidContent.toArray(new String[invalidContent.size()]); + } + } + + static class LegacyChain implements CertificateChain { + private final SignerInfo signerInfo; + private final SignedContent content; + + public LegacyChain(SignerInfo signerInfo, SignedContent content) { + this.signerInfo = signerInfo; + this.content = content; + } + + public Certificate[] getCertificates() { + return signerInfo.getCertificateChain(); + } + + public String getChain() { + StringBuffer sb = new StringBuffer(); + Certificate[] certs = getCertificates(); + for (int i = 0; i < certs.length; i++) { + X509Certificate x509Cert = ((X509Certificate) certs[i]); + sb.append(x509Cert.getSubjectDN().getName()); + sb.append("; "); //$NON-NLS-1$ + } + return sb.toString(); + } + + public Certificate getRoot() { + Certificate[] certs = getCertificates(); + return certs.length > 0 ? certs[certs.length - 1] : null; + } + + public Certificate getSigner() { + Certificate[] certs = getCertificates(); + return certs.length > 0 ? certs[1] : null; + } + + public Date getSigningTime() { + return content.getSigningTime(signerInfo); + } + + public boolean isTrusted() { + return signerInfo.isTrusted(); + } + + } +} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/PKCS7DateParser.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/PKCS7DateParser.java new file mode 100644 index 000000000..9c92a6291 --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/PKCS7DateParser.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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.osgi.internal.signedcontent; + +import java.security.*; +import java.security.cert.CertificateException; +import java.util.*; + +public class PKCS7DateParser { + + static Date parseDate(PKCS7Processor pkcs7Processor, String signer, String file) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, NoSuchProviderException { + Map unsignedAttrs = pkcs7Processor.getUnsignedAttrs(); + if (unsignedAttrs != null) { + // get the timestamp construct + byte[] timeStampConstruct = retrieveTimeStampConstruct(unsignedAttrs); + + // there is a timestamp in the signer info + if (timeStampConstruct != null) { + PKCS7Processor timestampProcess = new PKCS7Processor(timeStampConstruct, 0, timeStampConstruct.length, signer, file); + timestampProcess.verifyCerts(); + pkcs7Processor.setTSACertificates(timestampProcess.getCertificates()); + return timestampProcess.getSigningTime(); + } + } + return null; + } + + private static byte[] retrieveTimeStampConstruct(Map unsignedAttrs) { + Set objIDs = unsignedAttrs.keySet(); + Iterator iter = objIDs.iterator(); + while (iter.hasNext()) { + int[] objID = (int[]) iter.next(); + if (Arrays.equals(SignedContentConstants.TIMESTAMP_OID, objID)) { + return (byte[]) unsignedAttrs.get(objID); + } + } + return null; + } +} diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/PKCS7Processor.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/PKCS7Processor.java index f6c280026..5989221cb 100644 --- a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/PKCS7Processor.java +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/PKCS7Processor.java @@ -6,31 +6,25 @@ * * Contributors: IBM Corporation - initial API and implementation ******************************************************************************/ -package org.eclipse.osgi.internal.verifier; +package org.eclipse.osgi.internal.signedcontent; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.math.BigInteger; import java.security.*; import java.security.cert.*; import java.security.cert.Certificate; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; +import java.text.*; import java.util.*; - import javax.security.auth.x500.X500Principal; import org.eclipse.osgi.framework.log.FrameworkLogEntry; -import org.eclipse.osgi.internal.provisional.verifier.CertificateChain; -import org.eclipse.osgi.internal.provisional.verifier.CertificateTrustAuthority; import org.eclipse.osgi.util.NLS; /** * This class processes a PKCS7 file. See RFC 2315 for specifics. */ -public class PKCS7Processor implements CertificateChain, JarVerifierConstant { +public class PKCS7Processor implements SignedContentConstants { - private static CertificateFactory certFact; + static CertificateFactory certFact; static { try { @@ -40,10 +34,11 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { } } - private String certChain; + private final String signer; + private final String file; + private Certificate[] certificates; private Certificate[] tsaCertificates; - private boolean trusted; // key(object id) = value(structure) private Map signedAttrs; @@ -59,7 +54,7 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { private Certificate signerCert; private Date signingTime; - String oid2String(int oid[]) { + private static String oid2String(int oid[]) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < oid.length; i++) { if (i > 0) @@ -69,7 +64,7 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { return sb.toString(); } - String findEncryption(int encOid[]) throws NoSuchAlgorithmException { + private static String findEncryption(int encOid[]) throws NoSuchAlgorithmException { if (Arrays.equals(DSA_OID, encOid)) { return "DSA"; //$NON-NLS-1$ } @@ -79,7 +74,7 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { throw new NoSuchAlgorithmException("No algorithm found for " + oid2String(encOid)); //$NON-NLS-1$ } - String findDigest(int digestOid[]) throws NoSuchAlgorithmException { + private static String findDigest(int digestOid[]) throws NoSuchAlgorithmException { if (Arrays.equals(SHA1_OID, digestOid)) { return SHA1_STR; } @@ -92,32 +87,9 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { throw new NoSuchAlgorithmException("No algorithm found for " + oid2String(digestOid)); //$NON-NLS-1$ } - /* - * static void printBP(BERProcessor bp, int depth) { - * System.out.print(depth); for(int i = 0; i < depth; i++) - * System.out.print(" "); System.out.println(bp); } - * - * static void dumpSeq(BERProcessor bp, int depth) { - * while(!bp.endOfSequence()) { printBP(bp, depth); if (bp.constructed) { - * dumpSeq(bp.stepInto(), depth+1); } bp.stepOver(); } } - * - * void hexDump(byte buffer[], int off, int len) { for(int i = 0; i < len; - * i++) { System.out.print(Integer.toString(buffer[i]&0xff, 16) + " "); if - * (i % 16 == 15) System.out.println(); } System.out.println(); } - */ - - public PKCS7Processor(String certChain, boolean trusted, byte[][] certificates, long signingTime) throws CertificateException { - this.certChain = certChain; - this.trusted = trusted; - this.certificates = new Certificate[certificates.length]; - for (int i = 0; i < certificates.length; i++) - this.certificates[i] = certFact.generateCertificate(new ByteArrayInputStream(certificates[i])); - if (signingTime > Long.MIN_VALUE) - this.signingTime = new Date(signingTime); - } - - public PKCS7Processor(byte pkcs7[], int pkcs7Offset, int pkcs7Length) throws IOException, CertificateException, NoSuchAlgorithmException { - + public PKCS7Processor(byte pkcs7[], int pkcs7Offset, int pkcs7Length, String signer, String file) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, NoSuchProviderException { + this.signer = signer; + this.file = file; // First grab the certificates List certs = null; @@ -128,7 +100,7 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { // PKCS7: Step into the ContentType bp = bp.stepInto(); if (!Arrays.equals(bp.getObjId(), SIGNEDDATA_OID)) { - throw new IOException("Not a valid PKCS#7 file"); //$NON-NLS-1$ + throw new SignatureException(NLS.bind(SignedContentMessages.PKCS7_Invalid_File, signer, file)); } // PKCS7: Process the SignedData structure @@ -163,25 +135,15 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { // construct the cert path certs = constructCertPath(certs, signerCert); - // set the cert chain variable - int numCerts = certs.size(); - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < numCerts; i++) { - X509Certificate x509Cert = ((X509Certificate) certs.get(i)); - sb.append(x509Cert.getSubjectDN().getName()); - sb.append("; "); //$NON-NLS-1$ - } - certChain = sb.toString(); - // initialize the certificates - certificates = (Certificate[]) certs.toArray(new Certificate[numCerts]); - + certificates = (Certificate[]) certs.toArray(new Certificate[certs.size()]); + verifyCerts(); // if this pkcs7process is tsa asn.1 block, the signingTime should already be set - if (null == signingTime) - signingTime = PKCS7DateParser.parseDate(this); + if (signingTime == null) + signingTime = PKCS7DateParser.parseDate(this, signer, file); } - private void processEncapContentInfo(BERProcessor bp) throws IOException { + private void processEncapContentInfo(BERProcessor bp) throws SignatureException { // check immediately if TSTInfo is there BERProcessor encapContentBERS = bp.stepInto(); if (Arrays.equals(encapContentBERS.getObjId(), TIMESTAMP_TST_OID)) { @@ -198,9 +160,8 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { BERProcessor eContentBER = eContentStructure.stepInto(); int tsaVersion = eContentBER.getIntValue().intValue(); - if (tsaVersion != 1) { - throw new IOException("Not a version 1 time-stamp token"); //$NON-NLS-1$ - } + if (tsaVersion != 1) + throw new SignatureException("Not a version 1 time-stamp token"); //$NON-NLS-1$ // policty : TSAPolicyId eContentBER.stepOver(); @@ -216,9 +177,8 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { // check time ends w/ 'Z' String dateString = new String(eContentBER.getBytes()); - if (!dateString.endsWith("Z")) { //$NON-NLS-1$ - throw new IOException("Wrong dateformat used in time-stamp token"); //$NON-NLS-1$ - } + if (!dateString.endsWith("Z")) //$NON-NLS-1$ + throw new SignatureException("Wrong dateformat used in time-stamp token"); //$NON-NLS-1$ // create the appropriate date time string format // date format could be yyyyMMddHHmmss[.s...]Z or yyyyMMddHHmmssZ @@ -241,7 +201,7 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { dateFormt.setTimeZone(TimeZone.getTimeZone("GMT")); //$NON-NLS-1$ signingTime = dateFormt.parse(dateString); } catch (ParseException e) { - throw new IOException(JarVerifierMessages.PKCS7_Parse_Signing_Time_1); + throw new SignatureException(SignedContentMessages.PKCS7_Parse_Signing_Time); } } } @@ -281,9 +241,9 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { return certsList; } - public void validateCerts() throws CertificateExpiredException, CertificateNotYetValidException, InvalidKeyException, SignatureException { + public void verifyCerts() throws InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException { if (certificates == null || certificates.length == 0) { - throw new SecurityException("There are no certificates in the signature block file!"); //$NON-NLS-1$ + throw new CertificateException("There are no certificates in the signature block file!"); //$NON-NLS-1$ } int len = certificates.length; @@ -291,34 +251,17 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { // check the certs validity and signatures for (int i = 0; i < len; i++) { X509Certificate currentX509Cert = (X509Certificate) certificates[i]; - - if (signingTime == null) - currentX509Cert.checkValidity(); - else - currentX509Cert.checkValidity(signingTime); - - try { - if (i == len - 1) { - if (currentX509Cert.getSubjectDN().equals(currentX509Cert.getIssuerDN())) - currentX509Cert.verify(currentX509Cert.getPublicKey()); - } else { - X509Certificate nextX509Cert = (X509Certificate) certificates[i + 1]; - currentX509Cert.verify(nextX509Cert.getPublicKey()); - } - } catch (NoSuchAlgorithmException e) { - SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); - throw new SecurityException(NLS.bind(JarVerifierMessages.No_Such_Algorithm_Excep, new String[] {e.getMessage()})); - } catch (NoSuchProviderException e) { - SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); - throw new SecurityException(NLS.bind(JarVerifierMessages.No_Such_Provider_Excep, new String[] {e.getMessage()})); - } catch (CertificateException e) { - SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); - throw new SecurityException(NLS.bind(JarVerifierMessages.Validate_Certs_Certificate_Exception, new String[] {e.getMessage()})); + if (i == len - 1) { + if (currentX509Cert.getSubjectDN().equals(currentX509Cert.getIssuerDN())) + currentX509Cert.verify(currentX509Cert.getPublicKey()); + } else { + X509Certificate nextX509Cert = (X509Certificate) certificates[i + 1]; + currentX509Cert.verify(nextX509Cert.getPublicKey()); } } } - private Certificate processSignerInfos(BERProcessor bp, List certs) throws CertificateException, NoSuchAlgorithmException { + private Certificate processSignerInfos(BERProcessor bp, List certs) throws CertificateException, NoSuchAlgorithmException, SignatureException { // We assume there is only one SingerInfo element // PKCS7: SignerINFOS processing @@ -328,7 +271,7 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { // make sure the version is 1 BigInteger signerInfoVersion = bp.getIntValue(); if (signerInfoVersion.intValue() != 1) { - throw new CertificateException(JarVerifierMessages.PKCS7_SignerInfo_Version_Not_Supported); + throw new CertificateException(SignedContentMessages.PKCS7_SignerInfo_Version_Not_Supported); } // PKCS7: version CMSVersion @@ -390,7 +333,7 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { return newSignerCert; } - private void processUnsignedAttributes(BERProcessor bp) { + private void processUnsignedAttributes(BERProcessor bp) throws SignatureException { if (bp.classOfTag == BERProcessor.CONTEXTSPECIFIC_TAGCLASS && bp.tag == 1) { @@ -420,7 +363,7 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { } } - private void processSignedAttributes(BERProcessor bp) { + private void processSignedAttributes(BERProcessor bp) throws SignatureException { if (bp.classOfTag == BERProcessor.CONTEXTSPECIFIC_TAGCLASS) { // process the signed attributes @@ -444,72 +387,8 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { } } - /** - * Returns the Certificate of the signer of this PKCS7Block - */ - public Certificate getSigner() { - if (certificates == null || certificates.length == 0) - return null; - return certificates[0]; - } - - public Certificate getRoot() { - if (certificates == null || certificates.length == 0) - return null; - return certificates[certificates.length - 1]; - } - public Certificate[] getCertificates() { - return certificates; - } - - /** - * Returns the list of X500 distinguished names that make up the signature chain. Each - * distinguished name is separated by a ';'. - */ - public String getChain() { - return certChain; - } - - /** - * Returns true if the signer certificate is trusted - * @return true if the signer certificate is trusted - */ - public boolean isTrusted() { - return trusted; - } - - public boolean equals(Object obj) { - if (!(obj instanceof CertificateChain)) - return false; - if (certificates == null) - return false; - CertificateChain chain = (CertificateChain) obj; - if((signingTime == null ? chain.getSigningTime() != null : !signingTime.equals(chain.getSigningTime()))) - return false; - if (trusted != chain.isTrusted() || (certChain == null ? chain.getChain() != null : !certChain.equals(chain.getChain()))) - return false; - Certificate[] otherCerts = chain.getCertificates(); - if (otherCerts == null || certificates.length != otherCerts.length) - return false; - for (int i = 0; i < certificates.length; i++) - if (!certificates[i].equals(otherCerts[i])) - return false; - return true; - } - - public int hashCode() { - int hashCode = 0; - if (signingTime != null) - hashCode += signingTime.hashCode(); - if (trusted) - hashCode += 1; - if (certChain != null) - hashCode += certChain.hashCode(); - if (certificates != null) - for (int i = 0; i < certificates.length; i++) - hashCode += certificates[i].hashCode(); - return hashCode; + return certificates == null ? new Certificate[0] : certificates; } public void verifySFSignature(byte data[], int dataOffset, int dataLength) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException { @@ -517,7 +396,7 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { sig.initVerify(signerCert.getPublicKey()); sig.update(data, dataOffset, dataLength); if (!sig.verify(signature)) { - throw new SignatureException(JarVerifierMessages.Signature_Not_Verify); + throw new SignatureException(NLS.bind(SignedContentMessages.Signature_Not_Verify, signer, file)); } } @@ -545,8 +424,9 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { * @return a List of certificates from target cert to root cert in order * * @throws CertificateException + * @throws SignatureException */ - private List processCertificates(BERProcessor bp) throws CertificateException { + private List processCertificates(BERProcessor bp) throws CertificateException, SignatureException { List rtvList = new ArrayList(3); // Step into the first certificate-element @@ -567,18 +447,6 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { return rtvList; } - void determineTrust(CertificateTrustAuthority certsTrust) { - try { - certsTrust.checkTrust(certificates); - if (null != tsaCertificates) { - certsTrust.checkTrust(tsaCertificates); - } - trusted = true; - } catch (CertificateException e) { - trusted = false; - } - } - public Date getSigningTime() { return signingTime; } @@ -587,15 +455,8 @@ public class PKCS7Processor implements CertificateChain, JarVerifierConstant { this.tsaCertificates = tsaCertificates; } - /* - public static void main(String[] args) throws InvalidKeyException, CertificateException, NoSuchAlgorithmException, SignatureException, KeyStoreException, IOException { - byte buffer[] = new byte[65536]; - int len = System.in.read(buffer); - byte manifestBuff[] = new byte[65536]; - int rc = new FileInputStream("man").read(manifestBuff); - PKCS7Processor p7 = new PKCS7Processor(buffer, 0, len, manifestBuff, 0, rc); - System.out.println(p7.getSignerCertificate()); - System.out.println(p7.getCertificateChain()); - } - */ -}
\ No newline at end of file + public Certificate[] getTSACertificates() { + return (tsaCertificates == null) ? new Certificate[0] : tsaCertificates; + } + +} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignatureBlockProcessor.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignatureBlockProcessor.java new file mode 100644 index 000000000..a6bf865fc --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignatureBlockProcessor.java @@ -0,0 +1,484 @@ +/******************************************************************************* + * Copyright (c) 2007 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.osgi.internal.signedcontent; + +import java.io.IOException; +import java.io.InputStream; +import java.security.*; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.util.*; +import java.util.Map.Entry; +import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry; +import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile; +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.eclipse.osgi.signedcontent.SignerInfo; +import org.eclipse.osgi.util.NLS; + +public class SignatureBlockProcessor implements SignedContentConstants { + private final SignedBundleFile signedBundle; + private ArrayList signerInfos = new ArrayList(); + private HashMap contentMDResults = new HashMap(); + // map of tsa singers keyed by SignerInfo -> {tsa_SignerInfo, signingTime} + private HashMap tsaSignerInfos; + private final int supportFlags; + + public SignatureBlockProcessor(SignedBundleFile signedContent, int supportFlags) { + this.signedBundle = signedContent; + this.supportFlags = supportFlags; + } + + public SignedContentImpl process() throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException { + BundleFile wrappedBundleFile = signedBundle.getWrappedBundleFile(); + BundleEntry be = wrappedBundleFile.getEntry(META_INF_MANIFEST_MF); + if (be == null) + return createUnsignedContent(); + + // read all the signature block file names into a list + Enumeration en = wrappedBundleFile.getEntryPaths(META_INF); + List signers = new ArrayList(2); + while (en.hasMoreElements()) { + String name = (String) en.nextElement(); + if ((name.endsWith(DOT_DSA) || name.endsWith(DOT_RSA)) && name.indexOf('/') == name.lastIndexOf('/')) + signers.add(name); + } + + // this means the jar is not signed + if (signers.size() == 0) + return createUnsignedContent(); + + byte manifestBytes[] = readIntoArray(be); + // process the signers + Iterator iSigners = signers.iterator(); + for (int i = 0; iSigners.hasNext(); i++) + processSigner(wrappedBundleFile, manifestBytes, (String) iSigners.next()); + + // done processing now create a SingedContent to return + SignerInfo[] allSigners = (SignerInfo[]) signerInfos.toArray(new SignerInfo[signerInfos.size()]); + for (Iterator iResults = contentMDResults.entrySet().iterator(); iResults.hasNext();) { + Entry entry = (Entry) iResults.next(); + ArrayList[] value = (ArrayList[]) entry.getValue(); + SignerInfo[] entrySigners = (SignerInfo[]) value[0].toArray(new SignerInfo[value[0].size()]); + byte[][] entryResults = (byte[][]) value[1].toArray(new byte[value[1].size()][]); + entry.setValue(new Object[] {entrySigners, entryResults}); + } + SignedContentImpl result = new SignedContentImpl(allSigners, (supportFlags & SignedBundleHook.VERIFY_RUNTIME) != 0 ? contentMDResults : null); + result.setContent(signedBundle); + result.setTSASignerInfos(tsaSignerInfos); + return result; + } + + private SignedContentImpl createUnsignedContent() { + SignedContentImpl result = new SignedContentImpl(new SignerInfo[0], contentMDResults); + result.setContent(signedBundle); + return result; + } + + private void processSigner(BundleFile bf, byte[] manifestBytes, String signer) throws IOException, SignatureException, InvalidKeyException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException { + BundleEntry be = bf.getEntry(signer); + byte pkcs7Bytes[] = readIntoArray(be); + int dotIndex = signer.lastIndexOf('.'); + be = bf.getEntry(signer.substring(0, dotIndex) + DOT_SF); + byte sfBytes[] = readIntoArray(be); + + // Step 1, verify the .SF file is signed by the private key that corresponds to the public key + // in the .RSA/.DSA file + String baseFile = bf.getBaseFile() != null ? bf.getBaseFile().toString() : null; + PKCS7Processor processor = new PKCS7Processor(pkcs7Bytes, 0, pkcs7Bytes.length, signer, baseFile); + // call the Step 1 in the Jar File Verification algorithm + processor.verifySFSignature(sfBytes, 0, sfBytes.length); + // algorithm used + String digAlg = getDigAlgFromSF(sfBytes); + if (digAlg == null) + throw new SignatureException(NLS.bind(SignedContentMessages.SF_File_Parsing_Error, new String[] {bf.toString()})); + // get the digest results + // Process the Step 2 in the Jar File Verification algorithm + // Get the manifest out of the signature file and make sure + // it matches MANIFEST.MF + verifyManifestAndSingatureFile(manifestBytes, sfBytes); + + // create a SignerInfo with the processed information + SignerInfoImpl signerInfo = new SignerInfoImpl(processor.getCertificates(), null, digAlg); + if ((supportFlags & SignedBundleHook.VERIFY_RUNTIME) != 0) + // only populate the manifests digest information for verifying content at runtime + populateMDResults(manifestBytes, signerInfo); + signerInfos.add(signerInfo); + // check for tsa signers + Certificate[] tsaCerts = processor.getTSACertificates(); + Date signingTime = processor.getSigningTime(); + if (tsaCerts != null && signingTime != null) { + SignerInfoImpl tsaSignerInfo = new SignerInfoImpl(tsaCerts, null, digAlg); + if (tsaSignerInfos == null) + tsaSignerInfos = new HashMap(2); + tsaSignerInfos.put(signerInfo, new Object[] {tsaSignerInfo, signingTime}); + } + } + + /** + * Verify the digest listed in each entry in the .SF file with corresponding section in the manifest + */ + private void verifyManifestAndSingatureFile(byte[] manifestBytes, byte[] sfBytes) { + + String sf = new String(sfBytes); + sf = stripContinuations(sf); + + // check if there -Digest-Manfiest: header in the file + int off = sf.indexOf(digestManifestSearch); + if (off != -1) { + int start = sf.lastIndexOf('\n', off); + String manifestDigest = null; + if (start != -1) { + // Signature-Version has to start the file, so there + // should always be a newline at the start of + // Digest-Manifest + String digestName = sf.substring(start + 1, off); + if (digestName.equalsIgnoreCase(MD5_STR)) + manifestDigest = calculateDigest(getMessageDigest(MD5_STR), manifestBytes); + else if (digestName.equalsIgnoreCase(SHA1_STR)) + manifestDigest = calculateDigest(getMessageDigest(SHA1_STR), manifestBytes); + off += digestManifestSearchLen; + + // find out the index of first '\n' after the -Digest-Manifest: + int nIndex = sf.indexOf('\n', off); + String digestValue = sf.substring(off, nIndex - 1); + + // check if the the computed digest value of manifest file equals to the digest value in the .sf file + if (!digestValue.equals(manifestDigest)) { + Exception e = new SecurityException(NLS.bind(SignedContentMessages.Security_File_Is_Tampered, new String[] {signedBundle.getBaseFile().toString()})); + SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); + throw (SecurityException) e; + } + } + } + } + + private void populateMDResults(byte mfBuf[], SignerInfo signerInfo) throws NoSuchAlgorithmException { + // need to make a string from the MF file data bytes + String mfStr = new String(mfBuf); + + // start parsing each entry in the MF String + int entryStartOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME); + int length = mfStr.length(); + + while ((entryStartOffset != -1) && (entryStartOffset < length)) { + + // get the start of the next 'entry', i.e. the end of this entry + int entryEndOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME, entryStartOffset + 1); + if (entryEndOffset == -1) { + // if there is no next entry, then the end of the string + // is the end of this entry + entryEndOffset = mfStr.length(); + } + + // get the string for this entry only, since the entryStartOffset + // points to the '\n' befor the 'Name: ' we increase it by 1 + // this is guaranteed to not go past end-of-string and be less + // then entryEndOffset. + String entryStr = mfStr.substring(entryStartOffset + 1, entryEndOffset); + entryStr = stripContinuations(entryStr); + + // entry points to the start of the next 'entry' + String entryName = getEntryFileName(entryStr); + + // if we could retrieve an entry name, then we will extract + // digest type list, and the digest value list + if (entryName != null) { + + String aDigestLine = getDigestLine(entryStr, signerInfo.getMessageDigestAlgorithm()); + + if (aDigestLine != null) { + String msgDigestAlgorithm = getDigestAlgorithmFromString(aDigestLine); + if (!msgDigestAlgorithm.equalsIgnoreCase(signerInfo.getMessageDigestAlgorithm())) + continue; // TODO log error? + byte digestResult[] = getDigestResultsList(aDigestLine); + + // + // only insert this entry into the table if its + // "well-formed", + // i.e. only if we could extract its name, digest types, and + // digest-results + // + // sanity check, if the 2 lists are non-null, then their + // counts must match + // + // if ((msgDigestObj != null) && (digestResultsList != null) + // && (1 != digestResultsList.length)) { + // throw new RuntimeException( + // "Errors occurs when parsing the manifest file stream!"); //$NON-NLS-1$ + // } + ArrayList[] mdResult = (ArrayList[]) contentMDResults.get(entryName); + if (mdResult == null) { + mdResult = new ArrayList[2]; + mdResult[0] = new ArrayList(); + mdResult[1] = new ArrayList(); + contentMDResults.put(entryName, mdResult); + } + mdResult[0].add(signerInfo); + mdResult[1].add(digestResult); + } // could get lines of digest entries in this MF file entry + } // could retrieve entry name + // increment the offset to the ending entry... + entryStartOffset = entryEndOffset; + } + } + + private static byte[] getDigestResultsList(String digestLines) { + byte resultsList[] = null; + if (digestLines != null) { + // for each digest-line retrieve the digest result + // for (int i = 0; i < digestLines.length; i++) { + String sDigestLine = digestLines; + int indexDigest = sDigestLine.indexOf(MF_DIGEST_PART); + indexDigest += MF_DIGEST_PART.length(); + // if there is no data to extract for this digest value + // then we will fail... + if (indexDigest >= sDigestLine.length()) { + resultsList = null; + // break; + } + // now attempt to base64 decode the result + String sResult = sDigestLine.substring(indexDigest); + try { + resultsList = Base64.decode(sResult.getBytes()); + } catch (Throwable t) { + // malformed digest result, no longer processing this entry + resultsList = null; + } + } + return resultsList; + } + + private static String getDigestAlgorithmFromString(String digestLines) throws NoSuchAlgorithmException { + if (digestLines != null) { + // String sDigestLine = digestLines[i]; + int indexDigest = digestLines.indexOf(MF_DIGEST_PART); + String sDigestAlgType = digestLines.substring(0, indexDigest); + if (sDigestAlgType.equalsIgnoreCase(MD5_STR)) { + // remember the "algorithm type" + return MD5_STR; + } else if (sDigestAlgType.equalsIgnoreCase(SHA1_STR)) { + // remember the "algorithm type" object + return SHA1_STR; + } else { + // unknown algorithm type, we will stop processing this entry + // break; + throw new NoSuchAlgorithmException(NLS.bind(SignedContentMessages.Algorithm_Not_Supported, sDigestAlgType)); + } + } + return null; + } + + private static String getEntryFileName(String manifestEntry) { + // get the beginning of the name + int nameStart = manifestEntry.indexOf(MF_ENTRY_NAME); + if (nameStart == -1) { + return null; + } + // check where the name ends + int nameEnd = manifestEntry.indexOf('\n', nameStart); + if (nameEnd == -1) { + return null; + } + // if there is a '\r' before the '\n', then we'll strip it + if (manifestEntry.charAt(nameEnd - 1) == '\r') { + nameEnd--; + } + // get to the beginning of the actual name... + nameStart += MF_ENTRY_NAME.length(); + if (nameStart >= nameEnd) { + return null; + } + return manifestEntry.substring(nameStart, nameEnd); + } + + /** + * Returns the Base64 encoded digest of the passed set of bytes. + */ + private static String calculateDigest(MessageDigest digest, byte[] bytes) { + return new String(Base64.encode(digest.digest(bytes))); + } + + static synchronized MessageDigest getMessageDigest(String algorithm) { + try { + return MessageDigest.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) { + SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e); + } + return null; + } + + /** + * Read the .SF file abd assuming that same digest algorithm will be used through out the whole + * .SF file. That digest algorithm name in the last entry will be returned. + * + * @param SFBuf a .SF file in bytes + * @return the digest algorithm name used in the .SF file + */ + private static String getDigAlgFromSF(byte SFBuf[]) { + // need to make a string from the MF file data bytes + String mfStr = new String(SFBuf); + String entryStr = null; + + // start parsing each entry in the MF String + int entryStartOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME); + int length = mfStr.length(); + + while ((entryStartOffset != -1) && (entryStartOffset < length)) { + + // get the start of the next 'entry', i.e. the end of this entry + int entryEndOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME, entryStartOffset + 1); + if (entryEndOffset == -1) { + // if there is no next entry, then the end of the string + // is the end of this entry + entryEndOffset = mfStr.length(); + } + + // get the string for this entry only, since the entryStartOffset + // points to the '\n' befor the 'Name: ' we increase it by 1 + // this is guaranteed to not go past end-of-string and be less + // then entryEndOffset. + entryStr = mfStr.substring(entryStartOffset + 1, entryEndOffset); + entryStr = stripContinuations(entryStr); + break; + } + + if (entryStr != null) { + // process the entry to retrieve the digest algorith name + String digestLine = getDigestLine(entryStr, null); + + // throw parsing + return getMessageDigestName(digestLine); + } + return null; + } + + /** + * + * @param manifestEntry contains a single MF file entry of the format + * "Name: foo" + * "MD5-Digest: [base64 encoded MD5 digest data]" + * "SHA1-Digest: [base64 encoded SHA1 digest dat]" + * + * @param desireDigestAlg a string representing the desire digest value to be returned if there are + * multiple digest lines. + * If this value is null, return whatever digest value is in the entry. + * + * @return this function returns a digest line based on the desire digest algorithm value + * (since only MD5 and SHA1 are recognized here), + * or a 'null' will be returned if none of the digest algorithms + * were recognized. + */ + private static String getDigestLine(String manifestEntry, String desireDigestAlg) { + String result = null; + + // find the first digest line + int indexDigest = manifestEntry.indexOf(MF_DIGEST_PART); + // if we didn't find any digests at all, then we are done + if (indexDigest == -1) + return null; + + // while we continue to find digest entries + // note: in the following loop we bail if any of the lines + // look malformed... + while (indexDigest != -1) { + // see where this digest line begins (look to left) + int indexStart = manifestEntry.lastIndexOf('\n', indexDigest); + if (indexStart == -1) + return null; + // see where it ends (look to right) + int indexEnd = manifestEntry.indexOf('\n', indexDigest); + if (indexEnd == -1) + return null; + // strip off ending '\r', if any + int indexEndToUse = indexEnd; + if (manifestEntry.charAt(indexEndToUse - 1) == '\r') + indexEndToUse--; + // indexStart points to the '\n' before this digest line + int indexStartToUse = indexStart + 1; + if (indexStartToUse >= indexEndToUse) + return null; + + // now this may be a valid digest line, parse it a bit more + // to see if this is a preferred digest algorithm + String digestLine = manifestEntry.substring(indexStartToUse, indexEndToUse); + String digAlg = getMessageDigestName(digestLine); + if (desireDigestAlg != null) { + if (desireDigestAlg.equalsIgnoreCase(digAlg)) + return digestLine; + } + + // desireDigestAlg is null, always return the digestLine + result = digestLine; + + // iterate to next digest line in this entry + indexDigest = manifestEntry.indexOf(MF_DIGEST_PART, indexEnd); + } + + // if we couldn't find any digest lines, then we are done + return result; + } + + /** + * Return the Message Digest name + * + * @param digLine the message digest line is in the following format. That is in the + * following format: + * DIGEST_NAME-digest: digest value + * @return a string representing a message digest. + */ + private static String getMessageDigestName(String digLine) { + String rtvValue = null; + if (digLine != null) { + int indexDigest = digLine.indexOf(MF_DIGEST_PART); + if (indexDigest != -1) { + rtvValue = digLine.substring(0, indexDigest); + } + } + return rtvValue; + } + + private static String stripContinuations(String entry) { + if (entry.indexOf("\n ") < 0) //$NON-NLS-1$ + return entry; + StringBuffer buffer = new StringBuffer(entry.length()); + int cont = entry.indexOf("\n "); //$NON-NLS-1$ + int start = 0; + while (cont >= 0) { + buffer.append(entry.substring(start, cont - 1)); + start = cont + 2; + cont = cont + 2 < entry.length() ? entry.indexOf("\n ", cont + 2) : -1; //$NON-NLS-1$ + } + // get the last one continuation + if (start < entry.length()) + buffer.append(entry.substring(start)); + return buffer.toString(); + } + + private static byte[] readIntoArray(BundleEntry be) throws IOException { + int size = (int) be.getSize(); + InputStream is = be.getInputStream(); + byte b[] = new byte[size]; + int rc = readFully(is, b); + if (rc != size) { + throw new IOException("Couldn't read all of " + be.getName() + ": " + rc + " != " + size); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return b; + } + + private static int readFully(InputStream is, byte b[]) throws IOException { + int count = b.length; + int offset = 0; + int rc; + while ((rc = is.read(b, offset, count)) > 0) { + count -= rc; + offset += rc; + } + return offset; + } +} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedBundleFile.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedBundleFile.java new file mode 100644 index 000000000..24d7c1f87 --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedBundleFile.java @@ -0,0 +1,209 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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.osgi.internal.signedcontent; + +import java.io.*; +import java.net.URL; +import java.security.*; +import java.security.cert.*; +import java.security.cert.Certificate; +import java.util.Date; +import java.util.Enumeration; +import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry; +import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile; +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.eclipse.osgi.service.security.TrustEngine; +import org.eclipse.osgi.signedcontent.*; +import org.eclipse.osgi.util.NLS; + +/** + * This class wraps a Repository of classes and resources to check and enforce + * signatures. It requires full signing of the manifest by all signers. If no + * signatures are found, the classes and resources are retrieved without checks. + */ +public class SignedBundleFile extends BundleFile implements SignedContentConstants, SignedContent { + private BundleFile wrappedBundleFile; + SignedContentImpl signedContent; + private final int supportFlags; + + SignedBundleFile(SignedContentImpl signedContent, int supportFlags) { + this.signedContent = signedContent; + this.supportFlags = supportFlags; + } + + void setBundleFile(BundleFile bundleFile) throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException { + wrappedBundleFile = bundleFile; + if (signedContent == null) { + SignatureBlockProcessor signatureProcessor = new SignatureBlockProcessor(this, supportFlags); + signedContent = signatureProcessor.process(); + if (signedContent != null) + determineTrust(signedContent, supportFlags); + } + } + + static void determineTrust(SignedContentImpl trustedContent, int supportFlags) { + TrustEngine[] engines = null; + SignerInfo[] signers = trustedContent.getSignerInfos(); + for (int i = 0; i < signers.length; i++) { + // first check if we need to find an anchor + if (signers[i].getTrustAnchor() == null) { + // no anchor set ask the trust engines + if (engines == null) + engines = SignedBundleHook.getTrustEngines(); + // check trust of singer certs + Certificate[] signerCerts = signers[i].getCertificateChain(); + ((SignerInfoImpl) signers[i]).setTrustAnchor(findTrustAnchor(signerCerts, engines, supportFlags)); + // if signer has a tsa check trust of tsa certs + SignerInfo tsaSignerInfo = trustedContent.getTSASignerInfo(signers[i]); + if (tsaSignerInfo != null) { + Certificate[] tsaCerts = tsaSignerInfo.getCertificateChain(); + ((SignerInfoImpl) tsaSignerInfo).setTrustAnchor(findTrustAnchor(tsaCerts, engines, supportFlags)); + } + } + } + } + + private static Certificate findTrustAnchor(Certificate[] certs, TrustEngine[] engines, int supportFlags) { + if ((supportFlags & SignedBundleHook.VERIFY_TRUST) == 0) + // we are not searching the engines; in this case we just assume the root cert is trusted + return certs != null && certs.length > 0 ? certs[certs.length - 1] : null; + for (int i = 0; i < engines.length; i++) { + try { + Certificate anchor = engines[i].findTrustAnchor(certs); + if (anchor != null) + // found an anchor + return anchor; + } catch (IOException e) { + // log the exception and continue + SignedBundleHook.log("TrustEngine failure: " + engines[i].getName(), FrameworkLogEntry.WARNING, e); //$NON-NLS-1$ + } + } + return null; + } + + public File getFile(String path, boolean nativeCode) { + return wrappedBundleFile.getFile(path, nativeCode); + } + + public BundleEntry getEntry(String path) { + // strip off leading slashes so we can ensure the path matches the one provided in the manifest. + if (path.length() > 0 && path.charAt(0) == '/') + path = path.substring(1); + BundleEntry be = wrappedBundleFile.getEntry(path); + if ((supportFlags & SignedBundleHook.VERIFY_RUNTIME) == 0 || signedContent == null) + return be; + if (path.startsWith(META_INF)) + return be; + if (be == null) { + // double check that no signer thinks it should exist + SignedContentEntry signedEntry = signedContent.getSignedEntry(path); + if (signedEntry != null) + throw new SecurityException(NLS.bind(SignedContentMessages.file_is_removed_from_jar, getBaseFile().toString(), path)); + return null; + } + return new SignedBundleEntry(be); + } + + public Enumeration getEntryPaths(String path) { + return wrappedBundleFile.getEntryPaths(path); + } + + public void close() throws IOException { + wrappedBundleFile.close(); + } + + public void open() throws IOException { + wrappedBundleFile.open(); + } + + public boolean containsDir(String dir) { + return wrappedBundleFile.containsDir(dir); + } + + public File getBaseFile() { + return wrappedBundleFile.getBaseFile(); + } + + class SignedBundleEntry extends BundleEntry { + BundleEntry nestedEntry; + + SignedBundleEntry(BundleEntry nestedEntry) { + this.nestedEntry = nestedEntry; + } + + public InputStream getInputStream() throws IOException { + InputStream in = signedContent.getDigestInputStream(nestedEntry); + if (in == null) + throw new SecurityException("Corrupted file: the digest does not exist for the file " + nestedEntry.getName()); //$NON-NLS-1$ + return in; + } + + public long getSize() { + return nestedEntry.getSize(); + } + + public String getName() { + return nestedEntry.getName(); + } + + public long getTime() { + return nestedEntry.getTime(); + } + + public URL getLocalURL() { + return nestedEntry.getLocalURL(); + } + + public URL getFileURL() { + return nestedEntry.getFileURL(); + } + + } + + BundleFile getWrappedBundleFile() { + return wrappedBundleFile; + } + + SignedContentImpl getSignedContent() { + return signedContent; + } + + public SignedContentEntry[] getSignedEntries() { + return signedContent == null ? null : signedContent.getSignedEntries(); + } + + public SignedContentEntry getSignedEntry(String name) { + return signedContent == null ? null : signedContent.getSignedEntry(name); + } + + public SignerInfo[] getSignerInfos() { + return signedContent == null ? null : signedContent.getSignerInfos(); + } + + public Date getSigningTime(SignerInfo signerInfo) { + return signedContent == null ? null : signedContent.getSigningTime(signerInfo); + } + + public SignerInfo getTSASignerInfo(SignerInfo signerInfo) { + return signedContent == null ? null : signedContent.getTSASignerInfo(signerInfo); + } + + public boolean isSigned() { + return signedContent == null ? false : signedContent.isSigned(); + } + + public void checkValidity(SignerInfo signerInfo) throws CertificateExpiredException, CertificateNotYetValidException { + if (signedContent != null) + signedContent.checkValidity(signerInfo); + } + +} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedBundleHook.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedBundleHook.java new file mode 100644 index 000000000..8cf3f769b --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedBundleHook.java @@ -0,0 +1,304 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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.osgi.internal.signedcontent; + +import java.io.File; +import java.io.IOException; +import java.net.*; +import java.security.*; +import java.security.cert.CertificateException; +import java.util.Hashtable; +import java.util.Properties; +import org.eclipse.osgi.baseadaptor.*; +import org.eclipse.osgi.baseadaptor.bundlefile.*; +import org.eclipse.osgi.baseadaptor.hooks.AdaptorHook; +import org.eclipse.osgi.baseadaptor.hooks.BundleFileWrapperFactoryHook; +import org.eclipse.osgi.framework.adaptor.BundleData; +import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor; +import org.eclipse.osgi.framework.internal.core.AbstractBundle; +import org.eclipse.osgi.framework.internal.core.FrameworkProperties; +import org.eclipse.osgi.framework.log.FrameworkLog; +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.eclipse.osgi.internal.provisional.verifier.CertificateVerifierFactory; +import org.eclipse.osgi.internal.service.security.DefaultAuthorizationEngine; +import org.eclipse.osgi.internal.service.security.KeyStoreTrustEngine; +import org.eclipse.osgi.service.security.AuthorizationEngine; +import org.eclipse.osgi.service.security.TrustEngine; +import org.eclipse.osgi.signedcontent.SignedContent; +import org.eclipse.osgi.signedcontent.SignedContentFactory; +import org.eclipse.osgi.util.ManifestElement; +import org.osgi.framework.*; +import org.osgi.util.tracker.ServiceTracker; + +/** + * Implements signed bundle hook support for the framework + */ +public class SignedBundleHook implements AdaptorHook, BundleFileWrapperFactoryHook, HookConfigurator, SignedContentFactory { + static final int VERIFY_CERTIFICATE = 0x01; + static final int VERIFY_TRUST = 0x02; + static final int VERIFY_RUNTIME = 0x04; + static final int VERIFY_AUTHORITY = 0x08; + static final int VERIFY_ALL = VERIFY_CERTIFICATE | VERIFY_TRUST | VERIFY_RUNTIME | VERIFY_AUTHORITY; + private static String SUPPORT_CERTIFICATE = "certificate"; //$NON-NLS-1$ + private static String SUPPORT_TRUST = "trust"; //$NON-NLS-1$ + private static String SUPPORT_RUNTIME = "runtime"; //$NON-NLS-1$ + private static String SUPPORT_AUTHORITY = "authority"; //$NON-NLS-1$ + private static String SUPPORT_ALL = "all"; //$NON-NLS-1$ + private static String SUPPORT_TRUE = "true"; //$NON-NLS-1$ + + //TODO: comes from configuration!; + private static String CACERTS_PATH = System.getProperty("java.home") + File.separatorChar + "lib" + File.separatorChar + "security" + File.separatorChar + "cacerts"; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$//$NON-NLS-4$ + private static String CACERTS_TYPE = "JKS"; //$NON-NLS-1$ + private static ServiceTracker trustEngineTracker; + private static BaseAdaptor ADAPTOR; + private static String SIGNED_BUNDLE_SUPPORT = "osgi.support.signature.verify"; //$NON-NLS-1$ + private static String SIGNED_CONTENT_SUPPORT = "osgi.signedcontent.support"; //$NON-NLS-1$ + private static String OSGI_KEYSTORE = "osgi.framework.keystore"; //$NON-NLS-1$ + private static int supportSignedBundles; + private TrustEngineListener trustEngineListener; + private BundleInstallListener installListener; + private ServiceRegistration signedContentFactoryReg; + private ServiceRegistration systemTrustEngineReg; + private ServiceRegistration defaultAuthEngineReg; + private ServiceRegistration osgiTrustEngineReg; + private ServiceRegistration legacyFactoryReg; + + public boolean matchDNChain(String pattern, String dnChain[]) { + boolean satisfied = false; + if (dnChain != null) { + for (int i = 0; i < dnChain.length; i++) + if (DNChainMatching.match(dnChain[i], pattern)) { + satisfied = true; + break; + } + } + return satisfied; + } + + public void initialize(BaseAdaptor adaptor) { + SignedBundleHook.ADAPTOR = adaptor; + } + + public void frameworkStart(BundleContext context) throws BundleException { + // check if load time authority is enabled + if ((supportSignedBundles & VERIFY_AUTHORITY) != 0) { + // install the default bundle install listener + installListener = new BundleInstallListener(); + context.addBundleListener(installListener); + // register the default authorization engine + Hashtable properties = new Hashtable(7); + properties.put(Constants.SERVICE_RANKING, new Integer(Integer.MIN_VALUE)); + properties.put(SignedContentConstants.AUTHORIZATION_ENGINE, SignedContentConstants.DEFAULT_AUTHORIZATION_ENGINE); + defaultAuthEngineReg = context.registerService(AuthorizationEngine.class.getName(), new DefaultAuthorizationEngine(context, ADAPTOR.getState()), properties); + } + + // always register the trust engine + Hashtable properties = new Hashtable(7); + properties.put(Constants.SERVICE_RANKING, new Integer(Integer.MIN_VALUE)); + properties.put(SignedContentConstants.TRUST_ENGINE, SignedContentConstants.DEFAULT_TRUST_ENGINE); + KeyStoreTrustEngine systemTrustEngine = new KeyStoreTrustEngine(CACERTS_PATH, CACERTS_TYPE, null); + systemTrustEngineReg = context.registerService(TrustEngine.class.getName(), systemTrustEngine, properties); + String osgiTrustPath = context.getProperty(OSGI_KEYSTORE); + if (osgiTrustPath != null) { + try { + URL url = new URL(osgiTrustPath); + if ("file".equals(url.getProtocol())) { //$NON-NLS-1$ + String path = url.getPath(); + osgiTrustEngineReg = context.registerService(TrustEngine.class.getName(), new KeyStoreTrustEngine(path, CACERTS_TYPE, null), null); + } + } catch (MalformedURLException e) { + SignedBundleHook.log("Invalid setting for " + OSGI_KEYSTORE, FrameworkLogEntry.WARNING, e); //$NON-NLS-1$ + } + } + if ((supportSignedBundles & VERIFY_TRUST) != 0) + // initialize the trust engine listener only if trust is being established with a trust engine + trustEngineListener = new TrustEngineListener(context); + // always register the signed content factory + signedContentFactoryReg = context.registerService(SignedContentFactory.class.getName(), this, null); + legacyFactoryReg = context.registerService(CertificateVerifierFactory.class.getName(), new LegacyVerifierFactory(this), null); + } + + public void frameworkStop(BundleContext context) throws BundleException { + if (legacyFactoryReg != null) { + legacyFactoryReg.unregister(); + legacyFactoryReg = null; + } + if (signedContentFactoryReg != null) { + signedContentFactoryReg.unregister(); + signedContentFactoryReg = null; + } + if (systemTrustEngineReg != null) { + systemTrustEngineReg.unregister(); + systemTrustEngineReg = null; + } + if (osgiTrustEngineReg != null) { + osgiTrustEngineReg.unregister(); + osgiTrustEngineReg = null; + } + if (defaultAuthEngineReg != null) { + defaultAuthEngineReg.unregister(); + defaultAuthEngineReg = null; + } + if (trustEngineListener != null) { + trustEngineListener.stopTrustEngineListener(); + trustEngineListener = null; + } + if (installListener != null) { + context.removeBundleListener(installListener); + installListener = null; + } + if (trustEngineTracker != null) { + trustEngineTracker.close(); + trustEngineTracker = null; + } + } + + public void frameworkStopping(BundleContext context) { + // do nothing + } + + public void addProperties(Properties properties) { + // do nothing + } + + public URLConnection mapLocationToURLConnection(String location) throws IOException { + return null; + } + + public void handleRuntimeError(Throwable error) { + // do nothing + } + + public FrameworkLog createFrameworkLog() { + return null; + } + + public BundleFile wrapBundleFile(BundleFile bundleFile, Object content, BaseData data, boolean base) { + try { + if (bundleFile != null) { + SignedStorageHook hook = (SignedStorageHook) data.getStorageHook(SignedStorageHook.KEY); + SignedBundleFile signedBaseFile; + if (base && hook != null) { + signedBaseFile = new SignedBundleFile(hook.signedContent, supportSignedBundles); + if (hook.signedContent == null) { + signedBaseFile.setBundleFile(bundleFile); + SignedContentImpl signedContent = signedBaseFile.getSignedContent(); + hook.signedContent = signedContent != null && signedContent.isSigned() ? signedContent : null; + } + } else + signedBaseFile = new SignedBundleFile(null, supportSignedBundles); + signedBaseFile.setBundleFile(bundleFile); + SignedContentImpl signedContent = signedBaseFile.getSignedContent(); + if (signedContent != null && signedContent.isSigned()) { + // only use the signed file if there are certs + signedContent.setContent(signedBaseFile); + bundleFile = signedBaseFile; + } + } + } catch (IOException e) { + SignedBundleHook.log("Bad bundle file: " + bundleFile.getBaseFile(), FrameworkLogEntry.WARNING, e); //$NON-NLS-1$ + } catch (GeneralSecurityException e) { + SignedBundleHook.log("Bad bundle file: " + bundleFile.getBaseFile(), FrameworkLogEntry.WARNING, e); //$NON-NLS-1$ + } + return bundleFile; + } + + public void addHooks(HookRegistry hookRegistry) { + hookRegistry.addAdaptorHook(this); + String[] support = ManifestElement.getArrayFromList(FrameworkProperties.getProperty(SIGNED_CONTENT_SUPPORT, FrameworkProperties.getProperty(SIGNED_BUNDLE_SUPPORT)), ","); //$NON-NLS-1$ + for (int i = 0; i < support.length; i++) { + if (SUPPORT_CERTIFICATE.equals(support[i])) + supportSignedBundles |= VERIFY_CERTIFICATE; + else if (SUPPORT_TRUST.equals(support[i])) + supportSignedBundles |= VERIFY_CERTIFICATE | VERIFY_TRUST; + else if (SUPPORT_RUNTIME.equals(support[i])) + supportSignedBundles |= VERIFY_CERTIFICATE | VERIFY_RUNTIME; + else if (SUPPORT_AUTHORITY.equals(support[i])) + supportSignedBundles |= VERIFY_CERTIFICATE | VERIFY_TRUST | VERIFY_AUTHORITY; + else if (SUPPORT_TRUE.equals(support[i]) || SUPPORT_ALL.equals(support[i])) + supportSignedBundles |= VERIFY_ALL; + } + if ((supportSignedBundles & VERIFY_CERTIFICATE) != 0) { + hookRegistry.addStorageHook(new SignedStorageHook()); + hookRegistry.addBundleFileWrapperFactoryHook(this); + } + } + + public SignedContent getSignedContent(File content) throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException { + if (content == null) + throw new IllegalArgumentException("null content"); //$NON-NLS-1$ + BundleFile contentBundleFile; + if (content.isDirectory()) + contentBundleFile = new DirBundleFile(content); + else + contentBundleFile = new ZipBundleFile(content, null); + SignedBundleFile result = new SignedBundleFile(null, VERIFY_ALL); + result.setBundleFile(contentBundleFile); + return result.getSignedContent(); + } + + public SignedContent getSignedContent(Bundle bundle) throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException, IllegalArgumentException { + BundleData data = ((AbstractBundle) bundle).getBundleData(); + if (!(data instanceof BaseData)) + throw new IllegalArgumentException("Invalid bundle object. No BaseData found."); //$NON-NLS-1$ + SignedStorageHook hook = (SignedStorageHook) ((BaseData) data).getStorageHook(SignedStorageHook.KEY); + SignedContent result = hook != null ? hook.signedContent : null; + if (result != null) + return result; // just reuse the signed content the storage hook + return getSignedContent(((BaseData) data).getBundleFile().getBaseFile()); // must create a new signed content using the raw file + } + + public static void log(String msg, int severity, Throwable t) { + if (SignedBundleHook.ADAPTOR == null) { + System.err.println(msg); + t.printStackTrace(); + return; + } + FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, severity, 0, msg, 0, t, null); + SignedBundleHook.ADAPTOR.getFrameworkLog().log(entry); + } + + static BundleContext getContext() { + if (ADAPTOR == null) + return null; + return ADAPTOR.getContext(); + } + + static TrustEngine[] getTrustEngines() { + // find all the trust engines available + BundleContext context = SignedBundleHook.getContext(); + if (context == null) + return new TrustEngine[0]; + if (trustEngineTracker == null) { + // read the trust provider security property + String trustEngineProp = FrameworkProperties.getProperty(SignedContentConstants.TRUST_ENGINE); + Filter filter = null; + if (trustEngineProp != null) + try { + filter = FrameworkUtil.createFilter("(&(" + Constants.OBJECTCLASS + "=" + TrustEngine.class.getName() + ")(" + SignedContentConstants.TRUST_ENGINE + "=" + trustEngineProp + "))"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$//$NON-NLS-5$ + } catch (InvalidSyntaxException e) { + SignedBundleHook.log("Invalid trust engine filter", FrameworkLogEntry.WARNING, e); //$NON-NLS-1$ + } + if (filter != null) { + trustEngineTracker = new ServiceTracker(context, filter, null); + } else + trustEngineTracker = new ServiceTracker(context, TrustEngine.class.getName(), null); + trustEngineTracker.open(); + } + Object[] services = trustEngineTracker.getServices(); + if (services != null) { + TrustEngine[] engines = new TrustEngine[services.length]; + System.arraycopy(services, 0, engines, 0, services.length); + return engines; + } + return new TrustEngine[0]; + } +} diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/JarVerifierConstant.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedContentConstants.java index 76ad75702..fef1c4e5a 100644 --- a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/JarVerifierConstant.java +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedContentConstants.java @@ -7,9 +7,9 @@ * Contributors: IBM Corporation - initial API and implementation ******************************************************************************/ -package org.eclipse.osgi.internal.verifier; +package org.eclipse.osgi.internal.signedcontent; -public interface JarVerifierConstant { +public interface SignedContentConstants { public static final String SHA1_STR = "SHA1"; //$NON-NLS-1$ public static final String MD5_STR = "MD5"; //$NON-NLS-1$ @@ -39,11 +39,15 @@ public interface JarVerifierConstant { public static final int DSA_OID[] = {1, 2, 840, 10040, 4, 1}; public static final int RSA_OID[] = {1, 2, 840, 113549, 1, 1, 1}; - // constant for certs chain trust service - public static final String TRUST_AUTHORITY = "osgi.certificate.trust.authority"; //$NON-NLS-1$ - public static final Object DEFAULT_TRUST_AUTHORITY = "org.eclipse.osgi"; //$NON-NLS-1$ + // constant for trust engine service + public static final String TRUST_ENGINE = "osgi.signedcontent.trust.engine"; //$NON-NLS-1$ + public static final Object DEFAULT_TRUST_ENGINE = "org.eclipse.osgi"; //$NON-NLS-1$ - // consttant for the timestamp related + // constants for authorization engine service + public static final String AUTHORIZATION_ENGINE = "osgi.signedcontent.authorization.engine"; //$NON-NLS-1$ + public static final Object DEFAULT_AUTHORIZATION_ENGINE = "org.eclipse.osgi"; //$NON-NLS-1$ + + // constant for the timestamp related public static final int TIMESTAMP_OID[] = {1, 2, 840, 113549, 1, 9, 16, 2, 14}; public static final int TIMESTAMP_TST_OID[] = {1, 2, 840, 113549, 1, 9, 16, 1, 4}; public static final int SIGNING_TIME[] = {1, 2, 840, 113549, 1, 9, 5}; diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedContentImpl.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedContentImpl.java new file mode 100644 index 000000000..5d6ee3384 --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedContentImpl.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * Copyright (c) 2007 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.osgi.internal.signedcontent; + +import java.io.IOException; +import java.io.InputStream; +import java.security.cert.*; +import java.util.*; +import java.util.Map.Entry; +import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry; +import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile; +import org.eclipse.osgi.signedcontent.*; +import org.eclipse.osgi.util.NLS; + +public class SignedContentImpl implements SignedContent { + final static SignerInfo[] EMPTY_SIGNERINFO = new SignerInfo[0]; + // the content which is signed + volatile SignedBundleFile content; // TODO can this be more general? + // the content entry md results used for entry content verification + // keyed by entry path -> {SignerInfo[] infos, byte[][] results)} + private final HashMap contentMDResults; + private final SignerInfo[] signerInfos; + // map of tsa singers keyed by SignerInfo -> {tsa_SignerInfo, signingTime} + private HashMap tsaSignerInfos; + volatile private boolean checkedValid = false; + + public SignedContentImpl(SignerInfo[] signerInfos, HashMap contentMDResults) { + this.signerInfos = signerInfos == null ? EMPTY_SIGNERINFO : signerInfos; + this.contentMDResults = contentMDResults; + } + + public SignedContentEntry[] getSignedEntries() { + if (contentMDResults == null) + return new SignedContentEntry[0]; + ArrayList results = new ArrayList(contentMDResults.size()); + for (Iterator iMDResults = contentMDResults.entrySet().iterator(); iMDResults.hasNext();) { + Entry entry = (Entry) iMDResults.next(); + String entryName = (String) entry.getKey(); + Object[] mdResult = (Object[]) entry.getValue(); + results.add(new SignedContentEntryImpl(entryName, (SignerInfo[]) mdResult[0])); + } + return (SignedContentEntry[]) results.toArray(new SignedContentEntry[results.size()]); + } + + public SignedContentEntry getSignedEntry(String name) { + if (contentMDResults == null) + return null; + Object[] mdResult = (Object[]) contentMDResults.get(name); + return mdResult == null ? null : new SignedContentEntryImpl(name, (SignerInfo[]) mdResult[0]); + } + + public SignerInfo[] getSignerInfos() { + return signerInfos; + } + + public Date getSigningTime(SignerInfo signerInfo) { + if (tsaSignerInfos == null) + return null; + Object[] tsaInfo = (Object[]) tsaSignerInfos.get(signerInfo); + return tsaInfo == null ? null : (Date) tsaInfo[1]; + } + + public SignerInfo getTSASignerInfo(SignerInfo signerInfo) { + if (tsaSignerInfos == null) + return null; + Object[] tsaInfo = (Object[]) tsaSignerInfos.get(signerInfo); + return tsaInfo == null ? null : (SignerInfo) tsaInfo[0]; + } + + public boolean isSigned() { + return signerInfos.length > 0; + } + + public void checkValidity(SignerInfo signer) throws CertificateExpiredException, CertificateNotYetValidException { + Date signingTime = getSigningTime(signer); + if (checkedValid) + return; + Certificate[] certs = signer.getCertificateChain(); + for (int i = 0; i < certs.length; i++) { + if (!(certs[i] instanceof X509Certificate)) + continue; + if (signingTime == null) + ((X509Certificate) certs[i]).checkValidity(); + else + ((X509Certificate) certs[i]).checkValidity(signingTime); + } + checkedValid = true; + } + + void setContent(SignedBundleFile content) { + this.content = content; + } + + void setTSASignerInfos(HashMap tsaSignerInfos) { + this.tsaSignerInfos = tsaSignerInfos; + } + + void addTSASignerInfo(SignerInfo baseInfo, SignerInfo tsaSignerInfo, Date signingTime) { + // sanity check to make sure the baseInfo is here + if (!containsInfo(baseInfo)) + throw new IllegalArgumentException("The baseInfo is not found"); //$NON-NLS-1$ + if (tsaSignerInfos == null) + tsaSignerInfos = new HashMap(signerInfos.length); + tsaSignerInfos.put(baseInfo, new Object[] {tsaSignerInfo, signingTime}); + } + + HashMap getContentMDResults() { + return contentMDResults; + } + + private boolean containsInfo(SignerInfo signerInfo) { + for (int i = 0; i < signerInfos.length; i++) + if (signerInfo == signerInfos[i]) + return true; + return false; + } + + InputStream getDigestInputStream(BundleEntry nestedEntry) throws IOException { + if (contentMDResults == null) + return nestedEntry.getInputStream(); + Object[] mdResult = (Object[]) contentMDResults.get(nestedEntry.getName()); + if (mdResult == null) + return null; + return new DigestedInputStream(nestedEntry, content, (SignerInfo[]) mdResult[0], (byte[][]) mdResult[1], nestedEntry.getSize()); + } + + public class SignedContentEntryImpl implements SignedContentEntry { + private final String entryName; + private final SignerInfo[] entrySigners; + + public SignedContentEntryImpl(String entryName, SignerInfo[] entrySigners) { + this.entryName = entryName; + this.entrySigners = entrySigners == null ? EMPTY_SIGNERINFO : entrySigners; + } + + public String getName() { + return entryName; + } + + public SignerInfo[] getSignerInfos() { + return entrySigners; + } + + public boolean isSigned() { + return entrySigners.length > 0; + } + + public void verify() throws IOException, InvalidContentException { + BundleFile currentContent = content; + if (currentContent == null) + throw new InvalidContentException("The content was not set", null); //$NON-NLS-1$ + BundleEntry entry = currentContent.getEntry(entryName); + if (entry == null) + throw new InvalidContentException(NLS.bind(SignedContentMessages.file_is_removed_from_jar, currentContent.getBaseFile().toString(), entryName), null); + entry.getBytes(); + } + } +} diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/JarVerifierMessages.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.java index 7870821b2..de6d56511 100644 --- a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/JarVerifierMessages.java +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2006 IBM Corporation and others. + * Copyright (c) 2006, 2007 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 @@ -9,50 +9,39 @@ * IBM Corporation - initial API and implementation *******************************************************************************/ -package org.eclipse.osgi.internal.verifier; +package org.eclipse.osgi.internal.signedcontent; import org.eclipse.osgi.util.NLS; -public class JarVerifierMessages extends NLS { +public class SignedContentMessages extends NLS { // Jar file is tampered - public static String Jar_Is_Tampered; public static String file_is_removed_from_jar; public static String File_In_Jar_Is_Tampered; public static String Security_File_Is_Tampered; public static String Signature_Not_Verify; - public static String Signature_Not_Verify_1; // Jar file parsing public static String SF_File_Parsing_Error; // PKCS7 parsing errors public static String PKCS7_SignerInfo_Version_Not_Supported; - public static String PKCS7_Cert_Excep; - public static String PKCS7_No_Such_Algorithm; + public static String PKCS7_Invalid_File; public static String PKCS7_Parse_Signing_Time; - public static String PKCS7_Parse_Signing_Time_1; - - // validate certificate chain - public static String Validate_Certs_Certificate_Exception; // Security Exceptions public static String Algorithm_Not_Supported; - public static String No_Such_Algorithm_Excep; - public static String No_Such_Provider_Excep; - public static String Invalid_Key_Exception; - - // Certs Trust Determination - public static String Cert_Verifier_Illegal_Args; - public static String Cert_Verifier_Not_Trusted; - public static String Cert_Verifier_Add_Certs; - - // private static final String BUNDLE_PACKAGE = JarVerifierMessages.class.getPackage().getName() + "."; - private static final String BUNDLE_PACKAGE = "org.eclipse.osgi.internal.verifier."; //$NON-NLS-1$ - private static final String BUNDLE_FILENAME = "JarVerifierMessages"; //$NON-NLS-1$ + + public static String Default_Trust_Keystore_Load_Failed; + public static String Default_Trust_Read_Only; + public static String Default_Trust_Cert_Not_Found; + + // private static final String BUNDLE_PACKAGE = SignedContentMessages.class.getPackage().getName() + "."; + private static final String BUNDLE_PACKAGE = "org.eclipse.osgi.internal.signedcontent."; //$NON-NLS-1$ + private static final String BUNDLE_FILENAME = "SignedContentMessages"; //$NON-NLS-1$ private static final String BUNDLE_NAME = BUNDLE_PACKAGE + BUNDLE_FILENAME; static { - NLS.initializeMessages(BUNDLE_NAME, JarVerifierMessages.class); + NLS.initializeMessages(BUNDLE_NAME, SignedContentMessages.class); } } diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.properties b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.properties new file mode 100644 index 000000000..874e99bce --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.properties @@ -0,0 +1,32 @@ +############################################################################### +# Copyright (c) 2006 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 +############################################################################### + +# Jar file tampered error messages +file_is_removed_from_jar = A file \"{0}\" has been removed from the jar: {1} +File_In_Jar_Is_Tampered = The file \"{0}\" in the jar \"{1}\" has been tampered! +Security_File_Is_Tampered = Either the manifest file or the signature file has been tampered in this jar: {0} +Signature_Not_Verify = The signature cannot be verified for the signer \"{0}\" in this jar: {1} + +# Jar file parsing +SF_File_Parsing_Error = Error occurs parsing the .SF file to find out the digest algorithm in this bundle: {0} + +# PKCS7 parsing errors +PKCS7_SignerInfo_Version_Not_Supported = The SignerInfo version other than 1 is not supported! +PKCS7_Invalid_File = The file \"{0}\" is not a valid PKCS7 file in the jar: {1} +PKCS7_Parse_Signing_Time = The time stamp in the pkcs7 file cannot be parsed properly! + +# Security Exceptions +Algorithm_Not_Supported = {0} digest algorithm is not supported! + +# Default Trust Engine +Default_Trust_Keystore_Load_Failed = Failed to load the keystore from: {0} +Default_Trust_Read_Only=This trust engine is read only. +Default_Trust_Cert_Not_Found = Certificate not found.
\ No newline at end of file diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedStorageHook.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedStorageHook.java new file mode 100644 index 000000000..4b37d7d8e --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedStorageHook.java @@ -0,0 +1,264 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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.osgi.internal.signedcontent; + +import java.io.*; +import java.security.cert.*; +import java.util.*; +import java.util.Map.Entry; +import org.eclipse.osgi.baseadaptor.BaseData; +import org.eclipse.osgi.baseadaptor.hooks.StorageHook; +import org.eclipse.osgi.framework.util.KeyedElement; +import org.eclipse.osgi.signedcontent.SignedContent; +import org.eclipse.osgi.signedcontent.SignerInfo; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleException; + +public class SignedStorageHook implements StorageHook { + public static final String KEY = SignedStorageHook.class.getName(); + public static final int HASHCODE = KEY.hashCode(); + private static final int STORAGE_VERSION = 3; + private static ArrayList savedSignerInfo = new ArrayList(5); + private static long firstIDSaved = -1; + private static long lastIDSaved = -1; + private static ArrayList loadedSignerInfo = new ArrayList(5); + private static long lastIDLoaded; + + private BaseData bundledata; + SignedContentImpl signedContent; + + public int getStorageVersion() { + return STORAGE_VERSION; + } + + public StorageHook create(BaseData bundledata) throws BundleException { + SignedStorageHook hook = new SignedStorageHook(); + hook.bundledata = bundledata; + return hook; + } + + public void initialize(Dictionary manifest) throws BundleException { + // do nothing + } + + public StorageHook load(BaseData target, DataInputStream is) throws IOException { + if (lastIDLoaded > target.getBundleID()) + loadedSignerInfo.clear(); + lastIDLoaded = target.getBundleID(); + SignedStorageHook hook = new SignedStorageHook(); + hook.bundledata = target; + boolean signed = is.readBoolean(); + if (!signed) + return hook; + int numSigners = is.readInt(); + SignerInfo[] signerInfos = new SignerInfo[numSigners]; + for (int i = 0; i < numSigners; i++) + signerInfos[i] = readSignerInfo(is); + + int resultsSize = is.readInt(); + HashMap contentMDResults = null; + if (resultsSize > 0) { + contentMDResults = new HashMap(resultsSize); + for (int i = 0; i < resultsSize; i++) { + String path = is.readUTF(); + int numEntrySigners = is.readInt(); + SignerInfo[] entrySigners = new SignerInfo[numEntrySigners]; + byte[][] entryResults = new byte[numEntrySigners][]; + for (int j = 0; j < numEntrySigners; j++) { + entrySigners[j] = readSignerInfo(is); + int resultSize = is.readInt(); + entryResults[j] = new byte[resultSize]; + is.readFully(entryResults[j]); + } + contentMDResults.put(path, new Object[] {entrySigners, entryResults}); + } + } + SignedContentImpl result = new SignedContentImpl(signerInfos, contentMDResults); + for (int i = 0; i < numSigners; i++) { + boolean hasTSA = is.readBoolean(); + if (!hasTSA) + continue; + SignerInfo tsaSigner = readSignerInfo(is); + Date signingDate = new Date(is.readLong()); + result.addTSASignerInfo(signerInfos[i], tsaSigner, signingDate); + } + hook.signedContent = result; + return hook; + } + + public void save(DataOutputStream os) throws IOException { + getFirstLastID(); + if (firstIDSaved == bundledata.getBundleID()) + savedSignerInfo.clear(); + if (lastIDSaved == bundledata.getBundleID()) + firstIDSaved = lastIDSaved = -1; + os.writeBoolean(signedContent != null); + if (signedContent == null) + return; + SignerInfo[] signerInfos = signedContent.getSignerInfos(); + os.writeInt(signerInfos.length); + for (int i = 0; i < signerInfos.length; i++) + saveSignerInfo(signerInfos[i], os); + + // keyed by entry path -> {SignerInfo[] infos, byte[][] results)} + HashMap contentMDResults = signedContent.getContentMDResults(); + os.writeInt(contentMDResults == null ? -1 : contentMDResults.size()); + if (contentMDResults != null) + for (Iterator iResults = contentMDResults.entrySet().iterator(); iResults.hasNext();) { + Entry entry = (Entry) iResults.next(); + String path = (String) entry.getKey(); + os.writeUTF(path); + Object[] signerResults = (Object[]) entry.getValue(); + SignerInfo[] entrySigners = (SignerInfo[]) signerResults[0]; + byte[][] entryResults = (byte[][]) signerResults[1]; + os.writeInt(entrySigners.length); + for (int i = 0; i < entrySigners.length; i++) { + saveSignerInfo(entrySigners[i], os); + os.writeInt(entryResults[i].length); + os.write(entryResults[i]); + } + } + + for (int i = 0; i < signerInfos.length; i++) { + SignerInfo tsaInfo = signedContent.getTSASignerInfo(signerInfos[i]); + os.writeBoolean(tsaInfo != null); + if (tsaInfo == null) + continue; + saveSignerInfo(tsaInfo, os); + Date signingTime = signedContent.getSigningTime(signerInfos[i]); + os.writeLong(signingTime != null ? signingTime.getTime() : Long.MIN_VALUE); + } + } + + private void saveSignerInfo(SignerInfo signerInfo, DataOutputStream os) throws IOException { + int cacheIdx = savedSignerInfo.indexOf(signerInfo); + os.writeInt(cacheIdx); + if (cacheIdx >= 0) + return; + Certificate[] certs = signerInfo.getCertificateChain(); + int anchorIndex = -1; + os.writeInt(certs == null ? 0 : certs.length); + if (certs != null) + for (int i = 0; i < certs.length; i++) { + if (certs[i].equals(signerInfo.getTrustAnchor())) + anchorIndex = i; + byte[] certBytes; + try { + certBytes = certs[i].getEncoded(); + } catch (CertificateEncodingException e) { + throw new IOException(e.getMessage()); + } + os.writeInt(certBytes.length); + os.write(certBytes); + } + os.writeInt(anchorIndex); + os.writeUTF(signerInfo.getMessageDigestAlgorithm()); + savedSignerInfo.add(signerInfo); + } + + private SignerInfo readSignerInfo(DataInputStream is) throws IOException { + int index = is.readInt(); + if (index >= 0) + return (SignerInfo) loadedSignerInfo.get(index); + int numCerts = is.readInt(); + Certificate[] certs = new Certificate[numCerts]; + for (int i = 0; i < numCerts; i++) { + int certSize = is.readInt(); + byte[] certBytes = new byte[certSize]; + is.readFully(certBytes); + try { + certs[i] = PKCS7Processor.certFact.generateCertificate(new ByteArrayInputStream(certBytes)); + } catch (CertificateException e) { + throw new IOException(e.getMessage()); + } + } + int anchorIdx = is.readInt(); + SignerInfoImpl result = new SignerInfoImpl(certs, anchorIdx >= 0 ? certs[anchorIdx] : null, is.readUTF()); + loadedSignerInfo.add(result); + return result; + } + + private void getFirstLastID() { + if (firstIDSaved >= 0) + return; + Bundle[] bundles = bundledata.getAdaptor().getContext().getBundles(); + if (bundles.length > 1) { + firstIDSaved = bundles[1].getBundleId(); + lastIDSaved = bundles[bundles.length - 1].getBundleId(); + } + } + + public void copy(StorageHook storageHook) { + // do nothing + } + + public void validate() throws IllegalArgumentException { + // do nothing + } + + public Dictionary getManifest(boolean firstLoad) throws BundleException { + // do nothing + return null; + } + + public boolean forgetStatusChange(int status) { + // do nothing + return false; + } + + public boolean forgetStartLevelChange(int startlevel) { + // do nothing + return false; + } + + public boolean matchDNChain(String pattern) { + if (signedContent == null) + return false; + SignerInfo[] signerInfos = signedContent.getSignerInfos(); + for (int i = 0; i < signerInfos.length; i++) + if (signerInfos[i].isTrusted() && DNChainMatching.match(getDNChainString(signerInfos[i]), pattern)) { + try { + signedContent.checkValidity(signerInfos[i]); + return true; + } catch (CertificateException e) { + // move to next signer + } + } + return false; + } + + private String getDNChainString(SignerInfo signerInfo) { + StringBuffer sb = new StringBuffer(); + Certificate certs[] = signerInfo.getCertificateChain(); + for (int i = 0; i < certs.length; i++) { + if (!(certs[i] instanceof X509Certificate)) + continue; + sb.append(((X509Certificate) certs[i]).getSubjectDN().getName()); + sb.append("; "); //$NON-NLS-1$ + } + return sb.toString(); + } + + public int getKeyHashCode() { + return HASHCODE; + } + + public boolean compare(KeyedElement other) { + return other.getKey() == KEY; + } + + public Object getKey() { + return KEY; + } + + public SignedContent getSignedContent() { + return signedContent; + } + +} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignerInfoImpl.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignerInfoImpl.java new file mode 100644 index 000000000..84673b14e --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignerInfoImpl.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2007 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.osgi.internal.signedcontent; + +import java.security.cert.Certificate; +import org.eclipse.osgi.signedcontent.SignerInfo; + +public class SignerInfoImpl implements SignerInfo { + private final Certificate[] chain; + private final String mdAlgorithm; + volatile private Certificate trustAnchor; + + public SignerInfoImpl(Certificate[] chain, Certificate trustAnchor, String mdAlgorithm) { + this.chain = chain; + this.trustAnchor = trustAnchor; + this.mdAlgorithm = mdAlgorithm; + } + + public Certificate[] getCertificateChain() { + return chain; + } + + public Certificate getTrustAnchor() { + return trustAnchor; + } + + public boolean isTrusted() { + return trustAnchor != null; + } + + void setTrustAnchor(Certificate trustAnchor) { + this.trustAnchor = trustAnchor; + } + + public String getMessageDigestAlgorithm() { + return mdAlgorithm; + } + + public int hashCode() { + int result = mdAlgorithm.hashCode(); + for (int i = 0; i < chain.length; i++) + result += chain[i].hashCode(); + // Note that we do not hash based on trustAnchor; + // this changes dynamically but we need a constant hashCode for purposes of + // hashing in a Set. + return result; + } + + public boolean equals(Object obj) { + if (!(obj instanceof SignerInfo)) + return false; + if (obj == this) + return true; + SignerInfo other = (SignerInfo) obj; + if (!mdAlgorithm.equals(other.getMessageDigestAlgorithm())) + return false; + Certificate[] otherCerts = other.getCertificateChain(); + if (otherCerts.length != chain.length) + return false; + for (int i = 0; i < chain.length; i++) + if (!chain[i].equals(otherCerts[i])) + return false; + return trustAnchor == null ? other.getTrustAnchor() == null : trustAnchor.equals(other.getTrustAnchor()); + } +} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/TrustEngineListener.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/TrustEngineListener.java new file mode 100644 index 000000000..0a61d9c65 --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/TrustEngineListener.java @@ -0,0 +1,168 @@ +/******************************************************************************* + * Copyright (c) 2007 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.osgi.internal.signedcontent; + +import java.security.cert.Certificate; +import java.util.HashSet; +import java.util.Iterator; +import org.eclipse.osgi.baseadaptor.BaseData; +import org.eclipse.osgi.framework.internal.core.AbstractBundle; +import org.eclipse.osgi.framework.internal.core.FrameworkProperties; +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.eclipse.osgi.service.security.AuthorizationEngine; +import org.eclipse.osgi.signedcontent.SignerInfo; +import org.osgi.framework.*; +import org.osgi.service.packageadmin.PackageAdmin; +import org.osgi.util.tracker.ServiceTracker; + +public class TrustEngineListener { + // this is a singleton listener; see SignedBundleHook for initialization + private volatile static TrustEngineListener instance; + private final BundleContext context; + private final ServiceTracker authorizationTracker; + + TrustEngineListener(BundleContext context) { + this.context = context; + // read the trust provider security property + String authEngineProp = FrameworkProperties.getProperty(SignedContentConstants.AUTHORIZATION_ENGINE); + Filter filter = null; + if (authEngineProp != null) + try { + filter = FrameworkUtil.createFilter("(&(" + Constants.OBJECTCLASS + "=" + AuthorizationEngine.class.getName() + ")(" + SignedContentConstants.TRUST_ENGINE + "=" + authEngineProp + "))"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$//$NON-NLS-5$ + } catch (InvalidSyntaxException e) { + SignedBundleHook.log("Invalid authorization filter", FrameworkLogEntry.WARNING, e); //$NON-NLS-1$ + } + if (filter != null) + authorizationTracker = new ServiceTracker(context, filter, null); + else + authorizationTracker = new ServiceTracker(context, AuthorizationEngine.class.getName(), null); + authorizationTracker.open(); + instance = this; + } + + public static TrustEngineListener getInstance() { + return instance; + } + + void stopTrustEngineListener() { + authorizationTracker.close(); + instance = null; + } + + public void addedTrustAnchor(Certificate anchor) { + // find any SignedContent with SignerInfos that do not have an anchor; + // re-evaluate trust and check authorization for these SignedContents + Bundle[] bundles = context.getBundles(); + HashSet unresolved = new HashSet(); + for (int i = 0; i < bundles.length; i++) { + SignedContentImpl signedContent = getSignedContent(bundles[i]); + if (signedContent != null && signedContent.isSigned()) { + // check the SignerInfos for this content + SignerInfo[] infos = signedContent.getSignerInfos(); + for (int j = 0; j < infos.length; j++) { + if (infos[j].getTrustAnchor() == null) + // one of the signers is not trusted + unresolved.add(bundles[i]); + SignerInfo tsa = signedContent.getTSASignerInfo(infos[j]); + if (tsa != null && tsa.getTrustAnchor() == null) + // one of the tsa signers is not trusted + unresolved.add(bundles[i]); + } + } + if (unresolved.contains(bundles[i])) { + // found an untrusted signer for this bundle re-evaluate trust + SignedBundleFile.determineTrust(signedContent, SignedBundleHook.VERIFY_TRUST); + // now check the authorization handler + checkAuthorization(signedContent, bundles[i]); + } + } + // try to resolve + if (unresolved.size() > 0) + resolveBundles((Bundle[]) unresolved.toArray(new Bundle[unresolved.size()]), false); + } + + private void checkAuthorization(SignedContentImpl signedContent, Bundle bundle) { + AuthorizationEngine authEngine = getAuthorizationEngine(); + if (authEngine != null) + authEngine.authorize(signedContent, bundle); + } + + AuthorizationEngine getAuthorizationEngine() { + return (AuthorizationEngine) authorizationTracker.getService(); + } + + private void resolveBundles(Bundle[] bundles, boolean refresh) { + ServiceReference ref = context.getServiceReference(PackageAdmin.class.getName()); + if (ref == null) + return; + PackageAdmin pa = (PackageAdmin) context.getService(ref); + if (pa == null) + return; + try { + if (refresh) + pa.refreshPackages(bundles); + else + pa.resolveBundles(bundles); + } finally { + context.ungetService(ref); + } + } + + public void removedTrustAnchor(Certificate anchor) { + // find any signed content that has signerinfos with the supplied anchor + // re-evaluate trust and check authorization again. + Bundle[] bundles = context.getBundles(); + HashSet usingAnchor = new HashSet(); + HashSet untrustedSigners = new HashSet(); + for (int i = 0; i < bundles.length; i++) { + SignedContentImpl signedContent = getSignedContent(bundles[i]); + if (signedContent != null && signedContent.isSigned()) { + // check signer infos for this content + SignerInfo[] infos = signedContent.getSignerInfos(); + for (int j = 0; j < infos.length; j++) { + if (anchor.equals(infos[j].getTrustAnchor())) { + // one of the signers uses this anchor + untrustedSigners.add(infos[j]); + usingAnchor.add(bundles[i]); + } + SignerInfo tsa = signedContent.getTSASignerInfo(infos[j]); + if (tsa != null && anchor.equals(tsa.getTrustAnchor())) { + // one of the tsa signers uses this anchor + usingAnchor.add(bundles[i]); + untrustedSigners.add(tsa); + } + } + } + } + // remove trust anchors from untrusted signers + for (Iterator untrusted = untrustedSigners.iterator(); untrusted.hasNext();) + ((SignerInfoImpl) untrusted.next()).setTrustAnchor(null); + // re-establish trust and check authorization + for (Iterator untrustedBundles = usingAnchor.iterator(); untrustedBundles.hasNext();) { + Bundle bundle = (Bundle) untrustedBundles.next(); + SignedContentImpl signedContent = getSignedContent(bundle); + // found an signer using the anchor for this bundle re-evaluate trust + SignedBundleFile.determineTrust(signedContent, SignedBundleHook.VERIFY_TRUST); + // now check the authorization handler + checkAuthorization(signedContent, bundle); + } + // TODO an optimization here would be to check for real DisabledInfo objects for each bundle + // try to refresh + if (usingAnchor.size() > 0) + resolveBundles((Bundle[]) usingAnchor.toArray(new Bundle[usingAnchor.size()]), true); + } + + private SignedContentImpl getSignedContent(Bundle bundle) { + BaseData data = (BaseData) ((AbstractBundle) bundle).getBundleData(); + SignedStorageHook hook = (SignedStorageHook) data.getStorageHook(SignedStorageHook.KEY); + if (hook == null) + return null; + return (SignedContentImpl) hook.getSignedContent(); + } +} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/service/security/AuthorizationEngine.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/service/security/AuthorizationEngine.java new file mode 100644 index 000000000..759f956dc --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/service/security/AuthorizationEngine.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007 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.osgi.service.security; + +import org.eclipse.osgi.framework.eventmgr.*; +import org.eclipse.osgi.signedcontent.SignedContent; +import org.osgi.framework.BundleContext; +import org.osgi.util.tracker.ServiceTracker; + +/** + * An authorization engine is used to grant authorization to {@link SignedContent}. + * For example, an engine could determine if <code>SignedContent</code> is authorized + * to enable code from a signed bundle. + */ +public abstract class AuthorizationEngine { + + private EventManager manager = new EventManager(); + private EventDispatcher dispatcher = new AuthEventDispatcher(); + private final ServiceTracker listenerTracker; + + public AuthorizationEngine(BundleContext context) { + listenerTracker = new ServiceTracker(context, AuthorizationListener.class.getName(), null); + listenerTracker.open(); + } + + /** + * Authorizes a <code>SignedContent</code> object. The engine determines if the + * signed content authorization should be granted. The context is the entity + * associated with the signed content. For example, signed content + * for a bundle will have a <code>Bundle</code> object as the context. + * @param content the signed content. The value may be <code>null</code>. + * @param context the context associated with the signed content. The value may be <code>null</code>. + */ + public final void authorize(SignedContent content, Object context) { + fireEvent(doAuthorize(content, context)); + } + + private void fireEvent(AuthorizationEvent event) { + if (event == null) + return; + Object[] services = listenerTracker.getServices(); + if (services == null) + return; + EventListeners listeners = new EventListeners(); + for (int i = 0; i < services.length; i++) + listeners.addListener(services[i], null); + ListenerQueue queue = new ListenerQueue(manager); + queue.queueListeners(listeners, dispatcher); + queue.dispatchEventSynchronous(0, event); + } + + /** + * Authorizes a <code>SignedContent</code> object. The engine determines if the + * signed content authorization should be granted. + * @param content + * @param context the context associated with the signed content + * @return an authorization event which will be fired. A value of <code>null</code> + * may be returned; in this case no authorization event will be fired. + */ + protected abstract AuthorizationEvent doAuthorize(SignedContent content, Object context); + + public int getSeverity() { + return doGetSeverity(); + } + + protected abstract int doGetSeverity(); + + class AuthEventDispatcher implements EventDispatcher { + public void dispatchEvent(Object eventListener, Object listenerObject, int eventAction, Object eventObject) { + ((AuthorizationListener) eventListener).authorizationEvent((AuthorizationEvent) eventObject); + } + } +} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/service/security/AuthorizationEvent.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/service/security/AuthorizationEvent.java new file mode 100644 index 000000000..0716e5271 --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/service/security/AuthorizationEvent.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2007 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.osgi.service.security; + +import org.eclipse.osgi.signedcontent.SignedContent; + +/** + * An event that is fired when an AuthorizationEngine implementation makes + * a decision. + */ +public class AuthorizationEvent { + + /** + * Result code meaning that the operation was allowed + */ + public static final int ALLOWED = 0; + + /** + * Result code meaning that the operation was denied + */ + public static final int DENIED = 1; + + private final int result; + private final SignedContent content; + private final Object context; + private final int severity; + + /** + * Create a new AuthorizationEvent + * @param result - the result code + * @param content - the signed content + * @param context - operation specific context + * @param severity - severity code + */ + public AuthorizationEvent(int result, SignedContent content, Object context, int severity) { + this.result = result; + this.content = content; + this.context = context; + this.severity = severity; + } + + /** + * Get the result code + * @return - the result code + */ + public int getResult() { + return result; + } + + /** + * get the severity + * @return - the severity + */ + public int getSeverity() { + return severity; + } + + /** + * Get the SignedContent object being evaluated + * @return - SignedContent + */ + public SignedContent getSignedContent() { + return content; + } + + /** + * Get the operation specific context + * @return - context + */ + public Object getContext() { + return context; + } +} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/service/security/AuthorizationListener.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/service/security/AuthorizationListener.java new file mode 100644 index 000000000..c9a72284a --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/service/security/AuthorizationListener.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2007 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.osgi.service.security; + +import java.util.EventListener; + +/** + * A Listener interface for an authorization handler. Implementors + * should register as an OSGI service. + */ +public interface AuthorizationListener extends EventListener { + + /** + * Called when an AuthorizationEvent has occurred + */ + public void authorizationEvent(AuthorizationEvent event); + +} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/service/security/TrustEngine.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/service/security/TrustEngine.java new file mode 100644 index 000000000..a7bce560b --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/service/security/TrustEngine.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007 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.osgi.service.security; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.cert.Certificate; +import org.eclipse.osgi.internal.signedcontent.TrustEngineListener; + +/** + * A <code>TrustEngine</code> is used to establish the authenticity of a + * {@link Certificate} chain. + */ +public abstract class TrustEngine { + /** + * Returns the certificate trust anchor contained in the specified chain which + * was used to establish the authenticity of the chain. If no + * trust anchor is found in the chain then <code>null</code> is returned. + * @param chain - a complete or incomplete certificate chain, implementations *MAY* complete chains + * @return - the certificate trust anchor used to establish authenticity + * @throws IOException if there is a problem connecting to the backing store + */ + public abstract Certificate findTrustAnchor(Certificate[] chain) throws IOException; + + /** + * Add a trust anchor point to this trust engine. A trust anchor implies that a certificate, + * and any of its children, is to be considered trusted. If <code>null</code> is used + * as the alias then an alias will be generated based on the trust anchor certificate. + * @param anchor - the certificate to add as an anchor point + * @param alias - a unique and human-readable 'friendly name' which can be used to reference the certificate. + * A <code>null</code> value may be used. + * @return the alias used to store the entry + * @throws IOException if there is a problem connecting to the backing store + * @throws GeneralSecurityException if there is a certificate problem + * @throws IllegalArgumentException if the alias or anchor already exist in this trust engine + */ + public String addTrustAnchor(Certificate anchor, String alias) throws IOException, GeneralSecurityException { + String storedAlias = doAddTrustAnchor(anchor, alias); + TrustEngineListener listener = TrustEngineListener.getInstance(); + if (listener != null) + listener.addedTrustAnchor(anchor); + return storedAlias; + } + + /** + * Add a trust anchor point to this trust engine. A trust anchor implies that a certificate, + * and any of its children, is to be considered trusted. If <code>null</code> is used + * as the alias then an alias will be generated based on the trust anchor certificate. + * @param anchor - the certificate to add as an anchor point + * @param alias - a unique and human-readable 'friendly name' which can be used to reference the certificate. + * A <code>null</code> value may be used. + * @return the alias used to store the entry + * @throws IOException if there is a problem connecting to the backing store + * @throws GeneralSecurityException if there is a certificate problem + * @throws IllegalArgumentException if the alias or anchor already exist in this trust engine + */ + protected abstract String doAddTrustAnchor(Certificate anchor, String alias) throws IOException, GeneralSecurityException; + + /** + * Remove a trust anchor point from the engine, based on the certificate itself. + * @param anchor - the certificate to be removed + * @throws IOException if there is a problem connecting to the backing store + * @throws GeneralSecurityException if there is a certificate problem + */ + public final void removeTrustAnchor(Certificate anchor) throws IOException, GeneralSecurityException { + doRemoveTrustAnchor(anchor); + TrustEngineListener listener = TrustEngineListener.getInstance(); + if (listener != null) + listener.removedTrustAnchor(anchor); + } + + /** + * Remove a trust anchor point from the engine, based on the certificate itself. + * @param anchor - the certificate to be removed + * @throws IOException if there is a problem connecting to the backing store + * @throws GeneralSecurityException if there is a certificate problem + */ + protected abstract void doRemoveTrustAnchor(Certificate anchor) throws IOException, GeneralSecurityException; + + /** + * Remove a trust anchor point from the engine, based on the human readable "friendly name" + * @param alias - the name of the trust anchor + * @throws IOException if there is a problem connecting to the backing store + * @throws GeneralSecurityException if there is a certificate problem + */ + public void removeTrustAnchor(String alias) throws IOException, GeneralSecurityException { + Certificate existing = getTrustAnchor(alias); + doRemoveTrustAnchor(alias); + if (existing != null) { + TrustEngineListener listener = TrustEngineListener.getInstance(); + if (listener != null) + listener.removedTrustAnchor(existing); + } + } + + /** + * Remove a trust anchor point from the engine, based on the human readable "friendly name" + * @param alias - the name of the trust anchor + * @throws IOException if there is a problem connecting to the backing store + * @throws GeneralSecurityException if there is a certificate problem + */ + protected abstract void doRemoveTrustAnchor(String alias) throws IOException, GeneralSecurityException; + + /** + * Return the certificate associated with the unique "friendly name" in the engine. + * @param alias - the friendly name + * @return the associated trust anchor + * @throws IOException if there is a problem connecting to the backing store + * @throws GeneralSecurityException if there is a certificate problem + */ + public abstract Certificate getTrustAnchor(String alias) throws IOException, GeneralSecurityException; + + /** + * Return the list of friendly name aliases for the TrustAnchors installed in the engine. + * @return string[] - the list of friendly name aliases + * @throws IOException if there is a problem connecting to the backing store + * @throws GeneralSecurityException if there is a certificate problem + */ + public abstract String[] getAliases() throws IOException, GeneralSecurityException; + + /** + * Return a value indicate whether this trust engine is read-only. + * + * @return true if this trust engine is read-only false otherwise. + */ + public abstract boolean isReadOnly(); + + /** + * Return a representation string of this trust engine + * + * @return a string + */ + public abstract String getName(); +} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/InvalidContentException.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/InvalidContentException.java new file mode 100644 index 000000000..1518be974 --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/InvalidContentException.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007 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.osgi.signedcontent; + +import java.io.IOException; + +/** + * Indicates that signed content is invalid according to one of the signers. + */ +public class InvalidContentException extends IOException { + private static final long serialVersionUID = -399150159330289387L; + // TODO may want to add error codes to indicate the reason for the invalid/corruption error. + private final Throwable cause; + + /** + * Constructs an <code>InvalidContentException</code> with the specified detail + * message and cause. + * + * @param message the exception message + * @param cause the cause, may be <code>null</code> + */ + public InvalidContentException(String message, Throwable cause) { + super(message); + this.cause = cause; + } + + /** + * Returns the cause of this exception or <code>null</code> if no cause + * was specified when this exception was created. + * + * @return The cause of this exception or <code>null</code> if no cause was created. + */ + public Throwable getCause() { + return cause; + } + + /** + * The cause of this exception can only be set when constructed. + * + * @param t Cause of the exception. + * @return This object. + * @throws java.lang.IllegalStateException This method will always throw an + * <code>IllegalStateException</code> since the cause of this + * exception can only be set when constructed. + */ + public Throwable initCause(Throwable t) { + throw new IllegalStateException(); + } +} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/SignedContent.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/SignedContent.java new file mode 100644 index 000000000..0bf9f8f1a --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/SignedContent.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2007 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.osgi.signedcontent; + +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.util.Date; + +/** + * A <code>SignedContent</code> object represents content which may be signed. A + * {@link SignedContentFactory} is used to create signed content objects. + * <p> + * A <code>SignedContent</code> object is intended to provide information about + * the signers of the content, and cannot be used to access the actual data of the content. + * </p> + * <p> + * This interface is not intended to be implemented by clients. + * </p> + */ +public interface SignedContent { + + /** + * Returns all entries of the content. The returned entries can be used + * to verify the entry content using {@link SignedContentEntry#verify()} and + * get signer info for each entry in this content using {@link SignedContentEntry#getSignerInfos()}. + * Note that this operation may be expensive because it requires an + * exhaustive search for entries over the entire content. + * <p> + * Unsigned entries are included in the result. Entries for which signer info exists + * but no content is found are also returned. For example, when an entry is removed from + * a signed jar but the jar is not resigned, the signer thinks the entry should exist + * but the content got removed. This would be considered an invalid entry which would fail verification. + * </p> + * @return all entries of the content + */ + public SignedContentEntry[] getSignedEntries(); + + /** + * Returns the signed entry for the specified name. + * @param name the name of the entry + * @return the entry or null if the entry could not be found + */ + public SignedContentEntry getSignedEntry(String name); + + /** + * Returns all the signer infos for this <code>SignedContent</code>. If the content + * is not signed then an empty array is returned. + * @return all the signer infos for this <code>SignedContent</code> + */ + public SignerInfo[] getSignerInfos(); + + /** + * Returns true if the content is signed; false otherwise. This is a convenience method + * equivalent to calling <code>{@link #getSignerInfos()}.length > 0</code> + * @return true if the content is signed + */ + public boolean isSigned(); + + /** + * Returns the signing time for the signer info. If no TSA signers exist then null is returned + * @param signerInfo the signer info to get the signing time for + * @return the signing time + */ + public Date getSigningTime(SignerInfo signerInfo); + + /** + * Returns the TSA signer info used to authenticate the signer time of a signer info. + * @param signerInfo the signer info to get the TSA signer for + * @return the TSA signer info + */ + public SignerInfo getTSASignerInfo(SignerInfo signerInfo); + + /** + * Checks if the certificates are valid for the specified signer. If the signer has a singing time + * returned by {@link #getSigningTime(SignerInfo)} then that time is used to check the + * validity of the certificates; otherwise the current time is used. + * @param signerInfo the signer info to check validity for. + * @throws CertificateExpiredException if one of the certificates of this signer is expired + * @throws CertificateNotYetValidException if one of the certificates of this signer is not yet valid + */ + public void checkValidity(SignerInfo signerInfo) throws CertificateExpiredException, CertificateNotYetValidException; +} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/SignedContentEntry.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/SignedContentEntry.java new file mode 100644 index 000000000..060caa890 --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/SignedContentEntry.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007 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.osgi.signedcontent; + +import java.io.IOException; + +// encapsulates the status of an entry: isSigned, timestamp info, SignerInfos, etc +// implemented by SignedBundleFile.SignedBundleEntry +/** + * A <code>SignedContentEntry</code> represents a content entry which may be + * signed. + * <p> + * A <code>SignedContentEntry</code> object is intended to provide information about + * the signers of the content entry, and cannot be used to access the actual data of the entry. + * </p> + * <p> + * This interface is not intended to be implemented by clients. + * </p> + */ +public interface SignedContentEntry { + /** + * Returns the name of the entry. + * @return the name of the entry. + */ + public String getName(); + + /** + * Returns the signer infos for this <code>SignedContentEntry</code>. If the entry + * is not signed then an empty array is returned. + * @return the signer infos for this <code>SignedContentEntry</code> + */ + public SignerInfo[] getSignerInfos(); + + /** + * Returns true if the entry is signed; false otherwise. This is a convenience method + * equivalent to calling <code>{@link #getSignerInfos()}.length > 0</code> + * @return true if the content is signed + */ + public boolean isSigned(); + + // Does the digest of this entry match what is expected? + // TODO: what does this mean in the face of multiple signers + /** + * Verifies the content of this this entry is valid. + * @throws IOException if an error occurred reading the entry content + * @throws InvalidContentException if the entry content is not valid + */ + public void verify() throws IOException, InvalidContentException; + +} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/SignedContentFactory.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/SignedContentFactory.java new file mode 100644 index 000000000..a3ca738e1 --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/SignedContentFactory.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2007 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.osgi.signedcontent; + +import java.io.File; +import java.io.IOException; +import java.security.*; +import java.security.cert.CertificateException; +import org.osgi.framework.Bundle; + +/** + * A factory used to create {@link SignedContent} objects. + * <p> + * The framework will register a factory implementation as an OSGi service. + * This service can be used to get <code>SignedContent</code> for a bundle. + * It can also be used to get <code>SignedContent</code> for a repository file. + * The supported formats for file repositories are jar files and directories containing the + * content of an extracted jar. + * </p> + * <p> + * This interface is not intended to be implemented by clients. + * </p> + */ +public interface SignedContentFactory { + /** + * Returns a <code>SignedContent</code> object for the specified content of a repository. + * A value of <code>null</code> is returned if the specified content is not signed. + * @param content the content of the repository + * @return signed content for the specified repository + * @throws IOException if an IO exception occurs while reading the repository + * @throws NoSuchProviderException if there's no security provider for the signed content + * @throws NoSuchAlgorithmException if the cryptographic algorithm is not available for the signed content + * @throws CertificateException if there is a problem with one of the certificates of the signed content + * @throws SignatureException if there is a problem with one of the signatures of the signed content + * @throws InvalidKeyException if there is a problem with one of the certificate keys of the signed content + */ + public SignedContent getSignedContent(File content) throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException; + + /** + * Returns a <code>SignedContent</code> object for the specified bundle. + * A value of <code>null</code> is returned if the specified content is not signed. + * @param bundle the bundle to get a signed content for. + * @return signed content for the specified bundle. + * @throws IOException if an IO exception occurs while reading the bundle content + * @throws NoSuchProviderException if there's no security provider for the signed content + * @throws NoSuchAlgorithmException if the cryptographic algorithm is not available for the signed content + * @throws CertificateException if there is a problem with one of the certificates of the signed content + * @throws SignatureException if there is a problem with one of the signatures of the signed content + * @throws InvalidKeyException if there is a problem with one of the certificate keys of the signed content + */ + public SignedContent getSignedContent(Bundle bundle) throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException; +} diff --git a/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/SignerInfo.java b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/SignerInfo.java new file mode 100644 index 000000000..c2a635040 --- /dev/null +++ b/bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/SignerInfo.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007 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.osgi.signedcontent; + +import java.security.cert.Certificate; + +/** + * A <code>SignerInfo</code> object represents a single signer chain. + * <p> + * This interface is not intended to be implemented by clients. + * </p> + */ +public interface SignerInfo { + + /** + * Returns the certificate chain + * @return the certificate chain + */ + public Certificate[] getCertificateChain(); + + /** + * Returns the certificate trust anchor used to establish authenticity. + * If authenticity cannot be established then <code>null</code> is returned. + * @return the trust anchor + */ + public Certificate getTrustAnchor(); + + /** + * Returns true if the trust anchor has been authenticated. This is a convenience + * method equivalent to calling <code>{@link #getTrustAnchor()} != null</code> + * @return true if the the signer info is trusted + */ + public boolean isTrusted(); + + /** + * Returns the <code>MessageDigest</code> algorithm used to verify content signed by this + * signer info. + * @return the algorithm + */ + public String getMessageDigestAlgorithm(); + + // TODO need more thought here, TrustEngines could get stale since they are services, leaving off for now unless until we understand the usecase for this. + //public TrustEngine getTrustEngine(); + +} |