/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2008-2010 Sun Microsystems, Inc.
 *      Portions Copyright 2014-2015 ForgeRock AS
 */
package org.opends.server.tools;
import org.forgerock.i18n.LocalizableMessage;



import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import static org.opends.messages.ToolMessages.*;



/**
 * This class provides an implementation of an X.509 trust manager which will
 * interactively prompt the user (via the CLI) whether a given certificate
 * should be trusted.  It should only be used by interactive command-line tools,
 * since it will block until it gets a response from the user.
 * <BR><BR>
 * Note that this class is only intended for client-side use, and therefore may
 * not be used by a server to determine whether a client certificate is trusted.
 */
public class PromptTrustManager
       implements X509TrustManager
{



  /** The singleton trust manager array for this class. */
  private static TrustManager[] trustManagerArray =
       new TrustManager[] { new PromptTrustManager() };



  /**
   * Creates a new instance of this prompt trust manager.
   */
  private PromptTrustManager()
  {
    // No implementation is required.
  }



  /**
   * Retrieves the trust manager array that should be used to initialize an SSL
   * context in cases where the user should be interactively prompted about
   * whether to trust the server certificate.
   *
   * @return  The trust manager array that should be used to initialize an SSL
   *          context in cases where the user should be interactively prompted
   *          about whether to trust the server certificate.
   */
  public static TrustManager[] getTrustManagers()
  {
    return trustManagerArray;
  }



  /**
   * Determines whether an SSL client with the provided certificate chain should
   * be trusted.  This implementation is not intended for server-side use, and
   * therefore this method will always throw an exception.
   *
   * @param  chain     The certificate chain for the SSL client.
   * @param  authType  The authentication type based on the client certificate.
   *
   * @throws  CertificateException  To indicate that the provided client
   *                                certificate is not trusted.
   */
  public void checkClientTrusted(X509Certificate[] chain, String authType)
         throws CertificateException
  {
    LocalizableMessage message = ERR_PROMPTTM_REJECTING_CLIENT_CERT.get();
    throw new CertificateException(message.toString());
  }



  /**
   * Determines whether an SSL server with the provided certificate chain should
   * be trusted.  In this case, the user will be interactively prompted as to
   * whether the certificate should be trusted.
   *
   * @param  chain     The certificate chain for the SSL server.
   * @param  authType  The key exchange algorithm used.
   *
   * @throws  CertificateException  If the user rejects the certificate.
   */
  public void checkServerTrusted(X509Certificate[] chain, String authType)
         throws CertificateException
  {
    if (chain == null || chain.length == 0)
    {
      System.out.println(WARN_PROMPTTM_NO_SERVER_CERT_CHAIN.get());
    }
    else
    {
      Date currentDate   = new Date();
      Date notAfterDate  = chain[0].getNotAfter();
      Date notBeforeDate = chain[0].getNotBefore();

      if (currentDate.after(notAfterDate))
      {
        System.err.println(WARN_PROMPTTM_CERT_EXPIRED.get(notAfterDate));
      }
      else if (currentDate.before(notBeforeDate))
      {
        System.err.println(WARN_PROMPTTM_CERT_NOT_YET_VALID.get(notBeforeDate));
      }

      System.out.println(INFO_PROMPTTM_SERVER_CERT.get(
              chain[0].getSubjectDN().getName(),
              chain[0].getIssuerDN().getName(),
              notBeforeDate,
              notAfterDate));
    }


    LocalizableMessage prompt = INFO_PROMPTTM_YESNO_PROMPT.get();
    BufferedReader reader =
         new BufferedReader(new InputStreamReader(System.in));
    while (true)
    {
      try
      {
        System.out.print(prompt);
        String line = reader.readLine().toLowerCase();
        if (line.equalsIgnoreCase(
            INFO_PROMPT_YES_COMPLETE_ANSWER.get().toString()) ||
            line.equalsIgnoreCase(
            INFO_PROMPT_YES_FIRST_LETTER_ANSWER.get().toString()))
        {
          // Returning without an exception is sufficient to consider the
          // certificate trusted.
          return;
        }
        if (line.equalsIgnoreCase(
            INFO_PROMPT_NO_COMPLETE_ANSWER.get().toString()) ||
            line.equalsIgnoreCase(
            INFO_PROMPT_NO_FIRST_LETTER_ANSWER.get().toString()))
        {
          LocalizableMessage message = ERR_PROMPTTM_USER_REJECTED.get();
          throw new CertificateException(message.toString());
        }
      } catch (IOException ioe) {}

      System.out.println();
    }
  }



  /**
   * Retrieves the set of certificate authority certificates which are trusted
   * for authenticating peers.
   *
   * @return  An empty array, since we don't care what certificates are
   *          presented because we will always prompt the user.
   */
  public X509Certificate[] getAcceptedIssuers()
  {
    return new X509Certificate[0];
  }
}

