apk.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. package tools
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "math"
  8. "os"
  9. )
  10. const (
  11. apkSigBlockMinSize uint32 = 32
  12. // https://android.googlesource.com/platform/build/+/android-7.1.2_r27/tools/signapk/src/com/android/signapk/ApkSignerV2.java
  13. // APK_SIGNING_BLOCK_MAGIC = {
  14. // 0x41, 0x50, 0x4b, 0x20, 0x53, 0x69, 0x67, 0x20,
  15. // 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32 }
  16. apkSigBlockMagicHi = 0x3234206b636f6c42 // LITTLE_ENDIAN, High
  17. apkSigBlockMagicLo = 0x20676953204b5041 // LITTLE_ENDIAN, Low
  18. apkChannelBlockID = 0x71777777
  19. // https://en.wikipedia.org/wiki/Zip_(file_format)
  20. // https://android.googlesource.com/platform/build/+/android-7.1.2_r27/tools/signapk/src/com/android/signapk/ZipUtils.java
  21. zipEocdRecSig = 0x06054b50
  22. zipEocdRecMinSize = 22
  23. zipEocdCentralDirSizeFieldOffset = 12
  24. zipEocdCentralDirOffsetFieldOffset = 16
  25. zipEocdCommentLengthFieldOffset = 20
  26. )
  27. // ChannelInfo for apk
  28. type ChannelInfo struct {
  29. Channel string
  30. Extras map[string]string
  31. raw []byte
  32. }
  33. // ChannelInfo to string
  34. func (c *ChannelInfo) String() string {
  35. b := c.Bytes()
  36. if b == nil {
  37. return ""
  38. }
  39. return string(b)
  40. }
  41. // Bytes for ChannelInfo to byte array
  42. func (c *ChannelInfo) Bytes() []byte {
  43. if c.raw != nil {
  44. return c.raw
  45. }
  46. if len(c.Channel) == 0 && c.Extras == nil {
  47. return nil
  48. }
  49. var buf bytes.Buffer
  50. buf.WriteByte('{')
  51. if len(c.Channel) != 0 {
  52. buf.WriteString("\"channel\":")
  53. buf.WriteByte('"')
  54. buf.WriteString(c.Channel)
  55. buf.WriteByte('"')
  56. buf.WriteByte(',')
  57. }
  58. if c.Extras != nil {
  59. for k, v := range c.Extras {
  60. buf.WriteByte('"')
  61. buf.WriteString(k)
  62. buf.WriteByte('"')
  63. buf.WriteByte(':')
  64. buf.WriteByte('"')
  65. buf.WriteString(v)
  66. buf.WriteByte('"')
  67. buf.WriteByte(',')
  68. }
  69. }
  70. if buf.Len() > 2 {
  71. buf.Truncate(buf.Len() - 1)
  72. }
  73. buf.WriteByte('}')
  74. return buf.Bytes()
  75. }
  76. func readChannelInfo(file string) (c ChannelInfo, err error) {
  77. block, err := readChannelBlock(file)
  78. if err != nil {
  79. return c, err
  80. }
  81. if block != nil {
  82. var bundle map[string]string
  83. err := json.Unmarshal(block, &bundle)
  84. if err != nil {
  85. return c, err
  86. }
  87. c.Channel = bundle["channel"]
  88. delete(bundle, "channel")
  89. c.Extras = bundle
  90. c.raw = block
  91. }
  92. return c, nil
  93. }
  94. // read block associated to apkChannelBlockID
  95. func readChannelBlock(file string) ([]byte, error) {
  96. m, err := readIDValues(file, apkChannelBlockID)
  97. if err != nil {
  98. return nil, err
  99. }
  100. return m[apkChannelBlockID], nil
  101. }
  102. func readIDValues(file string, ids ...uint32) (map[uint32][]byte, error) {
  103. f, err := os.Open(file)
  104. if err != nil {
  105. return nil, err
  106. }
  107. defer f.Close()
  108. eocd, offset, err := findEndOfCentralDirectoryRecord(f)
  109. if err != nil {
  110. return nil, err
  111. }
  112. if offset <= 0 {
  113. return nil, errors.New("Cannot find EOCD record, maybe a broken zip file")
  114. }
  115. centralDirOffset := getEocdCentralDirectoryOffset(eocd)
  116. block, _, err := findApkSigningBlock(f, centralDirOffset)
  117. if err != nil {
  118. return nil, err
  119. }
  120. return findIDValuesInApkSigningBlock(block, ids...)
  121. }
  122. // End of central directory record (EOCD)
  123. //
  124. // Offset Bytes Description[23]
  125. // 0 4 End of central directory signature = 0x06054b50
  126. // 4 2 Number of this disk
  127. // 6 2 Disk where central directory starts
  128. // 8 2 Number of central directory records on this disk
  129. // 10 2 Total number of central directory records
  130. // 12 4 Size of central directory (bytes)
  131. // 16 4 Offset of start of central directory, relative to start of archive
  132. // 20 2 Comment length (n)
  133. // 22 n Comment
  134. // For a zip with no archive comment, the
  135. // end-of-central-directory record will be 22 bytes long, so
  136. // we expect to find the EOCD marker 22 bytes from the end.
  137. func findEndOfCentralDirectoryRecord(f *os.File) ([]byte, int64, error) {
  138. fi, err := f.Stat()
  139. if err != nil {
  140. return nil, -1, err
  141. }
  142. if fi.Size() < zipEocdRecMinSize {
  143. // No space for EoCD record in the file.
  144. return nil, -1, nil
  145. }
  146. // Optimization: 99.99% of APKs have a zero-length comment field in the EoCD record and thus
  147. // the EoCD record offset is known in advance. Try that offset first to avoid unnecessarily
  148. // reading more data.
  149. ret, offset, err := findEOCDRecord(f, 0)
  150. if err != nil {
  151. return nil, -1, err
  152. }
  153. if ret != nil && offset != -1 {
  154. return ret, offset, nil
  155. }
  156. // EoCD does not start where we expected it to. Perhaps it contains a non-empty comment
  157. // field. Expand the search. The maximum size of the comment field in EoCD is 65535 because
  158. // the comment length field is an unsigned 16-bit number.
  159. return findEOCDRecord(f, math.MaxUint16)
  160. }
  161. func findEOCDRecord(f *os.File, maxCommentSize uint16) ([]byte, int64, error) {
  162. if maxCommentSize > math.MaxUint16 {
  163. return nil, -1, os.ErrInvalid
  164. }
  165. fi, err := f.Stat()
  166. if err != nil {
  167. return nil, -1, err
  168. }
  169. fileSize := fi.Size()
  170. if fileSize < zipEocdRecMinSize {
  171. // No space for EoCD record in the file.
  172. return nil, -1, nil
  173. }
  174. // Lower maxCommentSize if the file is too small.
  175. if s := uint16(fileSize - zipEocdRecMinSize); maxCommentSize > s {
  176. maxCommentSize = s
  177. }
  178. maxEocdSize := zipEocdRecMinSize + maxCommentSize
  179. bufOffsetInFile := fileSize - int64(maxEocdSize)
  180. buf := make([]byte, maxEocdSize)
  181. n, e := f.ReadAt(buf, bufOffsetInFile)
  182. if e != nil {
  183. return nil, -1, err
  184. }
  185. eocdOffsetInFile :=
  186. func() int64 {
  187. eocdWithEmptyCommentStartPosition := n - zipEocdRecMinSize
  188. for expectedCommentLength := uint16(0); expectedCommentLength < maxCommentSize; expectedCommentLength++ {
  189. eocdStartPos := eocdWithEmptyCommentStartPosition - int(expectedCommentLength)
  190. if getUint32(buf, eocdStartPos) == zipEocdRecSig {
  191. n := eocdStartPos + zipEocdCommentLengthFieldOffset
  192. actualCommentLength := getUint16(buf, n)
  193. if actualCommentLength == expectedCommentLength {
  194. return int64(eocdStartPos)
  195. }
  196. }
  197. }
  198. return -1
  199. }()
  200. if eocdOffsetInFile == -1 {
  201. // No EoCD record found in the buffer
  202. return nil, -1, nil
  203. }
  204. // EoCD found
  205. return buf[eocdOffsetInFile:], bufOffsetInFile + eocdOffsetInFile, nil
  206. }
  207. func getEocdCentralDirectoryOffset(buf []byte) uint32 {
  208. return getUint32(buf, zipEocdCentralDirOffsetFieldOffset)
  209. }
  210. func getEocdCentralDirectorySize(buf []byte) uint32 {
  211. return getUint32(buf, zipEocdCentralDirSizeFieldOffset)
  212. }
  213. func setEocdCentralDirectoryOffset(eocd []byte, offset uint32) {
  214. putUint32(offset, eocd, zipEocdCentralDirOffsetFieldOffset)
  215. }
  216. func isExpected(ids []uint32, test uint32) bool {
  217. for _, id := range ids {
  218. if id == test {
  219. return true
  220. }
  221. }
  222. return false
  223. }
  224. func findIDValuesInApkSigningBlock(block []byte, ids ...uint32) (map[uint32][]byte, error) {
  225. ret := make(map[uint32][]byte)
  226. position := 8
  227. limit := len(block) - 24
  228. entryCount := 0
  229. for limit > position { // has remaining bytes
  230. entryCount++
  231. if limit-position < 8 { // but not enough
  232. return nil, fmt.Errorf("APK Signing Block broken on entry #%d", entryCount)
  233. }
  234. length := int(getUint64(block, position))
  235. position += 8
  236. if length < 4 || length > limit-position {
  237. return nil, fmt.Errorf("APK Signing Block broken on entry #%d,"+
  238. " size out of range: length=%d, remaining=%d", entryCount, length, limit-position)
  239. }
  240. nextEntryPosition := position + length
  241. id := getUint32(block, position)
  242. position += 4
  243. if len(ids) == 0 || isExpected(ids, id) {
  244. ret[id] = block[position : position+length-4]
  245. }
  246. position = nextEntryPosition
  247. }
  248. return ret, nil
  249. }
  250. // Find the APK Signing Block. The block immediately precedes the Central Directory.
  251. //
  252. // FORMAT:
  253. // uint64: size (excluding this field)
  254. // repeated ID-value pairs:
  255. // uint64: size (excluding this field)
  256. // uint32: ID
  257. // (size - 4) bytes: value
  258. // uint64: size (same as the one above)
  259. // uint128: magic
  260. func findApkSigningBlock(f *os.File, centralDirOffset uint32) (block []byte, offset int64, err error) {
  261. if centralDirOffset < apkSigBlockMinSize {
  262. return block, offset, fmt.Errorf("APK too small for APK Signing Block."+
  263. " ZIP Central Directory offset: %d", centralDirOffset)
  264. }
  265. // Read the footer of APK signing block
  266. // 24 = sizeof(uint128) + sizeof(uint64)
  267. footer := make([]byte, 24)
  268. _, err = f.ReadAt(footer, int64(centralDirOffset-24))
  269. if err != nil {
  270. return
  271. }
  272. // Read the magic and block size
  273. var blockSizeInFooter = getUint64(footer, 0)
  274. if blockSizeInFooter < 24 || blockSizeInFooter > uint64(math.MaxInt32-8 /* ID-value size field*/) {
  275. return block, offset, fmt.Errorf("APK Signing Block size out of range: %d", blockSizeInFooter)
  276. }
  277. if getUint64(footer, 8) != apkSigBlockMagicLo ||
  278. getUint64(footer, 16) != apkSigBlockMagicHi {
  279. return block, offset, errors.New("No APK Signing Block before ZIP Central Directory")
  280. }
  281. totalSize := blockSizeInFooter + 8 /* APK signing block size field*/
  282. offset = int64(uint64(centralDirOffset) - totalSize)
  283. if offset <= 0 {
  284. return block, offset, fmt.Errorf("invalid offset for APK Signing Block %d", offset)
  285. }
  286. block = make([]byte, totalSize)
  287. _, err = f.ReadAt(block, offset)
  288. if err != nil {
  289. return
  290. }
  291. blockSizeInHeader := getUint64(block, 0)
  292. if blockSizeInHeader != blockSizeInFooter {
  293. return nil, offset, fmt.Errorf("APK Signing Block sizes in header "+
  294. "and footer are mismatched! Except %d but %d", blockSizeInFooter, blockSizeInHeader)
  295. }
  296. return block, offset, nil
  297. }
  298. // FORMAT:
  299. // uint64: size (excluding this field)
  300. // repeated ID-value pairs:
  301. // uint64: size (excluding this field)
  302. // uint32: ID
  303. // (size - 4) bytes: value
  304. // uint64: size (same as the one above)
  305. // uint128: magic
  306. func makeSigningBlockWithChannelInfo(info ChannelInfo, signingBlock []byte) ([]byte, int, error) {
  307. signingBlockSize := getUint64(signingBlock, 0)
  308. signingBlockLen := len(signingBlock)
  309. if n := uint64(signingBlockLen - 8); signingBlockSize != n {
  310. return nil, 0, fmt.Errorf("APK Signing Block is illegal! Expect size %d but %d", signingBlockSize, n)
  311. }
  312. channelValue := info.Bytes()
  313. channelValueSize := uint64(4 + len(channelValue))
  314. resultSize := 8 + signingBlockSize + 8 + channelValueSize
  315. newBlock := make([]byte, resultSize)
  316. position := 0
  317. putUint64(resultSize-8, newBlock, position)
  318. position += 8
  319. // copy raw id-value pairs
  320. n, _ := copyBytes(signingBlock, 8, newBlock, position, int(signingBlockSize)-16-8)
  321. position += n
  322. putUint64(channelValueSize, newBlock, position)
  323. position += 8
  324. putUint32(apkChannelBlockID, newBlock, position)
  325. position += 4
  326. n, _ = copyBytes(channelValue, 0, newBlock, position, len(channelValue))
  327. position += n
  328. putUint64(resultSize-8, newBlock, position)
  329. position += 8
  330. copyBytes(signingBlock, signingBlockLen-16, newBlock, int(resultSize-16), 16)
  331. position += 16
  332. if position != int(resultSize) {
  333. return nil, -1, fmt.Errorf("count mismatched ! %d vs %d", position, resultSize)
  334. }
  335. return newBlock, int(resultSize) - signingBlockLen, nil
  336. }
  337. func makeEocd(origin []byte, newCentralDirOffset uint32) []byte {
  338. eocd := make([]byte, len(origin))
  339. copy(eocd, origin)
  340. setEocdCentralDirectoryOffset(eocd, newCentralDirOffset)
  341. return eocd
  342. }