Unable to deserialize key data on decoding JWT in Python - python

I am using Auth0 in my DRF project to generate tokens and authenticate. Everything works fine if I normally encode and decode the tokens. But I have written a method requires_scope which determines if the required scope (which the decorator on API tells) for any API. The method is below:
def requires_scope(required_scope):
"""Determines if the required scope is present in the Access Token
Args:
required_scope (str): The scope required to access the resource
"""
def require_scope(f):
#wraps(f)
def decorated(*args, **kwargs):
token = get_token_auth_header(args[0])
decoded = jwt.decode(token, verify=False, algorithms=settings.AUTH0_ALGORITHMS)
if decoded.get("scope"):
token_scopes = decoded["scope"].split()
for token_scope in token_scopes:
if token_scope == required_scope:
return f(*args, **kwargs)
response = JsonResponse({'message': 'You don\'t have access to this resource'})
response.status_code = 403
return response
return decorated
return require_scope
now when I use the decorator on API for any specific scope, it does not decode the JWT and shows the following ValueError 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=75497580, lib=9, reason=108, reason_text=b'error:0480006C:PEM routines::no start line')])
This is my decoding method:
def jwt_decode_token(token):
header = jwt.get_unverified_header(token)
jwks = requests.get('https://{}/.well-known/jwks.json'.format(settings.SOCIAL_AUTH_AUTH0_DOMAIN)).json()
public_key = None
for jwk in jwks['keys']:
if jwk['kid'] == header['kid']:
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(jwk))
# public_key = "-----BEGIN PUBLIC KEY-----\n" + jwk['x5c'][0] + "\n-----END PUBLIC KEY-----\n"
# print(public_key)
if public_key is None:
raise Exception('Public key not found.')
issuer = 'https://{}/'.format(settings.SOCIAL_AUTH_AUTH0_DOMAIN)
return jwt.decode(
token,
public_key,
audience=settings.AUTH0_TOKEN_API_AUDIENCE,
issuer=issuer,
algorithms=['RS256']
)
and this is the payload I am passing to API Call (it contains the scope):
payload = f"{{\"client_id\":\"{settings.SOCIAL_AUTH_AUTH0_KEY}\",\
\"client_secret\":\"{settings.SOCIAL_AUTH_AUTH0_SECRET}\",\
\"audience\":\"{settings.AUTH0_TOKEN_API_AUDIENCE}\",\
\"grant_type\":\"password\",\
\"username\":\"{email}\",\
\"password\":\"{password}\",\
\"scope\":\"read:messages\"}}"
What's wrong I am doing? Can anyone help?

The reason why it complains about the key is because it is still trying to do the verification. In order to correctly disable this with pyjwt is:
jwt.decode(token, algorithms=["RS256"], options={"verify_signature": False})
See the pyjwt documentation (interestingly, I cannot skip the algorithms block): https://pyjwt.readthedocs.io/en/latest/usage.html#reading-the-claimset-without-validation

Related

Problem decoding JWT token with public key from Gravitee in Python

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?

Why doesnt PyJwt recognize the positional argument "algorithm" when using "ES256" signing method?

