iOS - 验证私有应用商店的企业配置文件
iOS - Validate enterprise provisioning profiles for private appstore
作为我们移动设备管理功能的一部分,我们为客户提供私人应用商店。管理员可以将 ipa 文件上传到我们的服务器,我们将允许受管设备直接安装这些企业应用程序。
管理员上传ipa的时候,我们想做一些验证,如果ipa不符合要求就直接拒绝。具体来说:
- 如果ipa是由企业证书以外的证书签名的(例如appstore证书),我们要拒绝它;
- 如果 ipa 是由已过期的证书签名的,我们要拒绝它;
- 如果 ipa 是由已被吊销的证书签名的,我们要拒绝它。
我有以下问题:
- 对于要求 #1,我注意到企业构建在 ipa 中有一个文件
embedded.mobileprovision
,但应用商店构建没有该文件。检查该文件的存在是否足以确定上传的 ipa 是否为企业 ipa,或者是否有更准确的方法来识别非企业 ipa?
- 对于要求 #2,
embedded.mobileprovision
中似乎有一个字段 ExpirationDate
,我可以检查它的值以确定到期日期吗?
- 据我所知,上面的#1 和#2 是可能的,但是在用户实际尝试安装 ipa 之前无法验证#3。也就是说,当管理员上传 ipa 时,我无法捕捉到错误,但我会允许,用户将收到无法安装应用程序的错误。
提前致谢。
您的 ipa 文件中有一个 embedded.mobileprovision。
- 解压,有一个Payload/appName.app/
- 找出embedded.mobileprovision
尝试调查 embedded.mobileprovision
它是一个二进制 plist(plist 是一种有点特殊的 xml 格式,但仍然 xml)。我可以用 atom 和 vim 查看它。你会注意到有不可读的前缀和后缀,忽略它们。
进入中心部分。可以看到很多节点,比如TeamIdentifier、AppIDName、ProvisionedDevices。它有很多工作人员。
根据您的要求,我找到了一些您可能需要的代码
在 https://github.com/0xc010d/mobileprovision-read/blob/master/main.m
检查mobileprovision的一些关键节点。小于 4?
if ([option isEqualToString:@"type"]) {
if ([plist valueForKeyPath:@"ProvisionedDevices"]){
if ([[plist valueForKeyPath:@"Entitlements.get-task-allow"] boolValue]) {
printf("debug\n");
}
else {
printf("ad-hoc\n");
}
}
else if ([[plist valueForKeyPath:@"ProvisionsAllDevices"] boolValue]) {
printf("enterprise\n");
}
else {
printf("appstore\n");
}
}
关于解析器
您可以编写命令行解析器工具,也可以 google 一个。
步骤不会很难
- trim后缀和前缀
- 获取某些节点的值,例如
ProvisionedDevices
、ProvisionsAllDevices
- 如果
ProvisionsAllDevices
是 true
。接受。
完成
下面的代码完成了所需的任务。
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");
}
}
作为我们移动设备管理功能的一部分,我们为客户提供私人应用商店。管理员可以将 ipa 文件上传到我们的服务器,我们将允许受管设备直接安装这些企业应用程序。
管理员上传ipa的时候,我们想做一些验证,如果ipa不符合要求就直接拒绝。具体来说:
- 如果ipa是由企业证书以外的证书签名的(例如appstore证书),我们要拒绝它;
- 如果 ipa 是由已过期的证书签名的,我们要拒绝它;
- 如果 ipa 是由已被吊销的证书签名的,我们要拒绝它。
我有以下问题:
- 对于要求 #1,我注意到企业构建在 ipa 中有一个文件
embedded.mobileprovision
,但应用商店构建没有该文件。检查该文件的存在是否足以确定上传的 ipa 是否为企业 ipa,或者是否有更准确的方法来识别非企业 ipa? - 对于要求 #2,
embedded.mobileprovision
中似乎有一个字段ExpirationDate
,我可以检查它的值以确定到期日期吗? - 据我所知,上面的#1 和#2 是可能的,但是在用户实际尝试安装 ipa 之前无法验证#3。也就是说,当管理员上传 ipa 时,我无法捕捉到错误,但我会允许,用户将收到无法安装应用程序的错误。
提前致谢。
您的 ipa 文件中有一个 embedded.mobileprovision。
- 解压,有一个Payload/appName.app/
- 找出embedded.mobileprovision
尝试调查 embedded.mobileprovision
它是一个二进制 plist(plist 是一种有点特殊的 xml 格式,但仍然 xml)。我可以用 atom 和 vim 查看它。你会注意到有不可读的前缀和后缀,忽略它们。 进入中心部分。可以看到很多节点,比如TeamIdentifier、AppIDName、ProvisionedDevices。它有很多工作人员。
根据您的要求,我找到了一些您可能需要的代码 在 https://github.com/0xc010d/mobileprovision-read/blob/master/main.m
检查mobileprovision的一些关键节点。小于 4?
if ([option isEqualToString:@"type"]) {
if ([plist valueForKeyPath:@"ProvisionedDevices"]){
if ([[plist valueForKeyPath:@"Entitlements.get-task-allow"] boolValue]) {
printf("debug\n");
}
else {
printf("ad-hoc\n");
}
}
else if ([[plist valueForKeyPath:@"ProvisionsAllDevices"] boolValue]) {
printf("enterprise\n");
}
else {
printf("appstore\n");
}
}
关于解析器
您可以编写命令行解析器工具,也可以 google 一个。 步骤不会很难
- trim后缀和前缀
- 获取某些节点的值,例如
ProvisionedDevices
、ProvisionsAllDevices
- 如果
ProvisionsAllDevices
是true
。接受。
完成
下面的代码完成了所需的任务。
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");
}
}