<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>