/*****************************************************************************/
/*
                               SesolaMkCert.c

Make an X509 server certificate for an SSL service when there is none
configured.  WASD used to include a static key/cert PEM periodically manually 
generated for the same purpose.  As browsers have become more and more
reluctant to accept home-brew certificates, and more recently to accept those
with lives exceeding one year it has become increasingly difficult to bootstrap
a new TLS site.  This dynamically generated certificate goes some way to
addressing this with a per-startup 180 day certificate lifetime.  Browsers are
still reluctant and if they do accept the certificate generally require
manual user exception processing.  Private/Incognito/"porn-mode" browser
instances seem to be more relaxed about accepting these certificates.

Core functionality purloined from:

https://opensource.apple.com/source/OpenSSL/OpenSSL-22/openssl/demos/x509/mkcert.c


VERSION HISTORY
---------------
18-JUL-2020  MGD  initial
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

/* standard C header files */
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* VMS related header files */
#include <ssdef.h>
#include <stsdef.h>

/* application header files */
#define SESOLA_REQUIRED
#include "Sesola.h"

#define WASD_MODULE "SESOLAMKCERT"

/***************************************/
#ifdef SESOLA  /* secure sockets layer */
/***************************************/

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

#include <openssl/pem.h>
#include <openssl/conf.h>
#include <openssl/x509v3.h>

static int mkcert (X509 **x509p, EVP_PKEY **pkeyp,
                   int bits, int serial, int days, char *san);
static int add_ext (X509 *cert, int nid, char *value);

/********************/
/* external storage */
/********************/

#ifdef DBUG
extern BOOL Debug;
#else
#define Debug 0 
#endif

extern int  ToLowerCase[],
            ToUpperCase[];

extern char  ErrorSanityCheck[],
             ServerHostName[],
             SoftwareID[];

extern BIO  *SesolaBioMemPtr;

extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
*/

char* SesolaMkCert (char *san)
{
   int  bits = 2048,
        days = 180;
   unsigned long  serial[2];
   char  *pemptr;
   BIO  *bioptr;
   X509  *x5o9 = NULL;  /* note this is an oh not a zero */
   EVP_PKEY  *pkey = NULL;
   RSA  *rkey = NULL;
   
   /* generate a (likely) unique serial number */
   sys$gettim (&serial);
   serial[0] &= 0x7fffffff;

   mkcert (&x5o9, &pkey, bits, serial[0], days, san);
   
   rkey = EVP_PKEY_get1_RSA (pkey);
   bioptr = BIO_new (BIO_s_mem());
   RSA_print (bioptr, rkey, 0);
   X509_print (bioptr, x5o9);
   
   PEM_write_bio_PrivateKey (bioptr, pkey, NULL, NULL, 0, NULL, NULL);
   PEM_write_bio_X509 (bioptr, x5o9);
   
   X509_free (x5o9);
   EVP_PKEY_free (pkey);

   CRYPTO_cleanup_all_ex_data();
   
   BIO_get_mem_data (bioptr, &pemptr);

   if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchDataDump (pemptr, strlen(pemptr));

   return (pemptr);
}

/*****************************************************************************/

static void callback(int p, int n, void *arg)
{
   char c='B';
   
   if (p == 0) c='.';
   if (p == 1) c='+';
   if (p == 2) c='*';
   if (p == 3) c='\n';
   fputc(c,stderr);
}

/*****************************************************************************/
/*
*/

static int mkcert
(
X509 **x509p,
EVP_PKEY **pkeyp,
int bits,
int serial,
int days,
char *san
)
{
   char  *cptr, *sptr, *zptr;
   char  buf [256];
   X509  *x5o9;  /* note this is an oh not a zero */
   EVP_PKEY  *pkey;
   RSA  *rsa;
   X509_NAME  *name = NULL;
   
   if ((pkeyp == NULL) || (*pkeyp == NULL)) {
      if ((pkey=EVP_PKEY_new()) == NULL)
         return(0);
   }
   else
      pkey = *pkeyp;
   
   if ((x509p == NULL) || (*x509p == NULL)) {
      if ((x5o9 = X509_new()) == NULL)
         goto err;
   }
   else
      x5o9 = *x509p;
   
   FaoToStdout ("Generate !AZ !UL bit private key:\n", san, bits);
   rsa = RSA_generate_key (bits, RSA_F4, callback, NULL);
   if (!EVP_PKEY_assign_RSA (pkey, rsa)) goto err;
   rsa = NULL;
   
   X509_set_version (x5o9, 2);
   ASN1_INTEGER_set (X509_get_serialNumber(x5o9), serial);
   X509_gmtime_adj (X509_get_notBefore(x5o9), 0);
   X509_gmtime_adj (X509_get_notAfter(x5o9), (long)60*60*24*days);
   X509_set_pubkey (x5o9, pkey);
   
   name = X509_get_subject_name (x5o9);
   
   /* This function creates and adds the entry, working out the
    * correct string type and performing checks on its length.
    * Normally we'd check the return value for errors...
    */
   X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, "ARPA", -1, -1, 0);
   X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
                                    ServerHostName, -1, -1, 0);

   /* it's self signed so set the issuer name to be the same as the subject */
   X509_set_issuer_name (x5o9, name);
   
   /* add various extensions: standard extensions */
   add_ext (x5o9, NID_basic_constraints, "critical,CA:TRUE");
   add_ext (x5o9, NID_key_usage, "critical,keyCertSign,cRLSign,\
digitalSignature,keyEncipherment");
   add_ext (x5o9, NID_ext_key_usage, "serverAuth");
   
   add_ext (x5o9, NID_subject_key_identifier, "hash");

   zptr = (sptr = buf) + sizeof(buf)-1;
   for (cptr = "DNS.1:"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   for (cptr = san; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';
   add_ext (x5o9, NID_subject_alt_name, buf);
   
   zptr = (sptr = buf) + sizeof(buf)-1;
   for (cptr = SoftwareID; *cptr && sptr < zptr; *sptr++ = *cptr++);
   for (cptr = " dynamic certificate"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';
   add_ext (x5o9, NID_netscape_comment, buf);

   if (!X509_sign (x5o9, pkey, EVP_md5())) goto err;
   
   *x509p = x5o9;
   *pkeyp = pkey;
   return (1);

err:
   return (0);
}

/*****************************************************************************/
/*
Add extension using V3 code: we can set the config file as NULL because we wont
reference any other sections.
*/

static int add_ext (X509 *cert, int nid, char *value)
{
   X509_EXTENSION *ex;
   X509V3_CTX ctx;

   /* This sets the 'context' of the extensions. */
   /* No configuration database */
   X509V3_set_ctx_nodb (&ctx);

   /* Issuer and subject certs: both the target since it is self signed,
      no request and no CRL */
   X509V3_set_ctx (&ctx, cert, cert, NULL, NULL, 0);
   ex = X509V3_EXT_conf_nid (NULL, &ctx, nid, value);
   if (!ex) return 0;
   
   X509_add_ext (cert, ex, -1);
   X509_EXTENSION_free (ex);

   return (1);
}
        
/*****************************************************************************/

#else /* SESOLA */

char* SesolaMkCert ()
{
   ErrorNoticed (NULL, SS$_BUGCHECK, "This is a non-SSL version.", FI_LI);
   return (NULL);
}

/*****************************************************************************/

/************************/
#endif  /* ifdef SESOLA */
/************************/

