/*
 * NAME:
 *	signer - simple signature generator
 */
/*
 * RCSid:
 *	$Id: signer.c,v 1.13 2024/07/05 18:13:42 sjg Exp $
 *
 *	@(#) Copyright (c) 2012-2019 Simon J. Gerraty
 *
 *	This file is provided in the hope that it will
 *	be of use.  There is absolutely NO WARRANTY.
 *	Permission to copy, redistribute or otherwise
 *	use this file is hereby granted provided that 
 *	the above copyright notice and this notice are
 *	left intact. 
 *      
 *	Please send copies of changes and bug-fixes to:
 *	sjg@crufty.net
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>

/* Necessary for legacy RSA public key export */
#define OPENSSL_SUPPRESS_DEPRECATED

#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/bn.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h>

#include "libcrypto-compat.h"		/* compatability with < 1.1 */

#ifndef OPENSSL_NO_ENGINE
#include <openssl/engine.h>
#endif

#ifdef DEBUG
# define DEBUG_LOG(fmt, ...) syslog(LOG_DEBUG | LOG_DAEMON, fmt, ##__VA_ARGS__)
#else
# define DEBUG_LOG(fmt, ...) do { } while (0)
#endif

static ENGINE *Engine = NULL;

void
initialize(void)
{
    static int once;

    if (once)
	return;
    once = 1;
    //CRYPTO_malloc_init();
    ERR_load_crypto_strings();
    OpenSSL_add_all_algorithms();
}

/**
 * @brief
 * last error from OpenSSL as a string
 */
char *
get_error_string(void)
{
    initialize();
    return ERR_error_string(ERR_get_error(), NULL);
}

/**
 * @brief
 * tell OpenSSL the engine and ui to use
 */
int
use_engine_ui(const char *engine, UI_METHOD *uip)
{
#ifndef OPENSSL_NO_ENGINE
    ENGINE *e = NULL;

    if ((e = ENGINE_by_id(engine))) {
	if (uip)
	    ENGINE_ctrl_cmd(e, "SET_USER_INTERFACE", 0, uip, 0, 1);
	if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) {
	    ENGINE_free(e);
	    return 0;
	}
	Engine = e;
	return 1;
    }
#endif
    return 0;
}

/**
 * @brief
 * tell OpenSSL the engine to use with default ui
 */
int
use_engine(const char *engine)
{
    return use_engine_ui(engine, NULL);
}

/**
 * @brief
 * load a private key
 */
EVP_PKEY *
load_key(const char *file)
{
    BIO *f;
    EVP_PKEY *key = NULL;

    initialize();
    f = BIO_new(BIO_s_file());
    if (BIO_read_filename(f, file) > 0) {
	key = PEM_read_bio_PrivateKey(f, NULL, NULL, NULL);
	DEBUG_LOG("key(%s) = %lx\n", file, (unsigned long)key);
    }
    BIO_free(f);
    return key;
}

/**
 * @brief
 * load public key from X.509 certificate
 */
EVP_PKEY *
load_x509_pubkey(const char *file)
{
    X509 *x = NULL;
    BIO *f;
    EVP_PKEY *key = NULL;

    initialize();
    f = BIO_new(BIO_s_file());
    if (BIO_read_filename(f, file) > 0) {
	x = PEM_read_bio_X509_AUX(f, NULL, NULL, NULL);
	if (x)
	    key = X509_get_pubkey(x);
    }
    BIO_free(f);
    return key;
}

/**
 * @brief
 * load a public key
 */
EVP_PKEY *
load_PUBKEY(const char *file)
{
    BIO *f;
    EVP_PKEY *key = NULL;

    initialize();
    f = BIO_new(BIO_s_file());
    if (BIO_read_filename(f, file) > 0) {
	key = PEM_read_bio_PUBKEY(f, NULL, NULL, NULL);
    }
    BIO_free(f);
    return key;
}

/**
 * @brief
 * create an rsa public key from components
 */
