/* ossl-gnupg.c - A GnuPG based OpenSSL engine utilizing the gpg-agent.
 * Copyright 2015-2019 The OpenSSL Project Authors. All Rights Reserved.
 * Copyright 2019 g10 Code GmbH
 *
 * Licensed under the OpenSSL license (the "License").  You may not use
 * this file except in compliance with the License.  You can obtain a copy
 * in the file LICENSE in the source distribution or at
 * https://www.openssl.org/source/license.html
 *
 * Compile:
 *  gcc  -Iinclude -I../include -fPIC -pthread -m64 -Wa,--noexecstack -Wall -O3 -DNDEBUG -DENGINE_DYNAMIC_SUPPORT -c -o ossl-gnupg.o ossl-gnupg.c
 *
 * Link:
 *  gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -DENGINE_DYNAMIC_SUPPORT -O3 -Wl,-znodelete -shared -o ossl-gnupg.so ossl-gnupg.o -lcrypto -ldl -pthread
 *
 * Very basic Test:
 *  openssl engine dynamic -vvvv -t -pre SO_PATH:./ossl-gnupg.so  -pre LOAD
 *
 */

#if defined(_WIN32)
# include <windows.h>
#endif

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

#include <openssl/engine.h>
#include <openssl/sha.h>
#include <openssl/aes.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/async.h>
#include <openssl/bn.h>
#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/modes.h>
#include <openssl/err.h>

#if defined(OPENSSL_SYS_UNIX) && defined(OPENSSL_THREADS)
# undef ASYNC_POSIX
# define ASYNC_POSIX
# include <unistd.h>
#elif defined(_WIN32)
# undef ASYNC_WIN
# define ASYNC_WIN
#endif


#define GNUPGerr(f, r) ERR_GNUPG_error((f), (r), OPENSSL_FILE, OPENSSL_LINE)


/*
 * Function codes.
 */
#define GNUPG_F_BIND_GNUPG                             107
#define GNUPG_F_GNUPG_BN_MOD_EXP                       101
#define GNUPG_F_GNUPG_MOD_EXP                          102
#define GNUPG_F_GNUPG_PRIVATE_DECRYPT                  103
#define GNUPG_F_GNUPG_PRIVATE_ENCRYPT                  104
#define GNUPG_F_GNUPG_PUBLIC_DECRYPT                   105
#define GNUPG_F_GNUPG_PUBLIC_ENCRYPT                   106

/*
 * Reason codes.
 */
#define GNUPG_R_INIT_FAILED                            100


#ifndef OPENSSL_NO_ERR

static ERR_STRING_DATA GNUPG_str_functs[] = {
    {ERR_PACK(0, GNUPG_F_BIND_GNUPG, 0), "bind_gnupg"},
    {ERR_PACK(0, GNUPG_F_GNUPG_BN_MOD_EXP, 0), ""},
    {ERR_PACK(0, GNUPG_F_GNUPG_MOD_EXP, 0), ""},
    {ERR_PACK(0, GNUPG_F_GNUPG_PRIVATE_DECRYPT, 0), ""},
    {ERR_PACK(0, GNUPG_F_GNUPG_PRIVATE_ENCRYPT, 0), ""},
    {ERR_PACK(0, GNUPG_F_GNUPG_PUBLIC_DECRYPT, 0), ""},
    {ERR_PACK(0, GNUPG_F_GNUPG_PUBLIC_ENCRYPT, 0), ""},
    {0, NULL}
};

static ERR_STRING_DATA GNUPG_str_reasons[] = {
    {ERR_PACK(0, 0, GNUPG_R_INIT_FAILED), "init failed"},
    {0, NULL}
};

#endif /*!OPENSSL_NO_ERR*/


static int lib_code = 0;
static int error_loaded = 0;

static int ERR_load_GNUPG_strings(void)
{
    if (lib_code == 0)
        lib_code = ERR_get_next_error_library();

    if (!error_loaded) {
#ifndef OPENSSL_NO_ERR
        ERR_load_strings(lib_code, GNUPG_str_functs);
        ERR_load_strings(lib_code, GNUPG_str_reasons);
#endif
        error_loaded = 1;
    }
    return 1;
}

