/*
 *  cert.c
 *  SecurityNssSmime
 *
 *  Created by john on Wed Mar 12 2003.
 *  Copyright (c) 2003 __MyCompanyName__. All rights reserved.
 *
 */

#include "cert.h"
#include "cmstpriv.h"
#include "secerr.h"
#include <Security/SecKeychain.h>
#include <Security/SecKeychainItem.h>
#include <Security/SecKeychainSearch.h>
#include <Security/SecIdentityPriv.h>
#include <Security/SecIdentitySearch.h>
#include <Security/SecCertificatePriv.h>
#include <Security/SecPolicySearch.h>
#include <Security/oidsalg.h>
#include <Security/cssmapi.h>
#include <Security/oidscert.h>
#include <Security/oidscert.h>

/* for errKCDuplicateItem */
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>

/* @@@ Remove this once it's back in the appropriate header. */
static const uint8 X509V1IssuerNameStd[] = {INTEL_X509V3_CERT_R08, 23};
static const CSSM_OID OID_X509V1IssuerNameStd = {INTEL_X509V3_CERT_R08_LENGTH+1,  (uint8 *)X509V1IssuerNameStd};

/* 
 * Normalize a Printable String. Per RFC2459 (4.1.2.4), printable strings are case 
 * insensitive and we're supposed to ignore leading and trailing 
 * whitespace, and collapse multiple whitespace characters into one. 
 */
static void
CERT_NormalizeString(CSSM_DATA *string)
{
    char *pD, *pCh, *pEos;

    if (!string->Length)
	return;

    pD = pCh = (char *)string->Data;
    pEos = pCh + string->Length - 1;

    /* Strip trailing NULL terminators */
    while(*pEos == 0)
	pEos--;
    
    /* Remove trailing spaces */
    while(isspace(*pEos))
	pEos--;

    /* Point to one past last non-space character */
    pEos++;

    /* skip all leading whitespace */
    while(isspace(*pCh) && (pCh < pEos))
	pCh++;

    /* Eliminate multiple whitespace and convent to upper case.
     * pCh points to first non-white char.
     * pD still points to start of string. */
    while(pCh < pEos)
    {
	char ch = *pCh++;
	*pD++ = toupper(ch);
	if(isspace(ch))
	{
	    /* skip 'til next nonwhite */
	    while(isspace(*pCh) && (pCh < pEos))
		pCh++;
	}
    }

    string->Length = pD - (char *)string->Data;
}

/* 
 * Normalize an RDN. Per RFC2459 (4.1.2.4), printable strings are case 
 * insensitive and we're supposed to ignore leading and trailing 
 * whitespace, and collapse multiple whitespace characters into one. 
 *
 * Incoming NSS_Name is assumed to be entirely within specifed coder's
 * address space; we'll be munging some of that and possibly replacing
 * some pointers with others allocated from the same space. 
 */
void
CERT_NormalizeX509NameNSS(NSS_Name *nssName)
{
    NSS_RDN *rdn;

    for (rdn = *nssName->rdns; rdn; ++rdn)
    {
	NSS_ATV *attr;
	for (attr = *rdn->atvs; attr; ++attr)
	{
	    /* 
		* attr->value is an ASN_ANY containing an encoded
		* string. We only normalize Prinatable String types. 
		* If we find one, decode it, normalize it, encode the
		* result, and put the encoding back in attr->value.
		* We temporarily "leak" the original string, which only
		* has a lifetime of the incoming SecNssCoder. 
		*/
	    NSS_TaggedItem *attrVal = &attr->value;
	    if(attrVal->tag != SEC_ASN1_PRINTABLE_STRING)
		continue;

	    CERT_NormalizeString(&attrVal->item);
	}
    }
}

SecCertificateRef CERT_FindCertByNicknameOrEmailAddr(SecKeychainRef keychainOrArray, char *name)
{
   SecCertificateRef certificate;
    OSStatus status=SecCertificateFindByEmail(keychainOrArray,name,&certificate);
    return status==noErr?certificate:NULL;
}

