/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.appserv.management.client;
import java.io.File;
import java.io.IOException;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Date;
import java.text.DateFormat;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import javax.net.ssl.X509TrustManager;
import com.sun.appserv.management.util.misc.LineReaderImpl;
/**
This X509TrustManager implementation supports a trust-store file and allows
adding new certificates to it. It is designed to allow a subclass to
override a variety of protected methods including those of TrustManager:
<ul>
<li>checkClientTrusted</li>
<li>checkServerTrusted</li>
<li>getAcceptedIssuers</li>
</ul>
as well as:
<ul>
<li>#checkCertificate</li>
<li>#getTrustStorePassword</li>
<li>#shouldAddToTrustStore</li>
<li>#askShouldAddToTrustStore</li>
<li>#getCertificateAlias</li>
<li>#addCertificateToTrustStore</li>
<li>#writeStore</li>
<li>#certificateNotInTrustStore</li>
<li>#getTrustStore</li>
</ul>
<p>
For convenience, if setPrompt( true ) is called, then when a new Certificate
is encountered, askShouldAddToTrustStore( c ) prompts the user
via System.in as to whether to accept this new Certificate as trusted.
Subclasses can of course override this behavior any any desired way.
*/
public class TrustStoreTrustManager
implements X509TrustManager // do NOT make Serializable
{
private final File mTrustStoreFile;
private final char[] mTrustStorePassword;
private final String mKeyStoreType;
private KeyStore mTrustStore;
private boolean mPrompt;
/**
Create a new instance with the specified File and password
The trustStoreFile must exist.
@param trustStoreFile (not required to exist)
@param keyStoreType keystore (truststore) type, eg "JKS"
@param trustStorePassword (may be null)
*/
public
TrustStoreTrustManager(
final File trustStoreFile,
final String keyStoreType,
final char[] trustStorePassword )
{
if ( trustStoreFile == null || keyStoreType == null )
{
throw new IllegalArgumentException();
}
mTrustStoreFile = trustStoreFile;
mKeyStoreType = keyStoreType;
mTrustStorePassword = trustStorePassword;
mTrustStore = null;
mPrompt = false;
try
{
getTrustStore(); // force initialization now
}
catch( Exception e )
{
throw new RuntimeException( e );
}
}
/**
calls this( trustStoreFile,"JKS", trustStorePassword )
*/
public
TrustStoreTrustManager(
final File trustStoreFile,
final char[] trustStorePassword )
{
this( trustStoreFile, "JKS", trustStorePassword );
}
/**
If set to true, then when a new Certificate is encountered, the user
will be prompted via System.in as to whether it should be trusted.
@param prompt
*/
public void
setPrompt( final boolean prompt )
{
mPrompt = prompt;
}
/**
Create an instance using the system trust-store as returned by
getSystemTrustStoreFile().
@return an instance or null if not possible
*/
public static TrustStoreTrustManager
getSystemInstance()
{
final File trustStore = getSystemTrustStoreFile();
final char[] trustStorePassword = getSystemTrustStorePassword();
TrustStoreTrustManager mgr = null;
if ( trustStore != null && trustStorePassword != null )
{
return( new TrustStoreTrustManager( trustStore, trustStorePassword ) );
}
return( mgr );
}
private static char[]
toCharArray( final String s )
{
return( s == null ? null : s.toCharArray() );
}
/**
Standard system property denoting the trust-store.
*/
public static final String TRUSTSTORE_FILE_SPROP = "javax.net.ssl.trustStore";
/**
Standard system property denoting the trust-store password.
*/
public static final String TRUSTSTORE_PASSWORD_SPROP= "javax.net.ssl.trustStorePassword";
/**
Use System.getProperty( "javax.net.ssl.trustStore" ) to find a trust-store.
*/
public static File
getSystemTrustStoreFile()
{
final String prop = System.getProperty( TRUSTSTORE_FILE_SPROP );
final File trustStore = prop == null ? null : new File( prop );
return( trustStore );
}
/**
Use System.getProperty( "javax.net.ssl.trustStorePassword" ) to find the
trust-store password.
*/
public static char[]
getSystemTrustStorePassword()
{
return( toCharArray( System.getProperty( TRUSTSTORE_PASSWORD_SPROP ) ) );
}
/**
Return the trust-store that was initially passed in.
@return File
*/
public final File
getTrustStoreFile()
{
return( mTrustStoreFile );
}
/**
Subclass may choose to override this method to get the password from any
desired source. Otherwise, the password used to create this instance is
returned.
@return char[]
*/
protected char[]
getTrustStorePassword()
{
return( mTrustStorePassword );
}
public void
checkClientTrusted( X509Certificate[] chain, String authType)
throws CertificateException
{
throw new UnsupportedOperationException( "checkClientTrusted() not supported" );
}
public void
checkServerTrusted( X509Certificate[] chain, String authType)
throws CertificateException
{
if (chain == null || chain.length == 0)
{
throw new IllegalArgumentException();
}
checkCertificate(chain);
}
/**
By default, no issuers are trusted. It is better to trust specific
Certificates explicitly.
@return X509Certificate[]
*/
public X509Certificate[]
getAcceptedIssuers()
{
// none, by default
return( new X509Certificate[ 0 ] );
}
/**
Prompts via System.in to ask whether the Certificate should be added.
@param c
@return true if the response is yes.
*/
protected boolean
askShouldAddToTrustStore( final Certificate c )
throws IOException
{
final LineReaderImpl reader = new LineReaderImpl( System.in );
final String prompt = c.toString() +
"\n\nAdd the above certificate to the truststore [y/n]?";
final String result = reader.readLine( prompt );
return( result.equalsIgnoreCase( "y" ) || result.equalsIgnoreCase( "yes" ) );
}
/**
Subclass may wish to override this routine and call defaultShouldAddToTrustStore( c );
@param c
@return true if the Certificate should be trusted and added to the trust-store
*/
protected boolean
shouldAddToTrustStore( final Certificate c )
throws IOException
{
return( mPrompt ? askShouldAddToTrustStore( c ) : false );
}
/**
Return an alias for a Certificate to be added to the TrustStore.
@param c
@return an alias to be used for adding the Certificate to the trust-store
*/
protected String
getCertificateAlias( final Certificate c )
{
final DateFormat f = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
return( "cert" + f.format( new Date() ) );
}
/**
Add the Certificate with the specified alias to the trust-store.
@param alias
@param c
*/
protected void
addCertificateToTrustStore(
final String alias,
final Certificate c )
throws IOException,
KeyStoreException, NoSuchAlgorithmException, CertificateException
{
mTrustStore.setCertificateEntry( alias, c );
writeStore();
}
/**
Add the Certificate to the trust-store, using the alias returned by
getCertificateAlias( c ).
@param c
*/
protected void
addCertificateToTrustStore( final Certificate c )
throws IOException,
KeyStoreException, NoSuchAlgorithmException, CertificateException
{
final String aliasName = getCertificateAlias( c );
addCertificateToTrustStore( aliasName, c );
}
private void
writeStore(
final KeyStore trustStore,
final char[] trustStorePassword,
final File f )
throws IOException,
KeyStoreException, NoSuchAlgorithmException, CertificateException
{
FileOutputStream out = new FileOutputStream( f );
try
{
trustStore.store( out, trustStorePassword );
}
catch( Throwable t )
{
t.printStackTrace();
}
finally
{
out.close();
}
}
/**
Write the store to disk. Results are undefined if an error occurs while
writing the file.
*/
protected void
writeStore()
throws IOException,
KeyStoreException, NoSuchAlgorithmException, CertificateException
{
writeStore( getTrustStore(), getTrustStorePassword(), getTrustStoreFile() );
// NOTE: any exception thrown from here is squelched by calling JDK code
// if in the middle of a SSL negotiation
}
/**
The Certificate is not found in the trust-store.
If shouldAddToTrustStore( c ) returns false, then a CertificateException
is thrown. Otherwise, addCertificateToTrustStore( c ) is called.
@param c
*/
protected void
certificateNotInTrustStore( final Certificate c )
throws IOException,
KeyStoreException, NoSuchAlgorithmException, CertificateException
{
if ( shouldAddToTrustStore( c ) )
{
addCertificateToTrustStore( c );
}
else
{
throw new CertificateException( "Certificate not trusted:\n" + c );
}
}
private void
createTrustStoreFile(
final KeyStore keyStore,
final char[] pw,
final File f )
throws IOException,
CertificateException, NoSuchAlgorithmException,
KeyStoreException, FileNotFoundException
{
f.createNewFile();
writeStore( keyStore, pw, f );
}
/**
Get the KeyStore containing the Certificates to be trusted. This should
be a KeyStore corresponding to the file that was specified. The same
KeyStore should be returned each time.
@return KeyStore
*/
protected synchronized KeyStore
getTrustStore()
throws IOException,
CertificateException, NoSuchAlgorithmException, KeyStoreException, FileNotFoundException
{
if ( mTrustStore == null )
{
mTrustStore = KeyStore.getInstance( mKeyStoreType );
final File f = getTrustStoreFile();
final char[] pw = getTrustStorePassword();
if ( (! f.exists()) || f.length() == 0 )
{
f.delete();
mTrustStore.load( null, pw );
createTrustStoreFile( mTrustStore, pw, f);
}
else
{
final FileInputStream is = new FileInputStream( f );
try
{
mTrustStore.load( is, pw );
}
finally
{
is.close();
}
}
}
return( mTrustStore );
}
/**
@param chain
@throws RuntimeException
@throws CertificateException
*/
protected void
checkCertificate( final X509Certificate[] chain)
throws RuntimeException, CertificateException
{
try
{
//First ensure that the certificate is valid.
for (int i = 0 ; i < chain.length ; i ++)
{
chain[i].checkValidity();
}
mTrustStore = getTrustStore();
final Certificate cert = chain[ 0 ];
//if the certificate already exists in the truststore, it is implicitly trusted
if ( mTrustStore.getCertificateAlias( cert ) == null )
{
certificateNotInTrustStore( cert );
}
}
catch (CertificateException e)
{
throw e;
}
catch (Exception e)
{
throw new RuntimeException( e );
}
}
public String
toString()
{
return( "TrustStoreTrustManager--trusts certificates found in truststore: " + mTrustStore );
}
}