diff --git a/admin/admin.py b/admin/admin.py index 6560c815..ed13b693 100644 --- a/admin/admin.py +++ b/admin/admin.py @@ -959,7 +959,8 @@ def get_hmac(cfg, userId, objType, objId, perm): secret = settings["shared_secret"] now = 1000*int(time.time()) - message = "%s:%s:%d:%s:%d" % (userId, objType, objId, perm, now) + expiry = now+300 + message = "%s:%s:%d:%s:%d:timeout-token:%d" % (userId, objType, objId, perm, expiry, now) _hmac = hmac.new(str.encode(secret), str.encode(message), hashlib.sha256).hexdigest() ret = 'khmac:///sha-256;%s/%s' % (_hmac, message) diff --git a/app/commands/Console.scala b/app/commands/Console.scala index 560ee3ca..5012eaaf 100644 --- a/app/commands/Console.scala +++ b/app/commands/Console.scala @@ -658,7 +658,8 @@ class ConsoleImpl extends ConsoleInterface { case Some(time) => time case None => System.currentTimeMillis / 1000 } - val message = s"$userId:$objType:$objId:$perm:$now" + val expiry: Long = now + 300 + val message = s"$userId:$objType:$objId:$perm:$expiry:timeout-token:$now" val hmac = Crypto.hmac(shared_secret, message) val khmac = s"khmac:///sha-256;$hmac/$message" khmac diff --git a/app/controllers/BallotboxApi.scala b/app/controllers/BallotboxApi.scala index bdf9ae8f..e249d5ef 100644 --- a/app/controllers/BallotboxApi.scala +++ b/app/controllers/BallotboxApi.scala @@ -116,7 +116,8 @@ object BallotboxApi extends Controller with Response { val validated = vote.validate(pks, true, electionId, voterId) val result = DAL.votes.insertWithSession(validated) val now: Long = System.currentTimeMillis / 1000 - val message = s"$voterId:AuthEvent:$electionId:RegisterSuccessfulLogin:$now" + val expiry: Long = now + 300 + val message = s"$voterId:AuthEvent:$electionId:RegisterSuccessfulLogin:$expiry:timeout-token:$now" voteCallbackUrl.map { url => postVoteCallback( url diff --git a/app/controllers/ElectionsApi.scala b/app/controllers/ElectionsApi.scala index 443becde..5563d214 100644 --- a/app/controllers/ElectionsApi.scala +++ b/app/controllers/ElectionsApi.scala @@ -1540,7 +1540,8 @@ object ElectionsApi println(s"posting to $url") val userId: String = "admin" val now: Long = System.currentTimeMillis / 1000 - val timedAuth = s"$userId:AuthEvent:$electionId:Callback:$now" + val expiry: Long = now + 300 + val timedAuth = s"$userId:AuthEvent:$electionId:Callback:$expiry:timeout-token:$now" val hmac = Crypto.hmac(boothSecret, timedAuth) val khmac = s"khmac:///sha-256;$hmac/$timedAuth" val f = WS.url(url) diff --git a/app/utils/Actions.scala b/app/utils/Actions.scala index c7c23ae8..fd14af85 100644 --- a/app/utils/Actions.scala +++ b/app/utils/Actions.scala @@ -51,15 +51,17 @@ case class HMACActionHelper( val message = authorizationHeader.substring(slashPos + 1) val split = message.split(':') - if (split.length < 5) { + if (split.length < 7) { Logger.warn(s"Malformed authorization header") return Left(AuthErrorCodes.MALFORMED_USER_CREDENTIALS) } - val rcvUserId = split.slice(0, split.length - 4).mkString(":") - val rcvObjType = split(split.length - 4) - val rcvObjId = split(split.length - 3).toLong - val rcvPerm = split(split.length - 2) + val rcvUserId = split.slice(0, split.length - 6).mkString(":") + val rcvObjType = split(split.length - 6) + val rcvObjId = split(split.length - 5).toLong + val rcvPerm = split(split.length - 4) + val rcvExpiryTime = split(split.length - 3).toLong + val rcvToken = split(split.length - 2) val rcvTime = split(split.length - 1).toLong val now = new java.util.Date().getTime / 1000 val diff = now - rcvTime @@ -76,16 +78,19 @@ case class HMACActionHelper( // if the userId is the empty string we don't mind the user val userOk = (rcvUserId == userId || userId == "") + // Check if the current time is within the expiry timestamp + val withinExpiry = now <= rcvExpiryTime + // note that we can compare without doing contant time comparison received // strings because that's not critical for security, only hmac is - if(compareOk && (diff < expiry) && userOk && (rcvObjType == objType) && + if(compareOk && withinExpiry && userOk && (rcvObjType == objType) && (rcvObjId == objId) && permsOk) { return Right(true) } Logger.warn( - s"Failed to authorize hmac:\n\tauthorizationHeader=$authorizationHeader\tcompareOk=$compareOk\n\tdiff=$diff\n\texpiry=$expiry\n\tuserOk=$userOk\n\trcvObjType=$rcvObjType\n\tobjType=$objType\n\trcvObjId=$rcvObjId\n\tobjId=$objId\n\trcvPerm=$rcvPerm\n\tperm=$perm" + s"Failed to authorize hmac:\n\tauthorizationHeader=$authorizationHeader\tcompareOk=$compareOk\n\tdiff=$diff\n\texpiry=$expiry\n\tuserOk=$userOk\n\trcvObjType=$rcvObjType\n\tobjType=$objType\n\trcvObjId=$rcvObjId\n\tobjId=$objId\n\trcvPerm=$rcvPerm\n\tperm=$perm\n\tnow=$now\n\trcvExpiryTime=$rcvExpiryTime\n\t" ) return Left(AuthErrorCodes.INVALID_USER_CREDENTIALS) } diff --git a/test/ConsoleSpec.scala b/test/ConsoleSpec.scala index be410caa..e7385af1 100644 --- a/test/ConsoleSpec.scala +++ b/test/ConsoleSpec.scala @@ -210,7 +210,7 @@ class ConsoleSpec extends Specification with TestContexts val console = new ConsoleImpl() console.shared_secret = "" val khmac = console.get_khmac("user_id", "obj_type", 11, "perms", Some(22)) - khmac must be equalTo(s"khmac:///sha-256;1ba07fd2e1becf9adfe74b4f6e0814fb4c9af3e0a920e718cec287857b480856/user_id:obj_type:11:perms:22") + khmac must be equalTo(s"khmac:///sha-256;1ba07fd2e1becf9adfe74b4f6e0814fb4c9af3e0a920e718cec287857b480856/user_id:obj_type:11:perms:322:timeout-token:22") } } // get_khmac diff --git a/test/TestUtils.scala b/test/TestUtils.scala index 6481be02..ae98a4ab 100644 --- a/test/TestUtils.scala +++ b/test/TestUtils.scala @@ -110,7 +110,8 @@ trait TestContexts { def getAuth(userId: String, objType: String, objId: Long, perm: String) = { val authSecret = Play.current.configuration.getString("elections.auth.secret").get val time = (new java.util.Date().getTime / 1000) - val head = s"$userId:$objType:$objId:$perm:$time" + val expiry = time + 300 + val head = s"$userId:$objType:$objId:$perm:$expiry:timeout-token:$time" "khmac:///sha-256;" + Crypto.hmac(authSecret, head) + "/" + head }