1

I am working on a simple iOS Mobile Device Management (MDM) server in Java as a mental exercise and proof-of-concept. Up to now, I have a series of JAX-RS RESTful service endpoints that allow me to do:

  • Initial device enrollment
  • Initial MDM certificate enrollment (SCEP)
  • Device certificate enrollment (SCEP)
  • MDM profile payload installation

My MDM profile looks something like this. It configures the device certificate using SCEP and installs information about the checkin URL and MDM itself:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Inc//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>PayloadVersion</key>
        <integer>1</integer>
        <key>PayloadType</key>
        <string>Configuration</string>
        <key>PayloadUUID</key>
            <string>6470eee3-88e1-44fd-b301-7e51872822dd</string>
            <key>PayloadIdentifier</key>
            <string>org.example.mymdm.checkin</string>
        <key>PayloadContent</key>
        <array>
            <dict>
                <key>PayloadContent</key>
                <dict>
                    <key>URL</key>
                    <string>https://mymdmserver:8443/mdm/scep</string>
                    <key>Name</key>
                    <string>EnrollmentCAInstance</string>
                    <key>Subject</key>
                    <array>
                        <array>
                            <array>
                                <string>O</string>
                                <string>Example, Inc.</string>
                            </array>
                        </array>
                        <array>
                            <array>
                                <string>CN</string>
                                <string>User Device Cert2</string>
                            </array>
                        </array>
                    </array>
                    <key>Challenge</key>
                    <string>MyChallengeGoesHere</string>
                    <key>Keysize</key>
                    <integer>2048</integer>
                    <key>Key Type</key>
                    <string>RSA</string>
                    <key>Key Usage</key>
                    <integer>5</integer>
                </dict>
                <key>PayloadDescription</key>
                <string>Provides device encryption identity</string>
                <key>PayloadUUID</key>
                <string>be730dbc-3d6e-462c-8368-34cec86e2acd</string>
                <key>PayloadType</key>
                <string>com.apple.security.scep</string>
                <key>PayloadDisplayName</key>
                <string>Encryption Identity</string>
                <key>PayloadVersion</key>
                <integer>1</integer>
                <key>PayloadOrganization</key>
                <string>Example, Inc.</string>
                <key>PayloadIdentifier</key>
                <string>com.example.profileservice.scep.be730dbc-3d6e-462c-8368-34cec86e2acd</string>
            </dict>
            <dict>
                <key>AccessRights</key>
                <integer>8191</integer>
                <key>CheckInURL</key>
                <string>https://mymdmserver:8443/mdm/checkin</string>
                <key>CheckOutWhenRemoved</key>
                <true/>
                <key>IdentityCertificateUUID</key>
                <string>be730dbc-3d6e-462c-8368-34cec86e2acd</string>
                <key>PayloadDescription</key>
                <string>Checkin</string>
                <key>PayloadDisplayName</key>
                <string>Checkin</string>
                <key>PayloadIdentifier</key>
                <string>com.apple.mdm.995191e6-b387-47c6-98d1-c00a25d95047</string>
                <key>PayloadOrganization</key>
                <string>Gener-Tech</string>
                <key>PayloadType</key>
                <string>com.apple.mdm</string>
                <key>PayloadUUID</key>
                <string>527228e9-8094-40b8-89ce-7c5b09ad348b</string>
                <key>PayloadVersion</key>
                <integer>1</integer>
                <key>ServerURL</key>
                <string>https://mymdmserver:8443/mdm/checkin</string>
                <key>SignMessage</key>
                <true/>
                <key>Topic</key>
                <string>com.apple.mgmt.External.*</string>
                <key>UseDevelopmentAPNS</key>
                <false/>
            </dict>
        </array>
    </dict>
</plist>

Once the over-the-air installation finishes, I see the configuration and device certificate on my iOS device. I also see a call to my /checkin endpoint containing the PushMagic and device token values for APNS. It's at this point that I run into a roadblock.

I'm using the java-apns library to attempt to send a push notification via APNS to my device using the PushMagic and device token. My call looks something like this:

ApnsService apns = APNS.newService().withCert(Thread.currentThread().getContextClassLoader().getResourceAsStream("MDM_APNS_Cert.p12"),
                            certPassword)
                    .withAppleDestination(true).build();

byte[] tokenBytes = Base64.decodeBase64(deviceToken.getBytes());
        String hexToken = Hex.encodeHexString(tokenBytes);
        String pushNotificationPayload = APNS.newPayload().mdm(pushMagic).build();
        ApnsNotification response = apns.push(hexToken, pushNotificationPayload);