SecPublicKeyRef SECKEY_CopyPublicKey(SecPublicKeyRef pubKey)
{
    CFRetain(pubKey);
    return pubKey;
}

void SECKEY_DestroyPublicKey(SecPublicKeyRef pubKey)
{
    CFRelease(pubKey);
}

SecPublicKeyRef SECKEY_CopyPrivateKey(SecPublicKeyRef privKey)
{
    CFRetain(privKey);
    return privKey;
}

void SECKEY_DestroyPrivateKey(SecPublicKeyRef privKey)
{
    CFRelease(privKey);
}

void CERT_DestroyCertificate(SecCertificateRef cert)
{
    CFRelease(cert);
}

SecCertificateRef CERT_DupCertificate(SecCertificateRef cert)
{
    CFRetain(cert);
    return cert;
}

SecIdentityRef CERT_FindIdentityByUsage(SecKeychainRef keychainOrArray,
			 char *nickname, SECCertUsage usage, PRBool validOnly, void *proto_win)
{
    SecIdentityRef identityRef = NULL;
    SecCertificateRef cert = CERT_FindCertByNicknameOrEmailAddr(keychainOrArray, nickname);
    if (!cert)
	return NULL;

    SecIdentityCreateWithCertificate(keychainOrArray, cert, &identityRef);
    CFRelease(cert);

    return identityRef;
}

SecCertificateRef CERT_FindUserCertByUsage(SecKeychainRef keychainOrArray,
			 char *nickname,SECCertUsage usage,PRBool validOnly,void *proto_win)
{
    SecItemClass itemClass = kSecCertificateItemClass;
    SecKeychainSearchRef searchRef;
    SecKeychainItemRef itemRef = NULL;
    OSStatus status;
    SecKeychainAttribute attrs[1];
    const char *serialNumber = "12345678";
 //   const SecKeychainAttributeList attrList;
#if 0
    attrs[1].tag = kSecLabelItemAttr;
    attrs[1].length = strlen(nickname)+1;
    attrs[1].data = nickname;
#else
    attrs[1].tag = kSecSerialNumberItemAttr;
    attrs[1].length = strlen(serialNumber)+1;
    attrs[1].data = (uint8 *)serialNumber;
#endif
    SecKeychainAttributeList attrList = { 0, attrs };
 //   12 34 56 78
	status = SecKeychainSearchCreateFromAttributes(keychainOrArray,itemClass,&attrList,&searchRef);
    if (status)
    {
        printf("CERT_FindUserCertByUsage: SecKeychainSearchCreateFromAttributes:%ld",status);
        return NULL;
    }
	status = SecKeychainSearchCopyNext(searchRef,&itemRef);
    if (status)
    	printf("CERT_FindUserCertByUsage: SecKeychainSearchCopyNext:%ld",status);
    CFRelease(searchRef);
    return (SecCertificateRef)itemRef;
}

/*
startNewClass(X509Certificate)
CertType, kSecCertTypeItemAttr, "CertType", 0, NULL, UINT32)
CertEncoding, kSecCertEncodingItemAttr, "CertEncoding", 0, NULL, UINT32)
PrintName, kSecLabelItemAttr, "PrintName", 0, NULL, BLOB)
Alias, kSecAliasItemAttr, "Alias", 0, NULL, BLOB)
Subject, kSecSubjectItemAttr, "Subject", 0, NULL, BLOB)
Issuer, kSecIssuerItemAttr, "Issuer", 0, NULL, BLOB)
SerialNumber, kSecSerialNumberItemAttr, "SerialNumber", 0, NULL, BLOB)
SubjectKeyIdentifier, kSecSubjectKeyIdentifierItemAttr, "SubjectKeyIdentifier", 0, NULL, BLOB)
PublicKeyHash, kSecPublicKeyHashItemAttr, "PublicKeyHash", 0, NULL, BLOB)
endNewClass()
*/

