Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Watson2006-03-02 23:20:52 +0000
committerThomas Watson2006-03-02 23:20:52 +0000
commit91cb43f54b532cca415dd0a0e8ba06bd04a13dc6 (patch)
tree40628c103ca22561412e180797e21f68535fc910
parent5a181a3eac793629d4b3c32db51a3dcba5ab111a (diff)
downloadrt.equinox.framework-91cb43f54b532cca415dd0a0e8ba06bd04a13dc6.tar.gz
rt.equinox.framework-91cb43f54b532cca415dd0a0e8ba06bd04a13dc6.tar.xz
rt.equinox.framework-91cb43f54b532cca415dd0a0e8ba06bd04a13dc6.zip
Bug 127110 Promote org.eclipse.osgi.jarverifier from incubator
-rw-r--r--bundles/org.eclipse.osgi/.classpath1
-rw-r--r--bundles/org.eclipse.osgi/META-INF/MANIFEST.MF4
-rw-r--r--bundles/org.eclipse.osgi/build.properties10
-rw-r--r--bundles/org.eclipse.osgi/hookconfigurators.properties3
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/provisional/verifier/CertificateChain.java50
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/provisional/verifier/CertificateVerifier.java46
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/provisional/verifier/CertificateVerifierFactory.java42
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/BERProcessor.java271
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/Base64.java198
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/DNChainMatching.java385
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/DigestedInputStream.java155
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/KeyStores.java180
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/PKCS7Processor.java249
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/SignedBundleFile.java638
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/SignedBundleHook.java148
-rw-r--r--bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/SignedStorageHook.java225
-rw-r--r--bundles/org.eclipse.osgi/osgi/exceptions.jarbin52808 -> 99157 bytes
17 files changed, 2602 insertions, 3 deletions
diff --git a/bundles/org.eclipse.osgi/.classpath b/bundles/org.eclipse.osgi/.classpath
index 602f416a8..31be5848d 100644
--- a/bundles/org.eclipse.osgi/.classpath
+++ b/bundles/org.eclipse.osgi/.classpath
@@ -7,6 +7,7 @@
<classpathentry kind="src" path="defaultAdaptor/src"/>
<classpathentry kind="src" path="eclipseAdaptor/src"/>
<classpathentry kind="src" path="resolver/src"/>
+ <classpathentry kind="src" path="jarverifier"/>
<classpathentry kind="lib" path="osgi/exceptions.jar"/>
<classpathentry kind="lib" path="osgi/collx.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 8b43cb9e7..22b150889 100644
--- a/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF
@@ -41,7 +41,9 @@ Export-Package: org.eclipse.osgi.event;version="1.0",
org.eclipse.osgi.internal.baseadaptor;x-internal:=true,
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.resolver;x-internal:=true,
+ org.eclipse.osgi.internal.verifier;x-internal:=true,
+ org.eclipse.osgi.internal.provisional.verifier;x-friends:="org.eclipse.update.core"
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 d6221f3c1..66bd45f6d 100644
--- a/bundles/org.eclipse.osgi/build.properties
+++ b/bundles/org.eclipse.osgi/build.properties
@@ -17,4 +17,12 @@ bin.includes = .options,\
profile.list,\
hookconfigurators.properties
src.includes = about.html
-source.. = osgi/src, core/adaptor/, core/framework/, resolver/src/, defaultAdaptor/src/, eclipseAdaptor/src/, console/src/
+source.. = osgi/src,\
+ core/adaptor/,\
+ core/framework/,\
+ resolver/src/,\
+ defaultAdaptor/src/,\
+ eclipseAdaptor/src/,\
+ console/src/,\
+ jarverifier/
+output.. = bin/
diff --git a/bundles/org.eclipse.osgi/hookconfigurators.properties b/bundles/org.eclipse.osgi/hookconfigurators.properties
index b01d3377f..c9d25bc46 100644
--- a/bundles/org.eclipse.osgi/hookconfigurators.properties
+++ b/bundles/org.eclipse.osgi/hookconfigurators.properties
@@ -17,4 +17,5 @@ hook.configurators= \
org.eclipse.core.runtime.internal.adaptor.EclipseAdaptorHook,\
org.eclipse.core.runtime.internal.adaptor.EclipseClassLoadingHook,\
org.eclipse.core.runtime.internal.adaptor.EclipseLazyStarter,\
- org.eclipse.core.runtime.internal.stats.StatsManager
+ org.eclipse.core.runtime.internal.stats.StatsManager,\
+ org.eclipse.osgi.internal.verifier.SignedBundleHook
diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/provisional/verifier/CertificateChain.java b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/provisional/verifier/CertificateChain.java
new file mode 100644
index 000000000..b58d75848
--- /dev/null
+++ b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/provisional/verifier/CertificateChain.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * 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.provisional.verifier;
+
+import java.security.cert.Certificate;
+
+/**
+ * This class represents a chain of certificates.
+ * <p>
+ * <strong>EXPERIMENTAL</strong>. This class or interface has been added as
+ * part of a work in progress. There is no guarantee that this API will
+ * work or that it will remain the same. Please do not use this API without
+ * consulting with the equinox team.
+ * </p>
+ */
+public interface CertificateChain {
+ /**
+ * Returns the list of X500 distinguished names that make up the certificate chain. Each
+ * distinguished name is separated by a ';'. The first distinguished name is the signer
+ * and the last is the root Certificate Authority.
+ * @return the list of X500 distinguished names that make up the certificate chain
+ */
+ public String getChain();
+
+ /**
+ * Returns the first certificate of the certificate chain
+ * @return the first certificate of the certificate chain
+ */
+ public Certificate getSigner();
+
+ /**
+ * Returns the root certificate of the certificate chain
+ * @return the foot certificate of the certificate chain
+ */
+ public Certificate getRoot();
+
+ /**
+ * Returns true if this certificate chain is trusted
+ * @return true if this certificate chain is trusted
+ */
+ boolean isTrusted();
+}
diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/provisional/verifier/CertificateVerifier.java b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/provisional/verifier/CertificateVerifier.java
new file mode 100644
index 000000000..46b82b361
--- /dev/null
+++ b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/provisional/verifier/CertificateVerifier.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.provisional.verifier;
+
+/**
+ * A certificate verifier is used to verify the authenticity of a signed
+ * repository. A certificate verifier is created using a
+ * {@link CertificateVerifierFactory}.
+ * <p>
+ * <strong>EXPERIMENTAL</strong>. This class or interface has been added as
+ * part of a work in progress. There is no guarantee that this API will
+ * work or that it will remain the same. Please do not use this API without
+ * consulting with the equinox team.
+ * </p>
+ */
+public interface CertificateVerifier {
+ /**
+ * Verifies the content of the repository. An array is returned with the entry names
+ * which are corrupt. If no entries are currupt then an empty array is returned.
+ * @return An array of entry names which are corrupt. An empty array is returned if the
+ * repository is not corrupt or if the repository is not signed.
+ */
+ public String[] verifyContent();
+
+ /**
+ * Returns true if the repository is signed
+ * @return true if the repository is signed
+ */
+ public boolean isSigned();
+
+ /**
+ * Returns all certificate chains of the repository. All certificate chains
+ * are returned whether they are trusted or not. If the repository is not signed
+ * then an empty array is returned.
+ * @return all certificate chains of the repository
+ */
+ public CertificateChain[] getChains();
+}
diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/provisional/verifier/CertificateVerifierFactory.java b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/provisional/verifier/CertificateVerifierFactory.java
new file mode 100644
index 000000000..6ca7c68a1
--- /dev/null
+++ b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/provisional/verifier/CertificateVerifierFactory.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * 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.provisional.verifier;
+
+import java.io.File;
+import java.io.IOException;
+import org.osgi.framework.Bundle;
+
+/**
+ * A factory used to create certificate verifiers.
+ * <p>
+ * <strong>EXPERIMENTAL</strong>. This class or interface has been added as
+ * part of a work in progress. There is no guarantee that this API will
+ * work or that it will remain the same. Please do not use this API without
+ * consulting with the equinox team.
+ * </p>
+ */
+public interface CertificateVerifierFactory {
+ /**
+ * Creates a certificate verifier for the specified content of a repository
+ * @param content the content of the repository
+ * @return a certificate verifier for the specified content of a repository
+ * @throws IOException if an IO exception occurs while reading the repository
+ */
+ public CertificateVerifier getVerifier(File content) throws IOException;
+
+ /**
+ * Returns a certificate verifier for the specified bundle.
+ * @param bundle the bundle to get a verifier for
+ * @return a certificate verifier for the specified bundle.
+ * @throws IOException if an IO exception occurs while reading the bundle content
+ */
+ public CertificateVerifier getVerifier(Bundle bundle) throws IOException;
+}
diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/BERProcessor.java b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/BERProcessor.java
new file mode 100644
index 000000000..bdb0b4db2
--- /dev/null
+++ b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/BERProcessor.java
@@ -0,0 +1,271 @@
+/*******************************************************************************
+ * 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.math.BigInteger;
+
+/**
+ * This is a simple class that processes BER structures. This class
+ * uses BER processing as outlined in X.690.
+ */
+public class BERProcessor {
+ /**
+ * This is the buffer that contains the BER structures that are being interrogated.
+ */
+ byte buffer[];
+ /**
+ * The offset into <code>buffer</code> to the start of the structure being interrogated.
+ * If the offset is -1 that means that we have read the last structure.
+ */
+ int offset;
+ /**
+ * The last valid offset in <code>buffer</code>.
+ */
+ int lastOffset;
+ /**
+ * The offset into <code>buffer</code> to the start of the content of the structure
+ * being interrogated.
+ */
+ int contentOffset;
+ /**
+ * The length of the content of the structure being interrogated.
+ */
+ int contentLength;
+ /**
+ * The offset into <code>buffer</code> of the end of the structure being interrogated.
+ */
+ int endOffset;
+ /**
+ * The class of the tag of the current structure.
+ */
+ int classOfTag;
+ static final int UNIVERSAL_TAGCLASS = 0;
+ static final int APPLICATION_TAGCLASS = 1;
+ static final int CONTEXTSPECIFIC_TAGCLASS = 2;
+ static final int PRIVATE_TAGCLASS = 3;
+
+ static final byte BOOLTAG = 1;
+ static final byte INTTAG = 2;
+ static final byte OIDTAG = 6;
+ static final byte SEQTAG = 16;
+ static final byte SETTAG = 17;
+ static final byte NULLTAG = 5;
+
+ /**
+ * Tagnames used in toString()
+ */
+ static final String tagNames[] = {"<null>", "boolean", "int", "bitstring", "octetstring", "null", "objid", "objdesc", "external", "real", "enum", "pdv", "utf8", "relobjid", "resv", "resv", "sequence", "set", "char string"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ //$NON-NLS-12$ //$NON-NLS-13$ //$NON-NLS-14$ //$NON-NLS-15$ //$NON-NLS-16$ //$NON-NLS-17$ //$NON-NLS-18$ //$NON-NLS-19$
+
+ /**
+ * True if this is a structure for a constructed encoding.
+ */
+ public boolean constructed;
+ /**
+ * The tag type. Note that X.690 specifies encodings for tags with values greater than 31,
+ * but currently this class does not handle these kinds of tags.
+ */
+ public byte tag;
+
+ /**
+ * Constructs a BERProcessor to operate on the passed buffer. The first structure in the
+ * buffer will be processed before this method returns.
+ *
+ * @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.
+ */
+ public BERProcessor(byte buffer[], int offset, int len) {
+ this.buffer = buffer;
+ this.offset = offset;
+ lastOffset = len + offset;
+ processStructure();
+ }
+
+ /**
+ * Parse the structure found at the current <code>offset</code> into <code>buffer</code>.
+ * Most methods, constructor, and stepinto, will call this method automatically. If
+ * <code>offset</code> is modified outside of those methods, this method will need to
+ * be invoked.
+ */
+ public void processStructure() {
+ // Don't process if we are at the end
+ if (offset == -1)
+ return;
+ endOffset = offset;
+ // section 8.1.2.2
+ classOfTag = (buffer[offset] & 0xff) >> 6;
+ // section 8.1.2.5
+ constructed = (buffer[offset] & 0x20) != 0;
+ // section 8.1.2.3
+ byte tagNumber = (byte) (buffer[offset] & 0x1f);
+ if (tagNumber < 31) {
+ tag = tagNumber;
+ endOffset = offset + 1;
+ } else {
+ throw new IllegalArgumentException("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)
+ contentLength = buffer[endOffset];
+ endOffset++;
+ } else {
+ // 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$
+ contentLength = 0;
+ endOffset++;
+ for (int i = 0; i < octetCount; i++) {
+ contentLength <<= 8;
+ contentLength |= buffer[endOffset] & 0xff;
+ endOffset++;
+ }
+ // section 8.1.3.6 (doing the indefinite form
+ if (octetCount == 0)
+ contentLength = -1;
+ }
+ contentOffset = endOffset;
+ if (contentLength != -1)
+ endOffset += contentLength;
+ if (endOffset > lastOffset)
+ throw new ArrayIndexOutOfBoundsException(endOffset + " > " + lastOffset); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns a String representation of the current BER structure.
+ * @return a String representation of the current BER structure.
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ switch (classOfTag) {
+ case UNIVERSAL_TAGCLASS :
+ sb.append('U');
+ break;
+ case APPLICATION_TAGCLASS :
+ sb.append('A');
+ break;
+ case CONTEXTSPECIFIC_TAGCLASS :
+ sb.append('C');
+ break;
+ case PRIVATE_TAGCLASS :
+ sb.append('P');
+ break;
+ }
+ sb.append(constructed ? 'C' : 'P');
+ sb.append(" tag=" + tag); //$NON-NLS-1$
+ if (tag < tagNames.length) {
+ sb.append("(" + tagNames[tag] + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ sb.append(" len="); //$NON-NLS-1$
+ sb.append(contentLength);
+ switch (tag) {
+ case INTTAG :
+ sb.append(" value=" + getIntValue()); //$NON-NLS-1$
+ break;
+ case OIDTAG :
+ sb.append(" value="); //$NON-NLS-1$
+ int oid[] = getObjId();
+ for (int i = 0; i < oid.length; i++) {
+ if (i > 0)
+ sb.append('.');
+ sb.append(oid[i]);
+ }
+ }
+ if (tag == 12 || (tag >= 18 && tag <= 22) || (tag >= 25 && tag <= 30)) {
+ sb.append(" value="); //$NON-NLS-1$
+ sb.append(getString());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns a BERProcessor for the content of the current structure.
+ */
+ public BERProcessor stepInto() {
+ return new BERProcessor(buffer, contentOffset, contentLength);
+ }
+
+ public void stepOver() {
+ offset = endOffset;
+ if (endOffset >= lastOffset) {
+ offset = -1;
+ return;
+ }
+ processStructure();
+ }
+
+ public boolean endOfSequence() {
+ return offset == -1;
+ }
+
+ /**
+ * Gets the content from the current structure as a String.
+ * @return the content from the current structure as a String.
+ */
+ public String getString() {
+ return new String(buffer, contentOffset, contentLength);
+ }
+
+ /**
+ * Gets the content from the current structure as an int.
+ * @return the content from the current structure as an int.
+ */
+ public BigInteger getIntValue() {
+ return new BigInteger(getBytes());
+ }
+
+ /**
+ * Gets the content from the current structure as an object id (int[]).
+ * @return the content from the current structure as an object id (int[]).
+ */
+ public int[] getObjId() {
+ // First count the ids
+ int count = 0;
+ for (int i = 0; i < contentLength; i++) {
+ // section 8.19.2
+ if ((buffer[contentOffset + i] & 0x80) == 0)
+ count++;
+ }
+ count++; // section 8.19.3
+ int oid[] = new int[count];
+ int index = 0;
+ int currentValue = 0;
+ for (int i = 0; i < contentLength; i++) {
+ currentValue <<= 7;
+ currentValue |= buffer[contentOffset + i] & 0x7f;
+ // section 8.19.2
+ if ((buffer[contentOffset + i] & 0x80) == 0) {
+ if (index == 0) {
+ // section 8.19.4 special processing
+ oid[index++] = currentValue / 40;
+ oid[index++] = currentValue % 40;
+ } else {
+ oid[index++] = currentValue;
+ }
+ currentValue = 0;
+ }
+ }
+ return oid;
+ }
+
+ /**
+ * Get a copy of the bytes in the content of the current structure.
+ * @return a copy of the bytes in the content of the current structure.
+ */
+ public byte[] getBytes() {
+ byte v[] = new byte[contentLength];
+ System.arraycopy(buffer, contentOffset, v, 0, contentLength);
+ return v;
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/Base64.java b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/Base64.java
new file mode 100644
index 000000000..130192b1c
--- /dev/null
+++ b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/Base64.java
@@ -0,0 +1,198 @@
+/*******************************************************************************
+ * 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;
+
+public class Base64 {
+
+ private static final byte equalSign = (byte) '=';
+
+ static char digits[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', //
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', //
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', //
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
+
+ /**
+ * This method decodes the byte array in base 64 encoding into a char array
+ * Base 64 encoding has to be according to the specification given by the
+ * RFC 1521 (5.2).
+ *
+ * @param data the encoded byte array
+ * @return the decoded byte array
+ */
+ public static byte[] decode(byte[] data) {
+ if (data.length == 0)
+ return data;
+ int lastRealDataIndex = data.length - 1;
+ while (data[lastRealDataIndex] == equalSign)
+ lastRealDataIndex--;
+ // original data digit is 8 bits long, but base64 digit is 6 bits long
+ int padBytes = data.length - 1 - lastRealDataIndex;
+ int byteLength = data.length * 6 / 8 - padBytes;
+ byte[] result = new byte[byteLength];
+ // Each 4 bytes of input (encoded) we end up with 3 bytes of output
+ int dataIndex = 0;
+ int resultIndex = 0;
+ int allBits = 0;
+ // how many result chunks we can process before getting to pad bytes
+ int resultChunks = (lastRealDataIndex + 1) / 4;
+ for (int i = 0; i < resultChunks; i++) {
+ allBits = 0;
+ // Loop 4 times gathering input bits (4 * 6 = 24)
+ for (int j = 0; j < 4; j++)
+ allBits = (allBits << 6) | decodeDigit(data[dataIndex++]);
+ // Loop 3 times generating output bits (3 * 8 = 24)
+ for (int j = resultIndex + 2; j >= resultIndex; j--) {
+ result[j] = (byte) (allBits & 0xff); // Bottom 8 bits
+ allBits = allBits >>> 8;
+ }
+ resultIndex += 3; // processed 3 result bytes
+ }
+ // Now we do the extra bytes in case the original (non-encoded) data
+ // was not multiple of 3 bytes
+ switch (padBytes) {
+ case 1 :
+ // 1 pad byte means 3 (4-1) extra Base64 bytes of input, 18
+ // bits, of which only 16 are meaningful
+ // Or: 2 bytes of result data
+ allBits = 0;
+ // Loop 3 times gathering input bits
+ for (int j = 0; j < 3; j++)
+ allBits = (allBits << 6) | decodeDigit(data[dataIndex++]);
+ // NOTE - The code below ends up being equivalent to allBits =
+ // allBits>>>2
+ // But we code it in a non-optimized way for clarity
+ // The 4th, missing 6 bits are all 0
+ allBits = allBits << 6;
+ // The 3rd, missing 8 bits are all 0
+ allBits = allBits >>> 8;
+ // Loop 2 times generating output bits
+ for (int j = resultIndex + 1; j >= resultIndex; j--) {
+ result[j] = (byte) (allBits & 0xff); // Bottom 8
+ // bits
+ allBits = allBits >>> 8;
+ }
+ break;
+ case 2 :
+ // 2 pad bytes mean 2 (4-2) extra Base64 bytes of input, 12 bits
+ // of data, of which only 8 are meaningful
+ // Or: 1 byte of result data
+ allBits = 0;
+ // Loop 2 times gathering input bits
+ for (int j = 0; j < 2; j++)
+ allBits = (allBits << 6) | decodeDigit(data[dataIndex++]);
+ // NOTE - The code below ends up being equivalent to allBits =
+ // allBits>>>4
+ // But we code it in a non-optimized way for clarity
+ // The 3rd and 4th, missing 6 bits are all 0
+ allBits = allBits << 6;
+ allBits = allBits << 6;
+ // The 3rd and 4th, missing 8 bits are all 0
+ allBits = allBits >>> 8;
+ allBits = allBits >>> 8;
+ result[resultIndex] = (byte) (allBits & 0xff); // Bottom
+ // 8
+ // bits
+ break;
+ }
+ return result;
+ }
+
+ /**
+ * This method converts a Base 64 digit to its numeric value.
+ *
+ * @param data digit (character) to convert
+ * @return value for the digit
+ */
+ static int decodeDigit(byte data) {
+ char charData = (char) data;
+ if (charData <= 'Z' && charData >= 'A')
+ return charData - 'A';
+ if (charData <= 'z' && charData >= 'a')
+ return charData - 'a' + 26;
+ if (charData <= '9' && charData >= '0')
+ return charData - '0' + 52;
+ switch (charData) {
+ case '+' :
+ return 62;
+ case '/' :
+ return 63;
+ default :
+ throw new IllegalArgumentException("Invalid char to decode: " + data); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * This method encodes the byte array into a char array in base 64 according
+ * to the specification given by the RFC 1521 (5.2).
+ *
+ * @param data the encoded char array
+ * @return the byte array that needs to be encoded
+ */
+ public static byte[] encode(byte[] data) {
+ int sourceChunks = data.length / 3;
+ int len = ((data.length + 2) / 3) * 4;
+ byte[] result = new byte[len];
+ int extraBytes = data.length - (sourceChunks * 3);
+ // Each 4 bytes of input (encoded) we end up with 3 bytes of output
+ int dataIndex = 0;
+ int resultIndex = 0;
+ int allBits = 0;
+ for (int i = 0; i < sourceChunks; i++) {
+ allBits = 0;
+ // Loop 3 times gathering input bits (3 * 8 = 24)
+ for (int j = 0; j < 3; j++)
+ allBits = (allBits << 8) | (data[dataIndex++] & 0xff);
+ // Loop 4 times generating output bits (4 * 6 = 24)
+ for (int j = resultIndex + 3; j >= resultIndex; j--) {
+ result[j] = (byte) digits[(allBits & 0x3f)]; // Bottom
+ // 6
+ // bits
+ allBits = allBits >>> 6;
+ }
+ resultIndex += 4; // processed 4 result bytes
+ }
+ // Now we do the extra bytes in case the original (non-encoded) data
+ // is not multiple of 4 bytes
+ switch (extraBytes) {
+ case 1 :
+ allBits = data[dataIndex++]; // actual byte
+ allBits = allBits << 8; // 8 bits of zeroes
+ allBits = allBits << 8; // 8 bits of zeroes
+ // Loop 4 times generating output bits (4 * 6 = 24)
+ for (int j = resultIndex + 3; j >= resultIndex; j--) {
+ result[j] = (byte) digits[(allBits & 0x3f)]; // Bottom
+ // 6
+ // bits
+ allBits = allBits >>> 6;
+ }
+ // 2 pad tags
+ result[result.length - 1] = (byte) '=';
+ result[result.length - 2] = (byte) '=';
+ break;
+ case 2 :
+ allBits = data[dataIndex++]; // actual byte
+ allBits = (allBits << 8) | (data[dataIndex++] & 0xff); // actual
+ // byte
+ allBits = allBits << 8; // 8 bits of zeroes
+ // Loop 4 times generating output bits (4 * 6 = 24)
+ for (int j = resultIndex + 3; j >= resultIndex; j--) {
+ result[j] = (byte) digits[(allBits & 0x3f)]; // Bottom
+ // 6
+ // bits
+ allBits = allBits >>> 6;
+ }
+ // 1 pad tag
+ result[result.length - 1] = (byte) '=';
+ break;
+ }
+ return result;
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/DNChainMatching.java b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/DNChainMatching.java
new file mode 100644
index 000000000..c79dfa086
--- /dev/null
+++ b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/DNChainMatching.java
@@ -0,0 +1,385 @@
+/*******************************************************************************
+ * 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.util.ArrayList;
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * This class contains a method to match a distinguished name (DN) chain against
+ * and DN chain pattern.
+ * <p>
+ * The format of DNs are given in RFC 2253. We represent a signature chain for
+ * an X.509 certificate as a semicolon separated list of DNs. This is what we
+ * refer to as the DN chain. Each DN is made up of relative distinguished names
+ * (RDN) which in turn are made up of key value pairs. For example:
+ *
+ * <pre>
+ *
+ *
+ * cn=ben+ou=research,o=ACME,c=us;ou=Super CA,c=CA
+ *
+ *
+ * </pre>
+ *
+ * is made up of two DNs: "<code>cn=ben+ou=research,o=ACME,c=us</code>" and "
+ * <code>ou=Super CA,c=CA</code>". The first DN is made of of three RDNs: "
+ * <code>cn=ben+ou=research</code>" and "<code>o=ACME</code>" and "
+ * <code>c=us</code>". The first RDN has two name value pairs: "
+ * <code>cn=ben</code>" and "<code>ou=research</code>".
+ * <p>
+ * A chain pattern makes use of wildcards ('*') to match against DNs, DN
+ * prefixes, and value. If a DN in a DN chain is made up of a wildcard ("*"),
+ * that wildcard will match zero or more DNs in the chain. If the first RDN of a
+ * DN is the wildcard, that DN will match any other DN with the same suffix (the
+ * DN with the wildcard RDN removed). If a value of a name/value pair is a
+ * wildcard, the value will match any value for that name.
+ */
+public class DNChainMatching {
+ /**
+ * Check the name/value pairs of the rdn against the pattern.
+ *
+ * @param rdn ArrayList of name value pairs for a given RDN.
+ * @param rdnPattern ArrayList of name value pattern pairs.
+ * @return true if the list of name value pairs match the pattern.
+ */
+ private static boolean rdnmatch(ArrayList rdn, ArrayList rdnPattern) {
+ if (rdn.size() != rdnPattern.size())
+ return false;
+ for (int i = 0; i < rdn.size(); i++) {
+ String rdnNameValue = (String) rdn.get(i);
+ String patNameValue = (String) rdnPattern.get(i);
+ int rdnNameEnd = rdnNameValue.indexOf('=');
+ int patNameEnd = patNameValue.indexOf('=');
+ if (rdnNameEnd != patNameEnd || !rdnNameValue.regionMatches(0, patNameValue, 0, rdnNameEnd)) {
+ return false;
+ }
+ String patValue = patNameValue.substring(patNameEnd);
+ String rdnValue = rdnNameValue.substring(rdnNameEnd);
+ if (!rdnValue.equals(patValue) && !patValue.equals("=*") && !patValue.equals("=#16012a")) { //$NON-NLS-1$ //$NON-NLS-2$
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean dnmatch(ArrayList dn, ArrayList dnPattern) {
+ int dnStart = 0;
+ int patStart = 0;
+ int patLen = dnPattern.size();
+ if (patLen == 0) {
+ return false;
+ }
+ if (dnPattern.get(0).equals("*")) { //$NON-NLS-1$
+ patStart = 1;
+ patLen--;
+ }
+ if (dn.size() < patLen) {
+ return false;
+ } else if (dn.size() > patLen) {
+ if (!dnPattern.get(0).equals("*")) { //$NON-NLS-1$
+ // If the number of rdns do not match we must have a prefix map
+ return false;
+ }
+ // The rdnPattern and rdn must have the same number of elements
+ dnStart = dn.size() - patLen;
+ }
+ for (int i = 0; i < patLen; i++) {
+ if (!rdnmatch((ArrayList) dn.get(i + dnStart), (ArrayList) dnPattern.get(i + patStart))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Parses a distinguished name chain and returns an ArrayList where each
+ * element represents a distinguished name (DN) in the chain of DNs. Each
+ * element will be either a String, if the element represents a wildcard
+ * ("*"), or an ArrayList representing an RDN. Each element in the RDN
+ * ArrayList will be a String, if the element represents a wildcard ("*"),
+ * or an ArrayList of Strings, each String representing a name/value pair in
+ * the RDN.
+ *
+ * @param dnChain
+ * @return a list of DNs.
+ * @throws IllegalArgumentException
+ */
+ private static ArrayList parseDNchain(String dnChain) throws IllegalArgumentException {
+ ArrayList parsed = new ArrayList();
+ int startIndex = 0;
+ startIndex = skipSpaces(dnChain, startIndex);
+ while (startIndex < dnChain.length()) {
+ int endIndex = startIndex;
+ boolean inQuote = false;
+ out: while (endIndex < dnChain.length()) {
+ char c = dnChain.charAt(endIndex);
+ switch (c) {
+ case '"' :
+ inQuote = !inQuote;
+ break;
+ case '\\' :
+ endIndex++; // skip the escaped char
+ break;
+ case ';' :
+ if (!inQuote)
+ break out;
+ }
+ endIndex++;
+ }
+ if (endIndex > dnChain.length()) {
+ throw new IllegalArgumentException("unterminated escape"); //$NON-NLS-1$
+ }
+ parsed.add(dnChain.substring(startIndex, endIndex));
+ startIndex = endIndex + 1;
+ startIndex = skipSpaces(dnChain, startIndex);
+ }
+ // Now we parse is a list of strings, lets make ArrayList of rdn out of
+ // them
+ for (int i = 0; i < parsed.size(); i++) {
+ String dn = (String) parsed.get(i);
+ if (dn.equals("*")) //$NON-NLS-1$
+ continue;
+ ArrayList rdns = new ArrayList();
+ if (dn.charAt(0) == '*') {
+ if (dn.charAt(1) != ',')
+ throw new IllegalArgumentException("invalid wildcard prefix"); //$NON-NLS-1$
+ rdns.add("*"); //$NON-NLS-1$
+ dn = new X500Principal(dn.substring(2)).getName(X500Principal.CANONICAL);
+ } else {
+ dn = new X500Principal(dn).getName(X500Principal.CANONICAL);
+ }
+ // Now dn is a nice CANONICAL DN
+ parseDN(dn, rdns);
+ parsed.set(i, rdns);
+ }
+ if (parsed.size() == 0) {
+ throw new IllegalArgumentException("empty DN chain"); //$NON-NLS-1$
+ }
+ return parsed;
+ }
+
+ /**
+ * Increment startIndex until the end of dnChain is hit or until it is the
+ * index of a non-space character.
+ */
+ private static int skipSpaces(String dnChain, int startIndex) {
+ while (startIndex < dnChain.length() && dnChain.charAt(startIndex) == ' ')
+ startIndex++;
+ return startIndex;
+ }
+
+ /**
+ * Takes a distinguished name in canonical form and fills in the rdnArray
+ * with the extracted RDNs.
+ *
+ * @param dn the distinguished name in canonical form.
+ * @param rdnArray the array to fill in with RDNs extracted from the dn
+ * @throws IllegalArgumentException if a formatting error is found.
+ */
+ private static void parseDN(String dn, ArrayList rdnArray) throws IllegalArgumentException {
+ int startIndex = 0;
+ char c = '\0';
+ ArrayList nameValues = new ArrayList();
+ while (startIndex < dn.length()) {
+ int endIndex;
+ for (endIndex = startIndex; endIndex < dn.length(); endIndex++) {
+ c = dn.charAt(endIndex);
+ if (c == ',' || c == '+')
+ break;
+ if (c == '\\') {
+ endIndex++; // skip the escaped char
+ }
+ }
+ if (endIndex > dn.length())
+ throw new IllegalArgumentException("unterminated escape " + dn); //$NON-NLS-1$
+ nameValues.add(dn.substring(startIndex, endIndex));
+ if (c != '+') {
+ rdnArray.add(nameValues);
+ if (endIndex != dn.length())
+ nameValues = new ArrayList();
+ else
+ nameValues = null;
+ }
+ startIndex = endIndex + 1;
+ }
+ if (nameValues != null) {
+ throw new IllegalArgumentException("improperly terminated DN " + dn); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * This method will return an 'index' which points to a non-wild-card DN or
+ * the end-of-arraylist.
+ */
+ private static int skipWildCards(ArrayList dnChainPattern, int dnChainPatternIndex) throws IllegalArgumentException {
+ int i;
+ for (i = dnChainPatternIndex; i < dnChainPattern.size(); i++) {
+ Object dnPattern = dnChainPattern.get(i);
+ if (dnPattern instanceof String) {
+ if (!dnPattern.equals("*")) { //$NON-NLS-1$
+ throw new IllegalArgumentException("expected wild-card in DN pattern"); //$NON-NLS-1$
+ }
+ // otherwise continue skipping over wild cards
+ } else if (dnPattern instanceof ArrayList) {
+ // if its an arraylist then we have our 'non-wild-card' DN
+ break;
+ } else {
+ // unknown member of the DNChainPattern
+ throw new IllegalArgumentException("expected String or Arraylist in DN Pattern"); //$NON-NLS-1$
+ }
+ }
+ // i either points to end-of-arraylist, or to the first non-wild-card
+ // pattern
+ // after dnChainPatternIndex
+ return i;
+ }
+
+ /**
+ * recursively attempt to match the DNChain, and the DNChainPattern where
+ * DNChain is of the format: "DN;DN;DN;" and DNChainPattern is of the
+ * format: "DNPattern;*;DNPattern" (or combinations of this)
+ */
+ private static boolean dnChainMatch(ArrayList dnChain, int dnChainIndex, ArrayList dnChainPattern, int dnChainPatternIndex) throws IllegalArgumentException {
+ if (dnChainIndex >= dnChain.size()) {
+ return false;
+ }
+ if (dnChainPatternIndex >= dnChainPattern.size()) {
+ return false;
+ }
+ // check to see what the pattern starts with
+ Object dnPattern = dnChainPattern.get(dnChainPatternIndex);
+ if (dnPattern instanceof String) {
+ if (!dnPattern.equals("*")) { //$NON-NLS-1$
+ throw new IllegalArgumentException("expected wild-card in DN pattern"); //$NON-NLS-1$
+ }
+ // here we are processing a wild card as the first DN
+ // skip all wild-card DN's
+ dnChainPatternIndex = skipWildCards(dnChainPattern, dnChainPatternIndex);
+ if (dnChainPatternIndex >= dnChainPattern.size()) {
+ // the entire DNChainPattern was wild-cards, so we have a match
+ return true;
+ }
+ //
+ // we will now recursively call to see if the rest of the
+ // DNChainPattern
+ // matches increasingly smaller portions of the rest of the DNChain
+ //
+ for (int i = dnChainIndex; i < dnChain.size(); i++) {
+ if (dnChainMatch(dnChain, i, dnChainPattern, dnChainPatternIndex)) {
+ return true;
+ }
+ }
+ // if we are here, then we didn't find a match.. fall through to
+ // failure
+ } else if (dnPattern instanceof ArrayList) {
+ // here we have to do a deeper check for each DN in the pattern
+ // until we hit a wild card
+ do {
+ if (!dnmatch((ArrayList) dnChain.get(dnChainIndex), (ArrayList) dnPattern)) {
+ return false;
+ }
+ // go to the next set of DN's in both chains
+ dnChainIndex++;
+ dnChainPatternIndex++;
+ // if we finished the pattern then it all matched
+ if ((dnChainIndex >= dnChain.size()) && (dnChainPatternIndex >= dnChainPattern.size())) {
+ return true;
+ }
+ // if the DN Chain is finished, but the pattern isn't finished
+ // then if the rest of the pattern is not wildcard then we are
+ // done
+ if (dnChainIndex >= dnChain.size()) {
+ dnChainPatternIndex = skipWildCards(dnChainPattern, dnChainPatternIndex);
+ // return TRUE iff the pattern index moved past the
+ // array-size
+ // (implying that the rest of the pattern is all wild-cards)
+ return (dnChainPatternIndex >= dnChainPattern.size());
+ }
+ // if the pattern finished, but the chain continues then we have
+ // a mis-match
+ if (dnChainPatternIndex >= dnChainPattern.size()) {
+ return false;
+ }
+ // get the next DN Pattern
+ dnPattern = dnChainPattern.get(dnChainPatternIndex);
+ if (dnPattern instanceof String) {
+ if (!dnPattern.equals("*")) { //$NON-NLS-1$
+ throw new IllegalArgumentException("expected wild-card in DN pattern"); //$NON-NLS-1$
+ }
+ // if the next DN is a 'wild-card', then we will recurse
+ return dnChainMatch(dnChain, dnChainIndex, dnChainPattern, dnChainPatternIndex);
+ } else if (!(dnPattern instanceof ArrayList)) {
+ throw new IllegalArgumentException("expected String or Arraylist in DN Pattern"); //$NON-NLS-1$
+ }
+ // if we are here, then we will just continue to the match the
+ // next set of DN's from the DNChain, and the DNChainPattern
+ // since both are array-lists
+ } while (true);
+ // should never reach here?
+ } else {
+ throw new IllegalArgumentException("expected String or Arraylist in DN Pattern"); //$NON-NLS-1$
+ }
+ // if we get here, the the default return is 'mis-match'
+ return false;
+ }
+
+ /**
+ * Matches a distinguished name chain against a pattern of a distinguished
+ * name chain.
+ *
+ * @param dnChain
+ * @param pattern the pattern of distinguished name (DN) chains to match
+ * against the dnChain. Wildcards "*" can be used in three cases:
+ * <ol>
+ * <li>As a DN. In this case, the DN will consist of just the "*".
+ * It will match zero or more DNs. For example, "cn=me,c=US;*;cn=you"
+ * will match "cn=me,c=US";cn=you" and
+ * "cn=me,c=US;cn=her,c=CA;cn=you".
+ * <li>As a DN prefix. In this case, the DN must start with "*,".
+ * The wild card will match zero or more RDNs at the start of a DN.
+ * For example, "*,cn=me,c=US;cn=you" will match "cn=me,c=US";cn=you"
+ * and "ou=my org unit,o=my org,cn=me,c=US;cn=you"</li>
+ * <li>As a value. In this case the value of a name value pair in an
+ * RDN will be a "*". The wildcard will match any value for the given
+ * name. For example, "cn=*,c=US;cn=you" will match
+ * "cn=me,c=US";cn=you" and "cn=her,c=US;cn=you", but it will not
+ * match "ou=my org unit,c=US;cn=you". If the wildcard does not occur
+ * by itself in the value, it will not be used as a wildcard. In
+ * other words, "cn=m*,c=US;cn=you" represents the common name of
+ * "m*" not any common name starting with "m".</li>
+ * </ol>
+ * @return true if dnChain matches the pattern.
+ * @throws IllegalArgumentException
+ */
+ public static boolean match(String dnChain, String pattern) {
+ ArrayList parsedDNChain;
+ ArrayList parsedDNPattern;
+ try {
+ parsedDNChain = parseDNchain(dnChain);
+ } catch (IllegalArgumentException e) {
+ System.err.println(e.getMessage() + ": " + dnChain); //$NON-NLS-1$
+ return false;
+ }
+ try {
+ parsedDNPattern = parseDNchain(pattern);
+ } catch (IllegalArgumentException e) {
+ System.err.println(e.getMessage() + ": " + pattern); //$NON-NLS-1$
+ return false;
+ }
+ try {
+ return dnChainMatch(parsedDNChain, 0, parsedDNPattern, 0);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/DigestedInputStream.java b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/DigestedInputStream.java
new file mode 100644
index 000000000..b54f0c6ba
--- /dev/null
+++ b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/DigestedInputStream.java
@@ -0,0 +1,155 @@
+/*******************************************************************************
+ * 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.security.MessageDigest;
+
+/**
+ * This InputStream will calculate the digest of bytes as they are read. At the
+ * end of the InputStream, it will calculate the digests and throw an exception
+ * if the calculated digest do not match the expected digests.
+ */
+class DigestedInputStream extends FilterInputStream {
+ MessageDigest digest[];
+ byte result[][];
+ long remaining;
+
+ /**
+ * Constructs an InputStream that uses another InputStream as a source and
+ * calculates the digest. At the end of the stream an exception will be
+ * thrown if the calculated digest doesn't match the passed digest.
+ *
+ * @param in the stream to use as an input source.
+ * @param digest the MessageDigest used to compute the digest.
+ * @param result the expected digest.
+ */
+ DigestedInputStream(InputStream in, MessageDigest digest[], byte result[][], long size) {
+ super(in);
+ this.remaining = size;
+ this.digest = new MessageDigest[digest.length];
+ for (int i = 0; i < digest.length; i++) {
+ try {
+ this.digest[i] = (MessageDigest) digest[i].clone();
+ } catch (CloneNotSupportedException e) {
+ // This shouldn't happen since MessageDigest supports clone!
+ throw new RuntimeException("MessageDigest must support clone"); //$NON-NLS-1$
+ }
+ }
+ this.result = result;
+ }
+
+ /**
+ * Not supported.
+ */
+ public synchronized void mark(int readlimit) {
+ // Noop, we don't want to support this
+ }
+
+ /**
+ * Always returns false.
+ */
+ public boolean markSupported() {
+ return false;
+ }
+
+ /**
+ * Read a byte from the InputStream. Digests are calculated on reads. At the
+ * end of the stream the calculated digests must match the expected digests.
+ *
+ * @return the character read or -1 at end of stream.
+ * @throws IOException if there was an problem reading the byte or at the
+ * end of the stream the calculated digests do not match the
+ * expected digests.
+ * @see java.io.InputStream#read()
+ */
+ public int read() throws IOException {
+ if (remaining <= 0)
+ return -1;
+ int c = super.read();
+ if (c != -1) {
+ for (int i = 0; i < digest.length; i++)
+ digest[i].update((byte) c);
+ remaining--;
+ } else {
+ // We hit eof so set remaining to zero
+ remaining = 0;
+ }
+ if (remaining == 0)
+ verifyDigests();
+ return c;
+ }
+
+ private void verifyDigests() throws IOException {
+ // Check the digest at end of file
+ for (int i = 0; i < digest.length; i++) {
+ byte rc[] = digest[i].digest();
+ if (!MessageDigest.isEqual(result[i], rc))
+ throw new IOException("Corrupted file: the digest is valid for " + digest[i].getAlgorithm()); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Read bytes from the InputStream. Digests are calculated on reads. At the
+ * end of the stream the calculated digests must match the expected digests.
+ *
+ * @return the number of characters read or -1 at end of stream.
+ * @throws IOException if there was an problem reading or at the
+ * end of the stream the calculated digests do not match the
+ * expected digests.
+ * @see java.io.InputStream#read()
+ */
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (remaining <= 0)
+ return -1;
+ int rc = super.read(b, off, len);
+ if (rc != -1) {
+ for (int i = 0; i < digest.length; i++)
+ digest[i].update(b, off, rc);
+ remaining -= rc;
+ } else {
+ // We hit eof so set remaining to zero
+ remaining = 0;
+ }
+ if (remaining <= 0)
+ verifyDigests();
+ return rc;
+ }
+
+ /**
+ * Not supported.
+ *
+ * @throws IOException always thrown if this method is called since mark/reset is not supported.
+ * @see java.io.InputStream#reset()
+ */
+ public synchronized void reset() throws IOException {
+ // Throw IOException, we don't want to support this
+ throw new IOException("Reset not supported"); //$NON-NLS-1$
+ }
+
+ /**
+ * This method is implemented as a read into a bitbucket.
+ */
+ public long skip(long n) throws IOException {
+ byte buffer[] = new byte[4096];
+ long count = 0;
+ while (n - count > 0) {
+ int rc = (n - count) > buffer.length ? buffer.length : (int) (n - count);
+ rc = read(buffer, 0, rc);
+ if (rc == -1)
+ break;
+ count += rc;
+ n -= rc;
+ }
+ return count;
+ }
+} \ No newline at end of file
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
new file mode 100644
index 000000000..10c013dc4
--- /dev/null
+++ b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/KeyStores.java
@@ -0,0 +1,180 @@
+/*******************************************************************************
+ * 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.MalformedURLException;
+import java.net.URL;
+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();
+ try {
+ URL url;
+ try {
+ url = new URL(urlSpec);
+ } catch (MalformedURLException mue) {
+ url = new URL(rootURL, urlSpec);
+ }
+ KeyStore ks = KeyStore.getInstance(type);
+ ks.load(url.openStream(), null);
+ keyStores.add(ks);
+ } catch (Exception e) {
+ SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.WARNING, e);
+ }
+ }
+
+ /**
+ * 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/PKCS7Processor.java b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/PKCS7Processor.java
new file mode 100644
index 000000000..77eb6472a
--- /dev/null
+++ b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/PKCS7Processor.java
@@ -0,0 +1,249 @@
+/*******************************************************************************
+ * 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.ByteArrayInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.*;
+import java.security.cert.*;
+import java.security.cert.Certificate;
+import java.util.*;
+import javax.security.auth.x500.X500Principal;
+import org.eclipse.osgi.internal.provisional.verifier.CertificateChain;
+
+/**
+ * This class processes a PKCS7 file. See RFC 2315 for specifics.
+ */
+public class PKCS7Processor implements CertificateChain {
+ private static final int SIGNEDDATA_OID[] = {1, 2, 840, 113549, 1, 7, 2};
+ private static final int MD5_OID[] = {1, 2, 840, 113549, 2, 5};
+ private static final int MD2_OID[] = {1, 2, 840, 113549, 2, 2};
+ private static final int SHA1_OID[] = {1, 3, 14, 3, 2, 26};
+ private static final int DSA_OID[] = {1, 2, 840, 10040, 4, 1};
+ private static final int RSA_OID[] = {1, 2, 840, 113549, 1, 1, 1};
+
+ private static CertificateFactory certFact;
+ private static KeyStores keyStores = new KeyStores();
+ static {
+ try {
+ certFact = CertificateFactory.getInstance("X.509"); //$NON-NLS-1$
+ } catch (CertificateException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ private String certChain;
+ private Certificate signerCert;
+ private Certificate rootCert;
+ private boolean trusted;
+
+ String oid2String(int oid[]) {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < oid.length; i++) {
+ if (i > 0)
+ sb.append('.');
+ sb.append(oid[i]);
+ }
+ return sb.toString();
+ }
+
+ String findEncryption(int encOid[]) throws NoSuchAlgorithmException {
+ if (Arrays.equals(DSA_OID, encOid)) {
+ return "DSA"; //$NON-NLS-1$
+ }
+ if (Arrays.equals(RSA_OID, encOid)) {
+ return "RSA"; //$NON-NLS-1$
+ }
+ throw new NoSuchAlgorithmException("No algorithm found for " + oid2String(encOid)); //$NON-NLS-1$
+ }
+
+ String findDigest(int digestOid[]) throws NoSuchAlgorithmException {
+ if (Arrays.equals(SHA1_OID, digestOid)) {
+ return "SHA1"; //$NON-NLS-1$
+ }
+ if (Arrays.equals(MD5_OID, digestOid)) {
+ return "MD5"; //$NON-NLS-1$
+ }
+ if (Arrays.equals(MD2_OID, digestOid)) {
+ return "MD2"; //$NON-NLS-1$
+ }
+ 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[] signerCert, byte[] rootCert) throws CertificateException {
+ this.certChain = certChain;
+ this.trusted = trusted;
+ this.signerCert = certFact.generateCertificate(new ByteArrayInputStream(signerCert));
+ this.rootCert = certFact.generateCertificate(new ByteArrayInputStream(rootCert));
+ }
+
+ public PKCS7Processor(byte pkcs7[], int pkcs7Offset, int pkcs7Length, byte data[], int dataOffset, int dataLength) throws IOException, InvalidKeyException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException {
+ // First grab the certificates
+ Collection certs = certFact.generateCertificates(new ByteArrayInputStream(pkcs7, pkcs7Offset, pkcs7Length));
+ BERProcessor bp = new BERProcessor(pkcs7, pkcs7Offset, pkcs7Length);
+ // Just do a sanity check and make sure we are actually doing a PKCS7
+ // stream
+ bp = bp.stepInto();
+ if (!Arrays.equals(bp.getObjId(), SIGNEDDATA_OID)) {
+ throw new IOException("Not a valid PKCS#7 file"); //$NON-NLS-1$
+ }
+ bp.stepOver(); // skip over the oid
+ bp = bp.stepInto(); // go into the Signed data
+ bp = bp.stepInto(); // It is a structure;
+ bp.stepOver(); // Yeah, yeah version = 1
+ bp.stepOver(); // We'll see the digest stuff again;
+ bp.stepOver(); // We'll see the contentInfo in signerinfo
+ // Okay, here are our certificates.
+ bp.stepOver();
+ if (bp.classOfTag == BERProcessor.UNIVERSAL_TAGCLASS && bp.tag == 1) {
+ bp.stepOver(); // Don't use the CRLs if present
+ }
+ bp = bp.stepInto(); // Step into the set of signerinfos
+ bp = bp.stepInto(); // Step into the signerinfo sequence
+ bp.stepOver(); // Skip the version
+ BERProcessor issuerAndSN = bp.stepInto();
+ X500Principal signerIssuer = new X500Principal(new ByteArrayInputStream(issuerAndSN.buffer, issuerAndSN.offset, issuerAndSN.endOffset - issuerAndSN.offset));
+ issuerAndSN.stepOver();
+ BigInteger sn = issuerAndSN.getIntValue();
+ Certificate newSignerCert = null;
+ Iterator itr = certs.iterator();
+ while (itr.hasNext()) {
+ X509Certificate cert = (X509Certificate) itr.next();
+ if (cert.getIssuerX500Principal().equals(signerIssuer) && cert.getSerialNumber().equals(sn)) {
+ newSignerCert = cert;
+ break;
+ }
+ }
+ if (newSignerCert == null)
+ throw new CertificateException("Signer certificate not in pkcs7block"); //$NON-NLS-1$
+ bp.stepOver(); // skip the issuer name and serial number
+ BERProcessor digestAlg = bp.stepInto();
+ String digest = findDigest(digestAlg.getObjId());
+ bp.stepOver(); // skip the digest alg
+ if (bp.classOfTag == BERProcessor.CONTEXTSPECIFIC_TAGCLASS) {
+ bp.stepOver(); // This would be the authenticated attributes
+ }
+ BERProcessor encryptionAlg = bp.stepInto();
+ String enc = findEncryption(encryptionAlg.getObjId());
+ bp.stepOver(); // skip the encryption alg
+ byte signature[] = bp.getBytes();
+ Signature sig = Signature.getInstance(digest + "with" + enc); //$NON-NLS-1$
+ sig.initVerify(newSignerCert.getPublicKey());
+ sig.update(data, dataOffset, dataLength);
+ if (!sig.verify(signature)) {
+ throw new SignatureException("Signature doesn't verify"); //$NON-NLS-1$
+ }
+ this.signerCert = newSignerCert;
+ StringBuffer sb = new StringBuffer();
+ X509Certificate xcert = (X509Certificate) newSignerCert;
+ // We save off the previous certificate so that we can
+ // verify with the next certificate in the chain
+ Certificate prevCert = null;
+ boolean valid = true;
+ while (true) {
+ // TODO The CertificateFactory may do this check, but better safe than sorry
+ try {
+ xcert.checkValidity();
+ } catch (CertificateException e) {
+ valid = false;
+ }
+ if (prevCert != null) {
+ prevCert.verify(xcert.getPublicKey());
+ }
+ prevCert = xcert;
+
+ X500Principal subject = xcert.getSubjectX500Principal();
+ X500Principal issuer = xcert.getIssuerX500Principal();
+ if (sb.length() > 0)
+ sb.append("; "); //$NON-NLS-1$
+ sb.append(subject);
+ if (subject.equals(issuer))
+ break;
+ xcert = null;
+ itr = certs.iterator();
+ while (itr.hasNext()) {
+ X509Certificate cert = (X509Certificate) itr.next();
+ if (cert.getSubjectX500Principal().equals(issuer)) {
+ xcert = cert;
+ }
+ }
+ if (xcert == null)
+ throw new CertificateException(subject + " missing from chain"); //$NON-NLS-1$
+ }
+ rootCert = xcert;
+ // Now we have to make sure that the CA certificate (xcert) is in the KeyStore
+ trusted = valid && keyStores.isTrusted(xcert);
+ certChain = sb.toString();
+ }
+
+ /**
+ * Returns the Certificate of the signer of this PKCS7Block
+ */
+ public Certificate getSigner() {
+ return signerCert;
+ }
+
+ public Certificate getRoot() {
+ return rootCert;
+ }
+
+ /**
+ * 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 (rootCert == null || signerCert == null)
+ return false;
+ CertificateChain chain = (CertificateChain) obj;
+ return trusted == chain.isTrusted() && rootCert.equals(chain.getRoot()) && signerCert.equals(chain.getSigner()) && (certChain == null ? chain.getChain() == null : certChain.equals(chain.getChain()));
+ }
+ /*
+ 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
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
new file mode 100644
index 000000000..513dd4c70
--- /dev/null
+++ b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/SignedBundleFile.java
@@ -0,0 +1,638 @@
+/*******************************************************************************
+ * 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.URL;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+import org.eclipse.osgi.baseadaptor.bundlefile.*;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.internal.provisional.verifier.CertificateChain;
+import org.eclipse.osgi.internal.provisional.verifier.CertificateVerifier;
+
+/**
+ * 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 {
+ /**
+ * A precomputed MD5 MessageDigest. We will clone this everytime we want to
+ * use it.
+ */
+ static MessageDigest md5;
+ /**
+ * A precomputed SHA1 MessageDigest. We will clone this everytime we want to
+ * use it.
+ */
+ static MessageDigest sha1;
+ static {
+ try {
+ md5 = MessageDigest.getInstance("MD5"); //$NON-NLS-1$
+ } catch (NoSuchAlgorithmException e) {
+ // TODO Log this somewhere
+ e.printStackTrace();
+ }
+ try {
+ sha1 = MessageDigest.getInstance("SHA1"); //$NON-NLS-1$
+ } catch (NoSuchAlgorithmException e1) {
+ // TODO Log this somewhere
+ e1.printStackTrace();
+ }
+ }
+ //
+ // following are variables and methods to cache the entries related data
+ // for a given MF file
+ //
+ private static final String MF_ENTRY_NEWLN_NAME = "\nName: "; //$NON-NLS-1$
+ private static final String MF_ENTRY_NAME = "Name: "; //$NON-NLS-1$
+ private static final String MF_DIGEST_PART = "-Digest: "; //$NON-NLS-1$
+ private static final String digestManifestSearch = "-Digest-Manifest: "; //$NON-NLS-1$
+ private static final int digestManifestSearchLen = digestManifestSearch.length();
+ private static final String[] EMPTY_STRING = new String[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[] to use. Before using the MessageDigests
+ * must be cloned.
+ */
+ 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 MessageDigest results. Each
+ * result in the array will correspond to the MessageDigest at the same
+ * position for the same entry in digests4entries.
+ */
+ 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;
+ }
+
+ /**
+ * @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
+ * 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[]) {
+ // 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);
+
+ while ((entryStartOffset != -1) && (entryStartOffset < mfStr.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 = getName(entryStr);
+
+ // if we could retrieve an entry name, then we will extract
+ // digest type list, and the digest value list
+ if (entryName != null) {
+
+ String digestLines[] = getDigestLines(entryStr);
+
+ if (digestLines != null) {
+ MessageDigest digestList[] = getDigestList(digestLines);
+ byte digestResultsList[][] = getDigestResultsList(digestLines);
+
+ //
+ // 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 ((digestList != null) && (digestResultsList != null) && (digestList.length != digestResultsList.length)) {
+ throw new RuntimeException("digest and digest results were different counts.."); //$NON-NLS-1$
+ }
+ // see if we should insert this entry
+ if ((entryName != null) && (digestList != null) && (digestResultsList != null)) {
+ if (digests4entries == null) {
+ digests4entries = new Hashtable(10);
+ results4entries = new Hashtable(10);
+ }
+ // TODO throw exception if duplicate entry??
+ if (!digests4entries.contains(entryName)) {
+ digests4entries.put(entryName, digestList);
+ results4entries.put(entryName, digestResultsList);
+ }
+ } // could retrieve entry-name, digest list, and results list
+
+ } // 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 getName(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]"
+ *
+ * @return this function returns an array of strings for each
+ * recognized digest entry which will at most have 2 entries
+ * (since only MD5 and SHA1 are recognized here),
+ * or a 'null' will be returned if none of the digest algorithms
+ * were recognized, or more then 2 valid lines are found
+ */
+ private String[] getDigestLines(String manifestEntry) {
+
+ // this is the common case, that we will return 1 string
+ // for a single digest algorithm that we recognize
+ String digestLines[] = new String[1];
+ // keeps track of how many valid digest lines we found
+ int numFound = 0;
+
+ // 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);
+ if (digestLine.startsWith("MD5") || digestLine.startsWith("SHA1")) { //$NON-NLS-1$ //$NON-NLS-2$
+ // if we are here, then we will attempt to keep
+ // track of this digest line
+ numFound++;
+
+ if (numFound == 2) {
+ // SPECIAL CASE: if 2 lines are found
+ // then we grow the digestLines array manually
+ String tempDigestLines[] = digestLines;
+ digestLines = new String[2];
+ digestLines[0] = tempDigestLines[0];
+ digestLines[1] = digestLine;
+ } else if (numFound == 1) {
+ // SPECIAL CASE: if this is the first line found
+ // then we already have a pre-allocated array for this
+ digestLines[0] = digestLine;
+ } else {
+ // problem..., we found more then 2 valid
+ // digest lines
+ // TODO: (note that we can be more strict here and check
+ // to ensure that we didn't find 2 MD5's or 2 SHA1 lines)
+ return null;
+ }
+ } // if digest algorithm was MD5 or SHA1
+
+ // 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 numFound == 0 ? null : digestLines;
+ }
+
+ private MessageDigest[] getDigestList(String digestLines[]) {
+
+ MessageDigest mdList[] = new MessageDigest[digestLines.length];
+
+ // for each digest-line retrieve the digest algorithm
+ for (int i = 0; i < digestLines.length; i++) {
+ String sDigestLine = digestLines[i];
+ int indexDigest = sDigestLine.indexOf(MF_DIGEST_PART);
+ String sDigestAlgType = sDigestLine.substring(0, indexDigest);
+ if (sDigestAlgType.equals("MD5")) { //$NON-NLS-1$
+ // remember the "algorithm type" object
+ mdList[i] = md5;
+ } else if (sDigestAlgType.equals("SHA1")) { //$NON-NLS-1$
+ // remember the "algorithm type" object
+ mdList[i] = sha1;
+ } else {
+ // unknown algorithm type, we will stop processing this entry
+ mdList = null;
+ break;
+ }
+ }
+ return mdList;
+ }
+
+ private byte[][] getDigestResultsList(String digestLines[]) {
+ byte resultsList[][] = new byte[digestLines.length][];
+ // for each digest-line retrieve the digest result
+ for (int i = 0; i < digestLines.length; i++) {
+ String sDigestLine = digestLines[i];
+ 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[i] = Base64.decode(sResult.getBytes());
+ } catch (Throwable t) {
+ // malformed digest result, no longer processing this entry
+ resultsList = null;
+ break;
+ }
+ }
+ 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.
+ * @throws IOException
+ */
+ void setBundleFile(BundleFile bundleFile) throws IOException {
+ this.bundleFile = bundleFile;
+ if (certsInitialized)
+ return;
+ ArrayList chainList = new ArrayList();
+ BundleEntry be = bundleFile.getEntry("META-INF/MANIFEST.MF"); //$NON-NLS-1$
+ if (be == null) {
+ return;
+ }
+ byte manifestBytes[] = readIntoArray(be);
+ Enumeration en = bundleFile.getEntryPaths("META-INF/"); //$NON-NLS-1$
+ while (en.hasMoreElements()) {
+ String name = (String) en.nextElement();
+ if ((name.endsWith(".DSA") || name.endsWith(".RSA")) && name.indexOf('/') == name.lastIndexOf('/')) { //$NON-NLS-1$ //$NON-NLS-2$
+ be = bundleFile.getEntry(name);
+ byte pkcs7Bytes[] = readIntoArray(be);
+ int dotIndex = name.lastIndexOf('.');
+ be = bundleFile.getEntry(name.substring(0, dotIndex) + ".SF"); //$NON-NLS-1$
+ byte sfBytes[] = readIntoArray(be);
+ // Get the manifest out of the signature file and make sure
+ // it matches MANIFEST.MF
+ if (!checkManifestDigest(manifestBytes, sfBytes)) {
+ // We only recognize signatures over the Manifest
+ // XXX should log something here
+ continue;
+ }
+ // Now that the manifest digest checks out, check the signature
+ PKCS7Processor chain = null;
+ try {
+ chain = new PKCS7Processor(pkcs7Bytes, 0, pkcs7Bytes.length, sfBytes, 0, sfBytes.length);
+ } catch (Exception e) {
+ // For any problems we just bag this signature and move on
+ SignedBundleHook.log("Invalid or untrusted certificate: " + bundleFile.getBaseFile(), FrameworkLogEntry.WARNING, e); //$NON-NLS-1$
+ continue;
+ }
+ if (chain != null && chain.getSigner() != null)
+ chainList.add(chain);
+ }
+ }
+ chains = chainList.size() == 0 ? null : (CertificateChain[]) chainList.toArray(new CertificateChain[chainList.size()]);
+ if (chains != null)
+ populateManifest(manifestBytes);
+ }
+
+ /**
+ * Check the Manifest digests in a signature file. It only returns true if
+ * there is a digest for the manifest and the digest matches the actual
+ * digest of the manifest.
+ *
+ * @param manifestBytes the bytes that make up the real manifest file.
+ * @param sfBytes the bytes that make up the signature file.
+ * @return true if the signature file has a manifest digest entry that
+ * matches the real manifest file.
+ */
+ private boolean checkManifestDigest(byte[] manifestBytes, byte[] sfBytes) {
+ String sf = new String(sfBytes);
+ sf = stripContinuations(sf);
+ boolean foundDigest = false;
+ for (int off = sf.indexOf(digestManifestSearch); off != -1; off = sf.indexOf(digestManifestSearch, off)) {
+ int start = sf.lastIndexOf('\n', off);
+ String result = 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.equals("MD5")) { //$NON-NLS-1$
+ if (manifestMD5Result == null) {
+ manifestMD5Result = calculateDigest(md5, manifestBytes);
+ }
+ result = manifestMD5Result;
+ } else if (digestName.equals("SHA1")) { //$NON-NLS-1$
+ if (manifestSHAResult == null) {
+ manifestSHAResult = calculateDigest(sha1, manifestBytes);
+ }
+ result = manifestSHAResult;
+ }
+ off += digestManifestSearchLen;
+ if (result == null || !sf.startsWith(result, off)) {
+ // XXX should log something here
+ // Skip the signature since we can't verify
+ foundDigest = false;
+ break;
+ }
+ foundDigest = true;
+ }
+ }
+ return foundDigest;
+ }
+
+ /**
+ * Returns the Base64 encoded digest of the passed set of bytes.
+ */
+ private String calculateDigest(MessageDigest digest, byte[] bytes) {
+ String result;
+ try {
+ digest = (MessageDigest) digest.clone();
+ result = new String(Base64.encode(digest.digest(bytes)));
+ } catch (CloneNotSupportedException e1) {
+ // Won't happen since clone is supported by
+ // MessageDigest
+ throw new RuntimeException(digest.getAlgorithm() + " doesn't support clone()"); //$NON-NLS-1$
+ }
+ return result;
+ }
+
+ 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$
+ }
+
+ public File getFile(String path, boolean nativeCode) {
+ return bundleFile.getFile(path, nativeCode);
+ }
+
+ public BundleEntry getEntry(String path) {
+ BundleEntry be = bundleFile.getEntry(path);
+ if (be == null) {
+ if (digests4entries != null && digests4entries.get(path) == null)
+ return null;
+ throw new RuntimeException("A file has been removed from the bundle: " + getBaseFile().toString() + " : " + path); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if (be.getName().startsWith("META-INF/")) //$NON-NLS-1$
+ 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();
+ MessageDigest digests[] = digests4entries == null ? null : (MessageDigest[]) digests4entries.get(name);
+ if (digests == null)
+ return null; // return null if the digest does not exist
+ byte results[][] = (byte[][]) results4entries.get(name);
+ return new DigestedInputStream(nestedEntry.getInputStream(), digests, 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 String[] verifyContent() {
+ if (!isSigned())
+ 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;
+ }
+
+}
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
new file mode 100644
index 000000000..d47f90ba6
--- /dev/null
+++ b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/SignedBundleHook.java
@@ -0,0 +1,148 @@
+/*******************************************************************************
+ * 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.File;
+import java.io.IOException;
+import java.net.URLConnection;
+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.SignedBundleFileFactoryHook;
+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.CertificateVerifier;
+import org.eclipse.osgi.internal.provisional.verifier.CertificateVerifierFactory;
+import org.osgi.framework.*;
+
+/**
+ * Implements signed bundle hook support for the framework
+ */
+public class SignedBundleHook implements AdaptorHook, SignedBundleFileFactoryHook, HookConfigurator, CertificateVerifierFactory {
+ private static BaseAdaptor ADAPTOR;
+ private static String SIGNED_BUNDLE_SUPPORT = "osgi.support.signiture.verify"; //$NON-NLS-1$
+ private static boolean supportSignedBundles = false;
+ private ServiceRegistration reg;
+
+ 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 {
+ reg = context.registerService(CertificateVerifierFactory.class.getName(), this, null);
+ }
+
+ public void frameworkStop(BundleContext context) throws BundleException {
+ if (reg != null) {
+ reg.unregister();
+ reg = 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 createBundleFile(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 && hook.signedBundleFile != null)
+ signedBaseFile = hook.signedBundleFile;
+ else
+ signedBaseFile = new SignedBundleFile();
+ signedBaseFile.setBundleFile(bundleFile);
+ if (signedBaseFile.isSigned()) // only use the signed file if there are certs
+ bundleFile = signedBaseFile;
+ }
+ } catch (IOException e) {
+ // do nothing; its not your responsibility the error will be addressed later
+ }
+ return bundleFile;
+ }
+
+
+ public void addHooks(HookRegistry hookRegistry) {
+ supportSignedBundles = "true".equals(FrameworkProperties.getProperty(SIGNED_BUNDLE_SUPPORT)); //$NON-NLS-1$
+ hookRegistry.addAdaptorHook(this);
+ if (supportSignedBundles) {
+ hookRegistry.addStorageHook(new SignedStorageHook());
+ hookRegistry.setSignedBundleFileFactoryHook(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);
+ 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$
+ BundleFile bundleFile = ((BaseData) data).getBundleFile();
+ if (bundleFile instanceof SignedBundleFile)
+ return (SignedBundleFile) bundleFile; // just reuse the verifier from the bundle file
+ return getVerifier(bundleFile.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);
+ }
+}
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
new file mode 100644
index 000000000..4db8cb14e
--- /dev/null
+++ b/bundles/org.eclipse.osgi/jarverifier/org/eclipse/osgi/internal/verifier/SignedStorageHook.java
@@ -0,0 +1,225 @@
+/*******************************************************************************
+ * 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.security.MessageDigest;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.util.*;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
+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.BundleException;
+
+public class SignedStorageHook implements StorageHook {
+ static final String KEY = SignedStorageHook.class.getName();
+ static final int HASHCODE = KEY.hashCode();
+ private static ArrayList saveChainCache = new ArrayList(5);
+ private static long lastIDSaved;
+ private static ArrayList loadChainCache = new ArrayList(5);
+ private static long lastIDLoaded;
+
+ private BaseData bundledata;
+ SignedBundleFile signedBundleFile;
+ public int getStorageVersion() {
+ return 0;
+ }
+
+ 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 numBytes = is.readInt();
+ byte[] signerBytes = new byte[numBytes];
+ is.read(signerBytes);
+ numBytes = is.readInt();
+ byte[] rootBytes = new byte[numBytes];
+ is.read(rootBytes);
+ try {
+ chains[i] = new PKCS7Processor(chain, trusted, signerBytes, rootBytes);
+ } catch (CertificateException e) {
+ throw new IOException(e.getMessage());
+ }
+ loadChainCache.add(chains[i]);
+ }
+ int numEntries = is.readInt();
+ Hashtable digests = new Hashtable(numEntries);
+ Hashtable results = new Hashtable(numEntries);
+ for (int i = 0; i < numEntries; i++) {
+ String entry = is.readUTF();
+ int numMDs = is.readInt();
+ MessageDigest[] mds = new MessageDigest[numMDs];
+ byte[][] result = new byte[numMDs][];
+ for (int j = 0; j < numMDs; j++) {
+ if (is.readInt() == 0)
+ mds[j] = SignedBundleFile.md5;
+ else
+ mds[j] = SignedBundleFile.sha1;
+ result[j] = new byte[is.readInt()];
+ is.read(result[j]);
+ digests.put(entry, mds);
+ 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 {
+ if (lastIDSaved > bundledata.getBundleID())
+ saveChainCache.clear();
+ lastIDSaved = bundledata.getBundleID();
+ BundleFile bundleFile = bundledata.getBundleFile();
+ CertificateChain[] chains = null;
+ String md5Result = null;
+ String shaResult = null;
+ Hashtable digests = null;
+ Hashtable results = null;
+ if (bundleFile instanceof SignedBundleFile) {
+ SignedBundleFile signedFile = (SignedBundleFile) bundleFile;
+ 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());
+ byte[] certBytes;
+ try {
+ certBytes = chains[i].getSigner().getEncoded();
+ } catch (CertificateEncodingException e) {
+ throw new IOException(e.getMessage());
+ }
+ os.writeInt(certBytes.length);
+ os.write(certBytes);
+ try {
+ certBytes = chains[i].getRoot().getEncoded();
+ } catch (CertificateEncodingException e) {
+ throw new IOException(e.getMessage());
+ }
+ os.writeInt(certBytes.length);
+ os.write(certBytes);
+ }
+ os.writeInt(digests.size());
+ for (Enumeration entries = digests.keys(); entries.hasMoreElements();) {
+ String entry = (String) entries.nextElement();
+ MessageDigest[] mds = (MessageDigest[]) digests.get(entry);
+ byte[][] result = (byte[][]) results.get(entry);
+ os.writeUTF(entry);
+ os.writeInt(mds.length);
+ for (int i = 0; i < mds.length; i++) {
+ if (mds[i] == SignedBundleFile.md5)
+ os.writeInt(0);
+ else
+ os.writeInt(1);
+ os.writeInt(result[i].length);
+ os.write(result[i]);
+ }
+ }
+ os.writeBoolean(md5Result != null);
+ if (md5Result != null)
+ os.writeUTF(md5Result);
+ os.writeBoolean(shaResult != null);
+ if (shaResult != null)
+ os.writeUTF(shaResult);
+ }
+
+ 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) {
+ BundleFile base = bundledata.getBundleFile();
+ if (!(base instanceof SignedBundleFile))
+ return false;
+ return ((SignedBundleFile) base).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/osgi/exceptions.jar b/bundles/org.eclipse.osgi/osgi/exceptions.jar
index 43e9ef5c1..1d9225f20 100644
--- a/bundles/org.eclipse.osgi/osgi/exceptions.jar
+++ b/bundles/org.eclipse.osgi/osgi/exceptions.jar
Binary files differ

Back to the top