static void ERR_unload_GNUPG_strings(void)
{
    if (error_loaded) {
#ifndef OPENSSL_NO_ERR
        ERR_unload_strings(lib_code, GNUPG_str_functs);
        ERR_unload_strings(lib_code, GNUPG_str_reasons);
#endif
        error_loaded = 0;
    }
}

static void ERR_GNUPG_error(int function, int reason, char *file, int line)
{
    if (lib_code == 0)
        lib_code = ERR_get_next_error_library();
    ERR_PUT_error(lib_code, function, reason, file, line);
}


/* Engine Id and Name */
static const char *engine_gnupg_id = "ossl-gnupg";
static const char *engine_gnupg_name = "GnuPG based engine";


/* Engine Lifetime functions */
static int gnupg_destroy (ENGINE *e);
static int gnupg_init (ENGINE *e);
static int gnupg_finish (ENGINE *e);
static EVP_PKEY *gnupg_load_privkey (ENGINE *eng, const char *key_id,
                                     UI_METHOD *ui_method, void *callback_data);

void engine_load_gnupg_int (void);


/* RSA */

static int gnupg_pub_enc(int flen, const unsigned char *from,
                    unsigned char *to, RSA *rsa, int padding);
static int gnupg_pub_dec(int flen, const unsigned char *from,
                    unsigned char *to, RSA *rsa, int padding);
static int gnupg_rsa_priv_enc(int flen, const unsigned char *from,
                      unsigned char *to, RSA *rsa, int padding);
static int gnupg_rsa_priv_dec(int flen, const unsigned char *from,
                      unsigned char *to, RSA *rsa, int padding);
static int gnupg_rsa_mod_exp(BIGNUM *r0, const BIGNUM *I, RSA *rsa,
                             BN_CTX *ctx);

static int gnupg_rsa_init(RSA *rsa);
static int gnupg_rsa_finish(RSA *rsa);

static RSA_METHOD *gnupg_rsa_method = NULL;


/* Replacement for standard signing functions.  */
static int
gnupg_evp_pkey_sign_init (EVP_PKEY_CTX *ctx)
{
  /* int ret; */
  /* if (!ctx || !ctx->pmeth || !ctx->pmeth->sign) { */
  /*   EVPerr(EVP_F_EVP_PKEY_SIGN_INIT, */
  /*          EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE); */
  /*   return -2; */
  /* } */
  /* ctx->operation = EVP_PKEY_OP_SIGN; */
  /* if (!ctx->pmeth->sign_init) */
  /*   return 1; */
  /* ret = ctx->pmeth->sign_init(ctx); */
  /* if (ret <= 0) */
  /*   ctx->operation = EVP_PKEY_OP_UNDEFINED; */
  /* return ret; */
  fprintf (stderr, "ossl-gnupg: %s called\n", __func__);
  return -2;
}


static int
gnupg_evp_pkey_sign (EVP_PKEY_CTX *ctx, unsigned char *sig, size_t *siglen,
                     const unsigned char *tbs, size_t tbslen)
{
    /* if (!ctx || !ctx->pmeth || !ctx->pmeth->sign) { */
    /*     EVPerr(EVP_F_EVP_PKEY_SIGN, */
    /*            EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE); */
    /*     return -2; */
    /* } */
    /* if (ctx->operation != EVP_PKEY_OP_SIGN) { */
    /*     EVPerr(EVP_F_EVP_PKEY_SIGN, EVP_R_OPERATON_NOT_INITIALIZED); */
    /*     return -1; */
    /* } */
    /* M_check_autoarg(ctx, sig, siglen, EVP_F_EVP_PKEY_SIGN) */
    /*     return ctx->pmeth->sign(ctx, sig, siglen, tbs, tbslen); */
  fprintf (stderr, "ossl-gnupg: %s called\n", __func__);
  return -2;
}


