/*****************************************************************************/ /* Sesola.c THE GNU GENERAL PUBLIC LICENSE APPLIES DOUBLY TO ANYTHING TO DO WITH AUTHENTICATION AND AUTHORIZATION! This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License, or any later version. > This package is distributed in the hope that it will be useful, > but WITHOUT ANY WARRANTY; without even the implied warranty of > MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. GOOD ADVICE FROM THE README OF THE APACHE MOD_SSL SOURCE -------------------------------------------------------- You should be very sensible when using cryptography software, because just running an SSL server _DOES NOT_ mean your system is then secure! This is for a number of reasons. The following questions illustrate some of the problems. o SSL itself may not be secure. People think it is, do you? o Does this code implement SSL correctly? o Have the authors of the various components put in back doors? o Does the code take appropriate measures to keep private keys private? To what extent is your cooperation in this process required? o Is your system physically secure? o Is your system appropriately secured from intrusion over the network? o Whom do you trust? Do you understand the trust relationship involved in SSL certificates? Do your system administrators? o Are your keys, and keys you trust, generated careful enough to avoid reverse engineering of the private keys? o How do you obtain certificates, keys, and the like, securely? o Can you trust your users to safeguard their private keys? o Can you trust your browser to safeguard its generated private key? If you can't answer these questions to your personal satisfaction, then you usually have a problem. Even if you can, you may still _NOT_ be secure. Don't blame the authors if it all goes horribly wrong. Use it at your own risk! GENERAL ------- Be aware that export/import and/or use of cryptography software, or even just providing cryptography hooks, is illegal in some parts of the world. When you re-distribute this package or even email patches/suggestions to the author or other people PLEASE PAY CLOSE ATTENTION TO ANY APPLICABLE EXPORT/IMPORT LAWS. The author of this package is not liable for any violations you make here. Named "Sesola" to avoid any confusion and/or conflict with OpenSSL routines. SSL I/O is implemented as a OpenSSL BIO_METHOD, named "Sesola_method". It provides NON-BLOCKING SSL input/output. All routines that are part of this functionality are named "Sesola_..." and are grouped towards the end of this module. To provide some of the facilities available in this module it was necessary in places to directly access some of the internals of structures used by OpenSSL. Let's hope they don't change names too often! CLIENT CERTIFICATE AUTHORIZATION -------------------------------- Thanks to Ralf S.Engelschall (rse@engelschall.com) and Apache 'mod_ssl' package for hints on how to do some of this (all bits broken in the 'technology transfer' are mine :^) Client certificates may be used for authorization, against a realm name "X509". This means that a client (user at a browser for instance) is prompted to supply a X509 certificate as the authorization source. No username/password is required, and the usual such dialogs are not generated. Remote-User - Fingerprint ------------------------- By default WASD uses the FINGERPRINT OF THE CERTIFICATE (without the usual byte-delimiting colons) as the identifying username of the client. For authorization purposes this username may used like any other (i.e. in rule access restriction lists, added to group lists, etc.) The usual certificate fingerprint looks like 10:6C:83:42:89:0A:17:03:AA:A5:17:31:7B:14:5B:F7 Such a 'fingerprint' username comprises 32 hexadecimal digits (without the usual byte-delimiting colons) and looks like 106C8342890A1703AAA517317B145BF7 Such a fingerprint-username is UNIQUE (based on the MD5 algorithm) and a USEFUL REPRESENTATION OF THE CERTIFICATE AND USER of the client, even though lacking slightly in admin-friendly information. Some CGI variables (.e.g the AUTH_X509... and AUTH_USER) provided more accessable information. A CGI script can easily be designed to capture (and perhaps email) this information for use by the administrator in setting up authorization, etc. Remote-User - Subject DN Record ------------------------------- Alternatively, using the directives described immediately below, it is possible to specify that the server should derive the remote-user information from a particular record in the client certificate Subject Distinguished Name. The length of this identifying string is limited by AUTH_MAX_USERNAME_LENGTH specified in AUTH.H and has all white-space converted to underscores (white-space is not allowed for when processing authentication "usernames"). Any of the records identified in the 'SesolaCertDnRec' structure encoded below may be used, but the more obvious candidates for such a use are /O=, /OU=, /CN=, /S=, /Uid= and /EMAIL=. Note that when using this facility it is almost mandatory to put some further access control on the certificate to prevent an unexpected, perhaps even a user-generated and installed certificate's field contents from being used (even considering CA verification is applied by default). The following is an example of such an WASD_CONFIG_AUTH entry. [X509] /VMS/* r+w,param="[ru:/CN=][is:/O=WASD\ HTTPd\ CA\ Cert]" Client Cert Authorization Directives/Conditionals ------------------------------------------------- Conditionals provide extra control on which certificates and their content can and can't be used during client X509 authentication. They contain strings that set authentication parameters, or that are matched to specific elements of certificate and it's negotiation, and only if matched are the corresponding rules applied. The conditionals may contain the '*' and '%' wildcards, and optionally be negated by an '!' at the start of the conditional string. Conditionals are passed to the X509 authenticator via the 'param=""' in an authorization rule, and are delimited by '[' and ']'. Directives that set processing parameters must be one-per-directive. Conditional matching containing multiple, space-separated conditions may be included within one '[...]'. This behaves as a logical OR (i.e. the condition is true if only one is matched). Multiple '[...]' conditionals may be included. These act as a logical AND (i.e. all must have at least one condition matched). The result of an entire conditional may be optionally negated by prefixing the '[' with a '!'. Spaces must *not* be included in match strings. Reserved characters (i.e. spaces, exclamation marks and ']') may be escaped using a preceding backslash. Wildcards (asterisk and percent) cannot be escaped. Once verified the following conditionals control whether that certificate is allowed to be used for authentication. When string matching note that delimit wildcards are often required. Matching is case-insensitive. Note that the 'IS' and 'SU' conditionals each have two variants. As always the '@' is substituted here for '*' due to the issues with C comments. [!]IS:/record=string cert issuer DN record (e.g. [is:/O=VeriSign\ Inc.]) [!]IS:string cert entire issuer DN (e.g. [is:@/O=VeriSign\ Inc./@]) [!]SU:/record=string cert subject DN record (e.g. [su:/CN=Mark\ Daniel]) [!]SU:string cert entire subject DN (e.g. [su:@/CN=Mark%Daniel@]) [!]KS:string minimum keysize (e.g. [ks:128]) [!]CI:string negotiation cipher (e.g. [ci:RC4-MD5]) Note that the "IS:" and "SU:" conditionals each have a "specific-record" and an "entire-field" mode. If the conditional string begins with a slash then it is considered to be a match against a specified record's content within the field. If it begins with a wildcard then it is matched against the entire field's content. The following directives control how the certificate is to be processed. DP:integer set the CA verification depth LT:integer (re)set the authenticated session lifetime in minutes RU:string remote-user from certificate subject record (e.g. "/CN=") TO:integer set the session timeout in minutes ("EXPIRE" to expire) VF:OPTIONAL authorize even if the issuing CA cannot be verified VF:REQUIRED the issuing CA must be verified (default) VF:NONE do not get peer certificate (cancels any existing) Example WASD_CONFIG_AUTH entries: ["Just an example!"=X509] /cgi-bin/show_cert_details r,\ param="[VF:OPTIONAL_NO_CA]" /cgi-bin/show_verisign_details r,\ param="[VF:OPTIONAL_NO_CA] [IS:@/O=VeriSign\ Inc./@]" /some/path/or/other r+w, \ param="[DP:1] [TO:10] [SU:@/Email=Mark.Daniel@wasd.vsm.com.au]" /VMS/* r+w,param="[RU:/CN=]" Remember the WATCH facility provides considerable detail during the processing of all authorization mapping. OPENSSL ------- With the retirement of Eric Young from OpenSource software and the creation of the OpenSSL organisation this module will be based on this group's packages and in particular the the VMS port supported by Richard Levitte. richard@levitte.org http://www.free.lp.se/openssl/ http://www.openssl.org/ The initial development was done using SSLeay v0.8.1 Modified for SSLeay 0.9.0b A great leap forward with OpenSSL v0.9.3 (thanks Richard) Tested against OpenSSL v0.9.4 Tested and modified for OpenSSL v0.9.5 Tested against OpenSSL v0.9.6 Tested against OpenSSL v0.9.6a Tested against OpenSSL v0.9.6b Tested against OpenSSL v0.9.6c Tested against OpenSSL v0.9.6d Tested against OpenSSL v0.9.6e Tested against OpenSSL v0.9.6f Tested against CPQ AXPVMS SSL V1.0-A Tested and modified for OpenSSL v0.9.7-beta Tested and (minor) modification for OpenSSL v0.9.8 A general hard-copy reference (and there aren't many around) that has been interesting and informative is "SSL and TLS, Designing and Building Secure Systems", Eric Rescorla, 2001, Addison-Wesley (ISBN-201-61598-3). Though don't blame the book's author for the code author's shortcomings! SSL COMMAND LINE OPTIONS ------------------------ The /SSL= qualifier allows certain SSL runtime parameters to be set. The default is support of SSLv2/SSLv3, certificate file provided by WASD_SSL_CERT, selected ciphers, and a session cache of 128. /SSL=2 support only SSLv2 (pre-v6.0.0) /SSL=3 support only SSLv3 (pre-v6.0.0) /SSL=23 support both SSLv2 and SSLv3 (pre-v6.0.0) /SSL=SSLv2 support only SSLv2 /SSL=noSSLv2 turn SSLv2 option off /SSL=SSLv3 support only SSLv3 /SSL=noSSLv3 turn SSLv3 option off /SSL=(SSLv2,SSLv3) support both SSLv2 and SSLv3 /SSL=(SSLv2v3) support both SSLv2 and SSLv3 /SSL=TLSv1 support only TLSv1 (etc.) /SSL=noTLSv1 turn TLSv1 option off /SSL=(CACHE=integer) maximum number of records in session cache /SSL=(CAFILE=file) location of default client verify CA certificate file /SSL=(CERT=file) location of default OpenSSL-PEM certificate file /SSL=(CIPHER=list) semi-colon-separated list of supported ciphers /SSL=(ICACHE=SIZE=integer) maximum number of records in instance cache /SSL=(ICACHE=RECORD=integer) size of record in instance cache (bytes) /SSL=(KEY=file) location of default OpenSSL-PEM private key file (if separate from the certificate information) /SSL=noSNI disable Server Name Indication (SNI) /SSL=(+OP_ALL,-OP_ALL,+OP_LEG_REN,-OP_LEG_REN,+OP_NO_SES_RES, -OP_NO_SES_RES,+OP_SING_DH,-OP_SING_DH) options on and off /SSL=(OPTIONS=0xnn) hex SSL_OP_.. options (see [.INCLUDE.OPENSSL]SSL.H) /SSL=(TIMEOUT=integer) set session cache timeout /SSL=(VERIFY=integer) set client certificate CA chin verification depth Multiple parameters may be included by separating with commas. Example: /SSL=(CERT=HT_ROOT:[LOCAL]SITE.PEM,SSLV2,SSLV3,TIMEOUT=10) CGI VARIABLES ------------- CGI variables for scripting and SSI environments can be selectively generated for "https:" requests. See the mapping SETting "SSLCGI=" in MAPURL.C module. The Apache mod_SSL variables are based on the v2.7 documentation by Ralf S. Engelschall (rse@engelschall.com). The Purveyor variables are based on Purveyor documentation. The client certificate authentication AUTH_X509... are always generated when there is X509 authentication. Client Certificate Authorization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ o AUTH_X509_CIPHER ......... name of the cipher in use o AUTH_X509_FINGERPRINT .... (MD5) fingerprint of X509 cert o AUTH_X509_ISSUER ......... CA of client X509 cert o AUTH_X509_KEYSIZE ........ 40, 56, 128, etc. o AUTH_X509_SUBJECT ........ client details of X509 cert Apache mod_SSL -like SSL variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ o HTTPS .................... "true" indicating it's SSL o SSL_PROTOCOL ............. version (e.g. "SSLv2", "SSLv3") o SSL_SESSION_ID ........... hex-encoded SSL session ID o SSL_CIPHER ............... cipher name (e.g. "RC4-MD5") o SSL_CIPHER_EXPORT ........ "true" if export grade o SSL_CIPHER_USEKEYSIZE .... bits cipher used for session o SSL_CIPHER_ALGKEYSIZE .... bits cipher is capable of supporting o SSL_CLIENT_M_VERSION ..... server cert version o SSL_CLIENT_M_SERIAL ...... server cert serial number o SSL_CLIENT_S_DN .......... subject cert distinguished name o SSL_CLIENT_S_DN_x509 ..... subject cert DN components o SSL_CLIENT_I_DN .......... issuer cert distinguished name o SSL_CLIENT_I_DN_x509 ..... issuer cert DN components o SSL_CLIENT_V_START ....... cert validity start date o SSL_CLIENT_V_END ......... cert validity end date o SSL_CLIENT_A_SIG ......... server cert signature algorithm o SSL_CLIENT_A_KEY ......... server cert public key algorithm o SSL_CLIENT_CERT .......... (see note in SesolaCgiVariablesApacheModSsl()) o SSL_SERVER_M_VERSION ..... server cert version o SSL_SERVER_M_SERIAL ...... server cert serial number o SSL_SERVER_S_DN .......... subject cert distinguished name o SSL_SERVER_S_DN_x509 ..... subject cert DN components o SSL_SERVER_I_DN .......... issuer cert distinguished name o SSL_SERVER_I_DN_x509 ..... issuer cert DN components o SSL_SERVER_V_START ....... cert validity start date o SSL_SERVER_V_END ......... cert validity end date o SSL_SERVER_A_SIG ......... server cert signature algorithm o SSL_SERVER_A_KEY ......... server cert public key algorithm o SSL_SERVER_CERT .......... (see note in SesolaCgiVariablesApacheModSsl()) o SSL_TLS_SNI .............. supplied Server Name Indication string o SSL_VERSION_INTERFACE .... WASD server software ID o SSL_VERSION_LIBRARY ...... OpenSSL version "Purveyor"-like security/SSL variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ o SECURITY_STATUS .......... "NONE" or "SSL" o SSL_CIPHER ............... name of the cipher in use o SSL_CIPHER_KEYSIZE ....... 40, 56, 128, etc. o SSL_CLIENT_CA ............ client cert authority o SSL_CLIENT_DN ............ client cert distinguished name o SSL_SERVER_CA ............ server cert authority o SSL_SERVER_DN ............ server cert distinguished name o SSL_VERSION .............. SSL version (e.g. "SSLv2") LOGICAL/SYMBOL NAMES -------------------- The following logical names can be used to provide runtime information to the SSL functionality. Configuration options provided by /SSL= and [service] or /SERVICE= parameters override anything provided via these names. WASD_SSL_CAFILE location of default client verify CA certificate file WASD_SSL_CERT location of default OpenSSL-PEM certificate file WASD_SSL_CIPHER comma-separated list of default supported ciphers WASD_SSL_KEY location of default OpenSSL-PEM private key file WASD_SSL_PARAMS the equivalent functionality of the /SSL= qualifier VERSION HISTORY --------------- 14-SEP-2013 MGD rework SSL options parameters (and reporting) /SSL= +OP_ALL, -OP_ALL, +OP_LEG_REN, -OP_LEG_REN, +OP_NO_SES_RES, -OP_NO_SES_RES, +OP_SING_DH, -OP_SING_DH establish a default cipher list based on best practise SSL_get_certificate() bug workaround in SesolaReport() TLS1 Server Name Indication (SNI) extension (RFC 4366) /SSL=NOSNI parameter can disable establish and use a separate list of SSL services to expedite SSL service activities such as SNI default set options SSL_OP_SINGLE_DH_USE and SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION if session cache size is zero then SSL_CTX_sess_set_cache_mode() SSL_SESS_CACHE_OFF SesolaReport() add "Fingerprint:" to client cert data bugfix; SesolaInitService() (or refinement) SSL_CTX_set_session_id_context() against each service bugfix; SesolaReport() session hh:mm:ss minute 02-DEC-2012 MGD bugfix; SesolaInit() translate WASD_SSL_CIPHER logical name report SSL_CTX_set_cipher_list() error 01-JUL-2012 MGD bugfix; SesolaParseCertDn() return NULL if record not found 02-SEP-2011 MGD SesolaInit() default is SSLv2 off and SSLv3/TLSv1 on 13-FEB-2008 MGD SesolaReport() rework "Current Session" 'Session:' 28-MAR-2006 MGD added SSL=ICACHE=SIZE= and SSL=ICACHE=RECORD= to allow configuration of instance SSL session cache move SesolaCacheInit() out after AuthConfigInit() so that the record size can be increased if X509 realm is used 08-JUL-2005 MGD OpenSSL v0.9.8 changed macro name EVP_F_EVP_DECRYPTFINAL 25-MAY-2005 MGD SesolaWatchBioCallback() make >= 0 complete 19-APR-2005 MGD SesolaInitService() no longer needs to clone 21-AUG-2004 MGD significant refinements to SSL processing 22-JUL-2004 MGD persistent connections over SSL 14-JAN-2003 MGD DN record /email and /emailAddress 15-OCT-2002 MGD use internal ceritifcate and configuration in demo mode 28-AUG-2002 MGD add SHA1 fingerprint (everybody else has it ;^) 11-AUG-2002 MGD refine SesolaReport() for obtaining service ciphers (OpenSSLv0.9.6f/0.9.7-beta break it), built and tested against CPQ AXPVMS SSL V1.0-A, internal PEM cert/key as fallback; mainly for VMS (Open)SSL 03-JUL-2002 MGD refine SesolaReport() so it obtains the service certificate indirectly removing the need for SSL_LOCL.H (OpenSSL 0.9.7) 17-APR-2002 MGD bugfix; SesolaCertVerifyCallback() 02-JAN-2002 MGD rework SSL network functions into SESOLANET.C, add HTTP-SSL proxy (gateway) service initialization 27-OCT-2001 MGD break up a burgeoning SSL module into more source files 29-SEP-2001 MGD instance support 04-AUG-2001 MGD support module WATCHing 01-JUL-2001 MGD refine private key password request functionality 20-MAY-2001 MGD add [RU:/xx=] to allow a site to specify a certificate subject DN record as the authenticated remote-user, change [IS:/name=string] and [SU:/name=string] to allow individual issuer or subject DN records to be matched, private key password optionally can now be supplied via /DO=SSL=KEY=PASSWORD (see SesolaPrivateKeyPasswd()) 11-APR-2001 MGD remove http: check from SesolaAccept(), bugfix; SesolaFree() BioPtr 02-APR-2001 MGD refine SesolaClientCertVerifyCallback() 10-DEC-2000 MGD client certificate and authorization support, Richard Levitte in a recent email to vms-web-daemon@KJSL.COM points out using SSL_CTX_use_certificate_chain_file() is more versatile than SSL_CTX_use_certificate_file(), bugfix; SesolaCgiVariablesApacheModSsl() 22-NOV-2000 MGD where a service-specific certificate is supplied and no service-specific key assume the key is in the specific cert 17-OCT-2000 MGD modify SSL initialization so that "fallback" conditions (same port on same IP address) are more easily identified 26-SEP-2000 MGD built and verified against OpenSSL 0.9.6 change 'boolean' to 'BOOL' to accomodate new OpenSSL typedef 26-AUG-2000 MGD SesolaWatchPeek() 17-JUN-2000 MGD modifications for SERVICE.C requirements 06-MAY-2000 MGD added Apache mod_SSL style CGI variables 05-MAR-2000 MGD tested against OpenSSL v0.9.5 (SSL_OP_NON_EXPORT_FIRST generates error message, removed), use FaolToNet(), et.al. 23-DEC-1999 MGD modify NEEDSTRUCT reported by DECC v6.2 19-OCT-1999 MGD SesolaInitServiceList() service errors not fatal at startup 11-SEP-1999 MGD tested against OpenSSL v0.9.4, SSLeay no longer supported 18-AUG-1999 MGD add certificate CA and DN to SSL report, bugfix; certificate/key pairs when initializing service 12-JUN-1999 MGD refine SSL connection handling 26-MAY-1999 MGD allow some SSL options to be set if desired, set SSL_OP_NON_EXPORT_FIRST (for some certificate processing) 18-APR-1999 MGD improve error reporting during certificate loading 03-APR-1999 MGD support OpenSSL 0.9.3 (with initial, backward support for SSLeay 0.8 & 0.9), add WATCH callbacks 31-MAR-1999 MGD bugfix; report request from non-SSL port 20-JAN-1999 MGD report format refinement 07-NOV-1998 MGD WATCH facility 24-OCT-1998 MGD per-service context (implies per-service certificate) 01-JUN-1998 MGD builds OK with SSLeay 0.9.0b 25-JAN-1998 MGD initial development for v5.0, SSLeay v0.8.1 */ /*****************************************************************************/ #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" /* OpenSSL v0.9.8 changed macro name (provide some backward compatibility) */ #ifndef EVP_F_EVP_DECRYPTFINAL_EX # define EVP_F_EVP_DECRYPTFINAL_EX EVP_F_EVP_DECRYPTFINAL #endif /* not present with some earlier versions of HP SSL */ #ifndef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION # define SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION 0x00040000L #endif #define WASD_MODULE "SESOLA" /***************************************/ #ifdef SESOLA /* secure sockets layer */ /***************************************/ /* if there is no certificate/key file it will fallback to using this */ char SesolaInternalPEM [] = "\ -----BEGIN CERTIFICATE-----\n\ MIIEmzCCBASgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCQVUx\n\ CzAJBgNVBAgTAlNBMREwDwYDVQQHEwhBZGVsYWlkZTEbMBkGA1UEChMSV0FTRCBI\n\ VFRQZCBDQSBDZXJ0MSYwJAYDVQQLEx1JbnRlcm5hbCAoZmFsbGJhY2spIENlcnQg\n\ T25seTEkMCIGA1UEAxMbV0FTRCBWTVMgSHlwZXJ0ZXh0IFNlcnZpY2VzMSowKAYJ\n\ KoZIhvcNAQkBFhtNYXJrLkRhbmllbEB3YXNkLnZzbS5jb20uYXUwHhcNMDIwODEx\n\ MjMwNzExWhcNMjIwODEyMjMwNzExWjCByDELMAkGA1UEBhMCQVUxCzAJBgNVBAgT\n\ AlNBMREwDwYDVQQHEwhBZGVsYWlkZTEfMB0GA1UEChMWV0FTRCBIVFRQZCBTZXJ2\n\ ZXIgQ2VydDEmMCQGA1UECxMdSW50ZXJuYWwgKGZhbGxiYWNrKSBDZXJ0IE9ubHkx\n\ JDAiBgNVBAMTG1dBU0QgVk1TIEh5cGVydGV4dCBTZXJ2aWNlczEqMCgGCSqGSIb3\n\ DQEJARYbTWFyay5EYW5pZWxAd2FzZC52c20uY29tLmF1MFwwDQYJKoZIhvcNAQEB\n\ BQADSwAwSAJBAL7HIJ1JcMBxpYDOZPG37CGbWgoMdGQuxaAhWVFaqJEmfuFbvKTd\n\ 3s7IIoHUdCw7COHdDxDot3vyrFaCvy4zWp8CAwEAAaOCAdkwggHVMAkGA1UdEwQC\n\ MAAwCwYDVR0PBAQDAgXgMCoGCWCGSAGG+EIBBAQdFhtodHRwOi8vd3d3LnZzbS5j\n\ b20uYXU6ODAwMC8wLAYJYIZIAYb4QgENBB8WHVdBU0QgaXMgR05VLWxpY2Vuc2Vk\n\ IGZyZWV3YXJlMB0GA1UdDgQWBBQuyHy9/HGm5wU3cN17GhHzDzNeEzCB8QYDVR0j\n\ BIHpMIHmgBQsHpyosA2GNithw9EruoGDq1mKsaGByqSBxzCBxDELMAkGA1UEBhMC\n\ QVUxCzAJBgNVBAgTAlNBMREwDwYDVQQHEwhBZGVsYWlkZTEbMBkGA1UEChMSV0FT\n\ RCBIVFRQZCBDQSBDZXJ0MSYwJAYDVQQLEx1JbnRlcm5hbCAoZmFsbGJhY2spIENl\n\ cnQgT25seTEkMCIGA1UEAxMbV0FTRCBWTVMgSHlwZXJ0ZXh0IFNlcnZpY2VzMSow\n\ KAYJKoZIhvcNAQkBFhtNYXJrLkRhbmllbEB3YXNkLnZzbS5jb20uYXWCAQAwJgYD\n\ VR0RBB8wHYEbTWFyay5EYW5pZWxAd2FzZC52c20uY29tLmF1MCYGA1UdEgQfMB2B\n\ G01hcmsuRGFuaWVsQHdhc2QudnNtLmNvbS5hdTANBgkqhkiG9w0BAQQFAAOBgQCg\n\ FSJz6xgiIFWWLW40uLtHi2D0GpSGLqyrRzzqRc/tPXcFW6iibsdT+N1uJvClJrKa\n\ SogDuO3iUkwhX4pDTScxkSemmYit6E9+nnTiDifF8zRbvjU4omCpUSCLOMKQBqFV\n\ 8c/VF2SK7GHBfsLujYlhFpq6VhMBF/50Fkybemco3A==\n\ -----END CERTIFICATE-----\n\ -----BEGIN RSA PRIVATE KEY-----\n\ MIIBOQIBAAJBAL7HIJ1JcMBxpYDOZPG37CGbWgoMdGQuxaAhWVFaqJEmfuFbvKTd\n\ 3s7IIoHUdCw7COHdDxDot3vyrFaCvy4zWp8CAwEAAQJAfN36o9ggu2TnDZKJkYhv\n\ PmPfH/qc58GRSkjpnAz5jd5/6UgBZkXmjplOy0k3dANgxyQqErRJvD2RJWMF0qHf\n\ sQIhAPn3kWdRnP1eyl7/DhK8xl7aB62DgbSuJ+FNQ+IboOlXAiEAw2HbOAa9uLbi\n\ /RfW0XXiY2O32AZuuZvBDpCkV94So/kCIHTz7xUfK0ukuRy/Sw9bQZkJfAQj/mDS\n\ Bxiz9OnqsVvbAiA7K//AUApVTs4f6IBen10YzLJ48jnGbK1jQ9sB4XezwQIgBDT9\n\ TrsfJQlPy6Sk5UBxRV3h9OTqw8azr2eg6KBmS40=\n\ -----END RSA PRIVATE KEY-----\n\ "; /******************/ /* global storage */ /******************/ BOOL ProtocolHttpsAvailable = true, ProtocolHttpsConfigured, SesolaPrivateKeyPasswdRequested, SesolaSNI = true, SesolaVerifyCAConfigured; int SesolaDefaultVerifyDepth = SESOLA_DEFAULT_VERIFY_DEPTH, SesolaGblSecStructSize = sizeof(struct SesolaGblSecStruct), SesolaSessionCacheSize = SESOLA_DEFAULT_CACHE_SIZE, SesolaSessionCacheTimeout = SESOLA_DEFAULT_CACHE_TIMEOUT, SesolaOptions, SesolaPrivateKeyPasswdAttempt, SesolaServiceCount, SesolaSSLversion; char *SesolaDefaultCertPtr, *SesolaDefaultCipherListPtr, *SesolaDefaultKeyPtr, *SesolaDefaultCaFilePtr; char SesolaParams [256]; SesolaSSLversionString [64]; char ErrorSslPort [] = "This is an SSL ("https:") port.", HttpdSesola [] = " SSL"; SERVICE_STRUCT *SesolaListHead, *SesolaListTail; /*** With an older version of HP SSL product (prior to SNI support) the following link error may be reported. This may safely be ignored and the server code will detect this unresolved symbol and disable use of SNI and this associated function call. Quick and dirty for sure but cryptography code such as OpenSSL really should be kept as up-to-date as possible! %LINK-W-NUDFSYMS, 1 undefined symbol: %LINK-I-UDFSYM, SSL_GET_SERVERNAME %LINK-W-USEUNDEF, undefined symbol SSL_GET_SERVERNAME referenced in psect SESOLAGETSERVERNAMEFN offset %X00000000 in module SESOLA file WASD_ROOT:[SRC.HTTPD.OBJ_AXP]SESOLA_SSL.OBJ ***/ int (*SesolaGetServerNameFn)(SSL*, int) = SSL_get_servername; 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 } }; BIO_METHOD *SesolaBioMemPtr; /********************/ /* external storage */ /********************/ extern BOOL CliDemo, ServiceWwwImplied; extern int ExitStatus, HttpdTickSecond, InstanceNodeConfig, InstanceNodeCurrent, OpcomMessages, SesolaCacheRecordMax, SesolaCacheRecordSize; extern int ToLowerCase[], ToUpperCase[]; extern unsigned long SysPrvMask[]; extern char ErrorSanityCheck[], ServerHostPort[], SoftwareID[]; extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern HTTPD_GBLSEC *HttpdGblSecPtr; extern HTTPD_PROCESS HttpdProcess; extern MSG_STRUCT Msgs; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* */ SesolaInit () { char *cptr, *sptr; char buf [256]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaInit()"); FaoToStdout ("%HTTPD-I-SSL, !AZ\n", SSLeay_version(SSLEAY_VERSION)); /* command-line parameters, if none then use WASD_SSL_PARAMS */ cptr = SesolaParams; if (!*cptr) if (!(cptr = v10orPrev10(CONFIG_SSL_PARAMS,0))) cptr = ""; if (strsame (cptr, "/NOSSL", -1)) { FaoToStdout ("%HTTPD-W-SSL, disabled via /NOSSL\n"); return; } /* see [.APPS]S_SERVER.C */ SesolaOptions = SSL_OP_ALL; SesolaOptions |= SSL_OP_SINGLE_DH_USE; SesolaOptions |= SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; /* as of v10.1 SSLv2 is disabled by default */ SesolaOptions |= SSL_OP_NO_SSLv2; while (*cptr) { while (*cptr && (*cptr == '(' || *cptr == ',' || *cptr == ')')) cptr++; sptr = cptr; while (*cptr && *cptr != ',' && *cptr != ')') cptr++; if (*cptr) *cptr++ = '\0'; if (!*sptr) break; /* the 'digits' are for backward compatibility with pre-v6.0.0 */ if (strsame (sptr, "2", -1)) { SesolaSSLversion |= SESOLA_SSLV2; SesolaOptions &= ~SSL_OP_NO_SSLv2; } else if (strsame (sptr, "3", -1)) { SesolaSSLversion |= SESOLA_SSLV3; SesolaOptions &= ~SSL_OP_NO_SSLv3; } else if (strsame (sptr, "23", -1)) { SesolaSSLversion |= SESOLA_SSLV2 | SESOLA_SSLV3; SesolaOptions &= ~SSL_OP_NO_SSLv2; SesolaOptions &= ~SSL_OP_NO_SSLv3; } else if (strsame (sptr, "SSLv2", -1)) { SesolaSSLversion |= SESOLA_SSLV2; SesolaOptions &= ~SSL_OP_NO_SSLv2; } else if (strsame (sptr, "noSSLv2", -1)) { SesolaSSLversion &= ~SESOLA_SSLV3; SesolaOptions &= ~SSL_OP_NO_SSLv2; } else if (strsame (sptr, "SSLv3", -1)) { SesolaSSLversion |= SESOLA_SSLV3; SesolaOptions &= ~SSL_OP_NO_SSLv3; } else if (strsame (sptr, "noSSLv3", -1)) { SesolaSSLversion &= ~SESOLA_SSLV3; SesolaOptions |= SSL_OP_NO_SSLv3; } else if (strsame (sptr, "SSLv2v3", -1)) { SesolaSSLversion |= SESOLA_SSLV2 | SESOLA_SSLV3; SesolaOptions &= ~SSL_OP_NO_SSLv2; SesolaOptions &= ~SSL_OP_NO_SSLv3; } else if (strsame (sptr, "SNI", -1)) SesolaSNI = true; else if (strsame (sptr, "NOSNI", -1)) SesolaSNI = false; else if (strsame (sptr, "TLSv1", -1)) { SesolaSSLversion |= SESOLA_TLSV1; SesolaOptions &= ~SSL_OP_NO_TLSv1; } else if (strsame (sptr, "noTLSv1", -1)) { SesolaSSLversion &= ~SESOLA_TLSV1; SesolaOptions |= SSL_OP_NO_TLSv1; } else if (strsame (sptr, "CAFILE=", 7)) SesolaDefaultCaFilePtr = sptr + 7; else if (strsame (sptr, "CERT=", 5)) SesolaDefaultCertPtr = sptr + 5; else if (strsame (sptr, "CIPHER=", 7)) SesolaDefaultCipherListPtr = sptr + 7; else if (strsame (sptr, "CACHE=", 6)) SesolaSessionCacheSize = atoi(sptr+6); else if (strsame (sptr, "ICACHE=SIZE=", 12)) SesolaCacheRecordMax = atoi(sptr+12); else if (strsame (sptr, "ICACHE=RECORD=", 14)) SesolaCacheRecordSize = atoi(sptr+14); else if (strsame (sptr, "KEY=", 4)) SesolaDefaultKeyPtr = sptr + 4; else if (strsame (sptr, "OPTIONS=", 8)) { sptr += 8; if (isdigit(*sptr)) { /* enter first and then apply others */ SesolaOptions = strtol (sptr, NULL, 16); } } else if (strsame (sptr, "+OP_ALL", -1)) SesolaOptions |= SSL_OP_ALL; else if (strsame (sptr, "-OP_ALL", -1)) SesolaOptions &= ~SSL_OP_ALL; else if (strsame (sptr, "+OP_LEG_REN", -1)) SesolaOptions |= SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; else if (strsame (sptr, "-OP_LEG_REN", -1)) SesolaOptions &= ~SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; else if (strsame (sptr, "+OP_NO_SES_RES", -1)) SesolaOptions |= SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; else if (strsame (sptr, "-OP_NO_SES_RES", -1)) SesolaOptions &= ~SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; else if (strsame (sptr, "+OP_SING_DH", -1)) SesolaOptions |= SSL_OP_SINGLE_DH_USE; else if (strsame (sptr, "-OP_SING_DH", -1)) SesolaOptions &= ~SSL_OP_SINGLE_DH_USE; else if (strsame (sptr, "TIMEOUT=", 8)) SesolaSessionCacheTimeout = atoi(sptr+8); else if (strsame (sptr, "VERIFY=", 7)) SesolaDefaultVerifyDepth = atoi(sptr+7); else { FaoToStdout ("%HTTPD-E-SSL, unknown SSL parameter\n \\!AZ\\\n", sptr); exit (STS$K_ERROR | STS$M_INHIB_MSG); } } /* default is to support both SSLv3 and TLSv1 */ if (!(SesolaSSLversion & SESOLA_SSLV2) && !(SesolaSSLversion & SESOLA_SSLV3) && !(SesolaSSLversion & SESOLA_TLSV1)) { SesolaSSLversion = SESOLA_SSLV3 | SESOLA_TLSV1; SesolaOptions |= SSL_OP_NO_SSLv2; SesolaOptions &= ~SSL_OP_NO_SSLv3; SesolaOptions &= ~SSL_OP_NO_TLSv1; } sptr = cptr = SesolaSSLversionString; if (SesolaSSLversion & SESOLA_SSLV2) sptr += sprintf (sptr, "SSLv2"); if (SesolaSSLversion & SESOLA_SSLV3) sptr += sprintf (sptr, "%sSSLv3", *cptr?"/":""); if (SesolaSSLversion & SESOLA_TLSV1) sptr += sprintf (sptr, "%sTLSv1", *cptr?"/":""); FaoToStdout ("-SSL-I-PROTOCOL, !AZ\n", SesolaSSLversionString); FaoToStdout ("-SSL-I-OPTIONS, 0x!8XL\n", SesolaOptions); if (!SesolaGetServerNameFn) { FaoToStdout ("%HTTPD-W-SSL, SSL_get_servername() unresolved\n"); SesolaSNI = false; } FaoToStdout ("-SSL-!AZ-SNI, Server Name Indication !AZ\n", SesolaSNI ? "I" : "W", SesolaSNI ? "enabled" : "disabled"); /* lots and lots of non-determinate stuff */ RAND_seed (HttpdGblSecPtr, sizeof(HTTPD_GBLSEC)); SSL_load_error_strings (); SSL_library_init (); if (!SesolaDefaultCertPtr) SesolaDefaultCertPtr = v10orPrev10(CONFIG_SSL_CERT,0); if (!SesolaDefaultKeyPtr) SesolaDefaultKeyPtr = v10orPrev10(CONFIG_SSL_KEY,0); if (!SesolaDefaultCaFilePtr) SesolaDefaultCaFilePtr = v10orPrev10(CONFIG_SSL_CAFILE,0); if (!SesolaDefaultCipherListPtr) if (SesolaDefaultCipherListPtr = v10orPrev10(CONFIG_SSL_CIPHER,0)) SesolaDefaultCipherListPtr = SysTrnLnm (SesolaDefaultCipherListPtr); SesolaBioMemPtr = BIO_new (BIO_s_mem()); if (!SesolaBioMemPtr) { FaoToStdout ("-SSL-E-BIO, BIO_new(BIO_s_mem()) failed\n"); exit (STS$K_ERROR | STS$M_INHIB_MSG); } } /*****************************************************************************/ /* Called during service configuration to initialize an SSL service. SSL services are either created from scratch or if sharing an IP address and port with another SSL service are "cloned". */ BOOL SesolaInitService (SERVICE_STRUCT *svptr) { static unsigned long SessionIdContext [4]; BOOL CipherListSupplied, KeySupplied, VerifyCAConfigured; int value, VerifyMode; unsigned long ErrorNumber; char *cptr; RSA *RsaPtr; SESOLA_CONTEXT *scptr, *ssptr; SSL_CTX *SslCtx; X509 *CertPtr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaInitService() !&X", svptr->SSLserverPtr); FaoToStdout ("%HTTPD-I-SSL, !AZ\n", svptr->ServerHostPort); if (!SesolaListHead) SesolaListHead = SesolaListTail = svptr; else { SesolaListTail->SesolaNextPtr = svptr; SesolaListTail = svptr; } svptr->SesolaNextPtr = NULL; SesolaServiceCount++; if (!(scptr = (SESOLA_CONTEXT*)svptr->SSLserverPtr)) return (false); if (CliDemo) { scptr->Version = SesolaSSLversion; scptr->CaFilePtr = scptr->CertFilePtr = scptr->CipherListPtr = scptr->KeyFilePtr = ""; } else { if (scptr->CertFile[0]) scptr->CertFilePtr = scptr->CertFile; else if (SesolaDefaultCertPtr) scptr->CertFilePtr = SesolaDefaultCertPtr; else scptr->CertFilePtr = ""; if (scptr->KeyFile[0]) { KeySupplied = true; scptr->KeyFilePtr = scptr->KeyFile; } else { if (scptr->CertFile[0]) { /* service has a specific certificate, assume the key's in that */ scptr->KeyFilePtr = scptr->CertFile; } else if (SesolaDefaultKeyPtr) { /* use the separately specified key */ scptr->KeyFilePtr = SesolaDefaultKeyPtr; } else { /* if no default key then assume it's in the certificate file */ KeySupplied = false; scptr->KeyFilePtr = scptr->CertFilePtr; } } if (scptr->CipherList[0]) { CipherListSupplied = true; scptr->CipherListPtr = scptr->CipherList; } else { if (SesolaDefaultCipherListPtr) { CipherListSupplied = true; scptr->CipherListPtr = SesolaDefaultCipherListPtr; } else { CipherListSupplied = false; scptr->CipherListPtr = ""; } } if (scptr->CaFile[0]) { VerifyCAConfigured = true; scptr->CaFilePtr = scptr->CaFile; } else { if (SesolaDefaultCaFilePtr) { VerifyCAConfigured = true; scptr->CaFilePtr = SesolaDefaultCaFilePtr; } else { VerifyCAConfigured = false; scptr->CaFilePtr = ""; } } if (scptr->CaFilePtr[0]) /* set the global boolean as well (never reset it!) */ VerifyCAConfigured = SesolaVerifyCAConfigured = true; else VerifyCAConfigured = false; if (scptr->CertFilePtr[0]) FaoToStdout ("-SSL-I-CERT, !AZ\n", scptr->CertFilePtr); else FaoToStdout ("-SSL-W-CERT, using internal certificate\n"); if (KeySupplied) FaoToStdout ("-SSL-I-KEY, !AZ\n", scptr->KeyFilePtr); if (CipherListSupplied) FaoToStdout ("-SSL-I-CIPHER, !AZ\n", scptr->CipherListPtr); if (VerifyCAConfigured) FaoToStdout ("-SSL-I-CAFILE, !AZ\n", scptr->CaFilePtr); if (strsame (scptr->VersionString, "SSLV2V3", -1)) scptr->Version = SESOLA_SSLV2 | SESOLA_SSLV3; else if (strsame (scptr->VersionString, "SSLV3", -1)) scptr->Version = SESOLA_SSLV3; else if (strsame (scptr->VersionString, "SSLV2", -1)) scptr->Version = SESOLA_SSLV2; else if (strsame (scptr->VersionString, "TLSV1", -1)) scptr->Version = SESOLA_TLSV1; else if (scptr->VersionString[0]) { FaoToStdout ("%HTTPD-W-SSL, unknown protocol\n\ \\!AZ\\\n", scptr->VersionString); return (false); } else scptr->Version = SesolaSSLversion; } if ((scptr->Version & SESOLA_SSLV2) && (scptr->Version & SESOLA_SSLV3)) { SslCtx = SSL_CTX_new (SSLv23_method()); scptr->VersionStringPtr = "SSLV2V3"; } else if (scptr->Version & SESOLA_SSLV2) { SslCtx = SSL_CTX_new (SSLv2_method()); scptr->VersionStringPtr = "SSLV2"; } else if (scptr->Version & SESOLA_SSLV3) { SslCtx = SSL_CTX_new (SSLv3_method()); scptr->VersionStringPtr = "SSLV3"; } else if (scptr->Version & SESOLA_TLSV1) { SslCtx = SSL_CTX_new (TLSv1_method()); scptr->VersionStringPtr = "TLSV1"; } if (!SslCtx) { SesolaPrintOpenSslErrorList (); ErrorExitVmsStatus (0, "SSL_CTX_new()", FI_LI); } if (scptr->CertFilePtr[0]) value = SSL_CTX_use_certificate_chain_file (SslCtx, scptr->CertFilePtr); else { /* read the fallback certificate */ char Buffer [256]; BIO_puts (SesolaBioMemPtr, SesolaInternalPEM); CertPtr = PEM_read_bio_X509 (SesolaBioMemPtr, NULL, NULL, NULL); if (CertPtr) { value = SSL_CTX_use_certificate (SslCtx, CertPtr); X509_free (CertPtr); } else value = 0; /* empty anything not read */ while (BIO_gets (SesolaBioMemPtr, Buffer, sizeof(Buffer)) > 0); } if (!value) { SesolaPrintOpenSslErrorList (); return (false); } SesolaPrivateKeyPasswdAttempt = 1; for (;;) { /* set private key password callback */ SSL_CTX_set_default_passwd_cb (SslCtx, &SesolaPrivateKeyPasswd); SSL_CTX_set_default_passwd_cb_userdata (SslCtx, scptr); SesolaPrivateKeyPasswdRequested = false; if (scptr->KeyFilePtr[0]) value = SSL_CTX_use_RSAPrivateKey_file (SslCtx, scptr->KeyFilePtr, SSL_FILETYPE_PEM); else { /* read the fallback private key */ char Buffer [256]; BIO_puts (SesolaBioMemPtr, SesolaInternalPEM); RsaPtr = PEM_read_bio_RSAPrivateKey (SesolaBioMemPtr, NULL, &SesolaPrivateKeyPasswd, scptr); if (RsaPtr) { value = SSL_CTX_use_RSAPrivateKey (SslCtx, RsaPtr); RSA_free (RsaPtr); } else value = 0; /* empty anything not read */ while (BIO_gets (SesolaBioMemPtr, Buffer, sizeof(Buffer)) > 0); } /* reset private key password callback */ SSL_CTX_set_default_passwd_cb (SslCtx, NULL); SSL_CTX_set_default_passwd_cb_userdata (SslCtx, NULL); if (value) { if (SesolaPrivateKeyPasswdRequested && OpcomMessages) FaoToOpcom ("%HTTPD-I-PKPASSWD, password accepted"); break; } ErrorNumber = ERR_peek_error (); SesolaPrintOpenSslErrorList (); if (SesolaPrivateKeyPasswdRequested && ERR_GET_LIB(ErrorNumber) == ERR_LIB_EVP && ERR_GET_FUNC(ErrorNumber) == EVP_F_EVP_DECRYPTFINAL_EX && ERR_GET_REASON(ErrorNumber) == EVP_R_BAD_DECRYPT) { if (++SesolaPrivateKeyPasswdAttempt <= SESOLA_PKPASSWD_ATTEMPTS) { /* give the controlling ControlLocalCommand() a chance to notice */ sleep (2); continue; } } if (OpcomMessages) FaoToOpcom ("-SSL-E-PKPASSWD, password not accepted"); return (false); } value = SSL_CTX_check_private_key (SslCtx); if (!value) { SesolaPrintOpenSslErrorList (); return (false); } if (!*(cptr = scptr->CipherListPtr)) cptr = SESOLA_DEFAULT_CIPHER_LIST; if (!SSL_CTX_set_cipher_list (SslCtx, cptr)) { SesolaPrintOpenSslErrorList (); return (false); } if (SesolaSessionCacheSize) { if (SesolaSessionCacheSize < SESOLA_DEFAULT_CACHE_RECORD_MAX) SesolaSessionCacheSize = SESOLA_DEFAULT_CACHE_RECORD_MAX; SSL_CTX_set_session_cache_mode (SslCtx, SSL_SESS_CACHE_SERVER); SSL_CTX_sess_set_cache_size (SslCtx, SesolaSessionCacheSize); SSL_CTX_set_timeout (SslCtx, SesolaSessionCacheTimeout*60); /* inter-process session cache (if multiple per-node "instances") */ if (InstanceNodeConfig > 1) { SSL_CTX_sess_set_new_cb (SslCtx, &SesolaCacheAddRecord); SSL_CTX_sess_set_get_cb (SslCtx, &SesolaCacheFindRecord); SSL_CTX_sess_set_remove_cb (SslCtx, &SesolaCacheRemoveRecord); } } else SSL_CTX_set_session_cache_mode (SslCtx, SSL_SESS_CACHE_OFF); if (scptr->VerifyPeer) VerifyMode = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; else VerifyMode = SSL_VERIFY_NONE; SSL_CTX_set_verify (SslCtx, VerifyMode, &SesolaCertVerifyCallback); SSL_CTX_set_verify_depth (SslCtx, SesolaDefaultVerifyDepth); if (scptr->CaFilePtr[0]) { value = SSL_CTX_load_verify_locations (SslCtx, scptr->CaFilePtr, NULL); if (value) value = SSL_CTX_set_default_verify_paths (SslCtx); if (!value) { SesolaPrintOpenSslErrorList (); FaoToStdout ("-SSL-W-VERIFY, peer verification not enabled\n"); return (false); } } /* same algorithm as SesolaSessionId() but on a per-service basis */ sys$gettim(&SessionIdContext[0]); SessionIdContext[3] += SessionIdContext[0]; SessionIdContext[2] -= SessionIdContext[1]; SessionIdContext[1] ^= SessionIdContext[2]; value = SSL_CTX_set_session_id_context (SslCtx, &SessionIdContext, sizeof(SessionIdContext)); if (!value) { SesolaPrintOpenSslErrorList (); FaoToStdout ("-SSL-W-SESSID, set CTX session ID failed\n"); return (false); } SSL_CTX_set_options (SslCtx, SesolaOptions); if (SesolaSNI) { if (scptr->Version & SESOLA_TLSV1) { /* enable Server Name Indication */ if (!SSL_CTX_set_tlsext_servername_callback (SslCtx, SesolaSNICallback)) { SesolaSNI = false; FaoToStdout ("%HTTPD-E-SSL, SNI unsupported by SSL library\n"); } else SSL_CTX_set_tlsext_servername_arg (SslCtx, 0); } } ((SESOLA_CONTEXT*)svptr->SSLserverPtr)->SslCtx = (void*)SslCtx; return (true); } /*****************************************************************************/ /* Configure the basics of the SSL client service (i.e. can originate outgoing SSL sessions with SSL servers). Used by the HTTP-SSL proxy (gateway) service. */ BOOL SesolaInitClientService (SERVICE_STRUCT *svptr) { BOOL CipherListSupplied, KeySupplied; int value; unsigned long ErrorNumber; char *cptr; RSA *RsaPtr; SSL_CTX *SslCtx; SESOLA_CONTEXT *scptr; X509 *CertPtr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaInitClientService()"); if (!svptr->SSLclientPtr) { /* no client-specific context, use any server context */ if (svptr->SSLserverPtr) { svptr->SSLclientPtr = VmGet (sizeof(SESOLA_CONTEXT)); memcpy (svptr->SSLclientPtr, svptr->SSLserverPtr, sizeof(SESOLA_CONTEXT)); } } if (!(scptr = (SESOLA_CONTEXT*)svptr->SSLclientPtr)) return (false); if (scptr->CertFile[0]) scptr->CertFilePtr = scptr->CertFile; else if (SesolaDefaultCertPtr) scptr->CertFilePtr = SesolaDefaultCertPtr; else scptr->CertFilePtr = ""; if (scptr->KeyFile[0]) { KeySupplied = true; scptr->KeyFilePtr = scptr->KeyFile; } else { if (scptr->CertFile[0]) { /* service has a specific certificate, assume the key's in that */ scptr->KeyFilePtr = scptr->CertFile; } else if (SesolaDefaultKeyPtr) { /* use the separately specified key */ scptr->KeyFilePtr = SesolaDefaultKeyPtr; } else { /* if no default key then assume it's in the certificate file */ KeySupplied = false; scptr->KeyFilePtr = scptr->CertFilePtr; } } if (scptr->CipherList[0]) { CipherListSupplied = true; scptr->CipherListPtr = scptr->CipherList; } else { if (SesolaDefaultCipherListPtr) { CipherListSupplied = true; scptr->CipherListPtr = ""; } else { CipherListSupplied = false; scptr->CipherListPtr = ""; } } if (scptr->CaFile[0]) scptr->CaFilePtr = scptr->CaFile; else if (SesolaDefaultCaFilePtr) scptr->CaFilePtr = SesolaDefaultCaFilePtr; else scptr->CaFilePtr = ""; if (CipherListSupplied) FaoToStdout ("-SSL-I-CIPHER, (a/client) !AZ\n", scptr->CipherListPtr); if (scptr->VerifyCA) FaoToStdout ("-SSL-I-CAFILE, (a/client) !AZ\n", scptr->CaFilePtr); if (strsame (scptr->VersionString, "SSLV2V3", -1)) scptr->Version = SESOLA_SSLV2 | SESOLA_SSLV3; else if (strsame (scptr->VersionString, "SSLV3", -1)) scptr->Version = SESOLA_SSLV3; else if (strsame (scptr->VersionString, "SSLV2", -1)) scptr->Version = SESOLA_SSLV2; else if (strsame (scptr->VersionString, "TLSV1", -1)) scptr->Version = SESOLA_TLSV1; else if (scptr->VersionString[0]) { FaoToStdout ("-SSL-W-PROTOCOL, (a/client) unknown protocol\n\ \\!AZ\\\n", scptr->VersionString); return (false); } else scptr->Version = SesolaSSLversion; if ((scptr->Version & SESOLA_SSLV2) && (scptr->Version & SESOLA_SSLV3)) { SslCtx = SSL_CTX_new (SSLv23_method()); scptr->VersionStringPtr = "SSLV2V3"; } else if (scptr->Version & SESOLA_SSLV2) { SslCtx = SSL_CTX_new (SSLv2_method()); scptr->VersionStringPtr = "SSLV2"; } else if (scptr->Version & SESOLA_SSLV3) { SslCtx = SSL_CTX_new (SSLv3_method()); scptr->VersionStringPtr = "SSLV3"; } else if (scptr->Version & SESOLA_TLSV1) { SslCtx = SSL_CTX_new (TLSv1_method()); scptr->VersionStringPtr = "TLSV1"; } FaoToStdout ("-SSL-I-PROTOCOL, (a/client) version !AZ\n", scptr->VersionStringPtr); if (!SslCtx) { SesolaPrintOpenSslErrorList (); ErrorExitVmsStatus (0, "SSL_CTX_new()", FI_LI); } if (scptr->CertFilePtr[0]) value = SSL_CTX_use_certificate_file (SslCtx, scptr->CertFilePtr, SSL_FILETYPE_PEM); else { /* read the fallback certificate */ char Buffer [256]; BIO_puts (SesolaBioMemPtr, SesolaInternalPEM); CertPtr = PEM_read_bio_X509 (SesolaBioMemPtr, NULL, NULL, NULL); if (CertPtr) { value = SSL_CTX_use_certificate (SslCtx, CertPtr); X509_free (CertPtr); } else value = 0; /* empty anything not read */ while (BIO_gets (SesolaBioMemPtr, Buffer, sizeof(Buffer)) > 0); } if (!value) { SesolaPrintOpenSslErrorList (); return (false); } SesolaPrivateKeyPasswdAttempt = 1; for (;;) { /* set private key password callback */ SSL_CTX_set_default_passwd_cb (SslCtx, &SesolaPrivateKeyPasswd); SSL_CTX_set_default_passwd_cb_userdata (SslCtx, scptr); SesolaPrivateKeyPasswdRequested = false; if (scptr->KeyFilePtr[0]) value = SSL_CTX_use_RSAPrivateKey_file (SslCtx, scptr->KeyFilePtr, SSL_FILETYPE_PEM); else { /* read the fallback private key */ char Buffer [256]; BIO_puts (SesolaBioMemPtr, SesolaInternalPEM); RsaPtr = PEM_read_bio_RSAPrivateKey (SesolaBioMemPtr, NULL, &SesolaPrivateKeyPasswd, scptr); if (RsaPtr) { value = SSL_CTX_use_RSAPrivateKey (SslCtx, RsaPtr); RSA_free (RsaPtr); } else value = 0; /* empty anything not read */ while (BIO_gets (SesolaBioMemPtr, Buffer, sizeof(Buffer)) > 0); } /* reset private key password callback */ SSL_CTX_set_default_passwd_cb (SslCtx, NULL); SSL_CTX_set_default_passwd_cb_userdata (SslCtx, NULL); if (value) { if (SesolaPrivateKeyPasswdRequested && OpcomMessages) FaoToOpcom ("%HTTPD-I-PKPASSWD, password accepted"); break; } ErrorNumber = ERR_peek_error (); SesolaPrintOpenSslErrorList (); if (SesolaPrivateKeyPasswdRequested && ERR_GET_LIB(ErrorNumber) == ERR_LIB_EVP && ERR_GET_FUNC(ErrorNumber) == EVP_F_EVP_DECRYPTFINAL_EX && ERR_GET_REASON(ErrorNumber) == EVP_R_BAD_DECRYPT) { if (++SesolaPrivateKeyPasswdAttempt <= SESOLA_PKPASSWD_ATTEMPTS) { /* give the controlling ControlLocalCommand() a chance to notice */ sleep (2); continue; } } if (OpcomMessages) FaoToOpcom ("-SSL-E-PKPASSWD, password not accepted"); return (false); } value = SSL_CTX_check_private_key (SslCtx); if (!value) { SesolaPrintOpenSslErrorList (); return (false); } if (!*(cptr = scptr->CipherListPtr)) cptr = SESOLA_DEFAULT_CIPHER_LIST; if (!SSL_CTX_set_cipher_list (SslCtx, cptr)) { SesolaPrintOpenSslErrorList (); return (false); } if (SesolaSessionCacheSize) { if (SesolaSessionCacheSize < SESOLA_DEFAULT_CACHE_RECORD_MAX) SesolaSessionCacheSize = SESOLA_DEFAULT_CACHE_RECORD_MAX; SSL_CTX_set_session_cache_mode (SslCtx, SSL_SESS_CACHE_SERVER); SSL_CTX_sess_set_cache_size (SslCtx, SesolaSessionCacheSize); SSL_CTX_set_timeout (SslCtx, SesolaSessionCacheTimeout*60); /* inter-process session cache (if multiple per-node "instances") */ if (InstanceNodeConfig > 1) { SSL_CTX_sess_set_new_cb (SslCtx, &SesolaCacheAddRecord); SSL_CTX_sess_set_get_cb (SslCtx, &SesolaCacheFindRecord); SSL_CTX_sess_set_remove_cb (SslCtx, &SesolaCacheRemoveRecord); } } else SSL_CTX_set_session_cache_mode (SslCtx, SSL_SESS_CACHE_OFF); if (scptr->VerifyCA) SSL_CTX_set_verify (SslCtx, SSL_VERIFY_CLIENT_ONCE, &SesolaCertVerifyCallback); else SSL_CTX_set_verify (SslCtx, SSL_VERIFY_NONE, &SesolaCertVerifyCallback); SSL_CTX_set_verify_depth (SslCtx, SesolaDefaultVerifyDepth); if (scptr->CaFilePtr[0]) { value = SSL_CTX_load_verify_locations (SslCtx, scptr->CaFilePtr, NULL); if (value) value = SSL_CTX_set_default_verify_paths (SslCtx); if (!value) { SesolaPrintOpenSslErrorList (); FaoToStdout ("-SSL-W-SSL, (a/client) CA verification not enabled\n"); return (false); } } SSL_CTX_set_options (SslCtx, SesolaOptions); ((SESOLA_CONTEXT*)svptr->SSLclientPtr)->SslCtx = (void*)SslCtx; return (true); } /*****************************************************************************/ /* If the private key in WASD_SSL_CERT, WASD_SSL_KEY, or the equivalent, does not contain an embedded password (which is by far less "secure" against key stealing) the private key callback set above results in this function being called. Output a message to the console, to the monitor status message buffer, and optionally if enabled to OPCOM, requesting that a password be manually supplied. The password is input at the command line using the control /DO=SSL=KEY=PASSWORD directive, which prompts for the password and then writes it into the global section shared memory control directive buffer. This function continues to poll that buffer at one second intervals looking for the indicator of the password. When found it copies it into the 'PasswdBuffer' and returns the length. If not supplied it eventually times-out resulting in the startup being cancelled (which should then restart). Note that POLLING MUST BE USED because user-mode ASTs are disabled during service (network) initialization, rendering lock delivery and the like possibilities of notification unusable (at this mode) during this period. */ int SesolaPrivateKeyPasswd ( char *PasswdBuffer, int SizeOfPasswdBuffer, int UnknownParam, void *UserData ) { int cnt, PasswdLength; char *cptr, *sptr, *zptr; SERVICE_STRUCT *svptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaPrivateKeyPasswd() !UL !UL", SizeOfPasswdBuffer, UserData); /* indicates a private key password was required for this service */ SesolaPrivateKeyPasswdRequested = true; svptr = (SERVICE_STRUCT*)UserData; /* ensure we start with a nice empty buffer */ memset (HttpdGblSecPtr->PkPasswd, 0, sizeof(HttpdGblSecPtr->PkPasswd)); PasswdBuffer[0] = '\0'; /* wait for the password to magically appear!! (crude but effective) */ for (cnt = SESOLA_PKPASSWD_REQUEST_SECONDS; cnt; cnt--) { if (!(cnt % 60)) { FaoToStdout ("%HTTPD-I-PKPASSWD, \ !20%D, request !UL/!UL for private key password, !AZ//!AZ\n", 0, SesolaPrivateKeyPasswdAttempt, SESOLA_PKPASSWD_ATTEMPTS, svptr->RequestSchemeNamePtr, svptr->ServerHostPort); FaoToBuffer (HttpdGblSecPtr->StatusMessage, sizeof(HttpdGblSecPtr->StatusMessage), NULL, "%HTTPD-I-PKPASSWD, !20%D, \ !AZ request !UL/!UL for private key password, !AZ//!AZ", 0, InstanceNodeCurrent > 1 ? HttpdProcess.PrcNam : "", SesolaPrivateKeyPasswdAttempt, SESOLA_PKPASSWD_ATTEMPTS, svptr->RequestSchemeNamePtr, svptr->ServerHostPort); /* if any OPCOM at all is enabled then output this message */ if (OpcomMessages) FaoToOpcom ("%HTTPD-I-PKPASSWD, \ request !UL/!UL for private key password, !AZ//!AZ", SesolaPrivateKeyPasswdAttempt, SESOLA_PKPASSWD_ATTEMPTS, svptr->RequestSchemeNamePtr, svptr->ServerHostPort); } sleep (1); /* if a password was supplied whilst sleeping */ if (HttpdGblSecPtr->PkPasswd[0]) break; } HttpdGblSecPtr->StatusMessage[0] = '\0'; if (!cnt) { /* timed-out waiting for a response */ ControlMessage ("timeout waiting for private key password"); sleep (1); exit (SS$_CANCEL); } zptr = (sptr = PasswdBuffer) + SizeOfPasswdBuffer; for (cptr = HttpdGblSecPtr->PkPasswd; *cptr && sptr < zptr; *sptr++ = *cptr++); /* overflow just cancels the password */ if (sptr > zptr) sptr = PasswdBuffer; *sptr = '\0'; PasswdLength = sptr - PasswdBuffer; for (sptr = PasswdBuffer; *sptr && isspace(*sptr); sptr++); if (!*sptr && sptr > PasswdBuffer) { /* password comprising all spaces is used to abort the startup */ ControlMessage ("empty private key password, aborting startup"); /* just exit the process */ ExitStatus = SS$_ABORT; HttpdExit (&ExitStatus); /* cancel any startup messages provided for the monitor */ HttpdGblSecPtr->StatusMessage[0] = '\0'; sys$delprc (0, 0); } /* remove the actual password from the control buffer */ memset (HttpdGblSecPtr->PkPasswd, 0, sizeof(HttpdGblSecPtr->PkPasswd)); ControlMessage ("password received"); return (PasswdLength); } /*****************************************************************************/ /* Session ID is seeded with two longwords of 64bit VMS time, then some jiggery-pokery. This should be non-predictable, non-sequential enough. */ SesolaSessionId (SSL *SslPtr) { static unsigned long SesolaSessionIdContext [4]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaSessionId()"); sys$gettim(&SesolaSessionIdContext[0]); SesolaSessionIdContext[3] += SesolaSessionIdContext[0]; SesolaSessionIdContext[2] -= SesolaSessionIdContext[1]; SesolaSessionIdContext[1] ^= SesolaSessionIdContext[2]; SSL_set_session_id_context (SslPtr, &SesolaSessionIdContext, sizeof(SesolaSessionIdContext)); if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "!16&H", &SesolaSessionIdContext); } /*****************************************************************************/ /* Implement TLS1 Server Name Indication (SNI) callback (from RFC 4366). Searches the SSL service list for a matching name (certificates are issued against a host name, not against a name:port combination). The first name matched results in the SSL context being set/changed. Then continue and set the virtual service if available (significantly more efficient than doing it using ServiceFindVirtual() later). */ int SesolaSNICallback ( SSL *SslPtr, int *UnusedArg1, void *UnusedArg2 ) { BOOL wwwName; int snlen, ServerPort; char *cptr, *sptr, *snptr, *zptr; REQUEST_STRUCT *rqptr; SERVICE_STRUCT *svptr; SESOLA_STRUCT *sesolaptr; /*********/ /* begin */ /*********/ rqptr = (REQUEST_STRUCT*)SSL_get_ex_data (SslPtr, 0); snptr = SesolaGetServerNameFn (SslPtr, TLSEXT_NAMETYPE_host_name); if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA))) WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaSNICallback() !&Z", snptr); if (!snptr) return (SSL_TLSEXT_ERR_OK); if (ServiceWwwImplied) wwwName = SAME4(snptr,'www.'); else wwwName = false; sesolaptr = (SESOLA_STRUCT*)rqptr->rqNet.SesolaPtr; /* note the original service port number */ ServerPort = rqptr->ServicePtr->ServerPort; /* used to emulate the Apache SSL_TLS_SNI variable */ zptr = (sptr = sesolaptr->SNIServerName) + sizeof(sesolaptr->SNIServerName)-1; for (cptr = snptr; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; snlen = cptr - snptr; for (svptr = SesolaListHead; svptr; svptr = svptr->SesolaNextPtr) { if (!((SESOLA_CONTEXT*)svptr->SSLserverPtr)->SslCtx) continue; if (!wwwName || snlen != svptr->ServerHostNameLength + 4) if (snlen != svptr->ServerHostNameLength) continue; /* match the SNI name to the service name */ sptr = snptr; if (wwwName && !MATCH4 (sptr, "www.")) sptr += 4; cptr = svptr->ServerHostName; if (snlen >= 8) { if (!MATCH8 (sptr, cptr)) continue; sptr += 8; cptr += 8; } while (*cptr && *sptr && *cptr == *sptr) { cptr++; sptr++; } if (*sptr || *cptr) continue; /* name match! */ if (!sesolaptr->SNIctxSet) { /* set the SSL context */ SSL_set_SSL_CTX (SslPtr, ((SESOLA_CONTEXT*)svptr->SSLserverPtr)->SslCtx); sesolaptr->SNIctxSet = true; if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_SESOLA)) WatchThis (rqptr, FI_LI, WATCH_SESOLA, "SNI HIT !AZ (!UL)", snptr, svptr->ServerPort); } /* match to the original port */ if (ServerPort != svptr->ServerPort) continue; /* change to this service */ rqptr->ServicePtr = svptr; sesolaptr->SNIserviceSet = true; if (WATCHING(rqptr)) { if (WATCH_CATEGORY(WATCH_SESOLA)) WatchThis (rqptr, FI_LI, WATCH_SESOLA, "SNI VIRTUAL !AZ", svptr->ServerHostPort); else if (WATCH_CATEGORY(WATCH_CONNECT)) WatchThis (rqptr, FI_LI, WATCH_CONNECT, "VIRTUAL (SNI) !AZ", svptr->ServerHostPort); } break; } if (!sesolaptr->SNIctxSet) if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_SESOLA)) WatchThis (rqptr, FI_LI, WATCH_SESOLA, "SNI FAIL !AZ", snptr); return (SSL_TLSEXT_ERR_OK); } /*****************************************************************************/ /* Due to compilation complications in SERVICE.C have this stub return the value for ServiceFindVirtual() to use. */ BOOL SesolaSNIserviceSet (SESOLA_STRUCT *sesolaptr) { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaSNIserviceSet() !&B", sesolaptr->SNIserviceSet); return (sesolaptr->SNIserviceSet); } /*****************************************************************************/ /* 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. */ int SesolaCertVerifyCallback ( int OkValue, /* void* for convenience in compiling non-SSL version */ void *StoreCtxPtr ) { int ErrorNumber, ErrorDepth, VerifyDepth, VerifyMode, WatchThisType; 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 (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaCertVerifyCallback() !UL", OkValue); CtxPtr = (X509_STORE_CTX*)StoreCtxPtr; SslPtr = (SSL*)X509_STORE_CTX_get_app_data (CtxPtr); rqptr = (REQUEST_STRUCT*)SSL_get_ex_data (SslPtr, 0); CertPtr = X509_STORE_CTX_get_current_cert (CtxPtr); ErrorNumber = X509_STORE_CTX_get_error (CtxPtr); ErrorDepth = X509_STORE_CTX_get_error_depth (CtxPtr); VerifyMode = SSL_get_verify_mode (SslPtr); if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "VERIFY !UL", VerifyMode); if (!VerifyMode) return (1); else if (VerifyMode & SSL_VERIFY_PEER) sesolaptr = (SESOLA_STRUCT*)rqptr->rqNet.SesolaPtr; else if (VerifyMode & SSL_VERIFY_CLIENT_ONCE) sesolaptr = (SESOLA_STRUCT*)rqptr->ProxyTaskPtr->SesolaPtr; if (!sesolaptr) { ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI); return (0); } VerifyDepth = sesolaptr->CertVerifyDepth; sesolaptr->CertVerifyCallbackCount++; WatchThisType = 0; if (WATCH_CAT && WATCHING(rqptr)) { if (WATCH_CATEGORY(WATCH_SESOLA)) WatchThisType = WATCH_SESOLA; else if (WATCH_CATEGORY(WATCH_AUTH)) WatchThisType = WATCH_AUTH; } if (WATCH_CAT && WatchThisType) { WatchThis (rqptr, FI_LI, WatchThisType, "X509 VERIFY callback !UL !AZ !AZ", sesolaptr->CertVerifyCallbackCount, !(VerifyMode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) ? "(no fail on error)" : "(fail on error)", OkValue ? "OK" : "NOT-OK"); 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_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); } if (OkValue) sesolaptr->CertVerifyFailed = false; else { sesolaptr->CertVerifyFailed = true; if (WATCH_CAT && WatchThisType) WatchThis (rqptr, FI_LI, WatchThisType, "X509 VERIFY error, !UL \"!AZ\"", ErrorNumber, X509_verify_cert_error_string(ErrorNumber)); if (ErrorDepth > VerifyDepth) if (WATCH_CAT && WatchThisType) WatchThis (rqptr, FI_LI, WatchThisType, "X509 VERIFY certificate chain too long (!UL>!UL)", ErrorDepth, VerifyDepth); } /* always OK if verifying peer certificate but not for authorization */ if ((VerifyMode & SSL_VERIFY_PEER) && !(VerifyMode & SSL_VERIFY_FAIL_IF_NO_PEER_CERT)) return (1); return (OkValue); } /*****************************************************************************/ /* Print out the OpenSSL error list. For reporting errors associated with certificate loading. Based on [.CRYPTO.ERR]ERR_PRN.C. The error number (based on [.CRYPTO.ERR]ERR.H ERR_PACK macro) is a bit-vector comprising 31..24 (8 bits) the library code, 23..12 (12 bits) the function code 11..0 (12 bits) the reason code. A fatal error is the decimal value 32 (bit 6) and is used in various bit-wise operations. */ SesolaPrintOpenSslErrorList () { int LineNumber, Flags; unsigned long ErrorNumber; char *FileNamePtr, *DataStringPtr; char Buffer [256]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaPrintOpenSslErrorList()"); while (ErrorNumber = ERR_get_error_line_data (&FileNamePtr, &LineNumber, &DataStringPtr, &Flags)) { FaoToStdout ( "-SSL-W-ERROR, \"!AZ\" (!AZ !SL)!&?\"\r\r!AZ!&?\"\r\r\n", ERR_error_string (ErrorNumber, Buffer), FileNamePtr, LineNumber, ((Flags & ERR_TXT_STRING)), ((Flags & ERR_TXT_STRING) ? DataStringPtr : ""), ((Flags & ERR_TXT_STRING))); } } /*****************************************************************************/ /* Provides OpenSSL status information at various states of SSL processing via the WATCH facility. */ #if WATCH_CAT SesolaWatchInfoCallback ( SSL *SslPtr, int WhereExactly, int value ) { int item; char *cptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaWatchInfoCallback() !UL !SL", WhereExactly, value); rqptr = (REQUEST_STRUCT*)SSL_get_ex_data (SslPtr, 0); switch (WhereExactly & ~SSL_ST_MASK) { case SSL_ST_CONNECT : cptr = "SSL_CONNECT "; break; case SSL_ST_ACCEPT : cptr = "SSL_ACCEPT "; break; case SSL_ST_INIT : cptr = "SSL_INIT "; break; case SSL_ST_BEFORE : cptr = "SSL_BEFORE "; break; case SSL_ST_OK : cptr = "SSL_OK "; break; case SSL_ST_RENEGOTIATE : cptr = "SSL_RENEGOTIATE "; break; default : cptr = ""; } switch (WhereExactly & SSL_ST_MASK) { case SSL_CB_ALERT : WatchThis (rqptr, FI_LI, WATCH_SESOLA, "!AZalert !AZ !AZ:!AZ", cptr, (WhereExactly & SSL_CB_READ) ? "read" : "write", SSL_alert_type_string_long(value), SSL_alert_desc_string_long(value)); break; case SSL_CB_LOOP : WatchThis (rqptr, FI_LI, WATCH_SESOLA, "!AZ!AZ", cptr, SSL_state_string_long(SslPtr)); break; case SSL_CB_EXIT : if (value == 0) WatchThis (rqptr, FI_LI, WATCH_SESOLA, "!AZalert failed in !AZ", cptr, SSL_state_string_long(SslPtr)); else if (value < 0) WatchThis (rqptr, FI_LI, WATCH_SESOLA, "!AZerror/blocking in !AZ", cptr, SSL_state_string_long(SslPtr)); break; case SSL_CB_READ : WatchThis (rqptr, FI_LI, WATCH_SESOLA, "!AZread !AZ", cptr, SSL_state_string_long(SslPtr)); break; case SSL_CB_WRITE : WatchThis (rqptr, FI_LI, WATCH_SESOLA, "!AZwrite !AZ", cptr, SSL_state_string_long(SslPtr)); break; case SSL_CB_HANDSHAKE_START : WatchThis (rqptr, FI_LI, WATCH_SESOLA, "!AZstart handshake", cptr); break; case SSL_CB_HANDSHAKE_DONE : WatchThis (rqptr, FI_LI, WATCH_SESOLA, "!AZend handshake", cptr); break; default : WatchThis (rqptr, FI_LI, WATCH_SESOLA, "!&X !AZ", WhereExactly, SSL_state_string_long(SslPtr)); } } #endif /* WATCH_CAT */ /*****************************************************************************/ /* Provides OpenSSL Input/Output information each time an SSL read or write occurs via the WATCH facility. These macros can be found in [.CRYPTO.BIO]BIO.H */ int SesolaWatchBioCallback ( BIO *bioptr, int cmd, char *argp, int argi, long argl, long retval ) { #if WATCH_CAT REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = (REQUEST_STRUCT*)BIO_get_callback_arg (bioptr); if (WATCH_MODULE(WATCH_MOD_SESOLA)) { WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaWatchBioCallback() !&X !&X !SL !SL !SL", cmd, argp, argi, argl, retval); switch (cmd & 0xf) { case BIO_CB_READ : WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "!&?after\rbefore\r READ !UL !SL", cmd & BIO_CB_RETURN, argi, retval); break; case BIO_CB_WRITE : WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "!&?after\rbefore\r WRITE !UL !SL", cmd & BIO_CB_RETURN, argi, retval); break; case BIO_CB_FREE : WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "!&?after\rbefore\r FREE !SL !SL", cmd & BIO_CB_RETURN, argi, retval); break; case BIO_CB_CTRL : WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "!&?after\rbefore\r CTRL !SL !SL", cmd & BIO_CB_RETURN, argi, retval); break; default : WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "!&?after\rbefore\r !&X !UL !SL", cmd & BIO_CB_RETURN, cmd, argi, retval); } return (retval); } if (!(cmd & BIO_CB_RETURN)) return (retval); switch (cmd & 0xf) { case BIO_CB_READ : WatchThis (rqptr, FI_LI, WATCH_SESOLA, "READ !UL/!SL!&? (complete)\r (outstanding)\r", argi, retval, retval >= 0); break; case BIO_CB_WRITE : WatchThis (rqptr, FI_LI, WATCH_SESOLA, "WRITE !UL/!SL!&? (complete)\r (outstanding)\r", argi, retval, retval >= 0); break; case BIO_CB_FREE : WatchThis (rqptr, FI_LI, WATCH_SESOLA, "FREE !SL !SL", argi, retval); break; case BIO_CB_CTRL : WatchThis (rqptr, FI_LI, WATCH_SESOLA, "CTRL !SL !SL", argi, retval); break; default : WatchThis (rqptr, FI_LI, WATCH_SESOLA, "!&X !UL !SL", cmd, argi, retval); } #endif /* WATCH_CAT */ return (retval); } /*****************************************************************************/ /* Provide WATCH information after final SSL session establishment show version and cipher in use. */ SesolaWatchSession (SESOLA_STRUCT *sesolaptr) { #if WATCH_CAT int AlgKeySize, UseKeySize; char *CipherNamePtr, *VersionNamePtr; REQUEST_STRUCT *rqptr; SSL *SslPtr; SSL_CIPHER *CipherPtr; /*********/ /* begin */ /*********/ if (!(rqptr = sesolaptr->RequestPtr)) rqptr = sesolaptr->ProxyTaskPtr->RequestPtr; if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA))) WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaWatchSession()"); SslPtr = sesolaptr->SslPtr; CipherPtr = SSL_get_current_cipher (SslPtr); if (!(VersionNamePtr = (char*)SSL_get_version (SslPtr))) VersionNamePtr = "(null)"; if (!(CipherPtr = SSL_get_current_cipher (SslPtr))) { CipherNamePtr = "(null)"; AlgKeySize = UseKeySize = 0; } else { CipherNamePtr = (char*)SSL_CIPHER_get_name (CipherPtr); UseKeySize = SSL_CIPHER_get_bits(CipherPtr, &AlgKeySize); } WatchThis (rqptr, FI_LI, WATCH_SESOLA, "SESSION cached:!AZ protocol:!AZ cipher:!AZ keysize:!UL/!UL", SslPtr->hit ? "yes" : "no", VersionNamePtr, CipherNamePtr, UseKeySize, AlgKeySize); #endif /* WATCH_CAT */ } /*****************************************************************************/ /* Provide some OpenSSL-internal error information. (Based on code from [.CRYPTO.ERR]ERR_PRN.C) */ SesolaWatchErrors (SESOLA_STRUCT *sesolaptr) { #if WATCH_CAT int cnt, flags, LineNumber; unsigned long ErrorNumber; char ErrorString [256], ModuleName [256]; char *cptr, *cptr1, *cptr2, *cptr3, *sptr, *FileNamePtr, *DataPtr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (!(rqptr = sesolaptr->RequestPtr)) if (sesolaptr->ProxyTaskPtr) rqptr = sesolaptr->ProxyTaskPtr->RequestPtr; if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA))) WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaWatchErrors()"); while (cnt = ERR_get_error_line_data (&FileNamePtr, &LineNumber, &DataPtr, &flags)) { ERR_error_string_n (cnt, ErrorString, sizeof(ErrorString)); for (cptr1 = ErrorString; *cptr1 && *cptr1 != ':'; cptr1++); if (*cptr1) cptr1++; while (*cptr1 && *cptr1 != ':') cptr1++; if (*cptr1) cptr1++; for (cptr2 = cptr1; *cptr2 && *cptr2 != ':'; cptr2++); if (*cptr2) *cptr2++ = '\0'; for (cptr3 = cptr2; *cptr3 && *cptr3 != ':'; cptr3++); if (*cptr3) *cptr3++ = '\0'; for (cptr = FileNamePtr; *cptr; cptr++); while (cptr > FileNamePtr && *cptr != ']') cptr--; if (*cptr == ']') cptr++; sptr = ModuleName; while (*cptr && *cptr != ';') *sptr++ = *cptr++; *sptr = '\0'; WatchThis (rqptr, FI_LI, WATCH_SESOLA, "!AZ !AZ !AZ !AZ!&? \r\r(!AZ:!UL)", cptr1, cptr2, cptr3, flags & ERR_TXT_STRING ? DataPtr : "", flags & ERR_TXT_STRING, ModuleName, LineNumber); } #endif /* WATCH_CAT */ } /*****************************************************************************/ /* Peek at selected SesolaStruct data. The 'rqptr' is the request doing the peeking, the 'rqeptr' is the request being peeked at. If 'rqptr' is NULL then the information is written to SYS$OUTPUT. */ SesolaWatchPeek ( REQUEST_STRUCT *rqptr, REQUEST_STRUCT *rqeptr ) { static char SesolaFao [] = "!33<->SslPtr!> protocol:!AZ cipher:!AZ keysize:!UL/!UL\n\ !33<->ClientCertPtr!> !&@\n\ !33<->CertVerifyFailed!> !&B\n\ !33<->ClientAstFunction!> !&A\n\ !33<->NetChannel!> !UL (!AZ)\n\ !33<->ReadInProgress!> !&B\n\ !33<->ReadCompleted!> !&B\n\ !33<->ReadAstFunction!> !&A\n\ !33<->SslStateFunction!> !&A\n\ !33<->ReadDataPtr!> !&X\n\ !33<->ReadDataSize!> !UL\n\ !33<->ReadIOsb!> !&S !UL\n\ !33<->WriteInProgress!> !&B\n\ !33<->WriteCompleted!> !&B\n\ !33<->WriteAstFunction!> !&A\n\ !33<->WriteDataPtr!> !&X\n\ !33<->WriteDataLength!> !UL\n\ !33<->WriteIOsb!> !&S !UL\n\ !33<->HTTPduringHandshake!> !&B\n\ !33<->RenegotiateState!> !UL\n\ !33<->SslAcceptCount!> !UL\n\ !33<->SslConnectCount!> !UL\n\ !33<->SslShutdownCount!> !UL\n\ \n"; int status, AlgKeySize, UseKeySize; unsigned short Length; unsigned long *vecptr; unsigned long FaoVector [32]; char *CipherNamePtr, *VersionNamePtr; char Buffer [2048], CertFingerprintMD5 [64], CertFingerprintSHA1 [64], NetDevName [64], IssuerString [256], NotAfterString [32], SubjectString [256]; SESOLA_STRUCT *sesolaptr; SSL *SslPtr; SSL_CIPHER *CipherPtr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaWatchPeek()\n"); if (!rqeptr->rqNet.SesolaPtr) return; sesolaptr = (SESOLA_STRUCT*)rqeptr->rqNet.SesolaPtr; NetGetBgDevice (sesolaptr->NetChannel, NetDevName, sizeof(NetDevName)); SslPtr = sesolaptr->SslPtr; if (!(VersionNamePtr = (char*)SSL_get_version (SslPtr))) VersionNamePtr = "(null)"; if (!(CipherPtr = SSL_get_current_cipher (SslPtr))) { CipherNamePtr = "(null)"; AlgKeySize = UseKeySize = 0; } else { CipherNamePtr = (char*)SSL_CIPHER_get_name (CipherPtr); UseKeySize = SSL_CIPHER_get_bits(CipherPtr, &AlgKeySize); } if (sesolaptr->ClientCertPtr) { X509_NAME_oneline (X509_get_issuer_name(sesolaptr->ClientCertPtr), IssuerString, sizeof(SubjectString)); X509_NAME_oneline (X509_get_subject_name(sesolaptr->ClientCertPtr), SubjectString, sizeof(SubjectString)); SesolaCertFingerprint (sesolaptr->ClientCertPtr, &EVP_sha1, CertFingerprintSHA1, sizeof(CertFingerprintSHA1)); SesolaCertFingerprint (sesolaptr->ClientCertPtr, &EVP_md5, CertFingerprintMD5, sizeof(CertFingerprintMD5)); ASN1_UTCTIME_print (SesolaBioMemPtr, X509_get_notAfter(sesolaptr->ClientCertPtr)); BIO_gets (SesolaBioMemPtr, NotAfterString, sizeof(NotAfterString)); } vecptr = FaoVector; *vecptr++ = VersionNamePtr; *vecptr++ = CipherNamePtr; *vecptr++ = UseKeySize; *vecptr++ = AlgKeySize; if (sesolaptr->ClientCertPtr) { *vecptr++ = "issuer=!AZ\n\ !33 subject=!AZ\n\ !33 notAfter=!AZ\n\ !33 SHA1=!AZ\n\ !33 MD5=!AZ\n"; *vecptr++ = IssuerString; *vecptr++ = SubjectString; *vecptr++ = NotAfterString; *vecptr++ = CertFingerprintSHA1; *vecptr++ = CertFingerprintMD5; } else *vecptr++ = "0x00000000"; *vecptr++ = sesolaptr->CertVerifyFailed; *vecptr++ = sesolaptr->ClientCertAstFunction; *vecptr++ = sesolaptr->NetChannel; *vecptr++ = NetDevName+1; *vecptr++ = sesolaptr->ReadInProgress; *vecptr++ = sesolaptr->ReadCompleted; *vecptr++ = sesolaptr->ReadAstFunction; *vecptr++ = sesolaptr->SslStateFunction; *vecptr++ = sesolaptr->ReadDataPtr; *vecptr++ = sesolaptr->ReadDataSize; *vecptr++ = sesolaptr->ReadIOsb.Status; *vecptr++ = sesolaptr->ReadIOsb.Count; *vecptr++ = sesolaptr->WriteInProgress; *vecptr++ = sesolaptr->WriteCompleted; *vecptr++ = sesolaptr->WriteAstFunction; *vecptr++ = sesolaptr->WriteDataPtr; *vecptr++ = sesolaptr->WriteDataLength; *vecptr++ = sesolaptr->WriteIOsb.Status; *vecptr++ = sesolaptr->WriteIOsb.Count; *vecptr++ = sesolaptr->HTTPduringHandshake; *vecptr++ = sesolaptr->RenegotiateState; *vecptr++ = sesolaptr->SslAcceptCount; *vecptr++ = sesolaptr->SslConnectCount; *vecptr++ = sesolaptr->SslShutdownCount; status = FaolToBuffer (Buffer, sizeof(Buffer), &Length, SesolaFao, &FaoVector); if (VMSnok (status) || status == SS$_BUFFEROVF) ErrorNoticed (rqptr, status, NULL, FI_LI); if (rqptr) NetWrite (rqptr, 0, Buffer, Length); else fputs (Buffer, stdout); } /*****************************************************************************/ /* Brief report on this or a virtual service SSL context statistics and any current session information. */ SesolaReport ( REQUEST_STRUCT *rqptr, REQUEST_AST NextTaskFunction, char *VirtualHostPort ) { static char VirtualFao [] = "
\n\

