| /******************************************************************************* |
| * Copyright (c) 2006, 2010 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.signedcontent; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.*; |
| import java.security.*; |
| import java.security.cert.CertificateException; |
| import java.util.*; |
| import org.eclipse.osgi.baseadaptor.*; |
| import org.eclipse.osgi.baseadaptor.bundlefile.*; |
| import org.eclipse.osgi.baseadaptor.hooks.AdaptorHook; |
| import org.eclipse.osgi.baseadaptor.hooks.BundleFileWrapperFactoryHook; |
| import org.eclipse.osgi.framework.adaptor.BundleData; |
| import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor; |
| import org.eclipse.osgi.framework.internal.core.*; |
| import org.eclipse.osgi.framework.log.FrameworkLog; |
| import org.eclipse.osgi.framework.log.FrameworkLogEntry; |
| import org.eclipse.osgi.internal.provisional.service.security.AuthorizationEngine; |
| import org.eclipse.osgi.internal.provisional.verifier.CertificateVerifierFactory; |
| import org.eclipse.osgi.internal.service.security.DefaultAuthorizationEngine; |
| import org.eclipse.osgi.internal.service.security.KeyStoreTrustEngine; |
| import org.eclipse.osgi.service.security.TrustEngine; |
| import org.eclipse.osgi.signedcontent.SignedContent; |
| import org.eclipse.osgi.signedcontent.SignedContentFactory; |
| import org.eclipse.osgi.util.ManifestElement; |
| import org.eclipse.osgi.util.NLS; |
| import org.osgi.framework.*; |
| import org.osgi.framework.Constants; |
| import org.osgi.util.tracker.ServiceTracker; |
| |
| /** |
| * Implements signed bundle hook support for the framework |
| */ |
| public class SignedBundleHook implements AdaptorHook, BundleFileWrapperFactoryHook, HookConfigurator, SignedContentFactory { |
| static final int VERIFY_CERTIFICATE = 0x01; |
| static final int VERIFY_TRUST = 0x02; |
| static final int VERIFY_RUNTIME = 0x04; |
| static final int VERIFY_AUTHORITY = 0x08; |
| static final int VERIFY_ALL = VERIFY_CERTIFICATE | VERIFY_TRUST | VERIFY_RUNTIME | VERIFY_AUTHORITY; |
| private static String SUPPORT_CERTIFICATE = "certificate"; //$NON-NLS-1$ |
| private static String SUPPORT_TRUST = "trust"; //$NON-NLS-1$ |
| private static String SUPPORT_RUNTIME = "runtime"; //$NON-NLS-1$ |
| private static String SUPPORT_AUTHORITY = "authority"; //$NON-NLS-1$ |
| private static String SUPPORT_ALL = "all"; //$NON-NLS-1$ |
| private static String SUPPORT_TRUE = "true"; //$NON-NLS-1$ |
| |
| //TODO: comes from configuration!; |
| private static String CACERTS_PATH = System.getProperty("java.home") + File.separatorChar + "lib" + File.separatorChar + "security" + File.separatorChar + "cacerts"; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$//$NON-NLS-4$ |
| private static String CACERTS_TYPE = "JKS"; //$NON-NLS-1$ |
| private static ServiceTracker trustEngineTracker; |
| private static BaseAdaptor ADAPTOR; |
| private static String SIGNED_BUNDLE_SUPPORT = "osgi.support.signature.verify"; //$NON-NLS-1$ |
| private static String SIGNED_CONTENT_SUPPORT = "osgi.signedcontent.support"; //$NON-NLS-1$ |
| private static String OSGI_KEYSTORE = "osgi.framework.keystore"; //$NON-NLS-1$ |
| private static int supportSignedBundles; |
| private TrustEngineListener trustEngineListener; |
| private BundleInstallListener installListener; |
| private ServiceRegistration signedContentFactoryReg; |
| private ServiceRegistration systemTrustEngineReg; |
| private ServiceRegistration defaultAuthEngineReg; |
| private List osgiTrustEngineReg; |
| private ServiceRegistration legacyFactoryReg; |
| |
| public void initialize(BaseAdaptor adaptor) { |
| SignedBundleHook.ADAPTOR = adaptor; |
| } |
| |
| public void frameworkStart(BundleContext context) throws BundleException { |
| // check if load time authority is enabled |
| if ((supportSignedBundles & VERIFY_AUTHORITY) != 0) { |
| // install the default bundle install listener |
| installListener = new BundleInstallListener(); |
| context.addBundleListener(installListener); |
| // register the default authorization engine |
| Hashtable properties = new Hashtable(7); |
| properties.put(Constants.SERVICE_RANKING, new Integer(Integer.MIN_VALUE)); |
| properties.put(SignedContentConstants.AUTHORIZATION_ENGINE, SignedContentConstants.DEFAULT_AUTHORIZATION_ENGINE); |
| defaultAuthEngineReg = context.registerService(AuthorizationEngine.class.getName(), new DefaultAuthorizationEngine(context, ADAPTOR.getState()), properties); |
| } |
| |
| // always register the trust engine |
| Hashtable trustEngineProps = new Hashtable(7); |
| trustEngineProps.put(Constants.SERVICE_RANKING, new Integer(Integer.MIN_VALUE)); |
| trustEngineProps.put(SignedContentConstants.TRUST_ENGINE, SignedContentConstants.DEFAULT_TRUST_ENGINE); |
| KeyStoreTrustEngine systemTrustEngine = new KeyStoreTrustEngine(CACERTS_PATH, CACERTS_TYPE, null, "System"); //$NON-NLS-1$ |
| 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())) { //$NON-NLS-1$ |
| 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), trustEngineProps)); |
| } |
| } catch (MalformedURLException e) { |
| SignedBundleHook.log("Invalid setting for " + OSGI_KEYSTORE, FrameworkLogEntry.WARNING, e); //$NON-NLS-1$ |
| } |
| } 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), trustEngineProps)); |
| } |
| } |
| } |
| if ((supportSignedBundles & VERIFY_TRUST) != 0) |
| // initialize the trust engine listener only if trust is being established with a trust engine |
| trustEngineListener = new TrustEngineListener(context); |
| // always register the signed content factory |
| signedContentFactoryReg = context.registerService(SignedContentFactory.class.getName(), this, null); |
| legacyFactoryReg = context.registerService(CertificateVerifierFactory.class.getName(), new LegacyVerifierFactory(this), null); |
| } |
| |
| public void frameworkStop(BundleContext context) throws BundleException { |
| if (legacyFactoryReg != null) { |
| legacyFactoryReg.unregister(); |
| legacyFactoryReg = null; |
| } |
| if (signedContentFactoryReg != null) { |
| signedContentFactoryReg.unregister(); |
| signedContentFactoryReg = null; |
| } |
| if (systemTrustEngineReg != null) { |
| systemTrustEngineReg.unregister(); |
| systemTrustEngineReg = null; |
| } |
| if (osgiTrustEngineReg != null) { |
| for (Iterator it = osgiTrustEngineReg.iterator(); it.hasNext();) |
| ((ServiceRegistration) it.next()).unregister(); |
| osgiTrustEngineReg = null; |
| } |
| if (defaultAuthEngineReg != null) { |
| defaultAuthEngineReg.unregister(); |
| defaultAuthEngineReg = null; |
| } |
| if (trustEngineListener != null) { |
| trustEngineListener.stopTrustEngineListener(); |
| trustEngineListener = null; |
| } |
| if (installListener != null) { |
| context.removeBundleListener(installListener); |
| installListener = null; |
| } |
| if (trustEngineTracker != null) { |
| trustEngineTracker.close(); |
| trustEngineTracker = 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 wrapBundleFile(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) { |
| signedBaseFile = new SignedBundleFile(hook.signedContent, supportSignedBundles); |
| if (hook.signedContent == null) { |
| signedBaseFile.setBundleFile(bundleFile); |
| SignedContentImpl signedContent = signedBaseFile.getSignedContent(); |
| hook.signedContent = signedContent != null && signedContent.isSigned() ? signedContent : null; |
| } |
| } else |
| signedBaseFile = new SignedBundleFile(null, supportSignedBundles); |
| signedBaseFile.setBundleFile(bundleFile); |
| SignedContentImpl signedContent = signedBaseFile.getSignedContent(); |
| if (signedContent != null && signedContent.isSigned()) { |
| // only use the signed file if there are certs |
| signedContent.setContent(signedBaseFile); |
| bundleFile = signedBaseFile; |
| } |
| } |
| } catch (IOException e) { |
| SignedBundleHook.log("Bad bundle file: " + bundleFile.getBaseFile(), FrameworkLogEntry.WARNING, e); //$NON-NLS-1$ |
| } catch (GeneralSecurityException e) { |
| SignedBundleHook.log("Bad bundle file: " + bundleFile.getBaseFile(), FrameworkLogEntry.WARNING, e); //$NON-NLS-1$ |
| } |
| return bundleFile; |
| } |
| |
| public void addHooks(HookRegistry hookRegistry) { |
| hookRegistry.addAdaptorHook(this); |
| String[] support = ManifestElement.getArrayFromList(FrameworkProperties.getProperty(SIGNED_CONTENT_SUPPORT, FrameworkProperties.getProperty(SIGNED_BUNDLE_SUPPORT)), ","); //$NON-NLS-1$ |
| for (int i = 0; i < support.length; i++) { |
| if (SUPPORT_CERTIFICATE.equals(support[i])) |
| supportSignedBundles |= VERIFY_CERTIFICATE; |
| else if (SUPPORT_TRUST.equals(support[i])) |
| supportSignedBundles |= VERIFY_CERTIFICATE | VERIFY_TRUST; |
| else if (SUPPORT_RUNTIME.equals(support[i])) |
| supportSignedBundles |= VERIFY_CERTIFICATE | VERIFY_RUNTIME; |
| else if (SUPPORT_AUTHORITY.equals(support[i])) |
| supportSignedBundles |= VERIFY_CERTIFICATE | VERIFY_TRUST | VERIFY_AUTHORITY; |
| else if (SUPPORT_TRUE.equals(support[i]) || SUPPORT_ALL.equals(support[i])) |
| supportSignedBundles |= VERIFY_ALL; |
| } |
| if ((supportSignedBundles & VERIFY_CERTIFICATE) != 0) { |
| hookRegistry.addStorageHook(new SignedStorageHook()); |
| hookRegistry.addBundleFileWrapperFactoryHook(this); |
| } |
| } |
| |
| public SignedContent getSignedContent(File content) throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException { |
| 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(null, VERIFY_ALL); |
| try { |
| result.setBundleFile(contentBundleFile); |
| } catch (InvalidKeyException e) { |
| throw (InvalidKeyException) new InvalidKeyException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content)).initCause(e); |
| } catch (SignatureException e) { |
| throw (SignatureException) new SignatureException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content)).initCause(e); |
| } catch (CertificateException e) { |
| throw (CertificateException) new CertificateException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content)).initCause(e); |
| } catch (NoSuchAlgorithmException e) { |
| throw (NoSuchAlgorithmException) new NoSuchAlgorithmException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content)).initCause(e); |
| } catch (NoSuchProviderException e) { |
| throw (NoSuchProviderException) new NoSuchProviderException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content)).initCause(e); |
| } |
| return new SignedContentFile(result.getSignedContent()); |
| } |
| |
| public SignedContent getSignedContent(Bundle bundle) throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException, IllegalArgumentException { |
| final BundleData data = ((AbstractBundle) bundle).getBundleData(); |
| if (!(data instanceof BaseData)) |
| throw new IllegalArgumentException("Invalid bundle object. No BaseData found."); //$NON-NLS-1$ |
| SignedStorageHook hook = (SignedStorageHook) ((BaseData) data).getStorageHook(SignedStorageHook.KEY); |
| SignedContent result = hook != null ? hook.signedContent : null; |
| if (result != null) |
| return result; // just reuse the signed content the storage hook |
| // must create a new signed content using the raw file |
| if (System.getSecurityManager() == null) |
| return getSignedContent(((BaseData) data).getBundleFile().getBaseFile()); |
| try { |
| return (SignedContent) AccessController.doPrivileged(new PrivilegedExceptionAction() { |
| public Object run() throws Exception { |
| return getSignedContent(((BaseData) data).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()); //$NON-NLS-1$ |
| } |
| } |
| |
| public 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); |
| } |
| |
| static BundleContext getContext() { |
| if (ADAPTOR == null) |
| return null; |
| return ADAPTOR.getContext(); |
| } |
| |
| static TrustEngine[] getTrustEngines() { |
| // find all the trust engines available |
| BundleContext context = SignedBundleHook.getContext(); |
| if (context == null) |
| return new TrustEngine[0]; |
| if (trustEngineTracker == null) { |
| // read the trust provider security property |
| String trustEngineProp = FrameworkProperties.getProperty(SignedContentConstants.TRUST_ENGINE); |
| Filter filter = null; |
| if (trustEngineProp != null) |
| try { |
| filter = FilterImpl.newInstance("(&(" + Constants.OBJECTCLASS + "=" + TrustEngine.class.getName() + ")(" + SignedContentConstants.TRUST_ENGINE + "=" + trustEngineProp + "))"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$//$NON-NLS-5$ |
| } catch (InvalidSyntaxException e) { |
| SignedBundleHook.log("Invalid trust engine filter", FrameworkLogEntry.WARNING, e); //$NON-NLS-1$ |
| } |
| if (filter != null) { |
| trustEngineTracker = new ServiceTracker(context, filter, null); |
| } else |
| trustEngineTracker = new ServiceTracker(context, TrustEngine.class.getName(), null); |
| 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]; |
| } |
| } |