/* Simple ASN.1 parser
 * Copyright (C) 2000-2004 Andreas Steffen, Zuercher Hochschule Winterthur
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 */

#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <openswan.h>

#include "sysdep.h"
#include "constants.h"
#include "oswlog.h"
#include "oswtime.h"
#include "oswalloc.h"
#include "asn1.h"
#include "oid.h"

#define TIME_MAX       0x7fffffff

/*  If the oid is listed in the oid_names table then the corresponding
 *  position in the oid_names table is returned otherwise -1 is returned
 */
int
known_oid(chunk_t object)
{
    int oid = 0;

    while (object.len)
    {
	if (oid_names[oid].octet == *object.ptr)
	{
	    if (--object.len == 0 || oid_names[oid].down == 0)
	    {
		return oid;          /* found terminal symbol */
	    }
	    else
	    {
		object.ptr++; oid++; /* advance to next hex octet */
	    }
	}
	else
	{
	    if (oid_names[oid].next)
		oid = oid_names[oid].next;
	    else
		return OID_UNKNOWN;
	}
    }
    return -1;
}

/*
 *  Decodes the length in bytes of an ASN.1 object
 */
u_int
asn1_length(chunk_t *blob)
{
    u_char n;
    size_t len;

    /* advance from tag field on to length field */
    blob->ptr++;
    blob->len--;

    /* read first octet of length field */
    n = *blob->ptr++;
    blob->len--;

    if ((n & 0x80) == 0) { /* single length octet */
	if (n > blob->len) {
	    DBG(DBG_PARSING,
		DBG_log("number of length octets is larger than ASN.1 object")
	    )
	    return ASN1_INVALID_LENGTH;
	}
	return n;
    }

    /* composite length, determine number of length octets */
    n &= 0x7f;

    if (n > blob->len)
    {
	DBG(DBG_PARSING,
	    DBG_log("number of length octets is larger than ASN.1 object")
	)
	return ASN1_INVALID_LENGTH;
    }

    if (n > sizeof(len))
    {
	DBG(DBG_PARSING,
	    DBG_log("number of length octets is larger than limit of %d octets"
		, (int) sizeof(len))
	)
	return ASN1_INVALID_LENGTH;
    }

    len = 0;
    
    while (n-- > 0)
    {
	len = 256*len + *blob->ptr++;
	blob->len--;
    }
    if (len > blob->len)
    {
	DBG(DBG_PARSING,
	    DBG_log("length is larger than remaining blob size")
	)
	return ASN1_INVALID_LENGTH;
    }

    return len;
}

/*
 * codes ASN.1 lengths up to a size of 16'777'215 bytes
 */
void
code_asn1_length(size_t length, chunk_t *code)
{
    if (length < 128)
    {
	code->ptr[0] = length;
	code->len = 1;
    }
    else if (length < 256)
    {
	code->ptr[0] = 0x81;
	code->ptr[1] = (u_char) length;
	code->len = 2;
    }
    else if (length < 65536)
    {
	code->ptr[0] = 0x82;
	code->ptr[1] = length >> 8;
	code->ptr[2] = length & 0x00ff;
	code->len = 3;
    }
    else
    {
	code->ptr[0] = 0x83;
	code->ptr[1] = length >> 16;
	code->ptr[2] = (length >> 8) & 0x00ff;
	code->ptr[3] = length & 0x0000ff;
	code->len = 4;
    }
}

/*
 * build an empty asn.1 object with tag and length fields already filled in
 */
u_char*
build_asn1_object(chunk_t *object, asn1_t type, size_t datalen)
{
    u_char length_buf[4];
    chunk_t length = { length_buf, 0 };
    u_char *pos;

    /* code the asn.1 length field */
    code_asn1_length(datalen, &length);

    /* allocate memory for the asn.1 TLV object */
    object->len = 1 + length.len + datalen;
    object->ptr = alloc_bytes(object->len, "asn1 object");

    /* set position pointer at the start of the object */
    pos = object->ptr;

    /* copy the asn.1 tag field and advance the pointer */
   *pos++ = type;
   
   /* copy the asn.1 length field and advance the pointer */
   chunkcpy(pos, length);

   return pos;
}

/*
 * build an empty asn.1 object with explicit tags and length fields already filled in
 */
u_char*
build_asn1_explicit_object(chunk_t *object, asn1_t outer_type, asn1_t inner_type
,size_t datalen)
{
    u_char length_buf[4];
    chunk_t length = { length_buf, 0 };
    u_char *pos;

    /* code the inner asn.1 length field */
    code_asn1_length(datalen, &length);

    /*create the outer asn.1 object */
    pos = build_asn1_object(object, outer_type, 1 + length.len + datalen);

    /* copy the inner asn.1 tag field and advance the pointer */
   *pos++ = inner_type;

   /* copy the inner asn.1 length field and advance the pointer */
   chunkcpy(pos, length);

   return pos;
}