static int
gnupg_pkey_meths (ENGINE *e, EVP_PKEY_METHOD **pmeth, const int **nids, int nid)
{
  static int pkey_nids[] = {
    EVP_PKEY_RSA,
    0
  };

  if (!pmeth)
    {
      *nids = pkey_nids;
      return 1;
    }

  if (nid == EVP_PKEY_RSA)
    {
      static EVP_PKEY_METHOD *this_pmeth;

      if (!pmeth)
        {
          this_pmeth = EVP_PKEY_meth_new (EVP_PKEY_RSA, 0);
          if (!this_pmeth)
            {
              *pmeth = NULL;
              return 0;
            }
          EVP_PKEY_meth_set_sign (this_pmeth,
                                  gnupg_evp_pkey_sign_init,
                                  gnupg_evp_pkey_sign);
        }

      *pmeth = this_pmeth;
      return 1;
    }

  *pmeth = NULL;
  return 0;
}


static int
bind_gnupg (ENGINE *e)
{
  EVP_PKEY_METHOD *pmeth;

  fprintf (stderr, "ossl-gnupg: %s called\n", __func__);

  if (!(gnupg_rsa_method = RSA_meth_new ("GnuPG RSA methods", 0))
      || !RSA_meth_set_pub_enc (gnupg_rsa_method, gnupg_pub_enc)
      || !RSA_meth_set_pub_dec (gnupg_rsa_method, gnupg_pub_dec)
      || !RSA_meth_set_priv_enc (gnupg_rsa_method, gnupg_rsa_priv_enc)
      || !RSA_meth_set_priv_dec (gnupg_rsa_method, gnupg_rsa_priv_dec)
      || !RSA_meth_set_mod_exp (gnupg_rsa_method, gnupg_rsa_mod_exp)
      || !RSA_meth_set_bn_mod_exp (gnupg_rsa_method, BN_mod_exp_mont)
      || !RSA_meth_set_init (gnupg_rsa_method, gnupg_rsa_init)
      || !RSA_meth_set_finish (gnupg_rsa_method, gnupg_rsa_finish))
    {
      GNUPGerr(GNUPG_F_BIND_GNUPG, GNUPG_R_INIT_FAILED);
      return 0;
    }

  ERR_load_GNUPG_strings ();

  if (!ENGINE_set_id(e, engine_gnupg_id)
      || !ENGINE_set_name(e, engine_gnupg_name)
      || !ENGINE_set_RSA(e, gnupg_rsa_method)
      || !ENGINE_set_load_privkey_function(e, gnupg_load_privkey)
      || !ENGINE_set_pkey_meths (e, gnupg_pkey_meths)
      || !ENGINE_set_destroy_function(e, gnupg_destroy)
      || !ENGINE_set_init_function(e, gnupg_init)
      || !ENGINE_set_finish_function(e, gnupg_finish))
    {
      GNUPGerr(GNUPG_F_BIND_GNUPG, GNUPG_R_INIT_FAILED);
      return 0;
    }

  return 1;
}


#ifndef OPENSSL_NO_DYNAMIC_ENGINE
static int
bind_helper (ENGINE *e, const char *id)
{
  fprintf (stderr, "ossl-gnupg: %s called (id=%s)\n", __func__, id);
  if (id && strcmp (id, engine_gnupg_id))
    return 0;
  if (!bind_gnupg (e))
    return 0;
  return 1;
}

IMPLEMENT_DYNAMIC_CHECK_FN()
IMPLEMENT_DYNAMIC_BIND_FN(bind_helper)
#endif /*!OPENSSL_NO_DYNAMIC_ENGINE*/


static ENGINE *
engine_gnupg (void)
{
  ENGINE *ret;

  fprintf (stderr, "ossl-gnupg: %s called\n", __func__);
  ret = ENGINE_new ();
  if (!ret)
    return NULL;
  if (!bind_gnupg (ret))
    {
      ENGINE_free(ret);
      return NULL;
    }
  fprintf (stderr, "ossl-gnupg: %s success\n", __func__);
  return ret;
}


void
engine_load_gnupg_int (void)
{
  ENGINE *toadd;

  ERR_print_errors_fp (stderr);
  fprintf (stderr, "ossl-gnupg: %s called\n", __func__);
  toadd  = engine_gnupg ();
  if (!toadd)
        return;
  ENGINE_add (toadd);
  ENGINE_free (toadd);
  ERR_clear_error();
}


