forked from jmcardon/tsec
-
Notifications
You must be signed in to change notification settings - Fork 3
/
JWSMacHeader.scala
91 lines (82 loc) · 3.26 KB
/
JWSMacHeader.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package tsec.jws.mac
import cats.data.NonEmptyList
import cats.syntax.either._
import io.circe._
import io.circe.syntax._
import tsec.common._
import tsec.jws.JWSSerializer
import tsec.jws.header.JWSHeader
import tsec.jwt
import tsec.jwt.algorithms.JWTMacAlgo
import tsec.jwt.header.JWTtyp
/** A JWS header for JWT serialization.
* TODO: Crit logic on verification
*
* @param `type` the type of the content.
* In a less opinionated library, it could signal json serialization
* @param contentType The contentType, a non-recommended header
* @param critical The fields that _must_ be present
* @tparam A
*/
sealed abstract case class JWSMacHeader[A](
`type`: Option[JWTtyp] = Some(JWTtyp), //Type, which will almost always default to "JWT"
contentType: Option[String] = None, // Optional header, preferably not used
critical: Option[NonEmptyList[String]] = None //Headers not to ignore, they must be understood by the JWT implementation
)(implicit val algorithm: JWTMacAlgo[A])
extends JWSHeader[A] {
def toJsonString: String = jwt.JWTPrinter.print(this.asJson)
}
object JWSMacHeader {
def apply[A](implicit algo: JWTMacAlgo[A]): JWSMacHeader[A] =
new JWSMacHeader[A]() {}
implicit def encoder[A: JWTMacAlgo]: Encoder[JWSMacHeader[A]] {
def apply(a: JWSMacHeader[A]): Json
} = new Encoder[JWSMacHeader[A]] {
def apply(a: JWSMacHeader[A]): Json = Json.obj(
("typ", a.`type`.asJson),
("alg", a.algorithm.jwtRepr.asJson),
("cty", a.contentType.asJson),
("crit", a.critical.asJson)
)
}
/** For our decoder, we we know, a priori, the type of header we should have
* since we decode for some algorithm A, we avoid the vulnerability of
* parsing the algorithm, then verifying against it.
* That is, the server should know the algorithm before trying to deserialize it.
*
* @see [[https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/]]
*/
implicit def decoder[A: JWTMacAlgo]: Decoder[JWSMacHeader[A]] = new Decoder[JWSMacHeader[A]] {
def apply(c: HCursor): Either[DecodingFailure, JWSMacHeader[A]] =
c.downField("alg")
.as[String]
.map(JWTMacAlgo.fromString[A]) match {
case Right(opt) =>
opt match {
case Some(_) =>
for {
t <- c.downField("typ").as[Option[JWTtyp]]
cType <- c.downField("cty").as[Option[String]]
crit <- c.downField("crit").as[Option[NonEmptyList[String]]]
} yield
new JWSMacHeader[A](
`type` = t,
contentType = cType,
critical = crit
) {}
case None =>
Left(DecodingFailure("No algorithm found", Nil))
}
case Left(d) => Left(d)
}
}
implicit def genSerializer[A: JWTMacAlgo](
implicit d: Decoder[JWSMacHeader[A]],
e: Encoder[JWSMacHeader[A]]
): JWSSerializer[JWSMacHeader[A]] =
new JWSSerializer[JWSMacHeader[A]] {
def serializeToUtf8(body: JWSMacHeader[A]): Array[Byte] = jwt.JWTPrinter.print(body.asJson).utf8Bytes
def fromUtf8Bytes(array: Array[Byte]): Either[Error, JWSMacHeader[A]] =
io.circe.parser.decode[JWSMacHeader[A]](array.toUtf8String)
}
}