]> git.sesse.net Git - www-csrf/commitdiff
Initial checkin.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Tue, 12 Nov 2013 18:02:11 +0000 (19:02 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Tue, 12 Nov 2013 18:02:11 +0000 (19:02 +0100)
CSRF.pm [new file with mode: 0644]

diff --git a/CSRF.pm b/CSRF.pm
new file mode 100644 (file)
index 0000000..089c08b
--- /dev/null
+++ b/CSRF.pm
@@ -0,0 +1,73 @@
+
+use strict;
+use warnings;
+use Bytes::Random::Secure;
+use Digest::HMAC_SHA1;
+
+package WWW::CSRF;
+require Exporter;
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(generate_csrf_token check_csrf_token);
+
+sub generate_csrf_token {
+       my ($id, $secret) = @_;
+
+       my $time = time;
+
+       my $digest = Digest::HMAC_SHA1::hmac_sha1($time . "/" . $id, $secret);
+       my @digest_bytes = _to_byte_array($digest);
+
+       # Mask the token to avoid the BREACH attack.
+       my $random = Bytes::Random::Secure::random_bytes(scalar @digest_bytes);
+       my @random_bytes = _to_byte_array($random);
+       
+       my $masked_token = "";
+       my $mask = "";
+       for my $i (0..$#digest_bytes) {
+               $masked_token .= sprintf "%02x", ($digest_bytes[$i] ^ $random_bytes[$i]);
+               $mask .= sprintf "%02x", $random_bytes[$i];
+       }
+
+       return sprintf("%s,%s,%d", $masked_token, $mask, $time);
+}
+
+sub check_csrf_token {
+       my ($id, $secret, $csrf_token, $max_age) = @_;
+
+       if ($csrf_token !~ /^([0-9a-f]+),([0-9a-f]+),([0-9]+)$/) {
+               # Malformed token.
+               return 0;
+       }
+
+       my ($masked_token, $mask, $time) = ($1, $2, $3);
+       if (defined($max_age) && time - $time > $max_age) {
+               # Timed out.
+               return 0;
+       }
+
+       my @masked_bytes = _to_byte_array(pack('H*', $masked_token));
+       my @mask_bytes = _to_byte_array(pack('H*', $mask));
+
+       my $correct_token = Digest::HMAC_SHA1::hmac_sha1($time . '/' . $id, $secret);
+       my @correct_bytes = _to_byte_array($correct_token);
+
+       if ($#masked_bytes != $#mask_bytes || $#masked_bytes != $#correct_bytes) {
+               # Malformed token (wrong number of characters).
+               return 0;
+       }
+
+       # Compare in a way that should make timing attacks hard.
+       my $mismatches = 0;
+       for my $i (0..$#masked_bytes) {
+               $mismatches += $masked_bytes[$i] ^ $mask_bytes[$i] ^ $correct_bytes[$i];
+       }
+       return ($mismatches == 0);
+}
+
+# Converts each byte in the given string to its numeric value,
+# e.g., "ABCabc" becomes (65, 66, 67, 97, 98, 99).
+sub _to_byte_array {
+       return unpack("C*", $_[0]);
+}
+
+1;