/**
 * Copyright (C) 2020 Gary Lin <glin@suse.com>
 *
 * 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 3 of the License, or
 * (at your option) any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations
 * including the two.
 *
 * You must obey the GNU General Public License in all respects
 * for all of the code used other than OpenSSL.  If you modify
 * file(s) with this exception, you may extend this exception to your
 * version of the file(s), but you are not obligated to do so.  If you
 * do not wish to do so, delete this exception statement from your
 * version.  If you delete this exception statement from all source
 * files in the program, then also delete it here.
 */

#include <stdio.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>

#include "efi_x509.h"

int
print_x509 (const uint8_t *cert, const int cert_size)
{
	X509 *X509cert;
	EVP_MD_CTX *ctx;
	const EVP_MD *md;
	unsigned int md_len;
	const unsigned char *in = (const unsigned char *)cert;
	unsigned char fingerprint[EVP_MAX_MD_SIZE];

	X509cert = d2i_X509 (NULL, &in, cert_size);
	if (X509cert == NULL) {
		fprintf (stderr, "Invalid X509 certificate\n");
		return -1;
	}

	md = EVP_get_digestbyname ("SHA1");
	if(md == NULL) {
		fprintf (stderr, "Failed to get SHA1 digest\n");
		goto cleanup_cert;
	}

	ctx = EVP_MD_CTX_create ();
	if (ctx == NULL) {
		fprintf (stderr, "Failed to create digest context\n");
	        goto cleanup_cert;
	}

	if (!EVP_DigestInit_ex (ctx, md, NULL)) {
		fprintf (stderr, "Failed to initialize digest context\n");
		goto cleanup_ctx;
	}

	if (!EVP_DigestUpdate (ctx, cert, cert_size)) {
		fprintf (stderr, "Failed to hash into the digest context\n");
		goto cleanup_ctx;
	}

	if (!EVP_DigestFinal_ex (ctx, fingerprint, &md_len)) {
		fprintf (stderr, "Failed to get digest value\n");
		goto cleanup_ctx;
	}

	printf ("SHA1 Fingerprint: ");
	for (unsigned int i = 0; i < md_len; i++) {
		printf ("%02x", fingerprint[i]);
		if (i < md_len - 1)
			printf (":");
	}
	printf ("\n");
	X509_print_fp (stdout, X509cert);

cleanup_ctx:
	EVP_MD_CTX_destroy (ctx);
cleanup_cert:
	X509_free (X509cert);

	return 0;
}

int
is_valid_cert (const uint8_t *cert, const uint32_t cert_size)
{
	X509 *X509cert;

	if (cert == NULL)
		return 0;

	X509cert = d2i_X509 (NULL, &cert, cert_size);
	if (X509cert == NULL)
		return 0;

	X509_free (X509cert);

	return 1;
}

/**
 * Check whether the given CA cert is the immediate CA of the given cert
 **/
int
is_immediate_ca (const uint8_t *cert, const uint32_t cert_size,
		 const uint8_t *ca_cert, const uint32_t ca_cert_size)
{
	X509 *X509cert = NULL;
	X509 *X509ca = NULL;
	X509_STORE *cert_store = NULL;
	X509_STORE_CTX *cert_ctx = NULL;
	int ret = 0;

	if (cert == NULL || ca_cert == NULL)
		return 0;

	if (EVP_add_digest (EVP_md5 ()) == 0)
		return 0;
	if (EVP_add_digest (EVP_sha1 ()) == 0)
		return 0;
	if (EVP_add_digest (EVP_sha256 ()) == 0)
		return 0;

	X509cert = d2i_X509 (NULL, &cert, cert_size);
	if (X509cert == NULL)
		return 0;

	X509ca = d2i_X509 (NULL, &ca_cert, ca_cert_size);
	if (X509ca == NULL)
		goto err;

	cert_store = X509_STORE_new ();
	if (cert_store == NULL)
		goto err;

	if (X509_STORE_add_cert (cert_store, X509ca) == 0)
		goto err;

	/* Follow edk2 CryptoPkg to allow partial certificate chains and
	 * disable time checks */
	X509_STORE_set_flags (cert_store,
			      X509_V_FLAG_PARTIAL_CHAIN | X509_V_FLAG_NO_CHECK_TIME);

	cert_ctx = X509_STORE_CTX_new ();
	if (cert_ctx == NULL)
		goto err;

	if (X509_STORE_CTX_init (cert_ctx, cert_store, X509cert, NULL) == 0)
		goto err;

	/* Verify the cert */
	ret = X509_verify_cert (cert_ctx);
	/* Treat the exceptional error as FALSE */
	if (ret < 0)
		ret = 0;
	X509_STORE_CTX_cleanup (cert_ctx);

err:
	if (X509cert)
		X509_free (X509cert);

	if (X509ca)
		X509_free (X509ca);

	if (cert_store)
		X509_STORE_free (cert_store);

	if (cert_store)
		X509_STORE_CTX_free (cert_ctx);

	return ret;
}

/**
 * Get the Subject Key Identifier of the given certificate
 *
 * This function allocates the SKID string and the caller is responsible to
 * free the string.
 *
 * Return value:
 * -  0 : Success
 * - -1 : Error
 */
int
get_cert_skid(const uint8_t *cert, const uint32_t cert_size, char **skid)
{
	X509 *X509cert;
	const ASN1_OCTET_STRING *asn1_id;
	const uint8_t *data;
	int data_len, i;
	char *id_str, *ptr;
	int ret = -1;

	X509cert = d2i_X509 (NULL, &cert, cert_size);
	if (X509cert == NULL) {
		fprintf (stderr, "invalid x509 certificate\n");
		goto out;
	}

	asn1_id = X509_get0_subject_key_id (X509cert);
	if (asn1_id == NULL) {
		fprintf (stderr, "Failed to get Subject Key ID\n");
		goto out;
	}

	data = ASN1_STRING_get0_data (asn1_id);
	data_len = ASN1_STRING_length (asn1_id);

	id_str = malloc (data_len*2 + 1);
	if (id_str == NULL) {
		fprintf (stderr, "Failed to allocated id string\n");
		goto out;
	}

	ptr = id_str;
	for (i = 0; i < data_len; i++) {
		snprintf (ptr, 3, "%02x", data[i]);
		ptr += 2;
	}

	*skid = id_str;
	ret = 0;
out:
	if (X509cert)
		X509_free (X509cert);

	return ret;
}
