Background
All iOS devices have a unique device identifier (UDID) that is needed to sign and deploy apps outside of the App Store. Traditionally this requires that the user plug the device into a computer, launch iTunes, and click through the serial number field until the UDID shows up.
Fortunately, we can use a portion of the enterprise management support to gather that data without requiring as much effort by the user. The entire process is covered here, but we only need to do the first phase of it. There’s no need to do a formal enrollment with the iOS device.
Implementing this requires two things: an authentication system so the device can be associated with a specific user and a correctly signed .mobileconfig file for the initial request to the iOS device. The first can be done through a variety of methods, but the second has a very specific format it must be in and be delivered in.
Creating the .mobileconfig file
The base .mobileconfig file is a specially-formatted XML file that the iOS device will parse and use to return the indicated values.
<?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>PayloadContent</key>
<dict>
<key>URL</key>
<string><!-- URL to send the response to --></string>
<key>DeviceAttributes</key>
<array>
<string>UDID</string>
<string>IMEI</string>
<string>ICCID</string>
<string>VERSION</string>
<string>PRODUCT</string>
</array>
</dict>
<key>PayloadOrganization</key>
<string><!-- a DNS-style name for the organization requesting (e.g., example.com) --></string>
<key>PayloadDisplayName</key>
<string><!-- a human-readable name for this request --></string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadUUID</key>
<string><!-- a UUID-formatted unique identifier for this request (e.g., D77DD928-4312-4E9E-9562-690D695EC7CD) --></string>
<key>PayloadIdentifier</key>
<string><!-- a reverse DNS-formatted identifier for this request (e.g., com.example.profile-request) --></string>
<key>PayloadDescription</key>
<string><!-- a human-readable description of what this request is for --></string>
<key>PayloadType</key>
<string>Profile Service</string>
</dict>
</plist>
Aside from the few customizable portions, the rest must be included exactly.
Signing the .mobileconfig file
For the iOS device to consider the request valid it has to be delivered in a PKCS#7 envelope where the signing certificate chain resolves to a trusted certificate authority by the device. In simpler terms, it needs to be signed with a recognized SSL certificate and private key, and that signing needs to be in a specific format.
OpenSSL (included on many systems by default) can do the correct signing with its smime sub-command:
openssl smime -sign -signer <your ssl certificate file> -inkey <your private key file> -certfile <additional certificates if your issuer provides them> -nodetach -outform der -in <the basic .mobileconfig to sign> -out <the output file to save it as>
Delivering the .mobileconfig file
Now that the file is signed, it also has to be delivered in a specific way. This means setting a specific Content-Type
header when the file is delivered over an HTTP connection. The value for this header must be application/x-apple-aspen-config
.
Once delivered and accepted by the user on the device, the device will send back a response payload to the URL indicated in the request. Extracting that should be relatively straightforward.
Considerations
The signing command above requires both the private key and the resulting SSL certificate. If needed, verifying that the two pair up can be done with these two commands:
openssl x509 -noout -modulus -in server.crt | openssl md5
openssl rsa -noout -modulus -in server.key | openssl md5
The two output values should match.
Additionally, since the .mobileconfig file is signed with an SSL certificate, it’s important to note when the certificate expires. A new signed file must be generated each time the certificate is renewed.