package org.maachang.comet.net.ssl ;

import java.io.FileInputStream;
import java.net.InetAddress;
import java.security.KeyStore;
import java.security.SecureRandom;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.TrustManagerFactory;

import org.maachang.conf.Config;
import org.maachang.conf.ConvIniParam;

/**
 * SSLオプション.
 * 
 * @version 2007/11/17
 * @author  masahito suzuki
 * @since   MaachangComet 1.1D
 */
public class SslOption {
    
    /**
     * SSLセクション名.
     */
    private static final String SSL_SECTION = "ssl" ;
    
    /**
     * KeyStoreセクション名.
     */
    private static final String KEYSTORE_SECTION = "keystore" ;
    
    /**
     * TrustStoreセクション名.
     */
    private static final String TRUSTSTORE_SECTION = "truststore" ;
    
    /**
     * デフォルトストア.
     */
    public static final String DEFAULT_STORE = "JKS" ;
    
    /**
     * デフォルトマネージャアルゴリズム.
     */
    public static final String DEFAULT_MANAGER_ALGORITHM = "SunX509" ;
    
    /**
     * デフォルトSSLプロトコル.
     */
    public static final String DEFAULT_SSL_PROTOCOL = "TLS" ;
    
    /**
     * デフォルトポート.
     */
    public static final int DEFAULT_PORT = 3443 ;
    
    /**
     * キーストアタイプ.
     */
    private String keyStore = DEFAULT_STORE ;
    
    /**
     * トラストストアタイプ.
     */
    private String trustStore = DEFAULT_STORE ;
    
    /**
     * キーストアパスワード.
     */
    private String keyStorePasswd = null ;
    
    /**
     * トラストストアパスワード.
     */
    private String trustPassword = null ;
    
    /**
     * キーストアマネージャアルゴリズム.
     */
    private String keyManagerAlgorithm = DEFAULT_MANAGER_ALGORITHM ;
    
    /**
     * トラストストアマネージャアルゴリズム.
     */
    private String trustKeyManagerAlgorithm = DEFAULT_MANAGER_ALGORITHM ;
    
    /**
     * SSLプロトコル.
     */
    private String sslProtocol = DEFAULT_SSL_PROTOCOL ;
    
    /**
     * 乱数アルゴリズム(PRNG).
     */
    private String randomAlgorithm = null ;
    
    /**
     * キーストアファイル名.
     */
    private String keyStoreFile = null ;
    
    /**
     * トラストストアキーファイル名.
     */
    private String trustFile = null ;
    
    /**
     * クライアント認証必須条件.
     */
    private boolean needClientAuth = false;
    
    /**
     * クライアント認証要求条件.
     */
    private boolean wantClientAuth = false;
    
    /**
     * SSLポート番号.
     */
    private int sslPort = DEFAULT_PORT ;
    
    /**
     * SSLアドレス.
     */
    private InetAddress sslAddress = null ;
    
    /**
     * SSLコンテキスト.
     */
    private SSLContext ctx = null ;
    
    /**
     * コンストラクタ.
     */
    public SslOption() {
        
    }

