Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentFromBundleFile.java')
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentFromBundleFile.java315
1 files changed, 315 insertions, 0 deletions
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentFromBundleFile.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentFromBundleFile.java
new file mode 100644
index 000000000..b0bbf338f
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentFromBundleFile.java
@@ -0,0 +1,315 @@
+/*******************************************************************************
+ * Copyright (c) 2021 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.signedcontent;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.security.CodeSigner;
+import java.security.Timestamp;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.zip.ZipFile;
+import org.eclipse.osgi.internal.debug.Debug;
+import org.eclipse.osgi.signedcontent.InvalidContentException;
+import org.eclipse.osgi.signedcontent.SignedContent;
+import org.eclipse.osgi.signedcontent.SignedContentEntry;
+import org.eclipse.osgi.signedcontent.SignerInfo;
+import org.eclipse.osgi.storage.bundlefile.BundleFile;
+import org.eclipse.osgi.storage.bundlefile.DirBundleFile;
+import org.eclipse.osgi.storage.bundlefile.ZipBundleFile;
+
+public class SignedContentFromBundleFile implements SignedContent {
+ static abstract class BaseSignerInfo implements SignerInfo {
+ private volatile Certificate trustAnchor = null;
+ @Override
+ public Certificate getTrustAnchor() {
+ return trustAnchor;
+ }
+
+ @Override
+ public boolean isTrusted() {
+ return trustAnchor != null;
+ }
+
+ @Deprecated
+ @Override
+ public String getMessageDigestAlgorithm() {
+ return "unknown"; //$NON-NLS-1$
+ }
+
+ void setTrustAnchor(Certificate anchor) {
+ this.trustAnchor = anchor;
+ }
+ }
+
+ static class TimestampSignerInfo extends BaseSignerInfo {
+ private final Timestamp timestamp;
+
+ public TimestampSignerInfo(Timestamp timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ @Override
+ public Certificate[] getCertificateChain() {
+ return timestamp.getSignerCertPath().getCertificates().toArray(new Certificate[0]);
+ }
+
+ Date getTimestamp() {
+ return timestamp.getTimestamp();
+ }
+ }
+
+ static class CodeSignerInfo extends BaseSignerInfo {
+ private final CodeSigner codeSigner;
+ private final TimestampSignerInfo timestamp;
+
+ public CodeSignerInfo(CodeSigner codeSigner) {
+ this.codeSigner = codeSigner;
+ Timestamp ts = codeSigner.getTimestamp();
+ this.timestamp = ts == null ? null : new TimestampSignerInfo(ts);
+ }
+
+ @Override
+ public Certificate[] getCertificateChain() {
+ return codeSigner.getSignerCertPath().getCertificates().toArray(new Certificate[0]);
+ }
+
+ TimestampSignerInfo getTSASignerInfo() {
+ return timestamp;
+ }
+ }
+
+ static class CodeSignerEntry implements SignedContentEntry {
+ private final String name;
+ private final List<CodeSignerInfo> signerInfos;
+
+ public CodeSignerEntry(List<CodeSignerInfo> signerInfos, String name) {
+ this.name = name;
+ this.signerInfos = signerInfos;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public SignerInfo[] getSignerInfos() {
+ return signerInfos.toArray(new SignerInfo[0]);
+ }
+
+ @Override
+ public boolean isSigned() {
+ return !signerInfos.isEmpty();
+ }
+
+ @Override
+ public void verify() throws IOException, InvalidContentException {
+ // already verified
+ }
+ }
+
+ static class CorruptEntry implements SignedContentEntry {
+ final InvalidContentException verifyError;
+ final String name;
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public SignerInfo[] getSignerInfos() {
+ return new SignerInfo[0];
+ }
+
+ @Override
+ public boolean isSigned() {
+ return false;
+ }
+
+ @Override
+ public void verify() throws IOException, InvalidContentException {
+ throw verifyError;
+ }
+
+ public CorruptEntry(InvalidContentException verifyError, String name) {
+ super();
+ this.verifyError = verifyError;
+ this.name = name;
+ }
+
+ }
+
+ private final List<CodeSignerInfo> signerInfos = new ArrayList<>();
+ private final Map<String, SignedContentEntry> signedEntries;
+
+ public SignedContentFromBundleFile(BundleFile bundleFile) throws IOException {
+ signedEntries = getSignedEntries(() -> {
+ try {
+ return getJarInputStream(bundleFile);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }, () -> bundleFile, signerInfos);
+ }
+
+ public SignedContentFromBundleFile(File bundleFile, Debug debug) throws IOException {
+ DirBundleFile tmpDirBundleFile = null;
+ if (bundleFile.isDirectory()) {
+ try {
+ tmpDirBundleFile = new DirBundleFile(bundleFile, false);
+ } catch (IOException e) {
+ // ignore and move on
+ }
+ }
+ DirBundleFile dirBundleFile = tmpDirBundleFile;
+ signedEntries = getSignedEntries(() -> {
+ try {
+ if (dirBundleFile != null) {
+ return getJarInputStream(dirBundleFile);
+ }
+ return new FileInputStream(bundleFile);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }, () -> {
+ try {
+ if (dirBundleFile != null) {
+ return dirBundleFile;
+ }
+ // Make sure we have a ZipFile first, this will throw an IOException if not
+ // valid.
+ // Use SecureAction because it gives better errors about the path on exceptions
+ ZipFile temp = SignedBundleHook.secureAction.getZipFile(bundleFile, false);
+ temp.close();
+ return new ZipBundleFile(bundleFile, null, null, debug, false);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }, signerInfos);
+ }
+
+ private static Map<String, SignedContentEntry> getSignedEntries(Supplier<InputStream> input,
+ Supplier<BundleFile> bundleFile, List<CodeSignerInfo> signerInfos) throws IOException {
+ Map<CodeSigner, CodeSignerInfo> codeSigners = new HashMap<>();
+ Map<String, SignedContentEntry> signedEntries = new LinkedHashMap<>();
+ try (JarInputStream jarInput = new JarInputStream(input.get())) {
+
+ for (JarEntry entry = jarInput.getNextJarEntry(); entry != null; entry = jarInput.getNextJarEntry()) {
+ // drain the entry so we can get the code signer
+ try {
+ for (byte[] drain = new byte[4096]; jarInput.read(drain, 0, drain.length) != -1;) {
+ // nothing
+ }
+ CodeSigner[] signers = entry.getCodeSigners();
+ if (signers != null) {
+ List<CodeSignerInfo> entryInfos = new ArrayList<>(signers.length);
+ for (CodeSigner codeSigner : signers) {
+ CodeSignerInfo info = codeSigners.computeIfAbsent(codeSigner, CodeSignerInfo::new);
+ entryInfos.add(info);
+ }
+ CodeSignerEntry signedEntry = new CodeSignerEntry(entryInfos, entry.getName());
+ signedEntries.put(entry.getName(), signedEntry);
+ }
+ } catch (SecurityException | IOException e) {
+ // assume corruption
+ signedEntries.put(entry.getName(),
+ new CorruptEntry(new InvalidContentException(entry.getName(), e), entry.getName()));
+ }
+ }
+ } catch (SecurityException e) {
+ Enumeration<String> paths = bundleFile.get().getEntryPaths("", true); //$NON-NLS-1$
+ while (paths.hasMoreElements()) {
+ String path = paths.nextElement();
+ if (!path.endsWith("/") && !signedEntries.containsKey(path)) { //$NON-NLS-1$
+ signedEntries.put(path, new CorruptEntry(new InvalidContentException(path, e), path));
+ }
+ }
+ } catch (UncheckedIOException e) {
+ throw e.getCause();
+ }
+ signerInfos.addAll(codeSigners.values());
+ return signedEntries;
+ }
+
+ private static InputStream getJarInputStream(BundleFile bundleFile) throws IOException {
+ File f = bundleFile.getBaseFile();
+ if (f == null || f.isDirectory()) {
+ return new BundleToJarInputStream(bundleFile);
+ }
+ return new FileInputStream(f);
+ }
+
+ @Override
+ public SignedContentEntry[] getSignedEntries() {
+ return signedEntries.values().toArray(new SignedContentEntry[0]);
+ }
+
+ @Override
+ public SignedContentEntry getSignedEntry(String name) {
+ return signedEntries.get(name);
+ }
+
+ @Override
+ public SignerInfo[] getSignerInfos() {
+ return signerInfos.toArray(new SignerInfo[0]);
+ }
+
+ @Override
+ public boolean isSigned() {
+ return !signerInfos.isEmpty();
+ }
+
+ @Override
+ public Date getSigningTime(SignerInfo signerInfo) {
+ if (signerInfo instanceof CodeSignerInfo) {
+ TimestampSignerInfo tsInfo = ((CodeSignerInfo) signerInfo).getTSASignerInfo();
+ if (tsInfo != null) {
+ return tsInfo.getTimestamp();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public SignerInfo getTSASignerInfo(SignerInfo signerInfo) {
+ if (signerInfo instanceof CodeSignerInfo) {
+ return ((CodeSignerInfo) signerInfo).getTSASignerInfo();
+ }
+ return null;
+ }
+
+ @Override
+ public void checkValidity(SignerInfo signerInfo)
+ throws CertificateExpiredException, CertificateNotYetValidException {
+ // TODO Auto-generated method stub
+
+ }
+
+}

Back to the top