Files
edx-platform/openedx/core/djangoapps/credit/signature.py

87 lines
2.6 KiB
Python

"""
Calculate digital signatures for messages sent to/from credit providers,
using a shared secret key.
The signature is calculated as follows:
1) Encode all parameters of the request (except the signature) in a string.
2) Encode each key/value pair as a string of the form "{key}:{value}".
3) Concatenate key/value pairs in ascending alphabetical order by key.
4) Calculate the HMAC-SHA256 digest of the encoded request parameters, using a 32-character shared secret key.
5) Encode the digest in hexadecimal.
It is the responsibility of the credit provider to check the signature of messages
we send them, and it is our responsibility to check the signature of messages
we receive from the credit provider.
"""
import hashlib
import hmac
import logging
from django.conf import settings
log = logging.getLogger(__name__)
def _encode_secret(secret, provider_id):
"""
Helper function for encoding text_type secrets into ascii.
"""
try:
secret.encode('ascii')
except UnicodeEncodeError:
secret = None
log.error('Shared secret key for credit provider "%s" contains non-ASCII unicode.', provider_id)
return secret
def get_shared_secret_key(provider_id):
"""
Retrieve the shared secret for a particular credit provider.
It is possible for the secret to be stored in 2 ways:
1 - a key/value pair of provider_id and secret string
{'cool_school': '123abc'}
2 - a key/value pair of provider_id and secret list
{'cool_school': ['987zyx', '123abc']}
"""
secret = getattr(settings, "CREDIT_PROVIDER_SECRET_KEYS", {}).get(provider_id)
# When secret is just characters
if isinstance(secret, str):
secret = _encode_secret(secret, provider_id)
# When secret is a list containing multiple keys, encode all of them
elif isinstance(secret, list):
for index, secretvalue in enumerate(secret):
if isinstance(secretvalue, str):
secret[index] = _encode_secret(secretvalue, provider_id)
return secret
def signature(params, shared_secret):
"""
Calculate the digital signature for parameters using a shared secret.
Arguments:
params (dict): Parameters to sign. Ignores the "signature" key if present.
shared_secret (str): The shared secret string.
Returns:
str: The 32-character signature.
"""
encoded_params = "".join([
"{key}:{value}".format(key=key, value=params[key])
for key in sorted(params.keys())
if key != "signature"
])
hasher = hmac.new(shared_secret.encode('utf-8'), encoded_params.encode('utf-8'), hashlib.sha256)
return hasher.hexdigest()