Complete JWS implementation.

Adds support for signature and verification using RS256, RS384, RS512,
ES256, ES384, and ES512.
This commit is contained in:
Jonathan Bernard 2021-12-09 22:17:51 -06:00
parent 1c886e23b5
commit a87f92da2d
17 changed files with 607 additions and 97 deletions

View File

@ -28,4 +28,4 @@ type
A128GCM = "A128GCM", A192GCM = "A192GCM", A256GCM = "A256GCM"
EcCrv* = enum P256 = "P-256", P384 = "P-384", P521 = "P-521"
EcCurve* = enum P256 = "P-256", P384 = "P-384", P521 = "P-521"

View File

@ -1,4 +1,4 @@
import std/json, std/options, std/sequtils
import std/json, std/options, std/sequtils, std/strutils
import ../private/jsonutils
import ./jwa
@ -14,7 +14,7 @@ type
jwkopDeriveKey = "deriveKey", jwkopDeriveBits = "deriveBits"
EcPubKey = object of RootObj
crv*: string
crv*: EcCurve
x*: string
y*: Option[string]
@ -86,12 +86,12 @@ func `[]`*(jwk: JWK, key: string): Option[JsonNode] =
if jwk.json.hasKey(key): some(jwk.json[key])
else: none[JsonNode]()
## Public Parsing Functions
## ------------------------
# Public Parsing Functions
# ------------------------
func parseEcPubKey*(n: JsonNode): EcPubKey =
EcPubKey(
crv: n.reqStrVal("crv"),
crv: parseEnum[EcCurve](n.reqStrVal("crv")),
x: n.reqStrVal("x"),
y: n.optStrVal("y"))

View File

@ -1,4 +1,4 @@
import std/json, std/options, std/sequtils, std/strutils
import std/json, std/logging, std/options, std/sequtils, std/strutils
import ../private/crypto
import ../private/encoding
@ -71,6 +71,8 @@ proc initJWS*(b64: string): JWS =
raise newException(ValueError,
"invalid JWS, expected three parts but only found " & $parts.len)
debug "Base64url-encoded payload is:\n\t" & parts[1]
result = JWS(
payloadB64: parts[1],
signatures: @[ initJwsSignature(
@ -94,19 +96,23 @@ proc initJWS*(n: JsonNode): JWS =
payloadB64: n.reqStrVal("payload"),
signatures: @[initJwsSignature(n)])
proc sign*(payload: string, header: JoseHeader, key: JWK): JWS =
proc sign*(payload: string, header: JoseHeader, key: JWK, payloadIsB64Encoded = false): JWS =
## Create a JWS signing the given payload, which can be any arbitrary data.
## The returned JWS can be serialized with the compact serialization. All
## header data will be protected.
validateHeader(header)
let payloadB64 = b64UrlEncode(payload)
let payloadB64 = if payloadIsB64Encoded: payload
else: b64UrlEncode(payload)
let valueToSign = $header & "." & payloadB64
let signatureB64 =
b64UrlEncode(computeSignature(valueToSign, header.alg.get, key))
debug "Signed a value:\n\ttoSign : " & valueToSign &
"\n\tsignature: " & signatureB64
result = JWS(
payloadB64: payloadB64,
signatures: @[
@ -120,24 +126,30 @@ proc sign*(payload: string, header: JoseHeader, key: JWK): JWS =
proc sign*(claims: JwtClaims, header: JoseHeader, key: JWK): JWS =
## Create a JWS signing a set of JWT claims. The returned JWS will be a valid
## JWT.
sign($claims, header, key)
sign($claims, header, key, payloadIsB64Encoded = true)
proc sign*(
payload: string,
unprotected: JoseHeader,
protected: JoseHeader,
key: JWK): JWS =
key: JWK,
payloadIsB64Encoded = false): JWS =
## Create a JWS signing the given payload, which can be arbitrary data. The
## returned JWS can be serialized with the compact serialization.
let combinedHeader = combine(unprotected, protected)
validateHeader(combinedHeader)
let payloadB64 = b64UrlEncode(payload)
let payloadB64 = if payloadIsB64Encoded: payload
else: b64UrlEncode(payload)
let valueToSign = $protected & "." & payloadB64
let signatureB64 = b64UrlEncode(
computeSignature(valueToSign, combinedHeader.alg.get, key))
debug "Signed a value:\n\ttoSign : " & valueToSign &
"\n\tsignature: " & signatureB64
result = JWS(
payloadB64: payloadB64,
signatures: @[
@ -165,13 +177,16 @@ proc sign*(
let signatureB64 = b64UrlEncode(
computeSignature(valueToSign, combinedHeader.alg.get, key))
debug "Signed a value:\n\ttoSign : " & valueToSign &
"\n\tsignature: " & signatureB64
result = JWS(
payloadB64: jws.payloadB64,
signatures: jws.signatures &
@[initJwsSignature(
protected = protected,
header = unprotected,
signatureB64= signatureB64)])
signatureB64 = signatureB64)])
result.compactSerialization = none[string]()
@ -199,8 +214,14 @@ proc validate*(jws: JWS, alg: JwtAlgorithm, key: JWK, sigIdx = 0) =
raise newException(InvalidSignature,
"'" & $alg & "' is not a valid signing algorithm.")
let payload = $(jws[sigIdx].protected) & "." & jws.payloadB64
debug "Verifying a JWS:\n\tpayload : " & payload &
"\n\tsignature: " & jws[sigIdx].signatureB64
if not verifySignature(
payload = $(jws[sigIdx].protected) & "." & jws.payloadB64,
payload = payload,
signature = b64UrlDecode(jws[sigIdx].signatureB64),
alg = alg,
key = key):

View File

@ -1,10 +1,12 @@
import std/json, std/options, std/strutils, std/times
import ./claims, ./joseheader, ./jwe, ./jws
import ./claims, ./joseheader, ./jwa, ./jwe, ./jwk, ./jws, ./jwssig
export claims
type
InvalidToken* = object of CatchableError
JwtKind = enum jkJWE, jkJWS
JWT* = object
@ -20,27 +22,58 @@ func claims*(jwt: JWT): JwtClaims = jwt.claims
func header*(jwt: JWT): JoseHeader =
case jwt.kind:
of jkJWS: return jwt.jws.header
of jkJWS: return jwt.jws[0].protected
of jkJWE: return jwt.jwe.header
func `$`*(jwt: JWT): string =
result = jwt.header.rawB64 & "." & jwt.claims.rawB64 & "."
if jwt.signature.isSome: result &= jwt.signature.get
case jwt.kind:
of jkJWS:
result = jwt.header.rawB64 & "." & jwt.claims.rawB64 & "."
if jwt.jws.len > 0: result &= jwt.jws[0].signatureB64
of jkJWE:
result = jwt.header.rawB64 & "." & jwt.claims.rawB64 & "."
proc initJWT*(encoded: string): JWT =
let parts = encoded.split('.')
result = JWT(
signature: if parts.len > 2: some(parts[2])
else: none[string](),
header: initJoseHeader(parts[0]),
claims: initJwtClaims(parts[1]))
if parts.len == 3:
let jws = initJWS(encoded)
result = JWT(
kind: jkJWS,
claims: initJwtClaims(jws.payloadB64),
jws: jws)
else:
# TODO
raise newException(Exception, "not yet implemented")
proc toJWT*(encoded: string): JWT = initJWT(encoded)
proc createSignedJwt*(
header: JoseHeader,
claims: JwtClaims,
key: JWK
): JWT =
## Create a new JWT with the given header, claims, and signed with the given
## key.
proc initJWT*(header: JoseHeader, claims: JwtClaims): JWT =
result = JWT(
header: header,
kind: jkJWS,
claims: claims,
signature: none[string]())
jws: sign(claims, header, key))
proc validate*(jwt: JWT, sigAlg: JwtAlgorithm, key: JWK) =
case jwt.kind:
of jkJWS:
jwt.jws.validate(sigAlg, key)
of jkJWE:
raise newException(Exception, "JWE validation is not yet implemented")
proc tryValidate*(jwt: JWT, sigAlg: JwtAlgorithm, key: JWK): bool =
try:
jwt.validate(sigAlg, key)
return true
except: return false
# proc verify*(jwt: JWT): bool =

View File

@ -4,8 +4,9 @@ import bearssl
import ../jwt/jwa
import ../jwt/jwk
import ./crypto/hash
import ./crypto/ecdsa
import ./crypto/hmac
import ./crypto/rsa
proc validateAlgMatchesKey(alg: JwtAlgorithm, key: JWK) =
if key.alg.isSome and key.alg.get != $alg:
@ -16,13 +17,15 @@ proc validateAlgMatchesKey(alg: JwtAlgorithm, key: JWK) =
proc computeSignature*(
payload: string,
alg: JwtAlgorithm,
key: JWK
key: JWK | string
): string =
validateAlgMatchesKey(alg, key)
case alg:
of HS256, HS384, HS512: return hmac(alg, key, payload)
of HS256, HS384, HS512: return hmac(payload, alg, key)
of RS256, RS384, RS512: return rsaSign(payload, alg, key)
of ES256, ES384, ES512: return ecSign(payload, alg, key)
else:
raise newException(Exception,
"unsupported signature algorithm: " & $alg)
@ -38,8 +41,10 @@ proc verifySignature*(
case alg:
of HS256, HS384, HS512:
let hash = hmac(alg, key, payload)
let hash = hmac(payload, alg, key)
return hash == signature
of RS256, RS384, RS512: return rsaVerify(payload, signature, alg, key)
of ES256, ES384, ES512: return ecVerify(payload, signature, alg, key)
else:
raise newException(Exception,
"unsupported signature algorithm: " & $alg)

View File

@ -0,0 +1,146 @@
import std/options, std/tables
import bearssl
import ../../jwt/jwa
import ../../jwt/jwk
import ../encoding
import ./hash
type
EcPublicKeyObj = object
curve*: EcCurve
q*: string
bearKey: EcPublicKey
EcPrivateKeyObj = object
curve*: EcCurve
x*: string
bearKey: EcPrivateKey
func toBearSslCurveConst(curve: EcCurve): int32 =
result = case curve:
of P256: EC_secp256r1
of P384: EC_secp384r1
of P521: EC_secp521r1
func initEcPublicKeyObj(curve: EcCurve, q: string): EcPublicKeyObj =
result = EcPublicKeyObj(curve: curve, q: q)
result.bearKey.curve = curve.toBearSslCurveConst
result.bearKey.q = cast[ptr cuchar](result.q.cstring)
result.bearKey.qlen = q.len
func initEcPrivateKeyObj(curve: EcCurve, x: string): EcPrivateKeyObj =
result = EcPrivateKeyObj(curve: curve, x: x)
result.bearKey.curve = curve.toBearSslCurveConst
result.bearKey.x = cast[ptr cuchar](result.x.cstring)
result.bearKey.xlen = x.len
proc toEcPublicKey(jwk: JWK): EcPublicKeyObj =
## Convert an ECDSA public key in JWK format to the wrapper for BearSSL's
## ECPublicKey struct.
# Confusingly, the JWK standard and BearSSL use contradictory names for EC
# key components. In JWK parlance, x and y (optional) are the public key
# components. BearSSL only requires one public point, and calls this q.
let keyObj = if jwk.keyKind == EcPublic: jwk.ecPub
else: jwk.ecPrv
return initEcPublicKeyObj(
curve = keyObj.crv,
q = b64UrlDecode(keyObj.x))
proc toEcPrivateKey(jwk: JWK): EcPrivateKeyObj =
## Convert an ECDSA private key in JWK format to the wrapper for BearSSL's
## ECPrivateKey struct.
# Confusingly, the JWK standard and BearSSL use contradictory names for EC
# key components. In JWK parlance, d is the private key components. BearSSL
# calls this x.
return initEcPrivateKeyObj(
curve = jwk.ecPrv.crv,
x = b64UrlDecode(jwk.ecPrv.d))
proc getEcHashCfg(alg: JwtAlgorithm): HashCfg =
let hashAlg = case alg:
of ES256: SHA256
of ES384: SHA384
of ES512: SHA512
else: raise newException(ValueError,
"Unsupported ECDSA signature algorithm '" & $alg & "'")
result = HASHES[hashAlg]
proc bearEcSign(
message: string,
alg: JwtAlgorithm,
key: EcPrivateKeyObj
): string =
let hashCfg = getEcHashCfg(alg)
let hashed = hash(message, hashcfg.alg)
let ecSignImpl = ecdsaSignRawGetDefault()
# The signature fucntion will return the length of the signature in bytes,
# but we need to pre-allocate the space for it to write into. Per BearSSL
# documentation, the maximum signature size will by 132 bytes (see
# https://bearssl.org/apidoc/bearssl__ec_8h.html#ab7154b0c899ceb3af062c69016258667)
result = newString(132)
let sigLen = ecSignImpl(
addr ecAllM15,
hashCfg.vtable,
cast[ptr cuchar](unsafeAddr hashed[0]),
unsafeAddr key.bearKey,
cast[ptr cuchar](addr result[0]))
if sigLen == 0: raise newException(Exception, "EC signature failed")
result.setLen(sigLen)
proc bearEcVerify(
message, signature: string,
alg: JwtAlgorithm,
key: EcPublicKeyObj
): bool =
let hashCfg = getEcHashCfg(alg)
let hashed = hash(message, hashCfg.alg)
let ecVerifyImpl = ecdsaVrfyRawGetDefault()
let resultCode = ecVerifyImpl(
addr ecAllM15,
cast[ptr cuchar](unsafeAddr hashed[0]),
hashed.len,
unsafeAddr key.bearKey,
cast[ptr cuchar](unsafeAddr signature[0]),
signature.len)
result = resultCode == 1
proc ecSign*(message: string, alg: JwtAlgorithm, key: JWK): string =
## Sign a message using the ECDSA algorithm.
##
## *key* is expected to be a `JWK <,./../jwt/jwk.html#JWK>`_
if key.keyKind != JwkKeyType.EcPrivate:
raise newException(ValueError,
$alg & " requires an ECDSA private key (\"type\"=\"EC\"), not a " &
"\"typ\"=\"" & $(key.keyKind) & "\" key.")
return bearEcSign(message, alg, toEcPrivateKey(key))
proc ecVerify*(message, signature: string, alg: JwtAlgorithm, key: JWK): bool =
## Verify the signature for a message using ECDSA.
##
## *key* is expected to be a `JWK <../../jwt/jwk.html#JWK>`_
if key.keyKind != JwkKeyType.EcPrivate and
key.keyKind != JwkKeyType.EcPublic:
raise newException(ValueError,
$alg & " requires an ECDSA private key (\"type\"=\"EC\"), not a " &
"\"typ\"=\"" & $(key.keyKind) & "\" key.")
return bearEcVerify(message, signature, alg, toEcPublicKey(key))

View File

@ -5,35 +5,40 @@ type
HashAlgorithm* = enum SHA1, SHA256, SHA384, SHA512
HashCfg* = object
alg*: HashAlgorithm
vtable*: ptr HashClass
oid*: cstring
size*: int
let HASHES* = newTable[HashAlgorithm, HashCfg]([
(SHA1, HashCfg(
alg: SHA1,
vtable: addr sha1Vtable,
oid: HASH_OID_SHA1,
size: sha1Size)),
(SHA256, HashCfg(
alg: SHA256,
vtable: addr sha256Vtable,
oid: HASH_OID_SHA256,
size: sha256Size)),
(SHA384, HashCfg(
alg: SHA384,
vtable: addr sha384Vtable,
oid: HASH_OID_SHA384,
size: sha384Size)),
(SHA512, HashCfg(
alg: SHA512,
vtable: addr sha512Vtable,
oid: HASH_OID_SHA512,
size: sha512Size)),
])
proc hash*(alg: HashAlgorithm, data: string): string =
proc hash*(data: string, alg: HashAlgorithm): string =
var ctx: HashCompatContext
var pCtx = cast[ptr ptr HashClass](addr ctx)
let hashCfg = HASHES[alg]
result = newString(hashCfg.size)
hashCfg.vtable.init(pCtx)
hashCfg.vtable.update(pCtx, unsafeAddr data, data.len)
hashCfg.vtable.update(pCtx, unsafeAddr data[0], data.len)
hashCfg.vtable.output(pCtx, addr result[0])

View File

@ -7,7 +7,7 @@ import ../../jwt/jwk
import ../encoding
proc bearHMAC(alg: JwtAlgorithm, key, toSign: string): string =
proc bearHMAC(message: string, alg: JwtAlgorithm, key: string): string =
var vtable: ptr HashClass
var hashSize: int
@ -29,19 +29,19 @@ proc bearHMAC(alg: JwtAlgorithm, key, toSign: string): string =
hmacKeyInit(addr keyCtx, vtable, key.cstring, key.len)
hmacInit(addr hmacCtx, addr keyCtx, 0)
hmacUpdate(addr hmacCtx, toSign.cstring, toSign.len)
hmacUpdate(addr hmacCtx, message.cstring, message.len)
let resLen = hmacSize(addr hmacCtx)
result = newString(resLen)
discard hmacOut(addr hmacCtx, addr result[0])
proc hmac*(alg: JwtAlgorithm, key, toSign: string): string =
return bearHMAC(alg, key, toSign)
proc hmac*(message: string, alg: JwtAlgorithm, key: string): string =
return bearHMAC(message, alg, key)
proc hmac*(alg: JwtAlgorithm, key: JWK, toSign: string): string =
proc hmac*(message: string, alg: JwtAlgorithm, key: JWK): string =
if key.keyKind != Octet:
raise newException(ValueError,
$alg & " requires an octet key (\"typ\"=\"oct\"), not a \"typ\"=\"" &
$(key.keyKind) & "\" key.")
return bearHMAC(alg, b64UrlDecode(key.octKey.k), toSign)
return bearHMAC(message, alg, b64UrlDecode(key.octKey.k))

View File

@ -1,23 +1,151 @@
import std/options, std/tables
import bearssl
import ../../jwt/jwa
import ../../jwt/jwk
proc bearRsaSign(
alg: JwtAlgorithm,
key: RsaPrivateKey,
toSign: string
): string =
import ../encoding
var hashVtable: ptr HashClass
var hashOID: cstring
import ./hash
case alg:
of RS256: hashVtable = addr sha256Vtable; hashOID = HASH_OID_SHA256
of RS384: hashVtable = addr sha384Vtable; hashOID = HASH_OID_SHA384
of RS512: hashVtable = addr sha512Vtable; hashOID = HASH_OID_SHA512
type
RsaPublicKeyObj = object
n*, e*: string
bearKey: RsaPublicKey
RsaPrivateKeyObj = object
p*, q*, dp*, dq*, iq*: string
bearKey*: RsaPrivateKey
func initRsaPublicKeyObj(n, e: string): RsaPublicKeyObj =
result = RsaPublicKeyObj(n: n, e: e)
result.bearKey.n = cast[ptr cuchar](result.n.cstring)
result.bearKey.nlen = result.n.len
result.bearKey.e = cast[ptr cuchar](result.e.cstring)
result.bearKey.elen = result.e.len
func initRsaPrivateKeyObj(nBitLen: int, p, q, dp, dq, iq: string): RsaPrivateKeyObj =
result = RsaPrivateKeyObj(p: p, q: q, dp: dp, dq: dq, iq: iq)
result.bearKey.nBitLen = cast[uint32](nBitLen)
result.bearKey.p = cast[ptr cuchar](result.p.cstring)
result.bearKey.plen = result.p.len
result.bearKey.q = cast[ptr cuchar](result.q.cstring)
result.bearKey.qlen = result.q.len
result.bearKey.dp = cast[ptr cuchar](result.dp.cstring)
result.bearKey.dplen = result.dp.len
result.bearKey.dq = cast[ptr cuchar](result.dq.cstring)
result.bearKey.dqlen = result.dq.len
result.bearKey.iq = cast[ptr cuchar](result.iq.cstring)
result.bearKey.iqlen = result.iq.len
proc toRsaPublicKey(jwk: JWK): RsaPublicKeyObj =
## Convert an RSA public key in JWK format to the wrapper for BearSSL's
## RsaPublicKey struct.
return initRsaPublicKeyObj(
n = b64UrlDecode(jwk.rsaPub.n),
e = b64UrlDecode(jwk.rsaPub.e))
proc toRsaPrivateKey(jwk: JWK): RsaPrivateKeyObj =
## Convert an RSA private key in JWK format to the wrapper for BearSSL's
## RsaPrivateKey struct.
# TODO: JWS spec only requires a private key to have n, e, and d, as the
# remainder can be computed form these (p, q, dp, dq, and qi). BearSSL
# requires p, q, dp, dq, and qi (it calls iq). Because of this, we currently
# require all values to be present in JWKs for RSA privat keys. We should add
# the logic to compute the missing values to fully support the JWS spec.
#
# We also do not currently support keys with more than two prime factors.
let sk = jwk.rsaPrv
if sk.p.isNone or sk.q.isNone or sk.dp.isNone or sk.dq.isNone or sk.qi.isNone:
raise newException(ValueError,
"RSA private key must have values for: n, e, d, p, q, dp, dq, and qi")
let n = b64UrlDecode(sk.n)
return initRsaPrivateKeyObj(
nBitLen = n.len * 8,
p = b64UrlDecode(sk.p.get),
q = b64UrlDecode(sk.q.get),
dp = b64UrlDecode(sk.dp.get),
dq = b64UrlDecode(sk.dq.get),
iq = b64UrlDecode(sk.qi.get))
proc getRsaHashCfg(alg: JwtAlgorithm): HashCfg =
let hashAlg = case alg:
of RS256: SHA256
of RS384: SHA384
of RS512: SHA512
else: raise newException(ValueError,
"Unsupported RSA signature algorithm '" & $alg & "'")
# TODO
""
result = HASHES[hashAlg]
proc bearRsaSign(
message: string,
alg: JwtAlgorithm,
key: RsaPrivateKeyObj
): string =
let hashCfg = getRsaHashCfg(alg)
let hashed = hash(message, hashCfg.alg)
let rsaSignImpl = rsaPkcs1SignGetDefault()
result = newString((key.bearKey.nBitLen + 7) div 8)
let errCode = rsaSignImpl(
cast[ptr cuchar](hashCfg.oid),
cast[ptr cuchar](unsafeAddr hashed[0]),
hashed.len,
unsafeAddr key.bearKey,
cast[ptr cuchar](addr result[0]))
if errCode != 1: raise newException(Exception, "RSA signature failed")
proc bearRsaVerify(
message, signature: string,
alg: JwtAlgorithm,
key: RsaPublicKeyObj
): bool =
let hashCfg = getRsaHashCfg(alg)
let hashed = hash(message, hashCfg.alg)
let rsaVerifyImpl = rsaPkcs1VrfyGetDefault()
var recoveredHash = newString(hashCfg.size)
let errCode = rsaVerifyImpl(
cast[ptr cuchar](unsafeAddr signature[0]),
signature.len,
cast[ptr cuchar](hashCfg.oid),
hashed.len,
unsafeAddr key.bearKey,
cast[ptr cuchar](addr recoveredHash[0]))
if errCode != 1: return false
return hashed == recoveredHash
proc rsaSign*(message: string, alg: JwtAlgorithm, key: JWK): string =
## Sign a message using the RSA PKCS#1 v1.5 algorithm.
##
## *key* is expected to be a `JWK <../../jwt/jwk.html#JWK>`_
if key.keyKind != JwkKeyType.RsaPrivate:
raise newException(ValueError,
$alg & " requires an RSA Private key (\"type\"=\"RSA\"), not a " &
"\"typ\"=\"" & $(key.keyKind) & "\" key.")
return bearRsaSign(message, alg, toRsaPrivateKey(key))
proc rsaVerify*(message, signature: string; alg: JwtAlgorithm, key: JWK): bool =
## Verify the signature for a message using PKCS#1 v1.5 algorithm.
##
## *key* is expected to be a `JWK <../../jwt/jwk.html#JWK>`_
if key.keyKind != JwkKeyType.RsaPublic:
raise newException(ValueError,
$alg & " requires an RSA Public key (\"type\"=\"RSA\"), not a " &
"\"typ\"=\"" & $(key.keyKind) & "\" key.")
return bearRsaVerify(message, signature, alg, toRsaPublicKey(key))

View File

@ -1,30 +1,85 @@
## Example JWT from RFC 7519 (JWT) section 3.1
const rfc7519Sec3_1ExampleHeaderB64* = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9"
const rfc7519Sec3_1ExampleHeaderBytes* = [123, 34, 116, 121, 112, 34, 58, 34, 74, 87, 84, 34, 44, 13, 10, 32, 34, 97, 108, 103, 34, 58, 34, 72, 83, 50, 53, 54, 34, 125]
const rfc7519Sec3_1ExampleClaimsB64* = "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
const rfc7519Sec3_1ExampleClaimsBytes* = [123, 34, 105, 115, 115, 34, 58, 34, 106, 111, 101, 34, 44, 13, 10, 32, 34, 101, 120, 112, 34, 58, 49, 51, 48, 48, 56, 49, 57, 51, 56, 48, 44, 13, 10, 32, 34, 104, 116, 116, 112, 58, 47, 47, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 105, 115, 95, 114, 111, 111, 116, 34, 58, 116, 114, 117, 101, 125]
const rfc7519_S31_HeaderB64* = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9"
const rfc7519_S31_HeaderBytes* = [123, 34, 116, 121, 112, 34, 58, 34, 74, 87, 84, 34, 44, 13, 10, 32, 34, 97, 108, 103, 34, 58, 34, 72, 83, 50, 53, 54, 34, 125]
const rfc7519_S31_ClaimsB64* = "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
const rfc7519_S31_ClaimsBytes* = [123, 34, 105, 115, 115, 34, 58, 34, 106, 111, 101, 34, 44, 13, 10, 32, 34, 101, 120, 112, 34, 58, 49, 51, 48, 48, 56, 49, 57, 51, 56, 48, 44, 13, 10, 32, 34, 104, 116, 116, 112, 58, 47, 47, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 105, 115, 95, 114, 111, 111, 116, 34, 58, 116, 114, 117, 101, 125]
## Example JWS from RFC 7515 (JWS) Appendix A.1
const rfc7515A1ExampleHeaderB64* = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9"
const rfc7515A1ExampleHeaderBytes* = [123, 34, 116, 121, 112, 34, 58, 34, 74, 87, 84, 34, 44, 13, 10, 32, 34, 97, 108, 103, 34, 58, 34, 72, 83, 50, 53, 54, 34, 125]
const rfc7515A1ExapmleClaimsB64* = "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
const rfc7515A1ExampleClaimsBytes* = [123, 34, 105, 115, 115, 34, 58, 34, 106, 111, 101, 34, 44, 13, 10, 32, 34, 101, 120, 112, 34, 58, 49, 51, 48, 48, 56, 49, 57, 51, 56, 48, 44, 13, 10, 32, 34, 104, 116, 116, 112, 58, 47, 47, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 105, 115, 95, 114, 111, 111, 116, 34, 58, 116, 114, 117, 101, 125]
const rfc7515A1ExampleSigB64* = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
const rfc7515A1ExampleSigBytes* = [116, 24, 223, 180, 151, 153, 224, 37, 79, 250, 96, 125, 216, 173, 187, 186, 22, 212, 37, 77, 105, 214, 191, 240, 91, 88, 5, 88, 83, 132, 141, 121]
const rfc7515A1ExampleJwkStr* = """{
## Examples from RFC 7515 (JWS
const rfc7515_A1_HeaderB64* = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9"
const rfc7515_A1_HeaderBytes* = [123, 34, 116, 121, 112, 34, 58, 34, 74, 87, 84, 34, 44, 13, 10, 32, 34, 97, 108, 103, 34, 58, 34, 72, 83, 50, 53, 54, 34, 125]
const rfc7515_A1_ClaimsB64* = "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
const rfc7515_A1_ClaimsBytes* = [123, 34, 105, 115, 115, 34, 58, 34, 106, 111, 101, 34, 44, 13, 10, 32, 34, 101, 120, 112, 34, 58, 49, 51, 48, 48, 56, 49, 57, 51, 56, 48, 44, 13, 10, 32, 34, 104, 116, 116, 112, 58, 47, 47, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 105, 115, 95, 114, 111, 111, 116, 34, 58, 116, 114, 117, 101, 125]
const rfc7515_A1_SigB64* = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
const rfc7515_A1_SigBytes* = [116, 24, 223, 180, 151, 153, 224, 37, 79, 250, 96, 125, 216, 173, 187, 186, 22, 212, 37, 77, 105, 214, 191, 240, 91, 88, 5, 88, 83, 132, 141, 121]
const rfc7515_A1_Jwt* = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
const rfc7515_A1_JwkStr* = """{
"kty":"oct",
"k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"
}"""
const rfc7515A1ExampleJwt* = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
const rfc7515_A2_HeaderB64* = "eyJhbGciOiJSUzI1NiJ9"
const rfc7515_A2_HeaderBytes* = [123, 34, 97, 108, 103, 34, 58, 34, 82, 83, 50, 53, 54, 34, 125]
const rfc7515_A2_HeaderStr* = """{"alg":"RS256"}"""
const rfc7515_A2_ClaimsB64* = "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
const rfc7515_A2_SigB64* = "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw"
const rfc7515_A2_Jwt* = "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw"
const rfc7515_A2_JwkPrvStr* = """{
"kty":"RSA",
"n":"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddxHmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMsD1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSHSXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ",
"e":"AQAB",
"d":"Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97IjlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYTCBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLhBOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ",
"p":"4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdiYrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPGBY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc",
"q":"uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxaewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc",
"dp":"BwKfV3Akq5_MFZDFZCnW-wzl-CCo83WoZvnLQwCTeDv8uzluRSnm71I3QCLdhrqE2e9YkxvuxdBfpT_PI7Yz-FOKnu1R6HsJeDCjn12Sk3vmAktV2zb34MCdy7cpdTh_YVr7tss2u6vneTwrA86rZtu5Mbr1C1XsmvkxHQAdYo0",
"dq":"h_96-mK1R_7glhsum81dZxjTnYynPbZpHziZjeeHcXYsXaaMwkOlODsWa7I9xXDoRwbKgB719rrmI2oKr6N3Do9U0ajaHF-NKJnwgjMd2w9cjz3_-kyNlxAr2v4IKhGNpmM5iIgOS1VZnOZ68m6_pbLBSp3nssTdlqvd0tIiTHU",
"qi":"IYd7DHOhrWvxkwPQsRM2tOgrjbcrfvtQJipd-DlcxyVuuM9sQLdgjVk2oy26F0EmpScGLq2MowX7fhd_QJQ3ydy5cY7YIBi87w93IKLEdfnbJtoOPLUW0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U"
}"""
const rfc7515_A2_JwkPubStr* = """{
"kty":"RSA",
"n":"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddxHmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMsD1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSHSXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ",
"e":"AQAB"
}"""
const rfc7515_A3_HeaderB64* = "eyJhbGciOiJFUzI1NiJ9"
const rfc7515_A3_HeaderBytes* = [123, 34, 97, 108, 103, 34, 58, 34, 69, 83, 50, 53, 54, 34, 125]
const rfc7515_A3_HeaderStr* = """{"alg":"ES256"}"""
const rfc7515_A3_ClaimsB64* = "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
const rfc7515_A3_SignB64* = "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
const rfc7515_A3_Jwt* = "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
const rfc7515_A3_JwkStr* = """{
"kty":"EC",
"crv":"P-256",
"x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
"y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
"d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI"
}"""
const rfc7515_A4_HeaderB64* = "eyJhbGciOiJFUzUxMiJ9"
const rfc7515_A4_HeaderBytes* = [123, 34, 97, 108, 103, 34, 58, 34, 69, 83, 53, 49, 50, 34, 125]
const rfc7515_A4_HeaderStr* = """{"alg":"ES512"}"""
const rfc7515_A4_PayloadB64* = "UGF5bG9hZA"
const rfc7515_A4_PayloadBytes* = [80, 97, 121, 108, 111, 97, 100]
const rfc7515_A4_SignB64* = "AdwMgeerwtHoh-l192l60hp9wAHZFVJbLfD_UxMi70cwnZOYaRI1bKPWROc-mZZqwqT2SI-KGDKB34XO0aw_7XdtAG8GaSwFKdCAPZgoXD2YBJZCPEX3xKpRwcdOO8KpEHwJjyqOgzDO7iKvU8vcnwNrmxYbSW9ERBXukOXolLzeO_Jn"
const rfc7515_A4_Jwt* = "eyJhbGciOiJFUzUxMiJ9.UGF5bG9hZA.AdwMgeerwtHoh-l192l60hp9wAHZFVJbLfD_UxMi70cwnZOYaRI1bKPWROc-mZZqwqT2SI-KGDKB34XO0aw_7XdtAG8GaSwFKdCAPZgoXD2YBJZCPEX3xKpRwcdOO8KpEHwJjyqOgzDO7iKvU8vcnwNrmxYbSW9ERBXukOXolLzeO_Jn"
const rfc7515_A4_JwkStr* = """{
"kty":"EC",
"crv":"P-521",
"x":"AekpBQ8ST8a8VcfVOTNl353vSrDCLLJXmPk06wTjxrrjcBpXp5EOnYG_NjFZ6OvLFV1jSfS9tsz4qUxcWceqwQGk",
"y":"ADSmRA43Z1DSNx_RvcLI87cdL07l6jQyyBXMoxVg_l2Th-x3S1WDhjDly79ajL4Kkd0AZMaZmh9ubmf63e3kyMj2",
"d":"AY5pb7A0UFiB3RELSD64fTLOSV_jazdF7fLYyuTw8lOfRhWg6Y6rUrPAxerEzgdRhajnu0ferB0d53vM9mE15j2C"
}"""
# Note: due to the way Nim handles newline literals, this does not exactly
# match the data from the RFC. For cases where we want exact matches (to
# validate B64 conversion, signature, etc.) use either the Base64 version or
# the byte arrays above.
const rfc7519Sec3_1ExampleHeaderStr* = """{"typ":"JWT", "alg":"HS256"}"""
const rfc7519Sec3_1ExampleClaimsStr* = """{"iss":"joe", "exp":1300819380, "http://example.com/is_root":true}"""
const rfc7515A1ExampleHeaderStr* = """{"typ":"JWT", "alg":"HS256"}"""
const rfc7515A1ExampleClaimsStr* = """{"iss":"joe", "exp":1300819380, "http://example.com/is_root":true}"""
const rfc7519_S31_HeaderStr* = """{"typ":"JWT", "alg":"HS256"}"""
const rfc7519_S31_ClaimsStr* = """{"iss":"joe", "exp":1300819380, "http://example.com/is_root":true}"""
const rfc7515_A1_HeaderStr* = """{"typ":"JWT", "alg":"HS256"}"""
const rfc7515_A1_ClaimsStr* = """{"iss":"joe", "exp":1300819380, "http://example.com/is_root":true}"""
const rfc7515_A2_ClaimsStr* = """{"iss":"joe", "exp":1300819380, "http://example.com/is_root":true}"""
const sampleJwt* = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhYmMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.fKGoqlw-Nporec-dVTrNbC0ZC7kAhUsEs50s12Iwy14"
const sampleJwtHeaderDecoded* = """{"alg":"HS256","typ":"JWT"}"""

View File

@ -1,7 +1,8 @@
import std/unittest
import std/logging
import ./tclaims
import ./tencoding
import ./tjoseheader
import ./tjwk
import ./tjws
import ./tjwt

View File

@ -6,7 +6,7 @@ import ../testdata
suite "jwt/claims":
test "initClaims(b64)":
let rfcClaims = initJwtClaims(rfc7519Sec3_1ExampleClaimsB64)
let rfcClaims = initJwtClaims(rfc7519_S31_ClaimsB64)
let sampleClaims = initJwtClaims(sampleJwt.split('.')[1])
check:
@ -23,7 +23,7 @@ suite "jwt/claims":
sampleClaims["name"].get.getStr == "John Doe"
test "round trip":
let rfcClaims1 = initJwtClaims(rfc7519Sec3_1ExampleClaimsB64)
let rfcClaims1 = initJwtClaims(rfc7519_S31_ClaimsB64)
let rfcClaims2 = initJwtClaims($rfcClaims1)
let sampleClaims1 = initJwtClaims(sampleJwt.split('.')[1])

View File

@ -7,8 +7,10 @@ suite "private/encoding":
test "b64UrlEncode(int array)":
check:
rfc7519Sec3_1ExampleHeaderB64 == b64UrlEncode(rfc7519Sec3_1ExampleHeaderBytes)
rfc7519Sec3_1ExampleClaimsB64 == b64UrlEncode(rfc7519Sec3_1ExampleClaimsBytes)
rfc7519_S31_HeaderB64 == b64UrlEncode(rfc7519_S31_HeaderBytes)
rfc7519_S31_ClaimsB64 == b64UrlEncode(rfc7519_S31_ClaimsBytes)
rfc7515_A1_HeaderB64 == b64UrlEncode(rfc7515_A1_HeaderBytes)
rfc7515_A1_ClaimsB64 == b64UrlEncode(rfc7515_A1_ClaimsBytes)
test "b64UrlEncode(string)":
let jwtParts = sampleJwt.split('.')
@ -16,12 +18,13 @@ suite "private/encoding":
check:
b64UrlEncode(sampleJwtHeaderDecoded) == jwtParts[0]
b64UrlEncode(sampleJwtClaimsDecoded) == jwtParts[1]
rfc7515_A2_HeaderB64 == b64UrlEncode(rfc7515_A2_HeaderStr)
# Nim mangles \r\n somehow, causing the below to fail. However, the
# failure is at the string literal -> string var layer, not the encoding
# layer)
# rfc7519Sec3_1ExampleHeaderB64 == b64UrlEncode(rfc7519Sec3_1ExampleHeaderStr)
# rfc7519Sec3_1ExampleClaimsB64 == b64UrlEncode(rfc7519Sec3_1ExampleClaimsStr)
# rfc7519_S31_HeaderB64 == b64UrlEncode(rfc7519_S31_HeaderStr)
# rfc7519_S31_ClaimsB64 == b64UrlEncode(rfc7519_S31_ClaimsStr)
test "b64UrlDecode(string)":
let jwtParts = sampleJwt.split('.')
@ -29,7 +32,8 @@ suite "private/encoding":
check:
sampleJwtHeaderDecoded == b64UrlDecode(jwtParts[0])
sampleJwtClaimsDecoded == b64UrlDecode(jwtParts[1])
rfc7515_A2_HeaderStr == b64UrlDecode(rfc7515_A2_HeaderB64)
test "byteArrToString":
check:
byteArrToString(rfc7515A1ExampleHeaderBytes) == b64UrlDecode(rfc7515A1ExampleHeaderB64)
byteArrToString(rfc7515_A1_HeaderBytes) == b64UrlDecode(rfc7515_A1_HeaderB64)

View File

@ -8,11 +8,11 @@ import ../testdata
suite "jwt/joseheader":
test "initJoseHeader always sets rawB64":
let h1 = initJoseHeader(rfc7519Sec3_1ExampleHeaderB64)
let h2 = initJoseHeader(parseJson(rfc7519Sec3_1ExampleHeaderStr))
let h1 = initJoseHeader(rfc7519_S31_HeaderB64)
let h2 = initJoseHeader(parseJson(rfc7519_S31_HeaderStr))
let h3 = initJoseHeader(alg = some(HS256), typ = some("JWT"))
check:
h1.rawB64 == rfc7519Sec3_1ExampleHeaderB64
h1.rawB64 == rfc7519_S31_HeaderB64
h2.rawB64 == "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"
h3.rawB64 == "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"

View File

@ -62,7 +62,7 @@ suite "jwt/jwk":
jwk.kid.isSome
jwk.kid.get == "1"
jwk.key_ops.isNone
jwk.ecPub.crv == $P256
jwk.ecPub.crv == P256
jwk.ecPub.x == "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4"
jwk.ecPub.y.isSome
jwk.ecPub.y.get == "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
@ -126,7 +126,7 @@ suite "jwt/jwk":
jwk.kid.isSome
jwk.kid.get == "1"
jwk.key_ops.isNone
jwk.ecPrv.crv == $P256
jwk.ecPrv.crv == P256
jwk.ecPrv.x == "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4"
jwk.ecPrv.y.isSome
jwk.ecPrv.y.get == "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"

View File

@ -1,6 +1,6 @@
import std/json, std/unittest
import jwt/claims, jwt/joseheader, jwt/jwa, jwt/jwk, jwt/jws, jwt/jwssig
import jwt/claims, jwt/joseheader, jwt/jwa, jwt/jwk, jwt/jws
import private/encoding
@ -8,12 +8,19 @@ import ../testdata
suite "jwt/jws":
let rfc7515_A1_Jwk = initJwk(parseJson(rfc7515_A1_JwkStr))
let rfc7515_A2_JwkPubKey = initJwk(parseJson(rfc7515_A2_JwkPubStr))
let rfc7515_A2_JwkPrvKey = initJwk(parseJson(rfc7515_A2_JwkPrvStr))
let rfc7515_A3_Jwk = initJwk(parseJson(rfc7515_A3_JwkStr))
let rfc7515_A4_Jwk = initJwk(parseJson(rfc7515_A4_JwkStr))
let sampleKey = initJwk(parseJson(sampleJwtKey))
test "HS256 - sign":
check rfc7515A1ExampleJwt == $sign(
header = initJoseHeader(rfc7515A1ExampleHeaderB64),
payload = byteArrToString(rfc7515A1ExampleClaimsBytes),
key = initJwk(parseJson(rfc7515A1ExampleJwkStr)))
check rfc7515_A1_Jwt == $sign(
header = initJoseHeader(rfc7515_A1_HeaderB64),
payload = byteArrToString(rfc7515_A1_ClaimsBytes),
key = rfc7515_A1_Jwk)
check sampleJwt == $sign(
header = initJoseHeader(parseJson(sampleJwtHeaderDecoded)),
@ -23,27 +30,91 @@ suite "jwt/jws":
test "HS256 - verify":
validate(
jws = initJWS(rfc7515A1ExampleJwt),
jws = initJWS(rfc7515_A1_Jwt),
alg = HS256,
key = initJwk(parseJson(rfc7515A1ExampleJwkStr)))
key = rfc7515_A1_Jwk)
validate(
jws = initJWS(sampleJwt),
alg = HS256,
key = initJwk(parseJson(sampleJwtKey)))
key = sampleKey)
test "HS256 - round trip":
let payload = "This is a message I want to protect from tampering."
let jwk = initJwk(%*{
"kty": "oct",
"alg": "HS256",
"k": b64UrlEncode("This is my secret key")
})
let jws = sign(
header = initJoseHeader(%*{ "alg": "HS256" }),
payload = payload,
key = jwk)
key = sampleKey)
jws.validate(HS256, jwk)
jws.validate(HS256, sampleKey)
test "RS256 - sign":
check rfc7515_A2_Jwt == $sign(
header = initJoseHeader(rfc7515_A2_HeaderB64),
payload = b64UrlDecode(rfc7515_A2_ClaimsB64),
key = rfc7515_A2_JwkPrvKey)
test "RS256 - verify":
validate(
jws = initJWS(rfc7515_A2_Jwt),
alg = RS256,
key = rfc7515_A2_JwkPubKey)
test "RS256 - round trip":
let jws = sign(
header = initJoseHeader(%*{"alg":"RS256"}),
payload = "This is a message I want to protect from tampering.",
key = rfc7515_A2_JwkPrvKey)
jws.validate(RS256, rfc7515_A2_JwkPubKey)
test "ES256 - sign":
check rfc7515_A3_Jwt == $sign(
header = initJoseHeader(rfc7515_A3_HeaderB64),
payload = b64UrlDecode(rfc7515_A3_ClaimsB64),
key = rfc7515_A3_Jwk)
test "ES256 - verify":
validate(
jws = initJWS(rfc7515_A3_Jwt),
alg = ES256,
key = rfc7515_A3_Jwk)
test "ES256 - round trip":
let jws = sign(
header = initJoseHeader(%*{"alg": "ES256"}),
payload = "This is a message I want to protect from tampering.",
key = rfc7515_A3_Jwk)
jws.validate(ES256, rfc7515_A3_Jwk)
test "ES512 - sign":
check rfc7515_A4_Jwt == $sign(
header = initJoseHeader(rfc7515_A4_HeaderB64),
payload = b64UrlDecode(rfc7515_A4_PayloadB64),
key = rfc7515_A4_Jwk)
test "ES512 - verify":
validate(
jws = initJWS(rfc7515_A4_Jwt),
alg = ES512,
key = rfc7515_A4_Jwk)
test "ES512 - round trip":
let jws = sign(
header = initJoseHeader(%*{"alg": "ES512"}),
payload = "This is a message I want to protect from tampering.",
key = rfc7515_A4_Jwk)
jws.validate(ES512, rfc7515_A4_Jwk)

41
src/test/unit/tjwt.nim Normal file
View File

@ -0,0 +1,41 @@
import std/json, std/unittest
import jwt/claims, jwt/joseheader, jwt/jwa, jwt/jwk, jwt/jwt
import private/encoding
import ../testdata
suite "jwt/jwt":
let rfc7515_A1_Jwk = initJwk(parseJson(rfc7515_A1_JwkStr))
let rfc7515_A2_JwkPubKey = initJwk(parseJson(rfc7515_A2_JwkPubStr))
let rfc7515_A2_JwkPrvKey = initJwk(parseJson(rfc7515_A2_JwkPrvStr))
let sampleKey = initJwk(parseJson(sampleJwtKey))
test "sign - HS256":
check:
sampleJwt == $createSignedJwt(
header = initJoseHeader(b64UrlEncode(sampleJwtHeaderDecoded)),
claims = initJwtClaims(b64UrlEncode(sampleJwtClaimsDecoded)),
sampleKey)
rfc7515_A1_Jwt == $createSignedJwt(
header = initJoseHeader(rfc7515_A1_HeaderB64),
claims = initJwtClaims(rfc7515_A1_ClaimsB64),
rfc7515_A1_Jwk)
test "sign - RS256":
check:
rfc7515_A2_Jwt == $createSignedJwt(
header = initJoseHeader(rfc7515_A2_HeaderB64),
claims = initJwtClaims(rfc7515_A2_ClaimsB64),
rfc7515_A2_JwkPrvKey)
test "validate - HS256":
initJwt(rfc7515_A1_Jwt).validate(HS256, rfc7515_A1_Jwk)
initJwt(sampleJwt).validate(HS256, sampleKey)
test "validate - RS256":
initJwt(rfc7515_A2_Jwt).validate(RS256, rfc7515_A2_JwkPubKey)