Search code examples
iosprovisioning-profilemdm

iOS - Validate enterprise provisioning profiles for private appstore


As part of our Mobile Device Management feature, we offer private appstore to our customers. Administrators can upload an ipa file to our server, and we will allow the managed devices to install those enterprise apps directly.

When the administrators upload the ipa, we want to do some validation, and reject immediately if the ipa does not meet the requirements. Specifically:

  1. If the ipa is signed by a certificate other than enterprise certificate (such as an appstore certificate), we want to reject it;
  2. If the ipa is signed by a certificate that has expired, we want to reject it;
  3. If the ipa is signed by a certificate that has been revoked, we want to reject it.

I have the following questions:

  1. For requirement #1, I have noticed that enterprise builds have a file embedded.mobileprovision in the ipa, but appstore builds don't have the file. Is it sufficient to check for the existence of that file to determine whether the uploaded ipa is an enterprise ipa, or is there a more accurate way to identify a non-enterprise ipa?
  2. For requirement #2, it looks like there is a field ExpirationDate in embedded.mobileprovision, I could just check the value of that to determine the expiration date?
  3. To my knowledge, #1 and #2 above are possible, but #3 won't be able to be verified until the user actually tries to install the ipa. I.e., I can't catch the error when an admin uploads the ipa, but instead I'll allow that, and the user will get the error of not able to install the app.

Thanks in advance.


Solution

  • The code below accomplishes the task desired.

    Boolean foundMobileProvision = false;
    Pattern mobileProvisionPattern = Pattern.compile("embedded\\.mobileprovision$");
    
    while ((entry = zipStream.getNextEntry()) != null) {
        Matcher mobileProvisionMatcher = mobileProvisionPattern.matcher(entryName);
        if (!entry.isDirectory()) {
            if (mobileProvisionMatcher.find()) {
                foundMobileProvision = true;
                CMSSignedDataParser parser = new CMSSignedDataParser(new BcDigestCalculatorProvider(), zipStream);
                InputStream plistContentStream = parser.getSignedContent().getContentStream();
    
                Map<String, Object> mobileProvisionAttributes = PlistParser.parsePlistToMap(plistContentStream);
    
                plistContentStream.close();
    
                validateEnterpriseProvision(mobileProvisionAttributes);
            }
        }
    }
    zipStream.close();
    if (!foundMobileProvision) {
        throw new InvalidEnterpriseProvisionException("Uploaded app must have a valid enterprise provisioning profile");
    }
    
    private void validateEnterpriseProvision(Map<String, Object> mobileProvisionAttributes) {
        Boolean provisionAllDevices = (Boolean) mobileProvisionAttributes.get(IOS_MOBILE_PROVISION_ALL_DEVICES);
    
        if (provisionAllDevices == null || !provisionAllDevices) {
            throw new InvalidEnterpriseProvisionException("Uploaded app must have a valid enterprise provisioning profile");
        }
    
        Date expirationDate = (Date) mobileProvisionAttributes.get(IOS_MOBILE_PROVISION_EXPIRATION_DATE);
        if (new Date().after(expirationDate)) {
            throw new EnterpriseProvisionExpiredException("Profile expired");
        }
    }