    /**
     * コンフィグ情報から、SSLオプションを生成.
     * <BR>
     * @param conf 対象のコンフィグを設定します.
     * @return SslOption SSLオプションが返されます.
     */
    public static final SslOption create( Config conf ) {
        SslOption sslOpt = null ;
        if( conf.isSection( SSL_SECTION ) == true &&
            ConvIniParam.getBoolean( conf.get( SSL_SECTION,"ssl",0 ) ) == true ) {
            // SSLが有効.
            if( conf.isSection( KEYSTORE_SECTION ) == true &&
                conf.get( KEYSTORE_SECTION,"key-file",0 ) != null ) {
                sslOpt = new SslOption() ;
                Object val = null ;
                // ssl設定を取得.
                if( ( val = ConvIniParam.getInetAddress( conf.get( SSL_SECTION,"address",0 ) ) ) != null ) {
                    sslOpt.setSslAddress( ( InetAddress )val ) ;
                }
                if( ( val = conf.getIntObject( SSL_SECTION,"port",0 ) ) != null ) {
                    sslOpt.setSslPort( ( ( Integer )val ).intValue() ) ;
                }
                if( ( val = conf.get( SSL_SECTION,"protocol",0 ) ) != null ) {
                    sslOpt.setSslProtocol( ( String )val ) ;
                }
                if( ( val = conf.get( SSL_SECTION,"random-algorithm",0 ) ) != null ) {
                    sslOpt.setRandomAlgorithm( ( String )val ) ;
                }
                if( conf.getBoolean( SSL_SECTION,"need-auth",0 ) == true ) {
                    sslOpt.setNeedClientAuth( true ) ;
                }
                if( conf.getBoolean( SSL_SECTION,"want-auth",0 ) == true ) {
                    sslOpt.setWantClientAuth( true ) ;
                }
                // keystore情報を取得.
                if( ( val = conf.get( KEYSTORE_SECTION,"key-store",0 ) ) != null ) {
                    sslOpt.setKeyStore( ( String )val ) ;
                }
                if( ( val = conf.get( KEYSTORE_SECTION,"key-manager-algorithm",0 ) ) != null ) {
                    sslOpt.setKeyManagerAlgorithm( ( String )val ) ;
                }
                if( ( val = conf.get( KEYSTORE_SECTION,"key-passwd",0 ) ) != null ) {
                    sslOpt.setKeyStorePasswd( ( String )val ) ;
                }
                if( ( val = conf.get( KEYSTORE_SECTION,"key-file",0 ) ) != null ) {
                    sslOpt.setKeyStoreFile( ( String )val ) ;
                }
                // trustStore情報を取得.
                if( conf.isSection( TRUSTSTORE_SECTION ) == true &&
                    conf.getBoolean( TRUSTSTORE_SECTION,"trust",0 ) == true ) {
                    if( ( val = conf.get( TRUSTSTORE_SECTION,"trust-store",0 ) ) != null ) {
                        sslOpt.setTrustStore( ( String )val ) ;
                    }
                    else {
                        sslOpt.setTrustStore( null ) ;
                    }
                    if( ( val = conf.get( TRUSTSTORE_SECTION,"trust-manager-algorithm",0 ) ) != null ) {
                        sslOpt.setTrustKeyManagerAlgorithm( ( String )val ) ;
                    }
                    else {
                        sslOpt.setTrustKeyManagerAlgorithm( null ) ;
                    }
                    if( ( val = conf.get( TRUSTSTORE_SECTION,"trust-passwd",0 ) ) != null ) {
                        sslOpt.setTrustPassword( ( String )val ) ;
                    }
                    else {
                        sslOpt.setTrustPassword( null ) ;
                    }
                    if( ( val = conf.get( TRUSTSTORE_SECTION,"trust-file",0 ) ) != null ) {
                        sslOpt.setTrustFile( ( String )val ) ;
                    }
                    else {
                        sslOpt.setTrustFile( null ) ;
                    }
                }
                else {
                    sslOpt.setTrustStore( null ) ;
                    sslOpt.setTrustPassword( null ) ;
                    sslOpt.setTrustKeyManagerAlgorithm( null ) ;
                    sslOpt.setTrustFile( null ) ;
                }
            }
        }
        return sslOpt ;
    }

    /**
     * SSLコンテキストを取得.
     * <BR>
     * @return SSLContext SSLコンテキストが返されます.
     * @exception Exception 例外.
     */
    public SSLContext getSslContext()
        throws Exception {
        return getSslContext( false ) ;
    }

