Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RSA signature verification #22

Merged
merged 1 commit into from
May 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version = 1.6.0-RC3

maxColumn = 95

align = more
//align.openParenCallSite = false
newlines.alwaysBeforeTopLevelStatements = true
rewrite.rules = [
RedundantBraces,
RedundantParens,
SortModifiers,
PreferCurlyFors,
SortImports
]

//verticalMultilineAtDefinitionSite = true
verticalMultiline.atDefnSite = true
verticalMultiline.newlineAfterOpenParen = true
verticalMultiline.newlineBeforeImplicitKW = true
verticalMultiline.excludeDanglingParens = []
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Extensible JOSE library for Scala.
> Not yet implemented:
> * JWK serialization
> * JWE
> * RSA
> * RSA signing (RSA signature verification is supported)
> * Less common key sizes for ECDSA
> * Custom JOSE header parameters (custom JWT claims are supported)

Expand Down
29 changes: 29 additions & 0 deletions jose/src/black/door/jose/jwa/RSAlg.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package black.door.jose.jwa

import java.nio.charset.StandardCharsets
import java.security.Signature

import black.door.jose.jwk.RsaPublicKey

sealed case class RSAlg(hashBits: Int) extends SignatureAlgorithm {
val alg = s"RS$hashBits"
val jcaSignature = Signature.getInstance(s"SHA${hashBits}withRSA")

val validate = {
case (key: RsaPublicKey, header, signingInput, signature) if header.alg == alg =>
val veri = jcaSignature
veri.initVerify(key.toJcaPublicKey)
veri.update(signingInput.getBytes(StandardCharsets.US_ASCII))
veri.verify(signature)
}

val sign = PartialFunction.empty
}

object RSAlgs {
val RS256 = RSAlg(256)
val RS384 = RSAlg(384)
val RS512 = RSAlg(512)

val all = List(RS256, RS384, RS512)
}
2 changes: 1 addition & 1 deletion jose/src/black/door/jose/jwa/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ package object jwa {
}

object SignatureAlgorithms {
val all: Seq[SignatureAlgorithm] = ES256 +: HSAlgs.all
val all: Seq[SignatureAlgorithm] = HSAlgs.all ++ RSAlgs.all :+ ES256
}

