Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Watson2007-12-17 20:01:53 +0000
committerThomas Watson2007-12-17 20:01:53 +0000
commit0dc68cdfc27a0112b071bade9737965855ff7834 (patch)
treee325956db65682338a70d5f9b3c0b95b3c034cb2
parentb2c60c044ec9a85aaa715fcb0f81f85bdc74f33b (diff)
downloadrt.equinox.framework-20071217.tar.gz
rt.equinox.framework-20071217.tar.xz
rt.equinox.framework-20071217.zip
Bug 211745 [sec] Graduate signedcontent API and related servicesv20071217
-rw-r--r--bundles/org.eclipse.osgi/.classpath1
-rw-r--r--bundles/org.eclipse.osgi/META-INF/MANIFEST.MF7
-rw-r--r--bundles/org.eclipse.osgi/build.properties3
-rw-r--r--bundles/org.eclipse.osgi/core/adaptor/org/eclipse/osgi/service/resolver/DisabledInfo.java3
-rw-r--r--bundles/org.eclipse.osgi/core/adaptor/org/eclipse/osgi/service/resolver/State.java1
-rw-r--r--bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/internal/baseadaptor/BaseStorage.java14
-rw-r--r--bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/internal/baseadaptor/DefaultClassLoader.java16
-rw-r--r--bundles/org.eclipse.osgi/hookconfigurators.properties2
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/DefaultTrustAuthority.java48
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/JarVerifierMessages.properties46
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/KeyStores.java194
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/PKCS7DateParser.java65
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/SignedBundleFile.java779
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/SignedBundleHook.java221
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/SignedStorageHook.java235
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/service/security/DefaultAuthorizationEngine.java105
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/service/security/KeyStoreTrustEngine.java259
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/BERProcessor.java (renamed from bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/BERProcessor.java)21
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/Base64.java (renamed from bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/Base64.java)6
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/BundleInstallListener.java40
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/DNChainMatching.java (renamed from bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/DNChainMatching.java)4
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/DigestedInputStream.java (renamed from bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/DigestedInputStream.java)53
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/LegacyVerifierFactory.java138
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/PKCS7DateParser.java45
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/PKCS7Processor.java (renamed from bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/PKCS7Processor.java)229
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignatureBlockProcessor.java484
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedBundleFile.java209
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedBundleHook.java304
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedContentConstants.java (renamed from bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/JarVerifierConstant.java)16
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedContentImpl.java164
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.java (renamed from bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/JarVerifierMessages.java)37
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.properties32
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignedStorageHook.java264
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/SignerInfoImpl.java71
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/internal/signedcontent/TrustEngineListener.java168
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/service/security/AuthorizationEngine.java81
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/service/security/AuthorizationEvent.java81
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/service/security/AuthorizationListener.java26
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/service/security/TrustEngine.java142
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/InvalidContentException.java57
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/SignedContent.java90
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/SignedContentEntry.java58
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/SignedContentFactory.java60
-rw-r--r--bundles/org.eclipse.osgi/security/src/org/eclipse/osgi/signedcontent/SignerInfo.java53
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();
+
+}

Back to the top