Failed Validation while setting up LinkedIn Webhook - python

#app.route('/webhooks/linkedin', methods=['GET'])
def webhook_challenge_linkedIn():
# creates HMAC SHA-256 hash from incoming token and your consumer secret
sha256_hash_digest = hmac.new(bytes({api_secret},'utf-8'), msg=bytes(request.args.get('challengeCode'),'utf-8'), digestmod=hashlib.sha256)
# construct response data with base64 encoded hash
print((sha256_hash_digest))
val = {
"challengeCode" : request.args.get('challengeCode'),
"challengeResponse" : sha256_hash_digest.hexdigest()
}
return json.dumps(val)
When I try to set up the server and send an authentication request from LinkedIn for webhook to this endpoint it says Failed Validation
all i need to do for the verification is return the challenge code in
challengeResponse = Hex-encoded(HMACSHA256(challengeCode, clientSecret))
I think this is what I did but the still validation gets failed.
I don't seem to see the issue.

Try the following:
#app.route('/webhooks/linkedin', methods=['GET'])
def webhook_challenge_linkedIn():
challenge_code = request.args.get('challengeCode')
# creates HMAC SHA-256 hash from incoming token and your consumer secret
sha256_hash_digest = hmac.new(api_secret.encode(), challenge_code.encode(), hashlib.sha256).hexdigest()
# construct response data with base64 encoded hash
# print(sha256_hash_digest)
val = {
"challengeCode" : challenge_code,
"challengeResponse" : sha256_hash_digest
}
return json.dumps(val)
See this answer from How to use SHA256-HMAC in python code?

Related

How do I send credentials so I can unit test MSAL for Python?

