/*****************************************************************************/ /* SesolaCert.c Parse X509 certificates. See description in SESOLA.C. https://tools.ietf.org/html/rfc5280 https://zakird.com/2013/10/13/certificate-parsing-with-openssl/ https://stackoverflow.com/questions/22966461/reading-an-othername-value-from-a-subjectaltname-certificate-extension https://www.cs.auckland.ac.nz/~pgut001/dumpasn1.cfg VERSION HISTORY --------------- 11-FEB-2023 MGD SesolaCertVerifyCallback() new algorithm for determining client certificate validity (also see SesolaClientCert()) 02-MAR-2020 MGD bugfix; SesolaCertExtension() BIO_NOCLOSE memory leak 12-APR-2019 MGD bugfix; SesolaCertExtension() storage reset bugfix; SesolaCertParseDn() restore from 25-AUG-2015 [ru:/CN=] allows multiple to be selected between (e.g. "[ru:/CN=user*]", "[ru:/CN=^^\[^/=\]*$]" ) [ru:..] escape characters using '\' (especially ']') 20-NOV-2018 MGD SesolaCertExtension() eliminate need for UPN NID by always coverting the OID to text and then compare the string 26-SEP-2018 MGD SesolaCertVerifyCallback() moved to here from SESOLA.C 16-JUN-2017 MGD bugfix; SesolaCertExtension() generate UPN independently for each of pre- and post- OpenSSL 1.1.n 03-AUG-2016 MGD OpenSSL v1.1.0(-pre6) required code changes including #if (OPENSSL_VERSION_NUMBER < 0x10100000L) compilation 22-OCT-2015 MGD move from SESOLA.C and refine SesolaCertName() to parse X509 issuer and subject SesolaCertExtension() to parse X509 extensions SesolaCertParseDn() from original SesolaParseCertDn() */ /*****************************************************************************/ #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 "SESOLACERT" /***************************************/ #ifdef SESOLA /* secure sockets layer */ /***************************************/ static struct SesolaCertDnRecStruct { char *name; int length; } SesolaCertDnRec [] = { { "/C=", 3 }, /* countryName */ { "/ST=", 4 }, /* stateOrProvinceName */ { "/SP=", 4 }, /* stateOrProvinceName */ { "/L=", 3 }, /* localityName */ { "/O=", 3 }, /* organizationName */ { "/OU=", 4 }, /* organizationalUnitName */ { "/CN=", 4 }, /* commonName */ { "/T=", 3 }, /* title */ { "/I=", 3 }, /* initials */ { "/G=", 3 }, /* givenName */ { "/S=", 3 }, /* surname */ { "/D=", 3 }, /* description */ { "/Uid=", 5 }, /* uniqueIdentifier */ { "/Email=", 7 }, /* pkcs9_emailAddress */ { "/emailAddress=", 13 }, /* pkcs9_emailAddress */ { NULL, 0 } }; /********************/ /* external storage */ /********************/ #ifdef DBUG extern BOOL Debug; #else #define Debug 0 #endif extern char ErrorSanityCheck[], SoftwareID[]; extern BIO *SesolaBioMemPtr; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* OpenSSL calls this function at each stage during certificate verification processing (client or CA). It is used to report on that progress and to add other processing as required. Algorithm for determining client certificate validity involves collecting error codes in sesolaptr->CertVerifyCode[] counted by sesolaptr->CertVerifyCount up to IGNORE_ERROR_MAX and always returning success. SesolaClientCert() then checks sesolaptr->CertVerifyCount for non-zero and processes the error codes (ignoring any configuration set codes) to determine success of failure of the client certificate verification. */ int SesolaCertVerifyCallback ( int OkValue, /* void* for convenience in compiling non-SSL version */ void *StoreCtxPtr ) { int ErrorCode, ErrorDepth, VerifyDepth, VerifyMode, WatchThisCat; char *cptr, *sptr; char String [512]; REQUEST_STRUCT *rqptr; SESOLA_STRUCT *sesolaptr; SSL *SslPtr; X509 *CertPtr; X509_STORE_CTX *CtxPtr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaCertVerifyCallback() !UL !&X", OkValue, StoreCtxPtr); CtxPtr = (X509_STORE_CTX*)StoreCtxPtr; SslPtr = (SSL*)X509_STORE_CTX_get_ex_data (CtxPtr, SSL_get_ex_data_X509_STORE_CTX_idx()); sesolaptr = (SESOLA_STRUCT*)SSL_get_ex_data (SslPtr, 0); if (!sesolaptr) { ErrorNoticed (NULL, SS$_BUGCHECK, ErrorSanityCheck, FI_LI); return (0); } sesolaptr->CertVerifyCallbackCount++; CertPtr = X509_STORE_CTX_get_current_cert (CtxPtr); ErrorCode = X509_STORE_CTX_get_error (CtxPtr); ErrorDepth = X509_STORE_CTX_get_error_depth (CtxPtr); VerifyMode = SSL_get_verify_mode (SslPtr); if (WATCHMOD (sesolaptr, WATCH_MOD_SESOLA)) WatchThis (WATCHITM(sesolaptr), WATCH_MOD_SESOLA, "VERIFY !UL", VerifyMode); if (!VerifyMode) return (1); else if (VerifyMode & SSL_VERIFY_PEER) sesolaptr->X509CertRequested = true; else if (VerifyMode & SSL_VERIFY_CLIENT_ONCE) sesolaptr->X509CertRequested = true; VerifyDepth = sesolaptr->CertVerifyDepth = SSL_get_verify_depth (SslPtr); if (WATCHMOD (sesolaptr, WATCH_MOD_SESOLA)) WatchThis (WATCHITM(sesolaptr), WATCH_MOD_SESOLA, "DEPTH !UL", VerifyDepth); WatchThisCat = 0; if (WATCH_CAT && WATCHPNT(sesolaptr)) { if (WATCH_CATEGORY(WATCH_SESOLA)) WatchThisCat = WATCH_SESOLA; else if (WATCH_CATEGORY(WATCH_AUTH)) WatchThisCat = WATCH_AUTH; } if (WatchThisCat) { if (sesolaptr->CertVerifyCallbackCount == 1) SesolaCertWatch (CertPtr); WatchThis (WATCHITM(sesolaptr), WatchThisCat, "X509 VERIFY callback !UL pre:!SL error:!SL depth:!SL", sesolaptr->CertVerifyCallbackCount, OkValue, ErrorCode, ErrorDepth); if (WatchThisCat) if (ErrorCode) WatchThis (WATCHITM(sesolaptr), WatchThisCat, "X509 VERIFY error, !UL \"!AZ\"", ErrorCode, X509_verify_cert_error_string(ErrorCode)); } if (ErrorCode) { if (sesolaptr->CertVerifyCount < IGNORE_ERROR_MAX) sesolaptr->CertVerifyCode[sesolaptr->CertVerifyCount] = ErrorCode; sesolaptr->CertVerifyCount++; } return (1); } /*****************************************************************************/ /* Provide certificate information. */ void SesolaCertWatch (void *vCertPtr) { char *cptr, *sptr; char String [512]; X509 *CertPtr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaCertWatch()"); CertPtr = (X509*)vCertPtr; BIO_reset (SesolaBioMemPtr); X509_NAME_oneline (X509_get_issuer_name(CertPtr), String, sizeof(String)); WatchDataFormatted ("ISSUER: !AZ\n", String); X509_NAME_oneline (X509_get_subject_name(CertPtr), String, sizeof(String)); WatchDataFormatted ("SUBJECT: !AZ\n", String); ASN1_UTCTIME_print (SesolaBioMemPtr, X509_get_notBefore(CertPtr)); BIO_gets (SesolaBioMemPtr, String, sizeof(String)); WatchDataFormatted ("NOTBEFORE: !AZ\n", String); ASN1_UTCTIME_print (SesolaBioMemPtr, X509_get_notAfter(CertPtr)); BIO_gets (SesolaBioMemPtr, String, sizeof(String)); WatchDataFormatted ("NOTAFTER: !AZ\n", String); SesolaCertFingerprint (CertPtr, &EVP_sha256, String, sizeof(String)); WatchDataFormatted ("SHA256: !AZ\n", String); SesolaCertFingerprint (CertPtr, &EVP_sha1, String, sizeof(String)); WatchDataFormatted ("SHA1: !AZ\n", String); SesolaCertFingerprint (CertPtr, &EVP_md5, String, sizeof(String)); WatchDataFormatted ("MD5: !AZ\n", String); for (cptr = sptr = String; *cptr; cptr++) if (*cptr != ':') *sptr++ = *cptr; *sptr = '\0'; WatchDataFormatted ("FINGERPRINT: !AZ\n", String); BIO_reset (SesolaBioMemPtr); } /*****************************************************************************/ /* Performs two functions parsing X.509 certificate extensions. First, if supplied with an record name using |RecordNamePtr| it searches (case sensitive) for the matching record in the certificate supplied by |CertPtr|. If found it returns a pointer to the full record (e.g. "name=Testing Only"). If not found (and any other condition) it returns a NULL. |RecordNamePtr| can be any record name where underscores are substituted for spaces, with or without any string following on from the equate symbol. The second use is to progressively parse all extension name=value pairs, returning each of the records with each call, until the certificate is exhausted and a NULL is returned. The first call must include the |CertPtr| parameter and subsequent calls NULL. The |RecordNamePtr| parameter must be NULL. Needless-to-say (because of the use of returned static storage) this function is not reentrant!! */ char* SesolaCertName ( void *CertPtr, char *RecordNamePtr ) { static int EntryCount = 0, EntryNumber = 0, NameValueSize = 0; static uchar *NameValuePtr; static char RecordNameValue [2048]; static X509_NAME *NamePtr; int nid; uchar *cptr, *sptr, *zptr, *SelectPtr, *ValuePtr; uchar ObjectName [256], RecordName [256]; ASN1_OBJECT *obj; ASN1_STRING *data; X509_NAME_ENTRY *EntryPtr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaCertName() !UL/!UL !AZ", EntryNumber, EntryCount, RecordNamePtr); if (CertPtr) { /* initialise (subject is the default) */ EntryNumber = 0; if (RecordNamePtr) { if (MATCH6 (RecordNamePtr, "ISSUER")) { NamePtr = X509_get_issuer_name(CertPtr); RecordNamePtr += 6; if (*RecordNamePtr) RecordNamePtr++; } else if (MATCH7 (RecordNamePtr, "SUBJECT")) { NamePtr = X509_get_subject_name(CertPtr); RecordNamePtr += 7; if (*RecordNamePtr) RecordNamePtr++; } else NamePtr = X509_get_subject_name(CertPtr); if (!*RecordNamePtr) RecordNamePtr = NULL; } else NamePtr = X509_get_subject_name(CertPtr); EntryCount = X509_NAME_entry_count (NamePtr); if (!RecordNamePtr) return (NULL); } /* e.g. "[ru:commonName=email:]" */ RecordName[0] = '\0'; if (cptr = RecordNamePtr) { zptr = (sptr = RecordName) + sizeof(RecordName)-1; while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (*cptr == '=') SelectPtr = cptr; else SelectPtr = NULL; } /************************/ /* loop through records */ /************************/ while (EntryNumber < EntryCount) { if (!(EntryPtr = X509_NAME_get_entry (NamePtr, EntryNumber++))) { ErrorNoticed (NULL, SS$_BUGCHECK, "NULL", FI_LI); continue; } if (!(obj = X509_NAME_ENTRY_get_object (EntryPtr))) { ErrorNoticed (NULL, SS$_BUGCHECK, "NULL", FI_LI); continue; } if (MATCH4 (RecordName, "OID_")) { OBJ_obj2txt (RecordNameValue+4, sizeof(RecordNameValue)-4, obj, 1); sptr = RecordNameValue; *(ULONGPTR)sptr = 'OID_'; sptr += 4; } else { if ((nid = OBJ_obj2nid(obj)) == NID_undef) { *(ULONGPTR)(cptr = ObjectName) = 'OID_'; OBJ_obj2txt (ObjectName+4, sizeof(ObjectName)-4, obj, 1); } else if (!(cptr = OBJ_nid2ln (nid))) { ErrorNoticed (NULL, SS$_BUGCHECK, "NULL", FI_LI); continue; } zptr = (sptr = RecordNameValue) + sizeof(RecordNameValue)-1; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; sptr = RecordNameValue; } while (*sptr) { /* only alphanumerics and underscores (a la DCL symbols) */ if (!((*sptr >= '0' && *sptr <= '9') || (*sptr >= 'A' && *sptr <= 'Z') || (*sptr >= 'a' && *sptr <= 'z'))) *sptr = '_'; sptr++; } if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!&Z", RecordNameValue); /****************/ /* match record */ /****************/ /* if looking for a particular record */ if (RecordNamePtr) if (strcmp (RecordName, RecordNameValue)) continue; /****************/ /* record value */ /****************/ if (!(data = X509_NAME_ENTRY_get_data (EntryPtr))) { ErrorNoticed (NULL, SS$_BUGCHECK, "NULL", FI_LI); continue; } cptr = ASN1_STRING_data (data); if (sptr < zptr) *sptr++ = '='; ValuePtr = sptr; while (*cptr && sptr < zptr) { if (*cptr == '\\') *sptr++ = '\\'; if (sptr < zptr) *sptr++ = *cptr++; } /* if the buffer overflowed then just set an empty string! */ if (sptr >= zptr) sptr = RecordNameValue; *sptr = '\0'; if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!&Z", RecordNameValue); /**********************/ /* drill down further */ /**********************/ if (RecordNamePtr) { if (SelectPtr) { if (!StringMatchRegex (NULL, ValuePtr, SelectPtr)) { RecordNameValue[0] = '\0'; continue; } } } return (RecordNameValue); } EntryCount = EntryNumber = 0; NamePtr = NULL; return (NULL); } /*****************************************************************************/ /* Performs two functions parsing X.509 certificate extensions. First, if supplied with an extension name using |RecordNamePtr| it searches (case insensitive) for the matching extension in the certificate supplied by |CertPtr|. If found it returns a pointer to the full record (e.g. "name=Testing Only"). If not found (and any other condition) it returns a NULL. |RecordNamePtr| can be any extension name where underscores are substituted for spaces, with or without any string following on from the equate symbol. The second use is to progressively parse all extension name=value pairs, returning each of the extensions with each call, until the certificate is exhausted and a NULL is returned. The first call must include the |CertPtr| parameter and subsequent calls NULL. The |RecordNamePtr| parameter must be NULL. |BufferPtr| and |ExtensionsPtr| point to the buffers of counted null-terminated strings terminated by a zero length string. That is, "[ushort]string\0[ushort]string\0[zero]". The terminating null is counted as one character. The first outer loop populates |ExtensionsPtr| with the extensions parsed into one of the above strings. The second loop parses the |ExtensionsPtr| series of strings into a |BufferPtr| second series of strings where the various sub fields of the extension data are indivisual strings. All very involuted. Needless-to-say (because of the use of returned static storage) this function is not reentrant!! */ char* SesolaCertExtension ( void *CertPtr, char *RecordNamePtr ) { #define EXTENSION_BUFFER_QUANTUM 4096 #define EXTENSION_SHORTHAND_QUANTUM 256 static int BufferLength = 0, BufferSize = 0, ExtCount = 0, ExtNumber = 0, ExtensionsSize = 0, NameDigit = 0, ShortHandLength = 0, ShortHandSize = 0; static uchar *BufferPtr = NULL, *ContextPtr = NULL, *ExtensionsPtr = NULL, *ShortHandPtr = NULL; static STACK_OF(X509_EXTENSION) *ExtPtr; BOOL NoMulti, OnePart, TwoParts, UseOids, WithSelect; int cnt, idx, ncnt, nid, NameCount; uchar *aptr, *cptr, *sptr, *nptr, *zptr, *FindPtr, *HitPtr, *ExtensionNamePtr, *KeywordPtr, *ShortPtr, *StringPtr, *ValuePtr; uchar ObjectName [256], RecordName [256], String [2048]; ASN1_OBJECT *obj; BUF_MEM *bufptr; GENERAL_NAME *NamePtr; STACK_OF(GENERAL_NAME) *NamesPtr; X509_EXTENSION *ext; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaCertExtension() 0x!8XL !UL/!UL !&Z", CertPtr, ExtNumber, ExtCount, RecordNamePtr == (char*)-1 ? "-1" : RecordNamePtr); NoMulti = OnePart = TwoParts = UseOids = WithSelect = false; if (cptr = RecordNamePtr) { if (cptr == (char*)-1) { NoMulti = true; RecordNamePtr = NULL; } else if (MATCH3 (cptr, "OID") || MATCH3 (cptr, "oid")) { UseOids = true; if (MATCH4 (cptr, "OID") || MATCH4 (cptr, "oid")) RecordNamePtr = NULL; } if (cptr = RecordNamePtr) { /* is there a "one=two"? */ while (*cptr && *cptr != '=') cptr++; if (*cptr) { /* is there a "one=two=" (puntuation == wildcards) */ for (sptr = cptr + 1; *sptr; sptr++) if (ispunct(*sptr)) break; if (*sptr) OnePart = WithSelect = true; else TwoParts = true; } } else OnePart = true; } } if (CertPtr) { /* intialise the certificate parsing */ ExtNumber = ExtCount = 0; zptr = (sptr = ExtensionsPtr) + ExtensionsSize; if (ExtPtr = (X509_get0_extensions (CertPtr))) { ExtCount = sk_X509_EXTENSION_num (ExtPtr); if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "sk_X509_EXTENSION_num() !SL", ExtCount); } } /* otherwise use the already-buffered certificate data */ /***************************/ /* loop through extensions */ /***************************/ /* only do this loop when a certificate has been supplied */ zptr = (sptr = ExtensionsPtr) + ExtensionsSize; while (CertPtr) { if (sptr >= zptr) { /* need (more) buffer space */ ExtensionsSize += EXTENSION_BUFFER_QUANTUM; ExtensionsPtr = VmRealloc (ExtensionsPtr, ExtensionsSize, FI_LI); ExtNumber = 0; } zptr = (sptr = ExtensionsPtr) + ExtensionsSize; while (ExtNumber < ExtCount) { ShortPtr = sptr; sptr += sizeof(ushort); StringPtr = sptr; if (!(ext = sk_X509_EXTENSION_value (ExtPtr, ExtNumber++))) { ErrorNoticed (NULL, SS$_BUGCHECK, "NULL", FI_LI); continue; } if (!(obj = X509_EXTENSION_get_object (ext))) { ErrorNoticed (NULL, SS$_BUGCHECK, "NULL", FI_LI); continue; } if ((nid = OBJ_obj2nid(obj)) == NID_undef) { *(ULONGPTR)(cptr = ObjectName) = 'OID_'; OBJ_obj2txt (ObjectName+4, sizeof(ObjectName)-4, obj, 1); } else if (!(cptr = OBJ_nid2ln (nid))) { ErrorNoticed (NULL, SS$_BUGCHECK, "NULL", FI_LI); continue; } if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "OBJ_nid2ln() !AZ", cptr); /***************/ /* record name */ /***************/ if (UseOids) { *(ULONGPTR)sptr = 'OID_'; sptr += 4; OBJ_obj2txt (sptr, BufferSize-(sptr-BufferPtr), obj, 1); while (*sptr) sptr++; } else { while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!&Z", StringPtr); /****************/ /* record value */ /****************/ if (sptr < zptr) *sptr++ = '='; ValuePtr = sptr; *sptr = '\0'; NamesPtr = NULL; if (nid == NID_subject_alt_name) { /****************************/ /* subject alternative name */ /****************************/ /* parse SAN value as a stack of general names */ NamesPtr = X509_get_ext_d2i (CertPtr, NID_subject_alt_name, NULL, NULL); if (NamesPtr == NULL) { if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "X509_get_ext_d2i() NULL"); continue; } NameCount = sk_GENERAL_NAME_num(NamesPtr); for (ncnt = 0; ncnt < NameCount; ncnt++) { /* each name has an implicit context tag that identifies type */ NamePtr = sk_GENERAL_NAME_value(NamesPtr, ncnt); switch (NamePtr->type) { case GEN_OTHERNAME: /* generate the OID number */ aptr = nptr = ObjectName; *(ULONGPTR)aptr = 'OID_'; aptr += 4; OBJ_obj2txt (aptr, sizeof(ObjectName)-(aptr-ObjectName), NamePtr->d.otherName->type_id, 1); while (*aptr) aptr++; *(USHORTPTR)aptr = ':\0'; /* if it's the Microsoft UPN OID */ if (!memcmp (nptr, "OID_1.3.6.1.4.1.311.20.2.3:", 27)) nptr = "userPrincipalName:"; cptr = (char*)ASN1_STRING_data (NamePtr->d.otherName-> value->value.asn1_string); break; case GEN_EMAIL: nptr = "rfc822Name:"; cptr = (char*)ASN1_STRING_data (NamePtr->d.rfc822Name); break; case GEN_DNS: nptr = "dNSName:"; cptr = (char*)ASN1_STRING_data (NamePtr->d.dNSName); break; case GEN_X400: /* TODO: add support for X400 names */ nptr = "x400Address:"; cptr = "[unimplemented]"; break; case GEN_DIRNAME: nptr = "directoryName:"; cptr = X509_NAME_oneline (NamePtr->d.directoryName, String, sizeof(String)); break; case GEN_EDIPARTY: /* TODO: add support for EDI party names */ nptr = "ediPartyName:"; cptr = "[unimplemented]"; break; case GEN_URI: nptr = "uniformResourceIdentifier:"; cptr = (char*)ASN1_STRING_data (NamePtr-> d.uniformResourceIdentifier); break; case GEN_IPADD: nptr = "iPAddress:"; cptr = (uchar*)ASN1_STRING_data (NamePtr->d.iPAddress); if (ASN1_STRING_length (NamePtr->d.iPAddress) == 4) cptr = TcpIpAddressToString (*(uint*)cptr, 4); else if (ASN1_STRING_length (NamePtr->d.iPAddress) == 16) cptr = TcpIpAddressToString (cptr, 6); else cptr = "?"; break; case GEN_RID: /* TODO: add support for registered IDs */ nptr = "registeredID:"; cptr = "[unimplemented]"; break; default: nptr = "GEN_UNKNOWN:"; cptr = "[unimplemented]"; } /* if multi value add carriage-control to preceding line */ if (sptr > ValuePtr) { if (sptr < zptr) *sptr++ = '\r'; if (sptr < zptr) *sptr++ = '\n'; } /* copy the name in */ while (*nptr && sptr < zptr) { if (*nptr == '\\' && *(nptr+1)) *sptr++ = '\\'; if (sptr < zptr) *sptr++ = *nptr++; } *sptr = '\0'; if (!*StringPtr) break; /* copy the value in */ while (*cptr && sptr < zptr) { if (*cptr == '\\' && *(cptr+1)) *sptr++ = '\\'; if (sptr < zptr) *sptr++ = *cptr++; } } if (NamesPtr) GENERAL_NAMES_free (NamesPtr); } else { /************************/ /* some other extension */ /************************/ BIO_reset (SesolaBioMemPtr); if (!X509V3_EXT_print (SesolaBioMemPtr, ext, 0, 0)) ASN1_STRING_print (SesolaBioMemPtr, X509_EXTENSION_get_data(ext)); BIO_get_mem_ptr (SesolaBioMemPtr, &bufptr); cptr = bufptr->data; cnt = bufptr->length; /* append value to name */ while (cnt-- && sptr < zptr) { if (*cptr == '\0' || (*cptr == '\\' && *(cptr+1))) *sptr++ = '\\'; if (sptr < zptr) *sptr++ = *cptr++; } BIO_reset (SesolaBioMemPtr); *sptr = '\0'; if (!*StringPtr) break; } /* yes, we do increment */ *sptr++ = '\0'; if (!*StringPtr) break; /*********************/ /* end of name=value */ /*********************/ /* if overflowed then leave this loop */ if (sptr >= zptr) break; cnt = sptr - ShortPtr - sizeof(ushort); /* maximum string length of 65k */ if (cnt & 0xffff0000) ErrorExitVmsStatus (SS$_RESULTOVF, ErrorSanityCheck, FI_LI); /* insert the length of the string including terminating null */ *(USHORTPTR)ShortPtr = cnt; if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!UL !&Z", *(USHORTPTR)ShortPtr, ShortPtr + sizeof(ushort)); /* add the zero-length string sentinal */ *(USHORTPTR)sptr = 0; } /* if overflowed, go back, get more buffer space, do it all again */ if (sptr >= zptr) continue; break; } if (WATCH_MODULE(WATCH_MOD_SESOLA)) SesolaCertFind (ExtensionsPtr, "*", NULL); /******************/ /* parse elements */ /******************/ /* only do this loop when a certificate has been supplied */ zptr = (sptr = BufferPtr) + BufferSize; while (CertPtr) { if (sptr >= zptr) { /* need (more) buffer space */ BufferSize += EXTENSION_BUFFER_QUANTUM; BufferPtr = VmRealloc (BufferPtr, BufferSize, FI_LI); ExtNumber = 0; } zptr = (sptr = BufferPtr) + BufferSize; ContextPtr = NULL; SesolaCertFind (ExtensionsPtr, NULL, &ContextPtr); while (cptr = SesolaCertFind (NULL, NULL, &ContextPtr)) { ExtensionNamePtr = NULL; cptr += sizeof(ushort); while (*cptr) { /****************/ /* copy in name */ /****************/ /* set to a zero-length sentinal so we can SesolaCertFind() */ *(USHORTPTR)(ShortPtr = sptr) = 0; sptr += sizeof(ushort); StringPtr = sptr; if (ExtensionNamePtr) aptr = ExtensionNamePtr; else aptr = cptr; while (*aptr && *aptr != '=' && sptr < zptr) { if (*aptr == '\\' && *(aptr+1)) *sptr++ = *aptr++; if (sptr < zptr) *sptr++ = *aptr++; } *sptr = '\0'; if (!*StringPtr) break; if (ExtensionNamePtr) { /* second pass check for any known keywords */ KeywordPtr = SesolaCertKeyword (cptr); if (!KeywordPtr) { sptr = ShortPtr; break; } } else { /* buffer this for use with multiple keyword values */ ExtensionNamePtr = cptr; if (*aptr == '=') aptr++; cptr = aptr; KeywordPtr = NULL; } if (KeywordPtr) { /*******************/ /* copy in keyword */ /*******************/ /* go to the start of the keyword */ cptr = KeywordPtr; /* where two parts are separated by a '=' substitute a '$' */ if (sptr < zptr) *sptr++ = (TwoParts ? '$' : '_'); while (*cptr && *cptr != ':' && sptr < zptr) { if (*cptr == '\\' && *(cptr+1)) *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = *cptr++; } *sptr = '\0'; if (*cptr) cptr++; } /* name has only alphanums and underscores (a la DCL symbols) */ for (sptr = StringPtr; *sptr; sptr++) if (!((*sptr >= '0' && *sptr <= '9') || (*sptr >= 'A' && *sptr <= 'Z') || (*sptr >= 'a' && *sptr <= 'z') || (TwoParts && *sptr == '$'))) *sptr = '_'; /* compress multiple underscores into singles */ for (aptr = sptr = StringPtr; *aptr; aptr++) { while (*(USHORTPTR)aptr == '__') aptr++; *sptr++ = *aptr; } *sptr = '\0'; /********************/ /* handle multiples */ /********************/ /* if the name already exists append TWO undercores and a number */ nptr = sptr; for (cnt = 2; cnt < 32; cnt++) { if (!SesolaCertFind (BufferPtr, StringPtr, NULL)) break; sprintf (nptr, "__%d", cnt); } while (*sptr) sptr++; if (cnt >= 32) ErrorNoticed (NULL, SS$_BUGCHECK, NULL, FI_LI); /*****************/ /* copy in value */ /*****************/ /* separate the name from the value */ if (sptr < zptr) *sptr++ = '='; if (KeywordPtr) { /* up until carriage-control (end of line) */ while (*cptr && *cptr != '\r' && *cptr != '\n' && sptr < zptr) { if (*cptr == '\\' && *(cptr+1)) *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = *cptr++; } /* scan over any trailing carriage-control */ while (*cptr == '\r' || *cptr == '\n') cptr++; } else { ValuePtr = cptr; while (*cptr && sptr < zptr) { if (*cptr == '\\' && *(cptr+1)) *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = *cptr++; } /* a second pass will check for any keywords */ cptr = ValuePtr; } /* yes, we do increment */ *sptr++ = '\0'; /*********************/ /* end of name=value */ /*********************/ /* if overflowed then leave this loop */ if (sptr >= zptr) break; cnt = sptr - StringPtr; /* maximum string length of 65k */ if (cnt & 0xffff0000) ErrorExitVmsStatus (SS$_RESULTOVF, ErrorSanityCheck, FI_LI); /* insert the length of the string including terminating null */ *(USHORTPTR)ShortPtr = cnt; if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!UL !&Z", *(USHORTPTR)ShortPtr, ShortPtr + sizeof(ushort)); /* add a zero-length string sentinal */ *(USHORTPTR)sptr = 0; } /* if overflowed then leave this loop */ if (sptr >= zptr) break; } /* if overflowed, go back, get more buffer space, do it all again */ if (sptr >= zptr) continue; break; } if (WATCH_MODULE(WATCH_MOD_SESOLA)) SesolaCertFind (BufferPtr, "*", NULL); /**********************/ /* end parse elements */ /**********************/ /* e.g. "[ru:X509v3_Subject_Alternative_Name_email=]" */ if (cptr = RecordNamePtr) { /**********************/ /* search for element */ /**********************/ BOOL ShortHand = false; RecordName[0] = '\0'; /* first some shorthands (see also SesolaCgiVariablesExtension()) */ if (to_upper(*cptr) == 'X' && strsame (cptr, "X509V3_SAN", 10)) { /*****************/ /* SAN shorthand */ /*****************/ ShortHand = true; zptr = (sptr = RecordName) + sizeof(RecordName)-1; if (cptr[10] == '_') { if (strsame (cptr+10, "_UPN", 4) && (cptr[14] == '\0' || cptr[14] == '=')) cptr = "X509V3_SUBJECT_ALTERNATIVE_NAME_USERPRINCIPALNAME"; else if (strsame (cptr+10, "_822", 4) && (cptr[14] == '\0' || cptr[14] == '=')) cptr = "X509V3_SUBJECT_ALTERNATIVE_NAME_RFC822NAME"; } else if (cptr[10] == '=') { if (strsame (cptr+10, "=UPN", 4) && (cptr[14] == '\0' || cptr[14] == '=')) cptr = "X509V3_SUBJECT_ALTERNATIVE_NAME$USERPRINCIPALNAME"; else if (strsame (cptr+10, "=822", 4) && (cptr[14] == '\0' || cptr[14] == '=')) cptr = "X509V3_SUBJECT_ALTERNATIVE_NAME$RFC822NAME"; } if (cptr == RecordNamePtr) { /* concatenate the expanded SAN with what follows */ for (aptr = "X509V3_SUBJECT_ALTERNATIVE_NAME"; *aptr && sptr < zptr; *sptr++ = *aptr++); /* step over the "X509_SAN" */ cptr += 10; /* where two parts are separated by a '=' substitute a '$' */ if (TwoParts) { if (*cptr) cptr++; if (sptr < zptr) *sptr++ = '$'; } } /* up to but excluding any selection expression */ while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } else { zptr = (sptr = RecordName) + sizeof(RecordName)-1; while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = *cptr++; if (TwoParts) { if (*cptr && sptr < zptr) *sptr++ = *cptr++; while (*cptr && *cptr != '=' && sptr < zptr) *sptr++ = *cptr++; } *sptr = '\0'; } /********/ /* find */ /********/ HitPtr = SesolaCertFind (BufferPtr, RecordName, NULL); if (!HitPtr) return (NULL); if (WithSelect) { if (ShortHand) { /* refind the selection expression after the shorthand */ for (cptr = RecordNamePtr; *cptr; cptr++); while (cptr > RecordNamePtr && *cptr != '=') cptr--; if (*cptr) cptr++; } for (sptr = HitPtr + sizeof(ushort); *sptr && *sptr != '='; sptr++); if (*sptr) sptr++; if (!StringMatchRegex (NULL, sptr, cptr)) return (NULL); } if (!ShortHand) return (HitPtr + sizeof(short)); /* put the shorthand form in a buffer of its own to return */ for (;;) { zptr = (sptr = ShortHandPtr) + ShortHandSize; if (sptr >= zptr) { /* need (more) shorthand space */ ShortHandSize += EXTENSION_SHORTHAND_QUANTUM; ShortHandPtr = VmRealloc (ShortHandPtr, ShortHandSize, FI_LI); zptr = (sptr = ShortHandPtr) + ShortHandSize; } /* original shorthand name */ for (cptr = RecordNamePtr; *cptr && *cptr != '=' && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = '='; /* then append the returned hit value */ for (cptr = HitPtr + sizeof(ushort); *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; while (*cptr && sptr < zptr) *sptr++ = *cptr++; /* if overflow then go back and do it again */ if (sptr >= zptr) continue; *sptr = '\0'; break; } return (ShortHandPtr); } else { /***********************/ /* one-by-one per call */ /***********************/ if (CertPtr) SesolaCertFind (BufferPtr, NULL, NULL); else { HitPtr = SesolaCertFind (NULL, NULL, NULL); if (HitPtr) { HitPtr += sizeof(ushort); return (HitPtr); } } } return (NULL); } /*****************************************************************************/ /* Search the supplied string for instances of the internal and configurable ":" keyword. The can be any string terminated by a colon and the search is case-insensitive. If |StringPtr| doesn't point at the start of the keyword then the string is spanned for white-space leading into the ":". The logical name WASD_X509_EXTENSION_KEYWORDS defines a string in the format "::" for site-specific keywords. If the string begins with a plus symbol (e.g. "+::") these words supplement the internal keywords. When there is no leading '+' the string overrides the internal keywords. The logical name content can only be refreshed by server startup. */ uchar* SesolaCertKeyword (uchar* StringPtr) { static int StartCount = 2; static uchar *Keywords [3] = { NULL,NULL,SESOLA_X509_EXTENSION_KEYWORDS }; int cnt, len; uchar *aptr, *cptr, *sptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaCertKeyword() !&Z", StringPtr); if (!Keywords[1]) { /* read once at first use after server startup */ if (cptr = SysTrnLnm (WASD_X509_EXTENSION_KEYWORDS)) { Keywords[1] = VmGet (len = strlen(cptr)+1); strzcpy (Keywords[1], cptr, len); if (Keywords[1][0] == '+') Keywords[1]++; else StartCount = 1; } else Keywords[1] = ""; } while (*StringPtr) { len = 0; for (sptr = StringPtr; *sptr && isspace(*sptr); sptr++); if (!*sptr) break; StringPtr = sptr; for (cnt = StartCount; cnt; cnt--) { cptr = Keywords[cnt]; while (*cptr) { for (aptr = cptr; *aptr && *aptr != ':'; aptr++); if (!*aptr) break; if (!(len = aptr - cptr)) break; sptr = StringPtr; while (cptr < aptr && to_lower(*cptr) == to_lower(*sptr)) { cptr++; sptr++; } if (*cptr == ':' && *sptr == ':') break; cptr = aptr + 1; len = 0; } if (len) break; } if (len) break; for (sptr = StringPtr; *sptr && !isspace(*sptr); sptr++); if (!*sptr) break; StringPtr = sptr; } if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!UL !#AZ", len, len, len ? (char*)StringPtr : ""); if (len) return (StringPtr); return (NULL); } /*****************************************************************************/ /* |BufferPtr| points to a buffer of counted null-terminated strings, terminated by a zero length string. That is, "[ushort]string\0[ushort]string\0[zero]". The strings are in the form of a "[name]=[value]\0" pair. The terminating null is counted as one character. Return is to the [ushort] length, or NULL. */ uchar* SesolaCertFind ( uchar *BufferPtr, uchar *NamePtr, uchar **ContextPtr ) { static uchar *InternalPtr = NULL; ushort slen; uchar *aptr, *cptr, *sptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaCertFind() !&Z", NamePtr); if (WATCH_MODULE(WATCH_MOD_SESOLA)) { if (BufferPtr && NamePtr && *NamePtr == '*') { /* debug display all entries from the buffer pointer on */ int cnt = 0; WatchDataFormatted ("!80*~\n", BufferPtr); cptr = BufferPtr; for (;;) { if (!(slen = *(USHORTPTR)cptr)) break; WatchDataFormatted ("!2ZL: !3ZL !&Z\n", ++cnt, slen, cptr + sizeof(ushort)); cptr += sizeof(ushort) + slen; } WatchDataFormatted ("!80*~\n", BufferPtr); return (NULL); } } if (!BufferPtr && !NamePtr && !ContextPtr) ContextPtr = &InternalPtr; if (BufferPtr) { InternalPtr = BufferPtr; if (ContextPtr) *ContextPtr = BufferPtr; /* if (re)initialising and not searching */ if (!NamePtr) return (NULL); } else BufferPtr = *ContextPtr; if (!BufferPtr) return (NULL); cptr = BufferPtr; for (;;) { /* if zero length sentinal */ if (!(slen = *(USHORTPTR)cptr)) break; if (!(sptr = NamePtr)) { /* NULL |NamePtr| returns next string */ *ContextPtr = cptr + sizeof(ushort) + slen; if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "NEXT: !&Z", cptr + sizeof(ushort)); return (cptr); } /* search for specified name of "[ushort][name]=[value]\0" */ aptr = cptr; cptr += 2; while (*cptr && *sptr && ((to_upper(*cptr) == to_upper(*sptr) || /* where two parts are separated by a '=' substituted a '$' */ (*cptr == '$' && *sptr == '=')))) { cptr++; sptr++; } if (!*sptr && *cptr && !isalnum(*cptr)) { if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "HIT: !&Z", aptr + sizeof(ushort)); return (aptr); } cptr = aptr + sizeof(ushort) + slen; } if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "MISS!!"); if (ContextPtr) *ContextPtr = NULL; return (NULL); } /*****************************************************************************/ /* Performs two functions parsing X.509 Distinguished Names. First, if supplied with a DN record string using |RecordNamePtr| it searches (case sensitive) for the matching record in the string supplied by |DnPtr|. If found it returns a pointer to the full record (e.g. "/OU=Testing Only"). If not found (and any other condition) it returns a NULL. 'RecordNamePtr' can be "/OU=", "/CN=", etc., with or without any string following on from the equate symbol. The second use is to progressively parse a DN string, returning each of the full records with each call, until the string is exhausted and a NULL is returned. To reset the parse call with |RecordNamePtr| a NULL. Needless-to-say (because of the use of returned static storage) this function is not reentrant!! */ char* SesolaCertParseDn ( char *DnPtr, char *RecordNamePtr ) { static char *ContextPtr = NULL; static char RecordNameValue [1024]; int RecordNameLength; char *cptr, *sptr, *zptr, *SelectPtr; struct SesolaCertDnRecStruct *cdnptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaCertParseDn() !AZ !AZ", DnPtr, RecordNamePtr); if (!ContextPtr) ContextPtr = DnPtr; if (!(cptr = ContextPtr)) return (ContextPtr = NULL); if (!*cptr) return (ContextPtr = NULL); if (RecordNamePtr) { /* if a one-shot search just reset the context pointer */ if (RecordNamePtr) ContextPtr = NULL; /* search the subject string for the specified record name */ for (sptr = RecordNamePtr; *sptr && *sptr != '='; sptr++); SelectPtr = NULL; if (*sptr) { sptr++; if (*sptr) SelectPtr = sptr; } RecordNameLength = sptr - RecordNamePtr; } for (;;) { if (RecordNamePtr) { while (*cptr) { while (*cptr && *cptr != '/') cptr++; if (!*cptr) break; for (cdnptr = &SesolaCertDnRec; cdnptr->name; cdnptr++) if (!strncmp (cptr, cdnptr->name, cdnptr->length)) break; if (cdnptr->name && RecordNameLength == cdnptr->length && !strncmp (cdnptr->name, RecordNamePtr, RecordNameLength)) break; cptr++; } if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!&Z", cptr); /* if the certificate text has been exhausted */ if (!*cptr) return (NULL); } zptr = (sptr = RecordNameValue) + sizeof(RecordNameValue); while (*cptr && sptr < zptr) { if (*cptr == '/' && sptr < zptr) *sptr++ = *cptr++; while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++; if (!*cptr) break; /* check it's a recognized record delimiter */ for (cdnptr = &SesolaCertDnRec; cdnptr->name; cdnptr++) if (!strncmp (cptr, cdnptr->name, cdnptr->length)) break; if (cdnptr->name) break; } /* if the buffer overflowed then just set an empty string! */ if (sptr >= zptr) sptr = RecordNameValue; *sptr = '\0'; if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!AZ", RecordNameValue); if (RecordNamePtr) { if (SelectPtr) { if (!StringMatchRegex (NULL, RecordNameValue + RecordNameLength, SelectPtr)) { RecordNameValue[0] = '\0'; continue; } } } else ContextPtr = cptr; break; } return (RecordNameValue); } /*****************************************************************************/ /* Creates a "fingerprint" of the certificate. 'Colon' should be either a colon character (':') or a null character ('\0'). The first produces the common, printable fingerprint. The second just a string of hex digits used for certificate identification (a sort of hash). Return a string describing the digest algorithm (MD5) or an empty string indicating an error of some sort. */ char* SesolaCertFingerprint ( void *CertPtr, EVP_MD* (*DigestFunction)(void), char *BufferPtr, int SizeOfBuffer ) { static char HexDigits [] = "0123456789ABCDEF"; int idx, CertDigestLength; char *cptr, *sptr, *zptr; unsigned char CertDigest [EVP_MAX_MD_SIZE]; EVP_MD *DigestPtr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaCertFingerprint()"); if (SizeOfBuffer <= 0) return (""); zptr = (sptr = BufferPtr) + SizeOfBuffer - 1; DigestPtr = (*DigestFunction)(); if (X509_digest ((X509*)CertPtr, DigestPtr, CertDigest, &CertDigestLength)) { for (idx = 0; idx < CertDigestLength; idx++) { if (idx && sptr < zptr) *sptr++ = ':'; if (sptr < zptr) *sptr++ = HexDigits[(CertDigest[idx] & 0xf0) >> 4]; if (sptr < zptr) *sptr++ = HexDigits[(CertDigest[idx] & 0x0f)]; } *sptr = '\0'; return ((char*)OBJ_nid2sn (EVP_MD_type (DigestPtr))); } cptr = "X509_digest() ERROR!"; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; return (""); } /*****************************************************************************/ /* Place each name-value element from the distiguishing name into the buffer separated by newlines. |WhichPtr| parameter should be either "ISSUER" or "SUBJECT" (the default). */ int SesolaCertReportName ( void *CertPtr, char *WhichPtr, char *OutputBuffer, int SizeOfOutput ) { char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "SesolaCertReportName()"); SesolaCertName (CertPtr, WhichPtr); zptr = (sptr = OutputBuffer) + SizeOfOutput - 1; while (cptr = SesolaCertName (NULL, NULL)) { if (sptr > OutputBuffer && sptr < zptr) *sptr++ = '\n'; while (*cptr && sptr < zptr) *sptr++ = *cptr++; } *sptr = '\0'; if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!AZ", OutputBuffer); return (sptr - OutputBuffer); } /*****************************************************************************/ /* Just put a newline the certificate DN format at each component (meant to be placed inside
).
*/

int SesolaCertReportDn
(
char *InputDn,
char *OutputBuffer,
int SizeOfOutput
)
{
   char  *cptr, *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA,
                 "SesolaCertReportDn() !AZ", InputDn);

   zptr = (sptr = OutputBuffer) + SizeOfOutput - 1;
   while (cptr = SesolaCertParseDn (InputDn, NULL))
   {
      if (sptr > OutputBuffer && sptr < zptr) *sptr++ = '\n';
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   }
   *sptr = '\0';

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (WATCHALL, WATCH_MOD_SESOLA, "!AZ", OutputBuffer);

   return (sptr - OutputBuffer);
}

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

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