10

I recently upgraded from PyJWT 0.4.1 to 1.0.1 and I can't figure out how to verify a JWT signed with a public key.

My code looks like this:

import jwt
cert_string = "-----BEGIN CERTIFICATE-----\nMIICITCCAYqgAwIBAgIIBEsUSxL..."
token_string = "eyJhbGciOiJSUzI1NiIsImtpZCI6I..."
jwt.decode(token_string, cert_string, algorithms=['RS256'])

The error I get is:

File "<stdin>", line 1, in <module>
File "~/.virtualenvs/project/lib/python2.7/site-packages/jwt/api.py", line 117, in decode                            
key, algorithms, **kwargs)                                                                                         
File "~/.virtualenvs/project/lib/python2.7/site-packages/jwt/api.py", line 176, in _verify_signature                 
key = alg_obj.prepare_key(key)                                                                                     
File "~/.virtualenvs/project/lib/python2.7/site-packages/jwt/algorithms.py", line 165, in prepare_key                
key = load_pem_public_key(key, backend=default_backend())                                                          
File "~/.virtualenvs/project/lib/python2.7/site-packages/cryptography/hazmat/primitives/serialization.py", line 24, in load_pem_public_key
return backend.load_pem_public_key(data)                                                                           
File "~/.virtualenvs/project/lib/python2.7/site-packages/cryptography/hazmat/backends/multibackend.py", line 285, in load_pem_public_key
return b.load_pem_public_key(data)
File "~/.virtualenvs/project/lib/python2.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 716, in load_pem_public_key
self._handle_key_loading_error()
File "~/.virtualenvs/project/lib/python2.7/site-packages/cryptography/hazmat/backends/openssl/backend.py", line 912, in _handle_key_loading_error
raise ValueError("Could not unserialize key data.")                                                                
ValueError: Could not unserialize key data.

I'm confident my cert_string and token are good. The following code runs OK:

from cryptography.x509 import load_pem_x509_certificate
from cryptography.hazmat.backends import default_backend
load_pem_x509_certificate(cert_string, default_backend())

My code that used to work with 0.4.1 looked like this:

cert_string = "".join(cert_string.strip().split("\n")[1:-1])
der = a2b_base64(cert_string)
cert = DerSequence()
cert.decode(der)
tbsCertificate = DerSequence()
tbsCertificate.decode(cert[0])
subjectPublicKeyInfo = tbsCertificate[6]
pub_key = RSA.importKey(subjectPublicKeyInfo)
jwt.decode(token_string, pub_key)

Any help would be appreciated.

Lee
  • 2,610
  • 5
  • 29
  • 36

3 Answers3

20

You need to pass the public key instead of the full certificate to the decode method. So extract the key from the certificate in order to use it as in:

from cryptography.x509 import load_pem_x509_certificate
from cryptography.hazmat.backends import default_backend

cert_str = "-----BEGIN CERTIFICATE-----MIIDETCCAfm..."
cert_obj = load_pem_x509_certificate(cert_str, default_backend())
public_key = cert_obj.public_key()

and then:

token_string = "eyJhbGciOiJSUzI1NiIsImtpZCI6I..."
jwt.decode(token_string, public_key, algorithms=['RS256'])
Hans Z.
  • 50,496
  • 12
  • 102
  • 115
  • 4
    getting error `TypeError: initializer for ctype 'char[]' must be a bytes or list or tuple, not str` – Aman Agarwal Jun 25 '18 at 12:25
  • This worked for me but my certificate needed the \n in the certificate. e.g. `cert_str = "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIIUAu/wmcX4dQwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2...` – vincent31337 May 19 '20 at 20:27
  • 1
    In Python 3 `load_pem_x509_certificate` is expecting `cert_str` to be bytes rather than a string. If you pass it a string you get `TypeError: from_buffer() cannot return the address of a unicode object` – Jason Heiss Sep 21 '20 at 15:28
  • In Python3, the fix is to suffix encode to your keys, i.e. cert_obj.encode('utf-8') – J. Gwinner Jul 16 '21 at 19:16
3

If the key file is generated using ssh-keygen -t rsa (RFC4716), you can use the file directly.

Encode:

import jwt

pemfile = open("id_rsa", 'r')
keystring = pemfile.read()
pemfile.close()
token = jwt.encode(payload, keystring, algorithm='RS256')

Decode:

import jwt

pemfile = open("id_rsa.pub", 'r')
keystring = pemfile.read()
pemfile.close()
payload = jwt.decode(toekn, keystring,  verify=True)

dont forget to catch errors like jwt.ExpiredSignatureError etc

Ryu_hayabusa
  • 3,666
  • 2
  • 29
  • 32
1

I am using python 3.6

import jwt
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
key = b"""-----BEGIN PUBLIC KEY-----
MIIBI....<<Your public key basically>>
-----END PUBLIC KEY-----"""
public_key = serialization.load_pem_public_key(key, backend=default_backend())
print(jwt.decode(token, public_key))

This worked like a charm for me.

  • Unfortunately, with Python 3.6 (don't ask) I get Message=from_buffer() cannot return the address of a unicode object – J. Gwinner Jul 16 '21 at 18:54