    /**
     * SSLコンテキストを取得.
     * <BR>
     * @param recreate [true]の場合、コンテキストが既に作成されている場合、
     *                 再作成します.
     * @return SSLContext SSLコンテキストが返されます.
     * @exception Exception 例外.
     */
    public synchronized SSLContext getSslContext( boolean recreate )
        throws Exception {
        if( recreate == true || this.ctx == null ) {
            // 認証ファクトリを設定.
            KeyManagerFactory keyFactory = null ;
            if( getKeyStoreFile() != null &&
                getKeyStore() != null &&
                getKeyManagerAlgorithm() != null ) {
                
                char[] keyStorePasswd = null ;
                if( getKeyStorePasswd() != null &&
                    getKeyStorePasswd().length() > 0 ) {
                    keyStorePasswd = getKeyStorePasswd().toCharArray() ;
                }
                
                KeyStore keyStore = KeyStore.getInstance( getKeyStore() ) ;
                keyStore.load( new FileInputStream( getKeyStoreFile() ),keyStorePasswd ) ;
                keyFactory = KeyManagerFactory.getInstance( getKeyManagerAlgorithm() ) ;
                keyFactory.init( keyStore,keyStorePasswd ) ;
                
            }
            
            // ピア認証信頼を判断するファクトリを設定.
            TrustManagerFactory trustFactory = null ;
            if( getTrustFile() != null &&
                getTrustStore() != null &&
                getTrustKeyManagerAlgorithm() != null ) {
                
                char[] trustPasswd = null ;
                if( getTrustPassword() != null &&
                    getTrustPassword().length() > 0 ) {
                    trustPasswd = getTrustPassword().toCharArray() ;
                }
                
                KeyStore trust = KeyStore.getInstance( getTrustStore() ) ;
                trust.load( new FileInputStream( getTrustFile() ),trustPasswd ) ;
                trustFactory = TrustManagerFactory.getInstance( getTrustKeyManagerAlgorithm() ) ;
                trustFactory.init( trust ) ;
                
            }
            
            // 乱数アルゴリズム(PRNG)を設定.
            SecureRandom prng = null ;
            if( getRandomAlgorithm() != null ) {
                prng = SecureRandom.getInstance( getRandomAlgorithm() ) ;
            }
            
            // SSLコンテキストを設定.
            SSLContext context = SSLContext.getInstance( getSslProtocol() ) ;
            if( prng != null ) {
                context.init( ( keyFactory == null ) ? null : keyFactory.getKeyManagers(),
                    ( trustFactory == null ) ? null : trustFactory.getTrustManagers(),prng ) ;
            }
            else {
                context.init( ( keyFactory == null ) ? null : keyFactory.getKeyManagers(),
                    ( trustFactory == null ) ? null : trustFactory.getTrustManagers(),null ) ;
            }
            this.ctx = context ;
        }
        return this.ctx ;
    }

    /**
     * SSLEngineを取得.
     * <BR><BR>
     * @return SSLEngine 対象のSSLエンジンが返されます.
     */
    public SSLEngine getSSLEngine() throws Exception {
        return getSSLEngine( false ) ;
    }
    
    /**
     * SSLEngineを取得.
     * <BR><BR>
     * @param recreate [true]の場合、コンテキストが既に作成されている場合、
     *                 再作成します.
     * @return SSLEngine 対象のSSLエンジンが返されます.
     */
    public SSLEngine getSSLEngine( boolean recreate ) throws Exception {
        SSLContext context = getSslContext( recreate ) ;
        SSLEngine ret = context.createSSLEngine() ;
        ret.setNeedClientAuth( isNeedClientAuth() ) ;
        ret.setWantClientAuth( isWantClientAuth() ) ;
        ret.setUseClientMode( false ) ;
        return ret ;
    }

    /**
     * SSLServerSocketFactoryを取得.
     * <BR><BR>
     * @param recreate [true]の場合、コンテキストが既に作成されている場合、
     *                 再作成します.
     * @return SSLServerSocketFactory 対象のSSLServerSocketFactoryが返されます.
     */
    public SSLServerSocketFactory getSSLServerSocketFactory( boolean recreate ) throws Exception {
        SSLContext context = getSslContext( recreate ) ;
        return context.getServerSocketFactory() ;
    }

    /**
     * キーストアマネージャアルゴリズム を取得.
     * <BR><BR>
     * @return キーストアマネージャアルゴリズム が返されます.
     */
    public String getKeyManagerAlgorithm() {
        return keyManagerAlgorithm;
    }

    /**
     * キーストアマネージャアルゴリズム を設定.
     * <BR><BR>
     * @param keyManagerAlgorithm キーストアマネージャアルゴリズム を設定します.
     */
    public void setKeyManagerAlgorithm(String keyManagerAlgorithm) {
        if( keyManagerAlgorithm == null || ( keyManagerAlgorithm = keyManagerAlgorithm.trim() ).length() <= 0 ) {
            keyManagerAlgorithm = null ;
        }
        this.keyManagerAlgorithm = keyManagerAlgorithm;
    }

    /**
     * トラストストアマネージャアルゴリズム を取得.
     * <BR><BR>
     * @return トラストストアマネージャアルゴリズム が返されます.
     */
    public String getTrustKeyManagerAlgorithm() {
        return trustKeyManagerAlgorithm;
    }

