/* ***************************************************************** *
 * Copyright 1998 International Business Machines Corporation. All   *
 * Rights Reserved.                                                  *
 *                                                                   *
 * Please read this carefully.  Your use of this reference           *
 * implementation of certain of the IETF public-key infrastructure   *
 * specifications ("Software") indicates your acceptance of the      *
 * following.  If you do not agree to the following, do not install  *
 * or use any of the Software.                                       *
 *                                                                   *
 * Permission to use, reproduce, distribute and create derivative    *
 * works from the Software ("Software Derivative Works"), and to     *
 * distribute such Software Derivative Works is hereby granted to    *
 * you by International Business Machines Corporation ("IBM").  This *
 * permission includes a license under the patents of IBM that are   *
 * necessarily infringed by your use of the Software as provided by  *
 * IBM.                                                              *
 *                                                                   *
 * IBM licenses the Software to you on an "AS IS" basis, without     *
 * warranty of any kind.  IBM HEREBY EXPRESSLY DISCLAIMS ALL         *
 * WARRANTIES OR CONDITIONS, EITHER EXPRESS OR IMPLIED, INCLUDING,   *
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OR CONDITIONS OF       *
 * MERCHANTABILITY, NON INFRINGEMENT AND FITNESS FOR A PARTICULAR    *
 * PURPOSE.  You are solely responsible for determining the          *
 * appropriateness of using this Software and assume all risks       *
 * associated with the use of this Software, including but not       *
 * limited to the risks of program errors, damage to or loss of      *
 * data, programs or equipment, and unavailability or interruption   *
 * of operations.                                                    *
 *                                                                   *
 * IBM WILL NOT BE LIABLE FOR ANY DIRECT DAMAGES OR FOR ANY SPECIAL, *
 * INCIDENTAL, OR  INDIRECT DAMAGES OR FOR ANY ECONOMIC              *
 * CONSEQUENTIAL DAMAGES (INCLUDING LOST PROFITS OR SAVINGS), EVEN   *
 * IF IBM HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.  IBM  *
 * will not be liable for the loss of, or damage to, your records or *
 * data, or any damages claimed by you based on a third party claim. *
 *                                                                   *
 * IBM wishes to obtain your feedback to assist in improving the     *
 * Software.  You grant IBM a world-wide, royalty-free right to use, *
 * copy, distribute, sublicense and prepare derivative works based   *
 * upon any feedback, including materials, error corrections,        *
 * Software Derivatives, enhancements, suggestions and the like that *
 * you provide to IBM relating to the Software (this does not        *
 * include products for which you charge a royalty and distribute to *
 * IBM under other terms and conditions).                            *
 *                                                                   *
 * You agree to distribute the Software and any Software Derivatives *
 * under a license agreement that: 1) is sufficient to notify all    *
 * licensees of the Software and Software Derivatives that IBM       *
 * assumes no liability for any claim that may arise regarding the   *
 * Software or Software Derivatives, and 2) that disclaims all       *
 * warranties, both express and implied, from IBM regarding the      *
 * Software and Software Derivatives.  (If you include this          *
 * Agreement with any distribution of the Software or Software       *
 * Derivatives you will have met this requirement.)  You agree that  *
 * you will not delete any copyright notices in the Software.        *
 *                                                                   *
 * This Agreement is the exclusive statement of your rights in the   *
 * Software as provided by IBM.   Except for the rights granted to   *
 * you in the second paragraph above, You are not granted any other  *
 * patent rights, including but not limited to the right to make     *
 * combinations of the Software with products that infringe IBM      *
 * patents. You agree to comply with all applicable laws and         *
 * regulations, including all export and import laws and regulation. *
 * This Agreement is governed by the laws of the State of New York.  *
 * This Agreement supersedes all other communications,               *
 * understandings or agreements we may have had prior to this        *
 * Agreement.                                                        *
 * ***************************************************************** */

