/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Netscape security libraries.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 2004
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/*
 * Support for various policy related extensions
 *
 * $Id: pppolicy.c,v 1.3 2005/02/22 20:02:22 wtchang%redhat.com Exp $
 */

#include "seccomon.h"
#include "secport.h"
#include "secder.h"
#include "cert.h"
#include "secoid.h"
#include "secasn1.h"
#include "secerr.h"
#include "nspr.h"
#include "secutil.h"

/* This implementation is derived from the one in nss/lib/certdb/policyxtn.c .
** The chief difference is the addition of the OPTIONAL flag to many 
** parts.  The idea is to be able to parse and print as much of the 
** policy extension as possible, even if some parts are invalid.
**
** If this approach still is unable to decode policy extensions that
** contain invalid parts, then the next approach will be to parse 
** the PolicyInfos as a SEQUENCE of ANYs, and then parse each of them 
** as PolicyInfos, with the PolicyQualifiers being ANYs, and finally
** parse each of the PolicyQualifiers.
*/

static const SEC_ASN1Template secu_PolicyQualifierTemplate[] = {
    { SEC_ASN1_SEQUENCE,
	  0, NULL, sizeof(CERTPolicyQualifier) },
    { SEC_ASN1_OBJECT_ID,
	  offsetof(CERTPolicyQualifier, qualifierID) },
    { SEC_ASN1_ANY | SEC_ASN1_OPTIONAL,
	  offsetof(CERTPolicyQualifier, qualifierValue) },
    { 0 }
};

static const SEC_ASN1Template secu_PolicyInfoTemplate[] = {
    { SEC_ASN1_SEQUENCE,
	  0, NULL, sizeof(CERTPolicyInfo) },
    { SEC_ASN1_OBJECT_ID,
	  offsetof(CERTPolicyInfo, policyID) },
    { SEC_ASN1_SEQUENCE_OF | SEC_ASN1_OPTIONAL,
	  offsetof(CERTPolicyInfo, policyQualifiers),
	  secu_PolicyQualifierTemplate },
    { 0 }
};

static const SEC_ASN1Template secu_CertificatePoliciesTemplate[] = {
    { SEC_ASN1_SEQUENCE_OF,
	  offsetof(CERTCertificatePolicies, policyInfos),
	  secu_PolicyInfoTemplate, sizeof(CERTCertificatePolicies)  }
};


static CERTCertificatePolicies *
secu_DecodeCertificatePoliciesExtension(SECItem *extnValue)
{
    PRArenaPool *arena = NULL;
    SECStatus rv;
    CERTCertificatePolicies *policies;
    CERTPolicyInfo **policyInfos, *policyInfo;
    CERTPolicyQualifier **policyQualifiers, *policyQualifier;
    SECItem newExtnValue;
    
    /* make a new arena */
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    
    if ( !arena ) {
	goto loser;
    }

    /* allocate the certifiate policies structure */
    policies = PORT_ArenaZNew(arena, CERTCertificatePolicies);
    if ( policies == NULL ) {
	goto loser;
    }
    
    policies->arena = arena;

    /* copy the DER into the arena, since Quick DER returns data that points
       into the DER input, which may get freed by the caller */
    rv = SECITEM_CopyItem(arena, &newExtnValue, extnValue);
    if ( rv != SECSuccess ) {
	goto loser;
    }

    /* decode the policy info */
    rv = SEC_QuickDERDecodeItem(arena, policies, 
                                secu_CertificatePoliciesTemplate,
			        &newExtnValue);

    if ( rv != SECSuccess ) {
	goto loser;
    }

    /* initialize the oid tags */
    policyInfos = policies->policyInfos;
    while (policyInfos != NULL && *policyInfos != NULL ) {
	policyInfo = *policyInfos;
	policyInfo->oid = SECOID_FindOIDTag(&policyInfo->policyID);
	policyQualifiers = policyInfo->policyQualifiers;
	while ( policyQualifiers && *policyQualifiers != NULL ) {
	    policyQualifier = *policyQualifiers;
	    policyQualifier->oid =
		SECOID_FindOIDTag(&policyQualifier->qualifierID);
	    policyQualifiers++;
	}
	policyInfos++;
    }

    return(policies);
    
loser:
    if ( arena != NULL ) {
	PORT_FreeArena(arena, PR_FALSE);
    }
    
    return(NULL);
}


static char *
itemToString(SECItem *item)
{
    char *string;

    string = PORT_ZAlloc(item->len+1);
    if (string == NULL) return NULL;
    PORT_Memcpy(string,item->data,item->len);
    string[item->len] = 0;
    return string;
}

