mirror of
https://github.com/vacp2p/nim-jwt.git
synced 2026-01-09 20:27:56 -05:00
Link fixes + tests
This commit is contained in:
142
jwt/private/claims.nim
Normal file
142
jwt/private/claims.nim
Normal file
@@ -0,0 +1,142 @@
|
||||
import json, sequtils, strutils, times, tables
|
||||
|
||||
import utils
|
||||
|
||||
|
||||
type
|
||||
InvalidClaim = object of Exception
|
||||
ClaimKind* = enum
|
||||
ISS,
|
||||
SUB,
|
||||
NBF,
|
||||
EXP,
|
||||
AUD,
|
||||
IAT,
|
||||
JTI,
|
||||
GENERAL
|
||||
|
||||
Claim* = ref ClaimObj
|
||||
ClaimObj* {.acyclic.} = object
|
||||
node*: JsonNode
|
||||
kind*: ClaimKind
|
||||
|
||||
|
||||
|
||||
proc newClaims*(claims: varargs[tuple[key: string, val: Claim]]): TableRef[string, Claim] =
|
||||
result = newTable[string, Claim](claims)
|
||||
|
||||
|
||||
proc newClaim*(k: ClaimKind, node: JsonNode): Claim =
|
||||
new result
|
||||
result.kind = k
|
||||
result.node = node
|
||||
|
||||
# ISS
|
||||
proc newISS*(node: JsonNode): Claim =
|
||||
checkJsonNodeKind(node, JString)
|
||||
return newClaim(ISS, node)
|
||||
|
||||
# SUB
|
||||
proc newSUB*(node: JsonNode): Claim =
|
||||
checkJsonNodeKind(node, JString)
|
||||
return newClaim(SUB, node)
|
||||
|
||||
# AUD
|
||||
proc newAUD*(node: JsonNode): Claim =
|
||||
if node.kind != JArray and node.kind != JString:
|
||||
raise newException(ValueError, "Invalid kind")
|
||||
return newClaim(AUD, node)
|
||||
|
||||
proc newAUD*(recipients: seq[string]): Claim =
|
||||
var node = newJArray()
|
||||
for r in recipients:
|
||||
node.add(%r)
|
||||
result = newAUD(node)
|
||||
|
||||
proc newAUD*(recipient: string): Claim = return newAUD(@[recipient])
|
||||
|
||||
proc newAUD*(recipients: varargs[string]): Claim = return newAUD(@recipients)
|
||||
|
||||
|
||||
# Claims that have any kind of time
|
||||
proc newTimeClaim*(k: ClaimKind, j: JsonNode): Claim =
|
||||
# Check that the json kind is int..
|
||||
checkJsonNodeKind(j, JInt)
|
||||
return newClaim(k, j)
|
||||
|
||||
proc newTimeClaim*(k: ClaimKind, s: string): Claim =
|
||||
return newTimeClaim(k, %parseInt(s))
|
||||
|
||||
proc newTimeClaim*(k: ClaimKind, i: int64): Claim =
|
||||
return newTimeClaim(k, %i)
|
||||
|
||||
# Returns the claimKeyms value as a time
|
||||
proc getClaimTime*(c: Claim): Time =
|
||||
result = fromUnix(c.node.num)
|
||||
|
||||
# NBF
|
||||
proc newNBF*(s: string): Claim = return newTimeClaim(NBF, s)
|
||||
|
||||
proc newNBF*(j: JsonNode): Claim = return newTimeClaim(NBF, j)
|
||||
|
||||
proc newNBF*(i: int64): Claim = return newTimeClaim(NBF, i)
|
||||
|
||||
# EXP
|
||||
proc newEXP*(s: string): Claim = return newTimeClaim(EXP, s)
|
||||
|
||||
proc newEXP*(j: JsonNode): Claim = return newTimeClaim(EXP, j)
|
||||
|
||||
proc newEXP*(i: int64): Claim = return newTimeClaim(EXP, i)
|
||||
|
||||
# IAT
|
||||
proc newIAT*(s: string): Claim = return newTimeClaim(IAT, s)
|
||||
|
||||
proc newIAT*(j: JsonNode): Claim = return newTimeClaim(IAT, j)
|
||||
|
||||
proc newIAT*(i: int64): Claim = return newTimeClaim(IAT, i)
|
||||
|
||||
# JTI
|
||||
proc newJTI*(j: JsonNode): Claim =
|
||||
assert j.kind == JString
|
||||
return newClaim(JTI, j)
|
||||
|
||||
proc newJTI*(s: string): Claim =
|
||||
return newJTI(%s)
|
||||
|
||||
|
||||
proc toClaims*(j: JsonNode): TableRef[string, Claim] =
|
||||
result = newClaims()
|
||||
|
||||
for claimKey, claimNode in j:
|
||||
case claimKey:
|
||||
of "iss":
|
||||
result[claimKey] = newISS(claimNode)
|
||||
of "sub":
|
||||
result[claimKey] = newSUB(claimNode)
|
||||
of "aud":
|
||||
result[claimKey] = newAUD(claimNode)
|
||||
of "nbf":
|
||||
result[claimKey] = newNBF(claimNode)
|
||||
of "exp":
|
||||
result[claimKey] = newEXP(claimNode)
|
||||
of "iat":
|
||||
result[claimKey] = newIAT(claimNode)
|
||||
of "jti":
|
||||
result[claimKey] = newJTI(claimNode)
|
||||
else:
|
||||
result[claimKey] = newClaim(GENERAL, claimNode)
|
||||
|
||||
|
||||
proc `%`*(c: Claim): JsonNode =
|
||||
result = c.node
|
||||
|
||||
|
||||
proc `%`*(claims: TableRef[string, Claim]): JsonNode =
|
||||
result = newJObject()
|
||||
for k, v in claims:
|
||||
result[k] = %v
|
||||
|
||||
|
||||
proc toBase64*(claims: TableRef[string, Claim]): string =
|
||||
let asJson = %claims
|
||||
result = encodeUrlSafe($asJson)
|
||||
112
jwt/private/crypto.nim
Normal file
112
jwt/private/crypto.nim
Normal file
@@ -0,0 +1,112 @@
|
||||
import openssl, linktools
|
||||
|
||||
# TODO: Linkage flags should probably need more attention because of different
|
||||
# openssl versions. E.g. DigestSign* functions are not available in old openssl.
|
||||
when defined(macosx):
|
||||
const libcrypto = "crypto.35"
|
||||
else:
|
||||
const libcrypto = "crypto"
|
||||
|
||||
{.passL: "-l" & libcrypto.}
|
||||
|
||||
export EVP_PKEY_RSA
|
||||
|
||||
const
|
||||
HMAC_MAX_MD_CBLOCK* = 128
|
||||
|
||||
const sslIsOld = libHasSymbol(libcrypto, "EVP_MD_CTX_create")
|
||||
|
||||
type
|
||||
EVP_MD* = SslPtr
|
||||
EVP_MD_CTX* = SslPtr
|
||||
EVP_PKEY_CTX* = SslPtr
|
||||
ENGINE* = SslPtr
|
||||
|
||||
proc EVP_md_null*(): EVP_MD {.cdecl, importc.}
|
||||
proc EVP_md2*(): EVP_MD {.cdecl, importc.}
|
||||
proc EVP_md4*(): EVP_MD {.cdecl, importc.}
|
||||
proc EVP_md5*(): EVP_MD {.cdecl, importc.}
|
||||
proc EVP_sha*(): EVP_MD {.cdecl, importc.}
|
||||
proc EVP_sha1*(): EVP_MD {.cdecl, importc.}
|
||||
proc EVP_dss*(): EVP_MD {.cdecl, importc.}
|
||||
proc EVP_dss1*(): EVP_MD {.cdecl, importc.}
|
||||
proc EVP_ecdsa*(): EVP_MD {.cdecl, importc.}
|
||||
proc EVP_sha224*(): EVP_MD {.cdecl, importc.}
|
||||
proc EVP_sha256*(): EVP_MD {.cdecl, importc.}
|
||||
proc EVP_sha384*(): EVP_MD {.cdecl, importc.}
|
||||
proc EVP_sha512*(): EVP_MD {.cdecl, importc.}
|
||||
proc EVP_mdc2*(): EVP_MD {.cdecl, importc.}
|
||||
proc EVP_ripemd160*(): EVP_MD {.cdecl, importc.}
|
||||
proc EVP_whirlpool*(): EVP_MD {.cdecl, importc.}
|
||||
|
||||
proc HMAC*(evp_md: EVP_MD; key: pointer; key_len: cint; d: cstring;
|
||||
n: csize; md: cstring; md_len: ptr cuint): cstring {.cdecl, importc.}
|
||||
|
||||
proc PEM_read_bio_PrivateKey*(bp: BIO, x: ptr EVP_PKEY,
|
||||
cb: pointer, u: pointer): EVP_PKEY {.cdecl, importc.}
|
||||
proc EVP_PKEY_free*(p: EVP_PKEY) {.cdecl, importc.}
|
||||
|
||||
|
||||
when sslIsOld:
|
||||
proc EVP_MD_CTX_create*(): EVP_MD_CTX {.cdecl, importc.}
|
||||
proc EVP_MD_CTX_destroy*(ctx: EVP_MD_CTX) {.cdecl, importc.}
|
||||
else:
|
||||
# some times you will need this instead:
|
||||
proc EVP_MD_CTX_create*(): EVP_MD_CTX {.cdecl, importc: "EVP_MD_CTX_new".}
|
||||
proc EVP_MD_CTX_destroy*(ctx: EVP_MD_CTX) {.cdecl, importc: "EVP_MD_CTX_free".}
|
||||
|
||||
proc EVP_DigestSignInit*(ctx: EVP_MD_CTX, pctx: ptr EVP_PKEY_CTX, typ: EVP_MD, e: ENGINE, pkey: EVP_PKEY): cint {.cdecl, importc.}
|
||||
|
||||
proc EVP_DigestSignUpdate*(ctx: EVP_MD_CTX, data: pointer, len: cuint): cint {.cdecl, importc: "EVP_DigestUpdate".}
|
||||
proc EVP_DigestSignFinal*(ctx: EVP_MD_CTX, data: pointer, len: ptr csize): cint {.cdecl, importc.}
|
||||
|
||||
proc EVP_PKEY_CTX_new*(pkey: EVP_PKEY, e: ENGINE): EVP_PKEY_CTX {.cdecl, importc.}
|
||||
proc EVP_PKEY_sign_init*(c: EVP_PKEY_CTX): cint {.cdecl, importc.}
|
||||
|
||||
when not declared(BIO_new_mem_buf):
|
||||
proc BIO_new_mem_buf*(data: pointer, len: cint): BIO{.cdecl, importc.}
|
||||
|
||||
proc signPem*(data, key: string, alg: EVP_MD, typ: cint): seq[uint8] =
|
||||
var bufkey: BIO
|
||||
var pkey: EVP_PKEY
|
||||
var mdctx: EVP_MD_CTX
|
||||
|
||||
defer:
|
||||
if not bufkey.isNil: discard BIO_free(bufkey)
|
||||
if not pkey.isNil: EVP_PKEY_free(pkey)
|
||||
if not mdctx.isNil: EVP_MD_CTX_destroy(mdctx)
|
||||
|
||||
bufkey = BIO_new_mem_buf(unsafeAddr key[0], cint(key.len))
|
||||
if bufkey.isNil:
|
||||
raise newException(Exception, "Out of memory")
|
||||
pkey = PEM_read_bio_PrivateKey(bufkey, nil, nil, nil)
|
||||
if pkey.isNil:
|
||||
raise newException(Exception, "Invalid value")
|
||||
mdctx = EVP_MD_CTX_create()
|
||||
if mdctx.isNil:
|
||||
raise newException(Exception, "Out of memory")
|
||||
|
||||
let pkeyCtx = EVP_PKEY_CTX_new(pkey, nil)
|
||||
if EVP_PKEY_sign_init(pkeyCtx) <= 0:
|
||||
raise newException(Exception, "Invalid value")
|
||||
|
||||
# Initialize the DigestSign operation using alg
|
||||
if EVP_DigestSignInit(mdctx, nil, alg, nil, pkey) != 1:
|
||||
raise newException(Exception, "Invalid value")
|
||||
|
||||
# Call update with the message
|
||||
if EVP_DigestSignUpdate(mdctx, unsafeAddr data[0], cuint(data.len)) != 1:
|
||||
raise newException(Exception, "Invalid value")
|
||||
|
||||
# First, call EVP_DigestSignFinal with a NULL sig parameter to get length
|
||||
# of sig. Length is returned in slen
|
||||
var slen: csize
|
||||
if EVP_DigestSignFinal(mdctx, nil, addr slen) != 1:
|
||||
raise newException(Exception, "Invalid value")
|
||||
|
||||
# Allocate memory for signature based on returned size
|
||||
result = newSeq[uint8](slen)
|
||||
|
||||
# Get the signature
|
||||
if EVP_DigestSignFinal(mdctx, addr result[0], addr slen) != 1:
|
||||
raise newException(Exception, "Invalid value")
|
||||
58
jwt/private/jose.nim
Normal file
58
jwt/private/jose.nim
Normal file
@@ -0,0 +1,58 @@
|
||||
import json, strutils, tables
|
||||
|
||||
import utils
|
||||
|
||||
type
|
||||
CryptoException* = object of Exception
|
||||
UnsupportedAlgorithm* = object of CryptoException
|
||||
|
||||
SignatureAlgorithm* = enum
|
||||
NONE
|
||||
HS256
|
||||
HS384
|
||||
HS512
|
||||
RS256
|
||||
RS384
|
||||
RS512
|
||||
ES384
|
||||
|
||||
JOSEHeader* = object
|
||||
alg*: SignatureAlgorithm
|
||||
typ*: string
|
||||
|
||||
|
||||
proc strToSignatureAlgorithm(s: string): SignatureAlgorithm =
|
||||
try:
|
||||
result = parseEnum[SignatureAlgorithm](s)
|
||||
except ValueError:
|
||||
raise newException(UnsupportedAlgorithm, "$# isn't supported" % s)
|
||||
|
||||
|
||||
proc toHeader*(j: JsonNode): JOSEHeader =
|
||||
let algStr = j["alg"].str
|
||||
let algo = strToSignatureAlgorithm(algStr)
|
||||
|
||||
# Check that the keys are present so we dont blow up.
|
||||
utils.checkKeysExists(j, "alg", "typ")
|
||||
|
||||
result = JOSEHeader(
|
||||
alg: algo,
|
||||
typ: j["typ"].str
|
||||
)
|
||||
|
||||
|
||||
proc `%`*(alg: SignatureAlgorithm): JsonNode =
|
||||
let s = $alg
|
||||
return %s
|
||||
|
||||
|
||||
proc `%`*(h: JOSEHeader): JsonNode =
|
||||
return %{
|
||||
"alg": %h.alg,
|
||||
"typ": %h.typ
|
||||
}
|
||||
|
||||
|
||||
proc toBase64*(h: JOSEHeader): string =
|
||||
let asJson = %h
|
||||
result = encodeUrlSafe($asJson)
|
||||
32
jwt/private/utils.nim
Normal file
32
jwt/private/utils.nim
Normal file
@@ -0,0 +1,32 @@
|
||||
import json, strutils
|
||||
|
||||
from base64 import nil
|
||||
|
||||
|
||||
type
|
||||
KeyError = object of Exception
|
||||
|
||||
proc checkJsonNodeKind*(node: JsonNode, kind: JsonNodeKind) =
|
||||
# Check that a given JsonNode has a given kind, raise InvalidClaim if not
|
||||
if node.kind != kind:
|
||||
raise newException(ValueError, "Invalid kind")
|
||||
|
||||
|
||||
proc checkKeysExists*(node: JsonNode, keys: varargs[string]) =
|
||||
for key in keys:
|
||||
if not node.hasKey(key):
|
||||
raise newException(KeyError, "$# is not present." % key)
|
||||
|
||||
|
||||
proc encodeUrlSafe*(s: string): string =
|
||||
result = base64.encode(s, newLine="")
|
||||
while result.endsWith("="):
|
||||
result = result.substr(0, result.high-1)
|
||||
result = result.replace('+', '-').replace('/', '_')
|
||||
|
||||
|
||||
proc decodeUrlSafe*(s: string): string =
|
||||
var s = s
|
||||
while s.len mod 4 > 0:
|
||||
s &= "="
|
||||
base64.decode(s).replace('+', '-').replace('/', '_')
|
||||
Reference in New Issue
Block a user