CFArrayRef CERT_CertChainFromCert(SecCertificateRef cert, SECCertUsage usage, PRBool includeRoot)
{
    SecPolicySearchRef searchRef = NULL;
    SecPolicyRef policy = NULL;
    CFArrayRef wrappedCert = NULL;
    SecTrustRef trust = NULL;
    CFArrayRef certChain = NULL;
    CSSM_TP_APPLE_EVIDENCE_INFO *statusChain;
    OSStatus status;

    if (!cert)
	goto loser;

    status = SecPolicySearchCreate(CSSM_CERT_X_509v3, &CSSMOID_APPLE_X509_BASIC, NULL, &searchRef);
    if (status)
	goto loser;
    status = SecPolicySearchCopyNext(searchRef, &policy);
    if (status)
	goto loser;

    wrappedCert = CERT_CertListFromCert(cert);
    status = SecTrustCreateWithCertificates(wrappedCert, policy, &trust);
    if (status)
	goto loser;

    status = SecTrustEvaluate(trust, NULL);
    if (status)
	goto loser;

    status = SecTrustGetResult(trust, NULL, &certChain, &statusChain);
    if (status)
	goto loser;

    /* We don't drop the root if there is only 1 (self signed) certificate in the chain. */
    if (!includeRoot && CFArrayGetCount(certChain) > 1)
    {
	CFMutableArrayRef subChain =  CFArrayCreateMutableCopy(NULL, 0, certChain);
	CFRelease(certChain);
	certChain = subChain;
	if (subChain)
	    CFArrayRemoveValueAtIndex(subChain, CFArrayGetCount(subChain) - 1);
    }

loser:
    if (searchRef)
	CFRelease(searchRef);
    if (policy)
	CFRelease(policy);
    if (wrappedCert)
	CFRelease(wrappedCert);
    if (trust)
	CFRelease(trust);
    if (certChain && status)
    {
	CFRelease(certChain);
	certChain = NULL;
    }

    return certChain;
}

CFArrayRef CERT_CertListFromCert(SecCertificateRef cert)
{
    const void *value = cert;
    return cert ? CFArrayCreate(NULL, &value, 1, &kCFTypeArrayCallBacks) : NULL;
}

CFArrayRef CERT_DupCertList(CFArrayRef oldList)
{
    CFRetain(oldList);
    return oldList;
}

// Extract a public key object from a SubjectPublicKeyInfo
SecPublicKeyRef CERT_ExtractPublicKey(SecCertificateRef cert)
{
    SecPublicKeyRef keyRef = NULL;
    SecCertificateCopyPublicKey(cert,&keyRef);
    return keyRef;
}

SECStatus CERT_CheckCertUsage (SecCertificateRef cert,unsigned char usage)
{
    // abort();
    // @@@ It's all good, it's ok.
    return SECSuccess;
}

// Find a certificate in the database by a email address
// "emailAddr" is the email address to look up
SecCertificateRef CERT_FindCertByEmailAddr(SecKeychainRef keychainOrArray, char *emailAddr)
{
    abort();
    return NULL;
}

// Find a certificate in the database by a DER encoded certificate
// "derCert" is the DER encoded certificate
SecCertificateRef CERT_FindCertByDERCert(SecKeychainRef keychainOrArray, const SECItem *derCert)
{
    // @@@ Technically this should look though keychainOrArray for a cert matching this one I guess.
    SecCertificateRef cert = NULL;
    OSStatus rv;

    rv = SecCertificateCreateFromData(derCert, CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_DER, &cert);
    if (rv && cert)
    {
	PORT_SetError(SEC_ERROR_NO_EMAIL_CERT);
	CFRelease(cert);
	cert = NULL;
    }

    return cert;
}

