Jun 15

不依赖mac读取mobileprovision信息的一百种方法

Lrdcq , 2019/06/15 12:19 , 程序 , 閱讀(2984) , Via 本站原創
在iOS持续构建过程中,我们经常会涉及到读取mobileprovision信息的内容这一过程。我们都知道mobileprovision中包含了一个plist,包含了这个证书对应的大量信息,但是mobileprovision本身不是plist,因此整件事儿的核心就是把mobileprovision中的plist导出出来。网上能直接搜到的方案是:
security cms -D -i path_to_mobileprovision
#在ruby中即
plist = `security cms -D -i #{path_to_mobileprovision}`
plist_content = Plist.parse_xml(plist)

即可,但是注意到调用了shell中的security工具,security这个工具是mac自己提供了,简单来说是mac only。那么我们在非mac的通用ci上完成相关操作,就得找出怎么通过非security的方式打开这个文件了。

思路A:自己开文件

既然我们知道mobileprovision是包含了一个plist,那我们开几个mobileprovision文件看看不就得了:
點擊在新視窗中瀏覽此圖片

打开文件,我们即发现规律:

a. 整个文件内plist就一个,没有被任何奇怪的方式编码,也就是说我直接按照字符串打开文件,找到plist的开始与结束,直接拆取出来,也是可行的。比如:
file_str = File.read(path_to_mobileprovision)
start_index = file_str.index('<?xml')
end_index = file_str.index('</plist>')
plist = file_str[start_index, end_index - start_index + 8]
plist_content = Plist.parse_xml(plist)

不过无论怎么看这个方式也太粗暴了。

b. 好歹从hex浏览器上可以发现规律。假设plist是一个二进制文件的string段,并且发现plist末尾并不是00结尾的,那么肯定某个地方储存了字符串的长度。比如如上最后一个文件我拉出来plist长度为3690即0x0E6A,定眼一看,字符串前面不就是0x0E6A,所以可以明确我可以从字符串前2byte读取到字符串长度,越界问题解决了。

同时注意到多个文件对比,记录plist的段落明显是offset58开始,offset58 2byte 固定0x0482,offset60 2byte plist长度,offset62 长度byte即plist。

遵循这个规律,我们就能写出自己开文件的方法中,最合理的代码:
plist = nil
open path_to_mobileprovision, 'r' do |f|
  f.seek 60 #找到60读取plist长度
  s = f.read(2).unpack('n') #注意这里的无符号短整数是大端字节序
  f.seek 62 #找到62读取plist
  plist = f.read(s[0])
end
plist_content = Plist.parse_xml(plist)

思路B:明确文件含义

既然明确mobileprovision有文件的二进制格式,那么我们更合理的方案还是找出所使用的文件结构体了。搜索一番,得知mobileprovision本质上是一个签名专用的结构:PKCS#7 SignedData(http://pkiglobe.org/pkcs7.html),其中文件本质上是一个固定的结构体(而且正好plist之前的段落结构固定,因此到plist这里offset稳定),但是这个文件通过一个特殊的序列化方式:DER编码的ASN.1(https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One#Example_encoded_in_DER)储存的,因此我们首先找到ASN.1的反序列化工具即可。

在ruby标准库中搜索一番,我们只发现module OpenSSL中有OpenSSL::ASN1(https://docs.ruby-lang.org/en/2.1.0/OpenSSL/ASN1.html)可以解决我们的问题。至少可以反序列化打开文件了:
asn1 = OpenSSL::ASN1.decode_all(File.binread(path_to_mobileprovision))
pp asn1

反序列化文件后,我们可以打出整个mobileprovision的结构数了:
點擊在新視窗中瀏覽此圖片

当然,每个节点的含义我们还是需要对照PKCS#7的文档去check,但是幸福的是,毕竟是稳定的结构体,我们要找到的plist位置是固定的,稍作摸索,我们就可以找出实际的节点,并且完成代码:
asn1 = OpenSSL::ASN1.decode_all(File.binread(path_to_mobileprovision))
plist = asn1[0].value[1].value[0].value[2].value[1].value[0].value
plist_content = Plist.parse_xml(plist)

Reivew上面的方案:

既然我们知道OpenSSL可以开ASN1,一回想mac的security也是安全相关的工具,说不定security内部也是调用的openssl,那么openssl是不是也有shell命令可以开mobileprovision呢?一搜还真是,因此我们也可以:
plist = `openssl smime -inform der -verify -noverify -in #{path_to_mobileprovision}`
plist_content = Plist.parse_xml(plist)

最后总结一下,根据宿主环境的不同,我们可用的方案有:
點擊在新視窗中瀏覽此圖片
关键词:mobileprovision , ruby , mac
logo