The flow to this admin app is basically:
User goes to /admin.
#azure/msal-react checks if the user is logged in and if not redirects them to login.
When the accessToken, idToken, and oid have been received on the FE, they are sent to my API (/api) where they are validated.
If the tokens are validated, and verified as belonging to the oid that came from the FE, then a JWT is issued for the user indicating they are authenticated with the API as well.
The JWT is sent back to the FE where having both the MSAL isAuthenticated() and their API JWT isApiAuthenticated() indicates they are fully authenticated and it renders the FE components.
I'm trying to write unit tests for the what I have in the aad.py (bottom).
The issue I'm seeing is that I need to retrieve and send it a legit MSAL access_token, id_token, and oid. That means I actually need to log in a user against our AAD.
I do have a dummy user to use for this purpose, but I'm not seeing in the Python MSAL documentation how to sign the user in without interaction and just submit username and password.
Suggestions for how to write unit tests to test? Do I need to mockup something else, or should I be able to send a dummy user's username and password with Python MSAL?
# Python libraries
import json
# Third-party dependencies
import jwt
import requests
from base64 import b64decode, b64encode
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from config import settings
def ms_graph(access_token):
"""
Takes the access_token from the FE and uses it query MS Graph for user
information.
Returns an object.
Takes one argument:
access_token: sent from FE.
"""
graph_response = requests.get( # Use token to call downstream service
settings.GRAPH_URI,
headers={'Authorization': 'Bearer ' + access_token},)
return json.loads(graph_response.text)
def validate_token(token):
"""
Used to decode the token sent from the FE. The result is
a verified signature that contains information about the user and tokens.
Returns an object.
Takes one argument:
token: The idToken sent from the FE that needs to be validated.
"""
# Get a list of the possible public keys from the JWKS_URI endpoint
jwkeys = requests.get(settings.JWKS_URI).json()['keys']
# Extract the 'kid' from the unverified header to get the public key
token_key_id = jwt.get_unverified_header(token)['kid']
# Get the object corresponding to the 'kid' key
jwk = [key for key in jwkeys if key['kid'] == token_key_id][0]
# Encode the 'x5c' to eventually get the public key for decoding
der_cert = b64decode(jwk['x5c'][0])
cert = x509.load_der_x509_certificate(der_cert, default_backend())
public_key = cert.public_key()
pem_key = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
# Finally, validate and decode the token
token_claims = jwt.decode(
token,
pem_key,
algorithms=['RS256'],
audience=settings.CLIENT_ID
)
return token_claims
def validate_user(access_token, id_token, client_oid):
"""
Takes the id_token sent from the FE and validates the signature.
The validated signature also contains an oid claim which is used to verify
the oid sent from the FE.
Returns an object containing:
{
"user_validated": bool,
"graph_response": object
}
Takes two arguments:
access_token: access_token sent from the FE.
id_token: the id_token sent from the FE.
client_oid: the oid sent from the FE.
"""
# Decode the token to retrieve claims
id_token_payload = validate_token(id_token)
# Check if the claim oid and matches the oid sent from the FE
if id_token_payload['oid'] == client_oid:
graph_response = ms_graph(access_token)
res = dict()
res['user_validated'] = graph_response['id'] == client_oid
res['graph_response'] = graph_response
return res
If you really really want to obtain tokens for unit tests with username and password then look at acquire_token_by_username_password() and its example in MS docs.
But there are reasons to not let unit tests use the real MS Graph API:
dependency on network connection can make unit tests run slow or fail, even if your code still works fine,
storing credentials in repository is dangerous, keeping them properly is difficult,
the dummy account can be locked,
numerous test calls can cause throttling,
it is kind of unpolite to abuse the infrastructure with non-productive requests.
The suggestion for unit tests is usual: isolate test cases, then mock and patch any external dependencies not related to code being tested.
MSAL uses requests for calling the cloud, and there is a great package for mocking its work: meet responses. Prevent MSAL for sending out any requests and mock OpenID configuration call:
import responses
import time
import unittest
MS_OPENID_CONFIG = {
"authorization_endpoint": "https://login.microsoftonline.com/common/"
"oauth2/v2.0/authorize",
"token_endpoint": "https://login.microsoftonline.com/common/oauth2/"
"v2.0/token",
}
MS_TOKENS = {
'id_token_claims': {
'exp': time.time() + 60,
'oid': '00000000-0000-0000-0000-000000000000',
'upn': 'test_user_1_'
},
'id_token': {},
'access_token': {}
}
class BaseTest(unittest.TestCase):
def afterSetUp(self):
self.responses = responses.RequestsMock()
self.responses.start()
self.responses.add(
responses.GET,
'https://login.microsoftonline.com/common/v2.0/'
'.well-known/openid-configuration',
json=MS_OPENID_CONFIG
)
# add_calback() allows modifying mock token in runtime
self.responses.add_callback(
responses.POST,
'https://login.microsoftonline.com/common/oauth2/v2.0/token',
callback=lambda request: (200, {}, json.dumps(MS_TOKENS)),
content_type='application/json',
)
self.addCleanup(self.responses.stop)
self.addCleanup(self.responses.reset)
For testing validate_token() you can bundle pre-generated test RSA key pair:
openssl genrsa -out test_rsa_private.pem 512
openssl rsa -in test_rsa_private.pem -pubout -outform PEM -out test_rsa_public.pem
Then you can mock response to settings.JWKS_URI in the same way as OpenID config, returning test public key with known kid.
If you'd prefer to save time on signing test tokens, then just mock jwt signature verification to always return True:
from unittest import Mock, patch
def test_validate_token(self)
...
with patch('jwt.algorithms.RSAAlgorithm.verify', new=Mock(return_value=True)):
# jwt.decode(at, key=open('test_rsa_public.pem').read(), algorithms=['RS256'], options={'verify_exp': False, 'verify_aud': False})
validate_token(test_token)
And so on, unit test should only test the code of unit itself, everything around can be mocked as needed. Read the source of MSAL and jwt. Don't mock what looks like implementation details, they might change in future versions.

SwiftyRSA && Python's cryptography for signature verification

