Identity Services ("IDS") is a term that can be used to describe a protocol, key server, and encryption mechanism used by FaceTime and iMessage to facilitate end-to-end encryption, or it can refer to the local daemon ("identityservicesd") that implements the aforementioned protocol. Note that Identity Services can also refer to a system used on macOS for local user authentication.[1]
Bag
https://init.ess.apple.com/WebObjects/VCInit.woa/wa/getBag?ix=3
Authentication
Generate a 2048 bit RSA key. Create a CSR with this key:
- Common Name: Uppercase HEX Sha1 hash of User ID
- Version: 0
- Public key and signature from generated key.
Post to desired endpoint from bag with the non-binary gzipped plist:
authentication-data
: Service-Specific auth datacsr
: (data) CSR (der)realm-user-id
: User ID
With headers:
- GZip send/recv
user-agent
:com.apple.invitation-registration ...
x-protocol-version
: Protocol version
Responds with:
status
: Should be 0cert
: (data) DER encoded auth cert
Apple ID
Endpoint: id-authenticate-ds-id
Auth Data:
auth-token
: token from loginDelegates
Phone number
Endpoint: id-authenticate-phone-number
User ID: P:{phone number}
Auth Data:
push-token
: (data) push tokensigs
: (array of data) Phone Signatures
Request Signing
Body is signed, use gzipped if gzipped.
Nonce format
[01(http)/00(apns auth)] [time in ms rounded to sec, 64 bit BE] [8 random bytes]
Payload format
Nonce + Data Fields (BE 32-bit length, then field)
HTTP fields:
- Bag Key
- Query String
- Payload
- Push token
Signature format
PKCS1 SHA1 RSA signature of payload, prefixed with two 0x1 bytes.
Headers format
All base64 encoded
- x-
item
-nonce - x-
item
-sig - x-
item
-cert
Auth Requests
Get Handles
Send an HTTP GET to id-get-handles
Headers:
x-push-token
: Push token- Sign with
push
andauth
x-protocol-version
: Protocol versionx-auth-user-id
: User ID
Sample response:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>handles</key> <array> <dict> <key>aliases</key> <dict></dict> <key>is-user-visible</key> <true/> <key>uri</key> <string>mailto:user_test2@icloud.com</string> <key>status</key> <integer>5051</integer> </dict> </array> <key>self-handle</key> <dict> <key>uri</key> <string>urn:ds:20994360971</string> </dict> <key>status</key> <integer>0</integer> </dict> </plist>
Registration
Send an HTTP post to id-register
with headers:
x-push-token
: Push token- Sign with
push
andauth
, auth prefixed with N for each user x-protocol-version
: Protocol versionx-auth-user-id-N
: User ID for user Nuser-agent
:com.apple.invitation-registration ...
content-type
:application/x-apple-plist
- Gzip headers
HTTP body plist:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>device-name</key> <string>(Device name)</string> <key>hardware-version</key> <string>(SMBIOS version}</string> <key>language</key> <string>en-US</string> <key>os-version</key> <string>macOS,(version eg. 13.2.1),(build num eg. 22D68)</string> <key>private-device-data</key> <dict> (private device data) </dict> <key>retry-count</key> <integer>0</integer> <key>services</key> <array> <dict> <key>capabilities</key> <array> <dict> <key>flags</key> <integer>17</integer> <key>name</key> <string>Messenger</string> <key>version</key> <integer>1</integer> </dict> </array> <key>service</key> <string>com.apple.madrid</string> <key>sub-services</key> <array> <string>com.apple.private.alloy.sms</string> <string>com.apple.private.alloy.gelato</string> <string>com.apple.private.alloy.biz</string> <string>com.apple.private.alloy.gamecenter.imessage</string> </array> <key>users</key> <array> <dict> <key>client-data</key> <dict> <key>public-message-identity-key</key> <data> (encoded identity) </data> (...extras) </dict> <key>uris</key> <array> <dict> <key>uri</key> <string>mailto:example@test.com</string> </dict> <dict> <key>uri</key> <string>mailto:sidestorer@test.com</string> </dict> </array> <key>user-id</key> <string>(user_id)</string> (if phone) <key>tag</key> <string>SIM</string> (/if) </dict> </array> </dict> </array> <key>software-version</key> <string>(build num)</string> <key>validation-data</key> <data> (validation data) </data> </dict> </plist>
Response
Plist format:
- status: should be 0
- services: List of services, first is imsg,.then has
users
For each user:
- status: if 6009, get alert key of struct:
title: String, body: String, action?: { url: String, button: String, },
Otherwise,
- cert: DER-encoded ID cert
- uris: List of URIs, each with a "status" field (check all 0)
- user-id: user id of this user for matching
Identity Key Format
ASN.1 SEQUENCE with two byte arrays. First byte array is EC key. It's padded with 0x00 0x41. Starts with 0x4, then contains X and Y EC coordinates padded to 32 bytes. Second one is an x509 public key, padded with 0x00 0xAC.
Encryption key is 1280 bit RSA. EC key is type X9_62_PRIME256V1.
Hash
[Padded EC + Padded RSA] sha256
Pair-EC
Compact EC key format
Generation
Generate a P256 EC Key such that y <= P - y. Just keep generating if it doesn't work :) (this is official Apple strategy)
Encoding
Just encode `X`. We know which side of the curve it'll be on so Y doesn't matter.
Decoding
Pad the `X` with 0x3. Decode the compressed EC point. If y >= P - y set Y point to P - y.
Protobuf structures
message PreKeyData { bytes key = 1; bytes signature = 2; double timestamp = 3; }
- key: encoded prekey
- signature: Signature (see device data)
- timestamp: Signed timestamp (see device data)
message KtLoggableData { message NgmPublicIdentity { optional bytes publicKey = 1; } optional NgmPublicIdentity deviceIdentity = 1; optional uint32 ngmVersion = 2; optional uint32 ktVersion = 3; }
message InnerMessage { bytes message = 1; uint32 counter = 2; bytes ktGossipData = 3; bytes debugInfo = 99; }
message OuterMessage { bytes payload = 1; bytes key = 2; bytes signature = 3; bytes validator = 99; }
Device data
Generate two compact keys, a device key and a pre key. Registration data:
- ec-version: 1
- public-message-identity-ngm-version: 13
- public-message-ngm-device-prekey-data-key: PreKeyData protobuf
- kt-version: 5
Signature format
- Generate a byte string: "NGMPrekeySignature${prekeybytes}${secondssinceepochasfloat64LE}"
- Sha256 that.
- EC Sign that with your device key.
- r is lower 32 bytes, s is upper 32 bytes
- Timestamp must match PreKeyData struct
KT Loggable data
- kt-loggable-data (adjacent to client-data, lookups may place it in "ngm-public-identity" in client-data): KtLoggableData protobuf
Counter format
Hash my device public key, their pre key, and their device public key. Increment a counter on message send
Key validator format
7 Byte message
- First 2 bytes of sender device key
- First 2 bytes of receiver device key
- First 2 bytes of receiver pre key
- Something (don't check) 0xc (sample)
Encryption
Create an InnerMessage with the message and counter. Pad the protobuf-encoded message with random data such that the length is a multiple of 16. Append the amount of added bytes after the padding as a 32-bit unsigned little endian integer.
Make sure you check their prekey for validity first.
Create ephemeral compact EC key. ECDH the ephemeral key with their pre key to get a shared secret. Perform sha256 HKDF on the shared secret, with the salt "LastPawn-MessageKeys", with null info. First 32 bytes are an AES key, next 16 bytes are the IV.
AES CTR 64-bit counter (NONSTANDARD) with the key and IV, encrypt the padded message.
Signature data (concat): sharedSecret + targetPreKey + msgEphermeralKey + targetDeviceKey + ciphertext Sha256 the signature data. EC sign with the device key. R bottom 32 bits, s top 32 bits.
Build OuterMessage.
Query
Make an HTTP request over APNs to id-query
Body: gzipped (no header) text plist:
- uris: List of string to query
Headers:
- x-id-self-uri: Self handle
- x-protocol-version: Protocol version
- user-agent: com.apple.madrid-lookup ...
- x-push-token: push token
- sign with ID cert
Response: Plist:
- status: should be 0
- results?: Map of URI to response
Response:
- identities: List of identities
Identity:
- client-data: client data
- push-token: push token (bin)
- session-token: session token (bin)
- session-token-expires-seconds
- session-token-refresh-seconds
HTTP over APNs
- cT: content-type
- U: message id
- c: 96
- u: URL (from bag)
- h: headers list
- v: 2
- b: gzipped body (mtime 0)
Response
- U: message ID
- b: gzipped response