org.eclipse.osgi.internal.signedcontent.SignedContentFromBundleFile Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aspectjtools Show documentation
Show all versions of aspectjtools Show documentation
Tools from the AspectJ project
/*******************************************************************************
* 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.security.cert.X509Certificate;
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 signerInfos;
public CodeSignerEntry(List 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 signerInfos = new ArrayList<>();
private final Map 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 getSignedEntries(Supplier input,
Supplier bundleFile, List signerInfos) throws IOException {
Map codeSigners = new HashMap<>();
Map 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 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 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 {
Date signingTime = getSigningTime(signerInfo);
Certificate[] certs = signerInfo.getCertificateChain();
for (Certificate cert : certs) {
if (!(cert instanceof X509Certificate)) {
continue;
}
if (signingTime == null) {
((X509Certificate) cert).checkValidity();
} else {
((X509Certificate) cert).checkValidity(signingTime);
}
}
}
}