package org.eclipse.osgi.internal.signedcontent;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.*;
import java.util.zip.ZipFile;
import org.eclipse.osgi.framework.log.FrameworkLogEntry;
import org.eclipse.osgi.framework.util.SecureAction;
import org.eclipse.osgi.internal.framework.EquinoxBundle;
import org.eclipse.osgi.internal.framework.EquinoxContainer;
import org.eclipse.osgi.internal.hookregistry.*;
import org.eclipse.osgi.internal.service.security.KeyStoreTrustEngine;
import org.eclipse.osgi.internal.signedcontent.SignedStorageHook.StorageHookImpl;
import org.eclipse.osgi.service.security.TrustEngine;
import org.eclipse.osgi.signedcontent.*;
import org.eclipse.osgi.storage.BundleInfo.Generation;
import org.eclipse.osgi.storage.bundlefile.*;
import org.eclipse.osgi.util.ManifestElement;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.*;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
public class SignedBundleHook implements ActivatorHookFactory, BundleFileWrapperFactoryHook, HookConfigurator, SignedContentFactory {
static final SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction());
static final int VERIFY_CERTIFICATE = 0x01;
static final int VERIFY_TRUST = 0x02;
static final int VERIFY_RUNTIME = 0x04;
static final int VERIFY_ALL = VERIFY_CERTIFICATE | VERIFY_TRUST | VERIFY_RUNTIME;
private final static String SUPPORT_CERTIFICATE = "certificate";
private final static String SUPPORT_TRUST = "trust";
private final static String SUPPORT_RUNTIME = "runtime";
private final static String SUPPORT_ALL = "all";
private final static String SUPPORT_TRUE = "true";
private final static String CACERTS_PATH = System.getProperty("java.home") + File.separatorChar + "lib" + File.separatorChar + "security" + File.separatorChar + "cacerts";
private final static String CACERTS_TYPE = "JKS";
private final static String SIGNED_BUNDLE_SUPPORT = "osgi.support.signature.verify";
private final static String SIGNED_CONTENT_SUPPORT = "osgi.signedcontent.support";
private final static String OSGI_KEYSTORE = "osgi.framework.keystore";
private int supportSignedBundles;
TrustEngineListener trustEngineListener;
private String trustEngineNameProp;
private ServiceRegistration<?> signedContentFactoryReg;
private ServiceRegistration<?> systemTrustEngineReg;
private List<ServiceRegistration<?>> osgiTrustEngineReg;
private ServiceTracker<TrustEngine, TrustEngine> trustEngineTracker;
private BundleContext context;
private EquinoxContainer container;
@Override
public BundleActivator createActivator() {
return new BundleActivator() {
@Override
public void start(BundleContext bc) throws Exception {
frameworkStart(bc);
}
@Override
public void stop(BundleContext bc) throws Exception {
frameworkStop(bc);
}
};
}
BundleContext getContext() {
return context;
}
void frameworkStart(BundleContext bc) {
this.context = bc;
if ((supportSignedBundles & VERIFY_TRUST) != 0)
trustEngineListener = new TrustEngineListener(context, this);
Dictionary<String, Object> trustEngineProps = new Hashtable<>(7);
trustEngineProps.put(Constants.SERVICE_RANKING, Integer.valueOf(Integer.MIN_VALUE));
trustEngineProps.put(SignedContentConstants.TRUST_ENGINE, SignedContentConstants.DEFAULT_TRUST_ENGINE);
KeyStoreTrustEngine systemTrustEngine = new KeyStoreTrustEngine(CACERTS_PATH, CACERTS_TYPE, null, "System", this);
systemTrustEngineReg = context.registerService(TrustEngine.class.getName(), systemTrustEngine, trustEngineProps);
String osgiTrustPath = context.getProperty(OSGI_KEYSTORE);
if (osgiTrustPath != null) {
try {
URL url = new URL(osgiTrustPath);
if ("file".equals(url.getProtocol())) {
trustEngineProps.put(SignedContentConstants.TRUST_ENGINE, OSGI_KEYSTORE);
String path = url.getPath();
osgiTrustEngineReg = new ArrayList<>(1);
osgiTrustEngineReg.add(context.registerService(TrustEngine.class.getName(), new KeyStoreTrustEngine(path, CACERTS_TYPE, null, OSGI_KEYSTORE, this), trustEngineProps));
}
} catch (MalformedURLException e) {
log("Invalid setting for " + OSGI_KEYSTORE, FrameworkLogEntry.WARNING, e);
}
} else {
String osgiTrustRepoPaths = context.getProperty(Constants.FRAMEWORK_TRUST_REPOSITORIES);
if (osgiTrustRepoPaths != null) {
trustEngineProps.put(SignedContentConstants.TRUST_ENGINE, Constants.FRAMEWORK_TRUST_REPOSITORIES);
StringTokenizer st = new StringTokenizer(osgiTrustRepoPaths, File.pathSeparator);
osgiTrustEngineReg = new ArrayList<>(1);
while (st.hasMoreTokens()) {
String trustRepoPath = st.nextToken();
osgiTrustEngineReg.add(context.registerService(TrustEngine.class.getName(), new KeyStoreTrustEngine(trustRepoPath, CACERTS_TYPE, null, OSGI_KEYSTORE, this), trustEngineProps));
}
}
}
signedContentFactoryReg = context.registerService(SignedContentFactory.class.getName(), this, null);
}
void frameworkStop(BundleContext bc) {
if (signedContentFactoryReg != null) {
signedContentFactoryReg.unregister();
signedContentFactoryReg = null;
}
if (systemTrustEngineReg != null) {
systemTrustEngineReg.unregister();
systemTrustEngineReg = null;
}
if (osgiTrustEngineReg != null) {
for (Iterator<ServiceRegistration<?>> it = osgiTrustEngineReg.iterator(); it.hasNext();)
it.next().unregister();
osgiTrustEngineReg = null;
}
if (trustEngineTracker != null) {
trustEngineTracker.close();
trustEngineTracker = null;
}
}
@Override
public BundleFileWrapper wrapBundleFile(BundleFile bundleFile, Generation generation, boolean base) {
try {
if (bundleFile != null) {
StorageHookImpl hook = generation.getStorageHook(SignedStorageHook.class);
SignedBundleFile signedBaseFile;
if (base && hook != null) {
signedBaseFile = new SignedBundleFile(bundleFile, hook.signedContent, supportSignedBundles, this);
if (hook.signedContent == null) {
signedBaseFile.initializeSignedContent();
SignedContentImpl signedContent = signedBaseFile.getSignedContent();
hook.signedContent = signedContent != null && signedContent.isSigned() ? signedContent : null;
}
} else
signedBaseFile = new SignedBundleFile(bundleFile, null, supportSignedBundles, this);
signedBaseFile.initializeSignedContent();
SignedContentImpl signedContent = signedBaseFile.getSignedContent();
if (signedContent != null && signedContent.isSigned()) {
signedContent.setContent(signedBaseFile);
return new BundleFileWrapper(signedBaseFile);
}
}
} catch (IOException | GeneralSecurityException e) {
log("Bad bundle file: " + bundleFile.getBaseFile(), FrameworkLogEntry.WARNING, e);
}
return null;
}
@Override
public void addHooks(HookRegistry hookRegistry) {
container = hookRegistry.getContainer();
hookRegistry.addActivatorHookFactory(this);
String[] supportOptions = ManifestElement.getArrayFromList(hookRegistry.getConfiguration().getConfiguration(SIGNED_CONTENT_SUPPORT, hookRegistry.getConfiguration().getConfiguration(SIGNED_BUNDLE_SUPPORT)), ",");
for (String supportOption : supportOptions) {
if (SUPPORT_CERTIFICATE.equals(supportOption)) {
supportSignedBundles |= VERIFY_CERTIFICATE;
} else if (SUPPORT_TRUST.equals(supportOption)) {
supportSignedBundles |= VERIFY_CERTIFICATE | VERIFY_TRUST;
} else if (SUPPORT_RUNTIME.equals(supportOption)) {
supportSignedBundles |= VERIFY_CERTIFICATE | VERIFY_RUNTIME;
} else if (SUPPORT_TRUE.equals(supportOption) || SUPPORT_ALL.equals(supportOption)) {
supportSignedBundles |= VERIFY_ALL;
}
}
trustEngineNameProp = hookRegistry.getConfiguration().getConfiguration(SignedContentConstants.TRUST_ENGINE);
if ((supportSignedBundles & VERIFY_CERTIFICATE) != 0) {
hookRegistry.addStorageHookFactory(new SignedStorageHook());
hookRegistry.addBundleFileWrapperFactoryHook(this);
}
}
@Override
public SignedContent getSignedContent(File content) throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
if (content == null)
throw new IllegalArgumentException("null content");
BundleFile contentBundleFile;
if (content.isDirectory()) {
contentBundleFile = new DirBundleFile(content, false);
} else {
ZipFile temp = secureAction.getZipFile(content);
temp.close();
contentBundleFile = new ZipBundleFile(content, null, null, container.getConfiguration().getDebug());
}
SignedBundleFile result = new SignedBundleFile(contentBundleFile, null, VERIFY_ALL, this);
try {
result.initializeSignedContent();
} catch (InvalidKeyException e) {
throw new InvalidKeyException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content), e);
} catch (SignatureException e) {
throw new SignatureException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content), e);
} catch (CertificateException e) {
throw new CertificateException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content), e);
} catch (NoSuchAlgorithmException e) {
throw new NoSuchAlgorithmException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content), e);
} catch (NoSuchProviderException e) {
throw (NoSuchProviderException) new NoSuchProviderException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content)).initCause(e);
}
return new SignedContentFile(result.getSignedContent());
}
@Override
public SignedContent getSignedContent(Bundle bundle) throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException, IllegalArgumentException {
final Generation generation = (Generation) ((EquinoxBundle) bundle).getModule().getCurrentRevision().getRevisionInfo();
StorageHookImpl hook = generation.getStorageHook(SignedStorageHook.class);
SignedContent result = hook != null ? hook.signedContent : null;
if (result != null)
return result;
if (System.getSecurityManager() == null)
return getSignedContent(generation.getBundleFile().getBaseFile());
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<SignedContent>() {
@Override
public SignedContent run() throws Exception {
return getSignedContent(generation.getBundleFile().getBaseFile());
}
});
} catch (PrivilegedActionException e) {
if (e.getException() instanceof IOException)
throw (IOException) e.getException();
if (e.getException() instanceof InvalidKeyException)
throw (InvalidKeyException) e.getException();
if (e.getException() instanceof SignatureException)
throw (SignatureException) e.getException();
if (e.getException() instanceof CertificateException)
throw (CertificateException) e.getException();
if (e.getException() instanceof NoSuchAlgorithmException)
throw (NoSuchAlgorithmException) e.getException();
if (e.getException() instanceof NoSuchProviderException)
throw (NoSuchProviderException) e.getException();
throw new RuntimeException("Unknown error.", e.getException());
}
}
public void log(String msg, int severity, Throwable t) {
container.getLogServices().log(EquinoxContainer.NAME, severity, msg, t);
}
private TrustEngine[] getTrustEngines() {
if (context == null)
return new TrustEngine[0];
if (trustEngineTracker == null) {
Filter filter = null;
if (trustEngineNameProp != null)
try {
filter = context.createFilter("(&(" + Constants.OBJECTCLASS + "=" + TrustEngine.class.getName() + ")(" + SignedContentConstants.TRUST_ENGINE + "=" + trustEngineNameProp + "))");
} catch (InvalidSyntaxException e) {
log("Invalid trust engine filter", FrameworkLogEntry.WARNING, e);
}
if (filter != null) {
trustEngineTracker = new ServiceTracker<>(context, filter, new TrustEngineCustomizer());
} else
trustEngineTracker = new ServiceTracker<>(context, TrustEngine.class.getName(), new TrustEngineCustomizer());
trustEngineTracker.open();
}
Object[] services = trustEngineTracker.getServices();
if (services != null) {
TrustEngine[] engines = new TrustEngine[services.length];
System.arraycopy(services, 0, engines, 0, services.length);
return engines;
}
return new TrustEngine[0];
}
class TrustEngineCustomizer implements ServiceTrackerCustomizer<TrustEngine, TrustEngine> {
@Override
public TrustEngine addingService(ServiceReference<TrustEngine> reference) {
TrustEngine engine = getContext().getService(reference);
if (engine != null) {
try {
Field trustEngineListenerField = TrustEngine.class.getDeclaredField("trustEngineListener");
trustEngineListenerField.setAccessible(true);
trustEngineListenerField.set(engine, SignedBundleHook.this.trustEngineListener);
} catch (Exception e) {
log("Unable to set the trust engine listener.", FrameworkLogEntry.ERROR, e);
}
}
return engine;
}
@Override
public void modifiedService(ServiceReference<TrustEngine> reference, TrustEngine service) {
}
@Override
public void removedService(ServiceReference<TrustEngine> reference, TrustEngine service) {
}
}
void determineTrust(SignedContentImpl trustedContent, int supportFlags) {
TrustEngine[] engines = null;
SignerInfo[] signers = trustedContent.getSignerInfos();
for (SignerInfo signer : signers) {
if (signer.getTrustAnchor() == null) {
if (engines == null)
engines = getTrustEngines();
Certificate[] signerCerts = signer.getCertificateChain();
((SignerInfoImpl) signer).setTrustAnchor(findTrustAnchor(signerCerts, engines, supportFlags));
SignerInfo tsaSignerInfo = trustedContent.getTSASignerInfo(signer);
if (tsaSignerInfo != null) {
Certificate[] tsaCerts = tsaSignerInfo.getCertificateChain();
((SignerInfoImpl) tsaSignerInfo).setTrustAnchor(findTrustAnchor(tsaCerts, engines, supportFlags));
}
}
}
}
private Certificate findTrustAnchor(Certificate[] certs, TrustEngine[] engines, int supportFlags) {
if ((supportFlags & SignedBundleHook.VERIFY_TRUST) == 0)
return certs != null && certs.length > 0 ? certs[certs.length - 1] : null;
for (TrustEngine engine : engines) {
try {
Certificate anchor = engine.findTrustAnchor(certs);
if (anchor != null)
return anchor;
} catch (IOException e) {
log("TrustEngine failure: " + engine.getName(), FrameworkLogEntry.WARNING, e);
}
}
return null;
}
}