Verify token signature in constant time, Run cheap checks first in token validation process (#1032)
* Verify token signature in constant time To prevent timing side channel attacks * Run cheap checks first in token validation process Expensive checks such as the nonce lookup on the database or the signature check can be run after cheap/fast checks.
This commit is contained in:
parent
72a4962fd0
commit
0d536d11e3
@ -1,3 +1,5 @@
|
|||||||
|
require "crypto/subtle"
|
||||||
|
|
||||||
def generate_token(email, scopes, expire, key, db)
|
def generate_token(email, scopes, expire, key, db)
|
||||||
session = "v1:#{Base64.urlsafe_encode(Random::Secure.random_bytes(32))}"
|
session = "v1:#{Base64.urlsafe_encode(Random::Secure.random_bytes(32))}"
|
||||||
PG_DB.exec("INSERT INTO session_ids VALUES ($1, $2, $3)", session, email, Time.utc)
|
PG_DB.exec("INSERT INTO session_ids VALUES ($1, $2, $3)", session, email, Time.utc)
|
||||||
@ -76,14 +78,25 @@ def validate_request(token, session, request, key, db, locale = nil)
|
|||||||
raise translate(locale, "Hidden field \"token\" is a required field")
|
raise translate(locale, "Hidden field \"token\" is a required field")
|
||||||
end
|
end
|
||||||
|
|
||||||
if token["signature"] != sign_token(key, token)
|
expire = token["expire"]?.try &.as_i
|
||||||
raise translate(locale, "Invalid signature")
|
if expire.try &.< Time.utc.to_unix
|
||||||
|
raise translate(locale, "Token is expired, please try again")
|
||||||
end
|
end
|
||||||
|
|
||||||
if token["session"] != session
|
if token["session"] != session
|
||||||
raise translate(locale, "Erroneous token")
|
raise translate(locale, "Erroneous token")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
scopes = token["scopes"].as_a.map { |v| v.as_s }
|
||||||
|
scope = "#{request.method}:#{request.path.lchop("/api/v1/auth/").lstrip("/")}"
|
||||||
|
if !scopes_include_scope(scopes, scope)
|
||||||
|
raise translate(locale, "Invalid scope")
|
||||||
|
end
|
||||||
|
|
||||||
|
if !Crypto::Subtle.constant_time_compare(token["signature"].to_s, sign_token(key, token))
|
||||||
|
raise translate(locale, "Invalid signature")
|
||||||
|
end
|
||||||
|
|
||||||
if token["nonce"]? && (nonce = db.query_one?("SELECT * FROM nonces WHERE nonce = $1", token["nonce"], as: {String, Time}))
|
if token["nonce"]? && (nonce = db.query_one?("SELECT * FROM nonces WHERE nonce = $1", token["nonce"], as: {String, Time}))
|
||||||
if nonce[1] > Time.utc
|
if nonce[1] > Time.utc
|
||||||
db.exec("UPDATE nonces SET expire = $1 WHERE nonce = $2", Time.utc(1990, 1, 1), nonce[0])
|
db.exec("UPDATE nonces SET expire = $1 WHERE nonce = $2", Time.utc(1990, 1, 1), nonce[0])
|
||||||
@ -92,18 +105,6 @@ def validate_request(token, session, request, key, db, locale = nil)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
scopes = token["scopes"].as_a.map { |v| v.as_s }
|
|
||||||
scope = "#{request.method}:#{request.path.lchop("/api/v1/auth/").lstrip("/")}"
|
|
||||||
|
|
||||||
if !scopes_include_scope(scopes, scope)
|
|
||||||
raise translate(locale, "Invalid scope")
|
|
||||||
end
|
|
||||||
|
|
||||||
expire = token["expire"]?.try &.as_i
|
|
||||||
if expire.try &.< Time.utc.to_unix
|
|
||||||
raise translate(locale, "Token is expired, please try again")
|
|
||||||
end
|
|
||||||
|
|
||||||
return {scopes, expire, token["signature"].as_s}
|
return {scopes, expire, token["signature"].as_s}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user