app-info 2.8.5 → 3.0.0.beta1
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/dependabot.yml +12 -7
- data/.github/workflows/ci.yml +3 -1
- data/.rubocop.yml +31 -11
- data/CHANGELOG.md +22 -0
- data/Gemfile +7 -1
- data/README.md +64 -9
- data/Rakefile +11 -0
- data/app_info.gemspec +12 -3
- data/lib/app_info/aab.rb +58 -39
- data/lib/app_info/android/signature.rb +114 -0
- data/lib/app_info/android/signatures/base.rb +49 -0
- data/lib/app_info/android/signatures/info.rb +152 -0
- data/lib/app_info/android/signatures/v1.rb +59 -0
- data/lib/app_info/android/signatures/v2.rb +117 -0
- data/lib/app_info/android/signatures/v3.rb +127 -0
- data/lib/app_info/android/signatures/v4.rb +14 -0
- data/lib/app_info/apk.rb +43 -46
- data/lib/app_info/certificate.rb +181 -0
- data/lib/app_info/const.rb +41 -0
- data/lib/app_info/core_ext/object/try.rb +3 -1
- data/lib/app_info/core_ext/string/inflector.rb +2 -0
- data/lib/app_info/dsym/debug_info.rb +72 -0
- data/lib/app_info/dsym/macho.rb +55 -0
- data/lib/app_info/dsym.rb +27 -134
- data/lib/app_info/error.rb +7 -1
- data/lib/app_info/file.rb +23 -0
- data/lib/app_info/helper/archive.rb +37 -0
- data/lib/app_info/helper/file_size.rb +25 -0
- data/lib/app_info/helper/generate_class.rb +29 -0
- data/lib/app_info/helper/protobuf.rb +12 -0
- data/lib/app_info/helper/signatures.rb +229 -0
- data/lib/app_info/helper.rb +5 -126
- data/lib/app_info/info_plist.rb +14 -6
- data/lib/app_info/ipa/framework.rb +4 -4
- data/lib/app_info/ipa.rb +41 -36
- data/lib/app_info/macos.rb +34 -26
- data/lib/app_info/mobile_provision.rb +19 -30
- data/lib/app_info/pe.rb +226 -0
- data/lib/app_info/png_uncrush.rb +5 -4
- data/lib/app_info/proguard.rb +11 -17
- data/lib/app_info/protobuf/manifest.rb +1 -2
- data/lib/app_info/protobuf/models/Configuration_pb.rb +1 -0
- data/lib/app_info/protobuf/models/README.md +7 -0
- data/lib/app_info/protobuf/models/Resources_pb.rb +2 -0
- data/lib/app_info/protobuf/resources.rb +5 -5
- data/lib/app_info/version.rb +1 -1
- data/lib/app_info.rb +88 -45
- metadata +46 -35
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'app_info/android/signatures/info'
|
4
|
+
|
5
|
+
module AppInfo::Android::Signature
|
6
|
+
class Base
|
7
|
+
def self.verify(parser)
|
8
|
+
instance = new(parser)
|
9
|
+
instance.verify
|
10
|
+
instance
|
11
|
+
end
|
12
|
+
|
13
|
+
DESCRIPTION = 'APK Signature Scheme'
|
14
|
+
|
15
|
+
attr_reader :verified
|
16
|
+
|
17
|
+
def initialize(parser)
|
18
|
+
@parser = parser
|
19
|
+
@verified = false
|
20
|
+
end
|
21
|
+
|
22
|
+
# @abstract Subclass and override {#verify} to implement
|
23
|
+
def verify
|
24
|
+
raise NotImplementedError, ".#{__method__} method implantation required in #{self.class}"
|
25
|
+
end
|
26
|
+
|
27
|
+
# @abstract Subclass and override {#certificates} to implement
|
28
|
+
def certificates
|
29
|
+
raise NotImplementedError, ".#{__method__} method implantation required in #{self.class}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def scheme
|
33
|
+
"v#{version}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def description
|
37
|
+
"#{DESCRIPTION} #{scheme}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def logger
|
41
|
+
@parser.logger
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
require 'app_info/android/signatures/v1'
|
47
|
+
require 'app_info/android/signatures/v2'
|
48
|
+
require 'app_info/android/signatures/v3'
|
49
|
+
require 'app_info/android/signatures/v4'
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
require 'openssl'
|
5
|
+
|
6
|
+
module AppInfo::Android::Signature
|
7
|
+
# APK signature scheme signurate info
|
8
|
+
#
|
9
|
+
# FORMAT:
|
10
|
+
# OFFSET DATA TYPE DESCRIPTION
|
11
|
+
# * @+0 bytes uint64: size in bytes (excluding this field)
|
12
|
+
# * @+8 bytes payload
|
13
|
+
# * @-24 bytes uint64: size in bytes (same as the one above)
|
14
|
+
# * @-16 bytes uint128: magic value
|
15
|
+
class Info
|
16
|
+
include AppInfo::Helper::IOBlock
|
17
|
+
|
18
|
+
# Signature block information
|
19
|
+
SIG_SIZE_OF_BLOCK_SIZE = 8
|
20
|
+
SIG_MAGIC_BLOCK_SIZE = 16
|
21
|
+
SIG_BLOCK_MIN_SIZE = 32
|
22
|
+
|
23
|
+
# Magic value: APK Sig Block 42
|
24
|
+
SIG_MAGIC = [
|
25
|
+
0x41, 0x50, 0x4b, 0x20, 0x53, 0x69,
|
26
|
+
0x67, 0x20, 0x42, 0x6c, 0x6f, 0x63,
|
27
|
+
0x6b, 0x20, 0x34, 0x32
|
28
|
+
].freeze
|
29
|
+
|
30
|
+
attr_reader :total_size, :pairs, :magic, :logger
|
31
|
+
|
32
|
+
def initialize(version, parser, logger)
|
33
|
+
@version = version
|
34
|
+
@parser = parser
|
35
|
+
@logger = logger
|
36
|
+
|
37
|
+
pares_signatures_pairs
|
38
|
+
end
|
39
|
+
|
40
|
+
# Find singers
|
41
|
+
#
|
42
|
+
# FORMAT:
|
43
|
+
# OFFSET DATA TYPE DESCRIPTION
|
44
|
+
# * @+0 bytes uint64: size in bytes
|
45
|
+
# * @+8 bytes payload block
|
46
|
+
# * @+0 bytes uint32: id
|
47
|
+
# * @+4 bytes payload: value
|
48
|
+
def signers(block_id)
|
49
|
+
count = 0
|
50
|
+
until @pairs.eof?
|
51
|
+
left_bytes = left_bytes_check(
|
52
|
+
@pairs, UINT64_SIZE, NotFoundError,
|
53
|
+
"Insufficient data to read size of APK Signing Block ##{count}"
|
54
|
+
)
|
55
|
+
|
56
|
+
pair_buf = @pairs.read(UINT64_SIZE)
|
57
|
+
pair_size = pair_buf.unpack1('Q')
|
58
|
+
if pair_size < UINT32_SIZE || pair_size > UINT32_MAX_VALUE
|
59
|
+
raise NotFoundError,
|
60
|
+
"APK Signing Block ##{count} size out of range: #{pair_size} > #{UINT32_MAX_VALUE}"
|
61
|
+
end
|
62
|
+
|
63
|
+
if pair_size > left_bytes
|
64
|
+
raise NotFoundError,
|
65
|
+
"APK Signing Block ##{count} size out of range: #{pair_size} > #{left_bytes}"
|
66
|
+
end
|
67
|
+
|
68
|
+
# fetch next signer block position
|
69
|
+
next_pos = @pairs.pos + pair_size.to_i
|
70
|
+
|
71
|
+
id_block = @pairs.read(UINT32_SIZE)
|
72
|
+
id_bytes = id_block.unpack('C*')
|
73
|
+
if id_bytes == block_id
|
74
|
+
logger.debug "Signature block id v#{@version} scheme (0x#{id_block.unpack1('H*')}) found"
|
75
|
+
value = @pairs.read(pair_size - UINT32_SIZE)
|
76
|
+
return StringIO.new(value)
|
77
|
+
end
|
78
|
+
|
79
|
+
@pairs.seek(next_pos)
|
80
|
+
count += 1
|
81
|
+
end
|
82
|
+
|
83
|
+
block_id_hex = block_id.reverse.pack('C*').unpack1('H*')
|
84
|
+
raise NotFoundError, "Not found block id 0x#{block_id_hex} in APK Signing Block."
|
85
|
+
end
|
86
|
+
|
87
|
+
def zip64?
|
88
|
+
zip_io.zip64_file?(start_buffer)
|
89
|
+
end
|
90
|
+
|
91
|
+
def pares_signatures_pairs
|
92
|
+
block = signature_block
|
93
|
+
block.rewind
|
94
|
+
# get pairs size
|
95
|
+
@total_size = block.size - (SIG_SIZE_OF_BLOCK_SIZE + SIG_MAGIC_BLOCK_SIZE)
|
96
|
+
|
97
|
+
# get pairs block
|
98
|
+
@pairs = StringIO.new(block.read(@total_size))
|
99
|
+
|
100
|
+
# get magic value
|
101
|
+
block.seek(block.pos + SIG_SIZE_OF_BLOCK_SIZE)
|
102
|
+
@magic = block.read(SIG_MAGIC_BLOCK_SIZE)
|
103
|
+
end
|
104
|
+
|
105
|
+
def signature_block
|
106
|
+
@signature_block ||= lambda {
|
107
|
+
logger.debug "cdir_offset: #{cdir_offset}"
|
108
|
+
|
109
|
+
file_io.seek(cdir_offset - (Info::SIG_MAGIC_BLOCK_SIZE + Info::SIG_SIZE_OF_BLOCK_SIZE))
|
110
|
+
footer_block = file_io.read(Info::SIG_SIZE_OF_BLOCK_SIZE)
|
111
|
+
if footer_block.size < Info::SIG_SIZE_OF_BLOCK_SIZE
|
112
|
+
raise NotFoundError, "APK Signing Block size out of range: #{footer_block.size}"
|
113
|
+
end
|
114
|
+
|
115
|
+
footer = footer_block.unpack1('Q')
|
116
|
+
total_size = footer
|
117
|
+
offset = cdir_offset - total_size - Info::SIG_SIZE_OF_BLOCK_SIZE
|
118
|
+
raise NotFoundError, "APK Signing Block offset out of range: #{offset}" if offset.negative?
|
119
|
+
|
120
|
+
file_io.seek(offset)
|
121
|
+
header = file_io.read(Info::SIG_SIZE_OF_BLOCK_SIZE).unpack1('Q')
|
122
|
+
|
123
|
+
if header != footer
|
124
|
+
raise NotFoundError,
|
125
|
+
"APK Signing Block header and footer mismatch: #{header} != #{footer}"
|
126
|
+
end
|
127
|
+
|
128
|
+
io = file_io.read(total_size)
|
129
|
+
StringIO.new(io)
|
130
|
+
}.call
|
131
|
+
end
|
132
|
+
|
133
|
+
def cdir_offset
|
134
|
+
@cdir_offset ||= lambda {
|
135
|
+
eocd_buffer = zip_io.get_e_o_c_d(start_buffer)
|
136
|
+
eocd_buffer[12..16].unpack1('V')
|
137
|
+
}.call
|
138
|
+
end
|
139
|
+
|
140
|
+
def start_buffer
|
141
|
+
@start_buffer ||= zip_io.start_buf(file_io)
|
142
|
+
end
|
143
|
+
|
144
|
+
def zip_io
|
145
|
+
@zip_io ||= @parser.zip
|
146
|
+
end
|
147
|
+
|
148
|
+
def file_io
|
149
|
+
@file_io ||= File.open(@parser.file, 'rb')
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppInfo::Android::Signature
|
4
|
+
# Android v1 Signature
|
5
|
+
class V1 < Base
|
6
|
+
DESCRIPTION = 'JAR signing'
|
7
|
+
|
8
|
+
PKCS7_HEADER = [0x30, 0x82].freeze
|
9
|
+
|
10
|
+
attr_reader :certificates, :signatures
|
11
|
+
|
12
|
+
def version
|
13
|
+
Version::V1
|
14
|
+
end
|
15
|
+
|
16
|
+
def description
|
17
|
+
DESCRIPTION
|
18
|
+
end
|
19
|
+
|
20
|
+
def verify
|
21
|
+
@signatures = fetch_signatures
|
22
|
+
@certificates = fetch_certificates
|
23
|
+
|
24
|
+
raise NotFoundError, 'Not found certificates' if @certificates.empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def fetch_signatures
|
30
|
+
case @parser
|
31
|
+
when AppInfo::APK
|
32
|
+
signatures_from(@parser.apk)
|
33
|
+
when AppInfo::AAB
|
34
|
+
signatures_from(@parser)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def fetch_certificates
|
39
|
+
@signatures.each_with_object([]) do |(_, sign), obj|
|
40
|
+
next if sign.certificates.empty?
|
41
|
+
|
42
|
+
obj << AppInfo::Certificate.new(sign.certificates[0])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def signatures_from(parser)
|
47
|
+
signs = {}
|
48
|
+
parser.each_file do |path, data|
|
49
|
+
# find META-INF/xxx.{RSA|DSA|EC}
|
50
|
+
next unless path =~ %r{^META-INF/} && data.unpack('CC') == PKCS7_HEADER
|
51
|
+
|
52
|
+
signs[path] = OpenSSL::PKCS7.new(data)
|
53
|
+
end
|
54
|
+
signs
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
register(Version::V1, V1)
|
59
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppInfo::Android::Signature
|
4
|
+
# Android v2 Signature
|
5
|
+
#
|
6
|
+
# FULL FORMAT:
|
7
|
+
# OFFSET DATA TYPE DESCRIPTION
|
8
|
+
# * @+0 bytes uint32: signer size in bytes
|
9
|
+
# * @+4 bytes payload signer block
|
10
|
+
# * @+0 bytes unit32: signed data size in bytes
|
11
|
+
# * @+4 bytes payload signed data block
|
12
|
+
# * @+0 bytes unit32: digests with size in bytes
|
13
|
+
# * @+0 bytes unit32: digests with size in bytes
|
14
|
+
# * @+X bytes unit32: signatures with size in bytes
|
15
|
+
# * @+X+4 bytes payload signed data block
|
16
|
+
# * @+Y bytes unit32: public key with size in bytes
|
17
|
+
# * @+Y+4 bytes payload signed data block
|
18
|
+
class V2 < Base
|
19
|
+
include AppInfo::Helper::IOBlock
|
20
|
+
include AppInfo::Helper::Signatures
|
21
|
+
include AppInfo::Helper::Algorithm
|
22
|
+
|
23
|
+
# V2 Signature ID 0x7109871a
|
24
|
+
BLOCK_ID = [0x1a, 0x87, 0x09, 0x71].freeze
|
25
|
+
|
26
|
+
attr_reader :certificates, :digests
|
27
|
+
|
28
|
+
def version
|
29
|
+
Version::V2
|
30
|
+
end
|
31
|
+
|
32
|
+
# Verify
|
33
|
+
# @todo verified signatures
|
34
|
+
def verify
|
35
|
+
signers_block = singers_block(BLOCK_ID)
|
36
|
+
@certificates, @digests = verified_certs(signers_block, verify: true)
|
37
|
+
# @verified = true
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def verified_certs(signers_block, verify:)
|
43
|
+
unless (signers = length_prefix_block(signers_block))
|
44
|
+
raise SecurityError, 'Not found signers'
|
45
|
+
end
|
46
|
+
|
47
|
+
certificates = []
|
48
|
+
content_digests = {}
|
49
|
+
loop_length_prefix_io(signers, name: 'Singer', logger: logger) do |signer|
|
50
|
+
signer_certs, signer_digests = extract_signer_data(signer, verify: verify)
|
51
|
+
certificates.concat(signer_certs)
|
52
|
+
content_digests.merge!(signer_digests)
|
53
|
+
end
|
54
|
+
raise SecurityError, 'No signers found' if certificates.empty?
|
55
|
+
|
56
|
+
[certificates, content_digests]
|
57
|
+
end
|
58
|
+
|
59
|
+
def extract_signer_data(signer, verify:)
|
60
|
+
# raw data
|
61
|
+
signed_data = length_prefix_block(signer)
|
62
|
+
signatures = length_prefix_block(signer)
|
63
|
+
public_key = length_prefix_block(signer, raw: true)
|
64
|
+
|
65
|
+
# FIXME: extract code below and re-organizate
|
66
|
+
|
67
|
+
algorithems = signature_algorithms(signatures)
|
68
|
+
raise SecurityError, 'No signatures found' if verify && algorithems.empty?
|
69
|
+
|
70
|
+
# find best algorithem to verify signed data with public key and signature
|
71
|
+
unless best_algorithem = best_algorithem(algorithems)
|
72
|
+
raise SecurityError, 'No supported signatures found'
|
73
|
+
end
|
74
|
+
|
75
|
+
algorithems_digest = best_algorithem[:digest]
|
76
|
+
signature = best_algorithem[:signature]
|
77
|
+
|
78
|
+
pkey = OpenSSL::PKey.read(public_key)
|
79
|
+
digest = OpenSSL::Digest.new(algorithems_digest)
|
80
|
+
verified = pkey.verify(digest, signature, signed_data.string)
|
81
|
+
raise SecurityError, "#{algorithems_digest} signature did not verify" unless verified
|
82
|
+
|
83
|
+
# verify algorithm ID full equal (and sort) between digests and signature
|
84
|
+
digests = length_prefix_block(signed_data)
|
85
|
+
content_digests = signed_data_digests(digests)
|
86
|
+
content_digest = content_digests[algorithems_digest]&.fetch(:content)
|
87
|
+
|
88
|
+
unless content_digest
|
89
|
+
raise SecurityError,
|
90
|
+
'Signature algorithms don\'t match between digests and signatures records'
|
91
|
+
end
|
92
|
+
|
93
|
+
previous_digest = content_digests.fetch(algorithems_digest)
|
94
|
+
content_digests[algorithems_digest] = content_digest
|
95
|
+
if previous_digest && previous_digest[:content] != content_digest
|
96
|
+
raise SecurityError,
|
97
|
+
'Signature algorithms don\'t match between digests and signatures records'
|
98
|
+
end
|
99
|
+
|
100
|
+
certificates = length_prefix_block(signed_data)
|
101
|
+
certs = signed_data_certs(certificates)
|
102
|
+
raise SecurityError, 'No certificates listed' if certs.empty?
|
103
|
+
|
104
|
+
main_cert = certs[0]
|
105
|
+
if main_cert.public_key.to_der != pkey.to_der
|
106
|
+
raise SecurityError, 'Public key mismatch between certificate and signature record'
|
107
|
+
end
|
108
|
+
|
109
|
+
additional_attrs = length_prefix_block(signed_data)
|
110
|
+
verify_additional_attrs(additional_attrs, certs)
|
111
|
+
|
112
|
+
[certs, content_digests]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
register(Version::V2, V2)
|
117
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppInfo::Android::Signature
|
4
|
+
# Android v3 Signature
|
5
|
+
#
|
6
|
+
# FULL FORMAT:
|
7
|
+
# OFFSET DATA TYPE DESCRIPTION
|
8
|
+
# * @+0 bytes uint32: signer size in bytes
|
9
|
+
# * @+4 bytes payload signer block
|
10
|
+
# * @+0 bytes unit32: signed data size in bytes
|
11
|
+
# * @+4 bytes payload signed data block
|
12
|
+
# * @+0 bytes unit32: digests with size in bytes
|
13
|
+
# * @+0 bytes unit32: digests with size in bytes
|
14
|
+
# * @+W bytes unit32: minSDK
|
15
|
+
# * @+X+4 bytes unit32: maxSDK
|
16
|
+
# * @+Y+4 bytes unit32: signatures with size in bytes
|
17
|
+
# * @+Y+4 bytes payload signed data block
|
18
|
+
# * @+Z bytes unit32: public key with size in bytes
|
19
|
+
# * @+Z+4 bytes payload signed data block
|
20
|
+
class V3 < Base
|
21
|
+
include AppInfo::Helper::IOBlock
|
22
|
+
include AppInfo::Helper::Signatures
|
23
|
+
include AppInfo::Helper::Algorithm
|
24
|
+
|
25
|
+
# V3 Signature ID 0xf05368c0
|
26
|
+
V3_BLOCK_ID = [0xc0, 0x68, 0x53, 0xf0].freeze
|
27
|
+
|
28
|
+
# V3.1 Signature ID 0x1b93ad61
|
29
|
+
V3_1_BLOCK_ID = [0x61, 0xad, 0x93, 0x1b].freeze
|
30
|
+
|
31
|
+
attr_reader :certificates, :digests
|
32
|
+
|
33
|
+
def version
|
34
|
+
Version::V3
|
35
|
+
end
|
36
|
+
|
37
|
+
def verify
|
38
|
+
begin
|
39
|
+
signers_block = singers_block(V3_1_BLOCK_ID)
|
40
|
+
rescue NotFoundError
|
41
|
+
signers_block = singers_block(V3_BLOCK_ID)
|
42
|
+
end
|
43
|
+
|
44
|
+
@certificates, @digests = verified_certs(signers_block)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def verified_certs(signers_block)
|
50
|
+
unless (signers = length_prefix_block(signers_block))
|
51
|
+
raise SecurityError, 'Not found signers'
|
52
|
+
end
|
53
|
+
|
54
|
+
certificates = []
|
55
|
+
content_digests = {}
|
56
|
+
loop_length_prefix_io(signers, name: 'Singer', logger: logger) do |signer|
|
57
|
+
signer_certs, signer_digests = extract_signer_data(signer)
|
58
|
+
certificates.concat(signer_certs)
|
59
|
+
content_digests.merge!(signer_digests)
|
60
|
+
end
|
61
|
+
raise SecurityError, 'No signers found' if certificates.empty?
|
62
|
+
|
63
|
+
[certificates, content_digests]
|
64
|
+
end
|
65
|
+
|
66
|
+
def extract_signer_data(signer)
|
67
|
+
# raw data
|
68
|
+
signed_data = length_prefix_block(signer)
|
69
|
+
|
70
|
+
# TODO: verify min_sdk and max_sdk
|
71
|
+
min_sdk = signer.read(UINT32_SIZE)
|
72
|
+
max_sdk = signer.read(UINT32_SIZE)
|
73
|
+
|
74
|
+
signatures = length_prefix_block(signer)
|
75
|
+
public_key = length_prefix_block(signer, raw: true)
|
76
|
+
|
77
|
+
algorithems = signature_algorithms(signatures)
|
78
|
+
raise SecurityError, 'No signatures found' if algorithems.empty?
|
79
|
+
|
80
|
+
# find best algorithem to verify signed data with public key and signature
|
81
|
+
unless best_algorithem = best_algorithem(algorithems)
|
82
|
+
raise SecurityError, 'No supported signatures found'
|
83
|
+
end
|
84
|
+
|
85
|
+
algorithems_digest = best_algorithem[:digest]
|
86
|
+
signature = best_algorithem[:signature]
|
87
|
+
|
88
|
+
pkey = OpenSSL::PKey.read(public_key)
|
89
|
+
digest = OpenSSL::Digest.new(algorithems_digest)
|
90
|
+
verified = pkey.verify(digest, signature, signed_data.string)
|
91
|
+
raise SecurityError, "#{algorithems_digest} signature did not verify" unless verified
|
92
|
+
|
93
|
+
# verify algorithm ID full equal (and sort) between digests and signature
|
94
|
+
digests = length_prefix_block(signed_data)
|
95
|
+
content_digests = signed_data_digests(digests)
|
96
|
+
content_digest = content_digests[algorithems_digest]&.fetch(:content)
|
97
|
+
|
98
|
+
unless content_digest
|
99
|
+
raise SecurityError,
|
100
|
+
'Signature algorithms don\'t match between digests and signatures records'
|
101
|
+
end
|
102
|
+
|
103
|
+
previous_digest = content_digests.fetch(algorithems_digest)
|
104
|
+
content_digests[algorithems_digest] = content_digest
|
105
|
+
if previous_digest && previous_digest[:content] != content_digest
|
106
|
+
raise SecurityError,
|
107
|
+
'Signature algorithms don\'t match between digests and signatures records'
|
108
|
+
end
|
109
|
+
|
110
|
+
certificates = length_prefix_block(signed_data)
|
111
|
+
certs = signed_data_certs(certificates)
|
112
|
+
raise SecurityError, 'No certificates listed' if certs.empty?
|
113
|
+
|
114
|
+
main_cert = certs[0]
|
115
|
+
if main_cert.public_key.to_der != pkey.to_der
|
116
|
+
raise SecurityError, 'Public key mismatch between certificate and signature record'
|
117
|
+
end
|
118
|
+
|
119
|
+
additional_attrs = length_prefix_block(signed_data)
|
120
|
+
verify_additional_attrs(additional_attrs, certs)
|
121
|
+
|
122
|
+
[certs, content_digests]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
register(Version::V3, V3)
|
127
|
+
end
|
data/lib/app_info/apk.rb
CHANGED
@@ -5,8 +5,8 @@ require 'image_size'
|
|
5
5
|
require 'forwardable'
|
6
6
|
|
7
7
|
module AppInfo
|
8
|
-
# Parse APK file
|
9
|
-
class APK
|
8
|
+
# Parse APK file parser, wrapper for {https://github.com/icyleaf/android_parser android_parser}.
|
9
|
+
class APK < File
|
10
10
|
include Helper::HumanFileSize
|
11
11
|
extend Forwardable
|
12
12
|
|
@@ -21,18 +21,26 @@ module AppInfo
|
|
21
21
|
AUTOMOTIVE = 'Automotive'
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
# return file size
|
25
|
+
# @example Read file size in integer
|
26
|
+
# aab.size # => 3618865
|
27
|
+
#
|
28
|
+
# @example Read file size in human readabale
|
29
|
+
# aab.size(human_size: true) # => '3.45 MB'
|
30
|
+
#
|
31
|
+
# @param [Boolean] human_size Convert integer value to human readable.
|
32
|
+
# @return [Integer, String]
|
28
33
|
def size(human_size: false)
|
29
34
|
file_to_human_size(@file, human_size: human_size)
|
30
35
|
end
|
31
36
|
|
32
|
-
def
|
37
|
+
def file_type
|
38
|
+
Format::APK
|
39
|
+
end
|
40
|
+
|
41
|
+
def platform
|
33
42
|
Platform::ANDROID
|
34
43
|
end
|
35
|
-
alias file_type os
|
36
44
|
|
37
45
|
def_delegators :apk, :manifest, :resource, :dex
|
38
46
|
|
@@ -86,28 +94,25 @@ module AppInfo
|
|
86
94
|
end
|
87
95
|
alias min_os_version min_sdk_version
|
88
96
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
# 'v2'
|
95
|
-
# when ?
|
96
|
-
# https://source.android.com/security/apksigning/v3?hl=zh-cn
|
97
|
-
# 'v3'
|
98
|
-
'unknown'
|
97
|
+
# Return multi version certifiates of signatures
|
98
|
+
# @return [Array<Hash>]
|
99
|
+
# @see AppInfo::Android::Signature.verify
|
100
|
+
def signatures
|
101
|
+
@signatures ||= Android::Signature.verify(self)
|
99
102
|
end
|
100
103
|
|
104
|
+
# Legacy v1 scheme signatures, it will remove soon.
|
105
|
+
# @deprecated Use {#signatures}
|
106
|
+
# @return [Array<OpenSSL::PKCS7, nil>]
|
101
107
|
def signs
|
102
|
-
|
103
|
-
obj << Sign.new(path, sign)
|
104
|
-
end
|
108
|
+
@signs ||= v1sign&.signatures || []
|
105
109
|
end
|
106
110
|
|
111
|
+
# Legacy v1 scheme certificates, it will remove soon.
|
112
|
+
# @deprecated Use {#signatures}
|
113
|
+
# @return [Array<OpenSSL::PKCS7, nil>]
|
107
114
|
def certificates
|
108
|
-
|
109
|
-
obj << Certificate.new(path, certificate)
|
110
|
-
end
|
115
|
+
@certificates ||= v1sign&.certificates || []
|
111
116
|
end
|
112
117
|
|
113
118
|
def activities
|
@@ -118,13 +123,17 @@ module AppInfo
|
|
118
123
|
@apk ||= ::Android::Apk.new(@file)
|
119
124
|
end
|
120
125
|
|
126
|
+
def zip
|
127
|
+
@zip ||= apk.instance_variable_get(:@zip)
|
128
|
+
end
|
129
|
+
|
121
130
|
def icons
|
122
131
|
@icons ||= apk.icon.each_with_object([]) do |(path, data), obj|
|
123
|
-
icon_name = File.basename(path)
|
124
|
-
icon_path = File.join(contents, File.dirname(path))
|
125
|
-
icon_file = File.join(icon_path, icon_name)
|
132
|
+
icon_name = ::File.basename(path)
|
133
|
+
icon_path = ::File.join(contents, ::File.dirname(path))
|
134
|
+
icon_file = ::File.join(icon_path, icon_name)
|
126
135
|
FileUtils.mkdir_p icon_path
|
127
|
-
File.write(icon_file, data, encoding: Encoding::BINARY)
|
136
|
+
::File.write(icon_file, data, encoding: Encoding::BINARY)
|
128
137
|
|
129
138
|
obj << {
|
130
139
|
name: icon_name,
|
@@ -147,27 +156,15 @@ module AppInfo
|
|
147
156
|
end
|
148
157
|
|
149
158
|
def contents
|
150
|
-
@contents ||= File.join(Dir.mktmpdir, "AppInfo-android-#{SecureRandom.hex}")
|
159
|
+
@contents ||= ::File.join(Dir.mktmpdir, "AppInfo-android-#{SecureRandom.hex}")
|
151
160
|
end
|
152
161
|
|
153
|
-
|
154
|
-
class Certificate
|
155
|
-
attr_reader :path, :certificate
|
162
|
+
private
|
156
163
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
end
|
162
|
-
|
163
|
-
# Android Sign
|
164
|
-
class Sign
|
165
|
-
attr_reader :path, :sign
|
166
|
-
|
167
|
-
def initialize(path, sign)
|
168
|
-
@path = path
|
169
|
-
@sign = sign
|
170
|
-
end
|
164
|
+
def v1sign
|
165
|
+
@v1sign ||= Android::Signature::V1.verify(self)
|
166
|
+
rescue Android::Signature::NotFoundError
|
167
|
+
nil
|
171
168
|
end
|
172
169
|
end
|
173
170
|
end
|