EVP_PKEY *
set_rsa_pubkey(unsigned char *ndata, size_t nlen,
	       unsigned char *edata, size_t elen)
{
    EVP_PKEY *key = NULL;
    RSA *rsa = NULL;

    if ((rsa = RSA_new()) == NULL)
	goto err;
    if (RSA_set0_key(rsa,
		     BN_bin2bn(ndata, nlen, NULL),
		     BN_bin2bn(edata, elen, NULL),
		     NULL) != 0)
	goto err;
    key = EVP_PKEY_new();
    if (key != NULL) {
	if (!EVP_PKEY_set1_RSA(key, rsa)) {
	    EVP_PKEY_free(key);
	    key = NULL;
	}
    }
 err:
    if (rsa)
	RSA_free(rsa);
    return key;
}

size_t
sign_digest_buf(EVP_PKEY *pkey,
		const char *digest,
		unsigned char *mdata, size_t mlen,
		unsigned char *buf, size_t bufsz)
{
    const EVP_MD *md = NULL;
    EVP_PKEY_CTX *ctx = NULL;
    size_t len, mdlen, rc = 0;
    int i;

    DEBUG_LOG("%s: key= %lx\ndigest= %s\nmlen=%lu, blen=%lu",
	      __func__, (unsigned long)pkey, digest, mlen, bufsz);
    
    initialize();
    md = EVP_get_digestbyname(digest);

    if (!pkey) {
	DEBUG_LOG("%s: pkey == %p", __func__, pkey);
	rc = 0;
	goto out;
    }

    ctx = EVP_PKEY_CTX_new(pkey, Engine);
    if (!ctx) {
	DEBUG_LOG("%s: EVP_PKEY_CTX_new() == %p", __func__, ctx);
	rc = 0;
	goto out;
	}

    if ((i = EVP_PKEY_sign_init(ctx)) <= 0) {
	DEBUG_LOG("%s: EVP_PKEY_sign_init(ctx) == %d", __func__, i);
	rc = 0;
	goto out;
    }

    if (EVP_PKEY_id(pkey) == EVP_PKEY_RSA &&
	(i = EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING)) <= 0) {
	DEBUG_LOG("%s: EVP_PKEY_CTX_set_rsa_padding() == %d", __func__, i);
	rc = 0;
	goto out;
    }

    if ((i = EVP_PKEY_CTX_set_signature_md(ctx, md)) <= 0) {
	DEBUG_LOG("%s: EVP_PKEY_CTX_set_signature_md() == %d", __func__, i);
	rc = 0;
	goto out;
    }

    /* Determine buffer length */
    if ((i = EVP_PKEY_sign(ctx, NULL, &len, mdata, mlen)) <= 0) {
	DEBUG_LOG("%s: EVP_PKEY_sign() == %d", __func__, i);
	rc = 0;
	goto out;
    }

    if (len > bufsz) {
	DEBUG_LOG("%s: len > bufsz, len - bufsiz = %lu", __func__,
		  len - bufsz);
	rc = 0;
	goto out;
    }

    if ((i = EVP_PKEY_sign(ctx, buf, &len, mdata, mlen)) <= 0) {
	DEBUG_LOG("%s: EVP_PKEY_sign() == %d", __func__, i);
	rc = 0;
	goto out;
    }

    /* success */
    rc = len;

out:

    return rc;
}


void
sign_digest(EVP_PKEY *pkey,
	    const char *digest,
	    unsigned char *mdata, size_t mlen,
	    unsigned char **bp, size_t *bsz)
{
    unsigned char *cp, buf[BUFSIZ];
    int c;

    c = sign_digest_buf(pkey, digest, mdata, mlen, buf, sizeof(buf));
    if (c > 0) {
	cp = malloc(c);
	*bp = cp;
	if (cp) {
	    memcpy(cp, buf, c);
	    *bsz = c;
	} else {
	    *bsz = 0;
	}
    }
}