static int
gnupg_init (ENGINE *e)
{
  (void)e;
  return 1;
}


static int
gnupg_finish (ENGINE *e)
{
  (void)e;
  return 1;
}


static int
gnupg_destroy (ENGINE *e)
{
  (void)e;
  RSA_meth_free (gnupg_rsa_method);
  ERR_unload_GNUPG_strings ();
  return 1;
}


/* We are not interested in a private key but osslsigncode needs an
 * object.  Let's assume that only we handle that object and thus an
 * uninitialized object should be sufficient.  */
static EVP_PKEY *
gnupg_load_privkey (ENGINE *eng, const char *key_id,
                    UI_METHOD *ui_method, void *callback_data)
{
  EVP_PKEY *pkey;

  (void)eng;
  (void)key_id;
  (void)ui_method;
  (void)callback_data;

  fprintf (stderr, "ossl-gnupg: %s called\n", __func__);

  pkey = EVP_PKEY_new ();
  if (!pkey)
    return 0;

  if (!EVP_PKEY_set_type (pkey, EVP_PKEY_RSA))
    GNUPGerr (GNUPG_F_BIND_GNUPG, GNUPG_R_INIT_FAILED);


  fprintf (stderr, "ossl-gnupg: %s returns %p\n", __func__, pkey);
  return pkey;
}





/*
 * RSA implementation
 */

static int
gnupg_pub_enc(int flen, const unsigned char *from,
              unsigned char *to, RSA *rsa, int padding)
{
  ERR_print_errors_fp (stderr);
  fprintf (stderr, "ossl-gnupg: %s called\n", __func__);
  return RSA_meth_get_pub_enc(RSA_PKCS1_OpenSSL())
        (flen, from, to, rsa, padding);
}


static int
gnupg_pub_dec(int flen, const unsigned char *from,
              unsigned char *to, RSA *rsa, int padding)
{
  ERR_print_errors_fp (stderr);
  fprintf (stderr, "ossl-gnupg: %s called\n", __func__);
  return RSA_meth_get_pub_dec(RSA_PKCS1_OpenSSL())
    (flen, from, to, rsa, padding);
}


static int
gnupg_rsa_priv_enc(int flen, const unsigned char *from,
                   unsigned char *to, RSA *rsa, int padding)
{
  ERR_print_errors_fp (stderr);
  fprintf (stderr, "ossl-gnupg: %s called\n", __func__);
  return RSA_meth_get_priv_enc(RSA_PKCS1_OpenSSL())
    (flen, from, to, rsa, padding);
}


static int
gnupg_rsa_priv_dec(int flen, const unsigned char *from,
                   unsigned char *to, RSA *rsa, int padding)
{
  ERR_print_errors_fp (stderr);
  fprintf (stderr, "ossl-gnupg: %s called\n", __func__);
  return RSA_meth_get_priv_dec(RSA_PKCS1_OpenSSL())
    (flen, from, to, rsa, padding);
}


static int gnupg_rsa_mod_exp(BIGNUM *r0, const BIGNUM *I, RSA *rsa, BN_CTX *ctx)
{
  ERR_print_errors_fp (stderr);
  fprintf (stderr, "ossl-gnupg: %s called\n", __func__);
  return RSA_meth_get_mod_exp(RSA_PKCS1_OpenSSL())(r0, I, rsa, ctx);
}


static int gnupg_rsa_init(RSA *rsa)
{
  ERR_print_errors_fp (stderr);
  fprintf (stderr, "ossl-gnupg: %s called\n", __func__);
  return 1;  /*RSA_meth_get_init(RSA_PKCS1_OpenSSL())(rsa);*/
}


static int gnupg_rsa_finish(RSA *rsa)
{
  ERR_print_errors_fp (stderr);
  fprintf (stderr, "ossl-gnupg: %s called\n", __func__);
  return 1; /*RSA_meth_get_finish(RSA_PKCS1_OpenSSL())(rsa);*/
}
