app-info 2.8.4 → 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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +12 -7
  3. data/.github/workflows/ci.yml +3 -1
  4. data/.rubocop.yml +31 -11
  5. data/CHANGELOG.md +30 -2
  6. data/Gemfile +7 -1
  7. data/README.md +74 -9
  8. data/Rakefile +11 -0
  9. data/app_info.gemspec +12 -3
  10. data/lib/app_info/aab.rb +58 -39
  11. data/lib/app_info/android/signature.rb +114 -0
  12. data/lib/app_info/android/signatures/base.rb +49 -0
  13. data/lib/app_info/android/signatures/info.rb +152 -0
  14. data/lib/app_info/android/signatures/v1.rb +59 -0
  15. data/lib/app_info/android/signatures/v2.rb +117 -0
  16. data/lib/app_info/android/signatures/v3.rb +127 -0
  17. data/lib/app_info/android/signatures/v4.rb +14 -0
  18. data/lib/app_info/apk.rb +43 -46
  19. data/lib/app_info/certificate.rb +181 -0
  20. data/lib/app_info/const.rb +41 -0
  21. data/lib/app_info/core_ext/object/try.rb +3 -1
  22. data/lib/app_info/core_ext/string/inflector.rb +2 -0
  23. data/lib/app_info/dsym/debug_info.rb +72 -0
  24. data/lib/app_info/dsym/macho.rb +55 -0
  25. data/lib/app_info/dsym.rb +27 -134
  26. data/lib/app_info/error.rb +8 -0
  27. data/lib/app_info/file.rb +23 -0
  28. data/lib/app_info/helper/archive.rb +37 -0
  29. data/lib/app_info/helper/file_size.rb +25 -0
  30. data/lib/app_info/helper/generate_class.rb +29 -0
  31. data/lib/app_info/helper/protobuf.rb +12 -0
  32. data/lib/app_info/helper/signatures.rb +229 -0
  33. data/lib/app_info/helper.rb +5 -126
  34. data/lib/app_info/info_plist.rb +14 -6
  35. data/lib/app_info/ipa/framework.rb +4 -4
  36. data/lib/app_info/ipa.rb +41 -36
  37. data/lib/app_info/macos.rb +34 -26
  38. data/lib/app_info/mobile_provision.rb +19 -30
  39. data/lib/app_info/pe.rb +226 -0
  40. data/lib/app_info/png_uncrush.rb +5 -4
  41. data/lib/app_info/proguard.rb +11 -17
  42. data/lib/app_info/protobuf/manifest.rb +16 -9
  43. data/lib/app_info/protobuf/models/Configuration_pb.rb +1 -0
  44. data/lib/app_info/protobuf/models/README.md +7 -0
  45. data/lib/app_info/protobuf/models/Resources_pb.rb +2 -0
  46. data/lib/app_info/protobuf/resources.rb +5 -5
  47. data/lib/app_info/version.rb +1 -1
  48. data/lib/app_info.rb +91 -42
  49. metadata +46 -35
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppInfo
4
+ module Android
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,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
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppInfo::Android::Signature
4
+ # Android v4 Signature
5
+ #
6
+ # TODO: ApkSignatureSchemeV4Verifier.java
7
+ class V4 < Base
8
+ def version
9
+ Version::V4
10
+ end
11
+ end
12
+
13
+ # register(Version::V4, V4)
14
+ end