mirror of
https://github.com/vacp2p/nim-jwt.git
synced 2026-01-09 20:27:56 -05:00
Fix 11 (#17)
* ES256, ES384, ES512 verification support. Fixes #11. * GH actions CI
This commit is contained in:
29
.github/workflows/test.yml
vendored
Normal file
29
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: CI
|
||||
|
||||
on: push
|
||||
jobs:
|
||||
Test:
|
||||
if: |
|
||||
!contains(github.event.head_commit.message, '[skip ci]')
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
nim-channel: [stable, devel]
|
||||
|
||||
name: ${{ matrix.os }}-${{ matrix.nim-channel }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup nim
|
||||
uses: jiro4989/setup-nim-action@v1
|
||||
with:
|
||||
nim-version: ${{ matrix.nim-channel }}
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
nim --version
|
||||
nimble install -dy
|
||||
nimble test
|
||||
33
.travis.yml
33
.travis.yml
@@ -1,33 +0,0 @@
|
||||
language: c
|
||||
|
||||
# https://docs.travis-ci.com/user/caching/
|
||||
#
|
||||
# Caching the whole nim folder is better than relying on ccache - this way, we
|
||||
# skip the expensive bootstrap process and linking
|
||||
cache:
|
||||
directories:
|
||||
- nim
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
install:
|
||||
# check version of remote branch
|
||||
- "export NIMVER=$(git ls-remote https://github.com/nim-lang/nim.git HEAD | cut -f 1)"
|
||||
|
||||
# after building nim, wipe csources to save on cache space
|
||||
- "{ [ -f nim/$NIMVER/bin/nim ] && [ -f nim/$NIMVER/bin/nimble ] ; } ||
|
||||
{ rm -rf nim ;
|
||||
mkdir -p nim ;
|
||||
git clone --depth=1 https://github.com/nim-lang/nim.git nim/$NIMVER ;
|
||||
cd nim/$NIMVER ;
|
||||
sh build_all.sh ;
|
||||
rm -rf csources ;
|
||||
cd ../.. ;
|
||||
}"
|
||||
- "export PATH=$PWD/nim/$NIMVER/bin:$HOME/.nimble/bin:$PATH"
|
||||
|
||||
script:
|
||||
- nimble install -y
|
||||
- nimble test
|
||||
@@ -1,4 +1,4 @@
|
||||
JWT Implementation for Nim-lang [](https://travis-ci.org/yglukhov/nim-jwt)
|
||||
JWT Implementation for Nim [](https://github.com/yglukhov/nim-jwt/actions?query=branch%3Amaster)
|
||||
===============================
|
||||
|
||||
This is a implementation of JSON Web Tokens for Nim, it allows for the following operations to be performed:
|
||||
|
||||
22
jwt.nim
22
jwt.nim
@@ -6,7 +6,7 @@ from jwt/private/crypto import nil
|
||||
import jwt/private/[claims, jose, utils]
|
||||
|
||||
type
|
||||
InvalidToken* = object of Exception
|
||||
InvalidToken* = object of ValueError
|
||||
|
||||
JWT* = object
|
||||
headerB64: string
|
||||
@@ -73,8 +73,8 @@ proc signString*(toSign: string, secret: string, algorithm: SignatureAlgorithm =
|
||||
template rsSign(hc, oid: typed, hashLen: int): seq[byte] =
|
||||
crypto.bearSignRSPem(toSign, secret, addr hc, oid, hashLen)
|
||||
|
||||
template ecSign(eng, hc: typed): seq[byte] =
|
||||
crypto.bearSignECPem(toSign, secret, addr hc, addr eng)
|
||||
template ecSign(hc: typed): seq[byte] =
|
||||
crypto.bearSignECPem(toSign, secret, addr hc)
|
||||
|
||||
case algorithm
|
||||
of HS256:
|
||||
@@ -90,11 +90,11 @@ proc signString*(toSign: string, secret: string, algorithm: SignatureAlgorithm =
|
||||
of RS512:
|
||||
return rsSign(sha512Vtable, HASH_OID_SHA512, sha512SIZE)
|
||||
of ES256:
|
||||
return ecSign(ecAllM15, sha256Vtable)
|
||||
return ecSign(sha256Vtable)
|
||||
of ES384:
|
||||
return ecSign(ecAllM15, sha384Vtable)
|
||||
return ecSign(sha384Vtable)
|
||||
of ES512:
|
||||
return ecSign(ecAllM15, sha512Vtable)
|
||||
return ecSign(sha512Vtable)
|
||||
|
||||
# of ES384:
|
||||
# return rsSign(crypto.EVP_sha384())
|
||||
@@ -114,11 +114,15 @@ proc verifySignature*(data: string, signature: seq[byte], secret: string,
|
||||
result = crypto.bearVerifyRSPem(data, secret, signature, addr sha384Vtable, HASH_OID_SHA384, sha384SIZE)
|
||||
of RS512:
|
||||
result = crypto.bearVerifyRSPem(data, secret, signature, addr sha512Vtable, HASH_OID_SHA512, sha512SIZE)
|
||||
# of ES256:
|
||||
# result = crypto.bearVerifyECPem(data, secret, signature, addr sha256Vtable, addr ecPrimeI15, sha256SIZE)
|
||||
of ES256:
|
||||
result = crypto.bearVerifyECPem(data, secret, signature, addr sha256Vtable, sha256SIZE)
|
||||
of ES384:
|
||||
result = crypto.bearVerifyECPem(data, secret, signature, addr sha384Vtable, sha384SIZE)
|
||||
of ES512:
|
||||
result = crypto.bearVerifyECPem(data, secret, signature, addr sha512Vtable, sha512SIZE)
|
||||
|
||||
else:
|
||||
assert(false, "Not implemented")
|
||||
assert(false, "Not implemented")
|
||||
|
||||
proc sign*(token: var JWT, secret: string) =
|
||||
assert token.signature.len == 0
|
||||
|
||||
@@ -4,7 +4,6 @@ import utils
|
||||
|
||||
|
||||
type
|
||||
InvalidClaim = object of Exception
|
||||
ClaimKind* = enum
|
||||
ISS,
|
||||
SUB,
|
||||
|
||||
@@ -78,7 +78,7 @@ proc bearSignRSPem*(data, key: string, alg: ptr HashClass, hashOid: cstring, has
|
||||
result = newSeqUninitialized[byte](sigLen)
|
||||
let s = rsaPkcs1SignGetDefault()
|
||||
assert(not s.isNil)
|
||||
if s(cast[ptr cuchar](hashOid), cast[ptr cuchar](addr digest[0]), hashLen, addr pk, cast[ptr cuchar](addr result[0])) != 1:
|
||||
if s(cast[ptr char](hashOid), cast[ptr char](addr digest[0]), hashLen, addr pk, cast[ptr char](addr result[0])) != 1:
|
||||
raise newException(Exception, "Could not sign")
|
||||
|
||||
proc bearVerifyRSPem*(data, key: string, sig: openarray[byte], alg: ptr HashClass, hashOid: cstring, hashLen: int): bool =
|
||||
@@ -95,48 +95,19 @@ proc bearVerifyRSPem*(data, key: string, sig: openarray[byte], alg: ptr HashClas
|
||||
let s = rsaPkcs1VrfyGetDefault()
|
||||
var digest2: array[64, byte]
|
||||
|
||||
if s(cast[ptr cuchar](unsafeAddr sig[0]), sig.len, cast[ptr cuchar](hashOid), hashLen, addr pk, cast[ptr cuchar](addr digest2[0])) != 1:
|
||||
if s(cast[ptr char](unsafeAddr sig[0]), sig.len, cast[ptr char](hashOid), hashLen, addr pk, cast[ptr char](addr digest2[0])) != 1:
|
||||
raise newException(Exception, "Could not verify")
|
||||
|
||||
digest == digest2
|
||||
|
||||
# const ecPublicKey = """-----BEGIN PUBLIC KEY-----
|
||||
# MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
|
||||
# q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
|
||||
# -----END PUBLIC KEY-----"""
|
||||
|
||||
# var EC_P256_PUB_POINT = @[
|
||||
# 0x04.uint8, 0x60, 0xFE, 0xD4, 0xBA, 0x25, 0x5A, 0x9D,
|
||||
# 0x31, 0xC9, 0x61, 0xEB, 0x74, 0xC6, 0x35, 0x6D,
|
||||
# 0x68, 0xC0, 0x49, 0xB8, 0x92, 0x3B, 0x61, 0xFA,
|
||||
# 0x6C, 0xE6, 0x69, 0x62, 0x2E, 0x60, 0xF2, 0x9F,
|
||||
# 0xB6, 0x79, 0x03, 0xFE, 0x10, 0x08, 0xB8, 0xBC,
|
||||
# 0x99, 0xA4, 0x1A, 0xE9, 0xE9, 0x56, 0x28, 0xBC,
|
||||
# 0x64, 0xF2, 0xF1, 0xB2, 0x0C, 0x2D, 0x7E, 0x9F,
|
||||
# 0x51, 0x77, 0xA3, 0xC2, 0x94, 0xD4, 0x46, 0x22,
|
||||
# 0x99
|
||||
# ]
|
||||
|
||||
# var EC_P256_PRIV_X = @[
|
||||
# 0xC9.uint8, 0xAF, 0xA9, 0xD8, 0x45, 0xBA, 0x75, 0x16,
|
||||
# 0x6B, 0x5C, 0x21, 0x57, 0x67, 0xB1, 0xD6, 0x93,
|
||||
# 0x4E, 0x50, 0xC3, 0xDB, 0x36, 0xE8, 0x9B, 0x12,
|
||||
# 0x7B, 0x8A, 0x62, 0x2B, 0x12, 0x0F, 0x67, 0x21
|
||||
# ]
|
||||
|
||||
|
||||
proc bearSignECPem*(data, key: string, alg: ptr HashClass, impl: ptr EcImpl): seq[byte] =
|
||||
# Step 1. Extract RSA key from `key` in PEM format
|
||||
proc bearSignECPem*(data, key: string, alg: ptr HashClass): seq[byte] =
|
||||
# Step 1. Extract EC Priv key from `key` in PEM format
|
||||
var skCtx: SkeyDecoderContext
|
||||
decodeFromPem(skCtx, key)
|
||||
if skeyDecoderKeyType(addr skCtx) != KEYTYPE_EC:
|
||||
invalidPemKey()
|
||||
|
||||
template pk(): EcPrivateKey = skCtx.key.ec
|
||||
# var pk: EcPrivateKey
|
||||
# pk.curve = 23
|
||||
# pk.x = cast[ptr cuchar](addr EC_P256_PRIV_X[0])
|
||||
# pk.xlen = EC_P256_PRIV_X.len
|
||||
|
||||
# Step 2. Hash!
|
||||
var digest: array[64, byte]
|
||||
@@ -147,40 +118,28 @@ proc bearSignECPem*(data, key: string, alg: ptr HashClass, impl: ptr EcImpl): se
|
||||
result = newSeqUninitialized[byte](maxSigLen)
|
||||
let s = ecdsaSignRawGetDefault()
|
||||
assert(not s.isNil)
|
||||
let sz = s(impl, alg, addr digest[0], addr pk, cast[ptr cuchar](addr result[0]))
|
||||
let impl = ecGetDefault()
|
||||
let sz = s(impl, alg, addr digest[0], addr pk, cast[ptr char](addr result[0]))
|
||||
assert(sz <= maxSigLen)
|
||||
result.setLen(sz)
|
||||
|
||||
# if ecPublicKey != "":
|
||||
# var pkCtx: PkeyDecoderContext
|
||||
# decodeFromPem(pkCtx, ecPublicKey)
|
||||
# if pkeyDecoderKeyType(addr pkCtx) != KEYTYPE_EC:
|
||||
# invalidPemKey()
|
||||
# template puk(): EcPublicKey = pkCtx.key.ec
|
||||
# # var puk: EcPublicKey
|
||||
# # puk.curve = 23
|
||||
# # puk.q = cast[ptr cuchar](addr EC_P256_PUB_POINT[0])
|
||||
# # puk.qlen = EC_P256_PUB_POINT.len
|
||||
|
||||
|
||||
# let v = ecdsaVrfyRawGetDefault()
|
||||
# # echo "hs: ", hashLen
|
||||
# # echo "vrfy digest: ", digest.toHex
|
||||
# let r = v(impl, cast[ptr cuchar](addr digest[0]), 32, addr puk, cast[ptr cuchar](unsafeAddr result[0]), result.len)
|
||||
# echo "Verify after sign: ", r
|
||||
|
||||
# echo "len: ", result.toHex
|
||||
|
||||
proc bearVerifyECPem*(data, key: string, sig: openarray[byte], alg: ptr HashClass, impl: ptr EcImpl, hashLen: int): bool =
|
||||
# Step 1. Extract RSA key from `key` in PEM format
|
||||
proc bearVerifyECPem*(data, key: string, sig: openarray[byte], alg: ptr HashClass, hashLen: int): bool =
|
||||
# Step 1. Extract EC Pub key from `key` in PEM format
|
||||
var pkCtx: PkeyDecoderContext
|
||||
decodeFromPem(pkCtx, key)
|
||||
if pkeyDecoderKeyType(addr pkCtx) != KEYTYPE_EC:
|
||||
invalidPemKey()
|
||||
template pk(): EcPublicKey = pkCtx.key.ec
|
||||
|
||||
# bearssl ecdsaVrfy requires pubkey to be prepended with 0x04 byte, do it here
|
||||
assert((pk.q == addr pkCtx.key_data) and pk.qlen < sizeof(pkCtx.key_data))
|
||||
moveMem(addr pkCtx.key_data[1], addr pkCtx.key_data[0], pk.qlen)
|
||||
pkCtx.key_data[0] = 0x04
|
||||
inc pk.qlen
|
||||
|
||||
var digest: array[64, byte]
|
||||
calcHash(alg, data, digest)
|
||||
|
||||
let impl = ecGetDefault()
|
||||
let s = ecdsaVrfyRawGetDefault()
|
||||
result = s(impl, cast[ptr cuchar](addr digest[0]), hashLen, addr pk, cast[ptr cuchar](unsafeAddr sig[0]), sig.len) == 1
|
||||
result = s(impl, cast[ptr char](addr digest[0]), hashLen, addr pk, cast[ptr char](unsafeAddr sig[0]), sig.len) == 1
|
||||
|
||||
@@ -3,8 +3,7 @@ import json, strutils
|
||||
import utils
|
||||
|
||||
type
|
||||
CryptoException* = object of Exception
|
||||
UnsupportedAlgorithm* = object of CryptoException
|
||||
UnsupportedAlgorithm* = object of ValueError
|
||||
|
||||
SignatureAlgorithm* = enum
|
||||
NONE
|
||||
|
||||
@@ -3,11 +3,8 @@ 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
|
||||
# Check that a given JsonNode has a given kind, raise ValueError if not
|
||||
if node.kind != kind:
|
||||
raise newException(ValueError, "Invalid kind")
|
||||
|
||||
|
||||
@@ -79,11 +79,12 @@ Pk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii
|
||||
1D3jaW6pmGVJFhodzC31cy5sfOYotrzF
|
||||
-----END PUBLIC KEY-----"""
|
||||
ec512PrivKey = """-----BEGIN EC PRIVATE KEY-----
|
||||
MIHcAgEBBEIBiyAa7aRHFDCh2qga9sTUGINE5jHAFnmM8xWeT/uni5I4tNqhV5Xx
|
||||
0pDrmCV9mbroFtfEa0XVfKuMAxxfZ6LM/yKgBwYFK4EEACOhgYkDgYYABAGBzgdn
|
||||
P798FsLuWYTDDQA7c0r3BVk8NnRUSexpQUsRilPNv3SchO0lRw9Ru86x1khnVDx+
|
||||
duq4BiDFcvlSAcyjLACJvjvoyTLJiA+TQFdmrearjMiZNE25pT2yWP1NUndJxPcv
|
||||
VtfBW48kPOmvkY4WlqP5bAwCXwbsKrCgk6xbsp12ew==
|
||||
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBiyAa7aRHFDCh2qga
|
||||
9sTUGINE5jHAFnmM8xWeT/uni5I4tNqhV5Xx0pDrmCV9mbroFtfEa0XVfKuMAxxf
|
||||
Z6LM/yKhgYkDgYYABAGBzgdnP798FsLuWYTDDQA7c0r3BVk8NnRUSexpQUsRilPN
|
||||
v3SchO0lRw9Ru86x1khnVDx+duq4BiDFcvlSAcyjLACJvjvoyTLJiA+TQFdmrear
|
||||
jMiZNE25pT2yWP1NUndJxPcvVtfBW48kPOmvkY4WlqP5bAwCXwbsKrCgk6xbsp12
|
||||
ew==
|
||||
-----END EC PRIVATE KEY-----"""
|
||||
ec512PubKey = """-----BEGIN PUBLIC KEY-----
|
||||
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ
|
||||
@@ -148,21 +149,15 @@ suite "Token tests":
|
||||
|
||||
test "EC Signature":
|
||||
# Checked with https://jwt.io/
|
||||
# echo signedECToken("ES256")
|
||||
check:
|
||||
signedECToken("ES256", ec256PrivKey).header.toBase64 == "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9"
|
||||
signedECToken("ES256", ec256PrivKey).claims.toBase64 == "eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ"
|
||||
|
||||
$signedECToken("ES256", ec256PrivKey) == "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.Z70NdbsTiPV5PpE2foY9YSehQCm20naEKPLdCZy_dV2W6uPLJTOY6JvAA9r9gykdxuH6dbTZUPo2yxRjpxJrJg"
|
||||
$signedECToken("ES384", ec384PrivKey) == "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.OkBilspLWGezXYP0A2i5nIT98makjx5RSgDol4N8_vgvyiUpJK5IaI-xGEJ5iJbIASR-YJT4zfPcMdEiPGd6LxBhinYl8EuTDyaipwdYHj1t_DzrsadqxAtlKXtcPGmj"
|
||||
$signedECToken("ES512", ec512PrivKey) == "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.AWJbyLizFO3Hbz71V5WtvFRMluPEll3PxqyW9Hze3WI_3xyrxr48UCa5m3Vj60nUKfx3qMTSrc-onHPNFWr4XuadATyrybfhaGiTe0KL5H2V3nO3dC2uSD-lqLL9OXq_YgBwpbvqP1w5RslTfYg3lpCXGy4i6zWPCIfARwNJ3IdkFoFW"
|
||||
# We don't check signatures, as for ES* algorithms they are random
|
||||
|
||||
# signedECToken("ES256").verify(ecPublicKey, ES256)
|
||||
# toJWT("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.tyh-VfuzIxCyGYDlkBA7DfyjrqmSHu6pQ2hoZuFqUSLPNY2N0mpHb3nk5K17HWP_3cYHBw7AhHale5wky6-sVA").verify(ecPublicKey)
|
||||
|
||||
|
||||
# eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.gDQjM0w-foh2h54D2eNV5JzKa2Y5lwoU168jlj2IImH8DDhGHFrjfjstmXos8zGv9iHFzLp5HPYjOZDV_BqX7Q"
|
||||
# $signedECToken("ES256") was eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.Z70NdbsTiPV5PpE2foY9YSehQCm20naEKPLdCZy_dV2W6uPLJTOY6JvAA9r9gykdxuH6dbTZUPo2yxRjpxJrJg
|
||||
signedECToken("ES256", ec256PrivKey).verify(ec256PubKey, ES256)
|
||||
signedECToken("ES384", ec384PrivKey).verify(ec384PubKey, ES384)
|
||||
signedECToken("ES512", ec512PrivKey).verify(ec512PubKey, ES512)
|
||||
|
||||
test "header values":
|
||||
var token = toJWT(%*{
|
||||
|
||||
Reference in New Issue
Block a user