// Generate a certificate key from the issuer and serialnumber, then look it up in the database.
// Return the cert if found. "issuerAndSN" is the issuer and serial number to look for
SecCertificateRef CERT_FindCertByIssuerAndSN (CFTypeRef keychainOrArray, const SecCmsIssuerAndSN *issuerAndSN)
{
    SecCertificateRef certificate;
    OSStatus status = SecCertificateFindByIssuerAndSN(keychainOrArray, &issuerAndSN->derIssuer,
	&issuerAndSN->serialNumber, &certificate);
    if (status)
    {
	PORT_SetError(SEC_ERROR_NO_EMAIL_CERT);
	certificate = NULL;
    }

    return certificate;
}

SecCertificateRef CERT_FindCertBySubjectKeyID (CFTypeRef keychainOrArray, const SECItem *subjKeyID)
{
    SecCertificateRef certificate;
    OSStatus status = SecCertificateFindBySubjectKeyID(keychainOrArray,subjKeyID,&certificate);
    if (status)
    {
	PORT_SetError(SEC_ERROR_NO_EMAIL_CERT);
	certificate = NULL;
    }

    return certificate;
}

static SecIdentityRef
CERT_FindIdentityByCertificate (CFTypeRef keychainOrArray, SecCertificateRef certificate)
{
    SecIdentityRef  identity = NULL;
    SecIdentityCreateWithCertificate(keychainOrArray, certificate, &identity);
    if (!identity)
	PORT_SetError(SEC_ERROR_NOT_A_RECIPIENT);

    return identity;
}

SecIdentityRef
CERT_FindIdentityByIssuerAndSN (CFTypeRef keychainOrArray, const SecCmsIssuerAndSN *issuerAndSN)
{
    SecCertificateRef certificate = CERT_FindCertByIssuerAndSN(keychainOrArray, issuerAndSN);
    if (!certificate)
	return NULL;

    return CERT_FindIdentityByCertificate(keychainOrArray, certificate);
}

SecIdentityRef
CERT_FindIdentityBySubjectKeyID (CFTypeRef keychainOrArray, const SECItem *subjKeyID)
{
    SecCertificateRef certificate = CERT_FindCertBySubjectKeyID(keychainOrArray, subjKeyID);
    if (!certificate)
	return NULL;

    return CERT_FindIdentityByCertificate(keychainOrArray, certificate);
}

// find the smime symmetric capabilities profile for a given cert
SECItem *CERT_FindSMimeProfile(SecCertificateRef cert)
{
    return NULL;
}

// Return the decoded value of the subjectKeyID extension. The caller should 
// free up the storage allocated in retItem->data.
SECStatus CERT_FindSubjectKeyIDExtension (SecCertificateRef cert,SECItem *retItem)
{
    fprintf(stderr, "WARNING: CERT_FindSubjectKeyIDExtension unimplemented\n");
    return SECFailure;
}

