/*****************************************************************************/ /* Digest.c This module implements the DIGEST authentication functionality. The actual RSA MD5 digest code is contained in its own module MD5.c, and is almost exclusively the example implementation code presented in RFC1321. This module implements the digest access authentication as proposed in the HTTP Working Group's, IETF draft, RFC2069, January 1997. NOTE: Digest authentication relies on the storage of specially digested password being available for 'reverse' generation into an MD5 digest and subsequent comparison with the digest supplied from the browser. Therefore, authenication sources that cannot provide this digested password cannot use Digest authorization. This most certainly includes SYSUAF and authorization agents. For such environments and circumstances where both Basic and Digest methods are available a Digest challenge SHOULD NOT BE generated and returned to the browser indicating that Digest method is applicable to this request. The nonce is generated using three 8 digit, zero-filled, hexadecimal numbers. The client's 32 bit IP address, and each of the least and most significant longwords of the request's binary time. The nonce therefore comprises a string of 24 hexadecimal digits. It does not rely on any non-public value or format for its operation. This format allows the returned nonce to have the IP address and timestamp easily extracted and checked. If the client's IP address and the nonce's IP address do not match then it is rejected. If the timestamp indicates an unacceptable period between generation and use of the nonce has occured then it is rejected with a 'stale nonce' flag. The period is varied for different methods. Very much shorter for PUT and POST methods, etc., longer for GET, etc. BOOM! Tweaked and revalidated (28-AUG-2012) using Chrome 21.0, Firefox 15.0, MSIE 9.0, Opera 12.0 and Safari 6.0. All comply with RFC2069 which WASD is based on, not the more recent RFC2617. NOTE! Checked (27-JUL-2003) for compliance against Mozilla 1.4 (one of the very few agents supporting Digest authentication). WARNING! To date (09-JUL-1996) this functionality has only been tested with NCSA X Mosaic 2.7-4 .. This version of Mosaic seems a bit flakey with digest authentication. NCSA X Mosaic 2.7-4 seems to require the 'opaque' header field to generate a correct digest response. I thought it was optional, but I'll put in a dummy anyway. VERSION HISTORY --------------- 28-AUG-2012 MGD (yes - all but a decade on) numerious tweaks 27-JUL-2003 MGD (overdue) revision (against Mozilla 1.4) 04-AUG-2001 MGD support module WATCHing 29-APR-2000 MGD proxy authorization 30-OCT-1997 MGD a little maintenance 01-JUL-1996 MGD initial development of "Digest" authentication */ /*****************************************************************************/ #ifdef WASD_VMS_V7 #undef _VMS__V6__SOURCE #define _VMS__V6__SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif /* standard C header files */ #include #include #include #include /* VMS related header files */ #include #include #include #include /* application related header files */ #include "wasd.h" #define WASD_MODULE "DIGEST" #define DefaultNonceLifeTimeSeconds 60 /********************/ /* external storage */ /********************/ #ifdef DBUG extern BOOL Debug; #else #define Debug 0 #endif extern int ToLowerCase[], ToUpperCase[]; extern char SoftwareID[]; extern CONFIG_STRUCT Config; extern MSG_STRUCT Msgs; extern WATCH_STRUCT Watch; /**********************/ /* external functions */ /**********************/ Md5Digest (char*, int, MD5_HASH*); Md5HexString (char*, int, char*); /*****************************************************************************/ /* Convert the octets to a hexadecimal string. */ DigestHexString ( unsigned char *cptr, int cnt, unsigned char *sptr ) { static char HexDigits [] = "0123456789abcdef"; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "DigestHexString()"); while (cnt--) { *sptr++ = HexDigits[(*cptr & 0xf0) >> 4]; *sptr++ = HexDigits[*cptr++ & 0x0f]; } *sptr = '\0'; } /*****************************************************************************/ /* Create an MD5 16 byte digest of the username, realm and password. */ int DigestA1 ( char *UserName, char *Realm, char *Password, unsigned char *A1Digest ) { char *cptr, *sptr, *zptr; char A1String [256]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "DigestA1() !&Z !&Z !UL", UserName, Realm, strlen(Password)); zptr = (sptr = A1String) + sizeof(A1String)-1; for (cptr = UserName; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = ':'; for (cptr = Realm; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = ':'; for (cptr = Password; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; Md5Digest (A1String, sptr-A1String, (MD5_HASH*)A1Digest); return (SS$_NORMAL); } /*****************************************************************************/ /* Generate the request-digest/response string. */ int DigestResponse ( unsigned char *A1HexDigest, char *Nonce, char *Method, char *Uri, char *ResponseHexDigest ) { char *cptr, *sptr, *zptr; char A2HexDigest [33], A2String [1024], ResponseString [256]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "DigestResponse() !&Z !&Z !&Z !&Z", A1HexDigest, Nonce, Method, Uri); ResponseHexDigest[0] = '\0'; zptr = (sptr = A2String) + sizeof(A2String)-1; for (cptr = Method; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = ':'; for (cptr = Uri; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; Md5HexString (A2String, sptr-A2String, A2HexDigest); zptr = (sptr = ResponseString) + sizeof(ResponseString)-1; for (cptr = A1HexDigest; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = ':'; for (cptr = Nonce; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = ':'; for (cptr = A2HexDigest; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; Md5HexString (ResponseString, sptr-ResponseString, ResponseHexDigest); if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "!&Z", ResponseHexDigest); return (SS$_NORMAL); } /*****************************************************************************/ /* Generate a basic DIGEST challenge string, comprising the authentication realm and the nonce. Store this null-terminated string in dynamically allocated memory pointed to by 'rqptr->rqAuth.DigestChallengePtr'. The 'Other' allows other digest fields to be included, for example ", stale=true". */ int DigestChallenge ( REQUEST_STRUCT *rqptr, char *Other ) { static $DESCRIPTOR (NonceFaoDsc, "!8XL!8XL!8XL"); ulong *qtime; char Nonce [25], AuthHeader [1024]; char *cptr, *sptr, *zptr; $DESCRIPTOR (NonceDsc, Nonce); /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "DigestChallenge()"); qtime = &rqptr->rqTime.BeginTime64; sys$fao (&NonceFaoDsc, 0, &NonceDsc, IPADDRESS_ADR4(&rqptr->ClientPtr->IpAddress), qtime[0], qtime[1]); Nonce[sizeof(Nonce)-1] = '\0'; zptr = (sptr = AuthHeader) + sizeof(AuthHeader)-1; if (rqptr->ServicePtr->ProxyAuthRequired) cptr = "Proxy-Authenticate"; else cptr = "WWW-Authenticate"; while (*cptr && sptr < zptr) *sptr++ = *cptr++; for (cptr = ": Digest realm=\""; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = rqptr->rqAuth.RealmDescrPtr; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = "\", nonce=\""; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = Nonce; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = '\"'; for (cptr = Other; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; /* allocate heap memory */ rqptr->rqAuth.DigestChallengePtr = VmGetHeap (rqptr, sptr-AuthHeader); strcpy (rqptr->rqAuth.DigestChallengePtr, AuthHeader); return (SS$_NORMAL); } /*****************************************************************************/ /* Parse the "Digest" authorization HTTP header field. Extract and store the digest response, nonce, realm, username, and digest URI. */ int DigestAuthorization (REQUEST_STRUCT *rqptr) { int status; char Algorithm [16], Nonce [25], Opaque [33], Realm [16], Response [33], Uri [1024], UserName [33]; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "DigestAuthorization() !&Z", rqptr->rqAuth.RequestAuthorizationPtr); rqptr->rqAuth.Scheme = rqptr->rqAuth.ChallengeScheme = AUTH_SCHEME_DIGEST; cptr = rqptr->rqAuth.RequestAuthorizationPtr; /* skip across the authorization scheme name ("digest") */ while (*cptr && !ISLWS(*cptr)) cptr++; /* skip over white-space between scheme and digest parameters */ while (*cptr && ISLWS(*cptr)) cptr++; /****************/ /* parse values */ /****************/ Algorithm[0] = Nonce[0] = Opaque[0] = Realm[0] = Response[0] = Uri[0] = UserName[0] = '\0'; while (*cptr) { while (ISLWS(*cptr) || *cptr == ',') cptr++; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "!&Z", cptr); if (TOUP(*cptr) == 'A' && strsame (cptr, "algorithm=", 10)) zptr = (sptr = Algorithm) + sizeof(Algorithm)-1; else if (TOUP(*cptr) == 'N' && strsame (cptr, "nonce=", 6)) zptr = (sptr = Nonce) + sizeof(Nonce)-1; else if (TOUP(*cptr) == 'O' && strsame (cptr, "opaque=", 7)) zptr = (sptr = Opaque) + sizeof(Opaque)-1; else if (TOUP(*cptr) == 'R' && strsame (cptr, "realm=", 6)) zptr = (sptr = Realm) + sizeof(Realm)-1; else if (TOUP(*cptr) == 'R' && strsame (cptr, "response=", 9)) zptr = (sptr = Response) + sizeof(Response)-1; else if (TOUP(*cptr) == 'U' && strsame (cptr, "uri=", 4)) zptr = (sptr = Uri) + sizeof(Uri)-1; else if (TOUP(*cptr) == 'U' && strsame (cptr, "username=", 9)) zptr = (sptr = UserName) + sizeof(UserName)-1; else { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); return (STS$K_ERROR); } while (*cptr && *cptr != '=') cptr++; if (*cptr) cptr++; if (*cptr == '\"') { cptr++; while (*cptr && *cptr != '\"' && sptr < zptr) *sptr++ = *cptr++; if (*cptr) cptr++; } else while (*cptr && !ISLWS(*cptr) && *cptr != ',' && sptr < zptr) *sptr++ = *cptr++; if (sptr > zptr) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); return (STS$K_ERROR); } *sptr = '\0'; } if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "algorithm:!&Z nonce:!&Z opaque:!&Z realm:!&Z \ response:!&Z username:!&Z uri:!&Z", Algorithm, Nonce, Opaque, Realm, Response, UserName, Uri); /****************/ /* check values */ /****************/ if ((Algorithm[0] && !strsame (Algorithm, "MD5", -1)) || !Nonce[0] || !Realm[0] || !Response[0] || !Uri[0] || !UserName[0] || strcmp (rqptr->rqHeader.RequestUriPtr, Uri) || strcmp (rqptr->rqAuth.RealmDescrPtr, Realm) || strlen(Nonce) != 24) { rqptr->rqResponse.HttpStatus = 401; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_FAILED), FI_LI); return (STS$K_ERROR); } if (VMSnok (status = DigestCheckNonce (rqptr, Nonce))) return (status); /****************/ /* store values */ /****************/ rqptr->rqAuth.DigestNoncePtr = VmGetHeap (rqptr, strlen(Nonce)); strcpy (rqptr->rqAuth.DigestNoncePtr, Nonce); rqptr->rqAuth.RealmDescrPtr = VmGetHeap (rqptr, strlen(Realm)); strcpy (rqptr->rqAuth.RealmDescrPtr, Realm); rqptr->rqAuth.DigestResponsePtr = VmGetHeap (rqptr, strlen(Response)); strcpy (rqptr->rqAuth.DigestResponsePtr, Response); rqptr->rqAuth.DigestUriPtr = VmGetHeap (rqptr, strlen(Uri)); strcpy (rqptr->rqAuth.DigestUriPtr, Uri); if (strlen(UserName) >= sizeof(rqptr->RemoteUser)) { rqptr->rqResponse.HttpStatus = 401; ErrorGeneralOverflow (rqptr, FI_LI); return (STS$K_ERROR); } /* user names are always upper case for us! */ sptr = rqptr->RemoteUser; for (cptr = UserName; *cptr; *sptr++ = TOUP(*cptr++)); *sptr = '\0'; rqptr->RemoteUserLength = sptr - rqptr->RemoteUser; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "!&Z", rqptr->RemoteUser); return (SS$_NORMAL); } /*****************************************************************************/ /* The nonce comprises three 8 digit, zero-filled, hexadecimal numbers. The client's 32 bit IP address, and each of the least and most significant longwords of the request's binary time. The nonce is therefore a string of 24 hexadecimal digits. This format allows the returned nonce to have the IP address and timestamp easily extracted and checked. If the client's IP address and the nonce's IP address do not match then it is rejected. If the timestamp indicates an unacceptable period between generation and use of the nonce has occured then it is rejected with a 'stale nonce' flag. */ int DigestCheckNonce ( REQUEST_STRUCT *rqptr, char *Nonce ) { static unsigned long LibSecondOfYear = LIB$K_SECOND_OF_YEAR; int status; unsigned long NonceLifeTimeSeconds, NonceSecondOfYear, SecondOfYear; unsigned long NonceBinaryTime [2]; IPADDRESS ClientIpAddress; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "DigestCheckNonce()"); /* lib$cvt_htb() returns a 1 for OK or a 0 for conversion error */ if (status = lib$cvt_htb (8, Nonce, IPADDRESS_ADR4(&ClientIpAddress))) if (status = lib$cvt_htb (8, Nonce+8, &NonceBinaryTime[0])) status = lib$cvt_htb (8, Nonce+16, &NonceBinaryTime[1]); if (status != 1) { if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "!&S", status); rqptr->rqResponse.HttpStatus = 401; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_FAILED), FI_LI); return (STS$K_ERROR); } if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "!&X !&X !%D", IPADDRESS_ADR4(&ClientIpAddress), IPADDRESS_ADR4(&rqptr->ClientPtr->IpAddress), &NonceBinaryTime); if (!MATCH0 (IPADDRESS_ADR4(&ClientIpAddress), IPADDRESS_ADR4(&rqptr->ClientPtr->IpAddress), IPADDRESS_SIZE(&rqptr->ClientPtr->IpAddress))) { rqptr->rqResponse.HttpStatus = 401; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_FAILED), FI_LI); return (STS$K_ERROR); } lib$cvt_from_internal_time (&LibSecondOfYear, &SecondOfYear, &rqptr->rqTime.BeginTime64); lib$cvt_from_internal_time (&LibSecondOfYear, &NonceSecondOfYear, &NonceBinaryTime); if ((rqptr->rqHeader.Method & HTTP_METHOD_GET) || (rqptr->rqHeader.Method & HTTP_METHOD_HEAD)) NonceLifeTimeSeconds = Config.cfAuth.DigestNonceGetLifeTime; else NonceLifeTimeSeconds = Config.cfAuth.DigestNoncePutLifeTime; if (!NonceLifeTimeSeconds) NonceLifeTimeSeconds = DefaultNonceLifeTimeSeconds; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "SecondOfYear:!UL Nonce:!UL LifeTime:!UL Stale:!&B", SecondOfYear, NonceSecondOfYear, NonceLifeTimeSeconds, (NonceSecondOfYear+NonceLifeTimeSecondsrqAuth.Scheme = rqptr->rqAuth.ChallengeScheme = AUTH_SCHEME_DIGEST; DigestChallenge (rqptr, ", stale=true"); rqptr->rqResponse.HttpStatus = 401; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_FAILED), FI_LI); } return (SS$_NORMAL); } /*****************************************************************************/