Security

Your server that receives requests for the configured URL(s) should accept traffic over HTTPS and use SSL/TLS. (Galileo supports TLS 1.2.) In addition to in-transit encryption, you must also perform validation on the messages received on these endpoints to ensure that they are indeed from Galileo.

Based on the Content-Type of the request, which is configured according to your request, validation involves either a JWT (JSON web token) or a computed HMAC signature. Both involve use of a shared-secret key but have a number of differences that should be considered.

Similarities

  1. Both HMAC and JWT require that you configure a shared secret with Galileo.
  2. Both use the non-standard Encryption-Type header, which specifies the underlying algorithm used.
  3. Both have a value of galileo for the standard UserID header.

Differences

  1. HMAC is used for Content-Type: application/x-www-form-urlencoded requests, whereas JWT is used for Content-Type: application/json.
  2. Validation for requests that use HMAC is done using the non-standard Signature header, whereas those that use JWT use the standard Authorization header.

JWT Validation

The alternative approach (as shown in the Differences section) is to validate the JWT. For example, the request might look something like this:

POST /Authorization HTTP/1.1
Host: localhost:3150
X-Request-ID: 92c341e2-8b50-45bb-805c-8d0aa76124b8
Encryption-Type: JWT-HS256
Accept-Encoding: gzip, deflate
Content-Length: 380
Accept: */*
User-Agent: python-requests/2.9.1
Connection: keep-alive
Date: 20201110:163953UTC
User-ID: galileo
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnYWxpbGVvIiwiaWF0IjoxNjA1MDUxNTkzLCJleHAiOjE2MDUwNTE1OTZ9.jjHsPgAOZc13EGAGeqVHVHH0Nu_ajE5XpDcbDYmS1GI

{
    "balance": "195.70",
    "merch_name": "Target 2400 South 200 West",
    "network": "M",
    "timestamp": "20101108:201512UTC",
    "cur_code": "840",
    "tran_type": 5,
    "prn": "155174792582",
    "prod_id": 5042,
    "amount": -8.95,
    "blank": null,
    "mcc": "4678",
    "merch_num": "321654321654",
    "prog_id": 511,
    "type": "auth",
    "id": "M.auth.2994428",
    "tran_id": "2994428",
    "merch_loc": "Salt Lake City, UT"
}

Again, the implementation is language/library dependent, but it might look something like this if you use Python:

import jwt

# Shared with Galileo
secret_key = 'wow_i_should_encrypt_this'

# This library wants us to ignore the 'Bearer' part, as we should
token = request.headers['Authorization'].split(' ')[1]

try:
    jwt.decode(
        token,
        secret_key,
        issuer=request.headers['User-ID'],
        algorithms=['HS256']  # Won't change for JWT
    )
except jwt.ExpiredSignatureError:
    print('Oh no, the JWT is expired!')
    raise
except jwt.DecodeError:
    print("Oh no, this request probably isn't from Galileo!")
    raise

HMAC validation

For example, a request that looks similar to the following:

POST /Authorization HTTP/1.1
Host: localhost:3150
X-Request-ID: 234dd0dc-8e44-4311-b334-961b1e058b72
Encryption-Type: HMAC-SHA256
Accept-Encoding: gzip, deflate
Content-Length: 424
Accept: */*
User-Agent: python-requests/2.9.1
Connection: keep-alive
Signature: b0+KPPtDyvuWdx55QJ+5YQq0K0UkXPurk9cRMHFXLSc=
Date: 20201110:161601UTC
User-ID: galileo
Content-Type: application/x-www-form-urlencoded

token_type=Apple+Inc.&settle_amt=20.00&auth_id=18056167&auth_network=M&local_surcharge_amt=2.00&merch_name=Arbys+1532&merchant=Arbys+1532+Rochester+MN&de39=00&billing_amt=30.00&original_auth_id=5&mcc=5814&settle_surcharge_amt=2.50&billing_curr_code=666&merch_num=945000153210000&auth_tran_type=5&settle_curr_code=555&local_curr_code=444&merch_loc=Rochester%2C+MN&card_not_present=N&amount=30.00&local_amt=10.00&sign_amount=-
  1. Create a dictionary/hash map using the header field of the request:
signature_data = {
    'Content-Length': request['Content-Length'],
    'Encryption-Type': request['Encryption-Type'],
    'User-ID': request['User-ID'],
    'Content-Type': request['Content-Type'],
    'Date': request['date']
}
  1. Add the values from the form data in the request body to the hash map based on their key.

With a properly parsed body, you might be able to do something like this:

for field_key, field_value in request_body.items():
    signature_data[field_key] = field_value

or more explicitly:

signature_data['token_type'] = 'Apple Inc.'
signature_data['settle_amt'] = '20.00'
...

for this simple case, assuming that you've parsed the form data into some sort of dictionary/hashmap,

  1. Sort the hashmap, by key, alphabetically. For example, your map contains something like this:
signature_data = {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Encryption-Type': 'HMAC-SHA256',
    'billing_amt': '30.00',
    'token_type': 'Apple Inc.'    
}

Notice that billing_amt comes after Content-Type, even though b comes before c in the English alphabet. In short, the set of upper-case letters comes before the set of lower-case letters.

  1. Compute the string:
signature_string = ''

for key in signature_data:
    signature_string += key + '|' + base64encode(signature_data[key])
  1. Compute the HMAC-SHA256 signature over this string, using the shared secret key. The specifics of this are language dependent based on the library used, among other considerations, but in Python it might look something like this:
import base64
import hashlib
import hmac

# Shared with Galileo
secret_key = 'wow_i_should_encrypt_this'

signature = base64.b64encode(
    hmac.new(
        secret_key,
        signature_string,
        hashlib.sha256
    ).digest(),
)

if signature != request.headers['Signature']:
    print('Oh no!')

If you are having problems with this verification, ensure that all capitalization, whitespace, and other factors are maintained when computing the signature. Additionally, some HTTP libraries change the D in User-ID to look like User-Id, for instance. The easiest way to validate these things is to obtain a raw version of the request and ensure that nothing has changed from the time it was received over the network socket to the time it made it into your code.