#include <KeyStore.h>
#include <keymsg.h>
#include <pkcs.h>
#include <JonahOst.h>
#include <scFunc.h>
#include <Jonah.h>
#include <ApiNotify.h>
#include <JonahIni.h>
#include <jkl.h>


uint32 JnhGetPrivKey(const ObjStoreData * obj,
                     const TransientData * transientData,
                     const PrivateKeyInfo * &priv_key) {
  if (!(transientData->privKey.is_present())) {
// Should deal with encrypted private key here.
// For now just fail
    return KST_KEY_NOT_PRESENT;
  }
  priv_key = &(transientData->privKey);
  return 0;
}


uint32 JnhGetPrivKey(ObjStoreData * obj,
                     TransientData * transientData,
                     PrivateKeyInfo * &priv_key) {
  priv_key = &(transientData->privKey);
  return 0;
}



uint32 JnhGetCertReq(const ObjStoreData * obj,
                     const CertTemplate * &cert) {

  int selected;
  unsigned child_count;
  const CertReqMessages * msg;

  cert = NULL;

// First get a pointer to the certificate...
  if (!obj->msg.is_present()) {
    return KST_MISSING_MESSAGE;
  };

  selected = obj->msg.value.selected();
  if (selected == 1) {
    msg = &(obj->msg.value.ireq.value);
  } else if (selected == 2) {
    msg = &(obj->msg.value.certreq.value);
  } else {
    return KST_IMPROPER_MESSAGE_TYPE;
  };


  child_count = msg->get_child_count();
  if (child_count == 0) {
    return KST_MISSING_REQUEST;
  };
  
  if (child_count != 1) {
    return KST_MULTIPLE_REQUESTS;
  };

  cert = &((*msg)[0]->certReq.certTemplate);

  return 0;
}


uint32 JnhGetCertReq(ObjStoreData * obj,
                     CertTemplate * &cert) {

  int selected;
  unsigned child_count;
  const CertReqMessages * msg;

  cert = NULL;

// First get a pointer to the certificate...
  if (!obj->msg.is_present()) {
    return KST_MISSING_MESSAGE;
  };

  selected = obj->msg.value.selected();
  if (selected == 1) {
    msg = &(obj->msg.value.ireq.value);
  } else if (selected == 2) {
    msg = &(obj->msg.value.certreq.value);
  } else {
    return KST_IMPROPER_MESSAGE_TYPE;
  };


  child_count = msg->get_child_count();
  if (child_count == 0) {
    return KST_MISSING_REQUEST;
  };
  
  if (child_count != 1) {
    return KST_MULTIPLE_REQUESTS;
  };

  cert = &((*msg)[0]->certReq.certTemplate);

  return 0;
}



uint32 JnhGetCert(const ObjStoreData * obj,
                  const x509_certificate * &cert) {

  int selected;
  unsigned child_count;
  int certSelection;
  const CertRepMessage * msg;

  cert = NULL;

// First get a pointer to the certificate...
  if (!obj->msg.is_present()) {
    return KST_MISSING_MESSAGE;
  };

  selected = obj->msg.value.selected();
  if (selected == 3) {
    msg = &(obj->msg.value.irep.value);
  } else if (selected == 4) {
    msg = &(obj->msg.value.certrep.value);
  } else {
    return KST_IMPROPER_MESSAGE_TYPE;
  };


  child_count = msg->response.get_child_count();
  if (child_count == 0) {
    return KST_MISSING_RESPONSE;
  };
  
  if (child_count != 1) {
    return KST_MULTIPLE_CERTIFICATION;
  };

  certSelection = 
    msg->response[0]->certifiedKeyPair.certOrEncCert.selected();

  if (certSelection == 1) {
    return KST_ENC_CERT;
  };

  if (certSelection != 0) {
    return KST_NO_CERT_SELECTION;
  };

  cert = &(msg->response[0]->certifiedKeyPair.certOrEncCert.certificate.value);

  return 0;
}




