/*
 * Basic implementation of ITU-T X.690 (07/2002) for parsing BER encoded
 * X.509 certificates
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#ifdef HAVE_UNISTD_H
#  include <unistd.h>
#endif
#ifdef HAVE_STDLIB_H
#  include <stdlib.h>
#endif
#ifdef HAVE_STDARG_H
#  include <stdarg.h>
#endif
#ifdef HAVE_STDIO_H
#  include <stdio.h>
#endif
#ifdef HAVE_STRING_H
#  include <string.h>
#endif

#include "asn1-x509.h"

struct asn1_object {
	unsigned long tag;
	unsigned long size;
	void *contents;

	unsigned long asn1rep_len;
	void *asn1rep;
};

struct x509_object {
	struct asn1_object wholething;
		struct asn1_object certificate;
			struct asn1_object version;
			struct asn1_object serial_number;
			struct asn1_object signature_algo;
			struct asn1_object issuer;
			struct asn1_object validity;
			struct asn1_object subject;
			struct asn1_object pubkeyinfo;
				struct asn1_object pubkey_algoid;
					struct asn1_object pubkey_algo;
					struct asn1_object pubkey_algoparm;
				struct asn1_object pubkey;
};

static int _asn1_x509_read_asn1_object(unsigned char *buf, size_t buflen, va_list *args) {
	unsigned char small_object_size;
	unsigned char *buf_p;
	struct asn1_object *outbuf;

	outbuf = va_arg(*args, struct asn1_object *);

	if (outbuf == NULL) {
		return(0);
	}

	if (buflen == 0) {
		return(-1);
	}

	buf_p = buf;

	outbuf->tag = *buf_p;
	buf_p++;
	buflen--;
	if (buflen == 0) {
		return(-1);
	}

	/* NULL Tag -- no size is required */
	if (outbuf->tag == 0x00) {
		return(_asn1_x509_read_asn1_object(buf_p, buflen, args));
	}

	small_object_size = *buf_p;
	buf_p++;
	buflen--;
	if (buflen == 0) {
		return(-1);
	}

	if ((small_object_size & 0x80) == 0x80) {
		outbuf->size = 0;

		for (small_object_size ^= 0x80; small_object_size; small_object_size--) {
			outbuf->size <<= 8;
			outbuf->size += *buf_p;

			buf_p++;
			buflen--;
			if (buflen == 0) {
				break;
			}
		}
	} else {
		outbuf->size = small_object_size;
	}

	if (outbuf->size > buflen) {
		return(-1);
	}

	outbuf->contents = buf_p;
	outbuf->asn1rep_len = outbuf->size + (buf_p - buf);
	outbuf->asn1rep = buf;

	buf_p += outbuf->size;
	buflen -= outbuf->size;

	return(_asn1_x509_read_asn1_object(buf_p, buflen, args));
}

static int asn1_x509_read_asn1_object(unsigned char *buf, size_t buflen, ...) {
	va_list args;
	int retval;

	va_start(args, buflen);

	retval = _asn1_x509_read_asn1_object(buf, buflen, &args);

	va_end(args);

	return(retval);
}

static int asn1_x509_read_object(unsigned char *buf, size_t buflen, struct x509_object *outbuf) {
	int read_ret;

	read_ret = asn1_x509_read_asn1_object(buf, buflen, &outbuf->wholething, NULL);
	if (read_ret != 0) {
		return(-1);
	}

	read_ret = asn1_x509_read_asn1_object(outbuf->wholething.contents, outbuf->wholething.size, &outbuf->certificate, NULL);
	if (read_ret != 0) {
		return(-1);
	}

	read_ret = asn1_x509_read_asn1_object(outbuf->certificate.contents, outbuf->certificate.size, &outbuf->version, &outbuf->serial_number, &outbuf->signature_algo, &outbuf->issuer, &outbuf->validity, &outbuf->subject, &outbuf->pubkeyinfo, NULL);
	if (read_ret != 0) {
		return(-1);
	}

	read_ret = asn1_x509_read_asn1_object(outbuf->pubkeyinfo.contents, outbuf->pubkeyinfo.size, &outbuf->pubkey_algoid, &outbuf->pubkey, NULL);
	if (read_ret != 0) {
		return(-1);
	}

	return(0);
}

