This shows you the differences between two versions of the page.
ac:laboratoare:09 [2024/12/02 08:40] dimitrie.valu |
ac:laboratoare:09 [2024/12/15 06:42] (current) dimitrie.valu |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ===== Lab 09 - TOFU-based Authenticated Key Exchange ===== | + | ===== Lab 09 - EMV Basics ===== |
- | In this lab, we will implement Trust On First Use in a manner similar to that of SSH. We will base our implementation around ''%%PyCryptodome%%'' - you can find the relevant documentation [[https://www.pycryptodome.org/src/api |here]]. | + | We have presented in class the basic communication mechanisms in EMV, the protocol |
+ | used in banking transactions. We have seen the low-level communication layer, | ||
+ | as well as the higher layers of communication, including the format of commands (CAPDUs) | ||
+ | and responses (RAPDUs). Furthermore, we have also seen an example of transaction. | ||
- | ==== 0. Init ==== | + | Throughout this lab you'll have to analyze the transcript of a transaction, |
+ | specifying what commands are being sent and what data is received from the card. | ||
- | Use these commands to generate a private key and a Diffie-Hellman parameter file: | + | For this, you should get the EMV specification for contact/acceptance device (books 1-3) from [[http://emvco.com]]: |
+ | * [[https://www.emvco.com/specifications/emvlevel-1-specifications-for-payment-systems-emv-contact-interface-specification/|Book 0 (contact specs)]] | ||
+ | * [[https://www.emvco.com/specifications/book-1-application-independent-icc-to-terminal-interface-requirements-2/|Book 1]] | ||
+ | * [[https://www.emvco.com/specifications/book-2-security-and-key-management-2/|Book 2]] | ||
+ | * [[https://www.emvco.com/specifications/book-3-application-specification-2/|Book 3]] | ||
+ | |||
+ | |||
+ | ==== 1. Answer-to-Reset (2p) ==== | ||
+ | |||
+ | You are given the following ATR (each character represents a hexadecimal digit): | ||
<code> | <code> | ||
- | openssl genrsa -out private.pem 2048 | + | 3B6500002063CB6600 |
- | openssl dhparam -out dhparam.pem 2048 | + | |
</code> | </code> | ||
- | The ''%%dhparam%%'' file will be used as a hardcoded ''%%.pem%%'' that contains the necessary Diffie-Hellman parameters to generate our DH keys. These values are generally decided upon by convention and are hardcoded - check [[https://datatracker.ietf.org/doc/html/rfc7919#page-19 |RFC 7919]]. An example which can be found there is ''%%ffdhe2048%%''. | + | Decode it in order to understand the ATR parameters. Mention the available parameters. |
- | To better understand its structure, you can use the following commands: | + | <note tip> |
+ | See book 0 (contact specs), chapter "Answer to Reset", section 8.2 (Characters Returned by ICC at Answer to Reset) | ||
+ | </note> | ||
- | <code bash> | + | <solution -hidden> |
- | openssl dhparam -in dhparam.pem -text -noout | + | <code> |
- | openssl asn1parse < dhparam.pem | + | ATR: 3B 65 00 00 20 63 CB 66 00 |
+ | + TS = 3B --> Direct Convention | ||
+ | + T0 = 65, Y(1): 0110, K: 5 (historical bytes) | ||
+ | TB(1) = 00 --> VPP is not electrically connected | ||
+ | TC(1) = 00 --> Extra guard time: 0 | ||
+ | + Historical bytes: 20 63 CB 68 00 | ||
+ | Category indicator byte: 20 (proprietary format) | ||
</code> | </code> | ||
+ | </solution> | ||
- | Create a Python environment or use an existing one, then install the required packages: | ||
- | <code bash> | + | ==== 2. Card verification method (2p) ==== |
- | python3 -m venv create env | + | |
- | source ./env/bin/activate | + | A card returns the following TLV as a response to a READ RECORD command |
- | pip install --upgrade pip | + | (ignore the line breaks, this should be a single hexstring): |
- | pip install pycryptodome pyasn1 pyasn1-modules | + | <code> |
+ | 70538D06910A8A0295058E0C0000000000000000410000008C219F02069F03069F1A02950 | ||
+ | 55F2A029A039C019F37049F35019F45029F4C089F34039F561380000FFFFF000000000000 | ||
+ | 00000000000000009F5501809000 | ||
</code> | </code> | ||
- | We will use the ''%%pyasn1%%'' library to parse the ''%%.pem%%'' file's binarized DER format more conveniently. | + | Find what are the cardholder verification methods allowed. |
- | ==== 1. Implement DH + RSA signature ==== | + | <note tip> |
+ | Use [[https://emvlab.org/tlvutils/|this]] tool to decode the TLV (remove the spaces from above). | ||
+ | </note> | ||
- | Starting from {{:ac:laboratoare:lab_tofu.zip |these files}}, solve the TODO 1 series in ''%%dhe_server.py%%'' and ''%%dhe_client.py%%''. | ||
- | On the server side, you should: | + | <note tip> |
+ | See the EMV book 3 for "Cardholder Verification Method (CVM) List" in Section 10.5 and Appendix C3. | ||
+ | </note> | ||
- | * Send the RSA public key to the client. | + | <solution -hidden> |
- | * Generate a DH key pair (use the data hardcoded in ''%%dhparam.pem%%'') and send the public key to the client. | + | From the TLV decoder, you get the following Cardholder Verification Method (CVM) List (''%%8E%%''): |
- | * Generate a signature over the RSA and DH public keys (concatenate and hash them, then sign using the RSA private key) and send it to the client. Use [[https://pycryptodome.readthedocs.io/en/latest/src/signature/pkcs1_v1_5.html |this]] as your guide. | + | |
- | * Receive the client's DH public key and generate the shared DH secret. | + | |
- | * Derive a symmetric key from the shared secret. Use [[https://pycryptodome.readthedocs.io/en/latest/src/protocol/kdf.html#hkdf |HKDF]] for this. | + | |
- | On the client side, you should: | + | <code> |
+ | 000000000000000041000000 | ||
+ | </code> | ||
- | * Receive the RSA public key from the server. | + | We have 2 groups of 4 bytes prior to ''%%0x41%%'' - these represent two amount fields (''%%X%%'' and ''%%Y%%''). Not what this task is asking for. |
- | * Receive the server's DH public key. | + | |
- | * Receive the server's signature over the RSA and DH public keys and [[https://pycryptodome.readthedocs.io/en/latest/src/signature/pkcs1_v1_5.html |verify]] it using the server's public key. | + | |
- | * Generate a DH key pair and send the public key to the server. | + | |
- | * Compute the shared DH secret. | + | |
- | * Derive a symmetric key from the shared secret. | + | |
- | Generating a signature over the RSA and DH public keys is a way to authenticate the remote host. If the client successfully verifies this signature using the server's public key, then the server is authenticated unless the public key itself has been replaced by the attacker in a man in the middle attack. We will look at a way to (mostly) solve this issue in the next task. | + | Beyond the two amount fields, we have a variable-length Cardholder Verification Rules (CV Rules) field. Each CV Rule describes a CVM and the conditions under which it should be applied. We only have ''%%0x41%%'' as a CV Rule. If you look at Appendix C3 of EMV Book 3, you can decode it to the following: |
- | The symmetric key derived from the shared secret will be used to encrypt the communication between the client and server. Although we're stopping the tasks here, this key would be the one that you would use to encrypt and decrypt the communication between the client and server. If you want, you can check the [[https://pycryptodome.readthedocs.io/en/latest/src/cipher/aes.html |PyCryptodome documentation]] for more information on how to use AES. To see what ciphers SSH uses, run the following command: | + | <code> |
+ | bin(0x41) = 0b01000001 | ||
+ | 0 = RFU (Reserved for Future Use) | ||
+ | 1 = Apply succeeding CV Rule if this CVM is unsuccessful | ||
+ | 000001 = Plaintext PIN verification performed by ICC | ||
+ | </code> | ||
- | <code bash> | + | Note that the following CV Rule that would be applied in the case of PIN failure is ''%%0b00000000%%'', i.e. **Fail CVM processing**. |
- | ssh -Q cipher | + | |
+ | The only cardholder verification method allowed is, hence, plaintext PIN verification. | ||
+ | </solution> | ||
+ | |||
+ | ==== 3. Card verification method (2p) ==== | ||
+ | |||
+ | A short part of the communication between terminal (T) and card (C) is as follows: | ||
+ | <code> | ||
+ | T->C: 80CA9F1700 | ||
+ | C->T: 6C04 | ||
+ | T->C: 80CA9F1704 | ||
+ | C->T: CA9F1701069000 | ||
</code> | </code> | ||
- | ==== 2. I hate TOFU ==== | + | - What is the command being sent by the terminal ? |
+ | - What is it asking for ? | ||
+ | - What is the value obtained in the end for that item ? | ||
- | Now start solving the TODO 2 series by implementing Trust On First Use in ''%%dhe_client.py%%''. Do it as follows: | + | <note tip> |
+ | See EMV book 3: | ||
+ | * section 6.3.5 for status bytes | ||
+ | * section 6.3 and 6.5 for commands | ||
+ | </note> | ||
- | * Store the public key of the server in a file named ''%%known_hosts%%'' in the following format (the same way SSH does it): | + | <solution -hidden> |
- | <code text> | + | ''%%T->C: 80CA9F1700%%'' |
- | hostname1 public_key1 | + | |
- | hostname2 public_key2 | + | Command APDU Format: ''%%CLA INS P1 P2 Le%%'' |
- | ... | + | |
+ | <code> | ||
+ | 80 = (CLA) (class) proprietary class of commands, not ISO 7816-standard. | ||
+ | CA = (INS) (instruction) GET DATA. | ||
+ | From here, we know to look into Book 3, 6.5.7 - GET DATA Command-Response APDUs. | ||
+ | |||
+ | 9F17 = (P1+P2) (parameters 1/2) Tag of data that the terminal is requesting. | ||
+ | In this case, it's PIN Try Counter. | ||
+ | |||
+ | 00 = (Le) Length of command data, 0=no data is being sent with this command. | ||
</code> | </code> | ||
- | * If the client already has a public key for the given IP of the server, check that the public key of the server matches the one that is stored (if it's a first connection - i.e., **First Use** - just store the public key and **Trust** the host). | + | ''%%C->T: 6C04%%'' |
- | * If it matches, print a "connection established" message and proceed to use that key for verification of the signature over the DH share of the server. | + | |
- | * If it doesn't match, print a suggestive error message and exit. | + | |
- | This is very similar to what SSH does when connecting to a server using a pair of public/private keys and is known as Trust On First Use (TOFU) authentication. | + | Status Bytes Format: ''%%SW1 SW2%%'' (Status Bytes 1 and 2) |
+ | |||
+ | <code> | ||
+ | 6C = (SW1) Wrong length Le. | ||
+ | 04 = (SW2) The correct, exact length. | ||
+ | </code> | ||
+ | |||
+ | ''%%T->C: 80CA9F1704%%'' | ||
+ | |||
+ | Command APDU Format: ''%%CLA INS P1 P2 Le%%'' | ||
+ | |||
+ | <code> | ||
+ | Same as the first command, but with a correct length: | ||
+ | 04 = (Le) Length of command data, i.e. PIN is 4-bytes long. | ||
+ | </code> | ||
+ | |||
+ | ''%%C->T: CA9F1701069000%%'' | ||
+ | |||
+ | Response APDU Format: ''%%Data SW1 SW2%%'' | ||
+ | |||
+ | <code> | ||
+ | CA = Echo of the INS byte (GET DATA). | ||
+ | 9F17 = The tag for the data object being returned (PIN Try Counter). | ||
+ | 01 = Length of the value being returned, in bytes. | ||
+ | 06 = Value of the data object (i.e. 6 remaining PIN attempts). | ||
+ | 9000 = Operation successful. | ||
+ | </code> | ||
+ | |||
+ | </solution> | ||
+ | ==== 4. Card-holder verification (2p) ==== | ||
+ | |||
+ | A short part of the communication between terminal (T) and card (C) is as follows: | ||
+ | <code> | ||
+ | T->C: 0020008008241111FFFFFFFFFF | ||
+ | C->T: 9000 | ||
+ | </code> | ||
+ | |||
+ | - What is the command being sent by the terminal ? | ||
+ | - What is the data being sent by the terminal ? | ||
+ | - What is the response of the card? What does it mean ? | ||
+ | |||
+ | <note tip> | ||
+ | See EMV book 3: | ||
+ | * section 6.3.5 for status bytes | ||
+ | * section 6.3 and 6.5 for commands | ||
+ | </note> | ||
+ | |||
+ | <solution -hidden> | ||
+ | ''%%T->C: 0020008008241111FFFFFFFFFF%%'' | ||
+ | |||
+ | Command APDU Format: ''%%CLA INS P1 P2 Lc Data%%'' | ||
+ | |||
+ | <code> | ||
+ | 00 = (CLA) ISO 7816 class of instructions for standard commands. | ||
+ | 20 = (INS) VERIFY. Used for Cardholder Verification, such as PIN verification | ||
+ | 00 = (P1) PIN verification applies to the currently selected application. | ||
+ | 80 = (P2) Specifies the PIN type and format (plaintext data for 80). | ||
+ | 08 = (Lc) (Length of data) Data field is 8 bytes long. | ||
+ | |||
+ | 241111FFFFFFFFFF = (Data) Of which: | ||
+ | 241111 = The PIN to be verified. | ||
+ | FFFFFFFFFF = Padding (used to fill until 8 bytes). | ||
+ | </code> | ||
+ | |||
+ | ''%%C->T: 9000%%'' | ||
+ | |||
+ | Status Bytes Format: ''%%SW1 SW2%%'' | ||
+ | |||
+ | <code> | ||
+ | 90 = Operation successful. | ||
+ | 00 = No additional information. | ||
+ | </code> | ||
+ | |||
+ | **The card successfully verified the PIN sent by the terminal.** | ||
+ | </solution> | ||
+ | ==== 5. Transaction authentication (2p) ==== | ||
+ | |||
+ | A short part of the communication between terminal (T) and card (C) is as follows: | ||
+ | <code> | ||
+ | T->C: 80AE80002B00000000000000000000000000008000000000000000000000000000003400000000000000000000410002 | ||
+ | C->T: 612B | ||
+ | T->C: 00C000002B | ||
+ | C->T: C077299F2701809F360201349F2608817C3AAB208BE0659F10120310A00006250400000000000000000000FF9000 | ||
+ | </code> | ||
+ | |||
+ | - What is the command being sent by the terminal ? | ||
+ | - What is the data being sent by the terminal ? | ||
+ | - What is the response of the card? What does it mean ? | ||
+ | |||
+ | <note tip> | ||
+ | See EMV book 3: | ||
+ | * section 6.3.5 for status bytes | ||
+ | * section 6.3 and 6.5 for commands | ||
+ | </note> | ||
+ | |||
+ | <solution -hidden> | ||
+ | ''%%T->C: 80AE80002B00000000000000000000000000008000000000000000000000000000003400000000000000000000410002%%'' | ||
+ | |||
+ | Command APDU Format: ''%%CLA INS P1 P2 Lc Data%%'' | ||
+ | |||
+ | **The terminal is sending a Generate Application Cryptogram (GEN AC) command to request an ARQC (Authorisation Request Cryptogram) for transaction authentication.** | ||
+ | |||
+ | <code> | ||
+ | 80 = (CLA) Proprietary command. | ||
+ | AE = (INS) GEN AC (Generate Application Cryptogram). This is used for transaction authentication. | ||
+ | It requests the card to generate a cryptogram to verify the integrity of the transaction. | ||
+ | |||
+ | 80 = (P1) Terminal is requesting an Authorisation Request Cryptogram (ARQC). | ||
+ | 00 = (P2) RFU. | ||
+ | 2B = (Lc) (Length of Data) 43 bytes. | ||
+ | |||
+ | The data itself follows. It is broken into: | ||
+ | 000000000000 = Amount Authorized | ||
+ | 000000000000 = Amount Other | ||
+ | |||
+ | 0800 = Terminal Country Code | ||
+ | 0000 = Transaction Currency Code | ||
+ | 000000 = Transaction Date | ||
+ | 34 = Transaction Type | ||
+ | |||
+ | 0000000000000000 = Unpredictable Number (randomly generated) | ||
+ | 410002 = Data Authentication Code (ARQC input) | ||
+ | </code> | ||
+ | |||
+ | ''%%C->T: 612B%%'' | ||
+ | |||
+ | Status Bytes Format: ''%%SW1 SW2%%'' | ||
+ | |||
+ | <code> | ||
+ | 61 = Card has more data to send. | ||
+ | 2B = 43 bytes of data are available for retrieval. | ||
+ | </code> | ||
+ | |||
+ | ''%%T->C: 00C000002B%%'' | ||
+ | |||
+ | Command APDU Format: ''%%CLA INS P1 P2 Le%%'' | ||
+ | |||
+ | **Here, the terminal is requesting 43 bytes of data from the card.** | ||
+ | |||
+ | <code> | ||
+ | 00 = ISO 7816 standard. | ||
+ | C0 = GET RESPONSE. | ||
+ | 00 = RFU. | ||
+ | 00 = RFU. | ||
+ | 2B = 43 bytes. | ||
+ | </code> | ||
+ | |||
+ | ''%%C->T: C077299F2701809F360201349F2608817C3AAB208BE0659F10120310A00006250400000000000000000000FF9000%%'' | ||
+ | |||
+ | Response APDU Format: ''%%Data SW1 SW2%%'' | ||
+ | |||
+ | <code> | ||
+ | Data is broken into: | ||
+ | C0 = Proprietary or issuer-specific data. | ||
+ | 7729 = Template for cryptogram-related data (EMV tag 77). | ||
+ | |||
+ | Following data is TLV-encoded: | ||
+ | 9F27 = Cryptogram Information Data (CID). | ||
+ | 01 = Length of CID data. | ||
+ | 80 = Cryptogram type: ARQC (Authorisation Request Cryptogram). | ||
+ | |||
+ | 9F36 = Application Transaction Counter (ATC). | ||
+ | 02 = ATC value length. | ||
+ | 0134 = ATC value (Counter = 308). | ||
+ | |||
+ | 9F26 = Application Cryptogram. | ||
+ | 08 = Length of cryptogram. | ||
+ | 817C3AAB208BE065 = The actual cryptogram generated by the card. | ||
+ | |||
+ | 9F10 = Issuer Application Data (IAD). | ||
+ | 12 = Length of IAD. | ||
+ | 0310A00006250400000000000000000000FF = Encrypted issuer-specific data (used for ARQC verification). | ||
+ | |||
+ | 9000 = Operation successful, no more information. | ||
+ | </code> | ||
+ | |||
+ | **The response is concluded with 9000, meaning the operation was successful. This data will be sent to the issuer for further authentication.** | ||
+ | </solution> | ||
+ | ==== MAC generation (Bonus) (2p) ==== | ||
+ | |||
+ | Say you know the card's master key to be: | ||
+ | <code> | ||
+ | 79610497EFCB67E5546EF8CEBCB05D85 | ||
+ | </code> | ||
+ | |||
+ | Can you regenerate the cryptogram (MAC) from the information obtained in the previous exercises ? | ||
+ | |||
+ | You know the encryption algorithm is 3DES. | ||
+ | |||
+ | Besides the data from previous exercises, you are also given the | ||
+ | Application Interchange Profile is 0x1000. | ||
+ | |||
+ | <note tip> | ||
+ | See EMV book 2, section 8.1. | ||
+ | </note> | ||
+ | |||
+ | <solution -hidden> | ||
+ | We should allow students to select the data somewhat arbitrarily as it's not **entirely** provided in the exercises above, as per the minimum spec mentioned in EMV Book 2, page 87, section 8.1.1, table 28. | ||
+ | |||
+ | <code python> | ||
+ | from Crypto.Cipher import DES3 | ||
+ | from Crypto.Util.Padding import pad | ||
+ | |||
+ | master_key = bytes.fromhex("79610497EFCB67E5546EF8CEBCB05D85") | ||
+ | aip = bytes.fromhex("1000") | ||
+ | |||
+ | # concatenation of amount authorised and amount other, | ||
+ | # terminal country code, transaction currency code, | ||
+ | # transaction date, transaction type, | ||
+ | # unpredictable number, given AIP and the ATC | ||
+ | # we aren't given the terminal verification results, | ||
+ | # so I assumed that it's gonna be 5 * b'00' | ||
+ | transaction_data = bytes.fromhex( | ||
+ | "00000000000000000000000008000000000000000000000034000000000000000010000134" | ||
+ | ) # from task 5 | ||
+ | |||
+ | # 3DES block size is 8 bytes | ||
+ | padded_data = pad(transaction_data, 8) | ||
+ | |||
+ | # 3DES encryption | ||
+ | cipher = DES3.new(master_key, DES3.MODE_ECB) | ||
+ | mac = cipher.encrypt(padded_data) | ||
+ | |||
+ | mac = mac.hex() | ||
+ | print(mac) | ||
+ | </code> | ||
+ | </solution> | ||