I am trying to verify an idToken using a public key in python.
I first convert the JWK token to PEM but when I call the "decode" function, I see a "signature verification failed" exception. What am I missing?
# Long string goes here - this is the token to verify
myToken = 'ezFraWQiXXX.YYYYYYYY.ZZZZZZZZ'
# JWK Token
webkey = {
"alg": "RS256",
"e": "AQAB",
"kid": "d9FzOfniXuHf2sF3opIKZb0sW8Nuaa0d5d+AXXXXXXXX=",
"kty": "RSA",
"n": "nQwBvRlZKdXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX4HcenyO_WASyjr6korLEHxh8XXXXXXXXXXXX",
"use": "sig"
}
# Converting JWK to PEM
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(webkey)
pubk_bytes = public_key.public_bytes(encoding=serialization.Encoding.PEM,format=serialization.PublicFormat.SubjectPublicKeyInfo)
# This is where I get the "signature verification failed" exception
claim = jwt.decode(myToken, pubk_bytes, algorithms=['RS256']) # <<-- ideally this should decode the token for me
Looks like you are using PyJWT, which is a good choice of security library. Ideally get the library to download token signing public keys for you from the JWKS endpoint of the Authorization Server, for the cleanest and simplest code:
import jwt
from jwt import PyJWKClient
// The client will read the JWT header to get the kid field,
// then download token signing public keys and return that matching the kid.
// This key will then be cached for future JWTs with the same kid.
// The client will reliably handle new kids if keys are recycled.
jwks_client = PyJWKClient(url)
signing_key = jwks_client.get_signing_key_from_jwt(access_token_jwt)
// It is recommended to verify the signature, expiry, issuer and audience.
// As a best practice, the API should also specify the algorithms it expects
// to receive in JWT signatures.
claims = jwt.decode(
access_token_jwt,
signing_key.key,
algorithms=["RS256"],
issuer="my-issuer",
audience="my-audience")
You should be able to send a JWK directly into the library, rather than dealing with RSA or byte translation. See the pyJWT docs for some examples. For further security background, you may also find the Curity article on JWT Best Practices useful.
Related
It's unclear from the docs what you actually do to verify the jwsRepresentation string from a StoreKit 2 transaction on the server side.
Also "signedPayload" from the Apple App Store Notifications V2 seems to be the same, but there is also no documentation around actually validating that either outside of validating it client side on device.
What gives? What do we do with this JWS/JWT?
(DISCLAIMER: I am a crypto novice so check me on this if I'm using the wrong terms, etc. throughout)
The JWS in jwsRepresentation, and the signedPayload in the Notification V2 JSON body, are JWTs — you can take one and check it out at jwt.io. The job is to validate the JWT signature and extract the payload once you're sufficiently convinced it's really from Apple. Then the payload itself contains information you can use to upgrade the user's account/etc. server side once the data is trusted.
To validate the JWT, you need to find the signature that the JWT is signed with, specified in the JWT header's "x5c" collection, validate the certificate chain, and then validate that the signature is really from Apple.
STEP ONE: Load the well-known root & intermediate certs from Apple.
import requests
from OpenSSL import crypto
ROOT_CER_URL = "https://www.apple.com/certificateauthority/AppleRootCA-G3.cer"
G6_CER_URL = "https://www.apple.com/certificateauthority/AppleWWDRCAG6.cer"
root_cert_bytes: bytes = requests.get(ROOT_CER_URL).content
root_cert = crypto.load_certificate(crypto.FILETYPE_ASN1, root_cert_bytes)
g6_cert_bytes: bytes = requests.get(G6_CER_URL).content
g6_cert = crypto.load_certificate(crypto.FILETYPE_ASN1, g6_cert_bytes)
STEP TWO: Get certificate chain out of the JWT header
import jwt # PyJWT library
# Get the signing keys out of the JWT header. The header will look like:
# {"alg": "ES256", "x5c": ["...base64 cert...", "...base64 cert..."]}
header = jwt.get_unverified_header(apple_jwt_string)
provided_certificates: List[crypto.X509] = []
for cert_base64 in header['x5c']:
cert_bytes = base64url_decode(cert_base64)
cert = crypto.load_certificate(crypto.FILETYPE_ASN1, cert_bytes)
provided_certificates.append(cert)
STEP THREE: Validate the chain is what you think it is -- this ensures the cert chain is signed by the real Apple root & intermediate certs.
# First make sure these are the root & intermediate certs from Apple:
assert provided_certificates[-2].digest('sha256') == g6_cert.digest('sha256')
assert provided_certificates[-1].digest('sha256') == root_cert.digest('sha256')
# Now validate that the cert chain is cryptographically legit:
store = crypto.X509Store()
store.add_cert(root_cert)
store.add_cert(g6_cert)
for cert in provided_certificates[:-2]:
try:
crypto.X509StoreContext(store, cert).verify_certificate()
except crypto.X509StoreContextError:
logging.error("Invalid certificate chain in JWT: %s", apple_jwt)
return None
store.add_cert(cert)
FINALLY: Load & validate the JWT using the now-trusted certificate in the header.
# Now that the cert is validated, we can use it to verify the actual signature
# of the JWT. PyJWT does not understand this certificate if we pass it in, so
# we have to get the cryptography library's version of the same key:
cryptography_version_of_key = provided_certificates[0].get_pubkey().to_cryptography_key()
try:
return jwt.decode(apple_jwt, cryptography_version_of_key, algorithms=["ES256"])
except Exception:
logging.exception("Problem validating Apple JWT")
return None
Voila you now have a validated JWT body from the App Store at your disposal.
Gist of entire solution: https://gist.github.com/taylorhughes/3968575b40dd97f851f35892931ebf3e
Tried to decode the JWT provided by the google. I have used phone number authentication with google and returned verifyIdToken which is a jwt token. I have tried to verify with the jwt package available in python but it throws error.
When I tried to verify the signature with jwt.io It give a verified signature but it python can't.
Here is the link about the documentation of the google
The public key used is
-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIICc/DAoum8fgwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMjIw\nNTAzMDkzODM5WhcNMjIwNTE5MjE1MzM5WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAK7kUdLrSbHeVqsGd2KC2Kt4Bup69/+2cXmfGALrYFGsKdkE\nUdeV8Mdtbqtk5njUAzXibrZb+x3jfyG/WJZXbFXgBPkSHIsIcwFFnIMQMHRfXXwV\nq1Qe5U52x2ztSYGPtz3UNBUUXsHZLplGdljtjagDqNYX1vYA6ZXItQPr1ycM0i1f\nV7j96qQ0OJjir94B1j5cTVHHtZsqoJgcJXdDabF5zC6G1X3Gxh3OftJBqM0dqWjl\nLmgGQ6CtYRmB10zqotJNa3v9Q6jT0flNpLOswnEW8t44/sjRl3sf2Tv3IMrihZYB\n2CaEvL1b9DAvWfAucrG7x8BKBKTs5u2bowqPKlcCAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBAJpMc+noeLGaKXlBrW30vEnp9KjN+bhf4WipBSV4KlQx\nwxYnZov6hrxhctz0D0rhQmIHNlmgIFkG/ej9sszz4z08MZAxrPUJsaGoIY2e4PQ3\nYOzNHFp4VssK9D1L5jxRxf+/jf0fJf/ZfFKxCzz8tWfpZitHlzmOoSMuUBTIFXmB\nvQuUWeOu3pAT7Z+ddpaLvxbE264Ybd9ujxuFWzmXJCNHh+dOruymEZrpwoOkoAaH\nll7Jr2nEaY/SUCK4QZne3FXIc07rbs9l4C8+yrcG5RkAsVH9gdgtHDgFWLFnxGb8\nQQ8yX4+6Fq/3xWzUWg4PvAIb4aX8Fwc59uBFc7yeSvg=\n-----END CERTIFICATE-----\n
I tried removing \n and I verified it with jwt.io it worked but it doesn't work in the python the code I wrote is
import jwt
key = "-----BEGIN CERTIFICATE-----MIIDHDCCAgSgAwIBAgIICc/DAoum8fgwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UEAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMjIwNTAzMDkzODM5WhcNMjIwNTE5MjE1MzM5WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tlbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK7kUdLrSbHeVqsGd2KC2Kt4Bup69/+2cXmfGALrYFGsKdkEUdeV8Mdtbqtk5njUAzXibrZb+x3jfyG/WJZXbFXgBPkSHIsIcwFFnIMQMHRfXXwVq1Qe5U52x2ztSYGPtz3UNBUUXsHZLplGdljtjagDqNYX1vYA6ZXItQPr1ycM0i1fV7j96qQ0OJjir94B1j5cTVHHtZsqoJgcJXdDabF5zC6G1X3Gxh3OftJBqM0dqWjlLmgGQ6CtYRmB10zqotJNa3v9Q6jT0flNpLOswnEW8t44/sjRl3sf2Tv3IMrihZYB2CaEvL1b9DAvWfAucrG7x8BKBKTs5u2bowqPKlcCAwEAAaM4MDYwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQEFBQADggEBAJpMc+noeLGaKXlBrW30vEnp9KjN+bhf4WipBSV4KlQxwxYnZov6hrxhctz0D0rhQmIHNlmgIFkG/ej9sszz4z08MZAxrPUJsaGoIY2e4PQ3YOzNHFp4VssK9D1L5jxRxf+/jf0fJf/ZfFKxCzz8tWfpZitHlzmOoSMuUBTIFXmBvQuUWeOu3pAT7Z+ddpaLvxbE264Ybd9ujxuFWzmXJCNHh+dOruymEZrpwoOkoAaHll7Jr2nEaY/SUCK4QZne3FXIc07rbs9l4C8+yrcG5RkAsVH9gdgtHDgFWLFnxGb8QQ8yX4+6Fq/3xWzUWg4PvAIb4aX8Fwc59uBFc7yeSvg=-----END CERTIFICATE-----"
token = "XXXXXXXXX"
payload = jwt.decode(token, key.encode(), algorithms=['RS256',])
print(payload)
the error that throwed is
raise ValueError(
ValueError: ('Could not deserialize key data. The data may be in an incorrect format, it may be encrypted with an unsupported algorithm, or it may be an unsupported key type (e.g. EC curves with explicit parameters).', [_OpenSSLErrorWithText(code=75497580, lib=9, reason=108, reason_text=b'error:0480006C:PEM routines::no start line')])
I haven't included the token because it contains data.
I was able to reproduce the following:
import jwt
key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmwIDAQAB\n-----END PUBLIC KEY-----\n"
token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ"
payload = jwt.decode(token, key.encode(), algorithms=['RS256',])
print(payload)
>>> {'sub': '1234567890', 'name': 'John Doe', 'admin': True, 'iat': 1516239022}
I noticed that in your public key there are some escape characters \n inside your string (this may be causing your problem). You should only remove \n that appear inside the key and include them at the beginning and at the end. Try changing your public key to:
key = "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIICc/DAoum8fgwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UEAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMjIwNTAzMDkzODM5WhcNMjIwNTE5MjE1MzM5WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tlbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK7kUdLrSbHeVqsGd2KC2Kt4Bup69/+2cXmfGALrYFGsKdkEUdeV8Mdtbqtk5njUAzXibrZb+x3jfyG/WJZXbFXgBPkSHIsIcwFFnIMQMHRfXXwVq1Qe5U52x2ztSYGPtz3UNBUUXsHZLplGdljtjagDqNYX1vYA6ZXItQPr1ycM0i1fV7j96qQ0OJjir94B1j5cTVHHtZsqoJgcJXdDabF5zC6G1X3Gxh3OftJBqM0dqWjlLmgGQ6CtYRmB10zqotJNa3v9Q6jT0flNpLOswnEW8t44/sjRl3sf2Tv3IMrihZYB2CaEvL1b9DAvWfAucrG7x8BKBKTs5u2bowqPKlcCAwEAAaM4MDYwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQEFBQADggEBAJpMc+noeLGaKXlBrW30vEnp9KjN+bhf4WipBSV4KlQxwxYnZov6hrxhctz0D0rhQmIHNlmgIFkG/ej9sszz4z08MZAxrPUJsaGoIY2e4PQ3YOzNHFp4VssK9D1L5jxRxf+/jf0fJf/ZfFKxCzz8tWfpZitHlzmOoSMuUBTIFXmBvQuUWeOu3pAT7Z+ddpaLvxbE264Ybd9ujxuFWzmXJCNHh+dOruymEZrpwoOkoAaHll7Jr2nEaY/SUCK4QZne3FXIc07rbs9l4C8+yrcG5RkAsVH9gdgtHDgFWLFnxGb8QQ8yX4+6Fq/3xWzUWg4PvAIb4aX8Fwc59uBFc7yeSvg=\n-----END CERTIFICATE-----\n"
I'm trying to decode a Gravitee JWT Token using public key. I tested already PyJWT, authlib, python-jose and jwcrypto libraries and review a lot of posts on this page but I get the same error in all of them and I could not fix the problem.
Error:
('Could not deserialize key data. The data may be in an incorrect format, it may be encrypted with an unsupported algorithm, or it may be an unsupported key type (e.g. EC curves with explicit parameters).', [_OpenSSLErrorWithText(code=151584876, lib=9, reason=108, reason_text=b'error:0909006C:PEM routines:get_name:no start line')])
Firts of all I get the public key following Gravitee instructions:
https://docs.gravitee.io/am/current/am_userguide_create_certificate.html
Some info from https://jwt.io about my token:
HEADER:ALGORITHM & TOKEN TYPE
{
"kid": "default",
"alg": "RS256"
}
Python packeges versions:
PyJWT==2.3.0 (also tested with 2.1.0)
cryptography==36.0.0 (some posts suggests is required)
My code:
from rest_framework import permissions
from rest_framework.exceptions import APIException
from django.conf import settings
import jwt
class TokenNotValid(APIException):
status_code = 403
default_detail = "Invalid or absent JWT token field."
class NoAuthHeader(APIException):
status_code = 403
default_detail = "Absent 'Authorization' header."
class ValidJWTPermission(permissions.BasePermission):
"""
Global permission check for JWT token.
"""
def _get_pubkey(self):
key = """-----BEGIN PUBLIC KEY-----\n""" + settings.GRAVITEE_PUBLIC_KEY + """\n-----END PUBLIC KEY-----"""
return key
def has_permission(self, request, view):
auth_header = request.META.get('HTTP_AUTHORIZATION')
# print("Received header:")
# print(auth_header)
if auth_header is None:
raise NoAuthHeader
try:
token = auth_header.split()[1]
# print("Encoded Token:")
# print(token)
public_key = self._get_pubkey()
print(public_key)
claims = jwt.decode(token, key=public_key, algorithms=['RS256'])
claims.validate()
except Exception as e:
print(e)
raise TokenNotValid
# print("Decoded token:")
# print(dec_token)
return True
I tested also encoding the key like key.encode() and key.encode('ascii') or composing the key with "BEGIN RSA PUBLIC KEY" instead of "BEGIN PUBLIC KEY" and anything works for me. Always I have the same error.
Depending on the library, you often wants the key in JWK format as shown in this link:
https://demo.identityserver.io/.well-known/openid-configuration/jwks
This JSON Web Key (JWK) format is a standard for representing keys when you deal with tokens and perhaps you library wants it in that format too?
I have written the following code :
def check_token(token):
response = requests.get("https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com")
key_list = response.json()
decoded_token = jwt.decode(token, key=key_list, algorithms=["RS256"])
print(f"Decoded token : {decoded_token}")
I am trying to decode the token provided by firebase client side to verify it server-side.
The above code is throwing the following exception :
TypeError: Expecting a PEM-formatted key.
I have tried to not pass a list to the jwt.decode method, only the key content and i have a bigger error that the library could not deserialize the Key.
I was following this answer but i am getting this error.
Is it a requests conversion problem ? What am i doing wrong ?
The 2nd parameter key in decode() seems to take a string value instead of list. The Google API request returns a dict/map containing multiple keys. The flow goes like:
Fetch public keys from the Google API endpoint
Then read headers without validation to get the kid claim then use it to get appropriate key from that dict
That is a X.509 Certificate and not the public key as in this answer so you need to get public key from that.
The following function worked for me:
import jwt
import requests
from cryptography.hazmat.backends import default_backend
from cryptography import x509
def check_token(token):
n_decoded = jwt.get_unverified_header(token)
kid_claim = n_decoded["kid"]
response = requests.get("https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com")
x509_key = response.json()[kid_claim]
key = x509.load_pem_x509_certificate(x509_key.encode('utf-8'), backend=default_backend())
public_key = key.public_key()
decoded_token = jwt.decode(token, public_key, ["RS256"], options=None, audience="<FIREBASE_PROJECT_ID>")
print(f"Decoded token : {decoded_token}")
check_token("FIREBASE_ID_TOKEN")
So I have a credit card looking like smart card with a chip. This card logins on a website after the card is inserted into the card reader.
Now I have to write a program in python which can read the card and login on that website. After research on internet I found out that I need to extract :
Certificate and
Public key (since private key cannot be extracted)
from the card and then use these 2 things to create a HTTPs connection (example here) . So far I am able to extract certificate in pem format. But i cant find a way to extract key in pem format till now. I used PyKCS11 to read the card. Below is my code:
from asn1crypto import pem, x509
from PyKCS11 import *
import binascii
pkcs11 = PyKCS11Lib()
pkcs11.load(r'C:\Windows\System32\XXXX.dll')
print(pkcs11.getSlotList(tokenPresent=False))
slot = pkcs11.getSlotList(tokenPresent=False)[0]
print(pkcs11.getTokenInfo(slot))
session = pkcs11.openSession(0, CKF_SERIAL_SESSION | CKF_RW_SESSION)
session.login('123456')
result = []
result_pem = []
# find public key and print modulus
pubKey = session.findObjects([(CKA_CLASS, CKO_PUBLIC_KEY)])[0]
modulus = session.getAttributeValue(pubKey, [CKA_MODULUS])[0]
print("\nmodulus: {}".format(binascii.hexlify(bytearray(modulus))))
#find certificates
certs = session.findObjects([(CKA_CLASS, CKO_CERTIFICATE)])
for cert in certs:
cka_value, cka_id = session.getAttributeValue(cert, [CKA_VALUE, CKA_ID])
cert_der = bytes(cka_value)
cert = x509.Certificate.load(cert_der)
# Write out a PEM encoded value
cert_pem = pem.armor('CERTIFICATE', cert_der)
result.append(cert)
result_pem.append(cert_pem)
with open('cert.pem','wb') as f:
f.write(cert_pem)
print(result)
So here are my questions:
1. Is my approach right?
If yes, then how to extract public key in pem format?
How this smart card authentication actually works on client side and server side?
Public key extraxction
If you already have exported the certificate, it is probably easier to extract the public key from there, instead of from the smartcard. You can use openssl for that:
openssl x509 -in cert.pem -pubkey -out pubkey.pem -noout
Authentication
What you are trying to achieve is to open a TLS connection with mutual authentication using a client certificate. If you do this, the private key of your client certificate signs parts of the handshake to authenticate itself towards the server.
Extracting the certificate and the public key from the smartcard won't help you here. You need to find a library, which allows you to use your private key straight from your PKCS#11 token.