\n\ \n\ \n\
Virtual SSL Server
\n\ \n\ \n\ \n\
\n\

\n\

\n\ \n\ \n\
!AZ
\n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \ \n\ \ \n\ \n"; static char EndServerInfoFao [] = "
Version:!AZ
Build:!AZ
Protocol:!AZ
Server Certificate:!AZ
\n\ \n\ \ \n\ \ \n\ \n\ \n\ \n\
Issuer: !AZ
Subject: !AZ
Expires: !AZ
SHA1: !AZ
MD5: !AZ
\n\
Private Key:!AZ
CA Verify File:!&@
CA Verify Depth:!&@
Cipher List:!AZ!AZ
Supported Ciphers:\n\ \n\ \n"; static char CiphersFao [] = "\ \ \ \n"; static char EndCiphersFao [] = "
VersionName
!UL.  !AZ  !AZ  
\n\
Service Accepts:\ \n\ \n\ \n\
Total:  !UL
 Completed:  !UL
\n\
Session Cache:\ \n\ \n\ \n\ \n\ \n\ \n\ \n\
Size:  !UL
Current:  !UL
Full:  !UL
Hits:  !UL
Misses:  !UL
 Timeouts:  !UL
\n\
\n\
\n"; static char CurrentSessionFao [] = "

