app-info 2.8.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +12 -7
  3. data/.github/workflows/ci.yml +7 -5
  4. data/.github/workflows/create_release.yml +15 -0
  5. data/.rubocop.yml +33 -11
  6. data/CHANGELOG.md +107 -1
  7. data/Gemfile +10 -5
  8. data/README.md +82 -15
  9. data/Rakefile +11 -0
  10. data/app_info.gemspec +14 -5
  11. data/lib/app_info/aab.rb +76 -110
  12. data/lib/app_info/android/signature.rb +114 -0
  13. data/lib/app_info/android/signatures/base.rb +53 -0
  14. data/lib/app_info/android/signatures/info.rb +158 -0
  15. data/lib/app_info/android/signatures/v1.rb +63 -0
  16. data/lib/app_info/android/signatures/v2.rb +121 -0
  17. data/lib/app_info/android/signatures/v3.rb +131 -0
  18. data/lib/app_info/android/signatures/v4.rb +18 -0
  19. data/lib/app_info/android.rb +181 -0
  20. data/lib/app_info/apk.rb +77 -112
  21. data/lib/app_info/apple.rb +192 -0
  22. data/lib/app_info/certificate.rb +176 -0
  23. data/lib/app_info/const.rb +76 -0
  24. data/lib/app_info/core_ext/object/try.rb +3 -1
  25. data/lib/app_info/core_ext/string/inflector.rb +2 -0
  26. data/lib/app_info/dsym/debug_info.rb +81 -0
  27. data/lib/app_info/dsym/macho.rb +62 -0
  28. data/lib/app_info/dsym.rb +35 -135
  29. data/lib/app_info/error.rb +3 -1
  30. data/lib/app_info/file.rb +49 -0
  31. data/lib/app_info/helper/archive.rb +37 -0
  32. data/lib/app_info/helper/file_size.rb +25 -0
  33. data/lib/app_info/helper/generate_class.rb +29 -0
  34. data/lib/app_info/helper/protobuf.rb +12 -0
  35. data/lib/app_info/helper/signatures.rb +229 -0
  36. data/lib/app_info/helper.rb +5 -128
  37. data/lib/app_info/info_plist.rb +66 -29
  38. data/lib/app_info/ipa/framework.rb +4 -4
  39. data/lib/app_info/ipa.rb +61 -135
  40. data/lib/app_info/macos.rb +54 -102
  41. data/lib/app_info/mobile_provision.rb +66 -48
  42. data/lib/app_info/pe.rb +322 -0
  43. data/lib/app_info/png_uncrush.rb +25 -5
  44. data/lib/app_info/proguard.rb +39 -22
  45. data/lib/app_info/protobuf/manifest.rb +22 -11
  46. data/lib/app_info/protobuf/models/Configuration_pb.rb +1 -0
  47. data/lib/app_info/protobuf/models/README.md +8 -1
  48. data/lib/app_info/protobuf/models/Resources.proto +51 -0
  49. data/lib/app_info/protobuf/models/Resources_pb.rb +42 -0
  50. data/lib/app_info/protobuf/resources.rb +5 -5
  51. data/lib/app_info/version.rb +1 -1
  52. data/lib/app_info.rb +93 -43
  53. metadata +57 -37
data/lib/app_info/aab.rb CHANGED
@@ -1,156 +1,85 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'app_info/protobuf/manifest'
4
- require 'image_size'
5
- require 'forwardable'
6
4
 
7
5
  module AppInfo
8
- # Parse APK file
9
- class AAB
10
- include Helper::HumanFileSize
11
- extend Forwardable
12
-
13
- attr_reader :file
14
-
15
- # APK Devices
16
- module Device
17
- PHONE = 'Phone'
18
- TABLET = 'Tablet'
19
- WATCH = 'Watch'
20
- TV = 'Television'
21
- AUTOMOTIVE = 'Automotive'
22
- end
23
-
6
+ # Parse APK file parser
7
+ class AAB < Android
24
8
  BASE_PATH = 'base'
25
9
  BASE_MANIFEST = "#{BASE_PATH}/manifest/AndroidManifest.xml"
26
10
  BASE_RESOURCES = "#{BASE_PATH}/resources.pb"
27
11
 
