* ES256, ES384, ES512 verification support. Fixes #11.

* GH actions CI
This commit is contained in:
Yuriy Glukhov
2022-02-20 14:10:54 +00:00
committed by GitHub
parent 382405a7eb
commit d8a464d190
9 changed files with 71 additions and 122 deletions

29
.github/workflows/test.yml vendored Normal file
View 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

View File

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

View File

@@ -1,4 +1,4 @@
JWT Implementation for Nim-lang [![Build Status](https://travis-ci.org/yglukhov/nim-jwt.svg?branch=master)](https://travis-ci.org/yglukhov/nim-jwt)
JWT Implementation for Nim [![Build Status](https://github.com/yglukhov/nim-jwt/workflows/CI/badge.svg?branch=master)](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
View File

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

View File

@@ -4,7 +4,6 @@ import utils
type
InvalidClaim = object of Exception
ClaimKind* = enum
ISS,
SUB,

View File

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

View File

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

View File

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

View File

@@ -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(%*{