app-info 3.0.0.beta1 → 3.0.0.beta3

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.
@@ -3,150 +3,156 @@
3
3
  require 'stringio'
4
4
  require 'openssl'
5
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}"
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
61
40
  end
62
41
 
63
- if pair_size > left_bytes
64
- raise NotFoundError,
65
- "APK Signing Block ##{count} size out of range: #{pair_size} > #{left_bytes}"
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."
66
87
  end
67
88
 
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)
89
+ def zip64?
90
+ zip_io.zip64_file?(start_buffer)
77
91
  end
78
92
 
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
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)
104
98
 
105
- def signature_block
106
- @signature_block ||= lambda {
107
- logger.debug "cdir_offset: #{cdir_offset}"
99
+ # get pairs block
100
+ @pairs = StringIO.new(block.read(@total_size))
108
101
 
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}"
102
+ # get magic value
103
+ block.seek(block.pos + SIG_SIZE_OF_BLOCK_SIZE)
104
+ @magic = block.read(SIG_MAGIC_BLOCK_SIZE)
113
105
  end
114
106
 
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}"
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
126
135
  end
127
136
 
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
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
139
143
 
140
- def start_buffer
141
- @start_buffer ||= zip_io.start_buf(file_io)
142
- end
144
+ def start_buffer
145
+ @start_buffer ||= zip_io.start_buf(file_io)
146
+ end
143
147
 
144
- def zip_io
145
- @zip_io ||= @parser.zip
146
- end
148
+ def zip_io
149
+ @zip_io ||= @parser.zip
150
+ end
147
151
 
148
- def file_io
149
- @file_io ||= File.open(@parser.file, 'rb')
152
+ def file_io
153
+ @file_io ||= ::File.open(@parser.file, 'rb')
154
+ end
155
+ end
150
156
  end
151
157
  end
152
158
  end
@@ -1,59 +1,63 @@
1
1
  # frozen_string_literal: true
2
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)
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
35
58
  end
36
- end
37
-
38
- def fetch_certificates
39
- @signatures.each_with_object([]) do |(_, sign), obj|
40
- next if sign.certificates.empty?
41
59
 
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
60
+ register(Version::V1, V1)
55
61
  end
56
62
  end
57
-
58
- register(Version::V1, V1)
59
63
  end
@@ -1,117 +1,121 @@
1
1
  # frozen_string_literal: true
2
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'
3
+ module AppInfo
4
+ class Android < File
5
+ module Signature
6
+ # Android v2 Signature
7
+ #
8
+ # FULL FORMAT:
9
+ # OFFSET DATA TYPE DESCRIPTION
10
+ # * @+0 bytes uint32: signer size in bytes
11
+ # * @+4 bytes payload signer block
12
+ # * @+0 bytes unit32: signed data size in bytes
13
+ # * @+4 bytes payload signed data block
14
+ # * @+0 bytes unit32: digests with size in bytes
15
+ # * @+0 bytes unit32: digests with size in bytes
16
+ # * @+X bytes unit32: signatures with size in bytes
17
+ # * @+X+4 bytes payload signed data block
18
+ # * @+Y bytes unit32: public key with size in bytes
19
+ # * @+Y+4 bytes payload signed data block
20
+ class V2 < Base
21
+ include AppInfo::Helper::IOBlock
22
+ include AppInfo::Helper::Signatures
23
+ include AppInfo::Helper::Algorithm
24
+
25
+ # V2 Signature ID 0x7109871a
26
+ BLOCK_ID = [0x1a, 0x87, 0x09, 0x71].freeze
27
+
28
+ attr_reader :certificates, :digests
29
+
30
+ def version
31
+ Version::V2
32
+ end
33
+
34
+ # Verify
35
+ # @todo verified signatures
36
+ def verify
37
+ signers_block = singers_block(BLOCK_ID)
38
+ @certificates, @digests = verified_certs(signers_block, verify: true)
39
+ # @verified = true
40
+ end
41
+
42
+ private
43
+
44
+ def verified_certs(signers_block, verify:)
45
+ unless (signers = length_prefix_block(signers_block))
46
+ raise SecurityError, 'Not found signers'
47
+ end
48
+
49
+ certificates = []
50
+ content_digests = {}
51
+ loop_length_prefix_io(signers, name: 'Singer', logger: logger) do |signer|
52
+ signer_certs, signer_digests = extract_signer_data(signer, verify: verify)
53
+ certificates.concat(signer_certs)
54
+ content_digests.merge!(signer_digests)
55
+ end
56
+ raise SecurityError, 'No signers found' if certificates.empty?
57
+
58
+ [certificates, content_digests]
59
+ end
60
+
61
+ def extract_signer_data(signer, verify:)
62
+ # raw data
63
+ signed_data = length_prefix_block(signer)
64
+ signatures = length_prefix_block(signer)
65
+ public_key = length_prefix_block(signer, raw: true)
66
+
67
+ # FIXME: extract code below and re-organize
68
+
69
+ algorithems = signature_algorithms(signatures)
70
+ raise SecurityError, 'No signatures found' if verify && algorithems.empty?
71
+
72
+ # find best algorithem to verify signed data with public key and signature
73
+ unless best_algorithem = best_algorithem(algorithems)
74
+ raise SecurityError, 'No supported signatures found'
75
+ end
76
+
77
+ algorithems_digest = best_algorithem[:digest]
78
+ signature = best_algorithem[:signature]
79
+
80
+ pkey = OpenSSL::PKey.read(public_key)
81
+ digest = OpenSSL::Digest.new(algorithems_digest)
82
+ verified = pkey.verify(digest, signature, signed_data.string)
83
+ raise SecurityError, "#{algorithems_digest} signature did not verify" unless verified
84
+
85
+ # verify algorithm ID full equal (and sort) between digests and signature
86
+ digests = length_prefix_block(signed_data)
87
+ content_digests = signed_data_digests(digests)
88
+ content_digest = content_digests[algorithems_digest]&.fetch(:content)
89
+
90
+ unless content_digest
91
+ raise SecurityError,
92
+ 'Signature algorithms don\'t match between digests and signatures records'
93
+ end
94
+
95
+ previous_digest = content_digests.fetch(algorithems_digest)
96
+ content_digests[algorithems_digest] = content_digest
97
+ if previous_digest && previous_digest[:content] != content_digest
98
+ raise SecurityError,
99
+ 'Signature algorithms don\'t match between digests and signatures records'
100
+ end
101
+
102
+ certificates = length_prefix_block(signed_data)
103
+ certs = signed_data_certs(certificates)
104
+ raise SecurityError, 'No certificates listed' if certs.empty?
105
+
106
+ main_cert = certs[0]
107
+ if main_cert.public_key.to_der != pkey.to_der
108
+ raise SecurityError, 'Public key mismatch between certificate and signature record'
109
+ end
110
+
111
+ additional_attrs = length_prefix_block(signed_data)
112
+ verify_additional_attrs(additional_attrs, certs)
113
+
114
+ [certs, content_digests]
115
+ end
73
116
  end
74
117
 
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]
118
+ register(Version::V2, V2)
113
119
  end
114
120
  end
115
-
116
- register(Version::V2, V2)
117
121
  end