uint32 JnhGenerateKey(uint32 reqId,
                      bool object_already_locked) {
  uint32 status;
  CSSM_KEY              public_key;
  CSSM_KEY              private_key;
  CSSM_CSP_HANDLE       cylink_handle;
  CSSM_GUID             cylink_guid;
  CSSM_ALGORITHMS       key_cssm_alg;
  long keyLength;
  uint32 objClass;
  ObjStoreData * obj;
  char message[512];
  CertTemplate * cert;
  TransientData * transientData;  
  PrivateKeyInfo * priv_key;

  if (!object_already_locked) {
    status = JnhLockObject(reqId);

    if (status) return status;
  };

// Now the object is locked.  If we locked it, we have to remember 
// to unlock it on all exit paths.

  status = JnhGetObjectModify(reqId,
                              &objClass,
                              &obj);

  if (status) goto end;


  status = JnhGetTransientData(reqId,
                               &transientData);
  if (status) goto end;


  sprintf(message, "Generating Key");
  ApiDisplay(DISPLAY_STATUSBAR, (utf8String)message);

  if (!obj->keygen.is_present()) {
    sprintf(message, "Keygen parameter is not present");
    ApiDisplay(DISPLAY_LOGERROR, (utf8String)message);
    status = KST_KEYGEN_NO_PAR;
    goto end;
  };

  switch (obj->keygen.value.selected()) {
  case 0: // EE-generated
    status = obj->keygen.value.ee.value.numBits.get_value(keyLength);
    if (status) {
      sprintf(message, "Keygen parameter is not present");
      ApiDisplay(DISPLAY_LOGERROR, (utf8String)message);
      goto end;
    };
    break;
  case 1: // CA-generated
    if (!IniAmICA()) {
      status = KST_KEYGEN_NOT_CA;
      goto end;
    };
    status = KST_NOT_IMPLEMENTED;
    goto end;
  case 2: // Imported key
    status = KST_NOT_IMPLEMENTED;
    goto end;
  default: // ?
    status = KST_NOT_IMPLEMENTED;
    goto end;
  }


  

// Here if we're actually going to generate a key, and we've extracted the
// algorithm and size...

  sprintf(message, "Generating random key");
  ApiDisplay(DISPLAY_LOGDEBUG, (utf8String)message);


// For now, fix the Cylink toolkit as crypto provider...
  cylink_guid = *JKL_Get_CylinkCsp_GUID();
  key_cssm_alg = CSSM_ALGID_DSA;

  if ((status = JKL_AttachCSP(cylink_guid, cylink_handle))) {
    sprintf(message, "Error %lu attaching cylink CSP", status);
    ApiDisplay(DISPLAY_LOGERROR, (utf8String)message);
    goto end;
  }
//


  status = JnhGetCertReq(obj, cert);
  if (status) {
    sprintf(message, "Error %lu retrieving certreq for keygen", status);
    ApiDisplay(DISPLAY_LOGDEBUG, (utf8String)message);
    goto end;
  };

  if ((status = JKL_GenerateKeyPair(cylink_handle, 
                                    key_cssm_alg, 
                                    keyLength, 
                                    public_key, 
                                    private_key))) {
    sprintf(message, "Error %lu generating public key", status);
    ApiDisplay(DISPLAY_LOGERROR, (utf8String)message);
    goto end;
  }
  if ((status = JKL_cssm_to_asn(public_key, 
                                cert->publicKey.value))) {
    sprintf(message, "Error %lu converting public key to ASN.1", 
            status);
    ApiDisplay(DISPLAY_LOGERROR, (utf8String)message);
    goto end;
  };

  status = JnhGetPrivKey(obj, transientData, priv_key);
  if (status) {
    goto end;
  };
  if ((status = JKL_cssm_to_asn(private_key, 
                                *priv_key))) {
    sprintf(message, "Error %lu converting private key to ASN.1", 
            status);
    ApiDisplay(DISPLAY_LOGERROR, (utf8String)message);
    goto end;
  }

  ApiDisplay(DISPLAY_STATUSBAR, (utf8String) "Key Generated");

end:

  if (!object_already_locked) {
// If we locked the object, we have to unlock it...
    if (status) JnhDiscardObject(reqId);
    else JnhSynchObject(reqId);
  };
  return status;  

}



uint32 JnhExportCredentialPKCS12(uint32 reqId,
                                 const utf8String password,
                                 const utf8String filename,
                                 bool object_already_locked) {

  uint32			status;
  uint32			objClass;
  PFX				p12;
  unsigned char			message[BUFSIZ];
  TransientData			*tdataPtr;
  const x509_certificate	*cert;
  const PrivateKeyInfo		*privKey;
  const	ObjStoreData		*objsPtr;

  do {
    ApiDisplay(DISPLAY_LOGDEBUG, (utf8String) 
	       "Entering JnhExportCredential PKCS #12");
    if (!object_already_locked) {
      if ((status = JnhLockObject(reqId))) {
	sprintf((char *) message, "Error %lu locking object", status);
	ApiDisplay(DISPLAY_LOGDEBUG, message);
	break;
      }
    }
    if ((status = JnhGetObject(reqId, &objClass, &objsPtr))) {
      sprintf((char *) message, "Error %lu getting object", status);
      ApiDisplay(DISPLAY_LOGDEBUG, message);
      break;
    }
    if ((status = JnhGetTransientData(reqId, &tdataPtr))) {
      sprintf((char *) message, "Error %lu getting transient data", status);
      ApiDisplay(DISPLAY_LOGDEBUG, message);
      break;
    }
    status = JnhGetPrivKey(objsPtr, tdataPtr, privKey);
    switch(status) {
    case KST_ENC_KEY:
      ApiDisplay(DISPLAY_LOGDEBUG, (utf8String) 
		 "key is encrypted - not yet implemented");
      break;
    case KST_KEY_NOT_PRESENT:
      if (objClass & ObjStAll == ObjStEECertIssued) {
	sprintf((char *) message, "key not present, reading file %s", 
		filename);
	ApiDisplay(DISPLAY_LOGDEBUG, message);
	if ((status = p12.read_file(filename, password))) {
	  sprintf((char *) message, "Error %lu reading PKCS#12", 
		  status);
	  ApiDisplay(DISPLAY_LOGERROR, message);
	  break;
	}
	if ((status = JnhGetCert(objsPtr, cert))) {
	  if (status == KST_ENC_CERT) {
	    sprintf((char *) message, 
		    "Cert is encrypted - not yet implemented");
	    ApiDisplay(DISPLAY_LOGDEBUG, message);
	  } else  {
	    sprintf((char *) message, "Error %lu retrieving cert", status);
	    ApiDisplay(DISPLAY_LOGERROR, message);
	    break;
	  }
	}
	if ((status = p12.add_cert(cert))) {
	  sprintf((char *) message, "Error %lu adding cert to PKCS#12 export", 
		  status);
	  ApiDisplay(DISPLAY_LOGERROR, message);
	  break;
	}
      } else if ((objClass & ObjStAll) == ObjStEECertReqActive) {
	sprintf((char *) message, "Generating key");
	ApiDisplay(DISPLAY_LOGDEBUG, message);
	if ((status = JnhGenerateKey(reqId, true))) {
	  sprintf((char *) message, "Error generating key %lu", status);
	  ApiDisplay(DISPLAY_LOGERROR, message);
	  break;
	}
	if ((status = JnhGetPrivKey(objsPtr, tdataPtr, privKey))) {
	  sprintf((char *) message, "Error getting key %lu", status);
	  ApiDisplay(DISPLAY_LOGERROR, message);
	  break;
	}
	if ((status = p12.add_key(privKey))) {
	  sprintf((char *) message, "Error %lu while adding key to PKCS #12",
		  status);
	  ApiDisplay(DISPLAY_LOGERROR, message);
	  break;
	}
      }
      break;
    case 0: 
      if ((status = p12.add_key(privKey))) {
	sprintf((char *) message, "Error %lu while adding key to PKCS #12",
		status);
	ApiDisplay(DISPLAY_LOGERROR, message);
      }
      if (objClass & ObjStAll == ObjStEECertIssued) {
	if ((status = JnhGetCert(objsPtr, cert))) {
	  if (status == KST_ENC_CERT) {
	    sprintf((char *) message, 
		    "Cert is encrypted - not yet implemented");
	    ApiDisplay(DISPLAY_LOGDEBUG, message);
	  } else  {
	    sprintf((char *) message, "Error %lu retrieving cert", status);
	    ApiDisplay(DISPLAY_LOGERROR, message);
	    break;
	  }
	}
	if ((status = p12.add_cert(cert))) {
	  sprintf((char *) message, "Error %lu adding cert to PKCS#12 export", 
		  status);
	  ApiDisplay(DISPLAY_LOGERROR, message);
	  break;
	}
      }
      break;
    default:
      sprintf((char *) message, "Error %lu while retrieving private key",
	      status);
      ApiDisplay(DISPLAY_LOGERROR, message);
      break;
    }
    if (status) break;
    if ((status = p12.write_file(filename, password))) {
      	  sprintf((char *) message, "key is encrypted - not yet implemented");
	  ApiDisplay(DISPLAY_LOGDEBUG, message);
	  break;
    }
  } while(0);
  if (!object_already_locked) {
    if (status) {
      JnhDiscardObject(reqId);
    } else {
      JnhSynchObject(reqId); 
    }
  }
  return status;
}