I'm using PyJwt to generate and encode a JWT for apple application notification service token authentication which requires the ES256 signing algorithm as noted here .
self.server = settings.APNS_SERVER
self.algorithm = 'ES256'
PRIVATE_KEY = settings.APNS_PRIVATE_KEY
encoded_jwt = jwt.encode(
{'iss': settings.TEAM_ID, 'iat': time.time()},
PRIVATE_KEY,
algorithm=self.algorithm,
headers={'alg': self.algorithm, 'kid': settings.APNS_KEY_ID},
)
When passed this way, I get the following error:
encoded_jwt = jwt.encode(
File "/usr/local/lib/python3.9/site-packages/jwt/api_jwt.py", line 63, in encode
return api_jws.encode(json_payload, key, algorithm, headers, json_encoder)
File "/usr/local/lib/python3.9/site-packages/jwt/api_jws.py", line 114, in encode
signature = alg_obj.sign(signing_input, key)
File "/usr/local/lib/python3.9/site-packages/jwt/algorithms.py", line 423, in sign
der_sig = key.sign(msg, ec.ECDSA(self.hash_alg()))
TypeError: sign() missing 1 required positional argument: 'algorithm'
This doesn't happen when I use the RS256 or HS256 signing algorithm.
Why is this happening?
Most likely, your private key is not correctly generated. Use this instruction to generate ES256 key: https://connect2id.com/products/nimbus-jose-jwt/openssl-key-generation

Decode Firebase JWT in Python using PyJWT

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

Coinlist exchange API authentication - Python

For the past month I've been trying to understand coinlist API (https://coinlist.co) since there is no API wrapper available it's been a hard task. I could figure it out that their API docs are really similar to coinbase exchange, and tried to extrapolate but with no success.
import json, hmac, hashlib, time, requests
from requests.auth import AuthBase
# Before implementation, set environmental variables with the names API_KEY and API_SECRET
API_KEY = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
API_SECRET = 'xxxx/xxxxxxxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxx=='
# Create custom authentication for Coinlist API
class CoinlistWalletAuth(AuthBase):
def __init__(self, api_key, secret_key):
self.api_key = api_key
self.secret_key = secret_key
def __call__(self, request):
timestamp = str(int(time.time()))
message = timestamp + request.method + request.path_url + (request.body or '')
signature = hmac.new(self.secret_key, message, hashlib.sha256).hexdigest()
request.headers.update({
'CL-ACCESS-SIGN': signature,
'CL-ACCESS-TIMESTAMP': timestamp,
'CL-ACCESS-KEY': self.api_key,
})
return request
auth = CoinlistWalletAuth(API_KEY, API_SECRET)
#Test1 - Fetching account balance
response = requests.get('https://trade-api.coinlist.co/v1/accounts', auth=auth)
I am getting this TypeError: key: expected bytes or bytearray, but got 'str' when calling for the response.
Docs say - You must base64-encode the signature (the output of the sha256 HMAC). Why is it failing?
Two things:
TypeError: key: expected bytes or bytearray, but got 'str'
hmac.new(key, msg=None, digestmod='')
Return a new hmac object. key is a bytes or bytearray object giving the secret key. (...)
import base64
...
secret_key_as_bytes = base64.b64decode(self.secret_key, altchars=None)
The CL-ACCESS-SIG header is generated by creating a sha256 HMAC using the base64-decoded secret key on the prehash string timestamp + HTTP method + path + body (where '+' represents string concatenation) and base64-encode the output.
digest = hmac.new(secret_key_as_bytes, message, hashlib.sha256).digest()
signature = str(base64.b64encode(digest))
wish it's not too late
hmac.new()
function need the key argument to be bytes,so:
byte_key = bytes(self.secret_key, 'UTF-8') # key.encode() would also work in this case
but it's no done yet, if you go with this, you would get
TypeError: Unicode-objects must be encoded before hashing
to fix this , your message should be encoded.
message = message.encode()
h = hmac.new(byte_key, message, hashlib.sha256).hexdigest()
now it's done.
Howerver I got 400. sad.
I found someone have done this:
https://gist.github.com/badmofo/d109fbc447fea64d99e5ca58bdf53d7b

How to validate Slack API request?

I've a slack app that is sending to a service written in typescript that is forwarding the message to my python script where I'm trying to validate the request. However, for some reason, the validation always fails.
The typescript relevant code:
const rp = require('request-promise');
var qs = require('querystring')
export const handler = async (event: any, context: Context, callback: Callback): Promise<any> => {
const options = {
method: method,
uri: some_url,
body: qs.parse(event.body),
headers: {
signature: event.headers['X-Slack-Signature'],
timestamp: event.headers['X-Slack-Request-Timestamp']
},
json: true
};
return rp(options);
The python code (based on this article) :
def authenticate_message(self, request: Request) -> bool:
slack_signing_secret = bytes(SLACK_SIGNING_SECRET, 'utf-8')
slack_signature = request.headers['signature']
slack_timestamp = request.headers['timestamp']
request_body = json.loads(request.body)['payload']
basestring = f"v0:{slack_timestamp}:{request_body}".encode('utf-8')
my_signature = 'v0=' + hmac.new(slack_signing_secret, basestring, hashlib.sha256).hexdigest()
return hmac.compare_digest(my_signature, slack_signature))
I'm pretty sure the issue is the way I'm taking the body but tried several options and still no luck.
Any ideas?
Thanks,
Nir.
I had the same issue. My solution was to parse the payload to replace '/' by %2F and ':' by %3A. It's not explicit in the Slack doc but if you see the example, that's how it's shown:
'v0:1531420618:token=xyzz0WbapA4vBCDEFasx0q6G&team_id=T1DC2JH3J&team_domain=testteamnow&channel_id=G8PSS9T3V&channel_name=foobar&user_id=U2CERLKJA&user_name=roadrunner&command=%2Fwebhook-collect&text=&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT1DC2JH3J%2F397700885554%2F96rGlfmibIGlgcZRskXaIFfN&trigger_id=398738663015.47445629121.803a0bc887a14d10d2c447fce8b6703c'
You see command and response_url are parsed.
I managed to get this working in Python. I see you ask in Typescript, but I hope this python script helps:
#app.route('/slack-validation', methods=['GET', 'POST'])
def slack_secutiry():
headers = request.headers
timestamp = request.headers['X-Slack-Request-Timestamp']
slack_payload = request.form
dict_slack = slack_payload.to_dict()
### This is the key that solved the issue for me, where urllib.parse.quote(val, safe='')] ###
payload= "&".join(['='.join([key, urllib.parse.quote(val, safe='')]) for key, val in dict_slack.items()])
### compose the message:
sig_basestring = 'v0:' + timestamp + ':' + payload
sig_basestring = sig_basestring.encode('utf-8')
### secret
signing_secret = slack_signing_secret.encode('utf-8') # I had an env variable declared with slack_signing_secret
my_signature = 'v0=' + hmac.new(signing_secret, sig_basestring, hashlib.sha256).hexdigest()
print('my signature: ')
print(my_signature)
return '', 200
It might be useful for you to check how the request validation feature is implemented in the Bolt framework:
https://github.com/slackapi/bolt-python/blob/4e0709f0578080833f9aeab984a778be81a30178/slack_bolt/middleware/request_verification/request_verification.py
Note that it is implemented as a middleware, enabled by default when you instantiate the app (see attribute request_verification_enabled).
You can inspect this behaviour and/or change it if you want to validate the requests manually:
app = App(
token=SLACK_BOT_TOKEN,
signing_secret=SLACK_SIGNING_SECRET,
request_verification_enabled=False
)
The following solution solves the problem of verification of signing secret of slack
#!/usr/bin/env python3
import hashlib
import hmac
import base64
def verify_slack_request(event: dict, slack_signing_secret: str) -> bool:
"""Verify slack requests.
Borrowed from https://janikarhunen.fi/verify-slack-requests-in-aws-lambda-and-python.html
- Removed optional args
- Checks isBase64Encoded
:param event: standard event handler
:param slack_signing_secret: slack secret for the slash command
:return: True if verification worked
"""
slack_signature = event['headers']['x-slack-signature']
slack_time = event['headers']['x-slack-request-timestamp']
body = event['body']
if event['isBase64Encoded']:
body = base64.b64decode(body).decode("utf-8")
""" Form the basestring as stated in the Slack API docs. We need to make a bytestring"""
base_string = f'v0:{slack_time}:{body}'.encode('utf-8')
""" Make the Signing Secret a bytestring too. """
slack_signing_secret = bytes(slack_signing_secret, 'utf-8')
""" Create a new HMAC 'signature', and return the string presentation."""
my_signature = 'v0=' + hmac.new(
slack_signing_secret, base_string, hashlib.sha256
).hexdigest()
''' Compare the the Slack provided signature to ours.
If they are equal, the request should be verified successfully.
Log the unsuccessful requests for further analysis
(along with another relevant info about the request).'''
result = hmac.compare_digest(my_signature, slack_signature)
if not result:
logger.error('Verification failed. my_signature: ')
logger.error(f'{my_signature} != {slack_signature}')
return result
if __name__ == '__main__':
# add correct params here
print(verify_slack_request({}, None))
Borrowed From:
https://gist.github.com/nitrocode/288bb104893698011720d108e9841b1f
Credits: https://gist.github.com/nitrocode

Categories