28
- def initialize(file)
29
- @file = file
30
- end
31
-
32
- def size(human_size: false)
33
- file_to_human_size(@file, human_size: human_size)
34
- end
35
-
36
- def os
37
- Platform::ANDROID
38
- end
39
- alias file_type os
40
-
12
+ # @!method version_name
13
+ # @see Protobuf::Manifest#version_name
14
+ # @return [String]
15
+ # @!method deep_links
16
+ # @see Protobuf::Manifest#deep_links
17
+ # @return [String]
18
+ # @!method schemes
19
+ # @see Protobuf::Manifest#schemes
20
+ # @return [String]
41
21
  def_delegators :manifest, :version_name, :deep_links, :schemes
42
22
 
43
23
  alias release_version version_name
44
24
 
25
+ # @return [String]
45
26
  def package_name
46
27
  manifest.package
47
28
  end
48
29
  alias identifier package_name
49
30
  alias bundle_id package_name
50
31
 
32
+ # @return [String]
51
33
  def version_code
52
34
  manifest.version_code.to_s
53
35
  end
54
36
  alias build_version version_code
55
37
 
38
+ # @return [String]
56
39
  def name
57
40
  manifest.label
58
41
  end
59
42
 
60
- def device_type
61
- if wear?
62
- Device::WATCH
63
- elsif tv?
64
- Device::TV
65
- elsif automotive?
66
- Device::AUTOMOTIVE
67
- else
68
- Device::PHONE
69
- end
70
- end
71
-
72
- # TODO: find a way to detect
73
- # Found answer but not works: https://stackoverflow.com/questions/9279111/determine-if-the-device-is-a-smartphone-or-tablet
74
- # def tablet?
75
- # resource.first_package
76
- # .entries('bool')
77
- # .select{|e| e.name == 'isTablet' }
78
- # .size >= 1
79
- # end
80
-
81
- def wear?
82
- use_features.include?('android.hardware.type.watch')
83
- end
84
-
85
- def tv?
86
- use_features.include?('android.software.leanback')
87
- end
88
-
89
- def automotive?
90
- use_features.include?('android.hardware.type.automotive')
91
- end
92
-
43
+ # @return [String]
93
44
  def min_sdk_version
94
45
  manifest.uses_sdk.min_sdk_version
95
46
  end
96
47
  alias min_os_version min_sdk_version
97
48
 
49
+ # @return [String]
98
50
  def target_sdk_version
99
51
  manifest.uses_sdk.target_sdk_version
100
52
  end
101
53
 
54
+ # @return [Array<String>]
102
55
  def use_features
56
+ return [] unless manifest.respond_to?(:uses_feature)
57
+
103
58
  @use_features ||= manifest&.uses_feature&.map(&:name)
104
59
  end
105
60
 
61
+ # @return [Array<String>]
106
62
  def use_permissions
63
+ return [] unless manifest.respond_to?(:uses_permission)
64
+
107
65
  @use_permissions ||= manifest&.uses_permission&.map(&:name)
108
66
  end
109
67
 
68
+ # @return [Protobuf::Node]
110
69
  def activities
111
70
  @activities ||= manifest.activities
112
71
  end
113
72
 
73
+ # @return [Protobuf::Node]
114
74
  def services
115
75
  @services ||= manifest.services
116
76
  end
117
77
 
78
+ # @return [Protobuf::Node]
118
79
  def components
119
80
  @components ||= manifest.components.transform_values
120
81
  end
121
82
 
122
- def sign_version
123
- return 'v1' unless signs.empty?
124
-
125
- # when ?
126
- # https://source.android.com/security/apksigning/v2?hl=zh-cn
127
- # 'v2'
128
- # when ?
129
- # https://source.android.com/security/apksigning/v3?hl=zh-cn
130
- # 'v3'
131
- 'unknown'
132
- end
133
-
134
- def signs
135
- return @signs if @signs
136
-
137
- @signs = []
138
- each_file do |path, data|
139
- # find META-INF/xxx.{RSA|DSA}
140
- next unless path =~ %r{^META-INF/} && data.unpack('CC') == [0x30, 0x82]
141
-
142
- @signs << APK::Sign.new(path, OpenSSL::PKCS7.new(data))
143
- end
144
-
145
- @signs
146
- end
147
-
148
- def certificates
149
- @certificates ||= signs.each_with_object([]) do |sign, obj|
150
- obj << APK::Certificate.new(sign.path, sign.sign.certificates[0])
151
- end
152
- end
153
-
154
83
  def each_file