uint32 JnhExportCredentialTOK(uint32 reqId,
                              const utf8String password,
                              const utf8String filename,
                              bool object_already_locked) {
  return 0;
}


uint32 JnhExportCredentialVSC(uint32 reqId,
                              const utf8String password,
                              const utf8String filename,
                              bool object_already_locked) {
  uint32 status;
  uint32 objClass;
  const ObjStoreData * obj;
  const x509_certificate * cert;
  const CertTemplate * certTemplate;
  const PrivateKeyInfo * priv_key;
  TransientData * transientData;  

  buffer_t issuerName;
  buffer_t subjectName;
  buffer_t private_key_buffer(ASN_SECRET);
  buffer_t cert_buffer;
  r_buffer_t serial_buffer;
  buffer_t pin_buffer(ASN_SECRET);
  unsigned char * serPtr;
  size_t serLen;
  unsigned char message[512];

// This routine will do one of:
// a)  Export a complete credential to the VSC,
// b)  Add a certificate to a previously exported private-key,
// c)  Export just a private-key to the VSC and 
//     annotate the object with the VSC name and password, or
// c)  Annotate the object with the VSC name and password
// The routine will do whichever is appropriate given the current 
// state of the object.


  serial_buffer.data = NULL;
  serial_buffer.data_len = 0;


  ApiDisplay(DISPLAY_LOGDEBUG, (utf8String) "Entering JnhExportCredential");

  if (!object_already_locked) {
    status = JnhLockObject(reqId);

    if (status) {
      sprintf((char *) message, "Error %lu locking object",  status);
      ApiDisplay(DISPLAY_LOGDEBUG, message);
      return status;
    };
  };

// Now the object is locked.  If we locked it, we have to remember 
// to unlock it on all exit paths.

  status = JnhGetObject(reqId,
                        &objClass,
                        &obj);

  if (status) {
    sprintf((char *) message, "Error %lu getting object",  status);
    ApiDisplay(DISPLAY_LOGDEBUG, message);
    goto end;
  };

  status = JnhGetTransientData(reqId,
                               &transientData);

  if (status) {
    sprintf((char *) message, "Error %lu getting transient data",  status);
    ApiDisplay(DISPLAY_LOGDEBUG, message);
    goto end;
  };

  if ((objClass & ObjStAll) == ObjStEECertIssued) {

// The Cert has been issued.  We should check to see whether it includes a plaintext
// private key, and if so then save both the key and the cert.  If we have an 
// encrypted private-key, and a stored password, then decrypt the key and store
// the key and cert.  If we don't have a stored password, try the password we've been
// given as the VSC password - they may be the same.  If not, then we should fail with
// a password-needed error message.  If we have just a cert and no private key, and if 
// we have a place to store the 

    sprintf((char *) message, "Cert has been issued.  Will export...");
    ApiDisplay(DISPLAY_LOGDEBUG, message);


    status = JnhGetCert(obj, cert);
    if (status == KST_ENC_CERT) {
      sprintf((char *) message, "Cert is encrypted - not yet implemented");
      ApiDisplay(DISPLAY_LOGDEBUG, message);
//    deal with this, then retry.      
    };
    if (status) {
      sprintf((char *) message, "Error %lu retrieving cert", status);
      ApiDisplay(DISPLAY_LOGERROR, message);
      goto end;
    };

    cert_buffer.clear();
    status = cert->write(cert_buffer);
    if (status) {
      sprintf((char *) message, "Error %lu encoding cert", status);
      ApiDisplay(DISPLAY_LOGERROR, message);
      goto end;
    };
   
    status = cert->tbsCertificate.serialNumber.get_value(serPtr, serLen);
    if (status) {
      sprintf((char *) message, "Error %lu getting certificate serial number", status);
      ApiDisplay(DISPLAY_LOGERROR, message);
      goto end;
    };
    serial_buffer.data = serPtr;
    serial_buffer.data_len = serLen;
    
    subjectName.clear();  
    status = cert->tbsCertificate.subject.get_value_UTF8(subjectName);
    subjectName.append((unsigned char)0);  
    
    issuerName.clear();  
    status = cert->tbsCertificate.issuer.get_value_UTF8(issuerName);
    issuerName.append((unsigned char)0);  


// We have a certificate in hand.  Now to see if the object contains a private-key

    status = JnhGetPrivKey(obj, transientData, priv_key);
    if (status == KST_ENC_KEY) {
//    deal with this, then retry.      
//    *** TBS ***
    };
    if (status == KST_KEY_NOT_PRESENT) {
//    We don't have a private key.  We need to update the record we should
//    have already written.  Password and filename should be NULL, and we 
//    should use the ones from the object...
      if (transientData->pin.is_present()) {
        pin_buffer.append(transientData->pin);
      } else {
        pin_buffer.append((const char *)password);
      }
      if (pin_buffer.data_len == 0) {
        status = KST_NEED_PASSWORD;
      };
      pin_buffer.append((unsigned char)0);

      ApiDisplay(DISPLAY_LOGINFO, (utf8String) "Adding certificate to smart-card...");

      status = scUpdateKeySlot((const char *)pin_buffer.data,
                               subjectName.data,
                               issuerName.data,
                               1,
                               serial_buffer,
                               private_key_buffer,
                               cert_buffer);

      if (status) {
        sprintf((char *) message, "Error %lu writing to smart-card", 
                status);
        ApiDisplay(DISPLAY_LOGERROR, message);
      };
    } else if (status) {
      goto end;
    } else {
// We now have both key and cert to export.  Export them...
      private_key_buffer.clear();
      status = priv_key->write(private_key_buffer);
      if (status) {
        goto end;
      };


      transientData->pin.clear();
      transientData->pin.append((const char *)password);

      ApiDisplay(DISPLAY_LOGINFO, 
                 (utf8String) "Writing credentials to smart-card...");

      status = scCreateKeySlot((const char *)password,
                               subjectName.data,
                               issuerName.data,
                               1,
                               serial_buffer,
                               private_key_buffer,
                               cert_buffer);
    
      if (status) {
        sprintf((char *) message, "Error %lu writing to smart-card", 
                status);
        ApiDisplay(DISPLAY_LOGERROR, message);
      };


    };
  } else if ((objClass & ObjStAll) == ObjStEECertReqActive) {
// This is a certrequest about to be submitted.  It may have a key to 
// export, but it shouldn't have a certificate...



    status = JnhGetPrivKey(obj, transientData, priv_key);
    if (status == KST_ENC_KEY) {
      sprintf((char *) message, "Key is encrypted (not yet supported)");
      ApiDisplay(DISPLAY_LOGINFO, message);

//    deal with this, then retry.      
//    *** TBS ***
    };
    if (status == KST_KEY_NOT_PRESENT) {

      sprintf((char *) message, "Key not present - will generate one");
      ApiDisplay(DISPLAY_LOGDEBUG, message);

// We need to generate a key...
      status = JnhGenerateKey(reqId, true);
      if (status) {
        sprintf((char *) message, "Error %lu generating key", status);
        ApiDisplay(DISPLAY_LOGERROR, message);
        goto end;
      };
      status = JnhGetPrivKey(obj, transientData, priv_key);
      if (status) {
        sprintf((char *) message, "Error %lu retrieving private-key", status);
        ApiDisplay(DISPLAY_LOGERROR, message);
        goto end;
      };
    };

    private_key_buffer.clear();
    status = priv_key->write(private_key_buffer);
    if (status) {
      sprintf((char *) message, "Error %lu encoding private-key", status);
      ApiDisplay(DISPLAY_LOGERROR, message);
      goto end;
    };

    transientData->pin.clear();
    transientData->pin.append((const char *)password);

    status = JnhGetCertReq(obj, 
                           certTemplate);

    if (status) {
      sprintf((char *) message, "Error %lu retrieving cert template", status);
      ApiDisplay(DISPLAY_LOGERROR, message);
      goto end;
    };

    if (!certTemplate->subject.is_present()) {
      sprintf((char *) message, "Subject name is not present in cert-request");
      ApiDisplay(DISPLAY_LOGERROR, message);
      status = KST_SUBJECTNAME_NOT_FOUND;
      goto end;
    };

    if (!certTemplate->issuer.is_present()) {
      sprintf((char *) message, "Issuer name is not present in cert-request");
      ApiDisplay(DISPLAY_LOGERROR, message);
      status = KST_ISSUERNAME_NOT_FOUND;
      goto end;
    };

    subjectName.clear();
    status = certTemplate->subject.value.write(subjectName);
    if (status) {
      sprintf((char *) message, "Error %lu encoding subject name", status);
      ApiDisplay(DISPLAY_LOGERROR, message);
      goto end;
    };

    issuerName.clear();
    status = certTemplate->issuer.value.write(issuerName);
    if (status) {
      sprintf((char *) message, "Error %lu encoding issuer name", status);
      ApiDisplay(DISPLAY_LOGERROR, message);
      goto end;
    };
    cert_buffer.clear();

    ApiDisplay(DISPLAY_LOGINFO, 
               (utf8String) "Writing private-key to smart-card...");
    
    status = scCreateKeySlot((const char *)password,
                             subjectName.data,
                             issuerName.data,
                             1,
                             serial_buffer,
                             private_key_buffer,
                             cert_buffer);
    
    if (status) {
      sprintf((char *) message, "Error %lu writing to smart-card", 
              status);
      ApiDisplay(DISPLAY_LOGERROR, message);
    };

  } else {
    sprintf((char *) message, "JnhCredExport called with object in unexpected state");
    ApiDisplay(DISPLAY_LOGERROR, message);
    status = KST_WRONG_STATE;
  };


end:
  if (!object_already_locked) {
    if (status) JnhDiscardObject(reqId);
    else JnhSynchObject(reqId);
  };
  return status;  
}

