From f3a4019eb88a670f6593f9ad29f30cd7667aa1f0 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Tue, 12 Nov 2013 19:02:11 +0100 Subject: [PATCH] Initial checkin. --- CSRF.pm | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 CSRF.pm diff --git a/CSRF.pm b/CSRF.pm new file mode 100644 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; -- 2.39.2