Closed timotheecour closed 2 years ago
get pub_key as above get header from x-amzn-oidc-data
I would really appreciate a runnable example with hardcoded sample values.
I would really appreciate a runnable example with hardcoded sample values.
on my TODO list; in the meantime, here's the approach I've started:
wrap examples/main-auth.c from https://github.com/benmcollins/libjwt
(that binary works fine including in my above example, reporting the correct error codes (see JWT_VALIDATION_*
)
which allows writing a jwt verification using a simple wrapper (wrapping a single function, or maybe just a few for added flexibility) instead of relying on a lot of wrappers (bearssl etc) and duplicating tricky functionality.
all it needs is:
brew install libjwt
{.passl:"-ljwt".}
{.passc: """ example c code""".}
and this can be turned into a higher level wrapper
this would likely belong in a separate jwt library, but since nim doesn't have yet a fully featured jwt solution, I think it's worth exploring in parallel
I would really appreciate a runnable example with hardcoded sample values.
@yglukhov here you go. just need to uncomment this in jwt.nim
# of ES256:
# result = crypto.bearVerifyECPem(data, secret, signature, addr sha256Vtable, addr ecPrimeI15, sha256SIZE)
it fails with jwt.nim, and works with benmcollins/libjwt
#[
from https://jwt.io/ ES256
]#
import pkg/jwt
import tables, json
proc test1() =
let pub = """
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
-----END PUBLIC KEY-----
"""
let priv = """
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G
-----END PRIVATE KEY-----
"""
let token = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.tyh-VfuzIxCyGYDlkBA7DfyjrqmSHu6pQ2hoZuFqUSLPNY2N0mpHb3nk5K17HWP_3cYHBw7AhHale5wky6-sVA"
let jwt = token.toJWT()
let ok = verify(jwt, pub , alg = SignatureAlgorithm.ES256)
doAssert ok # fails
test1()
Thanks! I now remember the root cause of the problem is that there's an issue decoding a PEM EC public key into bearssl public key. Somehow signature verification fails with the decoded key. I've stolen the decoder from here https://github.com/earlephilhower/bearssl-esp8266, but I'm not sure how to fix it.
not sure if related to jwkToPem
see:
Use the public key to verify the signature using your JWT library. You might need to convert the JWK to PEM format first. This example takes the JWT and JWK and uses the Node.js library, jsonwebtoken, to verify the JWT signature:
what happen is:
0x04
prefix.ecdsaI15VrfyRaw
and so on requires 65 bytes length consist of 0x04
+ 64 bytes.q
of EcPublicKey
).EcPublicKey
:
curve
set to decoded curve
qlen
set to 65q
points to the bufferor:
key.ec.q
points to key_data
key_data
PkeyDecoderContext one byte to the right and add 0x04
to the first slot.qlen
of key.ec.qlen
to 65pem public keys can be taken from: https://jwt.io/ -> debugger section and the der structures/values can be decoded using https://lapo.it/asn1js/#
use brEcComputePublicKey
/br_ec_compute_pub
to compute public key from private key and compare it with decoded public key to know the difference.
@jangko Thanks for the tip! Unfortunately just tried that, and it did not help :( I tried your second suggestion (moving key_data):
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
var pkCtx: PkeyDecoderContext
decodeFromPem(pkCtx, key)
if pkeyDecoderKeyType(addr pkCtx) != KEYTYPE_EC:
invalidPemKey()
template pk(): EcPublicKey = pkCtx.key.ec
assert((pk.q == addr pkCtx.key_data) and pk.qlen == 64)
discard c_memmove(addr pkCtx.key_data[1], addr pkCtx.key_data[0], 64)
pkCtx.key_data[0] = 0x04
pk.qlen = 65
var digest: array[64, byte]
calcHash(alg, data, digest)
let s = ecdsaVrfyRawGetDefault()
result = s(impl, cast[ptr cuchar](addr digest[0]), hashLen, addr pk, cast[ptr cuchar](unsafeAddr sig[0]), sig.len) == 1
echo "VERIFY: ", result
weird, it works for me.
import ../jwt/private/crypto
import bearssl
const
ec256PrivKey = """-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G
-----END PRIVATE KEY-----"""
ec256PubKey = """-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
-----END PUBLIC KEY-----"""
const data = "hello bear"
var sig = bearSignECPem(data, ec256PrivKey, addr sha256Vtable, addr ecPrimeI15)
doAssert bearVerifyECPem(data, ec256PubKey, sig, addr sha256Vtable, addr ecPrimeI15, sha256SIZE)
it doesn't matter using moveMem
or c_memmove, they are the same
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
var pkCtx: PkeyDecoderContext
decodeFromPem(pkCtx, key)
if pkeyDecoderKeyType(addr pkCtx) != KEYTYPE_EC:
invalidPemKey()
template pk(): EcPublicKey = pkCtx.key.ec
assert((pk.q == addr pkCtx.key_data) and pk.qlen == 64)
moveMem(addr pkCtx.key_data[1], addr pkCtx.key_data[0], 64)
pkCtx.key_data[0] = 0x04
pk.qlen = 65
var digest: array[64, byte]
calcHash(alg, data, digest)
let s = ecdsaVrfyRawGetDefault()
result = s(impl, cast[ptr cuchar](addr digest[0]), hashLen, addr pk, cast[ptr cuchar](unsafeAddr sig[0]), sig.len) == 1
this one from t_jwt.nim also works
check:
signedECToken("ES256", ec256PrivKey).verify(ec256PubKey, ES256)
My bad, it was my local changes that confused me, and you're right, it works perfectly. Thanks a lot!
/cc @yglukhov
verifySignature doesn't work with AWS cognito / oauth2 which uses ES256
I'm not sure why this was commented, but I tried uncommenting and it still didn't work
whereas this snippet works in python3:
https://docs.aws.amazon.com/elasticloadbalancing/latest/application/listener-authenticate-users.html#user-claims-encoding
note:
pip install PyJWT
, notpip install JWT
links
existing implementations
pip install PyJWT
: see https://github.com/jpadilla/pyjwt https://pyjwt.readthedocs.io/en/latest/usage.html /usr/local//lib//python3.8/site-packages/jwt/algorithms.py