static SECStatus
secu_PrintUserNoticeQualifier(FILE *out, SECItem * qualifierValue,
                              char *msg, int level)
{
    CERTUserNotice *userNotice = NULL;
    if (qualifierValue)
	userNotice = CERT_DecodeUserNotice(qualifierValue);
    if (userNotice) {
	if (userNotice->noticeReference.organization.len != 0) {
            char *string = 
	            itemToString(&userNotice->noticeReference.organization);
            SECItem **itemList = userNotice->noticeReference.noticeNumbers;

	    while (itemList && *itemList) {
		SECU_PrintInteger(out,*itemList,string,level+1);
	        itemList++;
	    }
	    PORT_Free(string);
	}
	if (userNotice->displayText.len != 0) {
	    SECU_PrintString(out,&userNotice->displayText,
			     "Display Text", level+1);
	}
	CERT_DestroyUserNotice(userNotice);
	return SECSuccess;
    }
    return SECFailure;	/* caller will print this value */
}

static SECStatus
secu_PrintPolicyQualifier(FILE *out,CERTPolicyQualifier *policyQualifier,
			  char *msg,int level)
{
   SECStatus rv;
   SECItem * qualifierValue = &policyQualifier->qualifierValue;

   SECU_PrintObjectID(out, &policyQualifier->qualifierID , 
					"Policy Qualifier Name", level);
   if (!qualifierValue->data) {
	SECU_Indent(out, level);
	fprintf(out,"Error: missing qualifier\n");
   } else 
   switch (policyQualifier->oid) {
   case SEC_OID_PKIX_USER_NOTICE_QUALIFIER:
       rv = secu_PrintUserNoticeQualifier(out, qualifierValue, msg, level);
       if (SECSuccess == rv)
	   break;
       /* fall through on error */
   case SEC_OID_PKIX_CPS_POINTER_QUALIFIER:
   default:
	SECU_PrintAny(out, qualifierValue, "Policy Qualifier Data", level);
	break;
   }
   return SECSuccess;
}

static SECStatus
secu_PrintPolicyInfo(FILE *out,CERTPolicyInfo *policyInfo,char *msg,int level)
{
   CERTPolicyQualifier **policyQualifiers;

   policyQualifiers = policyInfo->policyQualifiers;
   SECU_PrintObjectID(out, &policyInfo->policyID , "Policy Name", level);
   
   while (policyQualifiers && *policyQualifiers != NULL) {
	secu_PrintPolicyQualifier(out,*policyQualifiers,"",level+1);
	policyQualifiers++;
   }
   return SECSuccess;
}

void
SECU_PrintPolicy(FILE *out, SECItem *value, char *msg, int level)
{
   CERTCertificatePolicies *policies = NULL;
   CERTPolicyInfo **policyInfos;

   if (msg) {
	SECU_Indent(out, level);
	fprintf(out,"%s: \n",msg);
	level++;
   }
   policies = secu_DecodeCertificatePoliciesExtension(value);
   if (policies == NULL) {
	SECU_PrintAny(out, value, "Invalid Policy Data", level);
	return;
   }

   policyInfos = policies->policyInfos;
   while (policyInfos && *policyInfos != NULL) {
	secu_PrintPolicyInfo(out,*policyInfos,"",level);
	policyInfos++;
   }

   CERT_DestroyCertificatePoliciesExtension(policies);
}


void
SECU_PrintPrivKeyUsagePeriodExtension(FILE *out, SECItem *value, 
			              char *msg, int level)
{
    CERTPrivKeyUsagePeriod * prd;
    PLArenaPool * arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);

    if ( !arena ) {
	goto loser;
    }
    prd = CERT_DecodePrivKeyUsagePeriodExtension(arena, value);
    if (!prd) {
	goto loser;
    }
    if (prd->notBefore.data) {
	SECU_PrintGeneralizedTime(out, &prd->notBefore, "Not Before", level);
    }
    if (prd->notAfter.data) {
	SECU_PrintGeneralizedTime(out, &prd->notAfter,  "Not After ", level);
    }
    if (!prd->notBefore.data && !prd->notAfter.data) {
	SECU_Indent(out, level);
	fprintf(out, "Error: notBefore or notAfter MUST be present.\n");
loser:
	SECU_PrintAny(out, value, msg, level);
    }
    if (arena) {
	PORT_FreeArena(arena, PR_FALSE);
    }
}
