我正在使用 Java 开发一个简单的 iOS 移动设备管理 (MDM) 服务器,作为脑力锻炼和概念验证。到目前为止,我有一系列 JAX-RS RESTful 服务端点允许我做:
- 初始设备注册
- 初始 MDM 证书注册 (SCEP)
- 设备证书注册 (SCEP)
- MDM 配置文件有效负载安装
我的 MDM 个人资料看起来像这样。它使用 SCEP 配置设备证书并安装有关签入 URL 和 MDM 本身的信息:
<?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>
无线安装完成后,我会在我的 iOS 设备上看到配置和设备证书。我还看到对我的 /checkin 端点的调用,其中包含 APNS 的 PushMagic 和设备令牌值。正是在这一点上,我遇到了障碍。
我正在使用 java-apns 库尝试使用 PushMagic 和设备令牌通过 APNS 向我的设备发送推送通知。我的电话看起来像这样:
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);
但是,当我尝试发送推送通知时,我在日志中看到如下所示的错误:
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
知道这里会发生什么吗?据我了解,iOS 设备应该会收到推送通知,然后它将 MDM 配置文件中定义的 URL 称为 ServerURL。这不正确吗?我误解了这里的流程吗?如何更正 MDM 推送通知的问题?
更新: 所以我现在尝试了两种测试方法:使用 java-apns 库和使用 curl。使用 curl,我的命令如下所示:
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>
然而,curl 命令失败,并显示错误消息{"reason":"BadDeviceToken"}
,即使我正在将设备在调用中提供的值复制并粘贴到我的/checkin
端点。完整的输出如下所示:
* 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"}
推送证书本身是通过在 macOS High Sierra 上安装 macOS 服务器然后导出安装 Profile Server 时生成的证书生成的。我不明白为什么当令牌直接来自设备本身时我会收到错误的令牌错误。我假设这是调用我的 java-apns 调用失败的相同错误。该调用的输出如下所示:
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