As a general approach, we modified the insecure example rather then starting from scratch. Details on changes to each component are below.
The high-level flow of a typical transaction is the following:
- a transaction is initiated on the atm_interface (XMLRPC)
- the users card_id retrieved from the card and sent to the Bank by the atm (https/json)
- The bank generates a 32 byte challenge for the card to "sign"
- the mongoDB document for that account is updated with the challenge, and an expire time for the challenge (5 seconds)
- the bank sends back the challenge
- the card is sent the challenge by the atm
- the card uses its unique AES_KEY to encrypt the challenge
- the card sends the encrypted bytes back to the atm
- the encrypted response, pin and other necessary transaction data is sent to the Bank (https/json)
- The bank verifies the encrypted response and pin, and completes the transaction if is valid.
- An encrypted response is valid if it:
- is given within 5 seconds of when the challenge was requested.
- The bank can use the saved AES_KEY to decrypt the encrypted_challenge and verify it is the same as the unencrypted challenge stored in the accounts mongoDB document.
- Both the HSM and the Bank use static hardware values and SuperFastHash to generate a 32 byte AES key. This is stored on the bank at provision time.
- Every time a transaction is initiated, the card receives a challenge, and "signs" it with its AES key. The bank verifies this since the AES_KEY is on the bank as well.
- Whenever a withdraw is initiated, the bank will send a 64 byte encrypted message. This message is encrypted with the HSMs AES_KEY. The format of this message is {first_bill_to_dispense},{last_bill_to_dispense}\x00 + random bytes. The HSM will ensure each 16 byte block is different, and that the first n bytes of the message are the same and follow this format before proceeding to allow a transaction to occur. Additionally, in order to work correctly, the first bill to dispense must match what is saved on the HSM as the next bill to dispense.
- A uuid is sent to an HSM and stored. (hsm.py)
- The card loads bills from the bill file. (hsm.py)
- The card returns its unqiue AES_KEY. (hsm.py)
- The AES_KEY and number of bills are sent to the bank server and stored. (new function in bank.py)
- A uuid is sent to the card and stored.
- the card returns its unique AES_KEY
- The unique AES_KEY and pin for the account is sent to the bank and stored. (new function in bank.py)
- A function was added to bank.py to request a 32 byte alphanumeric challenge from the bank for a given card_id.
- A function was added on card.py to request that a challenge be encrypted and return the result
- The function to withdraw bills from the HSM now accepts a 64 byte message that the HSM will use to decrypt and send the appropriate bills.
-
Instead of using a sqlite database, we used mongoDB. There is no particular reason for this other then flexibility of documents, and my previous experience with mongoDB and lack thereof with sqlite
-
There are two types of documents in the database, each having its own collection.
- An account is initialized with the following format:
new_account = {
'account_name' : account_name,
'card_id' : self.hash_card(card_id),
'balance' : amount,
'AES_KEY' : None,
'pin' : None,
'chall' : None,
'time' : None
}
- account_name: the plaintext account_name provided by an admin when the account is created
- card_id: The card_id, which is hashed using SHA256
- balance: The plaintext balance of an account
- AES_KEY: a key generated on the PSOC during each request
- pin: The pin salted with the plaintext card_id and hashed using argon2
- chall: a 32 byte alphanumeric string
- time: the time a the current chall will expire
- balance: The plaintext balance of an account
- pin: The pin salted with the plaintext card_id and hashed using argon2
- An ATM is initialized with the following format
new_atm = {
'atm_id' : atm_id,
'num_bills' : 0,
'num_dispensed_bills' : 0,
'AES_KEY' : None
}
- atm_id: The atm_id (hsm_id) in plaintext
- AES_KEY: a key generated on the PSOC during each withdraw request
- num_bills: The number of bills remaining on the HSM
####### Fields changed when a withdraw request is made
- num_dispensed_bills: The number of bills the hsm has dispensed so far.
- num_bills: The number of bills remaining on the HSM