IMessage
http://en.wikipedia.org/wiki/IMessage
Information on this page is based on my examination of the Messages Beta application for Mac OS X. The "client" works with three processes: iChat, imagent and applepushserviced. iChat (which is now Messages) shows the UI, but does not handle anything protocol specific, that's what imagent does (so the user stays logged in if iChat isn't running). However, for iMessage, imagent doesn't do make the connection itself, it uses applepushserviced. This is a system level daemon that maintains an persistent connection to courier.push.apple.com, probably for all push notifications.
Activation
This part looks very similar to the activation of iPhones.
The connection of applepushserviced is encrypted with TLS using a client side
certificate. To retrieve such a certificate, it posts to:
https://albert.apple.com/WebObjects/ALUnbrick.woa/wa/deviceActivation?device=MacOS
(NOTE: this has a content type of "application/x-www-form-urlencoded", contrary
to most other requests, it is shown unencoded here):
activation-info=<?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>ActivationInfoComplete</key>
<true/>
<key>ActivationInfoXML</key>
(again a plist, see next block)
<key>FairPlayCertChain</key>
(a certificate issued by "Apple FairPlay Certification Authority", where does this come from?)
<key>FairPlaySignature</key>
(about 3 lines, probably related to the previous certificate)
</dict>
</plist>
The ActivationInfoXML field contains (base64 encoded):
<?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>ActivationRandomness</key>
<string>(hexadecimal string)</string>
<key>ActivationState</key>
<string>Unactivated</string>
<key>BuildVersion</key>
<string>11D50</string>
<key>DeviceCertRequest</key>
(encoded: -----BEGIN CERTIFICATE REQUEST-----...-----END CERTIFICATE REQUEST-----)
<key>DeviceClass</key>
<string>MacOS</string>
<key>ProductType</key>
<string>iMac10,1</string>
<key>ProductVersion</key>
<string>10.7.3</string>
<key>SerialNumber</key>
<string>XXXXX</string>
<key>UniqueDeviceID</key>
<string>(hexadecimal string</string>
</dict>
</plist>
The server replies:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Document disableHistory="true" xmlns="http://www.apple.com/itms/">
<Protocol>
<plist version="1.0" >
<dict>
<key>device-activation</key>
<dict>
<key>activation-record</key>
<dict>
<key>AccountTokenCertificate</key>
(encoded: -----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----)
<key>DeviceCertificate</key>
(encoded: -----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----)
<key>AccountTokenSignature</key>
(a signature created with the first certificate? of the token?)
<key>AccountToken</key>
(encoded: {\n\t"ActivationRandomness" = "(same as sent)";\n\t"UniqueDeviceID" = "(same as sent)";\n})
</dict>
<key>ack-received</key>
<true/>
<key>show-settings</key>
<true/>
</dict>
</dict>
</plist>
</Protocol>
(after this, a number of lines of XML follow, appearing to describe an iTMS page)
applepushserviced
The applepushserviced first does a DNS TXT query for "push.apple.com" [ nslookup -query=txt push.apple.com] . This will return "count=50" or some number XX. The daemon then creates a name using a number between 1..XX and creates DNS name X-courier.push.apple.com. This DNS name is then handle by Akamai DNS to return an ipaddress in the 17.X netblock that belongs to Apple.
Then, applepushserviced connects to a host on port 5223. These present a certificate for courier.push.apple.com, and only accept connections which present a client side certificate (retrieved in the previous step).
Contrary to what port 5223 might imply, it uses a binary protocol which bears no resemblance to XMPP. Messages are of the form:
To enable debugging: sudo defaults write /Library/Preferences/com.apple.applepushserviced APSWriteLogs -bool TRUE sudo defaults write /Library/Preferences/com.apple.applepushserviced APSLogLevel -int 7 sudo killall applepushserviced
For enhanced debug log msgs defaults write /Library/Preferences/com.apple.applepushserviced EnableDetailedLogging true
I published a proxy for this push service, it prints all messages in a readable format. I also wrote some docs about the protocol: meeee/pushproxy --Meeee 17:00, 22 April 2012 (UTC)
(1 byte type) (4 byte length) [(1 byte identifier? type?) (2 byte length) (length bytes data)]*
The handshake happens as follows:
Client (the connect message):
07
00 00 00 27
01 00 20: (32 byte key?)
02 00 01: 01
Server (the connected message):
08
00 00 00 0e
01 00 01: 00
04 00 02: 10 00
05 00 02: 00 02
Client (the filter message):
09
00 00 00 23
01 00 20: (same 32 byte key)
It repeats, with a different key. This is the push token imagent uses:
Client:
07
00 00 00 27
01 00 20: (push token)
02 00 01: 01
Server:
08
00 00 00 0e
01 00 01: 00
04 00 02: 10 00
05 00 02: 00 02
Client:
09
00 00 00 51
01 00 20: (push token)
02 00 14: (20 byte key?)
02 00 14: (other 20 byte key?)
0a seems to be used for actual messages. The format is:
0a
00 00 00 ad
07 00 01: 02
04 00 04: 9d a1 c9 54 (identifier)
05 00 04: 00 00 00 00
06 00 08: 12 73 9a 13 c9 65 a4 30 (unix time in ms?)
01 00 20: (32 bytes, the topic hash)
02 00 14: (20 bytes, the token)
03 00 53: (the payload, i.e., a binary plist)
This gets acknowledged by a message:
0b
00 00 00 0b
08 00 01: 00
04 00 04: 9d a1 c9 54 (same as the 0a message had)
The 0c packet has the following syntax (probably used to identify a client?):
0c
00 00 00 2d
01 00 04
57 69 46 69 ("WiFi", probably used for iPhones?)
02 00 06
31 30 2e 37 2e 33 (10.7.3, system version)
03 00 05
31 31 44 35 30 (11D50, system build)
04 00 0d
4d 61 63 42 6f 6f 6b 50 72 6f 38 2c 31 (MacBookPro8,1, computer model)
05 00 02
31 35 (15?)
Which gets acknowledged by:
0d 00 00 00 00
0a messages can contain binary plists, indicated by bplist00. A decoded example:
{
D = 1;
E = pair;
P = (gzip compressed data: <1f8b0800 00000000 XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXX>);
U = (appears to be a UID of the message: <XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX>);
c = 100;
i = (identifier of this message);
sP = "mailto:(email of the sender)";
sT = (something DER encoded: <30530201 02170d31 32303231 38313232 3630335a 033fXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XX>);
t = (unknown? <XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX>);
tP = "mailto:(email of the receiver)";
ua = "(identification of OS, version, build and machine)";
v = 1;
}
Inflating the data in the P field gives again binary data. This could encrypted, but the first byte always seems to be 02, followed by a two byte length, followed by almost that much bytes, but not quite (?!), and then what looks like a sequence of two DER encoded integers:
02 (always 02)
01 28 (looks like the length of the following bytes, but it's 2 to 4 bytes off?)
XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX
46 (length of the remainder)
30 44 (sequence, 44 bytes)
02 20 (integer, 20 bytes)
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
02 20 (integer, 20 bytes)
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
The sT field contains DER encoded data. It contains a sequence of an integer (always 2), followed by a date (seems to be a recent time, but it doesn't change, so it's not a timestamp of the message. maybe the time the client's certificate was created?), and then a bitstring of around 64 bytes, which again appears to be an DER encoded sequence of two integers (of around 30 bytes).
These plists are acknowledged by the server using messages containing bplists of the form:
{
c = 255;
i = (same identifier);
s = 0;
}
Another form of acknowledgement is sent as (probably the delivery receipt):
{
U = (matches the U value of a previous plist <XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX>);
c = 101;
s = 0;
}
imagent
Getting the auth token
Messages.app posts to https://service.ess.apple.com/WebObjects/VCProfileService.woa/wa/authenticateUser, with a content type of "application/x-apple-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>password</key>
<string>sekret</string>
<key>username</key>
<string>steve@apple.com</string>
</dict>
</plist>
Server replies with something like:
<?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>status</key><integer>0</integer>
<key>auth-token</key>
<string>(long, random string)</string>
<key>profile-id</key><string>D:(some number, unknown usage)</string>
</dict>
</plist>
The default invitation context (how to look up contacts)
Client posts to https://service.ess.apple.com/WebObjects/VCProfileService.woa/wa/getDefaultInvitationContext, adding the following HTTP headers:
x-ds-client-id: t:(some long hexadecimal string, unknown use (identifies software version? or a computer?))
x-protocol-version: 4
x-vc-profile-id: D:(the profile-id as received in step 1)
x-vc-auth-token: (the auth token)
<?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/>
</plist>
Server replies:
<?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>extra</key>
<dict>
</dict>
<key>status</key><integer>0</integer>
<key>region-id</key><string>R:(two letter region code, examaple: NL)</string>
<key>base-phone-number</key><string>+310000000000</string>
<key>validated</key><true/>
</dict>
</plist>
Check if the email needs validation
Client posts to https://service.ess.apple.com/WebObjects/VCProfileService.woa/wa/validateEmail (with the same extra headers):
<?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>email-address</key>
<string>steve@mac.com</string>
<key>push-token</key>
(appears to be a random string, around 45 bytes)
</dict>
</plist>
(During this, the client displays a dialog with "You can be reached for messages at:", and a list with the used email, and the option to add more email addresses.)
Server replies:
<?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>status</key><integer>5051</integer>
</dict>
</plist>
(My best guess of why it has a non-zero status is that 0 indicates the validation of a user's email has started, and 5051 means "no validation needed")
Send a CSR and get a certificate
Client posts to https://service.ess.apple.com/WebObjects/VCProfileService.woa/wa/idsProvisionEmails WITHOUT the extra headers, only:
x-protocol-version: 5
<?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>authentication-data</key>
<dict>
<key>auth-token</key>
<string>(the auth token again)</string>
</dict>
<key>csr</key>
(a certificate signing request, PEM encoded)
<key>identities</key>
<array>
<dict>
<key>uri</key>
<string>mailto:steve@mac.com</string>
</dict>
</array>
<key>service</key>
<string>Messenger</string>
<key>user-id</key>
<string>D:(the profile id)</string>
</dict>
</plist>
The server replies:
<?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>identities</key>
<array>
<dict>
<key>status</key><integer>0</integer>
<key>uri</key><string>mailto:steve@mac.com</string>
</dict>
</array>
<key>status</key><integer>0</integer>
<key>cert</key>
(a PEM encoded certificate)
</dict>
</plist>
Unknown
Then the client posts to https://service2.ess.apple.com/WebObjects/TDIdentityService.woa/wa/initializeValidation, with only the extra header:
x-push-token: (the push token created in the earlier step)
<?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>session-info-request</key>
(7 lines of random appearing data. looks like something PEM encoded, but I haven't been able to decode it as a CSR or certificate.)
</dict>
</plist>
The server replies:
<?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>status</key><integer>0</integer>
<key>ttl</key><integer>300</integer>
<key>session-info</key>(yet another long string of random data)
</dict>
</plist>
Unknown2
Then the client posts to https://service2.ess.apple.com/WebObjects/TDIdentityService.woa/wa/register, without the extra headers, but with:
x-pr-cert: (the certificate the client received in the previous step)
x-pr-nonce: (the nonce used for generating the signature)
x-pr-sig: (the signature of the data? url?)
x-push-cert: (another cert in PEM format, don't know where it's from. issued by "Apple iPhone Device CA". has a "Mac OS Device Identity (Production)" section.)
x-push-nonce: (?)
x-push-sig: (?)
x-push-token: (the push token created before)
<?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>capabilities</key>
<array>
<dict>
<key>flags</key>
<integer>1</integer>
<key>name</key>
<string>Messenger</string>
<key>version</key>
<integer>1</integer>
</dict>
</array>
<key>client-data</key>
<dict>
<key>public-message-identity-key</key>
(a public key)
<key>public-message-identity-version</key>
<integer>1</integer>
</dict>
<key>hardware-version</key>
<string>(something which determines the Mac model, for example iMac10,1)</string>
<key>os-version</key>
<string>(system version, for example Mac OS X,10.7.3,11D1069)</string>
<key>software-version</key>
<string>(build number 11D1069)</string>
<key>validation-data</key>
</dict>
</plist>
The server replies:
?
Looking up contacts
Messages now shows the login as complete. Looking up users can be done as follows:
A client does a GET request for https://service1.ess.apple.com/WebObjects/TDIdentityService.woa/wa/query?uri= followed by either an email as mailto%3Asteve@mac.com or a phone number as tel%3A%2B30000000000. The only headers included are:
x-id-nonce: (the nonce used for signing)
x-id-cert: (the cert received from the server)
x-id-sig: (probably the contents? request? signed by the private key belonging to the certificate)
To which the server replies something like:
<?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>status</key><integer>0</integer>
<key>identities</key>
<array>
</array>
</dict>
</plist>