Skip to content

Commit

Permalink
Merge pull request #241 from guardian/remove-deprecated-jjwt-methods
Browse files Browse the repository at this point in the history
Avoid deprecated `jjwt` methods for signing & parsing
  • Loading branch information
rtyley committed Jul 12, 2024
2 parents f4097ac + e9f2e6b commit 768d4bc
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 13 deletions.
31 changes: 19 additions & 12 deletions play-v29/src/main/scala/com/gu/googleauth/auth.scala
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
package com.gu.googleauth

import java.math.BigInteger
import java.nio.charset.StandardCharsets.UTF_8
import java.security.SecureRandom
import java.time.{Clock, Duration}
import java.util.{Base64, Date}
import com.gu.googleauth.AntiForgeryChecker._
import com.gu.play.secretrotation.DualSecretTransition.InitialSecret
import com.gu.play.secretrotation.SnapshotProvider
import io.jsonwebtoken
import io.jsonwebtoken.SignatureAlgorithm.HS256
import io.jsonwebtoken._
import play.api.Logging
import play.api.http.HeaderNames.USER_AGENT
import play.api.http.HttpConfiguration
import play.api.libs.json.JsValue
import play.api.libs.ws.WSBodyWritables._
import play.api.libs.ws.{WSClient, WSResponse}
import play.api.mvc.Results.Redirect
import play.api.mvc.{RequestHeader, Result}

import java.math.BigInteger
import java.nio.charset.StandardCharsets.UTF_8
import java.security.SecureRandom
import java.time.{Clock, Duration}
import java.util.Date
import javax.crypto.spec.SecretKeySpec
import scala.concurrent.{ExecutionContext, Future}
import scala.language.postfixOps
import scala.util.{Failure, Success, Try}
import play.api.libs.ws.WSBodyWritables._

/**
* The configuration class for Google authentication
Expand Down Expand Up @@ -94,8 +96,13 @@ case class AntiForgeryChecker(
sessionIdKeyName: String = "play-googleauth-session-id"
) extends Logging {

private def base64EncodedSecretFrom(secret: String): String =
Base64.getEncoder.encodeToString(secret.getBytes(UTF_8))
/**
* This method is used, rather than the jjwt recommendation `Keys.hmacShaKeyFor(str)`, because that method would
* introduce new behaviour where the choice of signature algorithm depends on the size of the secret - to maintain
* consistency with earlier versions of play-googleauth, we fix the algorithm to the one provided in the
* AntiForgeryChecker constructor.
*/
private def keyFor(secret: String) = new SecretKeySpec(secret.getBytes(UTF_8), signatureAlgorithm.getJcaName)

def ensureUserHasSessionId(t: String => Future[Result])(implicit request: RequestHeader, ec: ExecutionContext):Future[Result] = {
val sessionId = request.session.get(sessionIdKeyName).getOrElse(generateSessionId())
Expand All @@ -106,7 +113,7 @@ case class AntiForgeryChecker(
def generateToken(sessionId: String)(implicit clock: Clock = Clock.systemUTC) : String = Jwts.builder()
.setExpiration(Date.from(clock.instant().plusSeconds(60)))
.claim(SessionIdJWTClaimPropertyName, sessionId)
.signWith(signatureAlgorithm, base64EncodedSecretFrom(secretsProvider.snapshot().secrets.active))
.signWith(keyFor(secretsProvider.snapshot().secrets.active), signatureAlgorithm)
.compact()

def checkChoiceOfSigningAlgorithm(claims: Jws[Claims]): Try[Unit] =
Expand All @@ -130,11 +137,11 @@ case class AntiForgeryChecker(
} yield ()

private def parseJwtClaimsFrom(oauthAntiForgeryState: String) = secretsProvider.snapshot().decode[Try[Jws[Claims]]]({
sc => Try(Jwts.parser().setSigningKey(base64EncodedSecretFrom(sc)).parseClaimsJws(oauthAntiForgeryState))
sc => Try(Jwts.parserBuilder().setSigningKey(keyFor(sc)).build().parseClaimsJws(oauthAntiForgeryState))
}, conclusiveDecode = {
case Failure(_: SignatureException) => false // signature doesn't match this secret, try a different one
case Failure(_: jsonwebtoken.security.SignatureException) => false // signature doesn't match this secret, try a different one
case _ => true
}).getOrElse(Failure(new SignatureException("OAuth anti-forgery state doesn't have a valid signature")))
}).getOrElse(Failure(new jsonwebtoken.security.SignatureException("OAuth anti-forgery state doesn't have a valid signature")))
}

object AntiForgeryChecker {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package com.gu.googleauth

import com.gu.play.secretrotation.DualSecretTransition.{InitialSecret, TransitioningSecret}
import io.jsonwebtoken.SignatureAlgorithm.{HS256, HS384}
import io.jsonwebtoken.{ExpiredJwtException, SignatureException, UnsupportedJwtException}
import io.jsonwebtoken.security.SignatureException
import io.jsonwebtoken.{ExpiredJwtException, UnsupportedJwtException}
import org.scalatest.TryValues
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
Expand Down

0 comments on commit 768d4bc

Please sign in to comment.