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 |
import std/[ openssl, base64, strutils, macros, json, times, pegs, sequtils, os ] import types 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 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 raiseJwtError(msg: string) = raise newException(EJwtValidationError, msg) proc getX5c*(token: JWT): string = let file = getCurrentDir() / "jwks.json" if not file.fileExists: raise newException(ValueError, "JWKS file not found: " & file) let keys = file.readFile.parseJson if token.header.hasKey("kid"): let kid = token.header["kid"].getStr return keys.filterIt(it["kid"].getStr == kid)[0]["x5c"].getStr return keys[0]["x5c"].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 let alg = EVP_sha256(); var x509: PX509 var pubkey: EVP_PKEY var pkeyctx: EVP_PKEY_CTX ### Validate Signature (Only RS256 supported) x509 = d2i_X509(cert) if x509.isNil: raiseJwtError("Invalid X509 certificate") pubkey = X509_get_pubkey(x509) if pubkey.isNil: raiseJwtError("An error occurred while retrieving the public key") let mdctx = EVP_MD_CTX_create() if mdctx.isNil: raiseJwtError("Unable to initialize MD CTX") if EVP_DigestVerifyInit(mdctx, addr pkeyctx, alg, nil, pubkey) != 1: raiseJwtError("Unable to initialize digest verification") if EVP_DigestVerify_Update(mdctx, addr payload[0], payload.len.cuint) != 1: raiseJwtError("Unable to update digest verification") if EVP_DigestVerify_Final(mdctx, addr sig[0], sig.len.cuint) != 1: raiseJwtError("Verification failed") 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) |