I am trying to sign a piece of text using a private key using SwiftyRSA, which I do successfully. Then send the signature to the python server which holds the public key for verification. the But I keep receiving the InvalidSignature exception.
I have tried different hashing algorithms, bit sizes, but still the same InvalidSignature exception. I am sure that the key and signature are related b/c they are generated at the same time!
My issue is not with the libraries themselves--which I think are functioning properly. I think it has to do with a padding discrepancy between the two libraries. Cryptography is using PSS, while I cant find the padding/salt that SwiftyRSA uses.
Key Verification Script
#app.route('/', methods=['POST'])
def verify():
record = json.loads(request.data)
signature = record['sig']
public_key = record['public_key']
hash_local_token = record['hash_local_token']
input_string = record['input_string']
verification_response = auth.verify_signature(signature=signature, pub_key=public_key, input_string=input_string)
print(verification_response)
return jsonify({"Verification": verification_response})
def verify_signature(signature, pub_key, input_string):
# Load the public key
# Url Safe Base64 Decoding
derdata = base64.b64decode(pub_key)
public_key = load_der_public_key(derdata, default_backend())
signature_decoded = base64.urlsafe_b64decode(signature)
# Perform the verification.
try:
public_key.verify(
signature_decoded,
str.encode(input_string),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH,
),
hashes.SHA256(),
)
return "SUCCESS: Signature Verified!"
except cryptography.exceptions.InvalidSignature as e:
return 'FAILED: Payload and/or signature files failed verification'
Key Generation Script
func moreTesting() {
do {
let keyPair = try SwiftyRSA.generateRSAKeyPair(sizeInBits: 4096)
let privateKey = keyPair.privateKey
let publicKey = keyPair.publicKey
let inputString = "test_string"
let clear = try ClearMessage(string: inputString, using: .utf8)
let signature = try clear.signed(with: privateKey, digestType: .sha256)
let base64Signature = signature.base64String
let parameters: [String: String] = [
"sig": base64Signature,
"input_string": inputString,
"public_key": try publicKey.base64String()
]
AF.request("http://127.0.0.1:5000/", method: .post, parameters: parameters, encoder: JSONParameterEncoder.default).responseJSON { response in
debugPrint(response)
}
}
catch {
print(Error.self)
}
}
It appears that SwiftyRSA's signing APIs use only PKCS1 and not PSS. See: https://github.com/TakeScoop/SwiftyRSA/blob/master/Source/Signature.swift#L20-L27
So to resolve this you'll want to switch your padding from PSS to PKCS1v15 on the Python side.

How to create a SECRET_HASH for AWS Cognito using boto3?

I want to create/calculate a SECRET_HASH for AWS Cognito using boto3 and python. This will be incorporated in to my fork of warrant.
I configured my cognito app client to use an app client secret. However, this broke the following code.
def renew_access_token(self):
"""
Sets a new access token on the User using the refresh token.
NOTE:
Does not work if "App client secret" is enabled. 'SECRET_HASH' is needed in AuthParameters.
'SECRET_HASH' requires HMAC calculations.
Does not work if "Device Tracking" is turned on.
https://stackoverflow.com/a/40875783/1783439
'DEVICE_KEY' is needed in AuthParameters. See AuthParameters section.
https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html
"""
refresh_response = self.client.initiate_auth(
ClientId=self.client_id,
AuthFlow='REFRESH_TOKEN',
AuthParameters={
'REFRESH_TOKEN': self.refresh_token
# 'SECRET_HASH': How to generate this?
},
)
self._set_attributes(
refresh_response,
{
'access_token': refresh_response['AuthenticationResult']['AccessToken'],
'id_token': refresh_response['AuthenticationResult']['IdToken'],
'token_type': refresh_response['AuthenticationResult']['TokenType']
}
)
When I run this I receive the following exception:
botocore.errorfactory.NotAuthorizedException:
An error occurred (NotAuthorizedException) when calling the InitiateAuth operation:
Unable to verify secret hash for client <client id echoed here>.
This answer informed me that a SECRET_HASH is required to use the cognito client secret.
The aws API reference docs AuthParameters section states the following:
For REFRESH_TOKEN_AUTH/REFRESH_TOKEN: USERNAME (required), SECRET_HASH
(required if the app client is configured with a client secret),
REFRESH_TOKEN (required), DEVICE_KEY
The boto3 docs state that a SECRET_HASH is
A keyed-hash message authentication code (HMAC) calculated using the
secret key of a user pool client and username plus the client ID in
the message.
The docs explain what is needed, but not how to achieve this.
The below get_secret_hash method is a solution that I wrote in Python for a Cognito User Pool implementation, with example usage:
import boto3
import botocore
import hmac
import hashlib
import base64
class Cognito:
client_id = app.config.get('AWS_CLIENT_ID')
user_pool_id = app.config.get('AWS_USER_POOL_ID')
identity_pool_id = app.config.get('AWS_IDENTITY_POOL_ID')
client_secret = app.config.get('AWS_APP_CLIENT_SECRET')
# Public Keys used to verify tokens returned by Cognito:
# http://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html#amazon-cognito-identity-user-pools-using-id-and-access-tokens-in-web-api
id_token_public_key = app.config.get('JWT_ID_TOKEN_PUB_KEY')
access_token_public_key = app.config.get('JWT_ACCESS_TOKEN_PUB_KEY')
def __get_client(self):
return boto3.client('cognito-idp')
def get_secret_hash(self, username):
# A keyed-hash message authentication code (HMAC) calculated using
# the secret key of a user pool client and username plus the client
# ID in the message.
message = username + self.client_id
dig = hmac.new(self.client_secret, msg=message.encode('UTF-8'),
digestmod=hashlib.sha256).digest()
return base64.b64encode(dig).decode()
# REQUIRES that `ADMIN_NO_SRP_AUTH` be enabled on Client App for User Pool
def login_user(self, username_or_alias, password):
try:
return self.__get_client().admin_initiate_auth(
UserPoolId=self.user_pool_id,
ClientId=self.client_id,
AuthFlow='ADMIN_NO_SRP_AUTH',
AuthParameters={
'USERNAME': username_or_alias,
'PASSWORD': password,
'SECRET_HASH': self.get_secret_hash(username_or_alias)
}
)
except botocore.exceptions.ClientError as e:
return e.response
I also got a TypeError when I tried the above solution. Here is the solution that worked for me:
import hmac
import hashlib
import base64
# Function used to calculate SecretHash value for a given client
def calculateSecretHash(client_id, client_secret, username):
key = bytes(client_secret, 'utf-8')
message = bytes(f'{username}{client_id}', 'utf-8')
return base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()
# Usage example
calculateSecretHash(client_id, client_secret, username)

