/*******************************************************************************
* Copyright (c) 2008 Cambridge Semantics Incorporated.
* 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:
*
* Cambridge Semantics Incorporated - Initial Implementation
*******************************************************************************/
package org.openanzo.client;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.apache.commons.codec.binary.Hex;
import org.openanzo.client.cli.CommandContext;
import org.openanzo.client.cli.CommandLineInterface;
import org.openanzo.exceptions.AnzoException;
import org.openanzo.exceptions.AnzoRuntimeException;
import org.openanzo.exceptions.ExceptionConstants;
import org.openanzo.rdf.utils.KeystoreUtils;
/**
* This is a subclass of the default Java Trust Manager, X509TrustManager. It gives us access to the certificates so that information about them can be reported
* should they not be trusted. In that event the user is given the option to trust the certificate anyway.
*
* @author Danny Kahn ( <a href="mailto:danny@cambridgesemantics.com">danny@cambridgesemantics.com </a>)
*
*/
public class AnzoTrustManager implements X509TrustManager {
/*
* The default X509TrustManager returned by SunX509. We'll delegate
* decisions to it, and fall back to the logic in this class if the
* default X509TrustManager doesn't trust it.
*/
private X509TrustManager x509tm;
private boolean trustAll;
private boolean showTrace;
private static final String[] MONTHS = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };
private static final String ANZO_DIR = ".anzo";
private static final String DEFAULT_CLIENT_TRUST = "client.ts";
private static final String DEFAULT_PWORD = "p@ssw0rd";
public AnzoTrustManager(boolean trustAll, boolean showTrace) throws AnzoException {
this.trustAll = trustAll;
this.showTrace = showTrace;
String truststorePath = CommandContext.preprocessString(System.getProperty("javax.net.ssl.trustStore"));
String userHome = System.getProperty("user.home");
try {
if (truststorePath == null && userHome != null) {
File truststoreFile = new File(new File(userHome, ANZO_DIR), DEFAULT_CLIENT_TRUST);
if (truststoreFile.exists()) // check the default location for the trust store in the user's .anzo directory
truststorePath = truststoreFile.getCanonicalPath();
}
String truststoreType = System.getProperty("javax.net.ssl.trustStoreType", "JCEKS");
String truststorePassword = System.getProperty("javax.net.ssl.trustStorePassword", DEFAULT_PWORD);
// create a "default" JSSE X509TrustManager.
KeyStore ks = KeyStore.getInstance(truststoreType);
if (truststorePath != null && truststorePassword != null) {
File trustFile = new File(truststorePath);
if (trustFile.exists()) {
ks.load(new FileInputStream(trustFile), truststorePassword.toCharArray());
}
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509", "SunJSSE");
tmf.init(ks);
TrustManager tms[] = tmf.getTrustManagers();
/*
* Iterate over the returned trustmanagers, look
* for an instance of X509TrustManager. If found,
* use that as our "default" trust manager.
*/
for (int i = 0; i < tms.length; i++) {
if (tms[i] instanceof X509TrustManager) {
x509tm = (X509TrustManager) tms[i];
return;
}
}
} catch (Exception e) {
throw new AnzoException(ExceptionConstants.CLIENT.FAILED_INITIALIZE_TRUST_MANAGER, e);
}
// could not find the java default trust manager so throw an exception
throw new AnzoRuntimeException(ExceptionConstants.CLIENT.FAILED_INITIALIZE_TRUST_MANAGER, "The default Java Trust Manager was not found");
}
/**
* Delegate to the default trust manager.
*/
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
x509tm.checkClientTrusted(chain, authType);
} catch (CertificateException excep) {
handleCertificateException(excep, chain);
}
}
/**
* Delegate to the default trust manager.
*/
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
x509tm.checkServerTrusted(chain, authType);
} catch (CertificateException excep) {
handleCertificateException(excep, chain);
}
}
/**
* Merely pass this through.
*/
public X509Certificate[] getAcceptedIssuers() {
return x509tm.getAcceptedIssuers();
}
private void handleCertificateException(CertificateException ce, X509Certificate[] chain) throws CertificateException {
if (trustAll) {
return;
}
System.err.println(ce.getMessage());
System.err.println("Certificate Information: \n");
Calendar cal = new GregorianCalendar();
cal.setTimeInMillis(chain[0].getNotBefore().getTime());
System.err.println("Creation Date: " + MONTHS[cal.get(Calendar.MONTH)] + " " + cal.get(Calendar.DAY_OF_MONTH) + ", " + cal.get(Calendar.YEAR));
//System.err.println("Entry type: " + chain[0].getType());
System.err.println("Certificate chain length: " + chain.length);
// print some information about the certificate(s) that failed
int i = 1;
for (X509Certificate cert : chain) {
System.err.println("Certificate[" + i++ + "]:");
System.err.println("Owner: " + cert.getSubjectX500Principal().toString());
System.err.println("Issuer: " + cert.getIssuerX500Principal().toString());
String serialNum = new String(Hex.encodeHex(cert.getSerialNumber().toByteArray()));
System.err.println("Serial Number: " + serialNum);
System.err.println("Valid from: " + cert.getNotBefore().toString() + " until: " + cert.getNotAfter().toString());
System.err.println("Certificate fingerprints: ");
try {
byte[] sig = cert.getEncoded();
System.err.println("\tMD5: " + getHash(sig, "MD5"));
System.err.println("\tSHA1: " + getHash(sig, "SHA1"));
} catch (NoSuchAlgorithmException e) {
}
System.err.println("\tSignature Algorithm Name: " + cert.getSigAlgName());
System.err.println("\tVersion: " + cert.getVersion());
System.err.println("-----------------------------------------------------");
}
System.err.println("Would you like to accept this certificate? (o)nce, (a)lways, (n)o");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String line = "";
try {
line = in.readLine();
} catch (IOException e) {
CommandLineInterface.DEFAULT_CONSOLE.printException(e, showTrace);
System.exit(1);
}
if (Character.toLowerCase(line.charAt(0)) == 'o') {
return;
} else if (Character.toLowerCase(line.charAt(0)) == 'a') {
try {
String truststoreType = System.getProperty("javax.net.ssl.trustStoreType", "JCEKS");
String truststorePassword = System.getProperty("javax.net.ssl.trustStorePassword", DEFAULT_PWORD);
String truststorePath = System.getProperty("javax.net.ssl.trustStore");
if (truststorePath == null) { // there is no trust store location in the user's settings.trig file
String userHome = System.getProperty("user.home");
if (userHome == null)
throw new AnzoException(ExceptionConstants.CLIENT.FAILED_INITIALIZE_TRUST_MANAGER, "User's home directory is not specified");
File truststoreFile = new File(new File(userHome, ANZO_DIR), DEFAULT_CLIENT_TRUST);
truststorePath = truststoreFile.getCanonicalPath();
if (!truststoreFile.exists())
openTruststore(truststoreType, truststorePath, truststorePassword);
} else {
truststorePath = CommandContext.preprocessString(truststorePath);
File truststoreFile = new File(truststorePath);
if (!truststoreFile.exists()) {
System.err.println("Could not find the specified trust store file at:");
System.err.println(truststoreFile.getCanonicalPath());
System.err.println("The trust store file is used for permanently trusting server certificates that");
System.err.println("are not trusted by default.");
System.err.println("Would you like to create a new trust store file at the specified location?");
System.err.println("(y)es, (n)o");
try {
line = in.readLine();
} catch (IOException e) {
CommandLineInterface.DEFAULT_CONSOLE.printException(e, showTrace);
System.exit(1);
}
if (Character.toLowerCase(line.charAt(0)) == 'y')
openTruststore(truststoreType, truststorePath, truststorePassword);
else
System.exit(1);
}
}
KeystoreUtils.addTrustedCert(truststorePath, truststoreType, truststorePassword, "imported_" + System.currentTimeMillis(), chain[0]);
} catch (AnzoException ae) {
System.err.println("Error importing certificate into truststore: ");
CommandLineInterface.DEFAULT_CONSOLE.printException(ae, showTrace);
System.exit(1);
} catch (IOException e) {
System.err.println("Error importing certificate into truststore: ");
CommandLineInterface.DEFAULT_CONSOLE.printException(e, showTrace);
System.exit(1);
}
} else {
System.exit(1); // if the user does not want to trust the certificate then exit
}
}
private void openTruststore(String truststoreType, String truststorePath, String truststorePassword) throws AnzoException {
try {
KeystoreUtils.generateTruststore(truststoreType, truststorePath, truststorePassword);
} catch (AnzoException ae) {
System.err.println("Could not open trust store file at:");
System.err.println(truststorePath);
System.err.println("The password or truststore type settings may be incorrect or the trust store file is invalid.");
throw ae;
}
}
private String getHash(byte[] key, String algName) throws NoSuchAlgorithmException {
MessageDigest digest;
digest = java.security.MessageDigest.getInstance(algName);
digest.update(key);
byte[] hash = digest.digest();
char[] digestChars = Hex.encodeHex(hash);
int len = (int) (digestChars.length + 0.5 * digestChars.length - 1);
char[] withColons = new char[len];
int j = 0;
for (int i = 0; i < digestChars.length; i += 2) {
withColons[j++] = Character.toUpperCase(digestChars[i]);
withColons[j++] = Character.toUpperCase(digestChars[i + 1]);
if (j < len)
withColons[j++] = ':';
}
return new String(withColons);
}
}