package org.jwall.security.cert;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.net.URL;
import java.security.KeyStore;
import java.security.Principal;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.logging.Logger;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.x500.X500Principal;
import javax.swing.JOptionPane;
import org.jwall.security.cert.ui.CertificateValidationDialog;
/**
* <p>
* This class implements a customizable trust manager for use within jwall.org applications.
* It basically loads trusted certificates from a keystore denoted by the system properties
* <div>
* <pre>
* org.jwall.security.cert.keystore.file
* org.jwall.security.cert.keystore.type
* </pre>
* </div>
* Per default (if these properties are not set) it searches for a keystore of type JKS at
* <code>/org/jwall/security/cert/keystore</code>.
* </p>
*
* @author Christian Bockermann <chris@jwall.org>
*
*/
public class CustomTrustManager
implements X509TrustManager, TrustManager
{
/** A unique logger for this class */
private static Logger log = Logger.getLogger( "ZeroTrustManager" );
/** The system property for disabling validation */
public final static String PROPERTY_TRUST_ALL = "org.jwall.security.certs.trust-all";
/** A system property for enabling/disabling user validation on unknown certificates */
public final static String PROPERTY_USER_VALIDATION = "org.jwall.security.certs.user-validation";
/** The singleton object of this class */
private static CustomTrustManager trustManager;
/** This is the list of accepted certs by this trust manager */
private KeyStore keystore;
/** The passphrase for the key store */
private char[] passphrase = "secret".toCharArray();
/** This flag can be used to disable certificate validation and trust everything */
private boolean trustAll = false;
/** A flag indicating whether the user is asked for approval on unknown certificates */
private boolean userValidation = true;
/**
* We usually allow only for one custom jwall trust manager to be available at a time.
* This singleton ensures no other manager of this class can be instantiated.
*
* @return The global trust manager.
*/
public synchronized static CustomTrustManager getInstance(){
if( trustManager == null )
trustManager = new CustomTrustManager();
return trustManager;
}
/**
*
* This constructor initializes the trust manager by loading the key store and
* looking for additional system properties.
*
*/
private CustomTrustManager(){
String keystoreFile = System.getProperty("user.home") + "/.jwall/keystore";
File f = new File( keystoreFile );
try {
log.info("Using default keystore...");
keystoreFile = "/org/jwall/security/cert/keystore";
char[] passphrase = "secret".toCharArray();
keystore = KeyStore.getInstance( "JKS" );
keystore.load( new FileInputStream( f ), passphrase );
} catch (Exception e){
e.printStackTrace();
keystore = null;
}
if( keystore == null ){
log.info( "Trying to load default keystore... " );
try {
URL url = CustomTrustManager.class.getResource( keystoreFile );
log.info( " loading from " + url );
char[] passphrase = "secret".toCharArray();
keystore = KeyStore.getInstance("JKS");
if( url != null )
keystore.load( url.openStream(), passphrase);
// now create a new user-specific key store by copying the default store in place
//
if( f.getParentFile() != null && !f.getParentFile().isDirectory() )
f.getParentFile().mkdirs();
keystore.store( new FileOutputStream( f ), passphrase );
} catch (Exception e) {
e.printStackTrace();
log.info( "Creating an empty keystore..." );
try {
keystore = KeyStore.getInstance( KeyStore.getDefaultType() );
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
// check if trust-checking is disabled, i.e. we trust all certificates, defaults to "false"
//
if( System.getProperty( PROPERTY_TRUST_ALL ) != null ){
trustAll = "true".equalsIgnoreCase( System.getProperty( PROPERTY_TRUST_ALL ) );
} else
trustAll = false;
// check if user-validation is wanted, defaults to "true"
//
if( System.getProperty( PROPERTY_USER_VALIDATION ) != null )
userValidation = "true".equalsIgnoreCase( System.getProperty( PROPERTY_USER_VALIDATION ) );
else
userValidation = true;
}
/**
* @see javax.net.ssl.X509TrustManager#checkClientTrusted(java.security.cert.X509Certificate[], java.lang.String)
*/
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
log.fine( "checkClientTrusted: \n");
for( X509Certificate cert : chain ){
log.fine("-------------------------------------------------------");
log.fine( " SubjectDN = "+cert.getSubjectDN() );
log.fine( " Issuer = " + cert.getIssuerDN() );
}
checkServerTrusted( chain, authType );
}
/**
* @see javax.net.ssl.X509TrustManager#checkServerTrusted(java.security.cert.X509Certificate[], java.lang.String)
*/
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
log.fine( "checkServerTrusted: \n");
for( X509Certificate cert : chain ){
log.fine("-------------------------------------------------------");
log.fine( " SubjectDN = "+cert.getSubjectDN() );
log.fine( " Issuer = " + cert.getIssuerDN() );
}
if( trustAll )
return;
boolean[] verified = new boolean[chain.length];
boolean allTrusted = true;
// verify all certificates by checking for validity and looking up their issuer certificates within the key store
//
for( int i = 0; i < chain.length; i++ ){
verified[i] = isIssuerTrusted( chain[i] );
allTrusted = allTrusted && verified[i];
}
// if one certificate cannot be verified we either directly throw an error or as for user approval
//
if( allTrusted )
return;
if( userValidation ){
if( requestUserApproval( chain, authType ) ) {
return;
} else
throw new CertificateException( "Untrusted issuer, user-approval not given!" );
} else
throw new CertificateException( "Untrusted/unknown issuer, user-approval disabled!" );
}
/**
*
* This method opens up a validation dialog presenting the certificate chain to the user
* and requesting manual approval.
*
* @param chain The certificate chain that is to be approved.
* @param authType The authType requested for this chain.
* @return <code>true</code>, if the user approved the certificate chain.
*/
public boolean requestUserApproval( X509Certificate[] chain, String authType ){
CertificateValidationDialog validator = new CertificateValidationDialog( chain );
validator.setVisible( true );
boolean approved = validator.isChainApproved();
if( approved && validator.isApprovedPermanently() ){
X509Certificate cert = chain[ chain.length - 1 ];
try {
StringBuffer alias = new StringBuffer();
Principal subject = cert.getSubjectDN();
log.info("Subject = " + subject);
if( subject != null )
log.info( " Subject.getName(): " + subject.getName() );
X500Principal x500 = cert.getSubjectX500Principal();
log.info( "x500 = " + x500 );
if( x500 != null ){
String[] tok = x500.toString().split(",");
int i = 0;
alias.append( cert.getSerialNumber() );
while( keystore.getCertificate( alias.toString() ) != null && i < tok.length ) {
log.info("Adding another attribute for uniqueness to current alias " + alias.toString() );
if( alias.length() > 0 )
alias.append( "," );
String[] kv = tok[i++].split( "=" );
alias.append( kv[1].trim() );
}
log.info( "Created unique alias: " + alias.toString() );
}
log.info( "Adding new certificate with alias \"" + alias + "\"" );
keystore.setCertificateEntry( alias.toString(), cert );
saveKeystore();
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog( validator, "Error while adding certificate to trusted cert store: " + e.getMessage(), "Error Storing Certificate", JOptionPane.ERROR_MESSAGE );
}
}
return approved;
}
/**
* This method checks if the issuer of the given certificate is valid and trusted, i.e.
* within the list of trusted issuers.
*
* @param cert The certificate which's issuers is to be checked.
* @return <code>true</code>, if the issuer is trusted to sign the certificate.
*/
public boolean isIssuerTrusted( X509Certificate cert ) {
try {
for( X509Certificate caCert : getAcceptedIssuers() ){
caCert.checkValidity();
// we require the CA cert to be allowed for key-signing
//
// if( caCert.getKeyUsage()[5] ){
if( cert.getIssuerDN() != null && cert.getIssuerDN().equals( caCert.getSubjectDN() ) )
return true;
if( cert.getSubjectAlternativeNames() != null && caCert.getSubjectAlternativeNames().contains( cert.getIssuerDN() ))
return true;
// }
}
} catch ( CertificateException e ) {
e.printStackTrace();
}
return false;
}
public boolean isValid( X509Certificate[] chain ){
// TODO: Implement
return false;
}
public boolean isValid( X509Certificate cert ){
// TODO: Implement
return false;
}
public void saveCertificate( X509Certificate cert ){
try {
String alias = "";
keystore.setCertificateEntry( alias, cert );
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Return all issuer certificates that are found in our keystore.
*
* @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()
*/
public X509Certificate[] getAcceptedIssuers() {
try {
ArrayList<X509Certificate> certs = new ArrayList<X509Certificate>();
Enumeration<String> en = keystore.aliases();
while( en.hasMoreElements() ){
String alias = en.nextElement();
try {
X509Certificate cert = (X509Certificate) keystore.getCertificate( alias );
// if( cert.getKeyUsage() != null && cert.getKeyUsage()[5] ){
cert.checkValidity();
certs.add( cert );
// } else
// log.info( "Skipping certificate " + cert + " as it is not suited for keyCertSigning!" );
} catch (Exception e) {
e.printStackTrace();
}
}
X509Certificate[] issuers = new X509Certificate[ certs.size() ];
for( int i = 0; i < issuers.length; i++ )
issuers[i] = certs.get( i );
return issuers;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private void saveKeystore(){
try {
String keystoreFileName = System.getProperty("user.home") + "/.jwall/keystore";
File keystoreFile = new File( keystoreFileName );
log.info( "Writing key store to " + keystoreFile.getAbsolutePath() );
keystore.store( new FileOutputStream( keystoreFile ), passphrase );
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main( String args[] ){
try {
KeyStore keystore = KeyStore.getInstance( "JKS" );
keystore.load( CustomTrustManager.class.getResourceAsStream( "/org/jwall/security/cert/keystore" ), "secret".toCharArray() );
Enumeration<String> en = keystore.aliases();
String alias = en.nextElement();
X509Certificate[] chain = new X509Certificate[]{ (X509Certificate) keystore.getCertificate( alias ) };
CustomTrustManager tm = CustomTrustManager.getInstance();
tm.requestUserApproval( chain, "" );
} catch (Exception e) {
}
}
}