ssize_t x509_to_issuer(void *x509_der_buf, size_t x509_der_buf_len, void **outbuf) {
	struct x509_object x509;
	int read_ret;

	read_ret = asn1_x509_read_object(x509_der_buf, x509_der_buf_len, &x509);
	if (read_ret != 0) {
		return(-1);
	}

	if (outbuf) {
		*outbuf = x509.issuer.asn1rep;
	}

	return(x509.issuer.asn1rep_len);
}

ssize_t x509_to_subject(void *x509_der_buf, size_t x509_der_buf_len, void **outbuf) {
	struct x509_object x509;
	int read_ret;

	read_ret = asn1_x509_read_object(x509_der_buf, x509_der_buf_len, &x509);
	if (read_ret != 0) {
		return(-1);
	}

	if (outbuf) {
		*outbuf = x509.subject.asn1rep;
	}

	return(x509.subject.asn1rep_len);
}

static ssize_t x509_to_serial(void *x509_der_buf, size_t x509_der_buf_len, void **outbuf) {
	struct x509_object x509;
	int read_ret;

	read_ret = asn1_x509_read_object(x509_der_buf, x509_der_buf_len, &x509);
	if (read_ret != 0) {
		return(-1);
	}

	if (outbuf) {
		*outbuf = x509.serial_number.asn1rep;
	}

	return(x509.serial_number.asn1rep_len);
}

static ssize_t x509_to_pubkey(void *x509_der_buf, size_t x509_der_buf_len, void **outbuf) {
	struct x509_object x509;
	int read_ret;

	read_ret = asn1_x509_read_object(x509_der_buf, x509_der_buf_len, &x509);
	if (read_ret != 0) {
		return(-1);
	}

	*outbuf = x509.pubkey.contents;
	return(x509.pubkey.size);
}

static ssize_t x509_to_modulus(void *x509_der_buf, size_t x509_der_buf_len, void **outbuf) {
	struct asn1_object null, pubkey, modulus, exponent;
	struct x509_object x509;
	int read_ret;

	read_ret = asn1_x509_read_object(x509_der_buf, x509_der_buf_len, &x509);
	if (read_ret != 0) {
		return(-1);
	}

	/* The structure of "pubkey" is specified in PKCS #1 */
	read_ret = asn1_x509_read_asn1_object(x509.pubkey.contents, x509.pubkey.size, &null, &pubkey, NULL);
	if (read_ret != 0) {
		return(-1);
	}

	read_ret = asn1_x509_read_asn1_object(pubkey.contents, pubkey.size, &modulus, &exponent, NULL);
	if (read_ret != 0) {
		return(-1);
	}

	if (outbuf) {
		*outbuf = modulus.contents;
	}

	return(modulus.size);
}

static ssize_t x509_to_exponent(void *x509_der_buf, size_t x509_der_buf_len, void **outbuf) {
	struct asn1_object null, pubkey, modulus, exponent;
	struct x509_object x509;
	int read_ret;

	read_ret = asn1_x509_read_object(x509_der_buf, x509_der_buf_len, &x509);
	if (read_ret != 0) {
		return(-1);
	}

	/* The structure of "pubkey" is specified in PKCS #1 */
	read_ret = asn1_x509_read_asn1_object(x509.pubkey.contents, x509.pubkey.size, &null, &pubkey, NULL);
	if (read_ret != 0) {
		return(-1);
	}

	read_ret = asn1_x509_read_asn1_object(pubkey.contents, pubkey.size, &modulus, &exponent, NULL);
	if (read_ret != 0) {
		return(-1);
	}

	if (outbuf) {
		*outbuf = exponent.contents;
	}

	return(exponent.size);
}

static ssize_t x509_to_keysize(void *x509_der_buf, size_t x509_der_buf_len) {
	struct asn1_object null, pubkey, modulus, exponent;
	struct x509_object x509;
	int read_ret;

	read_ret = asn1_x509_read_object(x509_der_buf, x509_der_buf_len, &x509);
	if (read_ret != 0) {
		return(-1);
	}

	/* The structure of "pubkey" is specified in PKCS #1 */
	read_ret = asn1_x509_read_asn1_object(x509.pubkey.contents, x509.pubkey.size, &null, &pubkey, NULL);
	if (read_ret != 0) {
		return(-1);
	}

	read_ret = asn1_x509_read_asn1_object(pubkey.contents, pubkey.size, &modulus, &exponent, NULL);
	if (read_ret != 0) {
		return(-1);
	}

	return(modulus.size - 1);
}

