/*****************************************************************************/ /* SesolaClient.c Secure Sockets Layer client (peer) certificate. VERSION HISTORY --------------- 07-JUL-2013 MGD SesolaWatchErrors() during renegotiation 21-AUG-2004 MGD significant refinements to SSL processing 22-JUL-2004 MGD SesolaClientCert() call RequestEnd() instead of SesolaNetSesolaClientCert() on network read/write error 14-JAN-2003 MGD DN record /email and /emailAddress 28-AUG-2002 MGD add SHA1 fingerprint (everybody else has it ;^) 07-APR-2002 MGD bugfix; SesolaClientCert() call SesolaNetRequestEnd() after network error for v8.0 SesolaNet..() support 28-FEB-2002 MGD bugfix; SesolaRenegotiateClientCert() reset SSL state to SSL_ST_OK if renegotiation fails 21-OCT-2001 MGD rework SESOLA.C */ /*****************************************************************************/ #ifdef WASD_VMS_V7 #undef _VMS__V6__SOURCE #define _VMS__V6__SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif /* standard C header files */ #include #include #include #include /* VMS related header files */ #include #include /* application header files */ #define SESOLA_REQUIRED #include "Sesola.h" #define WASD_MODULE "SESOLACLIENT" /***************************************/ #ifdef SESOLA /* secure sockets layer */ /***************************************/ /********************/ /* external storage */ /********************/ extern int ExitStatus, OpcomMessages; extern char ErrorSanityCheck[], SoftwareID[]; extern BIO_METHOD *SesolaBioMemPtr; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* This function is used for two purposes, authorization via a client certificate, and just getting the client certificate (for reports, etc.) Get the client certificate associated with the request. If none is available on the first call then initiate an SSL renegotiation with the client to supply one. If not authorizing using this certificate then just call the original AST to return the certificate via 'rqptr->rqNet.SesolaPtr->ClientCertPtr' (or of course no certificate if the pointer is NULL). When authorizing (SESOLA_VERIFY_PEER_AUTH) and a certificate is available (either on first call, i.e. session cached, or after renegotiation) then get a fingerprint of the certificate that can be used to identify the client user. Setting 'VerifyParam' to SESOLA_VERIFY_PEER_NONE (aka SSL_VERIFY_NONE) can be used to "logout" the client from it's current authorization, allowing another certficiate to be selected and used (via "?httpd=logout"). If a certificate is available this function generates all the appropriate authorization, user detail and certificate information. Values that can be passed via the 'VerifyMode' argument. SESOLA_VERIFY_PEER_AUTH verify the cert, fail, use for WASD authentication SESOLA_VERIFY_PEER_NEVER renegotiate without peer verification SESOLA_VERIFY_PEER_OPTIONAL get and verify the certificate (continue on fail) SESOLA_VERIFY_PEER_REQUIRED abort the connection if the cert does not verify */ int SesolaClientCert ( REQUEST_STRUCT *rqptr, int VerifyParam, REQUEST_AST AstFunction ) { int status, number, value, SessionHits, SessionTimeout, SessionTimeCSec, SessionTimeoutCSec, VerifyMode, WatchThisType; char *cptr, *sptr, *zptr, *CurrentPtr, *DigestTypePtr; char AuthFingerprint [64], CertFingerprintMD5 [64], CertFingerprintSHA1 [64], CertIssuer [512], CertSubject [512], String [256], TimeString [32], TimeoutString [32], UserDetails [256]; SESOLA_STRUCT *sesolaptr; SSL_SESSION *SessionPtr; struct tm *tmptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA))) WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaClientCert() !&A !UL", AstFunction, VerifyParam); sesolaptr = (SESOLA_STRUCT*)rqptr->rqNet.SesolaPtr; /* network errors? */ if (VMSnok (sesolaptr->ReadIOsb.Status)) { RequestEnd (rqptr); return (sesolaptr->ReadIOsb.Status); } if (VMSnok (sesolaptr->WriteIOsb.Status)) { RequestEnd (rqptr); return (sesolaptr->WriteIOsb.Status); } SessionPtr = SSL_get_session (sesolaptr->SslPtr); if (VerifyParam != SESOLA_VERIFY_AST) { /* initial call, not AST delivery - must have an AST address */ if (!AstFunction) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); /* set the peer verification type */ sesolaptr->CertVerifyMode = VerifyParam; } /* mask off any WASD-specific bits */ VerifyMode = sesolaptr->CertVerifyMode & SESOLA_VERIFY_PEER_MASK; if (sesolaptr->CertVerifyMode == SESOLA_VERIFY_PEER_AUTH) { /*****************/ /* authorization */ /*****************/ /* Check for directives about how to make the verification. We must do this every time, whether or not an actual renegotiation will take place, to set things like session timeouts, etc. I've tried to make it as efficient as possible. */ if ((cptr = rqptr->rqAuth.PathParameterPtr)[0]) { while (*cptr) { while (*cptr && *cptr != '[') { if (*cptr == '\\') cptr++; if (*cptr) cptr++; } if (!*cptr) break; switch (*(ULONGPTR)cptr) { case '[dp:' : case '[DP:' : /*****************************/ /* set CA verification depth */ /*****************************/ cptr += sizeof(unsigned long); if (isdigit (*cptr) || *cptr == '-') { number = atoi(cptr); SSL_set_verify_depth (sesolaptr->SslPtr, number); } break; case '[lt:' : case '[LT:' : /************************/ /* set session lifetime */ /************************/ cptr += sizeof(unsigned long); if (MATCH4 (cptr, "expi") || MATCH4 (cptr, "EXPI")) sesolaptr->SessionTimeoutMinutes = -1; else if (isdigit (*cptr) || *cptr == '-') sesolaptr->SessionLifetimeMinutes = atoi(cptr); break; case '[ru:' : case '[RU:' : /***************************/ /* source or 'remote-user' */ /***************************/ zptr = (sptr = sesolaptr->X509RemoteUserDnRecord) + sizeof(sesolaptr->X509RemoteUserDnRecord)-1; cptr += 4; while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; break; case '[to:' : case '[TO:' : /***********************/ /* set session timeout */ /***********************/ cptr += sizeof(unsigned long); if (MATCH4 (cptr, "expi") || MATCH4 (cptr, "EXPI")) sesolaptr->SessionTimeoutMinutes = -1; else if (isdigit (*cptr) || *cptr == '-') sesolaptr->SessionTimeoutMinutes = atoi(cptr); break; case '[vf:' : case '[VF:' : /************************************/ /* type of client cert verification */ /************************************/ cptr += sizeof(unsigned long); switch (*(ULONGPTR)cptr) { case 'none' : case 'NONE' : /* [VF:NONE] */ VerifyMode = SSL_VERIFY_NONE; break; case 'opti' : case 'OPTI' : /* [VF:OPTIONAL] */ sesolaptr->X509optionalNoCa = true; VerifyMode = SSL_VERIFY_PEER; break; case 'requ' : case 'REQU' : /* [VF:REQUIRED] */ VerifyMode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; break; } break; default : /* encountered some other directive, note it's location */ sesolaptr->X509ConditionalPtr = cptr; /* this *must* be done after the notation! */ cptr += sizeof(unsigned long); } } } } /* if the rule wants it pre-expired then ignore any cached certificate */ if (sesolaptr->SessionTimeoutMinutes < 0 && VerifyParam != SESOLA_VERIFY_AST) sesolaptr->ClientCertPtr = NULL; else if (sesolaptr->CertVerifyMode == SESOLA_VERIFY_PEER_NONE) sesolaptr->ClientCertPtr = NULL; else sesolaptr->ClientCertPtr = SSL_get_peer_certificate (sesolaptr->SslPtr); if (!sesolaptr->ClientCertPtr) { /***********************************************/ /* no client certficiate (currently) available */ /***********************************************/ if (VerifyParam == SESOLA_VERIFY_AST) { /* call from SesolaClientCertRenegotiate(), no certificate! */ if (sesolaptr->CertVerifyMode == SESOLA_VERIFY_PEER_AUTH) { /*****************/ /* authorization */ /*****************/ /* no certificate - no authentication! */ rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_LOGIN; } /* called as an AST, therefore call the original AST address */ SysDclAst (sesolaptr->ClientCertAstFunction, rqptr); return (SS$_NORMAL); } /* let's renegotiate with the client, trying to get a certificate */ sesolaptr->ClientCertAstFunction = AstFunction; sesolaptr->CertVerifyCallbackCount = 0; SSL_set_verify (sesolaptr->SslPtr, VerifyMode, &SesolaCertVerifyCallback); /* provide the request pointer for the verify callback */ SSL_set_ex_data (sesolaptr->SslPtr, 0, rqptr); SesolaClientCertRenegotiate (sesolaptr); if (sesolaptr->CertVerifyMode == SESOLA_VERIFY_PEER_AUTH) { /*****************/ /* authorization */ /*****************/ /* returning AUTH_PENDING, activate authorization AST function */ rqptr->rqAuth.AstFunction = rqptr->rqAuth.AstFunctionBuffer; rqptr->rqAuth.FinalStatus = AUTH_PENDING; } return (SESOLA_VERIFY_PEER_PENDING); } /*************************************/ /* yes, we have a client certificate */ /*************************************/ /* if a [LT:integer] was set earlier then now's the time to apply it */ if (sesolaptr->SessionLifetimeMinutes) { /* Hmmm, not sure if this is the absolutely best thing to do! Now that we've got a certificate make it a little easier on the client by extending the session timeout so the user will not need to respecify the certificate too often provided the session is continually used (updated each request by resetting the session timestamp). */ SSL_SESSION_set_time (SessionPtr, time(NULL)); } /* a [TO:integer] value will override a [LT:integer] one */ if (sesolaptr->SessionTimeoutMinutes) SSL_SESSION_set_timeout (SessionPtr, sesolaptr->SessionTimeoutMinutes*60); else if (sesolaptr->SessionLifetimeMinutes) SSL_SESSION_set_timeout (SessionPtr, sesolaptr->SessionLifetimeMinutes*60); 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 (WatchThisType || sesolaptr->CertVerifyMode == SESOLA_VERIFY_PEER_AUTH) { X509_NAME_oneline (X509_get_issuer_name(sesolaptr->ClientCertPtr), CertIssuer, sizeof(CertIssuer)); X509_NAME_oneline (X509_get_subject_name(sesolaptr->ClientCertPtr), CertSubject, sizeof(CertSubject)); DigestTypePtr = SesolaCertFingerprint (sesolaptr->ClientCertPtr, &EVP_md5, CertFingerprintMD5, sizeof(CertFingerprintMD5)); if (*DigestTypePtr) DigestTypePtr = SesolaCertFingerprint (sesolaptr->ClientCertPtr, &EVP_sha1, CertFingerprintSHA1, sizeof(CertFingerprintSHA1)); } if (WATCH_CAT && WatchThisType) { zptr = (sptr = AuthFingerprint) + sizeof(AuthFingerprint)-1; for (cptr = CertFingerprintMD5; *cptr; cptr++) if (*cptr != ':') *sptr++ = *cptr; *sptr = '\0'; SessionTimeCSec = SSL_SESSION_get_time (SessionPtr); SessionTimeout = SSL_SESSION_get_timeout (SessionPtr); SessionTimeoutCSec = SessionTimeCSec + SessionTimeout; tmptr = localtime (&SessionTimeCSec); if (!strftime (TimeString, sizeof(TimeString), "%b %d %T %Y", tmptr)) strcpy (TimeString, "strftime() error"); tmptr = localtime (&SessionTimeoutCSec); if (!strftime (TimeoutString, sizeof(TimeoutString), "%b %d %T %Y", tmptr)) strcpy (TimeoutString, "strftime() error"); SessionHits = SSL_CTX_sess_hits (sesolaptr->SslCtx); WatchThis (rqptr, FI_LI, WatchThisType, "X509 client certificate !AZ", VerifyParam == SESOLA_VERIFY_AST ? "RENEGOTIATED" : "CACHED"); WatchDataFormatted ("issuer: !AZ\n", CertIssuer); WatchDataFormatted ("subject: !AZ\n", CertSubject); WatchDataFormatted ("SHA1: !AZ\n", CertFingerprintSHA1); WatchDataFormatted ("MD5: !AZ\n", CertFingerprintMD5); WatchDataFormatted ("fingerprint: !AZ \n", AuthFingerprint); WatchDataFormatted ( "session: !UL hit!%s since !AZ, timeout (!SL) at !AZ\n", SessionHits, TimeString, SessionTimeout / 60, TimeoutString); } if (sesolaptr->CertVerifyMode != SESOLA_VERIFY_PEER_AUTH) { if (VerifyParam == SESOLA_VERIFY_AST) { /* call from SesolaClientCertRenegotiate() */ SysDclAst (sesolaptr->ClientCertAstFunction, rqptr); } return (SS$_NORMAL); } /*****************/ /* authorization */ /*****************/ if (sesolaptr->X509optionalNoCa) SSL_set_verify_result (sesolaptr->SslPtr, X509_V_OK); if (SSL_get_verify_result (sesolaptr->SslPtr) != X509_V_OK) { rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_LOGIN; if (VerifyParam == SESOLA_VERIFY_AST) { /* call from SesolaClientCertRenegotiate() */ SysDclAst (sesolaptr->ClientCertAstFunction, rqptr); } /* cancel the cached session by adjusting the timeout backwards */ SSL_SESSION_set_timeout (SessionPtr, -1); return (SS$_NORMAL); } if (!*DigestTypePtr) { /* hmmm, problem in generating the fingerprint */ rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_OTHER; if (VerifyParam == SESOLA_VERIFY_AST) { /* call from SesolaClientCertRenegotiate() */ SysDclAst (sesolaptr->ClientCertAstFunction, rqptr); } /* cancel the cached session by adjusting the timeout backwards */ SSL_SESSION_set_timeout (SessionPtr, -1); return (SS$_NORMAL); } /******************/ /* authenticated! */ /******************/ /* X509 authentication, full r+w access implied */ rqptr->rqAuth.UserCan = AUTH_READWRITE_ACCESS; rqptr->rqAuth.FinalStatus = SS$_NORMAL; rqptr->rqAuth.ClientCertIssuerLength = strlen(CertIssuer); rqptr->rqAuth.ClientCertIssuerPtr = VmGetHeap (rqptr, rqptr->rqAuth.ClientCertIssuerLength+1); strcpy (rqptr->rqAuth.ClientCertIssuerPtr, CertIssuer); rqptr->rqAuth.ClientCertSubjectLength = strlen(CertSubject); rqptr->rqAuth.ClientCertSubjectPtr = VmGetHeap (rqptr, rqptr->rqAuth.ClientCertSubjectLength+1); strcpy (rqptr->rqAuth.ClientCertSubjectPtr, CertSubject); /* if a conditional was detected during an earlier phase */ if (sesolaptr->X509ConditionalPtr) { if (!SesolaClientCertConditional (rqptr, sesolaptr->X509ConditionalPtr)) { rqptr->rqAuth.FinalStatus = AUTH_DENIED_BY_OTHER; if (VerifyParam == SESOLA_VERIFY_AST) { /* call from SesolaClientCertRenegotiate() */ SysDclAst (sesolaptr->ClientCertAstFunction, rqptr); } /* cancel the cached session by adjusting the timeout backwards */ SSL_SESSION_set_timeout (SessionPtr, -1); return (SS$_NORMAL); } } /* derive the user details from the /CN and /EMAIL of the subject */ zptr = (sptr = UserDetails) + sizeof(UserDetails)-1; cptr = SesolaParseCertDn (rqptr->rqAuth.ClientCertSubjectPtr, "/CN="); if (cptr) { while (*cptr && *cptr != '=') cptr++; if (*cptr) cptr++; while (*cptr && sptr < zptr) *sptr++ = *cptr++; } cptr = SesolaParseCertDn (rqptr->rqAuth.ClientCertSubjectPtr, "/Email="); if (!cptr) cptr = SesolaParseCertDn (rqptr->rqAuth.ClientCertSubjectPtr, "/emailAddress="); if (cptr) { while (*cptr && *cptr != '=') cptr++; if (*cptr) { cptr++; if (sptr < zptr) *sptr++ = ','; if (sptr < zptr) *sptr++ = ' '; while (*cptr && sptr < zptr) *sptr++ = *cptr++; } } *sptr = '\0'; rqptr->rqAuth.UserDetailsLength = sptr - UserDetails; rqptr->rqAuth.UserDetailsPtr = VmGetHeap (rqptr, rqptr->rqAuth.UserDetailsLength+1); strcpy (rqptr->rqAuth.UserDetailsPtr, UserDetails); rqptr->rqAuth.ClientCertFingerprintLength = strlen(CertFingerprintMD5); rqptr->rqAuth.ClientCertFingerprintPtr = VmGetHeap (rqptr, rqptr->rqAuth.ClientCertFingerprintLength+1); strcpy (rqptr->rqAuth.ClientCertFingerprintPtr, CertFingerprintMD5); zptr = (sptr = rqptr->RemoteUser) + sizeof(rqptr->RemoteUser)-1; if (sesolaptr->X509RemoteUserDnRecord[0]) { cptr = SesolaParseCertDn (rqptr->rqAuth.ClientCertSubjectPtr, sesolaptr->X509RemoteUserDnRecord); if (cptr) { while (*cptr && *cptr != '=') cptr++; if (*cptr) cptr++; while (*cptr && sptr < zptr) { /* convert white-space to underscores */ if (!ISLWS(*cptr)) { *sptr++ = *cptr++; continue; } *sptr++ = '_'; cptr++; } } } else { /* "remote user name" is derived from fingerprint without the colons */ cptr = rqptr->rqAuth.ClientCertFingerprintPtr; while (*cptr && sptr < zptr) { if (*cptr == ':') cptr++; else *sptr++ = *cptr++; } } /* overflow does not truncate, it empties (does not authorize)!! */ if (sptr >= zptr) { *sptr = '\0'; if (WATCH_CAT && WatchThisType) WatchThis (rqptr, FI_LI, WatchThisType, "X509 overflow at !UL bytes REMOTE_USER !AZ", sizeof(rqptr->RemoteUser)-1, rqptr->RemoteUser); sptr = rqptr->RemoteUser; } *sptr = '\0'; rqptr->RemoteUserLength = sptr - rqptr->RemoteUser; if (rqptr->RemoteUserLength) strcpy (rqptr->RemoteUserPassword, "anystringwilldo"); if (WATCH_CAT && WatchThisType) WatchThis (rqptr, FI_LI, WatchThisType, "X509 client certificate REMOTE_USER !AZ", rqptr->RemoteUser[0] ? rqptr->RemoteUser : "(none)"); if (VerifyParam == SESOLA_VERIFY_AST) { /* call from SesolaClientCertRenegotiate() */ SysDclAst (sesolaptr->ClientCertAstFunction, rqptr); } return (SS$_NORMAL); } /*****************************************************************************/ /* Scan the authorization parameter string evaluating the conditions! Return true or false. Anything it cannot understand it ignores! (and yes, it does look a little like MapUrl_Conditional() :^) */ BOOL SesolaClientCertConditional ( REQUEST_STRUCT *rqptr, char *ConditionalPtr ) { BOOL NegateThisCondition, NegateEntireConditional, Result, SoFarSoGood, WatchThisOne; int AlgKeySize, ConditionalCount, MinKeySize, UseKeySize; char *cptr, *csptr, *sptr, *zptr, *CipherNamePtr, *CurrentPtr, *VersionNamePtr; char Scratch [AUTH_MAX_PATH_PARAM_LENGTH+1]; SESOLA_STRUCT *sesolaptr; SSL_CIPHER *CipherPtr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA))) WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaClientCertConditional() !&Z\n", ConditionalPtr); sesolaptr = (SESOLA_STRUCT*)rqptr->rqNet.SesolaPtr; if (WATCHING(rqptr) && (WATCH_CATEGORY(WATCH_SESOLA) || WATCH_CATEGORY(WATCH_AUTH))) WatchThisOne = true; else WatchThisOne = false; CurrentPtr = NULL; ConditionalCount = 0; NegateEntireConditional = NegateThisCondition = SoFarSoGood = false; cptr = ConditionalPtr; while (*cptr) { while (ISLWS(*cptr)) cptr++; if (!*cptr) break; if (*cptr == '[' || SAME2(cptr,'![')) { CurrentPtr = cptr; if (*cptr == '!') { NegateEntireConditional = true; cptr++; } else NegateEntireConditional = false; cptr++; ConditionalCount = 0; SoFarSoGood = false; continue; } if (*cptr == ']') { cptr++; if (NegateEntireConditional) { SoFarSoGood = !SoFarSoGood; NegateEntireConditional = false; } if (ConditionalCount && !SoFarSoGood) { cptr = ""; break; } continue; } if (SoFarSoGood) { if (NegateEntireConditional) { SoFarSoGood = !SoFarSoGood; NegateEntireConditional = false; } /* at least one OK, skip to the end of the conditional */ while (*cptr && *cptr != ']') cptr++; if (!*cptr) break; } if (!CurrentPtr) CurrentPtr = cptr; NegateThisCondition = Result = false; zptr = (sptr = Scratch) + sizeof(Scratch)-1; if (*cptr == '!') { cptr++; NegateThisCondition = true; } switch (*(USHORTPTR)cptr) { case 'ci' : case 'CI' : /***************/ /* Cipher Name */ /***************/ ConditionalCount++; cptr += 3; while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr) { if (*cptr == '\\') cptr++; if (*cptr) *sptr++ = *cptr++; } *sptr = '\0'; if (!CipherPtr) CipherPtr = SSL_get_current_cipher (sesolaptr->SslPtr); if (!CipherPtr) { CipherNamePtr = ""; AlgKeySize = UseKeySize = 0; } if (!CipherNamePtr) CipherNamePtr = (char*)SSL_CIPHER_get_name (CipherPtr); if (!CipherNamePtr) CipherNamePtr = ""; Result = StringMatch (rqptr, CipherNamePtr, Scratch); break; case 'is' : case 'IS' : /******************/ /* Cert Issuer DN */ /******************/ ConditionalCount++; cptr += 3; while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr) { if (*cptr == '\\') cptr++; if (*cptr) *sptr++ = *cptr++; } *sptr = '\0'; csptr = rqptr->rqAuth.ClientCertIssuerPtr; /* if it begins with DN record name then confine to that record */ if (*(sptr = Scratch) == '/') { /* must begin with something like "/CN=string" */ if (!(csptr = SesolaParseCertDn (csptr, sptr))) Result = false; else { /* found the (example) "/CN=", skip over BOTH */ while (*sptr && *sptr != '=') sptr++; if (*sptr) sptr++; while (*csptr && *csptr != '=') csptr++; if (*csptr) csptr++; /* now search only the returned DN record value */ Result = StringMatch (rqptr, csptr, sptr); } } else { /* search the entire DN */ Result = StringMatch (rqptr, csptr, sptr); } break; case 'ks' : case 'KS' : /*****************/ /* User Key Size */ /*****************/ ConditionalCount++; cptr += 3; while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr) { if (*cptr == '\\') cptr++; if (*cptr) *sptr++ = *cptr++; } *sptr = '\0'; MinKeySize = atoi(Scratch); if (MinKeySize < 0) MinKeySize = 0; if (!CipherPtr) CipherPtr = SSL_get_current_cipher (sesolaptr->SslPtr); if (!CipherPtr) UseKeySize = 0; else UseKeySize = SSL_CIPHER_get_bits(CipherPtr, &AlgKeySize); Result = UseKeySize >= MinKeySize; break; case 'su' : case 'SU' : /*******************/ /* Cert Subject DN */ /*******************/ ConditionalCount++; cptr += 3; while (*cptr && !ISLWS(*cptr) && *cptr != ']' && sptr < zptr) { if (*cptr == '\\') cptr++; if (*cptr) *sptr++ = *cptr++; } *sptr = '\0'; csptr = rqptr->rqAuth.ClientCertSubjectPtr; /* if it begins with DN record name then confine to that record */ if (*(sptr = Scratch) == '/') { /* must begin with something like "/CN=string" */ if (!(csptr = SesolaParseCertDn (csptr, sptr))) Result = false; else { /* found the (example) "/CN=", skip over BOTH */ while (*sptr && *sptr != '=') sptr++; if (*sptr) sptr++; while (*csptr && *csptr != '=') csptr++; if (*csptr) csptr++; /* now search only the returned DN record value */ Result = StringMatch (rqptr, csptr, sptr); } } else { /* search the entire DN */ Result = StringMatch (rqptr, csptr, sptr); } break; default : /***********************************/ /* unknown (or 'VF', etc.), ignore */ /***********************************/ if (WATCH_CAT && WatchThisOne) WatchDataFormatted ("IGNORE !AZ\n", CurrentPtr); while (*cptr && !ISLWS(*cptr) && *cptr != ']') { if (*cptr == '\\') cptr++; if (*cptr) cptr++; } continue; } if (NegateThisCondition) SoFarSoGood = SoFarSoGood || !Result; else SoFarSoGood = SoFarSoGood || Result; if (WATCH_CAT && WatchThisOne) WatchDataFormatted ("!AZ !AZ\n", SoFarSoGood ? "PASS" : "FAIL", CurrentPtr); CurrentPtr = NULL; } if (!ConditionalCount) SoFarSoGood = true; else if (NegateEntireConditional) SoFarSoGood = !SoFarSoGood; if (WATCH_CAT && WatchThisOne) WatchDataFormatted ("!AZ conditional\n", SoFarSoGood ? "PASSED" : "FAILED"); return (SoFarSoGood); } /*****************************************************************************/ /* Initiate an SSL "renegotiate" (state 1) and then an SSL "accept" (state 2) sequence to give the client an opportunity to supply a client certificate. Due to the non-blocking I/O used by WASD this function will be called multiple times to complete the SSL renegotiate/accept dialog. Note that this can only be called after the *entire* request (header and body) has been read from the SSL stream. */ SesolaClientCertRenegotiate (SESOLA_STRUCT *sesolaptr) { int status, value, VerifyMode; char *cptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = sesolaptr->RequestPtr; if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA))) WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SesolaClientCertRenegotiate() !&F", &SesolaClientCertRenegotiate); if (!sesolaptr->RenegotiateState) { sesolaptr->RenegotiateState = 1; sesolaptr->SslStateFunction = &SesolaClientCertRenegotiate; if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_SESOLA)) { VerifyMode = SSL_get_verify_mode (sesolaptr->SslPtr); switch (VerifyMode) { case SSL_VERIFY_NONE : cptr = "NONE"; break; case SSL_VERIFY_PEER : cptr = "OPTIONAL"; break; case SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT : cptr = "REQUIRED"; break; default : "unknown!"; } WatchThis (rqptr, FI_LI, WATCH_SESOLA, "X509 RENEGOTIATE client certificate verify:!AZ", cptr); SesolaWatchErrors (sesolaptr); } /* different session ID */ SesolaSessionId (sesolaptr->SslPtr); SSL_renegotiate (sesolaptr->SslPtr); } while (sesolaptr->RenegotiateState <= 2) { if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA))) WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "->RenegotiateState !UL", sesolaptr->RenegotiateState); value = SSL_do_handshake (sesolaptr->SslPtr); if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA))) { WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "SSL_do_handshake() !SL !SL", value, SSL_get_error(sesolaptr->SslPtr,value)); SesolaWatchErrors (sesolaptr); } /* if non-blocking IO in progress just return and wait for delivery */ if (sesolaptr->ReadInProgress || sesolaptr->WriteInProgress) { if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_SESOLA))) WatchThis (rqptr, FI_LI, WATCH_MOD_SESOLA, "READ:!&?yes\rno\r WRITE:!&?yes\rno\r", sesolaptr->ReadInProgress, sesolaptr->WriteInProgress); return; } if (value <= 0) { if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_SESOLA)) WatchThis (rqptr, FI_LI, WATCH_SESOLA, "X509 RENEGOTIATE client certificate FAILURE"); SesolaWatchErrors (sesolaptr); sesolaptr->RenegotiateState = 0; sesolaptr->SslStateFunction = NULL; /* hammer the SSL state back to OK */ sesolaptr->SslPtr->state = SSL_ST_OK; SesolaClientCert (rqptr, SESOLA_VERIFY_AST, NULL); return; } sesolaptr->RenegotiateState++; if (sesolaptr->RenegotiateState == 2) { /* SSL_set_accept_state() does too much! */ sesolaptr->SslPtr->state = SSL_ST_ACCEPT | SSL_ST_BEFORE; } } if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_SESOLA)) SesolaWatchSession (sesolaptr); sesolaptr->RenegotiateState = 0; sesolaptr->SslStateFunction = NULL; SesolaClientCert (rqptr, SESOLA_VERIFY_AST, NULL); } /*****************************************************************************/ /* 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 */ /*********************/ extern char ErrorSanityCheck[]; SesolaClientCertRenegotiate (void *sesolaptr) { ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } /************************/ #endif /* ifdef SESOLA */ /************************/ /*****************************************************************************/