all repos — litestore @ 11fee4a9e94a6aa915e4e3fbcf57dbf832b3d83f

A minimalist nosql document store.

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)