- Thijs Alkemade (Xnyhps) [
http://en.wikipedia.org/wiki/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.
This part looks very similar to the activation of
[
iPhones](
http://theiphonewiki.com/wiki/index.php?title=Activation_Token).
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"> <key>ActivationInfoComplete
</key> <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)
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"> <key>ActivationRandomness
</key> <string>(hexadecimal
string)
</string> <key>ActivationState
</key> <string>Unactivated
</string> <key>DeviceCertRequest
</key> (encoded:
-----BEGIN
CERTIFICATE
REQUEST-----...-----END
CERTIFICATE
REQUEST-----)
<string>iMac10,1
</string> <key>ProductVersion
</key> <key>UniqueDeviceID
</key> <string>(hexadecimal
string
</string> <?xml version="1.0" encoding="UTF-8" standalone="no"?> <Document disableHistory="true" xmlns="http://www.apple.com/itms/"> <key>device-activation
</key> <key>activation-record
</key> <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?)
(encoded:
{\n\t"ActivationRandomness"
=
"(same
as
sent)";\n\t"UniqueDeviceID"
=
"(same
as
sent)";\n})
(after this, a number of lines of XML follow, appearing to describe an iTMS
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](
https://github.com/meeee/pushproxy) --Meeee 17:00, 22 April
(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):
Server (the connected message):
Client (the filter message):
01 00 20: (same 32 byte key) It repeats, with a different key. This is the push token imagent uses:
02 00 14: (other 20 byte key?) 0a seems to be used for actual messages. The format is:
04 00 04: 9d a1 c9 54 (identifier) 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:
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?):
57 69 46 69 ("WiFi", probably used for iPhones?) 31 30 2e 37 2e 33 (10.7.3, system version) 31 31 44 35 30 (11D50, system build) 4d 61 63 42 6f 6f 6b 50 72 6f 38 2c 31 (MacBookPro8,1, computer model) Which gets acknowledged by:
0a messages can contain binary plists, indicated by bplist00. A decoded
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>); 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)"; 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:
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
Another form of acknowledgement is sent as (probably the delivery receipt):
U = (matches the U value of a previous plist <XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX>); ### Getting the auth token 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"> <string>steve@apple.com
</string> 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"> <key>status
</key><integer>0
</integer> <string>(long,
random
string)
</string> <key>profile-id
</key><string>D:(some
number,
unknown
usage)
</string> ### The default invitation context (how to look up contacts) 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-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"> <?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"> <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/> ### Check if the email needs validation 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"> <string>steve@mac.com
</string> (appears
to
be
a
random
string,
around
45
bytes)
(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
<?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"> <key>status
</key><integer>5051
</integer> (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
### Send a CSR and get a certificate https://service.ess.apple.com/WebObjects/VCProfileService.woa/wa/idsProvisionEmails
WITHOUT the extra headers, only:
<?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"> <key>authentication-data
</key> <string>(the
auth
token
again)
</string> (a
certificate
signing
request,
PEM
encoded)
<string>mailto:steve@mac.com
</string> <string>Messenger
</string> <string>D:(the
profile
id)
</string> <?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"> <key>status
</key><integer>0
</integer> <key>uri
</key><string>mailto:steve@mac.com
</string> <key>status
</key><integer>0
</integer> (a
PEM
encoded
certificate)
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"> <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.)
<?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"> <key>status
</key><integer>0
</integer> <key>ttl
</key><integer>300
</integer> <key>session-info
</key>(yet
another
long
string
of
random
data)
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-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"> <string>Messenger
</string> <key>public-message-identity-key
</key> <key>public-message-identity-version
</key> <key>hardware-version
</key> <string>(something
which
determines
the
Mac
model,
for
example
iMac10,1)
</string> <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> Messages now shows the login as complete. Looking up users can be done as
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"> <key>status
</key><integer>0
</integer>