However, when I attempt to send the push notification I see errors in my logs that look a little like this:

13:29:55,133 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (MonitoringThread-2) Error-response packet 080800000002
13:29:55,133 DEBUG [com.notnoop.apns.internal.Utilities] (MonitoringThread-2) close 6a86b59d[SSL_RSA_WITH_3DES_EDE_CBC_SHA: Socket[addr=gateway.push.apple.com/17.188.147.157,port=2195,localport=58884]]
13:29:55,133 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (MonitoringThread-2) Closed connection cause=INVALID_TOKEN; id=2
13:29:55,133 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (MonitoringThread-2) Candidate for removal, message id 2
13:29:55,133 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (MonitoringThread-2) Bad message found 2
13:29:55,133 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (MonitoringThread-2) delegate.messageSendFailed, message id 2
13:29:55,133 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (MonitoringThread-2) resending 0 notifications
13:29:55,133 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (MonitoringThread-2) Monitoring input stream closed by EOF
13:29:55,133 DEBUG [com.notnoop.apns.internal.Utilities] (MonitoringThread-2) close 6a86b59d[SSL_RSA_WITH_3DES_EDE_CBC_SHA: Socket[addr=gateway.push.apple.com/17.188.147.157,port=2195,localport=58884]]
13:29:55,133 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (MonitoringThread-2) draining buffer

Any idea what could be going on here? As I understand it, the push notification should be received by the iOS device and then it will call the URL defined in the MDM profile as ServerURL. Is this not correct? Am I misunderstanding the flow here? How can I correct the issue with my MDM push notification?

UPDATE: So I've tried testing 2 ways now: Using the java-apns library, and using curl. With curl, my command looks like this:

curl -vk -E ./MDMPushCert.pem -d '{"aps":{"mdm":"<PUSH MAGIC VALUE FROM CHECKIN>"}}' -H "apns-topic: com.apple.mgmt.XServer.<UUID FROM PUSH CERT>" -H "apns-priority: 10"  https://api.push.apple.com/3/device/<DEVICE TOKEN FROM CHECKIN>

The curl command fails, however, with an error saying {"reason":"BadDeviceToken"}, even though I am copying and pasting the value that the device provided in the call to my /checkin endpoint. The full output looks like this:

