/* WCESSLSession.java -- implementation of SSLSession.
   Copyright (C) 2009 Mysaifu.com

This file is a part of Mysaifu JVM.

Mysaifu JVM is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.

Mysaifu JVM is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with Mysaifu JVM; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
USA

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version.  */


package com.mysaifu.jvm.java.security.provider;

import java.io.IOException;
import java.security.AccessController;
import java.security.AccessControlContext;
import java.security.Principal;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionBindingEvent;
import javax.net.ssl.SSLSessionBindingListener;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLPeerUnverifiedException;

public class WCESSLSession
    implements SSLSession
{
  /**
   * Cipher suite. SSL v3
   * 0x0000 - 0x001E
   */
  private static final String[] SSL_CIPHER_SUITES = {
                                                     "SSL_NULL_WITH_NULL_NULL",
                                                     "SSL_RSA_WITH_NULL_MD5",
                                                     "SSL_RSA_WITH_NULL_SHA",
                                                     "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
                                                     "SSL_RSA_WITH_RC4_128_MD5",
                                                     "SSL_RSA_WITH_RC4_128_SHA",
                                                     "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5",
                                                     "SSL_RSA_WITH_IDEA_CBC_SHA",
                                                     "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
                                                     "SSL_RSA_WITH_DES_CBC_SHA",
                                                     "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
                                                     "SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA",
                                                     "SSL_DH_DSS_WITH_DES_CBC_SHA",
                                                     "SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA",
                                                     "SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA",
                                                     "SSL_DH_RSA_WITH_DES_CBC_SHA",
                                                     "SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA",
                                                     "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
                                                     "SSL_DHE_DSS_WITH_DES_CBC_SHA",
                                                     "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
                                                     "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
                                                     "SSL_DHE_RSA_WITH_DES_CBC_SHA",
                                                     "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
                                                     "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
                                                     "SSL_DH_anon_WITH_RC4_128_MD5",
                                                     "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
                                                     "SSL_DH_anon_WITH_DES_CBC_SHA",
                                                     "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA",
                                                     "SSL_FORTEZZA_KEA_WITH_NULL_SHA",
                                                     "SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA",
                                                     "SSL_FORTEZZA_KEA_WITH_RC4_128_SHA" };

  // 0x00 0x62 - 0x00 0x64
  private static final String[] SSL_EXPORTABLE_CIPHER_SUITES = {
                                                                "SSL_RSA_EXPORT1024_WITH_RC4_56_SHA",
                                                                null,
                                                                "SSL_RSA_EXPORT1024_WITH_DES_CBC_SHA"

  };

  /**
   * TLS cipher suite (RFC 2246)
   * 0x0000 - 0x001B
   * 
   * (0x001C, 0x001D は TLSではSSLのFORTEZZAと衝突してしまうため欠番）
   *
   * Addition of Kerberos Cipher Suites to Transport Layer Security (TLS) (RFC 2712)
   * 0x001E - 0x002B
   *
   * Pre-Shared Key (PSK) Ciphersuites with NULL Encryption for
   *                    Transport Layer Security (TLS) (RFC 4785)
   * 0x002C - 0x002E
   *
   * Advanced Encryption Standard (AES) Ciphersuites for Transport Layer Security (TLS) (RFC 3268)
   * 0x002F - 0x003A
   */
  private static final String[] TLS_CIPHER_SUITES = {
                                                     "TLS_NULL_WITH_NULL_NULL",
                                                     "TLS_RSA_WITH_NULL_MD5",
                                                     "TLS_RSA_WITH_NULL_SHA",
                                                     "TLS_RSA_EXPORT_WITH_RC4_40_MD5",
                                                     "TLS_RSA_WITH_RC4_128_MD5",
                                                     "TLS_RSA_WITH_RC4_128_SHA",
                                                     "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5",
                                                     "TLS_RSA_WITH_IDEA_CBC_SHA",
                                                     "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA",
                                                     "TLS_RSA_WITH_DES_CBC_SHA",
                                                     "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
                                                     "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA",
                                                     "TLS_DH_DSS_WITH_DES_CBC_SHA",
                                                     "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA",
                                                     "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA",
                                                     "TLS_DH_RSA_WITH_DES_CBC_SHA",
                                                     "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA",
                                                     "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
                                                     "TLS_DHE_DSS_WITH_DES_CBC_SHA",
                                                     "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
                                                     "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
                                                     "TLS_DHE_RSA_WITH_DES_CBC_SHA",
                                                     "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
                                                     "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5",
                                                     "TLS_DH_anon_WITH_RC4_128_MD5",
                                                     "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
                                                     "TLS_DH_anon_WITH_DES_CBC_SHA",
                                                     "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA",
                                                     null,
                                                     null,
                                                     "TLS_KRB5_WITH_DES_CBC_SHA",
                                                     "TLS_KRB5_WITH_3DES_EDE_CBC_SHA",
                                                     "TLS_KRB5_WITH_RC4_128_SHA",
                                                     "TLS_KRB5_WITH_IDEA_CBC_SHA",
                                                     "TLS_KRB5_WITH_DES_CBC_MD5",
                                                     "TLS_KRB5_WITH_3DES_EDE_CBC_MD5",
                                                     "TLS_KRB5_WITH_RC4_128_MD5",
                                                     "TLS_KRB5_WITH_IDEA_CBC_MD5",
                                                     "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA",
                                                     "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA",
                                                     "TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
                                                     "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5",
                                                     "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5",
                                                     "TLS_KRB5_EXPORT_WITH_RC4_40_MD5",
                                                     "TLS_PSK_WITH_NULL_SHA",
                                                     "TLS_DHE_PSK_WITH_NULL_SHA",
                                                     "TLS_RSA_PSK_WITH_NULL_SHA",
                                                     "TLS_RSA_WITH_AES_128_CBC_SHA",
                                                     "TLS_DH_DSS_WITH_AES_128_CBC_SHA",
                                                     "TLS_DH_RSA_WITH_AES_128_CBC_SHA",
                                                     "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
                                                     "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
                                                     "TLS_DH_anon_WITH_AES_128_CBC_SHA",
                                                     "TLS_RSA_WITH_AES_256_CBC_SHA",
                                                     "TLS_DH_DSS_WITH_AES_256_CBC_SHA",
                                                     "TLS_DH_RSA_WITH_AES_256_CBC_SHA",
                                                     "TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
                                                     "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
                                                     "TLS_DH_anon_WITH_AES_256_CBC_SHA"
                                                     };

  // {0x00 0x62} - {0x00 0x66}
  private static final String[] TLS_EXPORTABLE_CIPHER_SUITES = {
                                                                "TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA",
                                                                "TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA",
                                                                "TLS_RSA_EXPORT1024_WITH_RC4_56_SHA",
                                                                "TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA",
                                                                "TLS_DHE_DSS_WITH_RC4_128_SHA", };

  /**
   * アプリケーションバッファサイズ　(RFC2246)
   */
  private int applicationBufferSize = (1 << 14);

  /**
   * ネットワークパケットバッファサイズ (RFC2246)
   */
  private int packetBufferSize = (1 << 14) + 2048;

  /**
   * 暗号化方式群 (Cipher suite)
   */
  private short cipherSuiteCode;

  /**
   * セッションID
   */
  private byte[] sessionID;

  /**
   * プロトコルバージョン
   */
  private short protocolVersion;

  /**
   * セッションが作成された時間
   */
  private long creationTime;

  /**
   * 自分側の証明書チェーン
   */
  private WCECertificate.Chain localCertificateChain;
  
  /**
   * 相手側証明書チェーン
   */
  private WCECertificate.Chain peerCertificateChain;

  /**
   * アプリケーション層のデータ
   * @see       #putValue(String,Object)
   * @see       #getValue(String)
   */
  private final Map<ProtectedName, Object> applicationData = new HashMap<ProtectedName, Object>();
  
  @Override
  public byte[] getId()
  {
    if (this.sessionID != null)
      {
        return (byte[]) this.sessionID.clone();
      }
    return null;
  }

  @Override
  public SSLSessionContext getSessionContext()
  {
    // Not supported.
    return null;
  }

  @Override
  public long getCreationTime()
  {
    return this.creationTime;
  }

  @Override
  public long getLastAccessedTime()
  {
    return getCreationTime();
  }

  @Override
  public void invalidate()
  {
    // Do nothing
  }

  @Override
  public boolean isValid()
  {
    // ToDo: implement
    return true;
  }

  @Override
  public void putValue(String name, Object value)
  {
    if (name == null || value == null)
      {
        throw new IllegalArgumentException("null arguments");
      }
    Object old = this.applicationData.put(new ProtectedName(name), value);
    if (old instanceof SSLSessionBindingListener)
      {
        ((SSLSessionBindingListener) old).valueUnbound(new SSLSessionBindingEvent(this, name));
      }
    if (value instanceof SSLSessionBindingListener)
      {
        ((SSLSessionBindingListener) value).valueBound(new SSLSessionBindingEvent(this, name));
      }
  }

  @Override
  public Object getValue(String name)
  {
    if (name == null)
      {
        throw new IllegalArgumentException("name==null");
      }
    return this.applicationData.get(new ProtectedName(name));
  }

  @Override
  public void removeValue(String name)
  {
    if (name == null)
      {
        throw new IllegalArgumentException("name==null");
      }
    Object old = this.applicationData.remove(new ProtectedName(name));
    if (old instanceof SSLSessionBindingListener)
      {
        ((SSLSessionBindingListener) old).valueUnbound(new SSLSessionBindingEvent(this, name));
      }
  }

  @Override
  public String[] getValueNames()
  {
    throw new UnsupportedOperationException("Not supported");
  }

  @Override
  public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException
  {
    Certificate[] result = null;
    try
      {
        if (this.peerCertificateChain != null)
          {
            result = this.peerCertificateChain.getCertificates();
          }
      }
    catch (CertificateException ce)
      {
        SSLPeerUnverifiedException e = new SSLPeerUnverifiedException(
                                                                      "Failed to create WCECertificate instance");
        e.initCause(ce);
        throw e;
      }
    catch (IOException ioe)
      {
        SSLPeerUnverifiedException e = new SSLPeerUnverifiedException(
                                                                      "Failed to create WCECertificate instance");
        e.initCause(ioe);
        throw e;

      }
    return result;
  }

  @Override
  public Certificate[] getLocalCertificates()
  {
    Certificate[] result = null;
    try
      {
        if (this.localCertificateChain != null)
          {
            result = this.localCertificateChain.getCertificates();
          }
      }
    catch (CertificateException ce)
      {
        ce.printStackTrace();
      }
    catch (IOException ioe)
      {
        ioe.printStackTrace();
      }
    return result;
  }

  @Override
  public javax.security.cert.X509Certificate[] getPeerCertificateChain()
      throws SSLPeerUnverifiedException
  {
    throw new UnsupportedOperationException("Not supported");
  }

  @Override
  public Principal getPeerPrincipal() throws SSLPeerUnverifiedException
  {
    throw new UnsupportedOperationException("Not supported");
  }

  @Override
  public Principal getLocalPrincipal()
  {
    throw new UnsupportedOperationException("Not supported");
  }

  @Override
  public String getCipherSuite()
  {
    if (cipherSuiteCode < 0)
      {
        throw new IllegalStateException("cipherSuiteCode=" + cipherSuiteCode);
      }
    String result = null;
    if (this.protocolVersion <= 0x0300)
      {
        if (cipherSuiteCode < SSL_CIPHER_SUITES.length)
          {
            result = SSL_CIPHER_SUITES[cipherSuiteCode];
          }
        else if (cipherSuiteCode >= 0x0062
                 && (cipherSuiteCode - 0x0062) < SSL_EXPORTABLE_CIPHER_SUITES.length)
          {
            result = SSL_EXPORTABLE_CIPHER_SUITES[cipherSuiteCode - 0x62];
          }
        if (result == null)
          {
            result = "SSL_UNKNOWN_CIPHER_SUITE("
                     + Integer.toHexString(cipherSuiteCode) + ")";
          }
      }
    else
      {
        if (cipherSuiteCode < TLS_CIPHER_SUITES.length)
          {
            result = TLS_CIPHER_SUITES[cipherSuiteCode];
          }
        else if (cipherSuiteCode >= 0x0062
                 && (cipherSuiteCode - 0x0062) < TLS_EXPORTABLE_CIPHER_SUITES.length)
          {
            result = TLS_EXPORTABLE_CIPHER_SUITES[cipherSuiteCode - 0x0062];
          }
        if (result == null)
          {
            result = "TLS_UNKNOWN_CIPHER_SUITE("
                     + Integer.toHexString(cipherSuiteCode) + ")";
          }
      }
    return result;
  }

  @Override
  public String getProtocol()
  {
    if (this.protocolVersion == 0x0300)
      {
        return "SSLv3";
      }
    else if (this.protocolVersion == 0x0301)
      {
        return "TLSv1";
      }
    else
      {
        return "UNKNOWN PROTOCOL(" + Integer.toHexString(this.protocolVersion)
               + ")";
      }
  }

  @Override
  public String getPeerHost()
  {
    throw new UnsupportedOperationException("Not supported");
  }

  @Override
  public int getPeerPort()
  {
    throw new UnsupportedOperationException("Not supported");
  }

  @Override
  public int getPacketBufferSize()
  {
    return this.packetBufferSize;
  }

  /**
   * アプリケーションバッファサイズを設定する。
   * WCESSLEngine.updateNativeSession()から呼び出される。
   */
  void setPacketBufferSize(int size)
  {
    this.packetBufferSize = size;
  }

  @Override
  public int getApplicationBufferSize()
  {
    return this.applicationBufferSize;
  }

  /**
   * アプリケーションバッファサイズを設定する。
   * WCESSLEngine.updateNativeSession()から呼び出される。
   */
  void setApplicationBufferSize(int size)
  {
    this.applicationBufferSize = size;
  }
  
  /**
   * Server helloの内容を設定する。 ネイティブメソッド側で server hello を処理した時点で呼び出される
   */
  void setServerHelloData(short protocolVersion, byte[] sessionID,
                          short cipherSuiteCode)
  {
    this.protocolVersion = protocolVersion;
    this.sessionID = sessionID; // ネイティブ側でこの配列の内容は書き換えられないので、clone()は不要
    this.cipherSuiteCode = cipherSuiteCode;
  }

  /**
   * 自局側の証明書と、検証結果を設定する。 ネイティブメソッドから設定される
   */
  void setEncodedLocalCertificates(byte[][] encodedCertificates,
                                  int chainError, int chainInfo)
  {
    this.localCertificateChain = new WCECertificate.Chain(null, null, encodedCertificates, chainError, chainInfo);
  }

  /**
   * 相手側の証明書と、検証結果を設定する。 ネイティブメソッドから設定される
   */
  void setEncodedPeerCertificates(byte[][] encodedCertificates,
                                  int chainError, int chainInfo)
  {
    this.peerCertificateChain = new WCECertificate.Chain(null, null, encodedCertificates, chainError, chainInfo);
  }

  /**
   * 自局側証明書が設定されているかを返す。
   * ネイティブメソッド側から呼び出される
   */
  boolean hasLocalCertificate()
  {
    return this.localCertificateChain != null;
  }

  /**
   * 相手側証明書が存在するかを返す。
   * ネイティブメソッド側から呼び出される
   */
  boolean hasPeerCertificate()
  {
    return this.peerCertificateChain != null;
  }

  /**
   * 異なる<code>AccessControlContext</code>間でputValue(String, Object)した
   * 情報が共有されないように、putValue(String, Object)で渡された情報をそのまま保持するのではなく、
   * AccessoControlContextとペアにして保持しておく
   */
  private static class ProtectedName
  {
    /**
     * putValue()に渡された名前
     */
    private String name;
    
    /**
     * putValue()が呼び出された時点におけるSecurityContext
     */
    private AccessControlContext context;
    
    ProtectedName(String name)
    {
      this.name = name;
      SecurityManager sm = System.getSecurityManager();
      if (sm != null)
        {
          this.context = AccessController.getContext();
        }
    }

    String getName()
    {
      return getName();
    }

    @Override
    public int hashCode()
    {
      int result = this.name.hashCode();
      if (context != null)
        {
          result ^= context.hashCode();
        }
      return result;
    }

    @Override
    public boolean equals(Object o)
    {
      if (o instanceof ProtectedName)
        {
          ProtectedName pn = (ProtectedName) o;
          if (this.name.equals(pn.name))
            {
              if (this.context != null)
                {
                  return this.context.equals(pn.context);
                }
              else
                {
                  return pn.context == null;
                }
            }
        }
      return false;
    }
  }
}
