/*****************************************************************************/ /* 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. Stores the certificate in a process logical so that it can be reused. If the server is just restarted the certificate remains. If the server is killed and completely started the certificate is regenerated (because the process logical name storage has gone). The per-process logical name cert storage can be disabled by defining the logical name WASD_MKCERT_NO_RETAIN prior to startup. Core functionality purloined from: https://opensource.apple.com/source/OpenSSL/OpenSSL-22/openssl/demos/x509/mkcert.c VERSION HISTORY --------------- 20-MAY-2021 MGD SesolaMkCertRetain() 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 #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 char* SesolaMkCertRetain (char* name, char *cert); 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 unsigned long SysNamMask[]; extern BIO *SesolaBioMemPtr; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* Called by SesolaInitService(). Generate a self-signed (actually an unsigned) certificate. */ char* SesolaMkCert (char *san) { int bits = 2048, days = 180; unsigned long serial[2]; char *cptr, *pemptr; BIO *bioptr; X509 *x5o9 = NULL; /* note this is an oh not a zero */ EVP_PKEY *pkey = NULL; RSA *rkey = NULL; /*********/ /* begin */ /*********/ if (pemptr = SesolaMkCertRetain (san, NULL)) return (pemptr); /* 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)); if (cptr = strstr (pemptr, "-----BEGIN PRIVATE KEY-----")) SesolaMkCertRetain (san, cptr); return (cptr); } /*****************************************************************************/ /* Stores the certificate in a process logical so that it can be reused. */ char* SesolaMkCertRetain (char* name, char *cert) { static int CertTextSize = 4096; static int LnmCount; static unsigned short LogValueLength; static char *CertTextPtr = NULL; static char LogValue [256]; static char LogicalName[256]; static $DESCRIPTOR (LogNameDsc, LogicalName); static $DESCRIPTOR (LnmProcessDsc, "LNM$PROCESS"); static uchar ExecMode = 1; /* PSL$C_EXEC */ static VMS_ITEM_LIST3 LnmItems [] = { { sizeof(LnmCount), LNM$_INDEX, &LnmCount, 0 }, { sizeof(LogValue)-1, LNM$_STRING, &LogValue, &LogValueLength }, { 0,0,0,0 } }; int status; char *cptr, *czptr, *sptr, *zptr; VMS_ITEM_LIST3 CreLnmItems [128]; /*********/ /* begin */ /*********/ if (SysTrnLnm (WASD_MKCERT_NO_RETAIN)) return (NULL); LogNameDsc.dsc$w_length = sprintf (LogicalName, "WASD_CERT_%s", name); for (cptr = LogicalName + 10; *cptr; *cptr++ = toupper(*cptr)); if (cert) { /**************/ /* store cert */ /**************/ memset (CreLnmItems, 0, sizeof(CreLnmItems)); cptr = cert; for (LnmCount = 0; LnmCount <= 127; LnmCount++) { for (sptr = cptr; *sptr && sptr - cptr < 255; sptr++); CreLnmItems[LnmCount].buf_len = sptr - cptr; CreLnmItems[LnmCount].item = LNM$_STRING; CreLnmItems[LnmCount].buf_addr = cptr; CreLnmItems[LnmCount].ret_len = 0; if (!*sptr) break; cptr = sptr; } if (VMSnok (status = sys$setprv (1, &SysNamMask, 0, 0))) ErrorExitVmsStatus (status, "sys$setprv()", FI_LI); status = sys$crelnm (0, &LnmProcessDsc, &LogNameDsc, &ExecMode, &CreLnmItems); if (VMSnok (status)) ErrorNoticed (NULL, status, LogicalName, FI_LI); if (VMSnok (status = sys$setprv (0, &SysNamMask, 0, 0))) ErrorExitVmsStatus (status, "sys$setprv()", FI_LI); return (NULL); } else { /*************/ /* read cert */ /*************/ for (;;) { if (!CertTextPtr) CertTextPtr = VmGet (CertTextSize); zptr = (sptr = CertTextPtr) + CertTextSize - 1; *sptr = '\0'; for (LnmCount = 0; LnmCount <= 127; LnmCount++) { status = sys$trnlnm (0, &LnmProcessDsc, &LogNameDsc, 0, &LnmItems); if (VMSok (status)) { if (!LogValueLength) break; for (czptr = (cptr = LogValue) + LogValueLength; cptr < czptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; } else break; } if (VMSnok (status)) return (NULL); if (!LogValueLength) return (CertTextPtr); VmFree (CertTextPtr, FI_LI); CertTextPtr = NULL; CertTextSize *= 2; } } } /*****************************************************************************/ 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 */ /************************/