\n\ \n\ \n"; static char EndPageFao [] = "
Current Session
\n\ \n\ \ \n"; static char ClientCertFao [] = "\n"; static char ClientCertNoneFao [] = "\ \n"; static char CurrentSessionEndFao [] = "
Session:!AZ (!2ZL:!2ZL:!2ZL ago), \ timeout (!2ZL:!2ZL:!2ZL) at !AZ
Client Certificate:\n\ \n\ \ \n\ \ \n\ \n\ \n\ \n\ \n\ \n\
Issuer: !AZ
Subject: !AZ
Begins: !AZ
Expires: !AZ
SHA1: !AZ
MD5: !AZ
Fingerprint: !AZ
\n\
Client Certificate:!AZ href=\"!AZ\">view a client certificate!AZ
\n\
\n\ \n\ \n"; int status, AlgKeySize, Count, SessionHits, SessionTimeout, SessionTimeCSec, SessionTimeoutCSec, Total, UseKeySize; char *cptr, *sptr, *zptr; unsigned long BinaryTime [2], /* WARNING: really does need to be fairly large!! */ FaoVector [256], ResultTime [2]; unsigned long *vecptr; char AuthFingerprint [64], CertString [256], CertCaString [256], CertDnString [256], CertFingerprintMD5 [64], CertFingerprintSHA1 [64], CertNotAfterString [32], CertNotBeforeString [32], TimeString [32], TimeoutString [32]; SESOLA_STRUCT *sesolaptr; SERVICE_STRUCT *svptr, *tsvptr; struct tm *tmptr; SESOLA_CONTEXT *scptr; SSL *SslPtr; SSL_CTX *SslCtx; SSL_CIPHER *CipherPtr; SSL_SESSION *SessionPtr; STACK_OF(SSL_CIPHER) *CipherStackPtr; X509 *ServerCertPtr; /*********/ /* begin */ /*********/ /* as this is also called as an AST (!!) avoid WATCHing parameters */ if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA))) WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaReport()"); if (!(sesolaptr = (SESOLA_STRUCT*)rqptr->rqNet.SesolaPtr)) { rqptr->rqResponse.HttpStatus = 403; ErrorGeneral (rqptr, "Only available via an SSL service!", FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } /* unbuffer these if called from an SSL renegotiate AST (see below) */ if (sesolaptr->ReportNextTaskFunction) { NextTaskFunction = sesolaptr->ReportNextTaskFunction; VirtualHostPort = sesolaptr->ReportVirtualHostPort; } /* now (!!) we can report "parameters" */ if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA))) WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "!&A !&Z", NextTaskFunction, VirtualHostPort); if (VirtualHostPort[0]) { for (svptr = SesolaListHead; svptr; svptr = svptr->SesolaNextPtr) if (strsame (svptr->ServerHostPort, VirtualHostPort, -1)) break; if (!svptr) { rqptr->rqResponse.HttpStatus = 403; ErrorGeneral (rqptr, "No such virtual service.", FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } } else svptr = rqptr->ServicePtr; if (svptr->RequestScheme != SCHEME_HTTPS || !((SESOLA_CONTEXT*)svptr->SSLserverPtr)->SslCtx) { rqptr->rqResponse.HttpStatus = 403; ErrorGeneral (rqptr, "Only available for an SSL service!", FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } if (strsame (rqptr->rqHeader.PathInfoPtr, ADMIN_REPORT_SSL_CLIENT, -1)) { /* Include any client certificate information. Note that if SesolaClientCert() completes asynchronously this function will be called again as an AST - with only one argument!! (the 'rqptr') This is not a major issue (apart for the purists ;^) as the 'NextTaskFunction' VirtualHostPort is buffered and unbuffered around such a call here. (I could have made it a variable length argument function, but this should work just as well!) */ if (!sesolaptr->ReportClientCertState) { sesolaptr->ReportClientCertState = 1; sesolaptr->ReportNextTaskFunction = NextTaskFunction; zptr = (sptr = sesolaptr->ReportVirtualHostPort) + sizeof(sesolaptr->ReportVirtualHostPort)-1; for (cptr = VirtualHostPort; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; /* using renegotiation, cancel any existing certificate */ status = SesolaClientCert (rqptr, SESOLA_VERIFY_PEER_NONE, &SesolaReport); if (status == SESOLA_VERIFY_PEER_PENDING) return; } if (sesolaptr->ReportClientCertState == 1) { sesolaptr->ReportClientCertState = 2; /* using (more) renegotiation, get a(nother) certificate */ status = SesolaClientCert (rqptr, SESOLA_VERIFY_PEER_OPTIONAL, &SesolaReport); if (status == SESOLA_VERIFY_PEER_PENDING) return; } } /**********/ /* report */ /**********/ /* the sesola service context */ scptr = (SESOLA_CONTEXT*)svptr->SSLserverPtr; /* this is the service's context - not the current session's! */ SslCtx = ((SESOLA_CONTEXT*)svptr->SSLserverPtr)->SslCtx; /* get the service's certificate and ciphers - not the current session's! */ SslPtr = SSL_new (SslCtx); if (!SslPtr) { ErrorNoticed (rqptr, 0, "SSL_new() failed", FI_LI); ErrorGeneral (rqptr, "SSL_new() failed.", FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } /*** 30-JUL-2013 MGD Arghhh! There's a bug in OpenSSL after 1.0.1b and at least at 1.0.1k, also the HP SSL V1.4-471 using the OpenSSL 0.9.8y fork, and again at least. http://openssl.6102.n7.nabble.com/NULL-ptr-deref-when-calling-SSL-get-certificate-with-1-0-0k-td43636.html Buggy code in OpenSSL v0.9.8? and v1.0.1? ... X509 *SSL_get_certificate(const SSL *s) { if (s->server) return(ssl_get_server_send_cert(s)); else if (s->cert != NULL) return(s->cert->key->x509); else return(NULL); } The problem is the ssl_get_server_send_cert() where a cert hasn't been sent (as in this fresh SSL context). It's only performed for a server context so just reset that flag - it's freed soon after anyway! :-) This workaround could be removed some appropriate time into the future. ***/ SslPtr->server = 0; ServerCertPtr = SSL_get_certificate (SslPtr); CipherStackPtr = SSL_get_ciphers (SslPtr); SSL_free (SslPtr); /* 'ServerCertPtr' has been initialized above */ X509_NAME_oneline (X509_get_issuer_name(ServerCertPtr), CertString, sizeof(CertString)); SesolaReportFormatCertDn (CertString, CertCaString, sizeof(CertCaString)); X509_NAME_oneline (X509_get_subject_name(ServerCertPtr), CertString, sizeof(CertString)); SesolaReportFormatCertDn (CertString, CertDnString, sizeof(CertDnString)); ASN1_UTCTIME_print (SesolaBioMemPtr, X509_get_notAfter(ServerCertPtr)); BIO_gets (SesolaBioMemPtr, CertNotAfterString, sizeof(CertNotAfterString)); SesolaCertFingerprint (ServerCertPtr, &EVP_sha1, CertFingerprintSHA1, sizeof(CertFingerprintSHA1)); SesolaCertFingerprint (ServerCertPtr, &EVP_md5, CertFingerprintMD5, sizeof(CertFingerprintMD5)); AdminPageTitle (rqptr, "SSL Report"); vecptr = FaoVector; *vecptr++ = ADMIN_REPORT_SSL; *vecptr++ = VirtualHostPort[0]; *vecptr++ = (SesolaServiceCount == 1); status = FaolToNet (rqptr, VirtualFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); if (SesolaServiceCount > 1) { for (tsvptr = SesolaListHead; tsvptr; tsvptr = tsvptr->SesolaNextPtr) { if (!((SESOLA_CONTEXT*)svptr->SSLserverPtr)->SslCtx) continue; vecptr = FaoVector; *vecptr++ = tsvptr->ServerHostPort; *vecptr++ = strsame (tsvptr->ServerHostPort, VirtualHostPort, -1); *vecptr++ = tsvptr->ServerHostPort; status = FaolToNet (rqptr, "

\n\ \n\ \n\
!AZ
\n\ \n\ \n\ \n"; static char CertStoreNull [] = "\n"; static char CertFao [] = "\n\ \n"; static char EndPageFao [] = "\n\ \n\
CA Verify File:!AZ
CA Verify Depth:!UL
 (none)

\n\ \n\ \ \ \n\ \ \n\ \n\ \n\
!UL.  Issuer: !AZ
Subject: !AZ
Begins: !AZ
Expires: !AZ
\n\

\ [Edit]  \ [List]  \ [Reload]\
\n\
\n\ \n\ \n"; int idx, status, Count; unsigned long FaoVector [16]; unsigned long *vecptr; char *cptr, *sptr, *zptr; char CertString [256], CertCaString [256], CertDnString [256], CertNotAfterString [32], CertNotBeforeString [32]; SERVICE_STRUCT *svptr; SESOLA_CONTEXT *scptr; SSL_CTX *SslCtx; X509 *CertPtr; X509_OBJECT *CertObjectPtr; X509_STORE *CertStorePtr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA))) WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaReportCA() !&A !AZ", NextTaskFunction, VirtualHostPort); if (VirtualHostPort[0]) { for (svptr = SesolaListHead; svptr; svptr = svptr->SesolaNextPtr) if (strsame (svptr->ServerHostPort, VirtualHostPort, -1)) break; if (!svptr) { rqptr->rqResponse.HttpStatus = 403; ErrorGeneral (rqptr, "No such virtual service.", FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } } else svptr = rqptr->ServicePtr; if (svptr->RequestScheme != SCHEME_HTTPS) { rqptr->rqResponse.HttpStatus = 403; ErrorGeneral (rqptr, "Only available for an SSL service!", FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } /* the sesola service context */ scptr = (SESOLA_CONTEXT*)svptr->SSLserverPtr; /* this is the service's context - not the current session's! */ SslCtx = ((SESOLA_CONTEXT*)svptr->SSLserverPtr)->SslCtx; AdminPageTitle (rqptr, "SSL Report, CA Verification"); vecptr = FaoVector; *vecptr++ = svptr->ServerHostPort; *vecptr++ = scptr->CaFilePtr; *vecptr++ = SSL_CTX_get_verify_depth (SslCtx); status = FaolToNet (rqptr, BeginPageFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); if (!(CertStorePtr = SSL_CTX_get_cert_store (SslCtx))) { status = FaolToNet (rqptr, CertStoreNull, NULL); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); } else { Count = 0; for (idx = 0; idx < sk_X509_OBJECT_num(CertStorePtr->objs); idx++) { CertObjectPtr = sk_X509_OBJECT_value (CertStorePtr->objs, idx); if (CertObjectPtr->type != X509_LU_X509) continue; Count++; CertPtr = (X509*)CertObjectPtr->data.x509; X509_NAME_oneline (X509_get_issuer_name(CertPtr), CertString, sizeof(CertString)); SesolaReportFormatCertDn (CertString, CertCaString, sizeof(CertCaString)); X509_NAME_oneline (X509_get_subject_name(CertPtr), CertString, sizeof(CertString)); SesolaReportFormatCertDn (CertString, CertDnString, sizeof(CertDnString)); ASN1_UTCTIME_print (SesolaBioMemPtr, X509_get_notAfter(CertPtr)); BIO_gets (SesolaBioMemPtr, CertNotAfterString, sizeof(CertNotAfterString)); ASN1_UTCTIME_print (SesolaBioMemPtr, X509_get_notBefore(CertPtr)); BIO_gets (SesolaBioMemPtr, CertNotBeforeString, sizeof(CertNotBeforeString)); vecptr = FaoVector; *vecptr++ = Count; *vecptr++ = CertCaString; *vecptr++ = CertDnString; *vecptr++ = CertNotBeforeString; *vecptr++ = CertNotAfterString; status = FaolToNet (rqptr, CertFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); } } vecptr = FaoVector; *vecptr++ = ADMIN_REVISE_SSL_CA; *vecptr++ = svptr->ServerHostPort; *vecptr++ = ADMIN_REPORT_SSL_CA; *vecptr++ = svptr->ServerHostPort; *vecptr++ = ADMIN_CONTROL_SSL_CA_LOAD; status = FaolToNet (rqptr, EndPageFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN; ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc); SysDclAst (NextTaskFunction, rqptr); } /*****************************************************************************/ /* Command-line CA verification file reload function. */ SesolaControlReloadCA () { int cnt, value, ErrorCount, SSLcount, ReloadCount; SERVICE_STRUCT *svptr; SESOLA_CONTEXT *scptr; SSL_CTX *SslCtx; X509_STORE *CertStorePtr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_SESOLA)) WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaControlReloadCA()"); /* enable SYSPRV to allow access to a possibly protected file */ sys$setprv (1, &SysPrvMask, 0, 0); ErrorCount = SSLcount = ReloadCount = 0; for (svptr = SesolaListHead; svptr; svptr = svptr->SesolaNextPtr) { for (cnt = 0; cnt < 2; cnt++) { if (cnt) scptr = (SESOLA_CONTEXT*)svptr->SSLclientPtr; else scptr = (SESOLA_CONTEXT*)svptr->SSLserverPtr; if (!scptr) continue; if (!scptr->SslCtx) continue; SSLcount++; if (!scptr->CaFilePtr[0]) continue; SslCtx = (SSL_CTX*)scptr->SslCtx; if (CertStorePtr = SslCtx->cert_store) { /* empty the current CA certificate store */ X509_STORE_free (CertStorePtr); SslCtx->cert_store = X509_STORE_new (); } /* load the file in afresh */ value = SSL_CTX_load_verify_locations (SslCtx, scptr->CaFilePtr, NULL); if (value) value = SSL_CTX_set_default_verify_paths (SslCtx); if (value) ReloadCount++; else { ErrorCount++; FaoToStdout ("%HTTPD-W-SSL, !AZ\n \\!AZ\\\n", svptr->ServerHostPort, scptr->CaFilePtr); SesolaPrintOpenSslErrorList (); FaoToStdout ("%HTTPD-W-SSL, client verification not enabled\n"); } } } sys$setprv (0, &SysPrvMask, 0, 0); FaoToStdout ("%HTTPD-I-SSL, \ reloaded CAs for !UL of !UL SSL contexts!&@\n", ReloadCount, SSLcount, ErrorCount ? ", !UL error!%s" : "", ErrorCount); if (OpcomMessages & OPCOM_HTTPD || OpcomMessages & OPCOM_AUTHORIZATION) FaoToOpcom ("%HTTPD-I-SSL, \ reloaded CAs for !UL of !UL SSL contexts!&@", ReloadCount, SSLcount, ErrorCount ? ", !UL error!%s" : "", ErrorCount); } /*****************************************************************************/ /* Just put a newline the certificate DN format at each component (meant to be placed inside

).
*/

SesolaReportFormatCertDn
(
char *InputDn,
char *OutputDn,
int SizeOfOutputDn
)
{
   char  *cptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA,
                 "SesolaReportFormatCertDn() !AZ", InputDn);

   zptr = (sptr = OutputDn) + SizeOfOutputDn - 1;
   for (;;)
   {
      cptr = SesolaParseCertDn (InputDn, NULL);
      if (!cptr) break;
      if (sptr > OutputDn && sptr < zptr) *sptr++ = '\n';
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   }
   *sptr = '\0';

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "!AZ", OutputDn);
}

/*****************************************************************************/
/*
Performs two functions parsing X.509 Distinguished Names.

First, if supplied with a DN record string using 'RecordNamePtr' it searches
for the matching record in the string supplied by 'StringPtr'.  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 'StringPtr' a NULL.

Needless-to-say (because of the use of static storage) this function is not
reentrant!!  Maximum record value size is 255 characters.
*/ 

char* SesolaParseCertDn
(
char *DnPtr,
char *RecordNamePtr
)
{
   static char  *ContextPtr = NULL;
   static char  RecordValue [256];

   int  RecordNameLength;
   char  *cptr, *sptr, *zptr;
   struct SesolaCertDnRecStruct  *cdnptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA,
                 "SesolaParseCertDn() !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++);
      if (*sptr) sptr++;
      RecordNameLength = sptr - RecordNamePtr;
      while (*cptr)
      {
         while (*cptr && *cptr != '/') cptr++;
         if (!*cptr) break;
         for (cdnptr = &SesolaCertDnRec; cdnptr->name; cdnptr++)
            if (strsame (cptr, cdnptr->name, cdnptr->length)) break;
         if (cdnptr->name &&
             (RecordNameLength == cdnptr->length ||
              /* bizarre!! */
              RecordNameLength == cdnptr->length+1) &&
             strsame (cdnptr->name, RecordNamePtr, RecordNameLength))
            break;
         cptr++;
      }
      if (WATCH_MODULE(WATCH_MOD_SESOLA))
         WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "!&Z", cptr);
      if (!*cptr) return (NULL);
   }

   zptr = (sptr = RecordValue) + sizeof(RecordValue);
   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 (strsame (cptr, cdnptr->name, cdnptr->length)) break;
      if (cdnptr->name) break;
   }
   /* if the buffer overflowed then just set an empty string! */
   if (sptr >= zptr) sptr = RecordValue;
   *sptr = '\0';
   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "!AZ", RecordValue);

   if (!RecordNamePtr) ContextPtr = cptr;

   return (RecordValue);
}

