+package WWW::CSRF;
use strict;
use warnings;
use Bytes::Random::Secure;
use Digest::HMAC_SHA1;
+use constant {
+ CSRF_OK => 0,
+ CSRF_MALFORMED_TOKEN => 1,
+ CSRF_INVALID_SIGNATURE => 2,
+ CSRF_EXPIRED => 3,
+};
-package WWW::CSRF;
require Exporter;
our @ISA = qw(Exporter);
-our @EXPORT_OK = qw(generate_csrf_token check_csrf_token);
+our @EXPORT_OK = qw(generate_csrf_token check_csrf_token CSRF_OK CSRF_MALFORMED_TOKEN CSRF_INVALID_SIGNATURE CSRF_EXPIRED);
our $VERSION = '1.00';
sub generate_csrf_token {
my ($id, $secret, $csrf_token, $options) = @_;
if ($csrf_token !~ /^([0-9a-f]+),([0-9a-f]+),([0-9]+)$/) {
- # Malformed token.
- return 0;
+ return CSRF_MALFORMED_TOKEN;
}
my $ref_time = $options->{'Time'} // time;
my ($masked_token, $mask, $time) = ($1, $2, $3);
my $max_age = $options->{'MaxAge'};
if (defined($max_age) && $ref_time - $time > $max_age) {
- # Timed out.
- return 0;
+ return CSRF_EXPIRED;
}
my @masked_bytes = _to_byte_array(pack('H*', $masked_token));
if ($#masked_bytes != $#mask_bytes || $#masked_bytes != $#correct_bytes) {
# Malformed token (wrong number of characters).
- return 0;
+ return CSRF_MALFORMED_TOKEN;
}
# Compare in a way that should make timing attacks hard.
for my $i (0..$#masked_bytes) {
$mismatches += $masked_bytes[$i] ^ $mask_bytes[$i] ^ $correct_bytes[$i];
}
- return ($mismatches == 0);
+ if ($mismatches == 0) {
+ return CSRF_OK;
+ } else {
+ return CSRF_INVALID_SIGNATURE;
+ }
}
# Converts each byte in the given string to its numeric value,
"5df5e9f17c929a45af5d33624ec052903599958f," .
"112233445566778899aabbccddeeff0011223344," .
"1234567890"),
- 1,
+ WWW::CSRF::CSRF_OK,
"check simple token");
-isnt(check_csrf_token("id", "secret",
- "0000000000000000000000000000000000000000," .
- "112233445566778899aabbccddeeff0011223344," .
- "1234567890"),
- 1,
- "check simple invalid token");
+is(check_csrf_token("id", "secret",
+ "0000000000000000000000000000000000000000," .
+ "112233445566778899aabbccddeeff0011223344," .
+ "1234567890"),
+ WWW::CSRF::CSRF_INVALID_SIGNATURE,
+ "check simple invalid token");
-isnt(check_csrf_token("id", "secret",
- "5df5e9f17c929a45af5d33624ec052903599958f," .
- "112233445566778899aabbccddeeff0011223344"),
- 1,
- "check simple malformed token");
+is(check_csrf_token("id", "secret",
+ "5df5e9f17c929a45af5d33624ec052903599958f," .
+ "112233445566778899aabbccddeeff0011223344"),
+ WWW::CSRF::CSRF_MALFORMED_TOKEN,
+ "check simple malformed token");
is(check_csrf_token("id", "secret",
"5df5e9f17c929a45af5d33624ec052903599958f," .
Time => 1234567895,
MaxAge => 10
}),
- 1,
+ WWW::CSRF::CSRF_OK,
"check with maxage");
-isnt(check_csrf_token("id", "secret",
- "5df5e9f17c929a45af5d33624ec052903599958f," .
- "112233445566778899aabbccddeeff0011223344," .
- "1234567890", {
- Time => 1234567895,
- MaxAge => 3
- }),
- 1,
- "check expired with maxage");
+is(check_csrf_token("id", "secret",
+ "5df5e9f17c929a45af5d33624ec052903599958f," .
+ "112233445566778899aabbccddeeff0011223344," .
+ "1234567890", {
+ Time => 1234567895,
+ MaxAge => 3
+ }),
+ WWW::CSRF::CSRF_EXPIRED,
+ "check expired with maxage");
-isnt(check_csrf_token("id", "secret",
- "5df5e9f17c929a45af5d33624ec052903599958f," .
- "112233445566778899aabbccddeeff0011223344," .
- "1234567894", {
- Time => 1234567895,
- MaxAge => 10
- }),
- 1,
- "check falsified timestamp");
+is(check_csrf_token("id", "secret",
+ "5df5e9f17c929a45af5d33624ec052903599958f," .
+ "112233445566778899aabbccddeeff0011223344," .
+ "1234567894", {
+ Time => 1234567895,
+ MaxAge => 10
+ }),
+ WWW::CSRF::CSRF_INVALID_SIGNATURE,
+ "check falsified timestamp");