// Extract the issuer and serial number from a certificate
SecCmsIssuerAndSN *CERT_GetCertIssuerAndSN(PRArenaPool *pl, SecCertificateRef cert)
{
    OSStatus status;
    SecCmsIssuerAndSN *certIssuerAndSN;
    CSSM_CL_HANDLE clHandle;
    CSSM_DATA_PTR serialNumber = 0;
    CSSM_DATA_PTR issuer = 0;
    CSSM_DATA certData = {};
    CSSM_HANDLE resultsHandle = 0;
    uint32 numberOfFields = 0;
    CSSM_RETURN result;
    void *mark;

    mark = PORT_ArenaMark(pl);

    status = SecCertificateGetCLHandle(cert, &clHandle);
    if (status)
	goto loser;
    status = SecCertificateGetData(cert, &certData);
    if (status)
	goto loser;

    /* Get the issuer from the cert. */
    result = CSSM_CL_CertGetFirstFieldValue(clHandle, &certData, &OID_X509V1IssuerNameStd, &resultsHandle, &numberOfFields, &issuer);

    /* @@@ Remove this once we are sure CSSMOID_X509V1IssuerNameStd is working. */
    /* Fall back on old normalized issuer if the new oid isn't supported yet. */
    if (result)
	result = CSSM_CL_CertGetFirstFieldValue(clHandle, &certData, &CSSMOID_X509V1IssuerName, &resultsHandle, &numberOfFields, &issuer);

    if (result || numberOfFields < 1)
	goto loser;
    result = CSSM_CL_CertAbortQuery(clHandle, resultsHandle);
    if (result)
	goto loser;


    /* Get the serialNumber from the cert. */
    result = CSSM_CL_CertGetFirstFieldValue(clHandle, &certData, &CSSMOID_X509V1SerialNumber, &resultsHandle, &numberOfFields, &serialNumber);
    if (result || numberOfFields < 1)
	goto loser;
    result = CSSM_CL_CertAbortQuery(clHandle, resultsHandle);
    if (result)
	goto loser;

    /* Allocate the SecCmsIssuerAndSN struct. */
    certIssuerAndSN = (SecCmsIssuerAndSN *)PORT_ArenaZAlloc (pl, sizeof(SecCmsIssuerAndSN));
    if (certIssuerAndSN == NULL)
	goto loser;

    /* Copy the issuer. */
    certIssuerAndSN->derIssuer.Data = (uint8 *) PORT_ArenaAlloc(pl, issuer->Length);
    if (!certIssuerAndSN->derIssuer.Data)
	goto loser;
    PORT_Memcpy(certIssuerAndSN->derIssuer.Data, issuer->Data, issuer->Length);
    certIssuerAndSN->derIssuer.Length = issuer->Length;

    /* Copy the serialNumber. */
    certIssuerAndSN->serialNumber.Data = (uint8 *) PORT_ArenaAlloc(pl, serialNumber->Length);
    if (!certIssuerAndSN->serialNumber.Data)
	goto loser;
    PORT_Memcpy(certIssuerAndSN->serialNumber.Data, serialNumber->Data, serialNumber->Length);
    certIssuerAndSN->serialNumber.Length = serialNumber->Length;

    PORT_ArenaUnmark(pl, mark);

    CSSM_CL_FreeFieldValue(clHandle, &CSSMOID_X509V1SerialNumber, serialNumber);
    CSSM_CL_FreeFieldValue(clHandle, &OID_X509V1IssuerNameStd, issuer);

    return certIssuerAndSN;

loser:
    PORT_ArenaRelease(pl, mark);

    if (serialNumber)
	CSSM_CL_FreeFieldValue(clHandle, &CSSMOID_X509V1SerialNumber, serialNumber);
    if (issuer)
	CSSM_CL_FreeFieldValue(clHandle, &OID_X509V1IssuerNameStd, issuer);

    PORT_SetError(SEC_INTERNAL_ONLY);
    return NULL;
}

// import a collection of certs into the temporary or permanent cert database
SECStatus CERT_ImportCerts(SecKeychainRef keychain, SECCertUsage usage, unsigned int ncerts,
    SECItem **derCerts, SecCertificateRef **retCerts, PRBool keepCerts, PRBool caOnly, char *nickname)
{
    OSStatus rv = SECFailure;
    SecCertificateRef cert;
    unsigned int ci;

    // @@@ Do something with caOnly and nickname
    if (caOnly || nickname)
	abort();

    for (ci = 0; ci < ncerts; ++ci)
    {
	rv = SecCertificateCreateFromData(derCerts[ci], CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_DER, &cert);
	if (rv)
	    break;
	if (keepCerts)
	{
	    rv = SecCertificateAddToKeychain(cert, keychain);
	    if (rv)
	    {
		if (rv == errKCDuplicateItem)
		    rv = noErr;
		else
		{
		    CFRelease(cert);
		    break;
		}
	    }
	}

	if (retCerts)
	{
	    // @@@ not yet
	    abort();
	}
	else
	    CFRelease(cert);
    }

    return rv;
}

SECStatus CERT_SaveSMimeProfile(SecCertificateRef cert, SECItem *emailProfile,SECItem *profileTime)
{
    fprintf(stderr, "WARNING: CERT_SaveSMimeProfile unimplemented\n");
    return SECSuccess;
}

