/*****************************************************************************/ /* 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 #include #include #include /* VMS related header files */ #include #include /* application header files */ #define SESOLA_REQUIRED #include "Sesola.h" #define WASD_MODULE "SESOLAMKCERT" /***************************************/ #ifdef SESOLA /* secure sockets layer */ /***************************************/ #include #include #include #include #include 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 */ /************************/