#include <platform.h>
#include <x509.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <Init.h>
#include <cssm.h>
#include <JonahIni.h>
#include <jkl.h>
#include <scfunc.h>
#include <constants.h>

void usage_error(void) {
  fprintf(stderr, "Usage: scinit\n");
  fprintf(stderr, "Usage: scinit -C [DSA | RSA]\n");
  fprintf(stderr, "       scinit -R [DSA | RSA]\n\n");
  exit(EXIT_FAILURE);
}

int main(int argc, char * argv[]) {

  const char * scFilename;
  const char * scPrincipal;
  const char * scAlgName;
  int istatus;
  uint32 status;
  int argp = 1;
  int server_type = Init_EE;
  char initsopin[32];
  char tokenName[128];
  char userpin[32];
  char sopin[32];
  char filename[512];
  CSSM_KEY public_key;
  CSSM_KEY private_key;
#define ALG_RSA 1
#define ALG_DSA 2
  int scAlg;
  char scKeyAlgIdent[32];
  char scSigAlgIdent[32];
  x509_certificate cert;
  buffer_t alg_params;
  time_t now;
  struct tm now_tm;
  struct tm then_tm;
  buffer_t tbs_data;
  CSSM_DATA tbs;
  CSSM_DATA sig;
  buffer_t sig_alg;
  buffer_t cert_buffer;
  r_buffer_t serial_buffer;
  buffer_t private_key_buffer;
  r_buffer_t principal_buffer;
  unsigned char * serPtr;
  size_t serLen;  
  int c;
  FILE * cert_file;
  CSSM_CSP_HANDLE cylink_handle;
  CSSM_GUID cylink_guid;
  CSSM_ALGORITHMS key_cssm_alg;
  CSSM_ALGORITHMS sig_cssm_alg;
  char principalName[1024];
  bool usage = false;
  uint32 version;
  char line[256];
  XSubjectKeyIdentifier gorp;
  buffer_t gorp_buf;

  buffer_t temp_buffer;

  x509_Extension * ext;

  SubjectPublicKeyInfo subjectPublicKeyInfo;
  PrivateKeyInfo privateKeyInfo;

  buffer_t res_serial_buffer;
  buffer_t res_private_key_buffer;
  buffer_t res_cert_buffer;

  asn_bitstring signature;

  if ((argc == 2) && (strcmp(argv[1], "?") == 0)) {
    fprintf(stdout, 
            "This program initializes a virtual smart-card.  For a CA or RA\n");
    fprintf(stdout, 
            "smart-card, a key and a self-signed certificate are generated for\n");
    fprintf(stdout,
            "the specified CA/RA principal.\n\n");
    fprintf(stdout, "Usage: scinit\n");
    fprintf(stdout, "    or scinit -C [<alg>]\n");
    fprintf(stdout, "    or scinit -R [<alg>]\n\n");
    fprintf(stdout, "where <alg> is one of DSA or RSA (DSA is the default).\n");
    fprintf(stdout, "      -C/-R select the CA or RA smart-cards.\n\n");
    return EXIT_SUCCESS;
  };
  if (argc > 3) {
    usage_error();
  };

  if ((argc > 1) && (argv[1][0] == '-')) {
    argp = 2;
    if (argv[1][1] == 'R') server_type = Init_RA;
    else if (argv[1][1] == 'C') server_type = Init_CA;
    else usage_error();
  } else {
    if (argc > 1) usage_error();   
    server_type = Init_EE;
  };


  if (argc > argp) scAlgName = argv[argp];
  else scAlgName = "DSA";

  if (strcmp(scAlgName, "RSA") == 0) {
    scAlg = ALG_RSA;
    strcpy(scKeyAlgIdent, "rsaEncryption");
    strcpy(scSigAlgIdent, "sha-1WithRSAEncryption");
    key_cssm_alg = CSSM_ALGID_RSA;
    sig_cssm_alg = CSSM_ALGID_SHA1WithRSA;
  } else if (strcmp(scAlgName, "DSA") == 0) {
    scAlg = ALG_DSA;
    strcpy(scKeyAlgIdent, "id-dsa");
    strcpy(scSigAlgIdent, "id-dsa-with-sha1");
    key_cssm_alg = CSSM_ALGID_DSA;
    sig_cssm_alg = CSSM_ALGID_SHA1WithDSA;
  } else {
    scAlg = 0;
    fprintf(stderr, "%s is not a supported algorithm.\n", scAlgName);
    fprintf(stderr, "Valid choices are: DSA, RSA\n\n");
    return EXIT_FAILURE;
  };


  fprintf(stdout, "Initializing Jonah...\n"); 
  fflush(stdout);
  Init(server_type, false);
  fprintf(stdout, "Jonah initialization complete\n"); 
  fflush(stdout);


  if (server_type != Init_EE) {
    if (!IniReadString("General",
                       "MyName",
                       principalName,
                       sizeof(principalName),
                       "NoName")) {
      fprintf(stderr, "Key \"MyName\" not found in \"General\" section of ini-file\n");
      return EXIT_FAILURE;
    };
    scPrincipal = &principalName[0];
  };

  if (!IniReadString("VSC",
                     "InitialSOpw",
                     initsopin,
                     sizeof(initsopin),
                     "NoPwd")) {
    fprintf(stderr, "Key \"InitialSOpw\" not found in \"VSC\" section of ini-file\n");
    return EXIT_FAILURE;
  };

  if (!IniReadString("VSC",
                     "TokenDir",
                     tokenName,
                     sizeof(tokenName),
                     "<None>")) {
    fprintf(stderr, "Key \"TokenDir\" not found in \"VSC\" section of ini-file\n");
    return EXIT_FAILURE;
  };

  if (server_type == Init_EE) {
    fprintf(stdout, "Initializing smart-card %s\n", tokenName);
  } else {
    fprintf(stdout, "Creating credentials for \"%s\" in card %s\n", scPrincipal, tokenName);
  };
  fflush(stdout);

  fprintf(stdout, "Attaching to crypto library...\n");
  fflush(stdout);

  cylink_guid = *JKL_Get_CylinkCsp_GUID();

  if ((istatus = JKL_AttachCSP(cylink_guid, cylink_handle)) != 0)
     return istatus;

  fprintf(stdout, "Security Officer Pin: ");
  fflush(stdout);
  fgets(sopin, sizeof(sopin), stdin);
  sopin[sizeof(sopin)-1] = 0;
  if (sopin[strlen(sopin)-1] == '\n') sopin[strlen(sopin)-1] = 0;


  fprintf(stdout, "Initializing card...\n");
  fflush(stdout);

  status = scInitializeCard(initsopin, sopin);

  if (status = 160) {
// 160 means login failed.  Maybe we're re-initing a card.  Try the 
// new PIN for both current and new value...
    status = scInitializeCard(sopin, sopin);
  };

  if (status) {
    fprintf(stderr, "Error %lu from scInitializeCard()\n", status);
    return EXIT_FAILURE;
  };
  fprintf(stdout, "Successfully initialized card\n");

  fprintf(stdout, "User Pin: ");
  fflush(stdout);
  fgets(userpin, sizeof(userpin), stdin);
  userpin[sizeof(userpin)-1] = 0;
  if (userpin[strlen(userpin)-1] == '\n') userpin[strlen(userpin)-1] = 0;

  status = scSetUserPin(sopin, userpin);
  if (status) {
    fprintf(stderr, "Error %lu from scSetUserPIN()\n", status);
    return EXIT_FAILURE;
  };
  fprintf(stdout, "Successfully installed user PIN on card\n");

  if (server_type == Init_EE) {
    fprintf(stdout, "Card is ready for use\n\n");
    return EXIT_SUCCESS;
  };

// Generate key.

  fprintf(stdout, "Generating a %s keypair for %s\n", scAlgName, scPrincipal);
  fflush(stdout);



  if ((istatus = JKL_GenerateKeyPair(cylink_handle,
                                    key_cssm_alg,
                                    512,
                                    public_key,
                                    private_key)) != 0)
      return status;



  if (istatus) {
    fprintf(stderr, "Error %d generating keypair\n", status);
    return EXIT_FAILURE;
  };

// Convert the CSP-specific keys to ASN objects...


  status = JKL_cssm_to_asn(public_key,
                           subjectPublicKeyInfo);
  if (status) {
    fprintf(stdout, "Error %lu converting public-key to ASN\n", status);
    return EXIT_FAILURE;
  };

  status = JKL_cssm_to_asn(private_key,
                           privateKeyInfo);


  if (status) {
    fprintf(stdout, "Error %lu converting private-key to ASN\n", status);
    return EXIT_FAILURE;
  };


  fprintf(stdout, "keypair generated, building certificate...\n");
  fflush(stdout);


// Generate the certificate...


  now = time(NULL);
  memcpy(&now_tm, gmtime(&now), sizeof(now_tm));
  memcpy(&then_tm, &now_tm, sizeof(then_tm));
  then_tm.tm_year++;  // One year lifetime.  

// Use serial number 0 for now.
  status = cert.tbsCertificate.serialNumber.set_value(0);
  if (status) {
    fprintf(stderr, "Error %lu setting certificate serial number\n", status);
    return EXIT_FAILURE;
  };

  status = cert.tbsCertificate.serialNumber.get_value(serPtr, serLen);
  if (status) {
    fprintf(stderr, "Error %lu reading certificate serial number\n", status);
    return EXIT_FAILURE;
  };

  status = cert.tbsCertificate.signature.algorithm.set_value(scSigAlgIdent);
  if (status) {
    fprintf(stderr, 
            "Error %lu setting certificate signature algorithm (%s)\n", 
            status,
            scSigAlgIdent);
    return EXIT_FAILURE;
  };


// THis bit should be re-written using Ian's JKL_ routines so we don't have to 
// build crypto knowledge into this program
  switch (scAlg) {
  case ALG_RSA:
// RSA has NULL as the algorithm parameters
    alg_params.clear();
    alg_params.append((unsigned char) 5);
    alg_params.append((unsigned char) 0);
    status = cert.tbsCertificate.signature.parameters.read(alg_params);
    if (status) {
      fprintf(stderr, "Error %lu setting certificate signature parameters\n", status);
      return EXIT_FAILURE;
    };
    break;
  case ALG_DSA:
// DSA doesn't have any signature parameters
    break;
  default:
    break;
  };

  sig_alg.clear();
  status = cert.tbsCertificate.signature.write(sig_alg);
  if (status) {
    fprintf(stderr, "Error %lu extracting inner signature algorithm\n", status);
    return EXIT_FAILURE;
  };

  status = cert.signatureAlgorithm.read(sig_alg);
  if (status) {
    fprintf(stderr, "Error %lu setting outer signature algorithm\n", status);
    return EXIT_FAILURE;
  };

  principal_buffer.data = (unsigned char *)scPrincipal;
  principal_buffer.data_len = strlen(scPrincipal);
  status = cert.tbsCertificate.issuer.set_value_UTF8(principal_buffer);
  if (status) {
    fprintf(stderr, "Error %lu setting certificate issuer\n", status);
    return EXIT_FAILURE;
  };

  status = cert.tbsCertificate.validity.notBefore.utcTime.set_value(
                          now_tm.tm_year + 1900,
                          now_tm.tm_mon + 1,
                          now_tm.tm_mday,
                          now_tm.tm_hour,
                          now_tm.tm_min,
                          now_tm.tm_sec,
                          0, 0); // Zulu time
  if (status) {
    fprintf(stderr, "Error %lu setting certificate start-date\n", status);
    return EXIT_FAILURE;
  };
  status = cert.tbsCertificate.validity.notBefore.select(0);
  if (status) {
    fprintf(stderr, "Error %lu setting certificate start-date to UTC\n", status);
    return EXIT_FAILURE;
  };

  status = cert.tbsCertificate.validity.notAfter.utcTime.set_value(
                          then_tm.tm_year + 1900,
                          then_tm.tm_mon + 1,
                          then_tm.tm_mday,
                          then_tm.tm_hour,
                          then_tm.tm_min,
                          then_tm.tm_sec,
                          0, 0); // Zulu time
  if (status) {
    fprintf(stderr, "Error %lu setting certificate end-date\n", status);
    return EXIT_FAILURE;
  };
  status = cert.tbsCertificate.validity.notAfter.select(0);
  if (status) {
    fprintf(stderr, "Error %lu setting certificate start-date to UTC\n", status);
    return EXIT_FAILURE;
  };

  status = cert.tbsCertificate.subject.set_value_UTF8(principal_buffer);
  if (status) {
    fprintf(stderr, "Error %lu setting certificate subject\n", status);
    return EXIT_FAILURE;
  };

  status = 
    cert.tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm.set_value(scKeyAlgIdent);
  if (status) {
    fprintf(stderr, "Error %lu setting certificate key algorithm\n", status);
    return EXIT_FAILURE;
  };



  temp_buffer.clear();
  status = subjectPublicKeyInfo.write(temp_buffer);
  if (status) {
    fprintf(stderr, "Error %lu writing public-key\n", status);
    return EXIT_FAILURE;
  };

  status = cert.tbsCertificate.subjectPublicKeyInfo.read(temp_buffer);
  
  if (status) {
    fprintf(stderr, "Error %lu reading subjectPublicKeyInfo into certificate\n", status);
    return EXIT_FAILURE;
  };  


  ext = cert.tbsCertificate.extensions.value.add_child();

  ext->extnID.set_value(subjKeyOid, 4);
  gorp.set_value((unsigned char *)"gorp", 4);
  gorp.write(gorp_buf);
  ext->extnValue.set_value(gorp_buf.data, gorp_buf.data_len);


// Sign the certificate...
  fprintf(stdout, "Certificate built, signing it...\n");
  fflush(stdout);

  status = cert.tbsCertificate.write(tbs_data);
  if (status) {
    fprintf(stderr, "Error %lu encoding certificate for signature\n", status);
    return EXIT_FAILURE;
  };

  tbs.Data = tbs_data.data;
  tbs.Length = tbs_data.data_len;
  sig.Data = 0;
  sig.Length = 0;


 if ((istatus = JKL_SignData(cylink_handle,
                             sig_cssm_alg,
                             private_key,
                             tbs,
                             sig)) != 0) {
    fprintf(stderr, "Error %d signing certificate for signature\n", istatus);
    return EXIT_FAILURE;
  };



  status = JKL_cssm_to_asn(sig,
                           signature,
                           cylink_guid);
  if (status) {
    fprintf(stderr, "Error %lu converting sig to ASN\n", status);
    return EXIT_FAILURE;
  };


  fprintf(stdout, "Got signature, storing in the certificate...\n");
  fflush(stdout);


  temp_buffer.clear();
  status = signature.write(temp_buffer);
  if (status) {
    fprintf(stderr, "Error %lu writing signature\n", status);
    return EXIT_FAILURE;
  };

  status = cert.signature.read(temp_buffer);

  if (status) {
    fprintf(stderr, "Error %lu attaching signature to certificate\n", status);
    return EXIT_FAILURE;
  };
  
// The outer algorithm identifier should already be here, so we're ready
// to encode the certificate.

  cert_buffer.clear();
  status = cert.write(cert_buffer);
  if (status) {
    fprintf(stderr, "Error %lu encoding certificate\n", istatus);
    return EXIT_FAILURE;
  };

// Now we have the private key in private_key, and the cert in cert_buffer.
// Save them to the card...


  private_key_buffer.clear();
  status = privateKeyInfo.write(private_key_buffer);
  if (status) {
    fprintf(stderr, "Error %lu encoding private key\n", status);
    return EXIT_FAILURE;
  };
  serial_buffer.data = serPtr;
  serial_buffer.data_len = serLen;

  fprintf(stdout, "Finished with certificate, storing in the card...\n");
  fflush(stdout);

  status = scCreateKeySlot(userpin,
                           (const utf8String)scPrincipal,
                           (const utf8String)scPrincipal,
                           1,
                           serial_buffer,
                           private_key_buffer,
                           cert_buffer);

  if (status) {
    fprintf(stderr, "Error %lu writing to smart-card\n", status);
    return EXIT_FAILURE;
  };

// Now to sanity-check the data we just stored on the card...

  fprintf(stdout, "\nVerifying the card...\n\n");
  fflush(stdout);


  version = 0;
  res_serial_buffer.clear();
  res_serial_buffer.append(serial_buffer);
  status = scRetrieveKeySlot(userpin,
                             (const utf8String)scPrincipal,
                             (const utf8String)scPrincipal,
                             version,
                             &res_serial_buffer,
                             &res_private_key_buffer,
                             &res_cert_buffer);

  fprintf(stdout, "Returned from RetrieveKeySlot...\n");
  fflush(stdout);

  if (status) {
    fprintf(stderr, "\nError %lu retrieving data from card\n\n", status);
  } else {
    if (!(res_serial_buffer == serial_buffer)) {
       fprintf(stderr, "Error: Serial numbers don't match\n");
    };
    if (!(res_private_key_buffer == private_key_buffer)) {
       fprintf(stderr, "Error: Private keys don't match\n");
    };
    if (!(res_cert_buffer == cert_buffer)) {
       fprintf(stderr, "Error: Certs don't match\n");
    };
  };

// And again for good measure

  fprintf(stdout, "\nVerifying the card again...\n\n");
  fflush(stdout);


  version = 0;
  res_serial_buffer.clear();
//  res_serial_buffer.append(serial_buffer);
  res_private_key_buffer.clear();
  res_cert_buffer.clear();
  status = scRetrieveKeySlot(userpin,
                             (const utf8String)scPrincipal,
                             (const utf8String)scPrincipal,
                             version,
                             &res_serial_buffer,
                             &res_private_key_buffer,
                             &res_cert_buffer);
  if (status) {
    fprintf(stderr, "\nError %lu retrieving data from card\n\n", status);
  } else {
    if (!(res_serial_buffer == serial_buffer)) {
       fprintf(stderr, "Error: Serial numbers don't match\n");
    };
    if (!(res_private_key_buffer == private_key_buffer)) {
       fprintf(stderr, "Error: Private keys don't match\n");
    };
    if (!(res_cert_buffer == cert_buffer)) {
       fprintf(stderr, "Error: Certs don't match\n");
    };
  };




  fprintf(stdout, "Smart-card initialization successful.\n");
  fprintf(stdout, "Record the PIN provided above, and keep it in a safe place.\n\n");

  fprintf(stdout, "Do you want a separate copy of the newly-created certificate (Y/N) [N]? ");
  fflush(stdout);
  fgets(line, sizeof(line), stdin);
  c = line[0];
  if ((c == 'Y') || (c == 'y')) {
retry:
    fprintf(stdout, "Destination file (return to abort): ");
    fflush(stdout);
    fgets(filename, sizeof(filename), stdin);
    filename[sizeof(filename)-1] = 0;
    if (filename[strlen(filename)-1] == '\n') filename[strlen(filename)-1] = 0;
    if (strlen(filename) != 0) {
      cert_file = fopen(filename, "wb");
      if (cert_file == NULL) {
        fprintf(stderr, "Error: Couldn't open %s for writing\n", filename);
        goto retry;
      };
      fwrite(cert_buffer.data, cert_buffer.data_len, 1, cert_file);
      fclose(cert_file);
      fprintf(stdout, "Written %d bytes to %s\n", cert_buffer.data_len, filename);
    };
  };

  return 0;
}