From 0d536d11e3d816802f4e6c569ef56d43140710aa Mon Sep 17 00:00:00 2001 From: leonklingele Date: Mon, 2 Mar 2020 17:04:36 +0100 Subject: [PATCH] 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. --- src/invidious/helpers/tokens.cr | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/invidious/helpers/tokens.cr b/src/invidious/helpers/tokens.cr index 30f7d4f4..0b609e80 100644 --- a/src/invidious/helpers/tokens.cr +++ b/src/invidious/helpers/tokens.cr @@ -1,3 +1,5 @@ +require "crypto/subtle" + def generate_token(email, scopes, expire, key, db) 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) @@ -76,14 +78,25 @@ def validate_request(token, session, request, key, db, locale = nil) raise translate(locale, "Hidden field \"token\" is a required field") end - if token["signature"] != sign_token(key, token) - raise translate(locale, "Invalid signature") + expire = token["expire"]?.try &.as_i + if expire.try &.< Time.utc.to_unix + raise translate(locale, "Token is expired, please try again") end if token["session"] != session raise translate(locale, "Erroneous token") 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 nonce[1] > Time.utc 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 - 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} end