blob: 83fdd36462186fa32bc1de73f6057cb56720d6f2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 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.service.security;
import java.io.*;
import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import org.eclipse.osgi.framework.log.FrameworkLogEntry;
import org.eclipse.osgi.internal.signedcontent.SignedBundleHook;
import org.eclipse.osgi.internal.signedcontent.SignedContentMessages;
import org.eclipse.osgi.service.security.TrustEngine;
import org.eclipse.osgi.util.NLS;
//*potential enhancements*
// 1. reloading from the backing file when it changes
// 3. methods to support lock/unlock
// 3a. Using a callback handler to collect the password
// 3b. managing lock/unlock between multiple threads. dealing with SWT UI thread
// 4. methods to support changing password, etc
// 5. methods to support export, etc
// 6. 'friendly-name' generator
// 7. Listeners for change events
public class KeyStoreTrustEngine extends TrustEngine {
private KeyStore keyStore;
private final String type;
private final String path;
private final char[] password;
private final String name;
/**
* Create a new KeyStoreTrustEngine that is backed by a KeyStore
* @param path - path to the keystore
* @param type - the type of keystore at the path location
* @param password - the password required to unlock the keystore
*/
public KeyStoreTrustEngine(String path, String type, char[] password, String name) { //TODO: This should be a *CallbackHandler*
this.path = path;
this.type = type;
this.password = password;
this.name = name;
}
/**
* Return the type
* @return type - the type for the KeyStore being managed
*/
private String getType() {
return type;
}
/**
* Return the path
* @return - the path for the KeyStore being managed
*/
private String getPath() {
return path;
}
/**
* Return the password
* @return password - the password as a char[]
*/
private char[] getPassword() {
return password;
}
/**
* Return the KeyStore managed
* @return The KeyStore instance, initialized and loaded
* @throws KeyStoreException
*/
private synchronized KeyStore getKeyStore() throws IOException, GeneralSecurityException {
if (null == keyStore) {
keyStore = KeyStore.getInstance(getType());
final InputStream in = getInputStream();
try {
loadStore(keyStore, in);
} finally {
try {
in.close();
} catch (IOException e) {
//ignore secondary failure
}
}
}
if (keyStore == null)
throw new KeyStoreException(NLS.bind(SignedContentMessages.Default_Trust_Keystore_Load_Failed, getPath()));
return keyStore;
}
public Certificate findTrustAnchor(Certificate[] certChain) throws IOException {
if (certChain == null || certChain.length == 0)
throw new IllegalArgumentException("Certificate chain is required"); //$NON-NLS-1$
try {
Certificate rootCert = null;
KeyStore store = getKeyStore();
for (int i = 0; i < certChain.length; i++) {
if (certChain[i] instanceof X509Certificate) {
if (i == certChain.length - 1) {
// this is the last certificate in the chain
// determine if we have a valid root
X509Certificate cert = (X509Certificate) certChain[i];
if (cert.getSubjectDN().equals(cert.getIssuerDN())) {
cert.verify(cert.getPublicKey());
rootCert = cert; // this is a self-signed certificate
} else {
// try to find a parent, we have an incomplete chain
return findAlternativeRoot(cert, store);
}
} else {
X509Certificate nextX509Cert = (X509Certificate) certChain[i + 1];
certChain[i].verify(nextX509Cert.getPublicKey());
}
}
synchronized (store) {
String alias = rootCert == null ? null : store.getCertificateAlias(rootCert);
if (alias != null)
return store.getCertificate(alias);
else if (rootCert != certChain[i]) {
alias = store.getCertificateAlias(certChain[i]);
if (alias != null)
return store.getCertificate(alias);
}
// if we have reached the end and the last cert is not found to be a valid root CA
// then we need to back off the root CA and try to find an alternative
if (certChain.length > 1 && i == certChain.length - 1 && certChain[i - 1] instanceof X509Certificate)
return findAlternativeRoot((X509Certificate) certChain[i - 1], store);
}
}
} catch (KeyStoreException e) {
throw (IOException) new IOException(e.getMessage()).initCause(e);
} catch (GeneralSecurityException e) {
SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.WARNING, e);
return null;
}
return null;
}
private Certificate findAlternativeRoot(X509Certificate cert, KeyStore store) throws InvalidKeyException, KeyStoreException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException, CertificateException {
synchronized (store) {
for (Enumeration e = store.aliases(); e.hasMoreElements();) {
Certificate nextCert = store.getCertificate((String) e.nextElement());
if (nextCert instanceof X509Certificate && ((X509Certificate) nextCert).getSubjectDN().equals(cert.getIssuerDN())) {
cert.verify(nextCert.getPublicKey());
return nextCert;
}
}
return null;
}
}
protected String doAddTrustAnchor(Certificate cert, String alias) throws IOException, GeneralSecurityException {
if (isReadOnly())
throw new IOException(SignedContentMessages.Default_Trust_Read_Only);
if (cert == null) {
throw new IllegalArgumentException("Certificate must be specified"); //$NON-NLS-1$
}
try {
KeyStore store = getKeyStore();
synchronized (store) {
String oldAlias = store.getCertificateAlias(cert);
if (null != oldAlias)
throw new CertificateException(SignedContentMessages.Default_Trust_Existing_Cert);
Certificate oldCert = store.getCertificate(alias);
if (null != oldCert)
throw new CertificateException(SignedContentMessages.Default_Trust_Existing_Alias);
store.setCertificateEntry(alias, cert);
final OutputStream out = getOutputStream();
try {
saveStore(store, out);
} finally {
safeClose(out);
}
}
} catch (KeyStoreException ke) {
throw (CertificateException) new CertificateException(ke.getMessage()).initCause(ke);
}
return alias;
}
protected void doRemoveTrustAnchor(Certificate cert) throws IOException, GeneralSecurityException {
if (isReadOnly())
throw new IOException(SignedContentMessages.Default_Trust_Read_Only);
if (cert == null) {
throw new IllegalArgumentException("Certificate must be specified"); //$NON-NLS-1$
}
try {
KeyStore store = getKeyStore();
synchronized (store) {
String alias = store.getCertificateAlias(cert);
if (alias == null) {
throw new CertificateException(SignedContentMessages.Default_Trust_Cert_Not_Found);
}
removeTrustAnchor(alias);
}
} catch (KeyStoreException ke) {
throw (CertificateException) new CertificateException(ke.getMessage()).initCause(ke);
}
}
protected void doRemoveTrustAnchor(String alias) throws IOException, GeneralSecurityException {
if (alias == null) {
throw new IllegalArgumentException("Alias must be specified"); //$NON-NLS-1$
}
try {
KeyStore store = getKeyStore();
synchronized (store) {
Certificate oldCert = store.getCertificate(alias);
if (oldCert == null)
throw new CertificateException(SignedContentMessages.Default_Trust_Cert_Not_Found);
store.deleteEntry(alias);
final OutputStream out = getOutputStream();
try {
saveStore(store, out);
} finally {
safeClose(out);
}
}
} catch (KeyStoreException ke) {
throw (CertificateException) new CertificateException(ke.getMessage()).initCause(ke);
}
}
public Certificate getTrustAnchor(String alias) throws IOException, GeneralSecurityException {
if (alias == null) {
throw new IllegalArgumentException("Alias must be specified"); //$NON-NLS-1$
}
try {
KeyStore store = getKeyStore();
synchronized (store) {
return store.getCertificate(alias);
}
} catch (KeyStoreException ke) {
throw (CertificateException) new CertificateException(ke.getMessage()).initCause(ke);
}
}
public String[] getAliases() throws IOException, GeneralSecurityException {
ArrayList returnList = new ArrayList();
try {
KeyStore store = getKeyStore();
synchronized (store) {
for (Enumeration aliases = store.aliases(); aliases.hasMoreElements();) {
String currentAlias = (String) aliases.nextElement();
if (store.isCertificateEntry(currentAlias)) {
returnList.add(currentAlias);
}
}
}
} catch (KeyStoreException ke) {
throw (CertificateException) new CertificateException(ke.getMessage()).initCause(ke);
}
return (String[]) returnList.toArray(new String[] {});
}
/**
* Load using the current password
*/
private void loadStore(KeyStore store, InputStream is) throws IOException, GeneralSecurityException {
store.load(is, getPassword());
}
/**
* Save using the current password
*/
private void saveStore(KeyStore store, OutputStream os) throws IOException, GeneralSecurityException {
store.store(os, getPassword());
}
/**
* Closes a stream and ignores any resulting exception. This is useful
* when doing stream cleanup in a finally block where secondary exceptions
* are not worth logging.
*/
private void safeClose(OutputStream out) {
try {
if (out != null)
out.close();
} catch (IOException e) {
//ignore
}
}
/**
* Get an input stream for the KeyStore managed
* @return inputstream - the stream
* @throws KeyStoreException
*/
private InputStream getInputStream() throws IOException {
return new FileInputStream(new File(getPath()));
}
/**
* Get an output stream for the KeyStore managed
* @return outputstream - the stream
* @throws KeyStoreException
*/
private OutputStream getOutputStream() throws IOException {
File file = new File(getPath());
if (!file.exists())
file.createNewFile();
return new FileOutputStream(file);
}
public boolean isReadOnly() {
return getPassword() == null || !(new File(path).canWrite());
}
public String getName() {
return name;
}
}