diff options
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.java | 315 |
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 + + } + +} |