Research by Grayson Martin
Last Updated 7/8/23
Value Added Services (VAS) is the protocol used by NFC capable passes in Apple Wallet. Access to this protocol is heavily restricted on both the device end (a special certificate issued by Apple is required to create these passes) and the reader end (NDA enforced confidentiality). As such, a desire arose to better understand the protocol in order to explore additional use cases and examine its cryptographic integrity. There are gaps in understanding in certain parts of this protocol, however this document contains the minimum necessary understanding to automatically select, read data from, and decrypt a pass.
Importantly, this specification does not enable a malicious actor to read the data from a pass for which they do not have both the reader's private key, and the pass type identifier. Impersonation of a pass is possible, however the information required to do so cannot be obtained in plaintext through this protocol, and such information must be manually extracted from the .pkpass file describing the pass.
A big thanks to the contributors of proxmark3 for the incredible research suite they've created. I hope to give back with this work and more. My implementation of this specification for the proxmark3 has been merged into that repository.
- Pass Type ID: A reverse-domain style string beginning with "pass." that identifies the type of pass (i.e. "pass.com.chargepoint.MobileCard" or "pass.com.panerabread.m.178")
- VAS message: A string (length <= 64) sent from the mobile device to the reader. Often (but not necessarily) the same as the pass' serial number
- Reader Key: A private key held in the VAS reader. All passes that the terminal can read contain a copy of the reader's public key, and use this key to encrypt the message
- Ephemeral Key: A one-time use key generated on the mobile device to complete an Elliptic Curve Diffie Hellman routine used to derive a shared secret for encryption/decryption
Several technical protocols and specifications are mentioned and a base-level understanding of those is assumed here
This protocol was reverse engineered by observing the interactions between a compatible mobile device and an official VAS-certified reader. Additional details about the decryption algorithm and the meaning/significance of several of the otherwise mysterious bytes were obtained from public documentation and user guides of closed source and hardware implementations of the VAS protocol.
I am bound by no non-disclosure agreement that covers the following information. This work is not affiliated with nor endorsed by Apple in any way.
My original goal for this project included a Javacard Applet to enable this protocol on other devices. However the mandatory inclusion of a validated timestamp in the protocol makes implementing this specification on a device with no concept of time, such as a smartcard, impossible.
As generating an NFC pass can only be done with a certification issued by Apple, testing a pass for which you know the private key can be difficult. Thankfully, Passkit (the company, not the old name of the Apple Wallet .pkpass file protocol) has a publically available site which generates demo passes with random VAS messages, and the reader private key is published.
To elicit a response from a mobile device when it is idle, ECP must be used. How this actually works is not well understood, and there is reason to believe that permutations of it are used in other NFC-related features of iOS including Apple Car Keys and HomeKit Keys. However, sending the bytes 6A 01 00 00 04
without an ISO 14443A select, just before the standard ISO 14443A wake-up polling begins, causes the device to respond when it otherwise would not. Not all readers support sending raw bytes without previously selecting and negotiating with a card, and as such, this aspect of the protocol is often handled by ECP-specific NFC controllers.
A standard ISO 7816 select command, with AID equal to the ASCII encoding of "OSE.VAS.01"
Byte | Value |
---|---|
CLA | 80 |
INS | A4 |
P1 | 04 |
P2 | 00 |
Lᶜ | Length of application identifier |
Data | AID of the VAS application |
Lᵉ | 00 |
Tag | Length | Value | Notes |
---|---|---|---|
6F |
29 | Response TLV | See table below |
Tag | Length | Value | Notes |
---|---|---|---|
50 |
8 | "ApplePay" | ISO7816 Application Label |
9F21 |
2 | 0100 |
Mobile Application Version |
9F23 |
4 | Mobile Capabilities | See table below |
9F24 |
4 | Nonce? |
Four bytes that indicate the capabilities and configuration of the mobile device.
Byte 1
b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 | Notes |
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | RFU |
Byte 2
b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 | Notes |
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | RFU |
Byte 3
b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 | Notes |
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | RFU |
Byte 4
b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 | Notes |
---|---|---|---|---|---|---|---|---|
0 | VAS disabled | |||||||
1 | VAS enabled | |||||||
0 | 0 | 0 | 1 | 1 | 1 | 0 | Meaning unknown, additional testing required |
Allows a reader to provide a URL (handled in a similar manner to a standard NDEF tag by the OS), request the encrypted message associated with a particular pass type identifier, or both.
Byte | Value |
---|---|
CLA | 80 |
INS | CA |
P1 | 01 |
P2 | 00 for URL only, 01 for full VAS |
Lᶜ | Length of request TLV |
Data | Request TLV, see below |
Lᵉ | 00 |
Tag | Length | Value | Notes |
---|---|---|---|
9F22 |
2 | 0100 |
Terminal Application Version |
9F25 |
32 | "Merchant ID" | SHA-256 hash of Pass Type ID |
9F28 |
4 | All values accepted | Required, nonce maybe? |
9F26 |
4 | Terminal Capabilities | See table below |
9F29 |
Variable | Merchant URL | Optional |
Four bytes that indicate the capabilities and configuration of the terminal.
Byte 1
b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 | Notes |
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | RFU |
Byte 2
b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 | Notes |
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | RFU |
Byte 3
b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 | Notes |
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | RFU |
Byte 4
b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 | Notes |
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | Bits 3-7 unused, set to 0 | |||
0 | No additional GET VAS DATA commands will be sent | |||||||
1 | One or more additional GET VAS DATA commands will be sent | |||||||
0 | 0 | Terminal supports VAS and EMV, but only one may be used in this session | ||||||
0 | 1 | Terminal supports VAS and EMV, and both may be used in this session | ||||||
1 | 0 | Terminal only supports, or is only configured for, VAS | ||||||
1 | 1 | Terminal only supports, or is only configured for, EMV |
An APDU status of 9000
indicates a successful response containing the following TLV:
Tag | Length | Value | Notes |
---|---|---|---|
70 |
Variable | VAS Response TLV | See table below |
All other APDU status codes indicate that either the device doesn't contain a pass with a matching pass type ID, or the user has been prompted for authentication and will attempt the transaction again
Tag | Length | Value | Notes |
---|---|---|---|
9F2A |
0 | Optional, presence indicates "Mobile Token"?? | |
9F27 |
Variable | Encrypted message |
The encrypted VAS message is broken into three parts:
Start Byte | End Byte | Description |
---|---|---|
0 | 4 | First 4 bytes of SHA-256 hash of raw X coordinate of the reader's EC P-256 public key |
4 | 36 | Compressed ephemeral public key used for ECDH w/ reader private key, 32 byte X coordinate. Note: when uncompressing, the Y coordinate is always even |
36 | End | Ciphertext + 16 byte authentication tag |
There are two possible encryption methods that are used, and as far as I can tell, the only way to proceed is to try both and see which one passes the GCM data integrity check.
Both methods use ECDH with ANSI X9.63 SHA 256 as the key derivation function and AES128 GCM Block Cipher. The initialization vector for GCM is always a zero-initialized 16-byte octet string. The resulting decrypted data is the concatenation of a four byte timestamp (seconds since Jan 1, 2001) and the raw VAS message.
- The concatenation of the byte
0x0D
, the ASCII encoded strings "id-aes256-GCM" and "ApplePay encrypted VAS data", and the SHA-256 hash of the pass type identifier will serve as the "Shared Info" in the ANSI X9.63 key derivation.
-
The string "ApplePay encrypted VAS data" will serve as the "Shared Info" in the ANSI X9.63 key derivation.
-
The SHA-256 hash of the pass type identifier will serve as the "Additional Authentication Data" in the GCM block cipher.
Oh my god it blows my mind. How long did it take you to research this? This is incredible.