155
84
  zip.each do |entry|
156
85
  next unless entry.file?
@@ -167,36 +96,70 @@ module AppInfo
167
96
  end
168
97
 
169
98
  def entry(name, base_path: BASE_PATH)
170
- entry = @zip.find_entry(File.join(base_path, name))
99
+ entry = @zip.find_entry(::File.join(base_path, name))
171
100
  raise NotFoundError, "'#{name}'" if entry.nil?
172
101
 
173
102
  entry
174
103
  end
175
104
 
105
+ # @return [Protobuf::Manifest]
176
106
  def manifest
177
107
  io = zip.read(zip.find_entry(BASE_MANIFEST))
178
108
  @manifest ||= Protobuf::Manifest.parse(io, resource)
179
109
  end
180
110
 
111
+ # @return [Protobuf::Resources]
181
112
  def resource
182
113
  io = zip.read(zip.find_entry(BASE_RESOURCES))
183
114
  @resource ||= Protobuf::Resources.parse(io)
184
115
  end
185
116
 
186
- def zip
187
- @zip ||= Zip::File.open(@file)
188
- end
189
-
190
- def icons
117
+ # Full icons metadata
118
+ # @example full icons
119
+ # aab.icons
120
+ # # => [
121
+ # # {
122
+ # # name: 'ic_launcher.png',
123
+ # # file: '/path/to/ic_launcher.webp',
124
+ # # dimensions: [29, 29]
125
+ # # },
126
+ # # {
127
+ # # name: 'ic_launcher.png',
128
+ # # file: '/path/to/ic_launcher.png',
129
+ # # dimensions: [120, 120]
130
+ # # },
131
+ # # {
132
+ # # name: 'ic_launcher.xml',
133
+ # # file: '/path/to/ic_launcher.xml',
134
+ # # dimensions: [nil, nil]
135
+ # # },
136
+ # # ]
137
+ # @example exclude xml icons
138
+ # aab.icons(filter: :xml)
139
+ # # => [
140
+ # # {
141
+ # # name: 'ic_launcher.png',
142
+ # # file: '/path/to/ic_launcher.webp',
143
+ # # dimensions: [29, 29]
144
+ # # },
145
+ # # {
146
+ # # name: 'ic_launcher.png',
147
+ # # file: '/path/to/ic_launcher.png',
148
+ # # dimensions: [120, 120]
149
+ # # }
150
+ # # ]
151
+ # @param [String, Symbol, Array<Symbol, Array>] filter filter file extension name
152
+ # @return [Array<Hash{Symbol => String, Array<Integer>}>] icons paths of icons
153
+ def icons(exclude: nil)
191
154
  @icons ||= manifest.icons.each_with_object([]) do |res, obj|
192
155
  path = res.value
193
- filename = File.basename(path)
194
- filepath = File.join(contents, File.dirname(path))
195
- file = File.join(filepath, filename)
196
- FileUtils.mkdir_p filepath
156
+ filename = ::File.basename(path)
157
+ filepath = ::File.join(contents, ::File.dirname(path))
158
+ file = ::File.join(filepath, filename)
159
+ FileUtils.mkdir_p(filepath)
197
160
 
198
161
  binary_data = read_file(path)
199
- File.write(file, binary_data, encoding: Encoding::BINARY)
162
+ ::File.write(file, binary_data, encoding: Encoding::BINARY)
200
163
 
201
164
  obj << {
202
165
  name: filename,
@@ -204,6 +167,8 @@ module AppInfo
204
167
  dimensions: ImageSize.path(file).size
205
168
  }
206
169
  end
170
+
171
+ extract_icon(@icons, exclude: exclude)
207
172
  end
208
173
 
209
174
  def clear!
@@ -218,14 +183,15 @@ module AppInfo
218
183
  @info = nil
219
184
  end
220
185
 
221
- def contents
222
- @contents ||= File.join(Dir.mktmpdir, "AppInfo-android-#{SecureRandom.hex}")
186
+ # @return [Zip::File]
187
+ def zip
188
+ @zip ||= Zip::File.open(@file)
223
189
  end
224
190
 
225
191
  private
226
192
 
227
193
  def xml_file?(file)
228
- File.extname(file) == '.xml'
194
+ ::File.extname(file) == '.xml'
229
195
  end
230
196
 
