<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <p>Hi again,</p>
    <p>I have updated the nonce generation subroutine according to the
      recommendations in RFC 5116,<br>
      chapter 3.2, "<span class="h3">Recommended Nonce Formation". I
        have added a nonce counter to the implementation,<br>
        while keeping the old stuff. This SHOULD be sufficient to keep
        the nonces unique:</span></p>
    <p><span class="h3"><font face="monospace">char *get_random_string
          (void)<br>
          {<br>
                  static bool _first_run = true;<br>
                  struct timespec tv, tv2;<br>
                  static struct drand48_data buffer;<br>
                  double result, result2;<br>
                  char *randomstr = NULL;<br>
                  char *nonce_ctr = get_nonce_ctr();<br>
                  char * retval = NULL;<br>
          <br>
                  if (nonce_ctr == NULL)<br>
                          goto get_random_exit2;<br>
          <br>
                  if (clock_gettime (CLOCK_MONOTONIC, &tv) == -1)<br>
                          goto get_random_exit;<br>
          <br>
                  if (clock_gettime (CLOCK_PROCESS_CPUTIME_ID, &tv2)
          == -1)<br>
                          goto get_random_exit;<br>
          <br>
                  if (_first_run) {<br>
                          _first_run = false;<br>
                          srand48_r (tv2.tv_nsec, &buffer);<br>
                  }<br>
                  drand48_r (&buffer, &result);<br>
                  drand48_r (&buffer, &result2);<br>
          <br>
                  int ret = asprintf (&randomstr,
          "%lf:%ld:%ld:%ld:%ld:%d:%lf:%s",<br>
                                          result, tv.tv_sec, tv.tv_nsec,<br>
                                          tv2.tv_sec, tv2.tv_nsec,
          getpid(), result2, nonce_ctr);<br>
          <br>
                  if (ret != -1 && randomstr != NULL)<br>
                          retval = sha256_string (randomstr);<br>
          <br>
                  if (get_serial_debug)<br>
                          fprintf (stderr, "randomstr = %s\n",
          randomstr);<br>
          <br>
                  if (randomstr != NULL)<br>
                          free (randomstr);<br>
          get_random_exit:<br>
                  if (nonce_ctr != NULL)<br>
                          free (nonce_ctr);<br>
          get_random_exit2:<br>
                  return retval;<br>
          }<br>
        </font></span></p>
    <p><span class="h3">I have also added authorization request sequence
        serial numbers (ARSSNs). These would like DNS zone<br>
        serials. The request is protected from tampering with an RFC
        2104-style HMAC using:</span></p>
    <p><span class="h3">H(str) ::= sha256sum(str)<br>
        N = H(rnd)<br>
        HMAC = H ( N || username || password || clientIP || serial ||
        secret || N )<br>
        HMACret = H ( N || serial || secret || N )</span></p>
    <p><span class="h3">Nonce is re-added at the end of "data" to
        prevent length extension attacks in theory.</span></p>
    <p><span class="h3">Authorization in the script goes like this:</span></p>
    <p><font face="monospace"><span class="h3"><?php<br>
          <br>
          $serial_file = "/usr/local/etc/myauth/serial";<br>
          <br>
          $ip_address = $_SERVER['REMOTE_ADDR'];<br>
          $ip_srv_address = $_SERVER['SERVER_ADDR'];<br>
          <br>
          if ( $ip_address !== $ip_srv_address )<br>
          {<br>
                  header("HTTP/1.1 403 Forbidden");<br>
                  echo "HOST NOT PERMITTED";<br>
                  exit(0);<br>
          }<br>
          else if( isset($_POST["user"]) &&
          isset($_POST["pass"]) && isset($_POST["mode"]) )<br>
          {<br>
                  $ret=-1;<br>
          <br>
                  $nonce = $_POST["nonce"];<br>
                  $serial = $_POST["serial"];<br>
                  $sha256 = $_POST["hash"];<br>
                  if (($rawsecret =
          file_get_contents("/usr/local/etc/myauth/secret")) !== false)
          {<br>
                          $secret = trim($rawsecret);<br>
                          $mysha256 = hash("sha256", $nonce .
          $_POST["user"] . $_POST["pass"] . $_POST["mode"] .
          $_POST["clientIP"] . $_POST["serial"] . $secret . $nonce);<br>
                          if ($sha256 !== $mysha256) {<br>
                                  $secret = "";<br>
                                  $ret = 401;<br>
                          } else {<br>
                                  $rethash = hash("sha256", $nonce .
          $serial . $secret . $nonce);<br>
                                  $secret = "";<br>
                                  $ret = 0;<br>
                          }<br>
                  } else {<br>
                          $ret = 402;<br>
                          $secret = "";<br>
                  }<br>
          <br>
                  if ( $ret !== 0 ) {<br>
                          header("HTTP/1.1 $ret Forbidden");<br>
                          echo "HOST NOT PERMITTED";<br>
                          exit(0);<br>
                  }<br>
          <br>
                  if ( $ret == 0 && ($rawserial =
          file_get_contents($serial_file)) !== false) {<br>
                          $myserial = trim($rawserial);<br>
                          if ($myserial >= $serial)<br>
                                  $ret = 405;<br>
                          else {<br>
                                  // remote serial is greater, we are
          going to the next stage<br>
                                  if (file_put_contents($serial_file,
          $serial) == false)<br>
                                          $ret = 406;<br>
                                  else<br>
                                          $ret = 0;<br>
                          }<br>
                  } else<br>
                          $ret = 404;<br>
          <br>
                  if ( $ret !== 0 ) {<br>
                          header("HTTP/1.1 $ret Forbidden");<br>
                          echo "HOST NOT PERMITTED";<br>
                          exit(0);<br>
                  }<br>
          <br>
                  switch($_POST["mode"])<br>
                  {<br>
          <br>
                  // additional username checking ...</span></font></p>
    <p><span class="h3">So, if authorization request serial counter
        unlikely flips over INT64_MAX, it is rolls over to 1, and future<br>
        authorizations are impossible until the administrator
        intervention.<br>
      </span></p>
    <p><span class="h3">This works in theory, however the implementation
        may still be buggy. I am hoping to get this working<br>
        so I could perform additional PAM authorizations like temporary
        locking out of certs without revoking<br>
        them completely, working days and working hours access etc. ...</span></p>
    <p><span class="h3">I am planning to disseminate the code on GitHub,
        and make the installation more user-friendly,<br>
        however the code may need to be more stable before publishing if
        you know what I mean.<br>
        I started HMAC authentication on Saturday afternoon, and several
        times I thought I had it, but<br>
        the circumstances proved me otherwise.</span></p>
    <p><span class="h3">I attribute my progress to the highly motivating
        atmosphere on the project:-)  and the help from Above.<br>
      </span></p>
    <p><span class="h3">Any idea? Am I on the right track?</span></p>
    <p><span class="h3">(Of course, this is still just as secure as the
        shell account's password and the www-data user<br>
        running PHP scripts.)</span></p>
    <p><span class="h3">RATIONALE is:<br>
      </span></p>
    <p><span class="h3">Since the original pam_url did not authenticate
        either pam_url module host and neither the<br>
        authentication PHP script running module, it seemed necessary to
        authenticate them and using<br>
        HMAC-SHA-256 seemed as a logical solution, when compared to
        transmission of secret in cleartext<br>
        over TLS channel, which later also implied the use of nonces and
        serials<br>
        that prevent replay attack in case that the TLS session had
        theoretically been recorded for offline<br>
        brute force or other form of man-in-the-middle attack.<br>
      </span></p>
    <p><span class="h3">Kind regards,<br>
        Mirsad<br>
      </span></p>
    <div class="moz-cite-prefix">On 7.2.2022. 8:58, Mirsad Goran
      Todorovac wrote:<br>
    </div>
    <blockquote type="cite"
      cite="mid:3e56a363-930d-2060-53b8-788271288476@alu.unizg.hr">Hi
      Paul,
      <br>
      <br>
      On 7.2.2022. 1:56, Paul Wouters wrote:
      <br>
      <blockquote type="cite">On Sun, 6 Feb 2022, Mirsad Goran Todorovac
        wrote:
        <br>
        <br>
        <blockquote type="cite">The passwordless authentication over
          pam_url used with IKEv2 with the certificates was considered
          <br>
          a source of brute force attacks and a dangerous module to
          implement for it could allow everyone to
          <br>
          access the system if accidentally left as the only and
          sufficient module in PAM stack.
          <br>
        </blockquote>
        <br>
        You can't really brute force the certificate validation part.
        <br>
        <br>
        The pam module is just an _additional_ restriction that can
        restrict an
        <br>
        otherwise validated certificate. It is never even called for
        invalid,
        <br>
        bad or revoked certificates as the connection is rejected before
        the pam
        <br>
        phase due to the failed verification.
        <br>
      </blockquote>
      Yes. Agreed. But: if TLS is somehow compromised (i.e. by a quantum
      computer attack), I would still want
      <br>
      to be able to authenticate safely. Or as safe as my hash function
      (currently being SHA-256, but nothing
      <br>
      prevents using a stronger one ...).
      <br>
      <br>
      I try this by sending message digest:
      <br>
      <br>
      H(str) = sha256sum(str);
      <br>
      N = H(rnd);
      <br>
      HMAC = H( N || username || password || clientIP || secret || N);
      <br>
      retHMAC = H( N || secret || N);
      <br>
      <br>
      So, the authorization script MUST prove knowledge of the secret
      and my nonce N as a challenge
      <br>
      by sending retHMAC which I check.
      <br>
      <br>
      It can only do so if knowing the nonce of the session N and the
      pre-shared secret.
      <br>
      <br>
      The key is to generate unique nonce. I do so like this:
      <br>
      <br>
      char *get_random_string (void)
      <br>
      {
      <br>
              static bool _first_run = true;
      <br>
              struct timespec tv, tv2;
      <br>
              static struct drand48_data buffer;
      <br>
              double result, result2;
      <br>
              char *randomstr = NULL;
      <br>
      <br>
              if (clock_gettime (CLOCK_MONOTONIC, &tv) == -1)
      <br>
                      return NULL;
      <br>
      <br>
              if (clock_gettime (CLOCK_PROCESS_CPUTIME_ID, &tv2) ==
      -1)
      <br>
                      return NULL;
      <br>
      <br>
              if (_first_run) {
      <br>
                      _first_run = false;
      <br>
                      srand48_r (tv2.tv_nsec, &buffer);
      <br>
              }
      <br>
              drand48_r (&buffer, &result);
      <br>
              drand48_r (&buffer, &result2);
      <br>
              int ret = asprintf (&randomstr,
      "%lf%ld%ld%ld%ld%d%lf",
      <br>
                                      result, tv.tv_sec, tv.tv_nsec,
      <br>
                                      tv2.tv_sec, tv2.tv_nsec, getpid(),
      result2);
      <br>
              if (ret == -1)
      <br>
                      return NULL;
      <br>
              char * hashstr = sha256_string (randomstr);
      <br>
              free (randomstr);
      <br>
              return hashstr;
      <br>
      }
      <br>
      <br>
      So, I am not sure that I get enough randomness i.e. if the PAM
      module is (theoretically) called for the
      <br>
      second time within the same nanosecond (or time resolution
      interval of the clock, which can be of lesser
      <br>
      quality!).
      <br>
      <br>
      <blockquote type="cite">
        <blockquote type="cite">So, the main question appears to be if
          there is a smarter way of preventing brute force replay
          attacks
          <br>
        </blockquote>
        <br>
        IKE has build-in protection against replay attacks. Both sides
        you a
        <br>
        nonce for different connection attempts. So it is always
        different and
        <br>
        there is no replaying possible.
        <br>
      </blockquote>
      Indeed, but I do not authenticate the second stage pam_url over
      IKE, but over TLS connection instead
      <br>
      to a CGI-bin module. In particular, this module is vulnerable to
      brute force attacks. I defend my system
      <br>
      by limiting to IP address, but that is easily defeated. So I
      thought of knowing a common secret like RADIUS
      <br>
      protocol.
      <br>
      <br>
      In particular, I did not want to send the secret in cleartext even
      over TLS connection, as i.e. private key
      <br>
      might have been compromised or brute force cracked by a quantum
      computer by yet undocumented
      <br>
      man-in-the-middle attack.
      <br>
      <br>
      I defend against these like this:
      <br>
      <br>
      $ip_address = $_SERVER['REMOTE_ADDR'];
      <br>
      $ip_srv_address = $_SERVER['SERVER_ADDR'];
      <br>
      <br>
      if ( $ip_address !== $ip_srv_address )
      <br>
      {
      <br>
              header("HTTP/1.1 403 Forbidden");
      <br>
              echo "HOST NOT PERMITTED";
      <br>
              exit(0);
      <br>
      }
      <br>
      else if( isset($_POST["user"]) && isset($_POST["pass"])
      && isset($_POST["mode"]) )
      <br>
      {
      <br>
              $ret=-1;
      <br>
      <br>
              $nonce = $_POST["nonce"];
      <br>
              $sha256 = $_POST["hash"];
      <br>
              if (($rawsecret =
      file_get_contents("/usr/local/etc/myauth/secret")) !== false) {
      <br>
                      $secret = trim($rawsecret);
      <br>
                      $mysha256 = hash("sha256", $nonce . $_POST["user"]
      . $_POST["pass"] . \
      <br>
                                                 $_POST["mode"] .
      $_POST["clientIP"] . $secret . $nonce);
      <br>
                      if ($sha256 !== $mysha256) {
      <br>
                              $secret = "";
      <br>
                              $ret = 401;
      <br>
                      } else {
      <br>
                              $rethash = hash("sha256", $nonce . $secret
      . $nonce);
      <br>
                              $secret = "";
      <br>
                              $ret = 0;
      <br>
                      }
      <br>
              } else {
      <br>
                      $ret = 402;
      <br>
              }
      <br>
      <br>
              if ( $ret == 0 )
      <br>
              switch($_POST["mode"])
      <br>
              {
      <br>
                      case "PAM_SM_AUTH";
      <br>
                              // Perform authing here
      <br>
                      case "PAM_SM_ACCOUNT";
      <br>
                              // Perform account aging here
      <br>
      <br>
      // .... the authorization code for the certificate ....
      <br>
      <br>
      So, I wonder how to generate unique-enough nonce, so my
      authorization process is safe.
      <br>
      (Safe as SHA256(str) hash at most.)
      <br>
      <br>
      In case of broken or compromised TLS, nothing prevents the
      adversary to record nonce and
      <br>
      SHA256 hash and reuse them. I defend against this by using
      rethash, which is a proof that the
      <br>
      authorization service is in possession of the nonce for this
      session (which must not be reused),
      <br>
      and in possession of the secret key (which we hope the adversary
      had not learned, for it is
      <br>
      never transmitted in clear like in PAP.
      <br>
      <br>
      I thought of maintaining some serial or sequence number for the
      authorization process which
      <br>
      would also be protected by the SHA256 hash, incremented and never
      repeated, but I still
      <br>
      haven't thought of a good implementation of this feature.
      <br>
      <br>
      I hope this explains.
      <br>
      <br>
      I know about sequence numbers in IKE as the protection from the
      replay attacks (I don't know
      <br>
      this in detail though), but let me remind you that pam_url doesn't
      authenticate over IKE nor
      <br>
      IPsec, so if we do not trust TLS, completely we open a host of
      issues ...
      <br>
      <br>
      Kind regards,
      <br>
      Mirsad
      <br>
      <br>
    </blockquote>
    <pre class="moz-signature" cols="72">-- 
Mirsad Todorovac
CARNet system engineer
Faculty of Graphic Arts | Academy of Fine Arts
University of Zagreb
Republic of Croatia, the European Union
--
CARNet sistem inženjer
Grafički fakultet | Akademija likovnih umjetnosti
Sveučilište u Zagrebu</pre>
  </body>
</html>