mirror of
https://github.com/vacp2p/nim-jwt.git
synced 2026-01-08 19:57:59 -05:00
feat: add toFlattenedJson
This commit is contained in:
50
README.md
50
README.md
@@ -17,7 +17,7 @@ This is a implementation of JSON Web Tokens for Nim, it allows for the following
|
|||||||
After installing nim's package manager `nimble` execute this:
|
After installing nim's package manager `nimble` execute this:
|
||||||
`nimble install jwt`
|
`nimble install jwt`
|
||||||
|
|
||||||
## Example
|
## Examples
|
||||||
|
|
||||||
An example to demonstrate use with a userId
|
An example to demonstrate use with a userId
|
||||||
|
|
||||||
@@ -90,3 +90,51 @@ proc request(url: string, body: string): string =
|
|||||||
let resp = request("https://www.googleapis.com/oauth2/v4/token", postdata).parseJson()
|
let resp = request("https://www.googleapis.com/oauth2/v4/token", postdata).parseJson()
|
||||||
echo "Access token is: ", resp["access_token"].str
|
echo "Access token is: ", resp["access_token"].str
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Registering in [Let's Encrypt's](https://letsencrypt.org/) [ACME](https://www.rfc-editor.org/rfc/rfc8555) server
|
||||||
|
```nim
|
||||||
|
let key = "your_rsa_key_here"
|
||||||
|
let registerAccountPayload = %*{"termsOfServiceAgreed": true}
|
||||||
|
let resp = makeSignedAcmeRequest(getDirectory()["newAccount"].getStr, registerAccountPayload, key, needsJwk = true)
|
||||||
|
echo resp.body
|
||||||
|
|
||||||
|
proc makeSignedAcmeRequest(
|
||||||
|
url: string, payload: JsonNode, accountKey: string, needsJwk: bool = false
|
||||||
|
): Response =
|
||||||
|
let key = loadRsaKey(accountKey)
|
||||||
|
var token = toJWT(
|
||||||
|
%*{
|
||||||
|
"header":
|
||||||
|
getAcmeHeader(url, needsJwk, base64UrlEncode(key.n), base64UrlEncode(key.e)),
|
||||||
|
"claims": payload,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
token.sign(accountKey)
|
||||||
|
|
||||||
|
var client = newHttpClient()
|
||||||
|
let body = token.toFlattennedJson
|
||||||
|
echo body
|
||||||
|
client.request(url, httpMethod = HttpPost, body = $body, headers = newHttpHeaders({"Content-Type": "application/jose+json"}))
|
||||||
|
|
||||||
|
proc getAcmeHeader(
|
||||||
|
url: string, needsJwk: bool, n: string = "", e: string = ""
|
||||||
|
): JsonNode =
|
||||||
|
var header = %*{"alg": Alg, "typ": "JWT", "nonce": getNewNonce(), "url": url}
|
||||||
|
if needsJwk:
|
||||||
|
header["jwk"] = %*{"kty": "RSA", "n": n, "e": e}
|
||||||
|
else:
|
||||||
|
header["kid"] = "some_kid"
|
||||||
|
return header
|
||||||
|
|
||||||
|
proc getNewNonce(): string =
|
||||||
|
let client = newHttpClient()
|
||||||
|
let nonceURL = getDirectory()["newNonce"].getStr
|
||||||
|
let resp = client.request(nonceURL, httpMethod = HttpGet)
|
||||||
|
return resp.headers["replay-nonce"]
|
||||||
|
|
||||||
|
proc getDirectory(): JsonNode =
|
||||||
|
let client = newHttpClient()
|
||||||
|
let directory = parseJson(client.get(LetsEncryptURL & "/directory").body)
|
||||||
|
return directory
|
||||||
|
|
||||||
|
```
|
||||||
|
|||||||
75
jwt.nim
75
jwt.nim
@@ -24,13 +24,15 @@ proc splitToken(s: string): seq[string] =
|
|||||||
raise newException(InvalidToken, "Invalid token")
|
raise newException(InvalidToken, "Invalid token")
|
||||||
result = parts
|
result = parts
|
||||||
|
|
||||||
proc initJWT*(header: JsonNode, claims: TableRef[string, Claim], signature: seq[byte] = @[]): JWT =
|
proc initJWT*(
|
||||||
|
header: JsonNode, claims: TableRef[string, Claim], signature: seq[byte] = @[]
|
||||||
|
): JWT =
|
||||||
JWT(
|
JWT(
|
||||||
headerB64: header.toBase64,
|
headerB64: header.toBase64,
|
||||||
claimsB64: claims.toBase64,
|
claimsB64: claims.toBase64,
|
||||||
header: header,
|
header: header,
|
||||||
claims: claims,
|
claims: claims,
|
||||||
signature: signature
|
signature: signature,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Load up a b64url string to JWT
|
# Load up a b64url string to JWT
|
||||||
@@ -48,7 +50,7 @@ proc toJWT*(s: string): JWT =
|
|||||||
claimsB64: claimsB64,
|
claimsB64: claimsB64,
|
||||||
header: headerJson.toHeader(),
|
header: headerJson.toHeader(),
|
||||||
claims: claimsJson.toClaims(),
|
claims: claimsJson.toClaims(),
|
||||||
signature: signature
|
signature: signature,
|
||||||
)
|
)
|
||||||
|
|
||||||
proc toJWT*(node: JsonNode): JWT =
|
proc toJWT*(node: JsonNode): JWT =
|
||||||
@@ -66,7 +68,9 @@ proc parsed*(token: JWT): string =
|
|||||||
result = token.header.toBase64 & "." & token.claims.toBase64
|
result = token.header.toBase64 & "." & token.claims.toBase64
|
||||||
|
|
||||||
# Signs a string with a secret
|
# Signs a string with a secret
|
||||||
proc signString*(toSign: string, secret: string, algorithm: SignatureAlgorithm = HS256): seq[byte] =
|
proc signString*(
|
||||||
|
toSign: string, secret: string, algorithm: SignatureAlgorithm = HS256
|
||||||
|
): seq[byte] =
|
||||||
template hsSign(meth: typed): seq[byte] =
|
template hsSign(meth: typed): seq[byte] =
|
||||||
crypto.bearHMAC(addr meth, secret, toSign)
|
crypto.bearHMAC(addr meth, secret, toSign)
|
||||||
|
|
||||||
@@ -75,7 +79,7 @@ proc signString*(toSign: string, secret: string, algorithm: SignatureAlgorithm =
|
|||||||
|
|
||||||
template ecSign(hc: typed): seq[byte] =
|
template ecSign(hc: typed): seq[byte] =
|
||||||
crypto.bearSignECPem(toSign, secret, addr hc)
|
crypto.bearSignECPem(toSign, secret, addr hc)
|
||||||
|
|
||||||
case algorithm
|
case algorithm
|
||||||
of HS256:
|
of HS256:
|
||||||
return hsSign(sha256Vtable)
|
return hsSign(sha256Vtable)
|
||||||
@@ -102,25 +106,34 @@ proc signString*(toSign: string, secret: string, algorithm: SignatureAlgorithm =
|
|||||||
raise newException(UnsupportedAlgorithm, $algorithm & " isn't supported")
|
raise newException(UnsupportedAlgorithm, $algorithm & " isn't supported")
|
||||||
|
|
||||||
# Verify that the token is not tampered with
|
# Verify that the token is not tampered with
|
||||||
proc verifySignature*(data: string, signature: seq[byte], secret: string,
|
proc verifySignature*(
|
||||||
alg: SignatureAlgorithm): bool =
|
data: string, signature: seq[byte], secret: string, alg: SignatureAlgorithm
|
||||||
|
): bool =
|
||||||
case alg
|
case alg
|
||||||
of HS256, HS384, HS512:
|
of HS256, HS384, HS512:
|
||||||
let dataSignature = signString(data, secret, alg)
|
let dataSignature = signString(data, secret, alg)
|
||||||
result = dataSignature == signature
|
result = dataSignature == signature
|
||||||
of RS256:
|
of RS256:
|
||||||
result = crypto.bearVerifyRSPem(data, secret, signature, addr sha256Vtable, HASH_OID_SHA256, sha256SIZE)
|
result = crypto.bearVerifyRSPem(
|
||||||
|
data, secret, signature, addr sha256Vtable, HASH_OID_SHA256, sha256SIZE
|
||||||
|
)
|
||||||
of RS384:
|
of RS384:
|
||||||
result = crypto.bearVerifyRSPem(data, secret, signature, addr sha384Vtable, HASH_OID_SHA384, sha384SIZE)
|
result = crypto.bearVerifyRSPem(
|
||||||
|
data, secret, signature, addr sha384Vtable, HASH_OID_SHA384, sha384SIZE
|
||||||
|
)
|
||||||
of RS512:
|
of RS512:
|
||||||
result = crypto.bearVerifyRSPem(data, secret, signature, addr sha512Vtable, HASH_OID_SHA512, sha512SIZE)
|
result = crypto.bearVerifyRSPem(
|
||||||
|
data, secret, signature, addr sha512Vtable, HASH_OID_SHA512, sha512SIZE
|
||||||
|
)
|
||||||
of ES256:
|
of ES256:
|
||||||
result = crypto.bearVerifyECPem(data, secret, signature, addr sha256Vtable, sha256SIZE)
|
result =
|
||||||
|
crypto.bearVerifyECPem(data, secret, signature, addr sha256Vtable, sha256SIZE)
|
||||||
of ES384:
|
of ES384:
|
||||||
result = crypto.bearVerifyECPem(data, secret, signature, addr sha384Vtable, sha384SIZE)
|
result =
|
||||||
|
crypto.bearVerifyECPem(data, secret, signature, addr sha384Vtable, sha384SIZE)
|
||||||
of ES512:
|
of ES512:
|
||||||
result = crypto.bearVerifyECPem(data, secret, signature, addr sha512Vtable, sha512SIZE)
|
result =
|
||||||
|
crypto.bearVerifyECPem(data, secret, signature, addr sha512Vtable, sha512SIZE)
|
||||||
else:
|
else:
|
||||||
assert(false, "Not implemented")
|
assert(false, "Not implemented")
|
||||||
|
|
||||||
@@ -130,16 +143,44 @@ proc sign*(token: var JWT, secret: string) =
|
|||||||
|
|
||||||
# Verify a token typically an incoming request
|
# Verify a token typically an incoming request
|
||||||
proc verify*(token: JWT, secret: string, alg: SignatureAlgorithm): bool =
|
proc verify*(token: JWT, secret: string, alg: SignatureAlgorithm): bool =
|
||||||
token.header.alg == alg and verifySignature(token.loaded, token.signature, secret, alg)
|
token.header.alg == alg and verifySignature(
|
||||||
|
token.loaded, token.signature, secret, alg
|
||||||
|
)
|
||||||
|
|
||||||
proc toString*(token: JWT): string =
|
proc toString*(token: JWT): string =
|
||||||
token.header.toBase64 & "." & token.claims.toBase64 & "." & token.signatureToB64
|
token.header.toBase64 & "." & token.claims.toBase64 & "." & token.signatureToB64
|
||||||
|
|
||||||
|
proc toFlattenedJson*(token: JWT, unprotectedHeader: bool = false): JsonNode =
|
||||||
|
# Converts JWT to flattened JSON format
|
||||||
|
# https://datatracker.ietf.org/doc/html/rfc7515#section-7.2.2
|
||||||
|
|
||||||
|
if not unprotectedHeader:
|
||||||
|
return
|
||||||
|
%*{
|
||||||
|
"payload": token.claims.toBase64,
|
||||||
|
"protected": token.header.toBase64,
|
||||||
|
"signature": token.signatureToB64,
|
||||||
|
}
|
||||||
|
|
||||||
|
# protected field is the base64url of "typ" and "alg"
|
||||||
|
let protected = %*{"typ": token.header["typ"], "alg": token.header["alg"]}
|
||||||
|
|
||||||
|
# (unprotected) header field contains all other header keys and not on base64url
|
||||||
|
var header = %*{}
|
||||||
|
for key in token.header.keys:
|
||||||
|
if not protected.hasKey(key):
|
||||||
|
header[key] = token.header[key]
|
||||||
|
return
|
||||||
|
%*{
|
||||||
|
"payload": token.claims.toBase64,
|
||||||
|
"protected": protected.toBase64,
|
||||||
|
"header": header,
|
||||||
|
"signature": token.signatureToB64,
|
||||||
|
}
|
||||||
|
|
||||||
proc `$`*(token: JWT): string =
|
proc `$`*(token: JWT): string =
|
||||||
token.toString
|
token.toString
|
||||||
|
|
||||||
|
|
||||||
proc `%`*(token: JWT): JsonNode =
|
proc `%`*(token: JWT): JsonNode =
|
||||||
let s = $token
|
let s = $token
|
||||||
%s
|
%s
|
||||||
@@ -153,7 +194,7 @@ proc verifyTimeClaims*(token: JWT) =
|
|||||||
|
|
||||||
if token.claims.hasKey("exp"):
|
if token.claims.hasKey("exp"):
|
||||||
let exp = token.claims["exp"].getClaimTime
|
let exp = token.claims["exp"].getClaimTime
|
||||||
if now > exp :
|
if now > exp:
|
||||||
raise newException(InvalidToken, "Token is expired")
|
raise newException(InvalidToken, "Token is expired")
|
||||||
|
|
||||||
# Verify token nbf exp
|
# Verify token nbf exp
|
||||||
|
|||||||
106
tests/t_jwt.nim
106
tests/t_jwt.nim
@@ -10,10 +10,8 @@ proc getToken(claims: JsonNode = newJObject(), header: JsonNode = newJObject()):
|
|||||||
initJWT(header.toHeader, claims.toClaims)
|
initJWT(header.toHeader, claims.toClaims)
|
||||||
|
|
||||||
proc tokenWithAlg(alg: string): JWT =
|
proc tokenWithAlg(alg: string): JWT =
|
||||||
let header = %*{ "typ": "JWT", "alg": alg }
|
let header = %*{"typ": "JWT", "alg": alg}
|
||||||
let claims = %*{ "sub": "1234567890",
|
let claims = %*{"sub": "1234567890", "name": "John Doe", "iat": 1516239022}
|
||||||
"name": "John Doe",
|
|
||||||
"iat": 1516239022 }
|
|
||||||
initJWT(header.toHeader, claims.toClaims)
|
initJWT(header.toHeader, claims.toClaims)
|
||||||
|
|
||||||
proc signedHSToken(alg: string): JWT =
|
proc signedHSToken(alg: string): JWT =
|
||||||
@@ -21,7 +19,8 @@ proc signedHSToken(alg: string): JWT =
|
|||||||
result.sign("your-256-secret")
|
result.sign("your-256-secret")
|
||||||
|
|
||||||
const
|
const
|
||||||
rsPrivateKey = """-----BEGIN RSA PRIVATE KEY-----
|
rsPrivateKey =
|
||||||
|
"""-----BEGIN RSA PRIVATE KEY-----
|
||||||
MIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWw
|
MIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWw
|
||||||
kWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mr
|
kWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mr
|
||||||
m/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEi
|
m/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEi
|
||||||
@@ -48,7 +47,8 @@ NvVi5vcba9oGdElJX3e9mxqUKMrw7msJJv1MX8LWyMQC5L6YNYHDfbPF1q5L4i8j
|
|||||||
y18Ae9n7dHVueyslrb6weq7dTkYDi3iOYRW8HRkIQh06wEdbxt0shTzAJvvCQfrB
|
y18Ae9n7dHVueyslrb6weq7dTkYDi3iOYRW8HRkIQh06wEdbxt0shTzAJvvCQfrB
|
||||||
jg/3747WSsf/zBTcHihTRBdAv6OmdhV4/dD5YBfLAkLrd+mX7iE=
|
jg/3747WSsf/zBTcHihTRBdAv6OmdhV4/dD5YBfLAkLrd+mX7iE=
|
||||||
-----END RSA PRIVATE KEY-----"""
|
-----END RSA PRIVATE KEY-----"""
|
||||||
rsPublicKey = """-----BEGIN PUBLIC KEY-----
|
rsPublicKey =
|
||||||
|
"""-----BEGIN PUBLIC KEY-----
|
||||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
|
||||||
vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
|
vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
|
||||||
aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
|
aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
|
||||||
@@ -57,28 +57,33 @@ e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb
|
|||||||
V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
|
V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
|
||||||
MwIDAQAB
|
MwIDAQAB
|
||||||
-----END PUBLIC KEY-----"""
|
-----END PUBLIC KEY-----"""
|
||||||
ec256PrivKey = """-----BEGIN PRIVATE KEY-----
|
ec256PrivKey =
|
||||||
|
"""-----BEGIN PRIVATE KEY-----
|
||||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
|
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
|
||||||
OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
|
OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
|
||||||
1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G
|
1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G
|
||||||
-----END PRIVATE KEY-----"""
|
-----END PRIVATE KEY-----"""
|
||||||
ec256PubKey = """-----BEGIN PUBLIC KEY-----
|
ec256PubKey =
|
||||||
|
"""-----BEGIN PUBLIC KEY-----
|
||||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
|
||||||
q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
|
q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
|
||||||
-----END PUBLIC KEY-----"""
|
-----END PUBLIC KEY-----"""
|
||||||
|
|
||||||
ec384PrivKey = """-----BEGIN EC PRIVATE KEY-----
|
ec384PrivKey =
|
||||||
|
"""-----BEGIN EC PRIVATE KEY-----
|
||||||
MIGkAgEBBDCAHpFQ62QnGCEvYh/pE9QmR1C9aLcDItRbslbmhen/h1tt8AyMhske
|
MIGkAgEBBDCAHpFQ62QnGCEvYh/pE9QmR1C9aLcDItRbslbmhen/h1tt8AyMhske
|
||||||
enT+rAyyPhGgBwYFK4EEACKhZANiAAQLW5ZJePZzMIPAxMtZXkEWbDF0zo9f2n4+
|
enT+rAyyPhGgBwYFK4EEACKhZANiAAQLW5ZJePZzMIPAxMtZXkEWbDF0zo9f2n4+
|
||||||
T1h/2sh/fviblc/VTyrv10GEtIi5qiOy85Pf1RRw8lE5IPUWpgu553SteKigiKLU
|
T1h/2sh/fviblc/VTyrv10GEtIi5qiOy85Pf1RRw8lE5IPUWpgu553SteKigiKLU
|
||||||
PeNpbqmYZUkWGh3MLfVzLmx85ii2vMU=
|
PeNpbqmYZUkWGh3MLfVzLmx85ii2vMU=
|
||||||
-----END EC PRIVATE KEY-----"""
|
-----END EC PRIVATE KEY-----"""
|
||||||
ec384PubKey = """-----BEGIN PUBLIC KEY-----
|
ec384PubKey =
|
||||||
|
"""-----BEGIN PUBLIC KEY-----
|
||||||
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEC1uWSXj2czCDwMTLWV5BFmwxdM6PX9p+
|
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEC1uWSXj2czCDwMTLWV5BFmwxdM6PX9p+
|
||||||
Pk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii
|
Pk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii
|
||||||
1D3jaW6pmGVJFhodzC31cy5sfOYotrzF
|
1D3jaW6pmGVJFhodzC31cy5sfOYotrzF
|
||||||
-----END PUBLIC KEY-----"""
|
-----END PUBLIC KEY-----"""
|
||||||
ec512PrivKey = """-----BEGIN EC PRIVATE KEY-----
|
ec512PrivKey =
|
||||||
|
"""-----BEGIN EC PRIVATE KEY-----
|
||||||
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBiyAa7aRHFDCh2qga
|
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBiyAa7aRHFDCh2qga
|
||||||
9sTUGINE5jHAFnmM8xWeT/uni5I4tNqhV5Xx0pDrmCV9mbroFtfEa0XVfKuMAxxf
|
9sTUGINE5jHAFnmM8xWeT/uni5I4tNqhV5Xx0pDrmCV9mbroFtfEa0XVfKuMAxxf
|
||||||
Z6LM/yKhgYkDgYYABAGBzgdnP798FsLuWYTDDQA7c0r3BVk8NnRUSexpQUsRilPN
|
Z6LM/yKhgYkDgYYABAGBzgdnP798FsLuWYTDDQA7c0r3BVk8NnRUSexpQUsRilPN
|
||||||
@@ -86,14 +91,14 @@ v3SchO0lRw9Ru86x1khnVDx+duq4BiDFcvlSAcyjLACJvjvoyTLJiA+TQFdmrear
|
|||||||
jMiZNE25pT2yWP1NUndJxPcvVtfBW48kPOmvkY4WlqP5bAwCXwbsKrCgk6xbsp12
|
jMiZNE25pT2yWP1NUndJxPcvVtfBW48kPOmvkY4WlqP5bAwCXwbsKrCgk6xbsp12
|
||||||
ew==
|
ew==
|
||||||
-----END EC PRIVATE KEY-----"""
|
-----END EC PRIVATE KEY-----"""
|
||||||
ec512PubKey = """-----BEGIN PUBLIC KEY-----
|
ec512PubKey =
|
||||||
|
"""-----BEGIN PUBLIC KEY-----
|
||||||
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ
|
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ
|
||||||
PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47
|
PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47
|
||||||
6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM
|
6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM
|
||||||
Al8G7CqwoJOsW7Kddns=
|
Al8G7CqwoJOsW7Kddns=
|
||||||
-----END PUBLIC KEY-----"""
|
-----END PUBLIC KEY-----"""
|
||||||
|
|
||||||
|
|
||||||
proc signedRSToken(alg: string): JWT =
|
proc signedRSToken(alg: string): JWT =
|
||||||
result = tokenWithAlg(alg)
|
result = tokenWithAlg(alg)
|
||||||
result.sign(rsPrivateKey)
|
result.sign(rsPrivateKey)
|
||||||
@@ -132,16 +137,22 @@ suite "Token tests":
|
|||||||
test "HS Signature":
|
test "HS Signature":
|
||||||
# Checked with https://jwt.io/
|
# Checked with https://jwt.io/
|
||||||
check:
|
check:
|
||||||
$signedHSToken("HS256") == "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.sBnEuqpBDTh4Q9wnxfhWKHPbbspoz-qPNxXqVSS7ZYE"
|
$signedHSToken("HS256") ==
|
||||||
$signedHSToken("HS384") == "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.e-lF0-wO2pi5y5fCOHPFLTuHqm2hR1LIX3gaCz0xI_Nvw-KPNIpkKVcbxWl2pPz8"
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.sBnEuqpBDTh4Q9wnxfhWKHPbbspoz-qPNxXqVSS7ZYE"
|
||||||
$signedHSToken("HS512") == "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.oAFx4658Y0Bbjko7Vm-X1AUd4XRjvnuznZk8cihzDuIRSZQjXnveoKuj8PIkAWviz-5c--R1HSyM6HZuONtrLQ"
|
$signedHSToken("HS384") ==
|
||||||
|
"eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.e-lF0-wO2pi5y5fCOHPFLTuHqm2hR1LIX3gaCz0xI_Nvw-KPNIpkKVcbxWl2pPz8"
|
||||||
|
$signedHSToken("HS512") ==
|
||||||
|
"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.oAFx4658Y0Bbjko7Vm-X1AUd4XRjvnuznZk8cihzDuIRSZQjXnveoKuj8PIkAWviz-5c--R1HSyM6HZuONtrLQ"
|
||||||
|
|
||||||
test "RS Signature":
|
test "RS Signature":
|
||||||
# Checked with https://jwt.io/
|
# Checked with https://jwt.io/
|
||||||
check:
|
check:
|
||||||
$signedRSToken("RS256") == "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.O2LIRo2GPEVHQCG3nHGvvY89__LgKLPo9EYXLDzH3oQnh_hZvlk350htpqaNMowOlxYGdM77oLsdHVxzFdto9c1pCH0jBG-HXzIKm131QxsZzCyO8ovW_2i6PGeNvsiaggrkdmOKcWcyMksasJcuqIf0h_fWhiK4wdq41Ls8ujLJpQBF3XNzOPt90so7XEvkY0zDVS0N3Bi6Hz5cN101FJFyMcDnq_3QSGMWPy829vC8PT8C0WCBIs7VdK9tEwIvpDENhRRj6cxhUqLCC0ALoynZYBeMcvOWQcz-LqbWuQGvuH2HGsN9zCpbaTdkiupNX__DKG0HUijnesYn1DkY2g"
|
$signedRSToken("RS256") ==
|
||||||
$signedRSToken("RS384") == "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.OGwjm7YvGCh4gpIuFnM7K88_dEeiSAWzpR0dXzhsne1IPygnXRKoTCdmhA2a01Mj_cW6tWhufSGcuu-7vmdm5Hi8hoDe5Q92kmM44oWikKptCy_zIM_Roe30TPjXxweE_WjV1fMZaAX6UFumikrtWCTcb9rLnSjpHYFgo-buS7cBXg_nK7xgOPz-bQvv8edVWsBWPf92B9Mak-LNZla_F5EAOjXrN16ZQ1y4qE94ro051kryqUddfVonmLSjCrCavttfBMugYf-SCbLp0w_QLaT9gA_bMXVzqyLnIj74Sr_JCWAxcYU5RaFmqZLEpowyp-m9XGdBwVS2118K0TooZg"
|
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.O2LIRo2GPEVHQCG3nHGvvY89__LgKLPo9EYXLDzH3oQnh_hZvlk350htpqaNMowOlxYGdM77oLsdHVxzFdto9c1pCH0jBG-HXzIKm131QxsZzCyO8ovW_2i6PGeNvsiaggrkdmOKcWcyMksasJcuqIf0h_fWhiK4wdq41Ls8ujLJpQBF3XNzOPt90so7XEvkY0zDVS0N3Bi6Hz5cN101FJFyMcDnq_3QSGMWPy829vC8PT8C0WCBIs7VdK9tEwIvpDENhRRj6cxhUqLCC0ALoynZYBeMcvOWQcz-LqbWuQGvuH2HGsN9zCpbaTdkiupNX__DKG0HUijnesYn1DkY2g"
|
||||||
$signedRSToken("RS512") == "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.dgb3ak_0nwQyLa2Ssmq3Jok-pr9QfVFnw_63YlFXTkq_V8r816VeCOzBRYvVv6ONvKGZDR_3SAqf3UJp1XkXtN-VyJ7VRoSHZ0d0-3DPArxDrIu20uvoQrbm4LqQtwbGPH-B-Z-7Bvfng-iwhOt1S717AepZsgVjQz2gOvBvzFsg_BDZ6nhU-5GOnIRkJ2amUt5N1TXbzKHkNLtMpKlq1BZbdv_xKSHgw_IHQRl9lIIQs_2_NuTgk8nQQiwtb9L1v3Y3KYpYGCBvgohWDcpyUKOv5f2EHekDpj1f_ALltd8gzWhIDgwK5VbBo8JAkLWDRfeTOS0fh0Faenfn551wqA"
|
$signedRSToken("RS384") ==
|
||||||
|
"eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.OGwjm7YvGCh4gpIuFnM7K88_dEeiSAWzpR0dXzhsne1IPygnXRKoTCdmhA2a01Mj_cW6tWhufSGcuu-7vmdm5Hi8hoDe5Q92kmM44oWikKptCy_zIM_Roe30TPjXxweE_WjV1fMZaAX6UFumikrtWCTcb9rLnSjpHYFgo-buS7cBXg_nK7xgOPz-bQvv8edVWsBWPf92B9Mak-LNZla_F5EAOjXrN16ZQ1y4qE94ro051kryqUddfVonmLSjCrCavttfBMugYf-SCbLp0w_QLaT9gA_bMXVzqyLnIj74Sr_JCWAxcYU5RaFmqZLEpowyp-m9XGdBwVS2118K0TooZg"
|
||||||
|
$signedRSToken("RS512") ==
|
||||||
|
"eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.dgb3ak_0nwQyLa2Ssmq3Jok-pr9QfVFnw_63YlFXTkq_V8r816VeCOzBRYvVv6ONvKGZDR_3SAqf3UJp1XkXtN-VyJ7VRoSHZ0d0-3DPArxDrIu20uvoQrbm4LqQtwbGPH-B-Z-7Bvfng-iwhOt1S717AepZsgVjQz2gOvBvzFsg_BDZ6nhU-5GOnIRkJ2amUt5N1TXbzKHkNLtMpKlq1BZbdv_xKSHgw_IHQRl9lIIQs_2_NuTgk8nQQiwtb9L1v3Y3KYpYGCBvgohWDcpyUKOv5f2EHekDpj1f_ALltd8gzWhIDgwK5VbBo8JAkLWDRfeTOS0fh0Faenfn551wqA"
|
||||||
|
|
||||||
signedRSToken("RS256").verify(rsPublicKey, RS256)
|
signedRSToken("RS256").verify(rsPublicKey, RS256)
|
||||||
signedRSToken("RS384").verify(rsPublicKey, RS384)
|
signedRSToken("RS384").verify(rsPublicKey, RS384)
|
||||||
@@ -150,8 +161,10 @@ suite "Token tests":
|
|||||||
test "EC Signature":
|
test "EC Signature":
|
||||||
# Checked with https://jwt.io/
|
# Checked with https://jwt.io/
|
||||||
check:
|
check:
|
||||||
signedECToken("ES256", ec256PrivKey).header.toBase64 == "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9"
|
signedECToken("ES256", ec256PrivKey).header.toBase64 ==
|
||||||
signedECToken("ES256", ec256PrivKey).claims.toBase64 == "eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ"
|
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9"
|
||||||
|
signedECToken("ES256", ec256PrivKey).claims.toBase64 ==
|
||||||
|
"eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ"
|
||||||
|
|
||||||
# We don't check signatures, as for ES* algorithms they are random
|
# We don't check signatures, as for ES* algorithms they are random
|
||||||
|
|
||||||
@@ -160,17 +173,50 @@ suite "Token tests":
|
|||||||
signedECToken("ES512", ec512PrivKey).verify(ec512PubKey, ES512)
|
signedECToken("ES512", ec512PrivKey).verify(ec512PubKey, ES512)
|
||||||
|
|
||||||
test "header values":
|
test "header values":
|
||||||
var token = toJWT(%*{
|
var token = toJWT(
|
||||||
"header": {
|
%*{
|
||||||
"alg": "HS256",
|
"header": {"alg": "HS256", "kid": "something", "typ": "JWT"},
|
||||||
"kid": "something",
|
"claims": {"userId": 1},
|
||||||
"typ": "JWT"
|
|
||||||
},
|
|
||||||
"claims": {
|
|
||||||
"userId": 1
|
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
token.sign(rsPrivateKey)
|
token.sign(rsPrivateKey)
|
||||||
let signed = $token
|
let signed = $token
|
||||||
let decoded = signed.toJWT()
|
let decoded = signed.toJWT()
|
||||||
check decoded.header["kid"].getStr() == "something"
|
check decoded.header["kid"].getStr() == "something"
|
||||||
|
|
||||||
|
test "toFlaflattenedJson":
|
||||||
|
var token = toJWT(
|
||||||
|
%*{
|
||||||
|
"header": {"alg": "HS256", "kid": "something", "typ": "JWT"},
|
||||||
|
"claims": {"userId": 1},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
token.sign(rsPrivateKey)
|
||||||
|
let expectedFlattened =
|
||||||
|
%*{
|
||||||
|
"payload": "eyJ1c2VySWQiOjF9",
|
||||||
|
"protected": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNvbWV0aGluZyJ9",
|
||||||
|
"signature": "JlHgw86VQ7xgOn1ACnwqjXfU28CHD_9GrCMu9JO0rr4",
|
||||||
|
}
|
||||||
|
|
||||||
|
check expectedFlattened == token.toFlattenedJson
|
||||||
|
|
||||||
|
test "toFlaflattenedJson with unprotectedHeader":
|
||||||
|
var token = toJWT(
|
||||||
|
%*{
|
||||||
|
"header": {"alg": "HS256", "kid": "something", "typ": "JWT"},
|
||||||
|
"claims": {"userId": 1},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
token.sign(rsPrivateKey)
|
||||||
|
let expectedFlattenedUnprotected =
|
||||||
|
%*{
|
||||||
|
"payload": "eyJ1c2VySWQiOjF9",
|
||||||
|
"protected": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9",
|
||||||
|
"header": {"kid": "something"},
|
||||||
|
"signature": "JlHgw86VQ7xgOn1ACnwqjXfU28CHD_9GrCMu9JO0rr4",
|
||||||
|
}
|
||||||
|
|
||||||
|
check expectedFlattenedUnprotected == token.toFlattenedJson(
|
||||||
|
unprotectedHeader = true
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user