Steam WebAPI AuthenticateUser

How to auth user via https://api.steampowered.com/ISteamUserAuth/AuthenticateUser/v0001 api method?
For example, I will get steam public key data from https://steamcommunity.com/login/getrsakey/, do some encryption and then send this data to specified api url as POST.
But server returns '403 Forbidden' everytime.
My code example:
from Crypto.Cipher import AES
from Crypto.Cipher.PKCS1_v1_5 import PKCS115_Cipher
from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
import hashlib
import json
import requests
steamid = '<MY_STEAMID64>'
steampass = '<MY_STEAM_PASSWORD>'
loginkey = hashlib.md5(bytes(steampass, 'utf-8')).hexdigest()
blob32 = get_random_bytes(32)
getrsa_url = 'https://steamcommunity.com/login/getrsakey/'
getrsa_data = {'username': '<MY_STEAM_USERNAME>'}
getrsa_resp = requests.get(getrsa_url, params=getrsa_data)
response = json.loads(getrsa_resp.text)
if response.get('success'):
steam_publickey_mod = response.get('publickey_mod')
steam_publickey_mod = int(steam_publickey_mod, 16)
steam_publickey_exp = response.get('publickey_exp')
steam_publickey_exp = int(steam_publickey_exp, 16)
steam_rsa_key = RSA.construct((steam_publickey_mod, steam_publickey_exp))
steam_rsa = PKCS115_Cipher(steam_rsa_key)
if steam_rsa_key.can_encrypt():
sessionkey = steam_rsa.encrypt(blob32)
if type(sessionkey) is tuple:
sessionkey = sessionkey[0]
steam_aes = AES.new(blob32)
encrypted_loginkey = steam_aes.encrypt(loginkey)
if all([steamid, sessionkey, encrypted_loginkey]):
authenticate_user_url = (
'https://api.steampowered.com/ISteamUserAuth/AuthenticateUser/v0001')
authenticate_user_json = {
'steamid': steamid,
'sessionkey': sessionkey,
'encrypted_loginkey': encrypted_loginkey,
}
if __name__ == '__main__':
import ipdb
ipdb.set_trace()
authenticate_user_resp = requests.post(url=authenticate_user_url,
data=authenticate_user_json)
authenticate_user_resp.ok returns False
authenticate_user_resp.status_code returns 403
authenticate_user_resp.reason returns Forbidden
Sorry for my bad English, please
AuthenticateUser doesn't do what you think it does. It's used by the Steam client to get web session logon cookies for the user who is currently logged into the client. The loginkey that AuthenticateUser asks for comes from the CM (the server which the client connects to).
If you want to log a user into the websites, you need to use the HTTP endpoints to do so. Once you have the RSA key and have encrypted your password with that key, you can authenticate by POSTing to https://steamcommunity.com/login/dologin/ with these urlencoded parameters in the body:
captcha_text - Empty string or the text of a CAPTCHA you've been prompted with
captchagid - The GID of the CAPTCHA you've been prompted with, or -1 if you haven't been
emailauth - The Steam Guard code sent to your email address, or empty string if not applicable
emailsteamid - Empty string
loginfriendlyname - Empty string
password - Your password, encrypted with the RSA public key, and the resulting ciphertext in base64
remember_login - true if you want to remember your login or false if not (the strings true and false)
rsatimestamp - The timestamp that you got with the RSA key
twofactorcode - The TOTP code you got from your mobile app, or empty string if not applicable
username - Your account name
As far as I am concerned, you are not allowed to do this operation, hence the "403 forbidden" So, you simply are not "authorized" to perform this with the credentials you have.
https://en.wikipedia.org/wiki/HTTP_403
A 403 response generally indicates one of two conditions:
Authentication was provided, but the authenticated user is not
permitted to perform the requested operation. The operation is
forbidden to all users. For example, requests for a directory listing
return code 403 when directory listing has been disabled.

