vADC Docs

Converting Digest to Basic Authentication

by markbod on ‎06-03-2013 05:54 AM - edited on ‎07-06-2015 03:43 PM by PaulWallace (1,235 Views)

There are two common methods of Authentication built into the HTTP Protocol: Basic and Digest. They both work by using the "WWW-Authenticate" server response header, and the "Authorization" client request header. The simpler and more common of the two is the Basic method. This simply encodes the username and password with Base64 encoding and submits it to the server in the Authorization header. By contrast the Digest method is far more secure and works without sending the username and password over the network. Instead the client is sent a nonce challenge by the server, which it must then hash with the password and other data to generate an authentication response. The hash is put in the Authorization header along with other data and sent back to the server. If the server calculates the same response value as the client did (by using information in the header, and the username and password), then the authentication is successful.

 

Fortunately (??) Digest Authentication is open to a Man-In-The-Middle attack if you control a proxy through which the client and server communicate. So should you find yourself in a situation where you need to convert authentication schemes, because your server will only accept Digest authentication, but  your client only knows how to do Basic, then Stingray can help!

 

The below rule will convert a Server request for Digest authentication into a request for Basic authentication. It will then convert a Basic authentication request from the client into a Digest authentication request to the server. The rule has been tested to work with Apaches auth_digest module, which uses a QOP of auth. Your mileage may vary with other Digest implementations.

 

sub parseResponse() {

   # Return if the response is not a 401
   $code = http.getResponseCode();
   if ( $code != 401 )
      return;

   # Return if the auth is not Digest
   $authHeader = http.getResponseHeader("WWW-Authenticate");
   if ( ! string.contains($authHeader, "Digest") )
      return;

   # Parse the header...

   $auth = parseDigestHeader($authHeader);

   # We only support "auth", if it's not an option, log a warning...

   if ( ! string.contains($auth["qop"], "auth") ) 
      log.warn("Rule only supports \"auth\". Server QOP options: " .  $auth["qop"] );
      return;

   }
 

   http.setResponseCookie("digest", string.base64encode($authHeader));
   http.setResponseHeader("WWW-Authenticate", "Basic realm=" . $auth["realm"] );


}


sub parseRequest() {
   

   # We only support Basic Auth conversion, return if this isn't one
   $authHeader = http.getHeader("Authorization");
   if ( ! string.contains($authHeader, "Basic") ) 
      return;

   # Decode the user and password
   $enc = string.skip($authHeader, 6);
   $user = string.split(string.base64decode( $enc ), ":"); 
   
   # Get digest information from cookie or return
   $digest = string.base64decode(http.getCookie("digest"));
   if ( ! $digest )
      return;

   # Parse the decoded cookie digest
   $auth = parseDigestHeader($digest);


   $digest = generateDigest($user, $auth);
   http.setHeader("Authorization", $digest );

}


sub parseDigestHeader($header) {  
   foreach ( $param in string.split( string.skip($header, 7), ",") ) {
      $param = string.trim($param);
      if ( string.regexmatch($param, "([^=]*)=\"(.*?)\"") ) {
         $auth[ $1 ] = $2;
      }
   }

   return $auth;

}


sub generateDigest($user, $auth) {   
   $uri = http.getPath();
   $method = http.getMethod();
   $nc = 1;
   $now = sys.time();


   $ha1 = string.lowercase(string.hexencode(string.hashMD5( $user[0] . ":" . $auth["realm"] . ":" . $user[1] )));
   $ha2 = string.lowercase(string.hexencode(string.hashMD5( $method . ":" . $uri )));


   $cnonce = string.lowercase(string.hexencode(string.hashMD5( request.getRemoteIP() . $now )));

   $digest =  $ha1 .":". $auth["nonce"] .":". string.sprintf("%08d", $nc) .":". $cnonce .":auth:". $ha2;
   $digest = string.lowercase(string.hexencode(string.hashMD5($digest)));
   
   $header = "Digest username=\"" . $user[0] . "\", realm=\"" . $auth["realm"] . "\", nonce=\"" . 
             $auth["nonce"] . "\", uri=\"" . $uri . "\", response=\"" . $digest . "\"" .
             ", cnonce=\"" . $cnonce . "\", qop=auth, nc=" . string.sprintf("%08d", $nc) . 
             ", algorithm=MD5"; 

   return $header;

}


if ( rule.getstate() == "REQUEST" ) {
   parseRequest();
} else {
   parseResponse();

}

 

The rule should be applied to the Virtual Server as both a Request and a Response rule. It will automatically intercept any requests for Digest authentication from the server and convert them to/from Basic authentication.

 

Enjoy!

Contributors