/* WCESSLEngine.java -- implementation of SSLEngine.
   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.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.DataFormatException;

import javax.crypto.IllegalBlockSizeException;
import javax.crypto.ShortBufferException;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509TrustManager;

/**
 * SSLEngine implementation for Windows CE device.
 */
public final class WCESSLEngine
    extends SSLEngine
{
  static
    {
      WCESecurity.loadLibraries();
      initIDs();
    }

  // サポートするプロトコル名
  // http://java.sun.com/javase/ja/6/docs/ja/technotes/guides/security/StandardNames.html

  /**
   * SSL Version 3.
   */
  static final String SSL_V3 = "SSLv3";

  /**
   * TLS Version 1.0.
   */
  static final String TLS_V1 = "TLSv1";

  /**
   * SSL Version 3とTLS Version1.0の2つをサポートする
   */
  static final String[] SUPPORTED_PROTOCOLS = new String[] { SSL_V3, TLS_V1 };

  /**
   * セッション
   */
  private final WCESSLSession session;

  /**
   * SSLContextSpi.
   */
  final WCESSLContextSpi contextSpi;

  /**
   * SSLSocket.
   */
  private final WCESSLSocket socket;

  /**
   * 有効になっているプロトコル
   */
  private String[] enabledProtocols = (String[]) SUPPORTED_PROTOCOLS.clone();

  /**
   * ネイティブライブラリ側で参照する値（ポインタ値） ハンドシェーク開始時点で初期化される
   */
  private int nativePointer;

  /**
   * AccessControlContext. このオブジェクトが生成された時点のコンテキストを保持している
   */
  private AccessControlContext accessControlContext = AccessController.getContext();

  /**
   * ハンドシェークステータス
   */
  private HandshakeStatus handshakeStatus = HandshakeStatus.NOT_HANDSHAKING;

  /**
   * ハンドシェイクTaskが投げたException
   */
  private Exception taskException;

  /**
   * 初期ハンドシェイクを開始したか
   */
  private boolean initialHandshakeHasBegun;

  /**
   * Package private constructor.
   * 
   * @param contextSpi WCESSLContextSpi
   * @param host Peer host name.
   * @param port Peer port number.
   */
  WCESSLEngine(WCESSLContextSpi contextSpi, WCESSLSocket socket, String host,
               int port)
  {
    super(host, port);
    this.socket = socket;
    this.contextSpi = contextSpi;
    this.session = new WCESSLSession();
    this.nativePointer = openNative(getPeerHost());
    if (this.nativePointer == 0)
      {
        throw new NullPointerException("Failed to create SSLEngine");
      }
  }

  /**
   * Initialize native library.
   */
  private static native void initIDs();

  /**
   * Open native resource.
   * 
   * @param host Peer host name.
   */
  private native int openNative(String host);

  /**
   * クローズする (パッケージプライベートメソッド)
   */
  synchronized void close()
  {
    if (this.nativePointer != 0)
      {
        closeNative(this.nativePointer);
        this.nativePointer = 0;
      }
  }

  /**
   * Close native resource.
   * 
   * @param nativePointer Native resource pointer.
   */
  private native void closeNative(int nativePointer);

  /**
   * Begin native handshake.
   * 
   * @param nativePointer Native resource pointer
   */
  private native void beginNativeHandshake(int nativePointer)
      throws SSLException;

  @Override
  public void beginHandshake() throws SSLException
  {
    if (initialHandshakeHasBegun)
      {
        return;
      }

    if (this.nativePointer == 0)
      {
        throw new SSLException("closed");
      }

    try
      {
        // ToDo: 相手側の鍵交換アルゴリズムに応じて、
        // chooserServerAlias()/chooseClientAlias()の引数を変更できるようにする
        X509ExtendedKeyManager km = contextSpi.getKeyManager();
        String[] keyTypes = { "RSA" };
        if (km != null)
          {
            String alias = null;
            if (getUseClientMode())
              {
                alias = km.chooseClientAlias(keyTypes, null, this.socket);
              }
            else
              {
                alias = km.chooseServerAlias(keyTypes[0], null, this.socket);
              }

            X509Certificate[] chain = km.getCertificateChain(alias);
            if (chain != null && chain.length > 0 && chain[0] != null)
              {
                if (chain[0] instanceof WCECertificate)
                  {
                    setNativeLocalCertificate(
                                              this.nativePointer,
                                              ((WCECertificate) chain[0]).getCertContext());
                  }
                else
                  {
                    // 現在の実装では、WCECertificate以外をサポートしていない
                    System.err.println("WARNING:Unsupported certificate class:"
                                       + chain[0].getClass().getName());
                  }
              }
          }
      }
    catch (CertificateException ce)
      {
        throw new SSLException(ce);
      }

    if (this.handshakeStatus == HandshakeStatus.NOT_HANDSHAKING)
      {
        beginNativeHandshake(this.nativePointer);
      }
    this.initialHandshakeHasBegun = true;
  }

  @Override
  public void closeInbound()
  {
    closeNativeInbound(this.nativePointer);
  }

  /**
   * 入力をクローズする
   */
  private native void closeNativeInbound(int nativePointer);

  @Override
  public void closeOutbound()
  {
    closeNativeOutbound(this.nativePointer);
  }

  /**
   * 出力をクローズする
   */
  private native void closeNativeOutbound(int nativePointer);

  /**
   * ネイティブタスクが存在するかを返す。
   * 
   * @param nativePointer ネイティブポインタ
   */
  private native boolean hasNativeTask(int nativePointer);

  private native void executeNativeTask(int nativePointer) throws Exception;

  @Override
  public Runnable getDelegatedTask()
  {
    if (this.nativePointer == 0)
      {
        return null;
      }
    if (hasNativeTask(this.nativePointer))
      {
        return new Runnable()
        {
          public void run()
          {
            try
              {
                // 証明書の検証を行う
                AccessController.doPrivileged(new CertificatesValidationTask());
              }
            catch (PrivilegedActionException ae)
              {
                taskException = ae.getException();
              }
            catch (RuntimeException re)
              {
                taskException = re;
              }
          }
        };
      }
    else
      {
        return null;
      }
  }

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

  @Override
  public String[] getEnabledProtocols()
  {
    return (String[]) this.enabledProtocols.clone();
  }

  @Override
  public boolean getEnableSessionCreation()
  {
    throw new UnsupportedOperationException("Not implemented");
  }

  private native HandshakeStatus getNativeHandshakeStatus(int nativePointer);

  @Override
  public HandshakeStatus getHandshakeStatus()
  {
    if (this.nativePointer == 0)
      {
        return HandshakeStatus.NOT_HANDSHAKING;
      }
    return getNativeHandshakeStatus(this.nativePointer);
  }

  @Override
  public boolean getNeedClientAuth()
  {
    return getNativeNeedClientAuth(this.nativePointer);
  }

  private native boolean getNativeNeedClientAuth(int nativePointer);

  @Override
  public SSLSession getSession()
  {
    if (this.nativePointer != 0)
      {
        updateNativeSession(this.nativePointer, this.session);
      }
    return this.session;
  }

  private native void updateNativeSession(int nativePointer,
                                          WCESSLSession session);

  @Override
  public boolean getUseClientMode()
  {
    return getNativeUseClientMode(this.nativePointer);
  }

  private native boolean getNativeUseClientMode(int nativePointer);

  @Override
  public boolean getWantClientAuth()
  {
    return getNativeWantClientAuth(this.nativePointer);
  }

  private native boolean getNativeWantClientAuth(int nativePointer);

  @Override
  public boolean isInboundDone()
  {
    return isNativeInboundDone(this.nativePointer);
  }

  /**
   * 入力が閉じているかを返す
   */
  private native boolean isNativeInboundDone(int nativePointer);

  @Override
  public boolean isOutboundDone()
  {
    return isNativeOutboundDone(this.nativePointer);
  }

  /**
   * 出力が閉じているかを返す。
   */
  private native boolean isNativeOutboundDone(int nativePointer);

  @Override
  public void setEnableSessionCreation(boolean createSessions)
  {
    // Do nothing.
  }

  @Override
  public void setEnabledCipherSuites(String[] suites)
  {
    // ToDo: Implement
  }

  @Override
  public void setEnabledProtocols(String[] protocols)
  {
    if (protocols == null)
      {
        throw new IllegalArgumentException("protocols==null");
      }
    for (int i = 0; i < protocols.length; ++i)
      {
        String p = protocols[i];
        boolean found = false;
        for (int j = 0; j < SUPPORTED_PROTOCOLS.length; ++j)
          {
            if (SUPPORTED_PROTOCOLS[j].equals(p))
              {
                found = true;
                break;
              }
          }
        if (! found)
          {
            throw new IllegalArgumentException("Protocol " + p
                                               + " not supported");
          }
      }
    this.enabledProtocols = (String[]) protocols.clone();
    setNativeEnabledProtocols(this.nativePointer, this.enabledProtocols);
  }

  private native void setNativeEnabledProtocols(int nativePointer,
                                                String[] protocols);

  @Override
  public String[] getSupportedCipherSuites()
  {
    // ToDo: implement
    return new String[0];
  }

  @Override
  public String[] getSupportedProtocols()
  {
    return (String[]) SUPPORTED_PROTOCOLS.clone();
  }

  @Override
  public void setNeedClientAuth(boolean needClientAuth)
  {
    setNativeNeedClientAuth(this.nativePointer, needClientAuth);
  }

  private native void setNativeNeedClientAuth(int nativePointer,
                                              boolean needClientAuth);

  @Override
  public void setUseClientMode(boolean clientMode)
  {
    setNativeUseClientMode(this.nativePointer, clientMode);
  }

  /**
   * クライアントモードを設定する。
   */
  private native void setNativeUseClientMode(int nativePointer,
                                             boolean clientMode);

  public @Override
  void setWantClientAuth(boolean wantClientAuth)
  {
    setNativeWantClientAuth(this.nativePointer, wantClientAuth);
  }

  private native void setNativeWantClientAuth(int nativePointer,
                                              boolean wantClientAuth);

  private native SSLEngineResult wrapNative(int nativePointer,
                                            ByteBuffer[] srcs, int offset,
                                            int length, ByteBuffer dst)
      throws SSLException;

  public SSLEngineResult wrap(ByteBuffer[] srcs, int offset, int length,
                              ByteBuffer dst) throws SSLException
  {
    checkTaskException();
    if (! this.initialHandshakeHasBegun)
      {
        beginHandshake();
      }
    return wrapNative(this.nativePointer, srcs, offset, length, dst);
  }

  private native int shutdownNative(int nativePointer, ByteBuffer dst)
      throws SSLException;

  private native SSLEngineResult unwrapNative(int nativePointer,
                                              ByteBuffer source,
                                              ByteBuffer[] sinks, int offset,
                                              int length) throws SSLException;

  @Override
  public SSLEngineResult unwrap(ByteBuffer source, ByteBuffer[] sinks,
                                int offset, int length) throws SSLException
  {
    checkTaskException();
    if (! this.initialHandshakeHasBegun)
      {
        beginHandshake();
      }
    return unwrapNative(this.nativePointer, source, sinks, offset, length);
  }

  /**
   * {@link #getDelegatedTask()}の実行により発生した例外をチェックする。 もし例外が発生していたら、その例外をthrowする。
   * 必要に応じて、SSLExceptionへのキャストも行う
   */
  private void checkTaskException() throws SSLException
  {
    if (this.taskException != null)
      {
        Exception e = this.taskException;
        this.taskException = null;
        throw new SSLException(e);
      }
  }

  /**
   * デフォルトの暗号化群を返す WCESSLServerSocket から呼び出される
   */
  static String[] getDefaultCipherSuites()
  {
    // ToDo: implement
    return new String[0];
  }

  /**
   * 自局側の証明書を設定する
   */
  private native void setNativeLocalCertificate(int nativePointer,
                                                int certContext);

  @Override
  protected void finalize()
  {
    close();
  }

  /**
   * 証明書の検証を行うタスク
   */
  private class CertificatesValidationTask
      implements PrivilegedExceptionAction<Void>
  {
    public Void run() throws Exception
    {
      executeNativeTask(nativePointer);
      // 現在の実装では、ネイティブ側で証明書情報を取得／検証する処理のみが行われている
      // 取得した証明書をTrustManagerに渡す
      SSLSession s = getSession();
      X509TrustManager tm = contextSpi.getTrustManager();
      X509Certificate[] chain = (X509Certificate[]) s.getPeerCertificates();
      if (getUseClientMode())
        {
          // サーバ証明書の検証
          tm.checkServerTrusted(chain, "RSA");
        }
      else if (getNeedClientAuth() || (chain != null && getWantClientAuth()))
        {
          // クライアント認証
          tm.checkClientTrusted(chain, "RSA");
        }
      return null;
    }
  }
}
