Link fixes + tests

This commit is contained in:
Yuriy Glukhov
2019-06-13 16:20:50 +03:00
parent 4d15cef10f
commit 4738c5eeaa
10 changed files with 73 additions and 28 deletions

142
jwt/private/claims.nim Normal file
View 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
View 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
View 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
View 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('/', '_')