    /**
     * トラストストアマネージャアルゴリズム を設定.
     * <BR><BR>
     * @param trustKeyManagerAlgorithm トラストストアマネージャアルゴリズム を設定します.
     */
    public void setTrustKeyManagerAlgorithm(String trustKeyManagerAlgorithm) {
        if( trustKeyManagerAlgorithm == null || ( trustKeyManagerAlgorithm = trustKeyManagerAlgorithm.trim() ).length() <= 0 ) {
            trustKeyManagerAlgorithm = null ;
        }
        this.trustKeyManagerAlgorithm = trustKeyManagerAlgorithm;
    }

    /**
     * キーストアタイプ を取得.
     * <BR><BR>
     * @return キーストアタイプ が返されます.
     */
    public String getKeyStore() {
        return keyStore;
    }

    /**
     * キーストアタイプ を設定.
     * <BR><BR>
     * @param keyStore キーストアタイプ を設定します.
     */
    public void setKeyStore(String keyStore) {
        if( keyStore == null || ( keyStore = keyStore.trim() ).length() <= 0 ) {
            keyStore = null ;
        }
        this.keyStore = keyStore;
    }

    /**
     * トラストストアタイプ を取得.
     * <BR><BR>
     * @return トラストストアタイプ が返されます.
     */
    public String getTrustStore() {
        return trustStore;
    }

    /**
     * トラストストアタイプ を設定.
     * <BR><BR>
     * @param trustStore トラストストアタイプ を設定します.
     */
    public void setTrustStore(String trustStore) {
        if( trustStore == null || ( trustStore = trustStore.trim() ).length() <= 0 ) {
            trustStore = null ;
        }
        this.trustStore = trustStore;
    }

    /**
     * キーストアファイル名 を取得.
     * <BR><BR>
     * @return キーストアファイル名 が返されます.
     */
    public String getKeyStoreFile() {
        return keyStoreFile;
    }

    /**
     * キーストアファイル名 を設定.
     * <BR><BR>
     * @param keyStoreFile キーストアファイル名 を設定します.
     */
    public void setKeyStoreFile(String keyStoreFile) {
        if( keyStoreFile == null || ( keyStoreFile = keyStoreFile.trim() ).length() <= 0 ) {
            keyStoreFile = null ;
        }
        this.keyStoreFile = keyStoreFile;
    }

    /**
     * キーストアパスワード を取得.
     * <BR><BR>
     * @return キーストアパスワード が返されます.
     */
    public String getKeyStorePasswd() {
        return keyStorePasswd;
    }

    /**
     * キーストアパスワード を設定.
     * <BR><BR>
     * @param keyStorePasswd キーストアパスワード を設定します.
     */
    public void setKeyStorePasswd(String keyStorePasswd) {
        if( keyStorePasswd == null || ( keyStorePasswd = keyStorePasswd.trim() ).length() <= 0 ) {
            keyStorePasswd = null ;
        }
        this.keyStorePasswd = keyStorePasswd;
    }

    /**
     * トラストストアパスワード を取得.
     * <BR><BR>
     * @return トラストストアパスワード が返されます.
     */
    public String getTrustPassword() {
        return trustPassword;
    }

    /**
     * トラストストアパスワード を設定.
     * <BR><BR>
     * @param trustPassword トラストストアパスワード を設定します.
     */
    public void setTrustPassword(String trustPassword) {
        if( trustPassword == null || ( trustPassword = trustPassword.trim() ).length() <= 0 ) {
            trustPassword = null ;
        }
        this.trustPassword = trustPassword;
    }

    /**
     * 乱数アルゴリズム(PRNG) を取得.
     * <BR><BR>
     * @return 乱数アルゴリズム(PRNG) が返されます.
     */
    public String getRandomAlgorithm() {
        return randomAlgorithm;
    }

    /**
     * 乱数アルゴリズム(PRNG) を設定.
     * <BR><BR>
     * @param randomAlgorithm 乱数アルゴリズム(PRNG) を設定します.
     */
    public void setRandomAlgorithm(String randomAlgorithm) {
        if( randomAlgorithm == null || ( randomAlgorithm = randomAlgorithm.trim() ).length() <= 0 ) {
            randomAlgorithm = null ;
        }
        this.randomAlgorithm = randomAlgorithm;
    }

