+
+ if (defined($ENV{'REMOTE_USER'})) {
+ $csrf_token = Digest::HMAC_SHA1::hmac_sha1($ENV{'REMOTE_USER'}, $itkaclcommon::hmac_key);
+
+ # Mask the CSRF token to avoid the BREACH attack.
+ my @hmac_bytes = map { ord($_) } (split //, $csrf_token);
+ my @random_bytes = map { int(rand(256)) } (0..$#hmac_bytes);
+ my @masked_bytes = map { $hmac_bytes[$_] ^ $random_bytes[$_] } (0..$#hmac_bytes);
+
+ my $random_bytes_string = join('', map { chr($_) } (@random_bytes));
+ my $masked_bytes_string = join('', map { chr($_) } (@masked_bytes));
+ $masked_csrf_token = unpack('H*', $random_bytes_string) . '_' . unpack("H*", $masked_bytes_string);
+ } else {
+ $csrf_token = '';
+ $masked_csrf_token = '';
+ }
+}
+
+sub check_csrf_token {
+ if ($csrf_token eq '') {
+ # Not logged in, so always fine.
+ return 1;
+ }
+
+ my $candidate_csrf_token = $cgi->param('csrftoken');
+ if ($candidate_csrf_token !~ /^([0-9a-f]+)_([0-9a-f]+)$/) {
+ die "Invalid CSRF token!";
+ }
+
+ my ($random_bytes_string, $masked_bytes_string) = ($1, $2);
+ if (length($random_bytes_string) != length($masked_bytes_string)) {
+ die "Length mismatch in CSRF token!";
+ }
+
+ my @random_bytes = map { ord($_) } (split //, pack('H*', $random_bytes_string));
+ my @masked_bytes = map { ord($_) } (split //, pack('H*', $masked_bytes_string));
+ my @hmac_bytes = map { $masked_bytes[$_] ^ $random_bytes[$_] } (0..$#masked_bytes);
+ my $hmac_string = join('', map { chr($_) } (@hmac_bytes));
+
+ die "CSRF token mismatch!" if ($hmac_string ne $csrf_token);