/*
 * Adapted by jiaqiang.ye@lnxall.com
 *
 * Code borrowed from: https://pagefault.blog/2019/04/22/how-to-sign-and-verify-using-openssl/
 *
 * 2024/01/11
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/sha.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define VERIFY_SIGN_SIZE 512

static FILE * load_pubkey(void)
{
	size_t len;
	FILE * rval = NULL;
	char * pstr = NULL;

	const char * pkstr =
		"-----BEGIN PUBLIC KEY-----\n"
		"MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsz48kaakYbkC4ev7fG0Y\n"
		"CYp/PeBwffrfJi/LeL8WJhSOU4IIBdgsR3Apwp5KBzlw9cMVlqI4g2uoMxPkm9de\n"
		"c1cFvtDIjVjfKptt96aH6p0B2cbpfrAL0uxq+LxuExyQBnB5Fx4eManPMM44Ra7u\n"
		"pNI+0P2ad+g/iSUn2VnP911CGRf0Fn3unnh1APnEA7v20KxIUB02VrRqCKjK8clg\n"
		"26vCWpmD8SvqXzukL9nXFD0zPEJqJSbPk+aSMkw9UYWSQ8Wx8BUvOyAN3aQml2Zf\n"
		"0xmMbjWcR8O6zLmF0D5kRHqUELRMq/jDi6VCqc+IRkTRV4JhSkBEQrAb8MlUSmb0\n"
		"SlZqvteDsmeXuZ5jdRHhE9m1h3zQZT7dEHycJur6FSV3q2Jd7PWfUtAqHKSfnbAW\n"
		"VfBVkDyhiBscKAPx0W0UhXJPsz4AYDBor6vqT5aXCmICwPW/r/2VKOmm76WITm5g\n"
		"XBwEBiCVrr6+G6dDt3yUaR5A0uLdYgmyk8sKXA/8Abp+SkQRYOoYUSCvacSTLrDQ\n"
		"9trRDWcISGwo6e1PTVCTRejROykGfdkyTizxTrWDoVPieZFAkfWf42ZJoeJrVzO4\n"
		"PpnGyLl9uWjgXOyCzemsgeDs4MqvhmyZCNZ/8kTJwo33sCobaytY/Sq8tbe+ipJZ\n"
		"716i2tAl+NMw+UIKo8JnY/8CAwEAAQ==\n"
		"-----END PUBLIC KEY-----\n";

	len = strlen(pkstr);
	pstr = (char *) malloc(len + 1);
	if (pstr == NULL) {
		fputs("Error, system out of memory!\n", stderr);
		fflush(stderr);
		return NULL;
	}

	memcpy(pstr, pkstr, len);
	pstr[len] = '\0';
	rval = fmemopen(pstr, len, "rb");
	if (rval == NULL) {
		int error = errno;
		fprintf(stderr, "Error, fmemopen has failed: %s\n", strerror(error));
		fflush(stderr);
		free(pstr);
	}

	return rval;
}

static int sha256_digest_file(const char * pfile,
	unsigned char * shadgst, unsigned char * psign)
{
	ssize_t rlen;
	int fd, error;
	char * tmpbuf;
	SHA256_CTX ctx;
	struct stat filest;
	size_t maxlen, curlen;

	error = 0;
	curlen = 0;
	tmpbuf = NULL;
	fd = open(pfile, O_RDONLY | O_CLOEXEC);
	if (fd == -1) {
		error = errno;
		fprintf(stderr, "Error, failed to open %s: %s\n", pfile, strerror(error));
		fflush(stderr);
		errno = error;
		return -1;
	}

	error = fstat(fd, &filest);
	if (error == -1 || !S_ISREG(filest.st_mode)) {
		close(fd);
		fprintf(stderr, "Error, not regular file: %s\n", pfile);
		fflush(stderr);
		return -1;
	}

	if (filest.st_size <= VERIFY_SIGN_SIZE || filest.st_size > 0x7FFFFFFF) {
		close(fd);
		fprintf(stderr, "Error, invalid file size '%s': %llu\n",
			pfile, (unsigned long long) filest.st_size);
		fflush(stderr);
		return -1;
	}

	maxlen = (size_t) filest.st_size;
	maxlen -= VERIFY_SIGN_SIZE;

	tmpbuf = (char *) malloc(16384);
	if (tmpbuf == NULL) {
		error = errno;
		close(fd);
		fputs("Error, system out of memory!\n", stderr);
		fflush(stderr);
		errno = errno;
		return -1;
	}

	SHA256_Init(&ctx);
	while (curlen < maxlen) {
		size_t len;
		len = maxlen - curlen;
		if (len > 16384)
			len = 16384;

		rlen = read(fd, tmpbuf, len);
		if (rlen <= 0)
			break;

		curlen += (size_t) rlen;
		SHA256_Update(&ctx, tmpbuf, (size_t) rlen);
	}

	SHA256_Final(shadgst, &ctx);
	rlen = read(fd, psign, VERIFY_SIGN_SIZE);
	if (rlen != VERIFY_SIGN_SIZE) {
		fputs("Error, failed to read signed certificate.\n", stderr);
		fflush(stderr);
	}

	close(fd);
	free(tmpbuf);
	return 0;
}
 
// Buffer for file read operations. The buffer must be able to accomodate
// the RSA signature in whole (e.g. 4096-bit RSA key produces 512 byte signature)
static unsigned char sig_buf[VERIFY_SIGN_SIZE];

int main(int argc, char *argv[])
{
	int result;
	FILE * pubkey;
	unsigned char digest[SHA256_DIGEST_LENGTH];

	pubkey = NULL;
	if (argc < 2) {
		fprintf(stderr, "Usage: %s datafile\n", argv[0]);
		fflush(stderr);
		return 1;
	}

	if (argc > 2)
		pubkey = fopen(argv[2], "rb");
	if (pubkey == NULL) {
		pubkey = load_pubkey();
		if (pubkey == NULL)
			return 2;
	}

	memset(sig_buf, 0, VERIFY_SIGN_SIZE);
	memset(digest, 0, SHA256_DIGEST_LENGTH);
	sha256_digest_file(argv[1], digest, sig_buf);

	// Read public key from memory
	RSA * rsa_pubkey = PEM_read_RSA_PUBKEY(pubkey, NULL, NULL, NULL);

	// Decrypt signature (in sig_buf) and verify it matches
	// with the digest calculated from data file.
	result = RSA_verify(NID_sha256, digest, SHA256_DIGEST_LENGTH,
		sig_buf, VERIFY_SIGN_SIZE, rsa_pubkey);
	RSA_free(rsa_pubkey);
	fclose(pubkey);

	if (result != 1) {
		fprintf(stderr, "Signature is not valid: %s\n", argv[1]);
		fflush(stderr);
		return 3;
	}

	fprintf(stdout, "Certificate verify OK: %s\n", argv[1]);
	fflush(stdout);
	return 0;
}