// used with JWE alg
Expand Down
54 changes: 33 additions & 21 deletions jose/src/black/door/jose/jwk/EcJwk.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ trait EcPublicKey extends EcJwk with PublicJwk {

protected def spec: ECParameterSpec
lazy val toJcaPublicKey = {
val publicKeySpec = new ECPublicKeySpec(new ECPoint(x.bigInteger, y.bigInteger), spec)
val publicKeySpec =
new ECPublicKeySpec(new ECPoint(x.bigInteger, y.bigInteger), spec)
val keyFactory = KeyFactory.getInstance("EC")
keyFactory.generatePublic(publicKeySpec).asInstanceOf[jECPublicKey]
}
Expand All @@ -24,11 +25,12 @@ trait EcPublicKey extends EcJwk with PublicJwk {
trait EcPrivateKey extends EcJwk with PrivateJwk {
val kty = "EC"
def d: BigInt
final def eccPrivateKey = d

protected def spec: ECParameterSpec
lazy val toJcaPrivateKey: jECPrivateKey = {
val privateKeySpec = new ECPrivateKeySpec(d.bigInteger, spec)
val keyFactory = KeyFactory.getInstance("EC")
val keyFactory = KeyFactory.getInstance("EC")
keyFactory.generatePrivate(privateKeySpec).asInstanceOf[jECPrivateKey]
}
}
Expand All @@ -41,46 +43,56 @@ trait EcKeyPair extends EcPublicKey with EcPrivateKey {
}

case class P256PublicKey(
x: BigInt,
y: BigInt,
alg: Option[String] = None,
use: Option[String] = None,
key_ops: Option[Seq[String]] = None,
kid: Option[String] = None
) extends EcPublicKey {
x: BigInt,
y: BigInt,
alg: Option[String] = None,
use: Option[String] = None,
key_ops: Option[Seq[String]] = None,
kid: Option[String] = None
) extends EcPublicKey {
val crv = "P-256"

def spec = P256KeyPair.P256ParameterSpec
}

case class P256KeyPair(
d: BigInt,
x: BigInt,
y: BigInt,
alg: Option[String] = None,
use: Option[String] = None,
key_ops: Option[Seq[String]] = None,
kid: Option[String] = None
) extends EcKeyPair with EcPublicKey with EcPrivateKey {
d: BigInt,
x: BigInt,
y: BigInt,
alg: Option[String] = None,
use: Option[String] = None,
key_ops: Option[Seq[String]] = None,
kid: Option[String] = None
) extends EcKeyPair
with EcPublicKey
with EcPrivateKey {
val crv = "P-256"

lazy val toPublic = P256PublicKey(x, y, alg, use, key_ops, kid)
def spec = P256KeyPair.P256ParameterSpec
def spec = P256KeyPair.P256ParameterSpec
}

object P256KeyPair {

def generate = {
val keyGen = KeyPairGenerator.getInstance("EC")
keyGen.initialize(256, new SecureRandom)
val pair = keyGen.generateKeyPair
P256KeyPair(
pair.getPrivate.asInstanceOf[java.security.interfaces.ECPrivateKey].getS,
pair.getPublic.asInstanceOf[java.security.interfaces.ECPublicKey].getW.getAffineX,
pair.getPublic.asInstanceOf[java.security.interfaces.ECPublicKey].getW.getAffineY
pair.getPublic
.asInstanceOf[java.security.interfaces.ECPublicKey]
.getW
.getAffineX,
pair.getPublic
.asInstanceOf[java.security.interfaces.ECPublicKey]
.getW
.getAffineY
)
}

val P256ParameterSpec = new ECParameterSpec(
// format: off
new EllipticCurve(
new ECFieldFp(new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853951")),
new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853948"),
Expand All @@ -92,6 +104,6 @@ object P256KeyPair {
),
new BigInteger("115792089210356248762697446949407573529996955224135760342422259061068512044369"),
1
// format: on
)
}

27 changes: 27 additions & 0 deletions jose/src/black/door/jose/jwk/RsaJwk.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package black.door.jose.jwk
import java.security.KeyFactory
import java.security.spec.RSAPublicKeySpec

sealed trait RsaJwk extends Jwk

case class RsaPublicKey(
modulus: BigInt,
exponent: BigInt,
alg: Option[String] = None,
use: Option[String] = None,
key_ops: Option[Seq[String]] = None,
kid: Option[String] = None
) extends RsaJwk
with PublicJwk {
val kty = "RSA"

final def n = modulus
final def e = exponent

lazy val toJcaPublicKey = {
val spec = new RSAPublicKeySpec(modulus.bigInteger, exponent.bigInteger)
val factory = KeyFactory.getInstance("RSA")

factory.generatePublic(spec)
}
}
61 changes: 52 additions & 9 deletions jose/test/src/black/door/jose/jws/JwsSpec.scala
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
package black.door.jose.jws

import black.door.jose.jwk.OctJwk
import com.nimbusds.jose.crypto.{MACSigner, MACVerifier}
import black.door.jose.json.common._
import black.door.jose.json.playjson.JsonSupport._
import black.door.jose.jwk.{OctJwk, RsaPublicKey}
import com.nimbusds.jose.crypto.{MACSigner, MACVerifier, _}
import com.nimbusds.jose.jwk.gen.RSAKeyGenerator
import com.nimbusds.jose.{JWSAlgorithm, JWSHeader, JWSObject, Payload}
import org.scalatest.{EitherValues, Matchers, WordSpec}

import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
import black.door.jose.json.playjson.JsonSupport._
import black.door.jose.json.common._

class JwsSpec extends WordSpec with Matchers with EitherValues {
"HS signatures" should {

val hsKey = OctJwk.generate(256)

"sign correctly" in {
val jws = Jws(JwsHeader("HS256"), "test data".getBytes)
val jws = Jws(JwsHeader("HS256"), "test data".getBytes)
val compact = jws.sign(hsKey)

val verifier = new MACVerifier(hsKey.k)
JWSObject.parse(compact).verify(verifier) shouldBe true
}

"validate correctly" in {
val signer = new MACSigner(hsKey.k)
val signer = new MACSigner(hsKey.k)
val payload = "test data"
val jwsObj = new JWSObject(new JWSHeader(JWSAlgorithm.HS256), new Payload(payload))
val jwsObj = new JWSObject(new JWSHeader(JWSAlgorithm.HS256), new Payload(payload))
jwsObj.sign(signer)
val compact = jwsObj.serialize

Expand All @@ -36,11 +37,53 @@ class JwsSpec extends WordSpec with Matchers with EitherValues {
}

"reject invalid signatures" in {
val jws = Jws(JwsHeader("HS256"), "test data".getBytes)
val jws = Jws(JwsHeader("HS256"), "test data".getBytes)
val compact = jws.sign(hsKey)

val parsedJws = Await.result(Jws.validate[String](compact, OctJwk.generate(256)), Duration.Inf)
val parsedJws =
Await.result(Jws.validate[String](compact, OctJwk.generate(256)), Duration.Inf)
parsedJws shouldBe 'left
}
}

"RS signatures" should {
val rsKey = new RSAKeyGenerator(2048).generate

"validate correctly" in {
val signer = new RSASSASigner(rsKey)

val payload = "test data"
val jwsObject = new JWSObject(new JWSHeader(JWSAlgorithm.RS256), new Payload(payload))

jwsObject.sign(signer)
val compact = jwsObject.serialize

val blackdoorKey = RsaPublicKey(
rsKey.getModulus.decodeToBigInteger,
rsKey.getPublicExponent.decodeToBigInteger
)

val jws = Await.result(Jws.validate[String](compact, blackdoorKey), Duration.Inf)
jws.right.value.payload shouldBe payload
}

"reject invalid signatures" in {
val signer = new RSASSASigner(rsKey)

val payload = "test data"
val jwsObject = new JWSObject(new JWSHeader(JWSAlgorithm.RS256), new Payload(payload))

jwsObject.sign(signer)
val compact = jwsObject.serialize

val otherKey = new RSAKeyGenerator(2048).generate
val blackdoorKey = RsaPublicKey(
otherKey.getModulus.decodeToBigInteger,
otherKey.getPublicExponent.decodeToBigInteger
)

val jws = Await.result(Jws.validate[String](compact, blackdoorKey), Duration.Inf)
jws shouldBe 'left
}
}
}