src/litestorepkg/lib/jwt.nim
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
import std/[
openssl, base64, strutils, macros, json, times, pegs, sequtils, os
]
import types, core
when defined(windows) and defined(amd64):
{.passL: "-static -L"&getProjectPath()&"/litestorepkg/vendor/openssl/windows -lssl -lcrypto -lbcrypt".}
elif defined(linux) and defined(amd64):
{.passL: "-static -L"&getProjectPath()&"/litestorepkg/vendor/openssl/linux -lssl -lcrypto".}
elif defined(macosx) and defined(amd64):
{.passL: "-Bstatic -L"&getProjectPath()&"/litestorepkg/vendor/openssl/macosx -lssl -lcrypto -Bdynamic".}
proc EVP_PKEY_new(): EVP_PKEY {.cdecl, importc.}
proc X509_get_pubkey(cert: PX509): EVP_PKEY {.cdecl, importc.}
proc EVP_DigestVerifyInit(ctx: EVP_MD_CTX; pctx: ptr EVP_PKEY_CTX; typ: EVP_MD;
e: ENGINE; pkey: EVP_PKEY): cint {.cdecl, importc.}
proc EVP_DigestVerifyUpdate(ctx: EVP_MD_CTX; data: pointer;
len: cuint): cint {.cdecl, importc.}
proc EVP_DigestVerifyFinal(ctx: EVP_MD_CTX; data: pointer;
len: cuint): cint {.cdecl, importc.}
proc getLastError(): string =
return $ERR_error_string(ERR_get_error(), nil)
proc raiseJwtError(msg: string) =
let err = getLastError()
raise newException(EJwtValidationError, msg&"\n"&err)
proc raiseX509Error(msg: string) =
let err = getLastError()
raise newException(EX509Error, msg&"\n"&err)
proc getX5c*(LS: LiteStore; token: JWT): string =
let keys = LS.jwks["keys"]
if token.header.hasKey("kid"):
let kid = token.header["kid"].getStr
return keys.filterIt(it["kid"].getStr == kid)[0]["x5c"][0].getStr
return keys[0]["x5c"][0].getStr
proc base64UrlDecode(encoded: string): string =
let padding = 4 - (encoded.len mod 4)
let base64String = encoded.replace("-", "+").replace("_", "/") & repeat("=", padding)
result = base64.decode(base64String)
proc newJwt*(token: string): JWT =
let parts = token.split(".")
result.token = token
result.payload = parts[0]&"."&parts[1]
result.header = parts[0].base64UrlDecode.parseJson
result.claims = parts[1].base64UrlDecode.parseJson
result.signature = parts[2].base64UrlDecode
proc verifyTimeClaims*(jwt: JWT) =
let t = now().toTime.toUnix
if jwt.claims.hasKey("nbf") and jwt.claims["nbf"].getInt > t:
raiseJwtError("Token cannot be used yet.")
if jwt.claims.hasKey("exp") and jwt.claims["exp"].getInt < t:
raiseJwtError("Token has expired.")
proc verifyAlgorithm*(jwt: JWT) =
let alg = jwt.header["alg"].getStr
if alg != "RS256":
raiseJwtError("Algorithm not supported: " & alg)
proc verifyScope*(jwt: JWT; reqScope: seq[string] = @[]) =
if reqScope.len == 0:
return
var scp = newSeq[string](0)
if jwt.claims.hasKey("scp"):
scp = jwt.claims["scp"].getStr.split(peg"\s")
elif jwt.claims.hasKey("scope"):
scp = jwt.claims["scope"].getStr.split(peg"\s")
if scp.len == 0:
raiseJwtError("No scp or scope claim found in token")
var authorized = ""
for s in scp:
for r in reqScope:
if r == s:
authorized = s
break
if authorized == "":
raise newException(EUnauthorizedError, "Unauthorized")
proc verifySignature*(jwt: JWT; x5c: string) =
let sig = jwt.signature
let payload = jwt.payload
let cert = x5c.decode
var pkeyctx: EVP_PKEY_CTX
var mdctx: EVP_MD_CTX
let alg = EVP_sha256();
var x509: PX509
var pubkey = EVP_PKEY_new()
try:
### Validate Signature (Only RS256 supported)
x509 = d2i_X509(cert)
if x509.isNil:
raiseX509Error("Invalid X509 certificate")
pubkey = X509_get_pubkey(x509)
if pubkey.isNil:
raiseX509Error("An error occurred while retrieving the public key")
mdctx = EVP_MD_CTX_create()
if mdctx.isNil:
raiseX509Error("Unable to initialize MD CTX")
pkeyctx = EVP_PKEY_CTX_new(pubkey, nil)
if pkeyctx.isNil:
raiseX509Error("Unable to initialize PKEY CTX")
if EVP_DigestVerifyInit(mdctx, addr pkeyctx, alg, nil, pubkey) != 1:
raiseJwtError("Unable to initialize digest verification")
if EVP_DigestVerifyUpdate(mdctx, addr payload[0], payload.len.cuint) != 1:
raiseJwtError("Unable to update digest verification")
if EVP_DigestVerifyFinal(mdctx, addr sig[0], sig.len.cuint) != 1:
raiseJwtError("Verification failed")
except CatchableError:
let err = getCurrentException()
if not mdctx.isNil:
EVP_MD_CTX_destroy(mdctx)
if not pkeyctx.isNil:
EVP_PKEY_CTX_free(pkeyctx)
if not pubkey.isNil:
EVP_PKEY_free(pubkey)
if not x509.isNil:
X509_free(x509)
raise err
when isMainModule:
let token = "token.txt".readFile
let x5c = "x5c.cert".readFile
let jwt = token.newJwt
echo token
echo "---"
echo x5c
jwt.verifySignature(x5c)
|