app-info 3.0.0.beta1 → 3.0.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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