app-info 3.3.2 → 3.4.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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +1 -1
- data/.rubocop.yml +9 -0
- data/CHANGELOG.md +26 -3
- data/README.md +124 -19
- data/app_info.gemspec +2 -2
- data/lib/app_info/android/signature.rb +65 -46
- data/lib/app_info/android/signatures/base.rb +10 -3
- data/lib/app_info/android/signatures/info.rb +47 -13
- data/lib/app_info/android/signatures/v1.rb +1 -1
- data/lib/app_info/android/signatures/v2.rb +12 -7
- data/lib/app_info/android/signatures/v3.rb +156 -15
- data/lib/app_info/dsym.rb +3 -1
- data/lib/app_info/hap.rb +80 -4
- data/lib/app_info/happ.rb +16 -1
- data/lib/app_info/harmonyos.rb +123 -0
- data/lib/app_info/helper/archive.rb +3 -1
- data/lib/app_info/helper/signatures.rb +16 -4
- data/lib/app_info/pe.rb +11 -6
- data/lib/app_info/png_uncrush.rb +2 -2
- data/lib/app_info/protobuf/resources.rb +3 -3
- data/lib/app_info/version.rb +1 -1
- metadata +7 -16
|
@@ -30,25 +30,39 @@ module AppInfo
|
|
|
30
30
|
# V3.1 Signature ID 0x1b93ad61
|
|
31
31
|
V3_1_BLOCK_ID = [0x61, 0xad, 0x93, 0x1b].freeze
|
|
32
32
|
|
|
33
|
-
attr_reader :certificates, :digests
|
|
33
|
+
attr_reader :certificates, :digests, :certificate_lineage
|
|
34
34
|
|
|
35
35
|
def version
|
|
36
36
|
Version::V3
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def verify
|
|
39
|
+
def verify(sdk: nil, **_options)
|
|
40
40
|
begin
|
|
41
41
|
signers_block = singers_block(V3_1_BLOCK_ID)
|
|
42
42
|
rescue NotFoundError
|
|
43
43
|
signers_block = singers_block(V3_BLOCK_ID)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
@certificates, @digests = verified_certs(signers_block)
|
|
46
|
+
@certificates, @digests = verified_certs(signers_block, sdk: sdk)
|
|
47
|
+
@verified = true
|
|
48
|
+
rescue SecurityError => e
|
|
49
|
+
@certificates ||= []
|
|
50
|
+
@digests ||= {}
|
|
51
|
+
code = if e.message.include?('content digest')
|
|
52
|
+
:content_digest_mismatch
|
|
53
|
+
elsif e.message.start_with?('SDK range')
|
|
54
|
+
:sdk_range_mismatch
|
|
55
|
+
elsif e.message.start_with?('Certificate lineage')
|
|
56
|
+
:certificate_lineage_invalid
|
|
57
|
+
else
|
|
58
|
+
:signature_invalid
|
|
59
|
+
end
|
|
60
|
+
add_verification_error(code, e.message)
|
|
47
61
|
end
|
|
48
62
|
|
|
49
63
|
private
|
|
50
64
|
|
|
51
|
-
def verified_certs(signers_block)
|
|
65
|
+
def verified_certs(signers_block, sdk: nil)
|
|
52
66
|
unless (signers = length_prefix_block(signers_block))
|
|
53
67
|
raise SecurityError, 'Not found signers'
|
|
54
68
|
end
|
|
@@ -56,7 +70,7 @@ module AppInfo
|
|
|
56
70
|
certificates = []
|
|
57
71
|
content_digests = {}
|
|
58
72
|
loop_length_prefix_io(signers, name: 'Singer', logger: logger) do |signer|
|
|
59
|
-
signer_certs, signer_digests = extract_signer_data(signer)
|
|
73
|
+
signer_certs, signer_digests = extract_signer_data(signer, sdk: sdk)
|
|
60
74
|
certificates.concat(signer_certs)
|
|
61
75
|
content_digests.merge!(signer_digests)
|
|
62
76
|
end
|
|
@@ -65,13 +79,15 @@ module AppInfo
|
|
|
65
79
|
[certificates, content_digests]
|
|
66
80
|
end
|
|
67
81
|
|
|
68
|
-
def extract_signer_data(signer)
|
|
82
|
+
def extract_signer_data(signer, sdk: nil)
|
|
69
83
|
# raw data
|
|
70
84
|
signed_data = length_prefix_block(signer)
|
|
71
85
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
max_sdk
|
|
86
|
+
min_sdk = signer.read(UINT32_SIZE).unpack1('V')
|
|
87
|
+
max_sdk = signer.read(UINT32_SIZE).unpack1('V')
|
|
88
|
+
if min_sdk > max_sdk || (sdk && !sdk.between?(min_sdk, max_sdk))
|
|
89
|
+
raise SecurityError, "SDK range #{min_sdk}..#{max_sdk} does not include #{sdk}"
|
|
90
|
+
end
|
|
75
91
|
|
|
76
92
|
signatures = length_prefix_block(signer)
|
|
77
93
|
public_key = length_prefix_block(signer, raw: true)
|
|
@@ -102,12 +118,8 @@ module AppInfo
|
|
|
102
118
|
'Signature algorithms don\'t match between digests and signatures records'
|
|
103
119
|
end
|
|
104
120
|
|
|
105
|
-
|
|
121
|
+
verify_content_digest(content_digests, algorithems_digest)
|
|
106
122
|
content_digests[algorithems_digest] = content_digest
|
|
107
|
-
if previous_digest && previous_digest[:content] != content_digest
|
|
108
|
-
raise SecurityError,
|
|
109
|
-
'Signature algorithms don\'t match between digests and signatures records'
|
|
110
|
-
end
|
|
111
123
|
|
|
112
124
|
certificates = length_prefix_block(signed_data)
|
|
113
125
|
certs = signed_data_certs(certificates)
|
|
@@ -119,10 +131,139 @@ module AppInfo
|
|
|
119
131
|
end
|
|
120
132
|
|
|
121
133
|
additional_attrs = length_prefix_block(signed_data)
|
|
122
|
-
|
|
134
|
+
@certificate_lineage = verify_v3_additional_attrs(additional_attrs, certs, pkey)
|
|
123
135
|
|
|
124
136
|
[certs, content_digests]
|
|
125
137
|
end
|
|
138
|
+
|
|
139
|
+
def verify_v3_additional_attrs(attrs, certs, public_key)
|
|
140
|
+
lineage = nil
|
|
141
|
+
loop_length_prefix_io(
|
|
142
|
+
attrs, name: 'Additional Attributes', raw: true, ignore_left_size_precheck: true
|
|
143
|
+
) do |raw_attr|
|
|
144
|
+
if raw_attr.bytesize < UINT32_SIZE
|
|
145
|
+
raise SecurityError,
|
|
146
|
+
'Certificate lineage attribute is malformed'
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
attr = StringIO.new(raw_attr)
|
|
150
|
+
id = attr.read(UINT32_SIZE).unpack1('V')
|
|
151
|
+
if id == SIG_STRIPPING_PROTECTION_ATTR_ID.pack('C*').unpack1('V')
|
|
152
|
+
raise SecurityError,
|
|
153
|
+
'V2 signature indicates APK is signed using APK Signature Scheme v3, ' \
|
|
154
|
+
'but none was found. Signature stripped?'
|
|
155
|
+
elsif id == SIG_PROOF_OF_ROTATION_ATTR_ID.pack('C*').unpack1('V')
|
|
156
|
+
raise SecurityError, 'Certificate lineage attribute is duplicated' if lineage
|
|
157
|
+
|
|
158
|
+
lineage = parse_certificate_lineage(attr.read, certs, public_key)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
lineage
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def parse_certificate_lineage(bytes, certs, public_key)
|
|
165
|
+
input = StringIO.new(bytes)
|
|
166
|
+
version = input.read(UINT32_SIZE)&.unpack1('V')
|
|
167
|
+
raise SecurityError, 'Certificate lineage has an invalid version' unless version == 1
|
|
168
|
+
|
|
169
|
+
nodes = []
|
|
170
|
+
previous_certificate = nil
|
|
171
|
+
previous_algorithm = nil
|
|
172
|
+
seen = {}
|
|
173
|
+
until input.eof?
|
|
174
|
+
node = parse_lineage_node(input)
|
|
175
|
+
verify_lineage_node(node, previous_certificate, previous_algorithm, seen)
|
|
176
|
+
nodes << node
|
|
177
|
+
previous_certificate = node[:certificate]
|
|
178
|
+
previous_algorithm = node[:signature_algorithm_id]
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
if nodes.empty? || nodes.last[:certificate].public_key.to_der != public_key.to_der
|
|
182
|
+
raise SecurityError, 'Certificate lineage does not end at the APK signer'
|
|
183
|
+
end
|
|
184
|
+
unless nodes.last[:certificate].to_der == certs.first.to_der
|
|
185
|
+
raise SecurityError, 'Certificate lineage does not match the APK certificate'
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
nodes
|
|
189
|
+
rescue AppInfo::Android::Signature::SecurityError
|
|
190
|
+
raise
|
|
191
|
+
rescue StandardError => e
|
|
192
|
+
raise SecurityError, "Certificate lineage is malformed: #{e.message}"
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def parse_lineage_node(input)
|
|
196
|
+
node_io = StringIO.new(length_prefix_block(input, raw: true))
|
|
197
|
+
signed_data = length_prefix_block(node_io, raw: true)
|
|
198
|
+
flags = node_io.read(UINT32_SIZE)&.unpack1('V')
|
|
199
|
+
signature_algorithm_id = node_io.read(UINT32_SIZE)&.unpack1('V')
|
|
200
|
+
signature = length_prefix_block(node_io, raw: true)
|
|
201
|
+
unless flags && signature_algorithm_id && node_io.eof?
|
|
202
|
+
raise SecurityError,
|
|
203
|
+
'Certificate lineage node is malformed'
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
signed_io = StringIO.new(signed_data)
|
|
207
|
+
certificate_der = length_prefix_block(signed_io, raw: true)
|
|
208
|
+
parent_algorithm_id = signed_io.read(UINT32_SIZE)&.unpack1('V')
|
|
209
|
+
unless parent_algorithm_id && signed_io.eof?
|
|
210
|
+
raise SecurityError,
|
|
211
|
+
'Certificate lineage signed data is malformed'
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
{
|
|
215
|
+
certificate: AppInfo::Certificate.parse(certificate_der),
|
|
216
|
+
certificate_der: certificate_der,
|
|
217
|
+
flags: flags,
|
|
218
|
+
signature_algorithm_id: signature_algorithm_id,
|
|
219
|
+
parent_signature_algorithm_id: parent_algorithm_id,
|
|
220
|
+
signature: signature,
|
|
221
|
+
signed_data: signed_data
|
|
222
|
+
}
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def verify_lineage_node(node, previous_certificate, previous_algorithm, seen)
|
|
226
|
+
if seen[node[:certificate_der]]
|
|
227
|
+
raise SecurityError, 'Certificate lineage contains duplicate certificates'
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
parent_algorithm = node[:parent_signature_algorithm_id]
|
|
231
|
+
if previous_certificate
|
|
232
|
+
unless parent_algorithm == previous_algorithm
|
|
233
|
+
raise SecurityError,
|
|
234
|
+
'Certificate lineage algorithm chain is invalid'
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
verify_lineage_signature(previous_certificate, parent_algorithm, node[:signature],
|
|
238
|
+
node[:signed_data])
|
|
239
|
+
elsif parent_algorithm != 0 || !node[:signature].empty?
|
|
240
|
+
raise SecurityError, 'Certificate lineage first node is invalid'
|
|
241
|
+
end
|
|
242
|
+
seen[node[:certificate_der]] = true
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def verify_lineage_signature(certificate, algorithm_id, signature, signed_data)
|
|
246
|
+
algorithm = [algorithm_id].pack('V').unpack('C*')
|
|
247
|
+
digest_name = algorithm_match(algorithm)
|
|
248
|
+
unless digest_name && algorithm_method(algorithm)
|
|
249
|
+
raise SecurityError,
|
|
250
|
+
"Certificate lineage uses unsupported signature algorithm #{algorithm_id}"
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
options = if algorithm_method(algorithm) == :rsa &&
|
|
254
|
+
[SIG_RSA_PSS_WITH_SHA256, SIG_RSA_PSS_WITH_SHA512].include?(algorithm)
|
|
255
|
+
{ rsa_padding_mode: :pss, rsa_pss_saltlen: OpenSSL::Digest.new(digest_name).digest_length }
|
|
256
|
+
else
|
|
257
|
+
{}
|
|
258
|
+
end
|
|
259
|
+
verified = certificate.public_key.verify(
|
|
260
|
+
OpenSSL::Digest.new(digest_name), signature, signed_data, **options
|
|
261
|
+
)
|
|
262
|
+
raise SecurityError, 'Certificate lineage signature did not verify' unless verified
|
|
263
|
+
rescue OpenSSL::PKey::PKeyError => e
|
|
264
|
+
raise SecurityError,
|
|
265
|
+
"Certificate lineage signature could not be verified: #{e.message}"
|
|
266
|
+
end
|
|
126
267
|
end
|
|
127
268
|
|
|
128
269
|
register(Version::V3, V3)
|
data/lib/app_info/dsym.rb
CHANGED
|
@@ -56,7 +56,9 @@ module AppInfo
|
|
|
56
56
|
|
|
57
57
|
dest_path = ::File.join(base_path, file_path)
|
|
58
58
|
FileUtils.mkdir_p(::File.dirname(dest_path))
|
|
59
|
-
|
|
59
|
+
next if ::File.exist?(dest_path)
|
|
60
|
+
|
|
61
|
+
entry.extract(file_path, destination_directory: base_path)
|
|
60
62
|
end
|
|
61
63
|
end
|
|
62
64
|
}.call
|
data/lib/app_info/hap.rb
CHANGED
|
@@ -42,6 +42,50 @@ module AppInfo
|
|
|
42
42
|
@module_info ||= JSON.parse(::File.read(module_info_path))
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
alias module_data module_info
|
|
46
|
+
|
|
47
|
+
# @return [Hash]
|
|
48
|
+
def app_info
|
|
49
|
+
module_info['app'] || {}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [Hash]
|
|
53
|
+
def module_metadata
|
|
54
|
+
module_info['module'] || {}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [String, nil]
|
|
58
|
+
def main_element
|
|
59
|
+
module_metadata['mainElement']
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @return [Integer, nil]
|
|
63
|
+
def min_api_version
|
|
64
|
+
app_info['minAPIVersion']
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @return [Integer, nil]
|
|
68
|
+
def target_api_version
|
|
69
|
+
app_info['targetAPIVersion']
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @return [String, nil]
|
|
73
|
+
def compile_sdk_version
|
|
74
|
+
app_info['compileSdkVersion']
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @return [String, nil]
|
|
78
|
+
def api_release_type
|
|
79
|
+
app_info['apiReleaseType']
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @return [Hash]
|
|
83
|
+
def profiles
|
|
84
|
+
@profiles ||= profile_paths.to_h do |path|
|
|
85
|
+
[::File.basename(path, '.json'), JSON.parse(::File.read(path))]
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
45
89
|
# @return [String]
|
|
46
90
|
def module_info_path
|
|
47
91
|
@module_info_path ||= ::File.join(contents, 'module.json')
|
|
@@ -49,12 +93,40 @@ module AppInfo
|
|
|
49
93
|
|
|
50
94
|
# @return [String]
|
|
51
95
|
def name
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
96
|
+
resource_value(app_info['label']) || pack_info.bundle_name
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def resource_value(reference)
|
|
102
|
+
return reference unless reference.is_a?(String) && reference.start_with?('$string:')
|
|
103
|
+
|
|
104
|
+
resource_strings[reference.delete_prefix('$string:')]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def resource_strings
|
|
108
|
+
@resource_strings ||= begin
|
|
109
|
+
values = ::File.binread(resources_index_path).split("\x00")
|
|
110
|
+
values.each_with_index.filter_map do |value, index|
|
|
111
|
+
next unless value.match?(/\A[ -~]+\z/)
|
|
112
|
+
|
|
113
|
+
key = value
|
|
114
|
+
value = values[0...index].reverse.find { |candidate| candidate.match?(/\A[ -~]+\z/) }
|
|
115
|
+
next unless value
|
|
116
|
+
|
|
117
|
+
[key, value.force_encoding(Encoding::UTF_8)]
|
|
118
|
+
end.to_h
|
|
119
|
+
rescue Errno::ENOENT
|
|
120
|
+
{}
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def resources_index_path
|
|
125
|
+
::File.join(contents, 'resources.index')
|
|
56
126
|
end
|
|
57
127
|
|
|
128
|
+
public
|
|
129
|
+
|
|
58
130
|
def clear!
|
|
59
131
|
return unless @contents
|
|
60
132
|
|
|
@@ -69,5 +141,9 @@ module AppInfo
|
|
|
69
141
|
@icons_path = nil
|
|
70
142
|
@icons = nil
|
|
71
143
|
end
|
|
144
|
+
|
|
145
|
+
def profile_paths
|
|
146
|
+
Dir.glob(::File.join(contents, 'resources', '**', 'profile', '*.json'))
|
|
147
|
+
end
|
|
72
148
|
end
|
|
73
149
|
end
|
data/lib/app_info/happ.rb
CHANGED
|
@@ -3,7 +3,20 @@
|
|
|
3
3
|
module AppInfo
|
|
4
4
|
# parser for HarmonyOS .APP file
|
|
5
5
|
class HAPP < HarmonyOS
|
|
6
|
-
def_delegators :default_entry, :icons
|
|
6
|
+
def_delegators :default_entry, :icons, :module_info, :app_info, :module_metadata,
|
|
7
|
+
:main_element, :min_api_version, :target_api_version, :compile_sdk_version,
|
|
8
|
+
:api_release_type, :profiles, :permissions, :use_permissions,
|
|
9
|
+
:features, :use_features, :device_types, :components, :activities,
|
|
10
|
+
:services, :schemes, :deep_links, :architectures, :native_codes
|
|
11
|
+
|
|
12
|
+
# @return [Array<HAP>]
|
|
13
|
+
def modules
|
|
14
|
+
@modules ||= pack_info.packages.filter_map do |package|
|
|
15
|
+
path = ::File.join(contents, "#{package['name']}.hap")
|
|
16
|
+
HAP.new(path) if ::File.file?(path)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
7
20
|
# @return [HAP]
|
|
8
21
|
def default_entry
|
|
9
22
|
hap_path = ::File.join(contents, "#{default_entry_name}.hap")
|
|
@@ -39,8 +52,10 @@ module AppInfo
|
|
|
39
52
|
|
|
40
53
|
@default_entry_name = nil
|
|
41
54
|
@default_entry&.clear!
|
|
55
|
+
@modules&.each(&:clear!)
|
|
42
56
|
|
|
43
57
|
@default_entry = nil
|
|
58
|
+
@modules = nil
|
|
44
59
|
end
|
|
45
60
|
end
|
|
46
61
|
end
|
data/lib/app_info/harmonyos.rb
CHANGED
|
@@ -9,6 +9,105 @@ module AppInfo
|
|
|
9
9
|
|
|
10
10
|
def_delegators :pack_info, :build_version, :release_version, :bundle_id
|
|
11
11
|
|
|
12
|
+
alias identifier bundle_id
|
|
13
|
+
alias bundle_name bundle_id
|
|
14
|
+
|
|
15
|
+
# @return [String, nil]
|
|
16
|
+
def version
|
|
17
|
+
release_version || build_version
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [String, nil]
|
|
21
|
+
def display_name
|
|
22
|
+
name
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @return [Array<String>]
|
|
26
|
+
def permissions
|
|
27
|
+
values = module_info.dig('module', 'requestPermissions')
|
|
28
|
+
values = module_info['requestPermissions'] if values.nil?
|
|
29
|
+
Array(values).filter_map { |permission| permission_value(permission) }
|
|
30
|
+
end
|
|
31
|
+
alias use_permissions permissions
|
|
32
|
+
|
|
33
|
+
# @return [Array<String>]
|
|
34
|
+
def features
|
|
35
|
+
Array(module_info.dig('module', 'deviceTypes'))
|
|
36
|
+
end
|
|
37
|
+
alias use_features features
|
|
38
|
+
|
|
39
|
+
# @return [Array<String>]
|
|
40
|
+
def device_types
|
|
41
|
+
features
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @return [Array<Hash>]
|
|
45
|
+
def components
|
|
46
|
+
abilities + extension_abilities
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [Array<Hash>]
|
|
50
|
+
def activities
|
|
51
|
+
abilities.reject { |ability| ability['type'] == 'service' }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [Array<Hash>]
|
|
55
|
+
def services
|
|
56
|
+
extension_abilities.select { |ability| ability['type'] == 'service' }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @return [Array<String>]
|
|
60
|
+
def schemes
|
|
61
|
+
skills.flat_map { |skill| Array(skill['uris']) }.filter_map { |uri| uri['scheme'] }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @return [Array<Hash>]
|
|
65
|
+
def deep_links
|
|
66
|
+
skills.flat_map { |skill| Array(skill['uris']) }
|
|
67
|
+
end
|
|
68
|
+
alias url_schemes schemes
|
|
69
|
+
|
|
70
|
+
# @return [Array<String>]
|
|
71
|
+
def architectures
|
|
72
|
+
native_codes
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @return [Array<String>]
|
|
76
|
+
def native_codes
|
|
77
|
+
Dir.glob(::File.join(contents, '**', 'libs', '*')).filter_map do |path|
|
|
78
|
+
::File.basename(path) if ::File.directory?(path)
|
|
79
|
+
end.uniq
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @return [Array<AppInfo::HAP>]
|
|
83
|
+
def modules
|
|
84
|
+
[self]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def phone?
|
|
88
|
+
device_types.include?('phone')
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def tablet?
|
|
92
|
+
device_types.include?('tablet')
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def tv?
|
|
96
|
+
device_types.include?('tv')
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def wearable?
|
|
100
|
+
device_types.any? { |type| %w[wearable watch].include?(type) }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def car?
|
|
104
|
+
device_types.include?('car')
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def two_in_one?
|
|
108
|
+
device_types.include?('2in1') || device_types.include?('2-in-1')
|
|
109
|
+
end
|
|
110
|
+
|
|
12
111
|
# return file size
|
|
13
112
|
# @example Read file size in integer
|
|
14
113
|
# aab.size # => 3618865
|
|
@@ -61,5 +160,29 @@ module AppInfo
|
|
|
61
160
|
def clear!
|
|
62
161
|
not_implemented_error!(__method__)
|
|
63
162
|
end
|
|
163
|
+
|
|
164
|
+
private
|
|
165
|
+
|
|
166
|
+
def module_info
|
|
167
|
+
return {} unless respond_to?(:module_data, true)
|
|
168
|
+
|
|
169
|
+
module_data
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def abilities
|
|
173
|
+
Array(module_info.dig('module', 'abilities'))
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def extension_abilities
|
|
177
|
+
Array(module_info.dig('module', 'extensionAbilities'))
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def skills
|
|
181
|
+
components.flat_map { |component| Array(component['skills']) }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def permission_value(permission)
|
|
185
|
+
permission.is_a?(Hash) ? permission['name'] : permission
|
|
186
|
+
end
|
|
64
187
|
end
|
|
65
188
|
end
|
|
@@ -19,7 +19,9 @@ module AppInfo::Helper
|
|
|
19
19
|
zip_file.each do |f|
|
|
20
20
|
f_path = ::File.join(base_path, f.name)
|
|
21
21
|
FileUtils.mkdir_p(::File.dirname(f_path))
|
|
22
|
-
|
|
22
|
+
next if ::File.exist?(f_path)
|
|
23
|
+
|
|
24
|
+
zip_file.extract(f, f.name, destination_directory: base_path)
|
|
23
25
|
end
|
|
24
26
|
end
|
|
25
27
|
end
|
|
@@ -104,6 +104,20 @@ module AppInfo::Helper
|
|
|
104
104
|
content_digests
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
+
def verify_content_digest(content_digests, digest_name)
|
|
108
|
+
expected = content_digests[digest_name]&.fetch(:content)
|
|
109
|
+
unless expected
|
|
110
|
+
raise AppInfo::Android::Signature::SecurityError,
|
|
111
|
+
'Signature algorithms don\'t match between digests and signatures records'
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
actual = AppInfo::Android::Signature.compute_content_digest(@parser, digest_name)
|
|
115
|
+
return if expected.string == actual
|
|
116
|
+
|
|
117
|
+
raise AppInfo::Android::Signature::SecurityError,
|
|
118
|
+
"#{digest_name} content digest did not verify"
|
|
119
|
+
end
|
|
120
|
+
|
|
107
121
|
# FIXME: this code not work, need fix.
|
|
108
122
|
def verify_additional_attrs(attrs, _certs)
|
|
109
123
|
loop_length_prefix_io(
|
|
@@ -166,12 +180,10 @@ module AppInfo::Helper
|
|
|
166
180
|
SIG_VERITY_DSA_WITH_SHA256 = [0x25, 0x04, 0x00, 0x00].freeze # 0x0425
|
|
167
181
|
|
|
168
182
|
SIG_STRIPPING_PROTECTION_ATTR_ID = [0x0d, 0xf0, 0xef, 0xbe].freeze # 0xbeeff00d
|
|
183
|
+
SIG_PROOF_OF_ROTATION_ATTR_ID = [0x8c, 0x6f, 0xa0, 0x3b].freeze # 0x3ba06f8c
|
|
169
184
|
|
|
170
185
|
def best_algorithem(algorithems)
|
|
171
|
-
|
|
172
|
-
best_method = methods.max { |a, b| algorithem_priority(a) <=> algorithem_priority(b) }
|
|
173
|
-
best_method_index = methods.index(best_method)
|
|
174
|
-
algorithems[best_method_index]
|
|
186
|
+
algorithems.max_by { |algorithem| algorithem_priority(algorithem[:id]) || -1 }
|
|
175
187
|
end
|
|
176
188
|
|
|
177
189
|
def compare_algorithem(source, target)
|
data/lib/app_info/pe.rb
CHANGED
|
@@ -119,8 +119,8 @@ module AppInfo
|
|
|
119
119
|
|
|
120
120
|
# @return [Hash{String => String}] imports imports of libraries
|
|
121
121
|
def imports
|
|
122
|
-
@imports ||= pe.imports.
|
|
123
|
-
|
|
122
|
+
@imports ||= pe.imports.to_h do |import|
|
|
123
|
+
[import.module_name, import.first_thunk.map(&:name).compact]
|
|
124
124
|
end
|
|
125
125
|
end
|
|
126
126
|
|
|
@@ -188,15 +188,20 @@ module AppInfo
|
|
|
188
188
|
# @return [String] binary_file path
|
|
189
189
|
def binary_file
|
|
190
190
|
@binary_file ||= lambda {
|
|
191
|
-
|
|
192
|
-
|
|
191
|
+
zip_file = ::File.open(@file, 'rb') do |file|
|
|
192
|
+
file.read(100) =~ Helper::FileTypeDetection::ZIP_RETGEX
|
|
193
|
+
end
|
|
194
|
+
return @file unless zip_file
|
|
193
195
|
|
|
194
196
|
zip_file = Zip::File.open(@file)
|
|
195
197
|
zip_entry = zip_file.glob('*.exe').first
|
|
196
198
|
raise NotFoundError, 'Not found .exe file in archive file' if zip_entry.nil?
|
|
197
199
|
|
|
198
200
|
exe_file = tempdir(zip_entry.name, prefix: 'pe-exe', system: true)
|
|
199
|
-
zip_entry.extract(
|
|
201
|
+
zip_entry.extract(
|
|
202
|
+
::File.basename(exe_file),
|
|
203
|
+
destination_directory: ::File.dirname(exe_file)
|
|
204
|
+
)
|
|
200
205
|
zip_file.close
|
|
201
206
|
|
|
202
207
|
exe_file
|
|
@@ -308,7 +313,7 @@ module AppInfo
|
|
|
308
313
|
@raw.each do |item|
|
|
309
314
|
next unless item.is_a?(PEdump::VS_VERSIONINFO)
|
|
310
315
|
|
|
311
|
-
versions = item[:Children].
|
|
316
|
+
versions = item[:Children].grep(PEdump::StringFileInfo)
|
|
312
317
|
next if versions.empty?
|
|
313
318
|
|
|
314
319
|
@info = versions[0][:Children][0][:Children]
|
data/lib/app_info/png_uncrush.rb
CHANGED
|
@@ -163,9 +163,9 @@ module AppInfo
|
|
|
163
163
|
newdata[pos] = data[pos, 1]
|
|
164
164
|
pos += 1
|
|
165
165
|
(0...width).each do |_|
|
|
166
|
-
newdata[pos
|
|
166
|
+
newdata[pos] = data[pos + 2, 1]
|
|
167
167
|
newdata[pos + 1] = data[pos + 1, 1]
|
|
168
|
-
newdata[pos + 2] = data[pos
|
|
168
|
+
newdata[pos + 2] = data[pos, 1]
|
|
169
169
|
newdata[pos + 3] = data[pos + 3, 1]
|
|
170
170
|
pos += 4
|
|
171
171
|
end
|
|
@@ -43,8 +43,8 @@ module AppInfo
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def define_packages(doc)
|
|
46
|
-
@packages = doc.package.
|
|
47
|
-
|
|
46
|
+
@packages = doc.package.to_h do |package|
|
|
47
|
+
[package.package_name, Resources::Package.new(package)]
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
|
|
@@ -158,7 +158,7 @@ module AppInfo
|
|
|
158
158
|
include Helper::Protobuf
|
|
159
159
|
extend Forwardable
|
|
160
160
|
|
|
161
|
-
attr_reader :
|
|
161
|
+
attr_reader :config, :original_value, :value, :type
|
|
162
162
|
|
|
163
163
|
def initialize(doc, package)
|
|
164
164
|
@package = package
|
data/lib/app_info/version.rb
CHANGED