HMAC SHA1 Digest in python

I'm using the Moves API to get some fitness data. Instead of querying the API on a regular basis I would like to use the storyline notifications.
It works, I get a request from the API but I'm unable to verify the hmac sha1 signature provided in the request.
The Documentation says:
All notification requests are signed with Base64 encoded HMAC-SHA1
signature. The signature is calculated as HMAC_SHA1(<your client
secret>,<request body>|<timestamp>|<nonce>), in other words the client
secret as the key and request body, timestamp and nonce concatenated
as the message data. HTTP headers are not included in the signature.
The headers X-Moves-Signature, X-Moves-Timestamp and X-Moves-Nonce
contain the signature, timestamp and nonce values. The timestamp is a
unix timestamp, seconds since Jan 01 1970 00:00:00 GMT.
My Implementation:
from hmac import new as hmac_new
from hashlib import sha1
def check_signature(signature, timestamp, nonce, client_secret, request_body):
msg = request_body + timestamp.encode('utf-8') + nonce.encode('utf-8')
hmac = hmac_new(key=client_secret, msg=msg, digestmod=sha1)
return hmac.digest().encode('base64') == signature
I get the request from flask and call my function likes this:
check_signature(headers['X-Moves-Signature'], headers['X-Moves-Timestamp'], headers['X-Moves-Nonce'], settings['client-secret'], request.data)
Values:
client-secret= mnMuu6rDMkeG5FL0Fm0ho2z14JUhMVWAntUnGz0VyXc446RtqP8J7ETfag0TQa58
request-body = {"userId": 34511428141091768, "storylineUpdates": [{"reason": "DataUpload", "endTime": "20150429T121602Z", "lastSegmentType": "place", "lastSegmentStartTime": "20150429T101434Z", "startTime": "20150429T101434Z"}]}
X-Moves-Nonce = eqVCO4bnNbN+8Hhiz7ZceA==
X-Moves-Signature = BRMwYCxglul01wbyXpfpdtiJh2Y=
X-Moves-Timestamp = 1430309780
my-digest = paWR/3yiJ8NT8KukorGVJlpmQeM=
my-hexdigest = a5a591ff7ca227c353f0aba4a2b195265a6641e3
moves_signature = BRMwYCxglul01wbyXpfpdtiJh2Y=
I also tried http://www.freeformatter.com/hmac-generator.html and also received a5a591ff7ca227c353f0aba4a2b195265a6641e3.
(the client secret is not valid anymore).
As you can see from the values my digest and the moves_signature are not equal. Sadly I'm unable to get a digest that is equal to the one from moves but I'm unable to locate the problem. Does anybody have an idea on how to fix this?

Categories

Resources