/*****************************************************************************/
/*
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* for convenience in compiling non-SSL version */
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 (NULL, FI_LI, 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 ("");
}

/*****************************************************************************/
/*
This is basically for inclusion in the WATCH report header.
*/

char* SesolaVersion ()

{
   static char  String [64];
   static $DESCRIPTOR (StringDsc, String);
   static $DESCRIPTOR (VersionFaoDsc, "!AZ (!AZ)\n\0");

   char  *cptr, *sptr;

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

   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaVersion()");

   if (String[0]) return (String);

   cptr = (char*)SSLeay_version (SSLEAY_BUILT_ON);
   if (sptr = strstr (cptr, "uilt on"))
   {
      cptr = sptr + 7;
      while (*cptr && (*cptr == ' ' || *cptr == ':')) cptr++;
   }

   sys$fao (&VersionFaoDsc, 0, &StringDsc,
            SSLeay_version (SSLEAY_VERSION), cptr);
   return (String);
}

/*****************************************************************************/
/*
For compilations without SSL these functions provide LINKage stubs for the
rest of the HTTPd modules, allowing for just recompiling the Sesola module to
integrate the SSL functionality.
*/

/*********************/
#else  /* not SESOLA */
/*********************/

/* required global storage */
BOOL  ProtocolHttpsAvailable = false,
      ProtocolHttpsConfigured,
      SesolaVerifyCAConfigured;