    /**
     * SSLプロトコル を取得.
     * <BR><BR>
     * @return SSLプロトコル が返されます.
     */
    public String getSslProtocol() {
        return sslProtocol;
    }

    /**
     * SSLプロトコル を設定.
     * <BR><BR>
     * @param sslProtocol SSLプロトコル を設定します.
     */
    public void setSslProtocol(String sslProtocol) {
        if( sslProtocol == null || ( sslProtocol = sslProtocol.trim() ).length() <= 0 ) {
            sslProtocol = null ;
        }
        this.sslProtocol = sslProtocol;
    }

    /**
     * トラストストアキーファイル名 を取得.
     * <BR><BR>
     * @return トラストストアファイル名 が返されます.
     */
    public String getTrustFile() {
        return trustFile;
    }

    /**
     * トラストストアキーファイル名 を設定.
     * <BR><BR>
     * @param trustFile トラストストアファイル名 を設定します.
     */
    public void setTrustFile(String trustFile) {
        if( trustFile == null || ( trustFile = trustFile.trim() ).length() <= 0 ) {
            trustFile = null ;
        }
        this.trustFile = trustFile;
    }

    /**
     * クライアント認証必須条件を設定.
     * <BR><BR>
     * @param clientAuth クライアント認証必須条件を設定します.
     */
    public void setNeedClientAuth( boolean needClientAuth ) {
        this.needClientAuth = needClientAuth ;
    }

    /**
     * クライアント認証必須条件を取得.
     * <BR><BR>
     * @return boolean クライアント認証必須条件が返されます.
     */
    public boolean isNeedClientAuth() {
        return this.needClientAuth ;
    }

    /**
     * クライアント認証要求条件を設定.
     * <BR><BR>
     * @param wantClientAuth クライアント認証要求条件を設定します.
     */
    public void setWantClientAuth( boolean wantClientAuth ) {
        this.wantClientAuth = wantClientAuth ;
    }

    /**
     * クライアント認証要求条件を取得.
     * <BR><BR>
     * @return boolean クライアント認証要求条件が返されます.
     */
    public boolean isWantClientAuth() {
        return this.wantClientAuth ;
    }

    /**
     * SSLポート番号を設定.
     * <BR><BR>
     * @param port SSLポート番号を設定します.
     */
    public void setSslPort( int sslPort ) {
        this.sslPort = sslPort ;
    }

    /**
     * SSLポート番号を取得.
     * <BR><BR>
     * @return int SSLポート番号を取得します.
     */
    public int getSslPort() {
        return this.sslPort ;
    }

    /**
     * SSLアドレスを設定.
     * <BR><BR>
     * @param sslAddress SSLアドレスを設定します.
     */
    public void setSslAddress( InetAddress sslAddress ) {
        this.sslAddress = sslAddress ;
    }

    /**
     * SSLアドレスを取得.
     * <BR>
     * @return InetAddress SSLアドレスが返されます.
     */
    public InetAddress getSslAddress() {
        return this.sslAddress ;
    }

    /**
     * 文字列に変換.
     * <BR>
     * @return String 文字列が返されます.
     */
    public String toString() {
        StringBuilder buf = new StringBuilder() ;
        buf.append( " sslProtocol:" + getSslProtocol() ) ;
        buf.append( " sslPort:" + getSslPort() ) ;
        buf.append( " sslAddress:" + getSslAddress() ) ;
        buf.append( " needClientAuth:" + isNeedClientAuth() ) ;
        buf.append( " wantClientAuth:" + isWantClientAuth() ) ;
        buf.append( " randomAlgorithm:" + getRandomAlgorithm() ) ;
        buf.append( " keyStore:" + getKeyStore() ) ;
        buf.append( " keyManagerAlgorithm:" + getKeyManagerAlgorithm() ) ;
        buf.append( " keyStoreFile:" + getKeyStoreFile() ) ;
        if( getTrustStore() != null ) {
            buf.append( " trustStore:" + getTrustStore() ) ;
            buf.append( " trustKeyManagerAlgorithm:" + getTrustKeyManagerAlgorithm() ) ;
            buf.append( " trustFile:" + getTrustFile() ) ;
        }
        return buf.toString() ;
    }
}