231
197
  # TODO: how to convert xml content after decode protoubufed content
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppInfo
4
+ class Android < File
5
+ # Android Signature
6
+ #
7
+ # Support digest and length:
8
+ #
9
+ # RSA:1024、2048、4096、8192、16384
10
+ # EC:NIST P-256、P-384、P-521
11
+ # DSA:1024、2048、3072
12
+ module Signature
13
+ class VersionError < Error; end
14
+ class SecurityError < Error; end
15
+ class NotFoundError < NotFoundError; end
16
+
17
+ module Version
18
+ V1 = 1
19
+ V2 = 2
20
+ V3 = 3
21
+ V4 = 4
22
+ end
23
+
24
+ # All registerd verions to verify
25
+ #
26
+ # key is the version
27
+ # value is the class
28
+ @versions = {}
29
+
30
+ class << self
31
+ # Verify Android Signature
32
+ #
33
+ # @example Get unverified v1 certificates, verified v2 certificates,
34
+ # and not found v3 certificate
35
+ #
36
+ # signature.versions(parser)
37
+ # # => [
38
+ # # {
39
+ # # version: 1,
40
+ # # verified: false,
41
+ # # certificates: [<AppInfo::Certificate>, ...],
42
+ # # verifier: AppInfo::Androig::Signature
43
+ # # },
44
+ # # {
45
+ # # version: 2,
46
+ # # verified: false,
47
+ # # certificates: [<AppInfo::Certificate>, ...],
48
+ # # verifier: AppInfo::Androig::Signature
49
+ # # },
50
+ # # {
51
+ # # version: 3
52
+ # # }
53
+ # # ]
54
+ # @todo version 4 no implantation yet
55
+ # @param [AppInfo::File] parser
56
+ # @param [Version, Integer] min_version
57
+ # @return [Array<Hash>] versions
58
+ def verify(parser, min_version: Version::V4)
59
+ min_version = min_version.to_i if min_version.is_a?(String)
60
+ if min_version && min_version > Version::V4
61
+ raise VersionError,
62
+ "No signature found in #{min_version} scheme or newer for android file"
63
+ end
64
+
65
+ if min_version.zero?
66
+ raise VersionError,
67
+ "Unkonwn version: #{min_version}, avaiables in 1/2/3 and 4 (no implantation yet)"
68
+ end
69
+
70
+ # try full version signatures if min_version is nil
71
+ versions = min_version.downto(Version::V1).each_with_object([]) do |version, signatures|
72
+ next unless kclass = fetch(version)
73
+
74
+ data = { version: version }
75
+ begin
76
+ verifier = kclass.verify(parser)
77
+ data[:verified] = verifier.verified
78
+ data[:certificates] = verifier.certificates
79
+ data[:verifier] = verifier
80
+ rescue SecurityError, NotFoundError
81
+ # not this version, try the low version
82
+ ensure
83
+ signatures << data
84
+ end
85
+ end
86
+
87
+ versions.sort_by { |entry| entry[:version] }
88
+ end
89
+
90
+ def registered
91
+ @versions.keys
92
+ end
93
+
94
+ def register(version, verifier)
95
+ @versions[version] = verifier
96
+ end
97
+
98
+ def fetch(version)
99
+ @versions[version]
100
+ end
101
+
102
+ def exist?(version)
103
+ @versions.key?(version)
104
+ end
105
+ end
106
+
107
+ UINT32_MAX_VALUE = 2_147_483_647
108
+ UINT32_SIZE = 4
109
+ UINT64_SIZE = 8
110
+ end
111
+ end
112
+ end
113
+
114
+ require 'app_info/android/signatures/base'
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'app_info/android/signatures/info'
4
+
5
+ module AppInfo
6
+ class Android < File
7
+ module Signature
8
+ class Base
9
+ def self.verify(parser)
10
+ instance = new(parser)
11
+ instance.verify
12
+ instance
13
+ end
14
+
15
+ DESCRIPTION = 'APK Signature Scheme'
16
+
17
+ attr_reader :verified
18
+
19
+ def initialize(parser)
20
+ @parser = parser
21
+ @verified = false
22
+ end
23
+
24
+ # @abstract Subclass and override {#verify} to implement
25
+ def verify
26
+ raise NotImplementedError, ".#{__method__} method implantation required in #{self.class}"
27
+ end
28
+
29
+ # @abstract Subclass and override {#certificates} to implement
30
+ def certificates
31
+ raise NotImplementedError, ".#{__method__} method implantation required in #{self.class}"
32
+ end
33
+
34
+ def scheme
35
+ "v#{version}"
36
+ end
37
+
38
+ def description
39
+ "#{DESCRIPTION} #{scheme}"
40
+ end
41
+
42
+ def logger
43
+ @parser.logger
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ require 'app_info/android/signatures/v1'
51
+ require 'app_info/android/signatures/v2'
52
+ require 'app_info/android/signatures/v3'
53
+ require 'app_info/android/signatures/v4'
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+ require 'openssl'
5
+
6
+ module AppInfo
7
+ class Android < File
8
+ module Signature
9
+ # APK signature scheme signurate info
10
+ #
11
+ # FORMAT:
12
+ # OFFSET DATA TYPE DESCRIPTION
13
+ # * @+0 bytes uint64: size in bytes (excluding this field)
14
+ # * @+8 bytes payload
15
+ # * @-24 bytes uint64: size in bytes (same as the one above)
16
+ # * @-16 bytes uint128: magic value
17
+ class Info
18
+ include AppInfo::Helper::IOBlock
19
+
20
+ # Signature block information
21
+ SIG_SIZE_OF_BLOCK_SIZE = 8
22
+ SIG_MAGIC_BLOCK_SIZE = 16
23
+ SIG_BLOCK_MIN_SIZE = 32
24
+
25
+ # Magic value: APK Sig Block 42
26
+ SIG_MAGIC = [
27
+ 0x41, 0x50, 0x4b, 0x20, 0x53, 0x69,
28
+ 0x67, 0x20, 0x42, 0x6c, 0x6f, 0x63,
29
+ 0x6b, 0x20, 0x34, 0x32
30
+ ].freeze
31
+
32
+ attr_reader :total_size, :pairs, :magic, :logger
33
+
34
+ def initialize(version, parser, logger)
35
+ @version = version
36
+ @parser = parser
37
+ @logger = logger
38
+
39
+ pares_signatures_pairs
40
+ end
41
+
42
+ # Find singers
43
+ #
44
+ # FORMAT:
45
+ # OFFSET DATA TYPE DESCRIPTION
46
+ # * @+0 bytes uint64: size in bytes
47
+ # * @+8 bytes payload block
48
+ # * @+0 bytes uint32: id
49
+ # * @+4 bytes payload: value
50
+ def signers(block_id)
51
+ count = 0
52
+ until @pairs.eof?
53
+ left_bytes = left_bytes_check(
54
+ @pairs, UINT64_SIZE, NotFoundError,
55
+ "Insufficient data to read size of APK Signing Block ##{count}"
56
+ )
57
+
58
+ pair_buf = @pairs.read(UINT64_SIZE)
59
+ pair_size = pair_buf.unpack1('Q')
60
+ if pair_size < UINT32_SIZE || pair_size > UINT32_MAX_VALUE
61
+ raise NotFoundError,
62
+ "APK Signing Block ##{count} size out of range: #{pair_size} > #{UINT32_MAX_VALUE}"
63
+ end
64
+
65
+ if pair_size > left_bytes
66
+ raise NotFoundError,
67
+ "APK Signing Block ##{count} size out of range: #{pair_size} > #{left_bytes}"
68
+ end
69
+
70
+ # fetch next signer block position
71
+ next_pos = @pairs.pos + pair_size.to_i
72
+
73
+ id_block = @pairs.read(UINT32_SIZE)
74
+ id_bytes = id_block.unpack('C*')
75
+ if id_bytes == block_id
76
+ logger.debug "Signature block id v#{@version} scheme (0x#{id_block.unpack1('H*')}) found"
77
+ value = @pairs.read(pair_size - UINT32_SIZE)
78
+ return StringIO.new(value)
79
+ end
80
+
81
+ @pairs.seek(next_pos)
82
+ count += 1
83
+ end
84
+
85
+ block_id_hex = block_id.reverse.pack('C*').unpack1('H*')
86
+ raise NotFoundError, "Not found block id 0x#{block_id_hex} in APK Signing Block."
87
+ end
88
+
89
+ def zip64?
90
+ zip_io.zip64_file?(start_buffer)
91
+ end
92
+
93
+ def pares_signatures_pairs
94
+ block = signature_block
95
+ block.rewind
96
+ # get pairs size
97
+ @total_size = block.size - (SIG_SIZE_OF_BLOCK_SIZE + SIG_MAGIC_BLOCK_SIZE)
98
+
99
+ # get pairs block
100
+ @pairs = StringIO.new(block.read(@total_size))
101
+
102
+ # get magic value
103
+ block.seek(block.pos + SIG_SIZE_OF_BLOCK_SIZE)
104
+ @magic = block.read(SIG_MAGIC_BLOCK_SIZE)
105
+ end
106
+
107
+ def signature_block
108
+ @signature_block ||= lambda {
109
+ logger.debug "cdir_offset: #{cdir_offset}"
110
+
111
+ file_io.seek(cdir_offset - (Info::SIG_MAGIC_BLOCK_SIZE + Info::SIG_SIZE_OF_BLOCK_SIZE))
112
+ footer_block = file_io.read(Info::SIG_SIZE_OF_BLOCK_SIZE)
113
+ if footer_block.size < Info::SIG_SIZE_OF_BLOCK_SIZE
114
+ raise NotFoundError, "APK Signing Block size out of range: #{footer_block.size}"
115
+ end
116
+
117
+ footer = footer_block.unpack1('Q')
118
+ total_size = footer
119
+ offset = cdir_offset - total_size - Info::SIG_SIZE_OF_BLOCK_SIZE
120
+ if offset.negative?
121
+ raise NotFoundError, "APK Signing Block offset out of range: #{offset}"
122
+ end
123
+
124
+ file_io.seek(offset)
125
+ header = file_io.read(Info::SIG_SIZE_OF_BLOCK_SIZE).unpack1('Q')
126
+
127
+ if header != footer
128
+ raise NotFoundError,
129
+ "APK Signing Block header and footer mismatch: #{header} != #{footer}"
130
+ end
131
+
132
+ io = file_io.read(total_size)
133
+ StringIO.new(io)
134
+ }.call
135
+ end
136
+
137
+ def cdir_offset
138
+ @cdir_offset ||= lambda {
139
+ eocd_buffer = zip_io.get_e_o_c_d(start_buffer)
140
+ eocd_buffer[12..16].unpack1('V')
141
+ }.call
142
+ end
143
+
144
+ def start_buffer
145
+ @start_buffer ||= zip_io.start_buf(file_io)
146
+ end
147
+
148
+ def zip_io
149
+ @zip_io ||= @parser.zip
150
+ end
151
+
152
+ def file_io
153
+ @file_io ||= ::File.open(@parser.file, 'rb')
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppInfo
4
+ class Android < File
5
+ module Signature
6
+ # Android v1 Signature
7
+ class V1 < Base
8
+ DESCRIPTION = 'JAR signing'
9
+
10
+ PKCS7_HEADER = [0x30, 0x82].freeze
11
+
12
+ attr_reader :certificates, :signatures
13
+
14
+ def version
15
+ Version::V1
16
+ end
17
+
18
+ def description
19
+ DESCRIPTION
20
+ end
21
+
22
+ def verify
23
+ @signatures = fetch_signatures
24
+ @certificates = fetch_certificates
25
+
26
+ raise NotFoundError, 'Not found certificates' if @certificates.empty?
27
+ end
28
+
29
+ private
30
+
31
+ def fetch_signatures
32
+ case @parser
33
+ when AppInfo::APK
34
+ signatures_from(@parser.apk)
35
+ when AppInfo::AAB
36
+ signatures_from(@parser)
37
+ end
38
+ end
39
+
40
+ def fetch_certificates
41
+ @signatures.each_with_object([]) do |(_, sign), obj|
42
+ next if sign.certificates.empty?
43
+
44
+ obj << AppInfo::Certificate.new(sign.certificates[0])
45
+ end
46
+ end
47
+
48
+ def signatures_from(parser)
49
+ signs = {}
50
+ parser.each_file do |path, data|
51
+ # find META-INF/xxx.{RSA|DSA|EC}
52
+ next unless path =~ %r{^META-INF/} && data.unpack('CC') == PKCS7_HEADER
53
+
54
+ signs[path] = OpenSSL::PKCS7.new(data)
55
+ end
56
+ signs
57
+ end
58
+ end
59
+
60
+ register(Version::V1, V1)
61
+ end
62
+ end
63
+ end