/* i.e. disabled */
int  SesolaSSLversion = -1,
     SesolaGblSecStructSize = 0;
char  HttpdSesola [] = "",
      SesolaParams [256] = "";


/* external storage */
extern char  ErrorSanityCheck[];
extern WATCH_STRUCT  Watch;

SesolaInit ()
{
   if (SesolaSSLversion <= 0) return;
   FaoToStdout ("%HTTPD-E-SSL, non-SSL version\n");
   exit (STS$K_ERROR | STS$M_INHIB_MSG);
}

SesolaInitService (SERVICE_STRUCT *svptr)
{
   return;
}

BOOL SesolaInitClientService (SERVICE_STRUCT *svptr)
{
   return (true);
}

BOOL SesolaSNIserviceSet (void *sesolaptr)
{
   return (false);
}

SesolaClientCert
(
REQUEST_STRUCT *rqptr,
int VerifyMode,
REQUEST_AST AstFunction
)
{
   return (AUTH_DENIED_BY_OTHER);
}

SesolaReport
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction,
char *VirtualHostPort
)
{
   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaReport()");

   rqptr->rqResponse.HttpStatus = 403;
   ErrorGeneral (rqptr, "This is a non-SSL version.", FI_LI);
   SysDclAst (NextTaskFunction, rqptr);
}

SesolaAdminReloadCA
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction
)
{
   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaAdminReloadCA()");

   rqptr->rqResponse.HttpStatus = 403;
   ErrorGeneral (rqptr, "This is a non-SSL version.", FI_LI);
   SysDclAst (NextTaskFunction, rqptr);
}

char* SesolaControlReloadCA ()
{
   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaControlReloadCA()");

   return ("!this is a non-SSL version");
}

SesolaReportCA
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction,
char *VirtualHostPort
)
{
   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaReportCA()");

   rqptr->rqResponse.HttpStatus = 403;
   ErrorGeneral (rqptr, "This is a non-SSL version.", FI_LI);
   SysDclAst (NextTaskFunction, rqptr);
}

char* SesolaVersion ()
{
   if (WATCH_MODULE(WATCH_MOD_SESOLA))
      WatchThis (NULL, FI_LI, WATCH_MOD_SESOLA, "SesolaVersion()");

   return ("");
}

SesolaWatchPeek
(
REQUEST_STRUCT *rqptr,
REQUEST_STRUCT *rqeptr
)
{
   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaWatchPeek()");

   return;
}

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

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