forked from rails/rails
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a ctor to AuthenticityToken and change class methods to instance
Do all the session manipulation in the constructor, and just operate on a copy of the CSRF token in the instance methods. Slightly simplifies the controller mixin code and makes the API read a bit better.
- Loading branch information
1 parent
ce62493
commit f57295f
Showing
4 changed files
with
92 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,54 +1,66 @@ | ||
module ActionController | ||
class AuthenticityToken | ||
class << self | ||
LENGTH = 32 | ||
|
||
def generate_masked(session) | ||
one_time_pad = SecureRandom.random_bytes(LENGTH) | ||
encrypted_csrf_token = xor_byte_strings(one_time_pad, master_csrf_token(session)) | ||
masked_token = one_time_pad + encrypted_csrf_token | ||
Base64.strict_encode64(masked_token) | ||
end | ||
LENGTH = 32 | ||
|
||
# Note that this will modify +session+ as a side-effect if there is | ||
# not a master CSRF token already present | ||
def initialize(session, logger = nil) | ||
session[:_csrf_token] ||= SecureRandom.base64(LENGTH) | ||
@master_csrf_token = Base64.strict_decode64(session[:_csrf_token]) | ||
@logger = logger | ||
end | ||
|
||
def valid?(session, encoded_masked_token, logger = nil) | ||
return false if encoded_masked_token.nil? | ||
def generate_masked | ||
# Start with some random bits | ||
masked_token = SecureRandom.random_bytes(LENGTH) | ||
|
||
masked_token = Base64.strict_decode64(encoded_masked_token) | ||
raise if masked_token.length != 32 | ||
|
||
# See if it's actually a masked token or not. In order to | ||
# deploy this code, we should be able to handle any unmasked | ||
# tokens that we've issued without error. | ||
if masked_token.length == LENGTH | ||
# This is actually an unmasked token | ||
if logger | ||
logger.warn "The client is using an unmasked CSRF token. This " + | ||
"should only happen immediately after you upgrade to masked " + | ||
"tokens; if this persists, something is wrong." | ||
end | ||
# XOR the random bits with the real token and concatenate them | ||
encrypted_csrf_token = self.class.xor_byte_strings(masked_token, @master_csrf_token) | ||
masked_token.concat(encrypted_csrf_token) | ||
|
||
masked_token == master_csrf_token(session) | ||
Base64.strict_encode64(masked_token) | ||
end | ||
|
||
def valid?(encoded_masked_token) | ||
return false unless encoded_masked_token | ||
|
||
elsif masked_token.length == LENGTH * 2 | ||
# Split the token into the one-time pad and the encrypted | ||
# value and decrypt it | ||
one_time_pad = masked_token[0...LENGTH] | ||
encrypted_csrf_token = masked_token[LENGTH..-1] | ||
csrf_token = xor_byte_strings(one_time_pad, encrypted_csrf_token) | ||
masked_token = Base64.strict_decode64(encoded_masked_token) | ||
|
||
csrf_token == master_csrf_token(session) | ||
# See if it's actually a masked token or not. In order to | ||
# deploy this code, we should be able to handle any unmasked | ||
# tokens that we've issued without error. | ||
if masked_token.length == LENGTH | ||
# This is actually an unmasked token | ||
if @logger | ||
@logger.warn "The client is using an unmasked CSRF token. This " + | ||
"should only happen immediately after you upgrade to masked " + | ||
"tokens; if this persists, something is wrong." | ||
end | ||
end | ||
|
||
private | ||
masked_token == @master_csrf_token | ||
|
||
def xor_byte_strings(s1, s2) | ||
s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*') | ||
end | ||
elsif masked_token.length == LENGTH * 2 | ||
# Split the token into the one-time pad and the encrypted | ||
# value and decrypt it | ||
one_time_pad = masked_token.first(LENGTH) | ||
encrypted_csrf_token = masked_token.last(LENGTH) | ||
csrf_token = self.class.xor_byte_strings(one_time_pad, encrypted_csrf_token) | ||
|
||
csrf_token == @master_csrf_token | ||
|
||
else | ||
# Malformed token of some strange length | ||
false | ||
|
||
def master_csrf_token(session) | ||
session[:_csrf_token] ||= SecureRandom.base64(LENGTH) | ||
Base64.strict_decode64(session[:_csrf_token]) | ||
end | ||
end | ||
|
||
def self.xor_byte_strings(s1, s2) | ||
raise "#{s1.length} #{s2.length}" if s1.length != s2.length | ||
|
||
s1.bytes.zip(s2.bytes).map! { |c1, c2| c1 ^ c2 }.pack('c*') | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters