Complete JWS implementation.
Adds support for signature and verification using RS256, RS384, RS512, ES256, ES384, and ES512.
This commit is contained in:
parent
1c886e23b5
commit
a87f92da2d
@ -28,4 +28,4 @@ type
|
|||||||
|
|
||||||
A128GCM = "A128GCM", A192GCM = "A192GCM", A256GCM = "A256GCM"
|
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"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import std/json, std/options, std/sequtils
|
import std/json, std/options, std/sequtils, std/strutils
|
||||||
|
|
||||||
import ../private/jsonutils
|
import ../private/jsonutils
|
||||||
import ./jwa
|
import ./jwa
|
||||||
@ -14,7 +14,7 @@ type
|
|||||||
jwkopDeriveKey = "deriveKey", jwkopDeriveBits = "deriveBits"
|
jwkopDeriveKey = "deriveKey", jwkopDeriveBits = "deriveBits"
|
||||||
|
|
||||||
EcPubKey = object of RootObj
|
EcPubKey = object of RootObj
|
||||||
crv*: string
|
crv*: EcCurve
|
||||||
x*: string
|
x*: string
|
||||||
y*: Option[string]
|
y*: Option[string]
|
||||||
|
|
||||||
@ -86,12 +86,12 @@ func `[]`*(jwk: JWK, key: string): Option[JsonNode] =
|
|||||||
if jwk.json.hasKey(key): some(jwk.json[key])
|
if jwk.json.hasKey(key): some(jwk.json[key])
|
||||||
else: none[JsonNode]()
|
else: none[JsonNode]()
|
||||||
|
|
||||||
## Public Parsing Functions
|
# Public Parsing Functions
|
||||||
## ------------------------
|
# ------------------------
|
||||||
|
|
||||||
func parseEcPubKey*(n: JsonNode): EcPubKey =
|
func parseEcPubKey*(n: JsonNode): EcPubKey =
|
||||||
EcPubKey(
|
EcPubKey(
|
||||||
crv: n.reqStrVal("crv"),
|
crv: parseEnum[EcCurve](n.reqStrVal("crv")),
|
||||||
x: n.reqStrVal("x"),
|
x: n.reqStrVal("x"),
|
||||||
y: n.optStrVal("y"))
|
y: n.optStrVal("y"))
|
||||||
|
|
||||||
|
@ -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/crypto
|
||||||
import ../private/encoding
|
import ../private/encoding
|
||||||
@ -71,6 +71,8 @@ proc initJWS*(b64: string): JWS =
|
|||||||
raise newException(ValueError,
|
raise newException(ValueError,
|
||||||
"invalid JWS, expected three parts but only found " & $parts.len)
|
"invalid JWS, expected three parts but only found " & $parts.len)
|
||||||
|
|
||||||
|
debug "Base64url-encoded payload is:\n\t" & parts[1]
|
||||||
|
|
||||||
result = JWS(
|
result = JWS(
|
||||||
payloadB64: parts[1],
|
payloadB64: parts[1],
|
||||||
signatures: @[ initJwsSignature(
|
signatures: @[ initJwsSignature(
|
||||||
@ -94,19 +96,23 @@ proc initJWS*(n: JsonNode): JWS =
|
|||||||
payloadB64: n.reqStrVal("payload"),
|
payloadB64: n.reqStrVal("payload"),
|
||||||
signatures: @[initJwsSignature(n)])
|
signatures: @[initJwsSignature(n)])
|
||||||
|
|
||||||
|
proc sign*(payload: string, header: JoseHeader, key: JWK, payloadIsB64Encoded = false): JWS =
|
||||||
proc sign*(payload: string, header: JoseHeader, key: JWK): JWS =
|
|
||||||
## Create a JWS signing the given payload, which can be any arbitrary data.
|
## Create a JWS signing the given payload, which can be any arbitrary data.
|
||||||
## The returned JWS can be serialized with the compact serialization. All
|
## The returned JWS can be serialized with the compact serialization. All
|
||||||
## header data will be protected.
|
## header data will be protected.
|
||||||
|
|
||||||
validateHeader(header)
|
validateHeader(header)
|
||||||
|
|
||||||
let payloadB64 = b64UrlEncode(payload)
|
let payloadB64 = if payloadIsB64Encoded: payload
|
||||||
|
else: b64UrlEncode(payload)
|
||||||
|
|
||||||
let valueToSign = $header & "." & payloadB64
|
let valueToSign = $header & "." & payloadB64
|
||||||
let signatureB64 =
|
let signatureB64 =
|
||||||
b64UrlEncode(computeSignature(valueToSign, header.alg.get, key))
|
b64UrlEncode(computeSignature(valueToSign, header.alg.get, key))
|
||||||
|
|
||||||
|
debug "Signed a value:\n\ttoSign : " & valueToSign &
|
||||||
|
"\n\tsignature: " & signatureB64
|
||||||
|
|
||||||
result = JWS(
|
result = JWS(
|
||||||
payloadB64: payloadB64,
|
payloadB64: payloadB64,
|
||||||
signatures: @[
|
signatures: @[
|
||||||
@ -120,24 +126,30 @@ proc sign*(payload: string, header: JoseHeader, key: JWK): JWS =
|
|||||||
proc sign*(claims: JwtClaims, 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
|
## Create a JWS signing a set of JWT claims. The returned JWS will be a valid
|
||||||
## JWT.
|
## JWT.
|
||||||
sign($claims, header, key)
|
sign($claims, header, key, payloadIsB64Encoded = true)
|
||||||
|
|
||||||
proc sign*(
|
proc sign*(
|
||||||
payload: string,
|
payload: string,
|
||||||
unprotected: JoseHeader,
|
unprotected: JoseHeader,
|
||||||
protected: JoseHeader,
|
protected: JoseHeader,
|
||||||
key: JWK): JWS =
|
key: JWK,
|
||||||
|
payloadIsB64Encoded = false): JWS =
|
||||||
## Create a JWS signing the given payload, which can be arbitrary data. The
|
## Create a JWS signing the given payload, which can be arbitrary data. The
|
||||||
## returned JWS can be serialized with the compact serialization.
|
## returned JWS can be serialized with the compact serialization.
|
||||||
|
|
||||||
let combinedHeader = combine(unprotected, protected)
|
let combinedHeader = combine(unprotected, protected)
|
||||||
validateHeader(combinedHeader)
|
validateHeader(combinedHeader)
|
||||||
|
|
||||||
let payloadB64 = b64UrlEncode(payload)
|
let payloadB64 = if payloadIsB64Encoded: payload
|
||||||
|
else: b64UrlEncode(payload)
|
||||||
|
|
||||||
let valueToSign = $protected & "." & payloadB64
|
let valueToSign = $protected & "." & payloadB64
|
||||||
let signatureB64 = b64UrlEncode(
|
let signatureB64 = b64UrlEncode(
|
||||||
computeSignature(valueToSign, combinedHeader.alg.get, key))
|
computeSignature(valueToSign, combinedHeader.alg.get, key))
|
||||||
|
|
||||||
|
debug "Signed a value:\n\ttoSign : " & valueToSign &
|
||||||
|
"\n\tsignature: " & signatureB64
|
||||||
|
|
||||||
result = JWS(
|
result = JWS(
|
||||||
payloadB64: payloadB64,
|
payloadB64: payloadB64,
|
||||||
signatures: @[
|
signatures: @[
|
||||||
@ -165,13 +177,16 @@ proc sign*(
|
|||||||
let signatureB64 = b64UrlEncode(
|
let signatureB64 = b64UrlEncode(
|
||||||
computeSignature(valueToSign, combinedHeader.alg.get, key))
|
computeSignature(valueToSign, combinedHeader.alg.get, key))
|
||||||
|
|
||||||
|
debug "Signed a value:\n\ttoSign : " & valueToSign &
|
||||||
|
"\n\tsignature: " & signatureB64
|
||||||
|
|
||||||
result = JWS(
|
result = JWS(
|
||||||
payloadB64: jws.payloadB64,
|
payloadB64: jws.payloadB64,
|
||||||
signatures: jws.signatures &
|
signatures: jws.signatures &
|
||||||
@[initJwsSignature(
|
@[initJwsSignature(
|
||||||
protected = protected,
|
protected = protected,
|
||||||
header = unprotected,
|
header = unprotected,
|
||||||
signatureB64= signatureB64)])
|
signatureB64 = signatureB64)])
|
||||||
|
|
||||||
result.compactSerialization = none[string]()
|
result.compactSerialization = none[string]()
|
||||||
|
|
||||||
@ -199,8 +214,14 @@ proc validate*(jws: JWS, alg: JwtAlgorithm, key: JWK, sigIdx = 0) =
|
|||||||
raise newException(InvalidSignature,
|
raise newException(InvalidSignature,
|
||||||
"'" & $alg & "' is not a valid signing algorithm.")
|
"'" & $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(
|
if not verifySignature(
|
||||||
payload = $(jws[sigIdx].protected) & "." & jws.payloadB64,
|
payload = payload,
|
||||||
signature = b64UrlDecode(jws[sigIdx].signatureB64),
|
signature = b64UrlDecode(jws[sigIdx].signatureB64),
|
||||||
alg = alg,
|
alg = alg,
|
||||||
key = key):
|
key = key):
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import std/json, std/options, std/strutils, std/times
|
import std/json, std/options, std/strutils, std/times
|
||||||
|
|
||||||
import ./claims, ./joseheader, ./jwe, ./jws
|
import ./claims, ./joseheader, ./jwa, ./jwe, ./jwk, ./jws, ./jwssig
|
||||||
|
|
||||||
export claims
|
export claims
|
||||||
|
|
||||||
type
|
type
|
||||||
|
InvalidToken* = object of CatchableError
|
||||||
|
|
||||||
JwtKind = enum jkJWE, jkJWS
|
JwtKind = enum jkJWE, jkJWS
|
||||||
|
|
||||||
JWT* = object
|
JWT* = object
|
||||||
@ -20,27 +22,58 @@ func claims*(jwt: JWT): JwtClaims = jwt.claims
|
|||||||
|
|
||||||
func header*(jwt: JWT): JoseHeader =
|
func header*(jwt: JWT): JoseHeader =
|
||||||
case jwt.kind:
|
case jwt.kind:
|
||||||
of jkJWS: return jwt.jws.header
|
of jkJWS: return jwt.jws[0].protected
|
||||||
of jkJWE: return jwt.jwe.header
|
of jkJWE: return jwt.jwe.header
|
||||||
|
|
||||||
func `$`*(jwt: JWT): string =
|
func `$`*(jwt: JWT): string =
|
||||||
result = jwt.header.rawB64 & "." & jwt.claims.rawB64 & "."
|
case jwt.kind:
|
||||||
if jwt.signature.isSome: result &= jwt.signature.get
|
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 =
|
proc initJWT*(encoded: string): JWT =
|
||||||
let parts = encoded.split('.')
|
let parts = encoded.split('.')
|
||||||
|
|
||||||
result = JWT(
|
if parts.len == 3:
|
||||||
signature: if parts.len > 2: some(parts[2])
|
let jws = initJWS(encoded)
|
||||||
else: none[string](),
|
result = JWT(
|
||||||
header: initJoseHeader(parts[0]),
|
kind: jkJWS,
|
||||||
claims: initJwtClaims(parts[1]))
|
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(
|
result = JWT(
|
||||||
header: header,
|
kind: jkJWS,
|
||||||
claims: claims,
|
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 =
|
# proc verify*(jwt: JWT): bool =
|
||||||
|
|
||||||
|
@ -4,8 +4,9 @@ import bearssl
|
|||||||
import ../jwt/jwa
|
import ../jwt/jwa
|
||||||
import ../jwt/jwk
|
import ../jwt/jwk
|
||||||
|
|
||||||
import ./crypto/hash
|
import ./crypto/ecdsa
|
||||||
import ./crypto/hmac
|
import ./crypto/hmac
|
||||||
|
import ./crypto/rsa
|
||||||
|
|
||||||
proc validateAlgMatchesKey(alg: JwtAlgorithm, key: JWK) =
|
proc validateAlgMatchesKey(alg: JwtAlgorithm, key: JWK) =
|
||||||
if key.alg.isSome and key.alg.get != $alg:
|
if key.alg.isSome and key.alg.get != $alg:
|
||||||
@ -16,13 +17,15 @@ proc validateAlgMatchesKey(alg: JwtAlgorithm, key: JWK) =
|
|||||||
proc computeSignature*(
|
proc computeSignature*(
|
||||||
payload: string,
|
payload: string,
|
||||||
alg: JwtAlgorithm,
|
alg: JwtAlgorithm,
|
||||||
key: JWK
|
key: JWK | string
|
||||||
): string =
|
): string =
|
||||||
|
|
||||||
validateAlgMatchesKey(alg, key)
|
validateAlgMatchesKey(alg, key)
|
||||||
|
|
||||||
case alg:
|
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:
|
else:
|
||||||
raise newException(Exception,
|
raise newException(Exception,
|
||||||
"unsupported signature algorithm: " & $alg)
|
"unsupported signature algorithm: " & $alg)
|
||||||
@ -38,8 +41,10 @@ proc verifySignature*(
|
|||||||
|
|
||||||
case alg:
|
case alg:
|
||||||
of HS256, HS384, HS512:
|
of HS256, HS384, HS512:
|
||||||
let hash = hmac(alg, key, payload)
|
let hash = hmac(payload, alg, key)
|
||||||
return hash == signature
|
return hash == signature
|
||||||
|
of RS256, RS384, RS512: return rsaVerify(payload, signature, alg, key)
|
||||||
|
of ES256, ES384, ES512: return ecVerify(payload, signature, alg, key)
|
||||||
else:
|
else:
|
||||||
raise newException(Exception,
|
raise newException(Exception,
|
||||||
"unsupported signature algorithm: " & $alg)
|
"unsupported signature algorithm: " & $alg)
|
||||||
|
146
src/main/private/crypto/ecdsa.nim
Normal file
146
src/main/private/crypto/ecdsa.nim
Normal 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))
|
@ -5,35 +5,40 @@ type
|
|||||||
HashAlgorithm* = enum SHA1, SHA256, SHA384, SHA512
|
HashAlgorithm* = enum SHA1, SHA256, SHA384, SHA512
|
||||||
|
|
||||||
HashCfg* = object
|
HashCfg* = object
|
||||||
|
alg*: HashAlgorithm
|
||||||
vtable*: ptr HashClass
|
vtable*: ptr HashClass
|
||||||
oid*: cstring
|
oid*: cstring
|
||||||
size*: int
|
size*: int
|
||||||
|
|
||||||
let HASHES* = newTable[HashAlgorithm, HashCfg]([
|
let HASHES* = newTable[HashAlgorithm, HashCfg]([
|
||||||
(SHA1, HashCfg(
|
(SHA1, HashCfg(
|
||||||
|
alg: SHA1,
|
||||||
vtable: addr sha1Vtable,
|
vtable: addr sha1Vtable,
|
||||||
oid: HASH_OID_SHA1,
|
oid: HASH_OID_SHA1,
|
||||||
size: sha1Size)),
|
size: sha1Size)),
|
||||||
(SHA256, HashCfg(
|
(SHA256, HashCfg(
|
||||||
|
alg: SHA256,
|
||||||
vtable: addr sha256Vtable,
|
vtable: addr sha256Vtable,
|
||||||
oid: HASH_OID_SHA256,
|
oid: HASH_OID_SHA256,
|
||||||
size: sha256Size)),
|
size: sha256Size)),
|
||||||
(SHA384, HashCfg(
|
(SHA384, HashCfg(
|
||||||
|
alg: SHA384,
|
||||||
vtable: addr sha384Vtable,
|
vtable: addr sha384Vtable,
|
||||||
oid: HASH_OID_SHA384,
|
oid: HASH_OID_SHA384,
|
||||||
size: sha384Size)),
|
size: sha384Size)),
|
||||||
(SHA512, HashCfg(
|
(SHA512, HashCfg(
|
||||||
|
alg: SHA512,
|
||||||
vtable: addr sha512Vtable,
|
vtable: addr sha512Vtable,
|
||||||
oid: HASH_OID_SHA512,
|
oid: HASH_OID_SHA512,
|
||||||
size: sha512Size)),
|
size: sha512Size)),
|
||||||
])
|
])
|
||||||
|
|
||||||
proc hash*(alg: HashAlgorithm, data: string): string =
|
proc hash*(data: string, alg: HashAlgorithm): string =
|
||||||
|
|
||||||
var ctx: HashCompatContext
|
var ctx: HashCompatContext
|
||||||
var pCtx = cast[ptr ptr HashClass](addr ctx)
|
var pCtx = cast[ptr ptr HashClass](addr ctx)
|
||||||
let hashCfg = HASHES[alg]
|
let hashCfg = HASHES[alg]
|
||||||
result = newString(hashCfg.size)
|
result = newString(hashCfg.size)
|
||||||
hashCfg.vtable.init(pCtx)
|
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])
|
hashCfg.vtable.output(pCtx, addr result[0])
|
||||||
|
@ -7,7 +7,7 @@ import ../../jwt/jwk
|
|||||||
|
|
||||||
import ../encoding
|
import ../encoding
|
||||||
|
|
||||||
proc bearHMAC(alg: JwtAlgorithm, key, toSign: string): string =
|
proc bearHMAC(message: string, alg: JwtAlgorithm, key: string): string =
|
||||||
|
|
||||||
var vtable: ptr HashClass
|
var vtable: ptr HashClass
|
||||||
var hashSize: int
|
var hashSize: int
|
||||||
@ -29,19 +29,19 @@ proc bearHMAC(alg: JwtAlgorithm, key, toSign: string): string =
|
|||||||
|
|
||||||
hmacKeyInit(addr keyCtx, vtable, key.cstring, key.len)
|
hmacKeyInit(addr keyCtx, vtable, key.cstring, key.len)
|
||||||
hmacInit(addr hmacCtx, addr keyCtx, 0)
|
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)
|
let resLen = hmacSize(addr hmacCtx)
|
||||||
result = newString(resLen)
|
result = newString(resLen)
|
||||||
discard hmacOut(addr hmacCtx, addr result[0])
|
discard hmacOut(addr hmacCtx, addr result[0])
|
||||||
|
|
||||||
proc hmac*(alg: JwtAlgorithm, key, toSign: string): string =
|
proc hmac*(message: string, alg: JwtAlgorithm, key: string): string =
|
||||||
return bearHMAC(alg, key, toSign)
|
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:
|
if key.keyKind != Octet:
|
||||||
raise newException(ValueError,
|
raise newException(ValueError,
|
||||||
$alg & " requires an octet key (\"typ\"=\"oct\"), not a \"typ\"=\"" &
|
$alg & " requires an octet key (\"typ\"=\"oct\"), not a \"typ\"=\"" &
|
||||||
$(key.keyKind) & "\" key.")
|
$(key.keyKind) & "\" key.")
|
||||||
|
|
||||||
return bearHMAC(alg, b64UrlDecode(key.octKey.k), toSign)
|
return bearHMAC(message, alg, b64UrlDecode(key.octKey.k))
|
||||||
|
@ -1,23 +1,151 @@
|
|||||||
|
import std/options, std/tables
|
||||||
import bearssl
|
import bearssl
|
||||||
|
|
||||||
import ../../jwt/jwa
|
import ../../jwt/jwa
|
||||||
import ../../jwt/jwk
|
import ../../jwt/jwk
|
||||||
|
|
||||||
proc bearRsaSign(
|
import ../encoding
|
||||||
alg: JwtAlgorithm,
|
|
||||||
key: RsaPrivateKey,
|
|
||||||
toSign: string
|
|
||||||
): string =
|
|
||||||
|
|
||||||
var hashVtable: ptr HashClass
|
import ./hash
|
||||||
var hashOID: cstring
|
|
||||||
|
|
||||||
case alg:
|
type
|
||||||
of RS256: hashVtable = addr sha256Vtable; hashOID = HASH_OID_SHA256
|
RsaPublicKeyObj = object
|
||||||
of RS384: hashVtable = addr sha384Vtable; hashOID = HASH_OID_SHA384
|
n*, e*: string
|
||||||
of RS512: hashVtable = addr sha512Vtable; hashOID = HASH_OID_SHA512
|
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,
|
else: raise newException(ValueError,
|
||||||
"Unsupported RSA signature algorithm '" & $alg & "'")
|
"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))
|
||||||
|
@ -1,30 +1,85 @@
|
|||||||
## Example JWT from RFC 7519 (JWT) section 3.1
|
## Example JWT from RFC 7519 (JWT) section 3.1
|
||||||
const rfc7519Sec3_1ExampleHeaderB64* = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9"
|
const rfc7519_S31_HeaderB64* = "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 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 rfc7519Sec3_1ExampleClaimsB64* = "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
|
const rfc7519_S31_ClaimsB64* = "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_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
|
## Examples from RFC 7515 (JWS
|
||||||
const rfc7515A1ExampleHeaderB64* = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9"
|
const rfc7515_A1_HeaderB64* = "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 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 rfc7515A1ExapmleClaimsB64* = "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
|
const rfc7515_A1_ClaimsB64* = "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 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 rfc7515A1ExampleSigB64* = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
|
const rfc7515_A1_SigB64* = "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 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 rfc7515A1ExampleJwkStr* = """{
|
const rfc7515_A1_Jwt* = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
|
||||||
|
const rfc7515_A1_JwkStr* = """{
|
||||||
"kty":"oct",
|
"kty":"oct",
|
||||||
"k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"
|
"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
|
# 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
|
# 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
|
# validate B64 conversion, signature, etc.) use either the Base64 version or
|
||||||
# the byte arrays above.
|
# the byte arrays above.
|
||||||
const rfc7519Sec3_1ExampleHeaderStr* = """{"typ":"JWT", "alg":"HS256"}"""
|
const rfc7519_S31_HeaderStr* = """{"typ":"JWT", "alg":"HS256"}"""
|
||||||
const rfc7519Sec3_1ExampleClaimsStr* = """{"iss":"joe", "exp":1300819380, "http://example.com/is_root":true}"""
|
const rfc7519_S31_ClaimsStr* = """{"iss":"joe", "exp":1300819380, "http://example.com/is_root":true}"""
|
||||||
const rfc7515A1ExampleHeaderStr* = """{"typ":"JWT", "alg":"HS256"}"""
|
const rfc7515_A1_HeaderStr* = """{"typ":"JWT", "alg":"HS256"}"""
|
||||||
const rfc7515A1ExampleClaimsStr* = """{"iss":"joe", "exp":1300819380, "http://example.com/is_root":true}"""
|
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 sampleJwt* = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhYmMiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.fKGoqlw-Nporec-dVTrNbC0ZC7kAhUsEs50s12Iwy14"
|
||||||
const sampleJwtHeaderDecoded* = """{"alg":"HS256","typ":"JWT"}"""
|
const sampleJwtHeaderDecoded* = """{"alg":"HS256","typ":"JWT"}"""
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import std/unittest
|
import std/logging
|
||||||
|
|
||||||
import ./tclaims
|
import ./tclaims
|
||||||
import ./tencoding
|
import ./tencoding
|
||||||
import ./tjoseheader
|
import ./tjoseheader
|
||||||
import ./tjwk
|
import ./tjwk
|
||||||
import ./tjws
|
import ./tjws
|
||||||
|
import ./tjwt
|
||||||
|
@ -6,7 +6,7 @@ import ../testdata
|
|||||||
suite "jwt/claims":
|
suite "jwt/claims":
|
||||||
|
|
||||||
test "initClaims(b64)":
|
test "initClaims(b64)":
|
||||||
let rfcClaims = initJwtClaims(rfc7519Sec3_1ExampleClaimsB64)
|
let rfcClaims = initJwtClaims(rfc7519_S31_ClaimsB64)
|
||||||
let sampleClaims = initJwtClaims(sampleJwt.split('.')[1])
|
let sampleClaims = initJwtClaims(sampleJwt.split('.')[1])
|
||||||
|
|
||||||
check:
|
check:
|
||||||
@ -23,7 +23,7 @@ suite "jwt/claims":
|
|||||||
sampleClaims["name"].get.getStr == "John Doe"
|
sampleClaims["name"].get.getStr == "John Doe"
|
||||||
|
|
||||||
test "round trip":
|
test "round trip":
|
||||||
let rfcClaims1 = initJwtClaims(rfc7519Sec3_1ExampleClaimsB64)
|
let rfcClaims1 = initJwtClaims(rfc7519_S31_ClaimsB64)
|
||||||
let rfcClaims2 = initJwtClaims($rfcClaims1)
|
let rfcClaims2 = initJwtClaims($rfcClaims1)
|
||||||
|
|
||||||
let sampleClaims1 = initJwtClaims(sampleJwt.split('.')[1])
|
let sampleClaims1 = initJwtClaims(sampleJwt.split('.')[1])
|
||||||
|
@ -7,8 +7,10 @@ suite "private/encoding":
|
|||||||
|
|
||||||
test "b64UrlEncode(int array)":
|
test "b64UrlEncode(int array)":
|
||||||
check:
|
check:
|
||||||
rfc7519Sec3_1ExampleHeaderB64 == b64UrlEncode(rfc7519Sec3_1ExampleHeaderBytes)
|
rfc7519_S31_HeaderB64 == b64UrlEncode(rfc7519_S31_HeaderBytes)
|
||||||
rfc7519Sec3_1ExampleClaimsB64 == b64UrlEncode(rfc7519Sec3_1ExampleClaimsBytes)
|
rfc7519_S31_ClaimsB64 == b64UrlEncode(rfc7519_S31_ClaimsBytes)
|
||||||
|
rfc7515_A1_HeaderB64 == b64UrlEncode(rfc7515_A1_HeaderBytes)
|
||||||
|
rfc7515_A1_ClaimsB64 == b64UrlEncode(rfc7515_A1_ClaimsBytes)
|
||||||
|
|
||||||
test "b64UrlEncode(string)":
|
test "b64UrlEncode(string)":
|
||||||
let jwtParts = sampleJwt.split('.')
|
let jwtParts = sampleJwt.split('.')
|
||||||
@ -16,12 +18,13 @@ suite "private/encoding":
|
|||||||
check:
|
check:
|
||||||
b64UrlEncode(sampleJwtHeaderDecoded) == jwtParts[0]
|
b64UrlEncode(sampleJwtHeaderDecoded) == jwtParts[0]
|
||||||
b64UrlEncode(sampleJwtClaimsDecoded) == jwtParts[1]
|
b64UrlEncode(sampleJwtClaimsDecoded) == jwtParts[1]
|
||||||
|
rfc7515_A2_HeaderB64 == b64UrlEncode(rfc7515_A2_HeaderStr)
|
||||||
|
|
||||||
# Nim mangles \r\n somehow, causing the below to fail. However, the
|
# Nim mangles \r\n somehow, causing the below to fail. However, the
|
||||||
# failure is at the string literal -> string var layer, not the encoding
|
# failure is at the string literal -> string var layer, not the encoding
|
||||||
# layer)
|
# layer)
|
||||||
# rfc7519Sec3_1ExampleHeaderB64 == b64UrlEncode(rfc7519Sec3_1ExampleHeaderStr)
|
# rfc7519_S31_HeaderB64 == b64UrlEncode(rfc7519_S31_HeaderStr)
|
||||||
# rfc7519Sec3_1ExampleClaimsB64 == b64UrlEncode(rfc7519Sec3_1ExampleClaimsStr)
|
# rfc7519_S31_ClaimsB64 == b64UrlEncode(rfc7519_S31_ClaimsStr)
|
||||||
|
|
||||||
test "b64UrlDecode(string)":
|
test "b64UrlDecode(string)":
|
||||||
let jwtParts = sampleJwt.split('.')
|
let jwtParts = sampleJwt.split('.')
|
||||||
@ -29,7 +32,8 @@ suite "private/encoding":
|
|||||||
check:
|
check:
|
||||||
sampleJwtHeaderDecoded == b64UrlDecode(jwtParts[0])
|
sampleJwtHeaderDecoded == b64UrlDecode(jwtParts[0])
|
||||||
sampleJwtClaimsDecoded == b64UrlDecode(jwtParts[1])
|
sampleJwtClaimsDecoded == b64UrlDecode(jwtParts[1])
|
||||||
|
rfc7515_A2_HeaderStr == b64UrlDecode(rfc7515_A2_HeaderB64)
|
||||||
|
|
||||||
test "byteArrToString":
|
test "byteArrToString":
|
||||||
check:
|
check:
|
||||||
byteArrToString(rfc7515A1ExampleHeaderBytes) == b64UrlDecode(rfc7515A1ExampleHeaderB64)
|
byteArrToString(rfc7515_A1_HeaderBytes) == b64UrlDecode(rfc7515_A1_HeaderB64)
|
||||||
|
@ -8,11 +8,11 @@ import ../testdata
|
|||||||
suite "jwt/joseheader":
|
suite "jwt/joseheader":
|
||||||
|
|
||||||
test "initJoseHeader always sets rawB64":
|
test "initJoseHeader always sets rawB64":
|
||||||
let h1 = initJoseHeader(rfc7519Sec3_1ExampleHeaderB64)
|
let h1 = initJoseHeader(rfc7519_S31_HeaderB64)
|
||||||
let h2 = initJoseHeader(parseJson(rfc7519Sec3_1ExampleHeaderStr))
|
let h2 = initJoseHeader(parseJson(rfc7519_S31_HeaderStr))
|
||||||
let h3 = initJoseHeader(alg = some(HS256), typ = some("JWT"))
|
let h3 = initJoseHeader(alg = some(HS256), typ = some("JWT"))
|
||||||
|
|
||||||
check:
|
check:
|
||||||
h1.rawB64 == rfc7519Sec3_1ExampleHeaderB64
|
h1.rawB64 == rfc7519_S31_HeaderB64
|
||||||
h2.rawB64 == "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"
|
h2.rawB64 == "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"
|
||||||
h3.rawB64 == "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
|
h3.rawB64 == "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
|
||||||
|
@ -62,7 +62,7 @@ suite "jwt/jwk":
|
|||||||
jwk.kid.isSome
|
jwk.kid.isSome
|
||||||
jwk.kid.get == "1"
|
jwk.kid.get == "1"
|
||||||
jwk.key_ops.isNone
|
jwk.key_ops.isNone
|
||||||
jwk.ecPub.crv == $P256
|
jwk.ecPub.crv == P256
|
||||||
jwk.ecPub.x == "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4"
|
jwk.ecPub.x == "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4"
|
||||||
jwk.ecPub.y.isSome
|
jwk.ecPub.y.isSome
|
||||||
jwk.ecPub.y.get == "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
|
jwk.ecPub.y.get == "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
|
||||||
@ -126,7 +126,7 @@ suite "jwt/jwk":
|
|||||||
jwk.kid.isSome
|
jwk.kid.isSome
|
||||||
jwk.kid.get == "1"
|
jwk.kid.get == "1"
|
||||||
jwk.key_ops.isNone
|
jwk.key_ops.isNone
|
||||||
jwk.ecPrv.crv == $P256
|
jwk.ecPrv.crv == P256
|
||||||
jwk.ecPrv.x == "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4"
|
jwk.ecPrv.x == "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4"
|
||||||
jwk.ecPrv.y.isSome
|
jwk.ecPrv.y.isSome
|
||||||
jwk.ecPrv.y.get == "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
|
jwk.ecPrv.y.get == "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import std/json, std/unittest
|
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
|
import private/encoding
|
||||||
|
|
||||||
@ -8,12 +8,19 @@ import ../testdata
|
|||||||
|
|
||||||
suite "jwt/jws":
|
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":
|
test "HS256 - sign":
|
||||||
|
|
||||||
check rfc7515A1ExampleJwt == $sign(
|
check rfc7515_A1_Jwt == $sign(
|
||||||
header = initJoseHeader(rfc7515A1ExampleHeaderB64),
|
header = initJoseHeader(rfc7515_A1_HeaderB64),
|
||||||
payload = byteArrToString(rfc7515A1ExampleClaimsBytes),
|
payload = byteArrToString(rfc7515_A1_ClaimsBytes),
|
||||||
key = initJwk(parseJson(rfc7515A1ExampleJwkStr)))
|
key = rfc7515_A1_Jwk)
|
||||||
|
|
||||||
check sampleJwt == $sign(
|
check sampleJwt == $sign(
|
||||||
header = initJoseHeader(parseJson(sampleJwtHeaderDecoded)),
|
header = initJoseHeader(parseJson(sampleJwtHeaderDecoded)),
|
||||||
@ -23,27 +30,91 @@ suite "jwt/jws":
|
|||||||
test "HS256 - verify":
|
test "HS256 - verify":
|
||||||
|
|
||||||
validate(
|
validate(
|
||||||
jws = initJWS(rfc7515A1ExampleJwt),
|
jws = initJWS(rfc7515_A1_Jwt),
|
||||||
alg = HS256,
|
alg = HS256,
|
||||||
key = initJwk(parseJson(rfc7515A1ExampleJwkStr)))
|
key = rfc7515_A1_Jwk)
|
||||||
|
|
||||||
validate(
|
validate(
|
||||||
jws = initJWS(sampleJwt),
|
jws = initJWS(sampleJwt),
|
||||||
alg = HS256,
|
alg = HS256,
|
||||||
key = initJwk(parseJson(sampleJwtKey)))
|
key = sampleKey)
|
||||||
|
|
||||||
test "HS256 - round trip":
|
test "HS256 - round trip":
|
||||||
|
|
||||||
let payload = "This is a message I want to protect from tampering."
|
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(
|
let jws = sign(
|
||||||
header = initJoseHeader(%*{ "alg": "HS256" }),
|
header = initJoseHeader(%*{ "alg": "HS256" }),
|
||||||
payload = payload,
|
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
41
src/test/unit/tjwt.nim
Normal 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)
|
Loading…
Reference in New Issue
Block a user