iOS MDM — Sending MDM Push Notifications from Java

2019-07-26 15:30发布

问题:

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