/* $Id: KeystoreManager.java 988245 2010-08-23 18:39:35Z kwright $ */
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.manifoldcf.connectorcommon.keystore;
import org.apache.manifoldcf.core.interfaces.*;
import org.apache.manifoldcf.connectorcommon.interfaces.*;
import org.apache.manifoldcf.core.common.*;
import org.apache.manifoldcf.core.system.Logging;
import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate;
import java.util.*;
import java.io.*;
/** This interface describes a class that manages keys and certificates in a secure manner.
* It's built on top of the JDK 1.4+ JSSE integration, and provides all the necessary logic
* to work well within the ManifoldCF java environment.
*/
public class KeystoreManager implements IKeystoreManager
{
public static final String _rcsid = "@(#)$Id: KeystoreManager.java 988245 2010-08-23 18:39:35Z kwright $";
// The keystore passcode
protected final String passcode;
// The keystore itself
protected final KeyStore keystore;
/** Create the keystore object.
*/
public KeystoreManager(String passcode)
throws ManifoldCFException
{
this.passcode = passcode;
try
{
keystore = KeyStore.getInstance("JKS");
keystore.load(null,passcode.toCharArray());
}
catch (KeyStoreException e)
{
throw new ManifoldCFException("Keystore exception: "+e.getMessage(),e);
}
catch (InterruptedIOException e)
{
throw new ManifoldCFException("Interrupted IO: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (IOException e)
{
throw new ManifoldCFException("IO error creating keystore: "+e.getMessage(),e);
}
catch (NoSuchAlgorithmException e)
{
throw new ManifoldCFException("Unknown algorithm exception creating keystore: "+e.getMessage(),e);
}
catch (CertificateException e)
{
throw new ManifoldCFException("Unknown certificate exception creating keystore: "+e.getMessage(),e);
}
}
/** Create the keystore object from an existing base 64 string.
*/
public KeystoreManager(String passcode, String base64String)
throws ManifoldCFException
{
this.passcode = passcode;
try
{
keystore = KeyStore.getInstance("JKS");
byte[] decodedBytes = new org.apache.manifoldcf.core.common.Base64().decodeString(base64String);
try(InputStream base64Input = new ByteArrayInputStream(decodedBytes))
{
keystore.load(base64Input,passcode.toCharArray());
}
}
catch (KeyStoreException e)
{
throw new ManifoldCFException("Keystore exception: "+e.getMessage(),e);
}
catch (InterruptedIOException e)
{
throw new ManifoldCFException("Interrupted IO: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (IOException e)
{
throw new ManifoldCFException("IO error creating keystore: "+e.getMessage(),e);
}
catch (NoSuchAlgorithmException e)
{
throw new ManifoldCFException("Unknown algorithm exception creating keystore: "+e.getMessage(),e);
}
catch (CertificateException e)
{
throw new ManifoldCFException("Unknown certificate exception creating keystore: "+e.getMessage(),e);
}
}
/** Get a unique hashstring for this keystore. The hashcode depends only on the certificates
* in the store.
*@return the hash string for this keystore.
*/
@Override
public String getHashString()
throws ManifoldCFException
{
StringBuilder sb = new StringBuilder();
// Get the certs in the store
String[] aliases = getContents();
for (String alias : aliases)
{
String description = getDescription(alias);
sb.append(":").append(alias).append(":").append(description);
}
return sb.toString();
}
/** Grab a list of the aliases in the key store.
*@return the list, as a string array.
*/
@Override
public String[] getContents()
throws ManifoldCFException
{
try
{
String[] rval = new String[keystore.size()];
Enumeration enumeration = keystore.aliases();
int i = 0;
while (enumeration.hasMoreElements())
{
String alias = (String)enumeration.nextElement();
rval[i++] = alias;
}
return rval;
}
catch (KeyStoreException e)
{
throw new ManifoldCFException("Keystore not initialized: "+e.getMessage(),e);
}
}
/** For an alias, get some descriptive information from the object in the keystore.
*@param alias is the alias name.
*@return a description of what's in the alias.
*/
@Override
public String getDescription(String alias)
throws ManifoldCFException
{
try
{
Certificate c = keystore.getCertificate(alias);
if (c == null)
return null;
return c.toString();
}
catch (KeyStoreException e)
{
throw new ManifoldCFException("Keystore not initialized: "+e.getMessage(),e);
}
}
/** Import a certificate or key into the list. The data must be added as binary.
*@param alias is the name of the certificate.
*@param certData is the binary data for the certificate.
*/
@Override
public void importCertificate(String alias, InputStream certData)
throws ManifoldCFException
{
try
{
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate c = cf.generateCertificate(certData);
keystore.setCertificateEntry(alias,c);
if (Logging.keystore.isDebugEnabled())
{
if (keystore.isCertificateEntry(alias))
Logging.keystore.debug("The certificate just imported is a Trust Certificate");
else
Logging.keystore.debug("The certificate just imported is NOT a Trust Certificate");
}
}
catch (KeyStoreException e)
{
throw new ManifoldCFException("Keystore exception: "+e.getMessage(),e);
}
catch (CertificateException e)
{
throw new ManifoldCFException("Certificate error: "+e.getMessage(),e);
}
}
/** Read a certificate from the keystore.
*/
@Override
public java.security.cert.Certificate getCertificate(String alias)
throws ManifoldCFException
{
try
{
return keystore.getCertificate(alias);
}
catch (KeyStoreException e)
{
throw new ManifoldCFException("Keystore exception: "+e.getMessage(),e);
}
}
/** Add a certificate to the keystore.
*/
@Override
public void addCertificate(String alias, java.security.cert.Certificate certificate)
throws ManifoldCFException
{
try
{
keystore.setCertificateEntry(alias,certificate);
if (Logging.keystore.isDebugEnabled())
{
if (keystore.isCertificateEntry(alias))
Logging.keystore.debug("The certificate just added is a Trust Certificate");
else
Logging.keystore.debug("The certificate just added is NOT a Trust Certificate");
}
}
catch (KeyStoreException e)
{
throw new ManifoldCFException("Keystore exception: "+e.getMessage(),e);
}
}
/** Remove a certificate.
*@param alias is the name of the certificate to remove.
*/
@Override
public void remove(String alias)
throws ManifoldCFException
{
try
{
keystore.deleteEntry(alias);
}
catch (KeyStoreException e)
{
throw new ManifoldCFException("Error deleting keystore entry",e);
}
}
/** Convert to a base64 string.
*@return the base64-encoded string. NOTE WELL: as of JDK 1.6, you will not get the same exact string twice from this method --
* so it cannot be used for a hash!!
*/
@Override
public String getString()
throws ManifoldCFException
{
try
{
ByteArrayOutputStream output = new ByteArrayOutputStream();
try
{
keystore.store(output,passcode.toCharArray());
return new org.apache.manifoldcf.core.common.Base64().encodeByteArray(output.toByteArray());
}
catch (KeyStoreException e)
{
throw new ManifoldCFException("Error accessing keystore: "+e.getMessage(),e);
}
catch (InterruptedIOException e)
{
throw new ManifoldCFException("Interrupted IO: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (IOException e)
{
throw new ManifoldCFException("IO error saving keystore: "+e.getMessage(),e);
}
catch (NoSuchAlgorithmException e)
{
throw new ManifoldCFException("Unknown algorithm exception saving keystore: "+e.getMessage(),e);
}
catch (CertificateException e)
{
throw new ManifoldCFException("Certificate exception saving keystore: "+e.getMessage(),e);
}
finally
{
output.close();
}
}
catch (InterruptedIOException e)
{
throw new ManifoldCFException("Interrupted IO: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (IOException e)
{
throw new ManifoldCFException("IO exception storing keystore: "+e.getMessage(),e);
}
}
/** Build a secure socket factory based on this keystore.
*/
@Override
public javax.net.ssl.SSLSocketFactory getSecureSocketFactory()
throws ManifoldCFException
{
try
{
// Construct a key manager and a trust manager
javax.net.ssl.KeyManagerFactory keyManagerFactory = null;
// javax.net.ssl.KeyManagerFactory keyManagerFactory = javax.net.ssl.KeyManagerFactory.getInstance(javax.net.ssl.KeyManagerFactory.getDefaultAlgorithm());
// keyManagerFactory.init(keystore,passcode);
javax.net.ssl.TrustManagerFactory trustManagerFactory = javax.net.ssl.TrustManagerFactory.getInstance(javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm());
Logging.keystore.debug("Contents of current trust keystore is:");
if (Logging.keystore.isDebugEnabled())
{
String[] contents = getContents();
int i = 0;
while (i < contents.length)
{
Logging.keystore.debug("Description "+Integer.toString(i)+": "+getDescription(contents[i]));
i++;
}
}
Logging.keystore.debug("Reading trust keystore...");
trustManagerFactory.init(keystore);
if (Logging.keystore.isDebugEnabled())
{
Logging.keystore.debug("...done");
javax.net.ssl.TrustManager array[] = trustManagerFactory.getTrustManagers();
Logging.keystore.debug("Found "+Integer.toString(array.length)+" trust managers");
int i = 0;
while (i < array.length)
{
javax.net.ssl.TrustManager tm = array[i];
if (tm instanceof javax.net.ssl.X509TrustManager)
{
Logging.keystore.debug("Trust manager "+Integer.toString(i)+" is an x509 trust manager; it's class is "+tm.getClass().getName());
javax.net.ssl.X509TrustManager tm2 = (javax.net.ssl.X509TrustManager)tm;
java.security.cert.X509Certificate calist[] = tm2.getAcceptedIssuers();
Logging.keystore.debug("There are "+Integer.toString(calist.length)+" accepted issuers");
int j = 0;
while (j < calist.length)
{
String value = calist[j].getSubjectDN().toString();
Logging.keystore.debug("Authority "+Integer.toString(j)+" is "+value);
j++;
}
}
i++;
}
Logging.keystore.debug("No more trust contents");
}
java.security.SecureRandom secureRandom = java.security.SecureRandom.getInstance("SHA1PRNG");
// Create an SSL context
javax.net.ssl.SSLContext sslContext = javax.net.ssl.SSLContext.getInstance("SSL");
sslContext.init(((keyManagerFactory==null)?null:keyManagerFactory.getKeyManagers()),((trustManagerFactory==null)?null:trustManagerFactory.getTrustManagers()),
secureRandom);
return sslContext.getSocketFactory();
}
catch (java.security.NoSuchAlgorithmException e)
{
throw new ManifoldCFException("No such algorithm: "+e.getMessage(),e);
}
catch (java.security.KeyStoreException e)
{
throw new ManifoldCFException("Keystore exception: "+e.getMessage(),e);
}
catch (java.security.KeyManagementException e)
{
throw new ManifoldCFException("Key management exception: "+e.getMessage(),e);
}
}
}