You are here

function _digest_md5_response in Secure Site 8

Same name and namespace in other branches
  1. 6.2 digest_md5/digest_md5.php \_digest_md5_response()
  2. 7.2 digest_md5/digest_md5.php \_digest_md5_response()

Process an authentication string.

Parameters

$edit:

  • data*
  • method*
  • uri
  • realm (defaults to machine name if not in data)
  • entity-body

Return value

Authentication info string or new challenge if authentication failed.

1 call to _digest_md5_response()
digest_md5.php in digest_md5/digest_md5.php
This script implements the DIGEST-MD5 mechanism for all protocols. Only the root user should have access to this script and the database used to store passwords and nonce values.

File

digest_md5/digest_md5.php, line 186
This script implements the DIGEST-MD5 mechanism for all protocols. Only the root user should have access to this script and the database used to store passwords and nonce values.

Code

function _digest_md5_response($edit) {
  global $time, $max_nc;

  // Get status.
  $fields = array();
  foreach (explode(',', trim($edit['data'])) as $part) {
    if (!empty($part)) {
      list($key, $value) = explode('=', trim($part), 2);
      $fields[$key] = trim($value, '"');
    }
  }
  $required = array(
    'username',
    'realm',
    'nonce',
    'uri',
    'response',
  );
  if (isset($fields['qop'])) {
    $required[] = 'cnonce';

    //    if ($edit['method'] != 'AUTHENTICATE') {
    $required[] = 'opaque';

    //    }
    $required[] = 'nc';
  }
  $uri = isset($edit['uri']) ? parse_url($edit['uri']) : NULL;
  $field_uri = isset($fields['uri']) ? parse_url($fields['uri']) : NULL;
  if (array_diff($required, array_keys($fields)) == array() && (!isset($edit['uri']) || $uri['path'] == $field_uri['path'])) {

    // Required fields are present and URI matches.
    $edit['realm'] = isset($edit['realm']) ? $edit['realm'] : $fields['realm'];
    $sn = db_query("SELECT qop, nc, opaque, hash FROM `securesite_nonce` WHERE nonce = :nonce AND realm = :realm", array(
      ':nonce' => $fields['nonce'],
      ':realm' => $edit['realm'],
    ))
      ->fetchAssoc();
    $pass = db_query("SELECT pass FROM `securesite_passwords` WHERE name = :name AND realm = :realm", array(
      ':name' => $fields['username'],
      ':realm' => $edit['realm'],
    ))
      ->fetchField();
    if ($pass !== FALSE) {

      // Password exists for this user.
      $ha1 = md5("{$fields['username']}:{$fields['realm']}:{$pass}");
      if (isset($fields['qop'])) {

        // Generate digest with quality of protection.
        switch ($fields['qop']) {
          case 'auth-int':
            $ha2 = md5("{$edit['method']}:{$fields['uri']}:{$sn['hash']}");
            break;
          case 'auth':
            $ha2 = md5("{$edit['method']}:{$fields['uri']}");
            break;
        }
        $digest = md5("{$ha1}:{$fields['nonce']}:{$fields['nc']}:{$fields['cnonce']}:{$fields['qop']}:{$ha2}");
      }
      else {

        // Generate digest without quality of protection.
        $ha2 = md5("{$edit['method']}:{$fields['uri']}");
        $digest = md5("{$ha1}:{$fields['nonce']}:{$ha2}");
      }
      if ($digest == $fields['response']) {

        // Response is valid.
        if ($sn === FALSE) {

          // Stale nonce; send new challenge with stale notice.
          $status = STALE_NONCE;
          $fields['nonce'] = uniqid();
        }
        else {
          if (isset($fields['qop']) && in_array($fields['qop'], explode(',', $sn['qop'])) && $fields['opaque'] == $sn['opaque'] || !isset($fields['qop']) && !isset($fields['nc'])) {
            $dec_nc = isset($fields['qop']) ? hexdec($fields['nc']) : $sn['nc'] + 1;
            $max_nc = isset($max_nc) ? $max_nc : $dec_nc + 1;
            if ($dec_nc <= $sn['nc']) {

              // Replay attack; re-send challenge.
              $status = REPLAY_ATTACK;
            }
            elseif ($dec_nc > $max_nc) {

              // Stale nonce; send new challenge with stale notice.
              $status = STALE_NONCE;
              db_query("DELETE FROM `securesite_nonce` WHERE nonce = :nonce AND realm = :realm", array(
                ':nonce' => $fields['nonce'],
                ':realm' => $edit['realm'],
              ));
              $fields['nonce'] = uniqid();
            }
            else {

              // User authenticated; send response.
              $status = AUTHENTICATED;
            }
          }
          else {

            // Bad request; re-send challenge.
            $status = BAD_REQUEST;
          }
        }
      }
      else {

        // Response is invalid; re-send challenge.
        $status = WRONG_PASSWORD;
      }
    }
    else {

      // Unknown user; re-send challenge.
      $status = UNKNOWN_USER;
    }
  }
  else {

    // Bad request; re-send challenge.
    $status = BAD_REQUEST;
    if (!isset($edit['realm']) && !isset($fields['realm'])) {
      $uname = posix_uname();
      $edit['realm'] = $fields['realm'] = $uname['nodename'];
    }
    elseif (!isset($edit['realm']) && isset($fields['realm'])) {
      $edit['realm'] = $fields['realm'];
    }
    elseif (isset($edit['realm']) && !isset($fields['realm'])) {
      $fields['realm'] = $edit['realm'];
    }
    if (isset($fields['nonce'])) {
      $sn = db_query("SELECT qop, nc, opaque, hash FROM `securesite_nonce` WHERE nonce = :nonce AND realm = :realm", array(
        ':nonce' => $fields['nonce'],
        ':realm' => $edit['realm'],
      ))
        ->fetchAssoc();
    }
    else {
      $fields['nonce'] = uniqid();
    }
  }

  // Create output.
  switch ($status) {
    case BAD_REQUEST:
    case UNKNOWN_USER:
    case WRONG_PASSWORD:
    case REPLAY_ATTACK:
    case STALE_NONCE:
      if (isset($sn) && $sn !== FALSE) {
        $fields['opaque'] = $sn['opaque'];
        $fields['qop'] = $sn['qop'];
      }
      else {
        $fields['opaque'] = isset($fields['opaque']) ? $fields['opaque'] : base64_encode($fields['nonce']);
        $qop = isset($edit['entity-body']) ? 'auth,auth-int' : 'auth';
        $fields['qop'] = isset($fields['qop']) ? $fields['qop'] : $qop;
      }
      $challenge = array(
        'realm="' . $fields['realm'] . '"',
        'nonce="' . $fields['nonce'] . '"',
        'qop="' . $fields['qop'] . '"',
        'opaque="' . $fields['opaque'] . '"',
      );
      if ($status == 'stale') {
        $challenge[] = 'stale=true';
      }
      if (!isset($sn) || $sn === FALSE) {
        $values = array(
          'nonce' => $fields['nonce'],
          'opaque' => $fields['opaque'],
          'time' => $time,
          'realm' => $edit['realm'],
          'qop' => $fields['qop'],
        );
        $values += isset($edit['entity-body']) ? array(
          'hash' => md5($edit['entity-body']),
        ) : array();
        $output = _digest_md5_challenge(array(
          'values' => $values,
          'challenge' => $challenge,
          'new' => TRUE,
        ));
      }
      else {
        $output = _digest_md5_challenge(array(
          'challenge' => $challenge,
          'new' => FALSE,
        ));
      }
      break;
    case AUTHENTICATED:
      $response = array();
      if ($dec_nc < $max_nc) {
        $values = array(
          'nonce' => $fields['nonce'],
          'time' => $time,
          'realm' => $edit['realm'],
        );
        $values += isset($edit['entity-body']) ? array(
          'hash' => md5($edit['entity-body']),
        ) : array();
        $values += isset($fields['qop']) ? array(
          'nc' => $dec_nc,
        ) : array();
        _digest_md5_challenge(array(
          'values' => $values,
          'new' => FALSE,
        ));
      }
      else {
        db_query("DELETE FROM `securesite_nonce` WHERE nonce = :nonce AND realm = :realm", array(
          ':nonce' => $fields['nonce'],
          ':realm' => $edit['realm'],
        ));
        $nextnonce = uniqid();
        $values = array(
          'nonce' => $nextnonce,
          'opaque' => $fields['opaque'],
          'time' => $time,
          'realm' => $edit['realm'],
          'qop' => $fields['qop'],
        );
        $values += isset($edit['entity-body']) ? array(
          'hash' => md5($edit['entity-body']),
        ) : array();
        _digest_md5_challenge(array(
          'values' => $values,
          'new' => TRUE,
        ));
        $response[] = 'nextnonce="' . $nextnonce . '"';
      }
      if (isset($fields['qop'])) {
        $response[] = 'qop=' . $fields['qop'];
        switch ($fields['qop']) {
          case 'auth-int':
            $response[] = 'cnonce="' . $fields['cnonce'] . '"';
            $response[] = 'nc="' . $fields['nc'] . '"';

            //TODO check next line
            $ha2 = md5(":{$fields['uri']}:{$sn['hash']}");
            break;
          case 'auth':
            $response[] = 'cnonce="' . $fields['cnonce'] . '"';
            $response[] = 'nc=' . $fields['nc'];
            $ha2 = md5(":{$fields['uri']}");
            break;
          default:
            $ha2 = md5(":{$fields['uri']}");
            break;
        }
        $digest = md5("{$ha1}:{$fields['nonce']}:{$fields['nc']}:{$fields['cnonce']}:{$fields['qop']}:{$ha2}");
      }
      else {
        $digest = md5("{$ha1}:{$fields['nonce']}:" . md5(":{$fields['uri']}"));
      }
      $response[] = 'rspauth="' . $digest . '"';
      $output = implode(', ', $response);
      break;
  }
  return array(
    $output,
    $status,
  );
}