/*
 * http://www.blackberry.com/developers/docs/4.6.0api/javax/microedition/pki/Certificate.html
 */
static const char *_x509_objectid_to_label_string(void *buf, size_t buflen) {
	switch (buflen) {
		case 3:
			if (memcmp(buf, "\x55\x04\x03", 3) == 0) {
				return("CN");
			}
			if (memcmp(buf, "\x55\x04\x04", 3) == 0) {
				return("SN");
			}
			if (memcmp(buf, "\x55\x04\x06", 3) == 0) {
				return("C");
			}
			if (memcmp(buf, "\x55\x04\x07", 3) == 0) {
				return("L");
			}
			if (memcmp(buf, "\x55\x04\x08", 3) == 0) {
				return("ST");
			}
			if (memcmp(buf, "\x55\x04\x09", 3) == 0) {
				return("STREET");
			}
			if (memcmp(buf, "\x55\x04\x0A", 3) == 0) {
				return("O");
			}
			if (memcmp(buf, "\x55\x04\x0B", 3) == 0) {
				return("OU");
			}
			break;
		case 9:
			if (memcmp(buf, "\x2A\x86\x48\x86\xF7\x0D\x01\x09\x01", 9) == 0) {
				return("EmailAddress");
			}
			break;
	}

	return("???");
}

static ssize_t x509_dn_to_string(void *asn1_der_buf, size_t asn1_der_buf_len, char *outbuf, size_t outbuf_len, char *matchlabel) {
	struct asn1_object whole_thing, current_set, current_seq;
	struct asn1_object label, value;
	const char *label_str;
	ssize_t snprintf_ret, retval;
	char *outbuf_s;
	int read_ret;
	int offset;

	if (outbuf == NULL) {
		return(-1);
	}

	if (outbuf_len == 0 || asn1_der_buf_len == 0 || asn1_der_buf == NULL) {
		return(0);
	}

	read_ret = asn1_x509_read_asn1_object(asn1_der_buf, asn1_der_buf_len, &whole_thing, NULL);
	if (read_ret != 0) {
		return(-1);
	}

	/* Terminate string, in case no valid elements are found we still return a valid string */
	*outbuf = '\0';
	outbuf_s = outbuf;

	offset = 0;
	while (1) {
		read_ret = asn1_x509_read_asn1_object(whole_thing.contents + offset, whole_thing.size - offset, &current_set, NULL);
		if (read_ret != 0) {
			break;
		}

		offset += current_set.size + 2;

		read_ret = asn1_x509_read_asn1_object(current_set.contents, current_set.size, &current_seq, NULL);
		if (read_ret != 0) {
			break;
		}

		read_ret = asn1_x509_read_asn1_object(current_seq.contents, current_seq.size, &label, &value, NULL);

		label_str = _x509_objectid_to_label_string(label.contents, label.size);

		/* If the user requested only certain labels, exclude others */
		if (matchlabel) {
			if (strcmp(matchlabel, label_str) != 0) {
				continue;
			}
		}

		/* If the user requested only certain labels, don't include them in the reply */
		if (matchlabel) {
			snprintf_ret = snprintf(outbuf, outbuf_len, "%.*s, ", (unsigned int) value.size, (char *) value.contents);
		} else {
			snprintf_ret = snprintf(outbuf, outbuf_len, "%s=%.*s, ", label_str, (unsigned int) value.size, (char *) value.contents);
		}
		if (snprintf_ret < 0) {
			break;
		}

		if (snprintf_ret > outbuf_len) {
			snprintf_ret = outbuf_len;
		}

		outbuf += snprintf_ret;
		outbuf_len -= snprintf_ret;

		if (outbuf_len < 2) {
			break;
		}
	}

	retval = outbuf - outbuf_s;

	/* Remove trailing ", " added by cumulative process, if found. */
	if (retval > 2) {
		if (outbuf_s[retval - 2] == ',') {
			outbuf_s[retval - 2] = '\0';
			retval -= 2;
		}
	}

	return(retval);
}