*   Trying 17.188.152.35...
* TCP_NODELAY set
* Connected to api.push.apple.com (17.188.152.35) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS handshake, CERT verify (15):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=api.push.apple.com; OU=management:idms.group.533599; O=Apple Inc.; ST=California; C=US
*  start date: Sep  5 17:11:04 2017 GMT
*  expire date: Oct  5 17:11:04 2019 GMT
*  issuer: CN=Apple IST CA 2 - G1; OU=Certification Authority; O=Apple Inc.; C=US
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fdbf7809400)
> POST /3/device/<TOKEN FROM CHECKIN>
0 HTTP/2
> Host: api.push.apple.com
> User-Agent: curl/7.54.0
> Accept: */*
> apns-topic: com.apple.mgmt.XServer.<UUID FROM APNS CERT>
> apns-priority: 10
> Content-Length: 54
> Content-Type: application/x-www-form-urlencoded
> 
* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
* We are completely uploaded and fine
< HTTP/2 400 
< apns-id: 7E178A20-9C17-8E21-592F-185E02D17F14
< 
* Connection #0 to host api.push.apple.com left intact
{"reason":"BadDeviceToken"}

The push certificate itself was generated by installing macOS Server on macOS High Sierra and then exporting the certificate that was generated when installing Profile Server. What I don't understand is why I'm getting a bad token error when the token came directly from the device itself. I'm assuming this is the same error calling my java-apns call to fail. The output from that call looks like this:

09:04:10,735 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (default task-75) sendMessage Message(Id=1; Token=7BC93DC7AF930EED7379899B4E3FB9676DDABDCBA7B4F39D5E7654ECDC3F1DD0; Payload={"mdm":"4A846CEF-3241-426E-B451-F258E4B1ABA6"}) fromBuffer: false
09:04:10,803 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (default task-75) Connected new socket 3e1dae5f[SSL_NULL_WITH_NULL_NULL: Socket[addr=gateway.push.apple.com/17.188.168.12,port=2195,localport=64265]]
09:04:10,803 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (default task-75) Launching Monitoring Thread for socket 3e1dae5f[SSL_NULL_WITH_NULL_NULL: Socket[addr=gateway.push.apple.com/17.188.168.12,port=2195,localport=64265]]
09:04:10,805 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (default task-75) Made a new connection to APNS
09:04:10,805 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (MonitoringThread-1) Started monitoring thread
09:04:10,920 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (default task-75) draining buffer
09:04:10,921 DEBUG [com.mymdm.rest.test.TestMDMService] (default task-75) push(HttpServletRequest, HttpServletResponse) - ApnsNotification response=Message(Id=1; Token=7BC93DC7AF930EED7379899B4E3FB9676DDABDCBA7B4F39D5E7654ECDC3F1DD0; Payload={"mdm":"4A846CEF-3241-426E-B451-F258E4B1ABA6"})
09:04:10,921 DEBUG [com.mymdm.rest.test.TestMDMService] (default task-75) push(HttpServletRequest, HttpServletResponse) - end
09:04:10,962 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (MonitoringThread-1) Error-response packet 080800000001
09:04:10,962 DEBUG [com.notnoop.apns.internal.Utilities] (MonitoringThread-1) close 3e1dae5f[SSL_RSA_WITH_3DES_EDE_CBC_SHA: Socket[addr=gateway.push.apple.com/17.188.168.12,port=2195,localport=64265]]
09:04:10,964 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (MonitoringThread-1) Closed connection cause=INVALID_TOKEN; id=1
09:04:10,964 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (MonitoringThread-1) Candidate for removal, message id 1
09:04:10,964 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (MonitoringThread-1) Bad message found 1
09:04:10,964 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (MonitoringThread-1) delegate.messageSendFailed, message id 1
09:04:10,964 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (MonitoringThread-1) resending 0 notifications
09:04:10,964 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (MonitoringThread-1) Monitoring input stream closed by EOF
09:04:10,965 DEBUG [com.notnoop.apns.internal.Utilities] (MonitoringThread-1) close 3e1dae5f[SSL_RSA_WITH_3DES_EDE_CBC_SHA: Socket[addr=gateway.push.apple.com/17.188.168.12,port=2195,localport=64265]]
09:04:10,965 DEBUG [com.notnoop.apns.internal.ApnsConnectionImpl] (MonitoringThread-1) draining buffer
Shadowman
  • 11,150
  • 19
  • 100
  • 198
  • I'm assuming you're past all the hurdles and what not for enrolling the devices and that the only struggle here is just sending a command to the device. If this is the case then you'll need to show me the payload command you're sending – TNguyen Dec 12 '17 at 14:35
  • I just added more details, including output from troubleshooting with curl to send my push notification – Shadowman Dec 12 '17 at 14:48
  • which command are you trying to send? – TNguyen Dec 12 '17 at 15:05
  • I'm not sending any command yet, just what you see in the curl command. As I understood it, you don't send the command in the push notification. You send a push notification to the device (what you see in the curl command) and that triggers the device to call the endpoint in ServerURL (from the MDM profile). At that point, you return the command you want the device to execute. I haven't gotten past the APNS message, though. I get the BadDeviceToken message. – Shadowman Dec 12 '17 at 15:08
  • I don't think it's `deviceToken` for `https://api.push.apple.com/3/device/` it should be the `deviceUDID` – TNguyen Dec 12 '17 at 15:09
  • oh my mistake, I thought you were at the point where the device was calling `/server` for some reason – TNguyen Dec 12 '17 at 15:10
  • Yeah, just tried deviceUDID too. That didn't work either. – Shadowman Dec 12 '17 at 15:11
  • i'm guessing the pushmagic and/or devicetoken is formatted incorrectly. Can you show me them ? ( you can hide some characters if need be but I don't think it's a big deal showing it if you're using a test device) – TNguyen Dec 12 '17 at 15:37
  • Fixed it! Turns out the curl command issue was due to the format of the payload. It should look like {"mdm":""}, not {"aps":{"mdm":""}} – Shadowman Dec 12 '17 at 16:13
  • that's great news, although i'm surprised the `java-apns` library would get that wrong though – TNguyen Dec 12 '17 at 16:14
  • 1
    @Shadowman I'm having the same problem you had. Can you please let me know what did you use for device token in the url? – Ivan Mar 29 '19 at 12:55
  • @Ivan or anyone else who runs into this in the future: The Token response sent by the phone on check in is base64 encoded binary data. The /device/ is expecting the hexadecimal bytes. This required decoding base64 and then converting the binary data into hexadecimal representation. – bobcat Mar 10 '21 at 20:28

0 Answers0