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) |