/* 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 <libreswan.h>

#include "sysdep.h"
#include "constants.h"
#include "lswlog.h"
#include "lswtime.h"
#include "lswalloc.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
 */
size_t 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 */
	if (sscanf((const char *)utctime->ptr,
		   (type ==
		    ASN1_UTCTIME) ? "%2d%2d%2d%2d%2d" : "%4d%2d%2d%2d%2d",
		   &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;
}