/*
 *  determines if a character string is of type ASN.1 printableString
 */
bool
is_printablestring(chunk_t str)
{
    const char printablestring_charset[] =
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 '()+,-./:=?";
    u_int i;

    for (i = 0; i < str.len; i++)
    {
	if (strchr(printablestring_charset, str.ptr[i]) == NULL)
	    return FALSE;
    }
    return TRUE;
}

/*
 *  Converts ASN.1 UTCTIME or GENERALIZEDTIME into calender time
 */
time_t
asn1totime(const chunk_t *utctime, asn1_t type)
{
    struct tm t;
    time_t tc, tz_offset;
    char *eot = NULL;

    if ((eot = memchr(utctime->ptr, 'Z', utctime->len)) != NULL)
    {
	tz_offset = 0; /* Zulu time with a zero time zone offset */
    }
    else if ((eot = memchr(utctime->ptr, '+', utctime->len)) != NULL)
    {
	int tz_hour, tz_min;

	if (sscanf(eot+1, "%2d%2d", &tz_hour, &tz_min) != 2)
	{
	    return 0; /* error in positive timezone offset format */
	}

	tz_offset = 3600*tz_hour + 60*tz_min;  /* positive time zone offset */
    }
    else if ((eot = memchr(utctime->ptr, '-', utctime->len)) != NULL)
    {
	int tz_hour, tz_min;

	if (sscanf(eot+1, "%2d%2d", &tz_hour, &tz_min) != 2)
	{
	     return 0; /* error in negative timezone offset format */
	}
	tz_offset = -3600*tz_hour - 60*tz_min;  /* negative time zone offset */
    }
    else
    {
	return 0; /* error in time format */
    }

    /* parse ASN.1 time string */
    {
	const char* format = (type == ASN1_UTCTIME)? "%2d%2d%2d%2d%2d":
						     "%4d%2d%2d%2d%2d";

	if (sscanf((const char *)utctime->ptr, format, &t.tm_year, &t.tm_mon, &t.tm_mday,
					 &t.tm_hour, &t.tm_min) != 5)
	{
	    return 0; /* error in time st [yy]yymmddhhmm time format */
	}

    }

    /* is there a seconds field? */
    if ((eot - (char *)utctime->ptr) == ((type == ASN1_UTCTIME)?12:14))
    {
	if (sscanf(eot-2, "%2d", &t.tm_sec) != 1)
	{
	    return 0; /* error in ss seconds field format */
	}

    }
    else
    {
	t.tm_sec = 0;
    }

    /* representation of year */
    if (t.tm_year >= 1900)
    {
	t.tm_year -= 1900;
    }
    else if (t.tm_year >= 100)
    {
	return 0;
    }
    else if (t.tm_year < 50)
    {
	t.tm_year += 100;
    }

    if (t.tm_mon < 1 || t.tm_mon > 12)
    {
	return 0; /* error in month format */
    }
    /* representation of month 0..11 in struct tm */
    t.tm_mon--;

    /* set daylight saving time to off */
    t.tm_isdst = 0;

    tc = mktime(&t);
    /* if no conversion overflow occurred, compensate timezone */
    return (tc == -1) ? TIME_MAX : tc - timezone - tz_offset;
}

/*
 * Initializes the internal context of the ASN.1 parser
 */
void
asn1_init(asn1_ctx_t *ctx, chunk_t blob, u_int level0,
	bool implicit, u_int cond)
{
    ctx->blobs[0] = blob;
    ctx->level0   = level0;
    ctx->implicit = implicit;
    ctx->cond     = cond;
    memset(ctx->loopAddr, '\0', sizeof(ctx->loopAddr));
}

/*
 * Parses and extracts the next ASN.1 object
 */
