Signing jwt with coincurve (python)

#1

I try to implement something like DID auth for OI Chat. On the server with python I use coincurve to verify the jwt signature:

    pwd = password.encode("utf-8")
    try:
        signing_input, crypto_segment = pwd.rsplit(b".", 1)
        header_segment, payload_segment = signing_input.split(b".", 1)
    except ValueError:
        raise DecodeError("Not enough segments")
    payload = self.base64url_decode(payload_segment)
    payload = json.loads(payload.decode("utf-8"))
    publicKey = payload["public_keys"][0]
    pubKey = bytes.fromhex(publicKey)
    sig = bytes.fromhex(crypto_segment.hex())
    coincurve.verify_signature(sig, payload_segment,  pubKey)

This fails with The DER-encoded signature could not be parsed.
Am I using the method incorrectly?

The full code can be found here:

In particular in feature branch jwt-auth:

To run a test just call python3 matrix_blockstack_password_provider/test.py

Any help to make the test past would be appreciated.
There is a question on bitcoin stackoverflow, however, I am not sure how to apply the answer to my code. Is the public key in DER format? https://bitcoin.stackexchange.com/questions/86043/question-about-python-library-coincurve-libsecp256k

Is there a (better) package to verify ES256k signatures?

#2

I can’t speak to coincurve (never used it). I know that our JWT format isn’t standardized by the W3C (because ES256k1 isn’t standard). Can you sign something with coincurve and verify it with jsontokens-js at least? Is it possible to sign the same JSON object with coincurve and jsontokens-js and compare the resulting JWTs?

#3

I tried to sign the same token as in https://github.com/blockstack/jsontokens-js/blob/master/src/test/mainTests.ts

But the results are not the same. Do I have to use a different encoding?

    privateKey = coincurve.PrivateKey.from_hex("278a5de700e29faae8e40e366ec5012b5ec63d36ec77e8a2417154cc1d25383f")
    jwt = privateKey.sign("{\"issuedAt\":\"1440713414.85\",\"challenge\":\"7cd9ed5e-bb0e-49ea-a323-f28bde3a0549\",\"issuer\":{\"publicKey\":\"03fdd57adec3d438ea237fe46b33ee1e016eda6b585c3e27ea66686c2ea5358479\",\"chainPath\":\"bd62885ec3f0e3838043115f4ce25eedd22cc86711803fb0c19601eeef185e39\",\"publicKeychain\":\"xpub661MyMwAqRbcFQVrQr4Q4kPjaP4JjWaf39fBVKjPdK6oGBayE46GAmKzo5UDPQdLSM9DufZiP8eauy56XNuHicBySvZp7J5wsyQVpi2axzZ\",\"blockchainid\":\"ryan\"}}".encode("utf-8"))
    logger.info(base64.urlsafe_b64encode(jwt))

The result is

b'MEQCIFpiUT5BsuGQnGYXWHeNDXZWrAt6Nf0SVHoEIjsH3VtTAiAnjxf-6NWmuQtLD6_nm7PcGxSV9Y2le0xpDByaNCqz0A=='

With jsontoken.js the result is
signature:

'oO7ROPKq3T3X0azAXzHsf6ub6CYy5nUUFDoy8MS22B3TlYisqsBrRtzWIQcSYiFXLytrXwAdt6vjehj3OFioDQ'

or

 'DUf6Rnw6FBKv4Q3y95RX7rR6HG_L1Va96ThcIYTycOf1j_bf9WleLsOyiZ-35Qfw7FgDnW7Utvz4sNjdWOSnhQ'

Do you have any ideas what is wrong? Is there an alternative to coincurve in python?

#4

I now understand that the signature of coincurve is in DER format while jsontoken-js returns jose signature.

I see that the signature is the same at the beginning

  • coincurve
    "DUf6Rnw6FBKv4Q3y95RX7rR6HG_L1Va96ThcIYTycOcKcAkgCpah0TxNdmBIGvgNzlbZSUBz6T7HIYWvd1GZvA=="
  • jsontoken-js
    "DUf6Rnw6FBKv4Q3y95RX7rR6HG_L1Va96ThcIYTycOf1j_bf9WleLsOyiZ-35Qfw7FgDnW7Utvz4sNjdWOSnhQ"