size_t
verify_digest(EVP_PKEY *pkey,
	      const char *digest,
	      unsigned char *mdata, size_t mlen,
	      unsigned char *sdata, size_t slen)
{
    EVP_MD_CTX *ctx = NULL;
    const EVP_MD *md = NULL;
    EVP_PKEY_CTX *pctx = NULL;
    size_t len;
    size_t rc = 0;
    int i = -1;

    initialize();
    ctx = EVP_MD_CTX_new();
    md = EVP_get_digestbyname(digest);
    EVP_DigestInit(ctx, md);

    pctx = EVP_PKEY_CTX_new(pkey, NULL);
    if (!pctx)
	goto fail;;
    if (EVP_PKEY_verify_init(pctx) <= 0)
	goto fail;
    if (EVP_PKEY_CTX_set_signature_md(pctx, EVP_MD_CTX_md(ctx)) <= 0)
	goto fail;
    i = EVP_PKEY_verify(pctx, sdata, slen, mdata, mlen);
    if (i > 0)
	rc = i;
 fail:
    EVP_MD_CTX_free(ctx);
    EVP_PKEY_CTX_free(pctx);
    return rc;
}
	    
#ifdef MAIN
#include <sys/param.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>
#include <openssl/err.h>

int
main(int argc, char *argv[])
{
    EVP_PKEY *key = NULL;
    const EVP_MD *md = NULL;
    EVP_MD_CTX *mctx = NULL;
    const char *hname = "sha256";
    const char *ext = ".rsig";
    char fbuf[MAXPATHLEN];
    unsigned char buf[BUFSIZ];
    unsigned char mdata[BUFSIZ];
    size_t mlen;
    int c;
    int fd;
    int n;
    int vflag = 0;

    initialize();
    mctx = EVP_MD_CTX_new();
    while ((c = getopt(argc, argv, "e:x:h:k:P:p:")) != -1) {
	switch (c) {
	case 'e':
	    use_engine_ui(optarg, NULL);
	    break;
	case 'x':
	    ext = optarg;
	    break;
	case 'h':
	    hname = optarg;
	    break;
	case 'k':
	    key = load_key(optarg);
	    if (!key)
		errx(1, "cannot load: %s: %s", optarg, get_error_string());
	    break;
	case 'P':
	    vflag = 1;
	    key = load_PUBKEY(optarg);
	    if (!key)
		errx(1, "cannot load: %s: %s", optarg, get_error_string());
	    break;
	case 'p':
	    vflag = 1;
	    key = load_x509_pubkey(optarg);
	    if (!key)
		errx(1, "cannot load: %s: %s", optarg, get_error_string());
	    break;
	}
    }

    if (!key)
	errx(1, "must specify key");
    
    md = EVP_get_digestbyname(hname);
    if (!md)
	errx(1, "unknown digest: %s", hname);


    if (!vflag) {
	const char *sname;
	const EVP_PKEY_ASN1_METHOD *ameth;

	ameth = EVP_PKEY_get0_asn1(key);
	if (ameth) {
	    EVP_PKEY_asn1_get0_info(NULL, NULL,
				    NULL, NULL, &sname, ameth);
	    printf("%s+%s\n", sname, EVP_MD_name(md));
	}
    }
    
    for (; optind < argc; optind++) {
	if ((fd = open(argv[optind], O_RDONLY)) < 0)
	    err(1, "%s", argv[optind]);
    
	EVP_DigestInit(mctx, md);
	while ((c = read(fd, buf, sizeof(buf))) > 0) {
	    EVP_DigestUpdate(mctx, buf, c);
	}
	mlen = sizeof(mdata);
	EVP_DigestFinal(mctx,mdata,(unsigned int *)&mlen);
	close(fd);
	snprintf(fbuf, sizeof(fbuf), "%s%s", argv[optind], ext);
	if (vflag) {
	    if ((fd = open(fbuf, O_RDONLY)) < 0)
		err(1, "open %s", fbuf);
	    
	    n = read(fd, buf, sizeof(buf));
	    close(fd);
	    c = verify_digest(key, hname, mdata, mlen, buf, n);
	    if (!c)
		errx(1, "verify failure: %s", get_error_string());
	} else {
	    if ((fd = open(fbuf, O_WRONLY|O_CREAT, 0644)) < 0)
		err(1, "open %s", fbuf);
	    c = sign_digest_buf(key, hname, mdata, mlen, buf, sizeof(buf));
	    if (!c)
		errx(1, "sign failure: %s", get_error_string());
	    write(fd, buf, c);
	    close(fd);
	}
    }

    EVP_MD_CTX_free(mctx);

    exit(0);
    
}
#endif