// Check the hostname to make sure that it matches the shexp that
// is given in the common name of the certificate.
SECStatus CERT_VerifyCertName(SecCertificateRef cert, const char *hostname)
{
    fprintf(stderr, "WARNING: CERT_VerifyCertName unimplemented\n");
    return SECSuccess;
}

/*
** OLD OBSOLETE FUNCTIONS with enum SECCertUsage - DO NOT USE FOR NEW CODE
** verify a certificate by checking validity times against a certain time,
** that we trust the issuer, and that the signature on the certificate is
** valid.
**	"cert" the certificate to verify
**	"checkSig" only check signatures if true
*/
SECStatus
CERT_VerifyCert(SecKeychainRef keychainOrArray, SecCertificateRef cert,
		CFTypeRef policies, CFAbsoluteTime stime, SecTrustRef *trustRef)
{
    CFArrayRef certificates;
    const void *certs = cert;
    SecTrustRef trust = NULL;
    OSStatus rv;

    certificates = CFArrayCreate(NULL, &certs, 1, &kCFTypeArrayCallBacks);
    rv = SecTrustCreateWithCertificates(certificates, policies, &trust);
    CFRelease(certificates);
    if (rv)
	goto loser;

    rv = SecTrustSetKeychains(trust, keychainOrArray);
    if (rv)
	goto loser;

    CFDateRef verifyDate = CFDateCreate(NULL, stime);
    rv = SecTrustSetVerifyDate(trust, verifyDate);
    CFRelease(verifyDate);
    if (rv)
	goto loser;

    if (trustRef)
    {
	*trustRef = trust;
    }
    else
    {
	SecTrustResultType result;
	/* The caller doesn't want a SecTrust object, so let's evaluate it for them. */
	rv = SecTrustEvaluate(trust, &result);
	if (rv)
	    goto loser;

	switch (result)
	{
	case kSecTrustResultProceed:
	case kSecTrustResultUnspecified:
	    /* TP Verification succeeded and there was either a UserTurst entry
	       telling us to procceed, or no user trust setting was specified. */
	    CFRelease(trust);
	    break;
	default:
	    PORT_SetError(SEC_ERROR_UNTRUSTED_CERT);
	    rv = SECFailure;
	    goto loser;
	    break;
	}
    }

    return SECSuccess;
loser:
    if (trust)
	CFRelease(trust);

    return rv;
}

CFTypeRef
CERT_PolicyForCertUsage(SECCertUsage certUsage)
{
    SecPolicySearchRef search;
    SecPolicyRef policy = NULL;
    const CSSM_OID *policyOID;
    OSStatus rv;

    switch (certUsage)
    {
    case certUsageSSLServerWithStepUp:
    case certUsageSSLCA:
    case certUsageVerifyCA:
    case certUsageAnyCA:
	goto loser;
	break;
    case certUsageSSLClient:
    case certUsageSSLServer:
	policyOID = &CSSMOID_APPLE_TP_SSL;
	break;
    case certUsageUserCertImport:
	policyOID = &CSSMOID_APPLE_TP_CSR_GEN;
	break;
    case certUsageStatusResponder:
	policyOID = &CSSMOID_APPLE_TP_REVOCATION_OCSP;
	break;
    case certUsageObjectSigner:
    case certUsageProtectedObjectSigner:
	policyOID = &CSSMOID_APPLE_ISIGN;
	break;
    case certUsageEmailSigner:
    case certUsageEmailRecipient:
	policyOID = &CSSMOID_APPLE_X509_BASIC;
	break;
    }
    rv = SecPolicySearchCreate(CSSM_CERT_X_509v3, policyOID, NULL, &search);
    if (rv)
	goto loser;

    rv = SecPolicySearchCopyNext(search, &policy);
    if (rv)
	goto loser;

loser:
    CFRelease(search);
    return policy;
}