bool
extract_object(asn1Object_t const *objects,
	u_int *objectID, chunk_t *object, u_int *level, asn1_ctx_t *ctx)
{
    asn1Object_t obj = objects[*objectID];
    chunk_t *blob;
    chunk_t *blob1;
    u_char *start_ptr;

    *object = empty_chunk;

    if (obj.flags & ASN1_END)  /* end of loop or option found */
    {
	if (ctx->loopAddr[obj.level] && ctx->blobs[obj.level+1].len > 0)
	{
	    *objectID = ctx->loopAddr[obj.level]; /* another iteration */
	    obj = objects[*objectID];
	}
	else
	{
	    ctx->loopAddr[obj.level] = 0;         /* exit loop or option*/
	    return TRUE;
	}
    }

    *level = ctx->level0 + obj.level;
    blob = ctx->blobs + obj.level;
    blob1 = blob + 1;
    start_ptr = blob->ptr;

   /* handle ASN.1 defaults values */

    if ((obj.flags & ASN1_DEF)
    && (blob->len == 0 || *start_ptr != obj.type) )
    {
	/* field is missing */
	DBG(DBG_PARSING,
	    DBG_log("L%d - %s:", *level, obj.name);
	)
	if (obj.type & ASN1_CONSTRUCTED)
	{
	    (*objectID)++ ;  /* skip context-specific tag */
	}
	return TRUE;
    }

    /* handle ASN.1 options */

    if ( (obj.flags & ASN1_OPT) &&
	 ( blob->len == 0 || *start_ptr != obj.type) )
    {
        /* advance to end of missing option field */
	do
        
	    (*objectID)++;
	  while (!((objects[*objectID].flags & ASN1_END) &&
		      (objects[*objectID].level == obj.level )));
	return TRUE;
    }

     /* an ASN.1 object must possess at least a tag and length field */

     if (blob->len < 2)
     {
       DBG(DBG_PARSING,
           DBG_log("L%d - %s:  ASN.1 object smaller than 2 octets",
                   ctx->level0+obj.level, obj.name);
       )
       return FALSE;
     }


    blob1->len = asn1_length(blob);

    if (blob1->len == ASN1_INVALID_LENGTH)
    {
	DBG(DBG_PARSING,
	    DBG_log("L%d - %s:  length of ASN1 object invalid or too large",
                   *level, obj.name);
	)
	return FALSE;
    }

    blob1->ptr = blob->ptr;
    blob->ptr += blob1->len;
    blob->len -= blob1->len;

    /* return raw ASN.1 object without prior type checking */

    if (obj.flags & ASN1_RAW)
    {
	DBG(DBG_PARSING,
	    DBG_log("L%d - %s:", *level, obj.name);
        )
	object->ptr = start_ptr;
	object->len = (size_t)(blob->ptr - start_ptr);
	return TRUE;
    }

    if (*start_ptr != obj.type && !(ctx->implicit && *objectID == 0))
    {
	DBG(DBG_PARSING,
	    DBG_log("L%d - %s: ASN1 tag 0x%02x expected, but is 0x%02x",
		*level, obj.name, obj.type, *start_ptr);
	    DBG_dump("", start_ptr, (u_int)(blob->ptr - start_ptr));
	)
	return FALSE;
    }

    DBG(DBG_PARSING,
	DBG_log("L%d - %s:", ctx->level0+obj.level, obj.name);
    )

    /* In case of "SEQUENCE OF" or "SET OF" start a loop */

    if (obj.flags & ASN1_LOOP)
    {
	if (blob1->len > 0)
	{
	    /* at least one item, start the loop */
	    ctx->loopAddr[obj.level] = *objectID + 1;
	}
	else
	{
	    /* no items, advance directly to end of loop */
	    do
		(*objectID)++;
	    while (!((objects[*objectID].flags & ASN1_END)
		  && (objects[*objectID].level == obj.level)));
	    return TRUE;
	}
    }


    if (obj.flags & ASN1_OBJ)
    {
	object->ptr = start_ptr;
	object->len = (size_t)(blob->ptr - start_ptr);
	DBG(ctx->cond,
	    DBG_dump_chunk("", *object);
	)
    }
    else if (obj.flags & ASN1_BODY)
    {
	int oid;
	*object = *blob1;

	switch (obj.type)
	{
	case ASN1_OID:
	    oid = known_oid(*object);
	    if (oid != OID_UNKNOWN)
	    {
		DBG(DBG_PARSING,
		   DBG_log("  '%s'",oid_names[oid].name);
		)
		return TRUE;
	    }
	    break;
	case ASN1_UTF8STRING:
	case ASN1_IA5STRING:
	case ASN1_PRINTABLESTRING:
	case ASN1_T61STRING:
	case ASN1_VISIBLESTRING:
	    DBG(DBG_PARSING,
		DBG_log("  '%.*s'", (int)object->len, object->ptr);
	    )
	    return TRUE;
	case ASN1_UTCTIME:
	case ASN1_GENERALIZEDTIME:
	    DBG(DBG_PARSING,
		time_t timep = asn1totime(object, obj.type);
		char tbuf[TIMETOA_BUF];
		DBG_log("  '%s'", timetoa(&timep, TRUE, tbuf, sizeof(tbuf)));
	    )
	    return TRUE;

	default:
	    break;
	}
	DBG(ctx->cond,
	    DBG_dump_chunk("", *object);
	)
    }
    return TRUE;
}

/*
 *  tests if a blob contains a valid ASN.1 set or sequence
 */
bool
is_asn1(chunk_t blob)
{
    u_int len;
    u_char tag = *blob.ptr;

    if (tag != ASN1_SEQUENCE && tag != ASN1_SET)
    {
	DBG(DBG_PARSING,
	    DBG_log("  file content is not binary ASN.1");
	)
	return FALSE;
    }
    len = asn1_length(&blob);
    if (len != blob.len)
    {
	DBG(DBG_PARSING,
	    DBG_log("  file size does not match ASN.1 coded length");
	)
	return FALSE;
    }
    return TRUE;
}