@jude Do you have any ideas what could be wrong?

#5

It looks like they use different encoding schemes, and are probably not compatible (barring a way to convert one signature format to another).

If you’re just looking for a Python-based way to verify a JWT using a JOSE signature, there’s always jsontokens-py. It’s no longer maintained, but the code is small and should still work. I tested as follows:

$ python
Python 2.7.16 (default, May  6 2019, 19:28:45) 
[GCC 8.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 
>>> import jsontokens
>>> v = jsontokens.TokenVerifier()
>>> v.verify('eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3N1ZWRBdCI6IjE0NDA3MTM0MTQuMTkifQ.7UpSjte-bbk0CsBgC3AJyogLKu6SGzyigFgo2qZeUN6zKHaQsBlz_pFwHkPGLmiz4yvOd5gfWu8R2BwFX55okQ', '027d28f9951ce46538951e3697c62588a87f1f1f295de4a14fdd4c780fc52cfe69')
/usr/lib/python2.7/site-packages/jsontokens-0.0.5-py2.7.egg/jsontokens/token_verifier.py:188: CryptographyDeprecationWarning: signer and verifier have been deprecated. Please use sign and verify instead.
  return verifying_key.verifier(signature, self.signing_function)
True
>>>

and

$ node
> jsontokens = require('jsontokens')
{ TokenSigner: [Function: TokenSigner],
  createUnsecuredToken: [Function: createUnsecuredToken],
  TokenVerifier: [Function: TokenVerifier],
  decodeToken: [Function: decodeToken],
  MissingParametersError: [Function: MissingParametersError],
  InvalidTokenError: [Function: InvalidTokenError],
  SECP256K1Client:
   { [Function: SECP256K1Client]
     ec:
      EC {
        curve: [ShortCurve],
        n:
         <BN: fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141>,
        nh:
         <BN: 7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0>,
        g:
         <EC Point x: 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 y: 483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8>,
        hash: [Function] },
     algorithmName: 'ES256K',
     keyEncoder: KeyEncoder { options: [Object], algorithmID: [Array] } },
  cryptoClients:
   { ES256K:
      { [Function: SECP256K1Client] ec: [EC], algorithmName: 'ES256K', keyEncoder: [KeyEncoder] } } }
> (node:27870) [DEP0079] DeprecationWarning: Custom inspection function on Objects via .inspect() is deprecated

> v = new jsontokens.TokenVerifier('ES26K', '027d28f9951ce46538951e3697c62588a87f1f1f295de4a14fdd4c780fc52cfe69')
Thrown: 'invalid signing algorithm'
> v = new jsontokens.TokenVerifier('ES256K', '027d28f9951ce46538951e3697c62588a87f1f1f295de4a14fdd4c780fc52cfe69')
TokenVerifier {
  tokenType: 'JWT',
  cryptoClient:
   { [Function: SECP256K1Client]
     ec:
      EC {
        curve: [ShortCurve],
        n:
         <BN: fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141>,
        nh:
         <BN: 7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0>,
        g:
         <EC Point x: 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 y: 483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8>,
        hash: [Function] },
     algorithmName: 'ES256K',
     keyEncoder: KeyEncoder { options: [Object], algorithmID: [Array] } },
  rawPublicKey:
   '027d28f9951ce46538951e3697c62588a87f1f1f295de4a14fdd4c780fc52cfe69' }
> v.verify('eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3N1ZWRBdCI6IjE0NDA3MTM0MTQuMTkifQ.7UpSjte-bbk0CsBgC3AJyogLKu6SGzyigFgo2qZeUN6zKHaQsBlz_pFwHkPGLmiz4yvOd5gfWu8R2BwFX55okQ')
true
#6

I didn’t know about jsontokens-py. That looks like exactly what I need.

I would need to upgrade to python 3.