app-info 2.8.2 → 3.0.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/dependabot.yml +12 -7
- data/.github/workflows/ci.yml +7 -5
- data/.github/workflows/create_release.yml +15 -0
- data/.rubocop.yml +33 -11
- data/CHANGELOG.md +107 -1
- data/Gemfile +10 -5
- data/README.md +82 -15
- data/Rakefile +11 -0
- data/app_info.gemspec +14 -5
- data/lib/app_info/aab.rb +76 -110
- data/lib/app_info/android/signature.rb +114 -0
- data/lib/app_info/android/signatures/base.rb +53 -0
- data/lib/app_info/android/signatures/info.rb +158 -0
- data/lib/app_info/android/signatures/v1.rb +63 -0
- data/lib/app_info/android/signatures/v2.rb +121 -0
- data/lib/app_info/android/signatures/v3.rb +131 -0
- data/lib/app_info/android/signatures/v4.rb +18 -0
- data/lib/app_info/android.rb +181 -0
- data/lib/app_info/apk.rb +77 -112
- data/lib/app_info/apple.rb +192 -0
- data/lib/app_info/certificate.rb +176 -0
- data/lib/app_info/const.rb +76 -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 +81 -0
- data/lib/app_info/dsym/macho.rb +62 -0
- data/lib/app_info/dsym.rb +35 -135
- data/lib/app_info/error.rb +3 -1
- data/lib/app_info/file.rb +49 -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 -128
- data/lib/app_info/info_plist.rb +66 -29
- data/lib/app_info/ipa/framework.rb +4 -4
- data/lib/app_info/ipa.rb +61 -135
- data/lib/app_info/macos.rb +54 -102
- data/lib/app_info/mobile_provision.rb +66 -48
- data/lib/app_info/pe.rb +322 -0
- data/lib/app_info/png_uncrush.rb +25 -5
- data/lib/app_info/proguard.rb +39 -22
- data/lib/app_info/protobuf/manifest.rb +22 -11
- data/lib/app_info/protobuf/models/Configuration_pb.rb +1 -0
- data/lib/app_info/protobuf/models/README.md +8 -1
- data/lib/app_info/protobuf/models/Resources.proto +51 -0
- data/lib/app_info/protobuf/models/Resources_pb.rb +42 -0
- data/lib/app_info/protobuf/resources.rb +5 -5
- data/lib/app_info/version.rb +1 -1
- data/lib/app_info.rb +93 -43
- metadata +57 -37
data/lib/app_info/dsym.rb
CHANGED
@@ -1,79 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'app_info/dsym/debug_info'
|
4
4
|
|
5
5
|
module AppInfo
|
6
6
|
# DSYM parser
|
7
|
-
class DSYM
|
7
|
+
class DSYM < File
|
8
8
|
include Helper::Archive
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
@file = file
|
14
|
-
end
|
15
|
-
|
16
|
-
def file_type
|
17
|
-
Platform::DSYM
|
18
|
-
end
|
19
|
-
|
20
|
-
def object
|
21
|
-
@object ||= File.basename(app_path)
|
22
|
-
end
|
23
|
-
|
24
|
-
def macho_type
|
25
|
-
@macho_type ||= ::MachO.open(app_path)
|
26
|
-
end
|
27
|
-
|
28
|
-
def machos
|
29
|
-
@machos ||= case macho_type
|
30
|
-
when ::MachO::MachOFile
|
31
|
-
[MachO.new(macho_type, File.size(app_path))]
|
32
|
-
else
|
33
|
-
size = macho_type.fat_archs.each_with_object([]) do |arch, obj|
|
34
|
-
obj << arch.size
|
35
|
-
end
|
36
|
-
|
37
|
-
machos = []
|
38
|
-
macho_type.machos.each_with_index do |file, i|
|
39
|
-
machos << MachO.new(file, size[i])
|
40
|
-
end
|
41
|
-
machos
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def release_version
|
46
|
-
info.try(:[], 'CFBundleShortVersionString')
|
10
|
+
# @return [Symbol] {Manufacturer}
|
11
|
+
def manufacturer
|
12
|
+
Manufacturer::APPLE
|
47
13
|
end
|
48
14
|
|
49
|
-
|
50
|
-
|
15
|
+
# @return [nil]
|
16
|
+
def each_file(&block)
|
17
|
+
files.each { |file| block.call(file) }
|
51
18
|
end
|
19
|
+
alias each_objects each_file
|
52
20
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
def info
|
59
|
-
return nil unless File.exist?(info_path)
|
60
|
-
|
61
|
-
@info ||= CFPropertyList.native_types(CFPropertyList::List.new(file: info_path).value)
|
62
|
-
end
|
63
|
-
|
64
|
-
def info_path
|
65
|
-
@info_path ||= File.join(contents, 'Contents', 'Info.plist')
|
66
|
-
end
|
67
|
-
|
68
|
-
def app_path
|
69
|
-
unless @app_path
|
70
|
-
path = File.join(contents, 'Contents', 'Resources', 'DWARF')
|
71
|
-
name = Dir.entries(path).reject { |f| ['.', '..'].include?(f) }.first
|
72
|
-
@app_path = File.join(path, name)
|
21
|
+
# @return [Array<DebugInfo>] dsym_files files by alphabetical order
|
22
|
+
def files
|
23
|
+
@files ||= Dir.children(contents).sort.each_with_object([]) do |file, obj|
|
24
|
+
obj << DebugInfo.new(::File.join(contents, file))
|
73
25
|
end
|
74
|
-
|
75
|
-
@app_path
|
76
26
|
end
|
27
|
+
alias objects files
|
77
28
|
|
78
29
|
def clear!
|
79
30
|
return unless @contents
|
@@ -81,85 +32,34 @@ module AppInfo
|
|
81
32
|
FileUtils.rm_rf(@contents)
|
82
33
|
|
83
34
|
@contents = nil
|
84
|
-
@
|
85
|
-
@info = nil
|
86
|
-
@object = nil
|
87
|
-
@macho_type = nil
|
35
|
+
@files = nil
|
88
36
|
end
|
89
37
|
|
38
|
+
# @return [String] contents path of dsym
|
90
39
|
def contents
|
91
|
-
|
92
|
-
if File.directory?(@file)
|
93
|
-
@contents = @file
|
94
|
-
else
|
95
|
-
dsym_dir = nil
|
96
|
-
@contents = unarchive(@file, path: 'dsym') do |path, zip_file|
|
97
|
-
zip_file.each do |f|
|
98
|
-
unless dsym_dir
|
99
|
-
dsym_dir = f.name
|
100
|
-
# fix filename is xxx.app.dSYM/Contents
|
101
|
-
dsym_dir = dsym_dir.split('/')[0] if dsym_dir.include?('/')
|
102
|
-
end
|
40
|
+
@contents ||= lambda {
|
41
|
+
return @file if ::File.directory?(@file)
|
103
42
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
43
|
+
dsym_filenames = []
|
44
|
+
unarchive(@file, prefix: 'dsym') do |base_path, zip_file|
|
45
|
+
zip_file.each do |entry|
|
46
|
+
file_path = entry.name
|
47
|
+
next unless file_path.downcase.include?('.dsym/contents/')
|
48
|
+
next if ::File.basename(file_path).start_with?('.')
|
109
49
|
|
110
|
-
|
111
|
-
|
112
|
-
end
|
113
|
-
|
114
|
-
@contents
|
115
|
-
end
|
116
|
-
|
117
|
-
# DSYM Mach-O
|
118
|
-
class MachO
|
119
|
-
include Helper::HumanFileSize
|
120
|
-
|
121
|
-
def initialize(file, size = 0)
|
122
|
-
@file = file
|
123
|
-
@size = size
|
124
|
-
end
|
125
|
-
|
126
|
-
def cpu_name
|
127
|
-
@file.cpusubtype
|
128
|
-
end
|
129
|
-
|
130
|
-
def cpu_type
|
131
|
-
@file.cputype
|
132
|
-
end
|
133
|
-
|
134
|
-
def type
|
135
|
-
@file.filetype
|
136
|
-
end
|
137
|
-
|
138
|
-
def size(human_size: false)
|
139
|
-
return number_to_human_size(@size) if human_size
|
140
|
-
|
141
|
-
@size
|
142
|
-
end
|
143
|
-
|
144
|
-
def uuid
|
145
|
-
@file[:LC_UUID][0].uuid_string
|
146
|
-
end
|
147
|
-
alias debug_id uuid
|
50
|
+
dsym_filename = file_path.split('/').select { |f| f.downcase.end_with?('.dsym') }.last
|
51
|
+
dsym_filenames << dsym_filename unless dsym_filenames.include?(dsym_filename)
|
148
52
|
|
149
|
-
|
150
|
-
|
151
|
-
|
53
|
+
unless file_path.start_with?(dsym_filename)
|
54
|
+
file_path = file_path.split('/')[1..-1].join('/')
|
55
|
+
end
|
152
56
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
size: size,
|
160
|
-
human_size: size(human_size: true)
|
161
|
-
}
|
162
|
-
end
|
57
|
+
dest_path = ::File.join(base_path, file_path)
|
58
|
+
FileUtils.mkdir_p(::File.dirname(dest_path))
|
59
|
+
entry.extract(dest_path) unless ::File.exist?(dest_path)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
}.call
|
163
63
|
end
|
164
64
|
end
|
165
65
|
end
|
data/lib/app_info/error.rb
CHANGED
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# AppInfo base file
|
4
|
+
module AppInfo
|
5
|
+
class File
|
6
|
+
attr_reader :file, :logger
|
7
|
+
|
8
|
+
def initialize(file, logger: AppInfo.logger)
|
9
|
+
@file = file
|
10
|
+
@logger = logger
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Symbol] {Format}
|
14
|
+
def format
|
15
|
+
@format ||= lambda {
|
16
|
+
if instance_of?(AppInfo::File) || instance_of?(AppInfo::Apple) ||
|
17
|
+
instance_of?(AppInfo::Android)
|
18
|
+
not_implemented_error!(__method__)
|
19
|
+
end
|
20
|
+
|
21
|
+
self.class.name.split('::')[-1].downcase.to_sym
|
22
|
+
}.call
|
23
|
+
end
|
24
|
+
|
25
|
+
# @abstract Subclass and override {#platform} to implement.
|
26
|
+
def platform
|
27
|
+
not_implemented_error!(__method__)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @abstract Subclass and override {#manufacturer} to implement.
|
31
|
+
def manufacturer
|
32
|
+
not_implemented_error!(__method__)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @abstract Subclass and override {#device} to implement.
|
36
|
+
def device
|
37
|
+
not_implemented_error!(__method__)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @abstract Subclass and override {#size} to implement
|
41
|
+
def size(human_size: false)
|
42
|
+
not_implemented_error!(__method__)
|
43
|
+
end
|
44
|
+
|
45
|
+
def not_implemented_error!(method)
|
46
|
+
raise NotImplementedError, ".#{method} method implantation required in #{self.class}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zip'
|
4
|
+
require 'tmpdir'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'securerandom'
|
7
|
+
|
8
|
+
module AppInfo::Helper
|
9
|
+
module Archive
|
10
|
+
# Unarchive zip file
|
11
|
+
#
|
12
|
+
# source: https://github.com/soffes/lagunitas/blob/master/lib/lagunitas/ipa.rb
|
13
|
+
def unarchive(file, prefix:, dest_path: '/tmp')
|
14
|
+
base_path = Dir.mktmpdir("appinfo-#{prefix}", dest_path)
|
15
|
+
Zip::File.open(file) do |zip_file|
|
16
|
+
if block_given?
|
17
|
+
yield base_path, zip_file
|
18
|
+
else
|
19
|
+
zip_file.each do |f|
|
20
|
+
f_path = ::File.join(base_path, f.name)
|
21
|
+
FileUtils.mkdir_p(::File.dirname(f_path))
|
22
|
+
zip_file.extract(f, f_path) unless ::File.exist?(f_path)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
base_path
|
28
|
+
end
|
29
|
+
|
30
|
+
def tempdir(file, prefix:, system: false)
|
31
|
+
base_path = system ? '/tmp' : ::File.dirname(file)
|
32
|
+
full_prefix = "appinfo-#{prefix}-#{::File.basename(file, '.*')}"
|
33
|
+
dest_path = Dir.mktmpdir(full_prefix, base_path)
|
34
|
+
::File.join(dest_path, ::File.basename(file))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppInfo::Helper
|
4
|
+
module HumanFileSize
|
5
|
+
def file_to_human_size(file, human_size:)
|
6
|
+
number = ::File.size(file)
|
7
|
+
human_size ? number_to_human_size(number) : number
|
8
|
+
end
|
9
|
+
|
10
|
+
FILE_SIZE_UNITS = %w[B KB MB GB TB].freeze
|
11
|
+
|
12
|
+
def number_to_human_size(number)
|
13
|
+
if number.to_i < 1024
|
14
|
+
exponent = 0
|
15
|
+
else
|
16
|
+
max_exp = FILE_SIZE_UNITS.size - 1
|
17
|
+
exponent = (Math.log(number) / Math.log(1024)).to_i
|
18
|
+
exponent = max_exp if exponent > max_exp
|
19
|
+
number = Kernel.format('%<number>.2f', number: (number / (1024**exponent.to_f)))
|
20
|
+
end
|
21
|
+
|
22
|
+
"#{number} #{FILE_SIZE_UNITS[exponent]}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppInfo::Helper
|
4
|
+
module GenerateClass
|
5
|
+
def create_class(klass_name, parent_class, namespace:)
|
6
|
+
klass = Class.new(parent_class) do
|
7
|
+
yield if block_given?
|
8
|
+
end
|
9
|
+
|
10
|
+
name = namespace.to_s.empty? ? klass_name : "#{namespace}::#{klass_name}"
|
11
|
+
if Object.const_get(namespace).const_defined?(klass_name)
|
12
|
+
Object.const_get(namespace).const_get(klass_name)
|
13
|
+
elsif Object.const_defined?(name)
|
14
|
+
Object.const_get(name)
|
15
|
+
else
|
16
|
+
Object.const_get(namespace).const_set(klass_name, klass)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def define_instance_method(key, value)
|
21
|
+
instance_variable_set("@#{key}", value)
|
22
|
+
self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
23
|
+
def #{key}
|
24
|
+
@#{key}
|
25
|
+
end
|
26
|
+
RUBY
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppInfo::Helper
|
4
|
+
module Protobuf
|
5
|
+
def reference_segments(value)
|
6
|
+
new_value = value.is_a?(Aapt::Pb::Reference) ? value.name : value
|
7
|
+
return new_value.split('/', 2) if new_value.include?('/')
|
8
|
+
|
9
|
+
[nil, new_value]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppInfo::Helper
|
4
|
+
# Binary IO Block Helper
|
5
|
+
module IOBlock
|
6
|
+
def length_prefix_block(
|
7
|
+
io, size: AppInfo::Android::Signature::UINT32_SIZE,
|
8
|
+
raw: false, ignore_left_size_precheck: false
|
9
|
+
)
|
10
|
+
offset = io.size - io.pos
|
11
|
+
if offset < AppInfo::Android::Signature::UINT32_SIZE
|
12
|
+
raise SecurityError,
|
13
|
+
'Remaining buffer too short to contain length of length-prefixed field.'
|
14
|
+
end
|
15
|
+
|
16
|
+
size = io.read(size).unpack1('I')
|
17
|
+
raise SecurityError, 'Negative length' if size.negative?
|
18
|
+
|
19
|
+
if !ignore_left_size_precheck && size > io.size
|
20
|
+
message = "Underflow while reading length-prefixed value. #{size} > #{io.size}"
|
21
|
+
raise SecurityError, message
|
22
|
+
end
|
23
|
+
|
24
|
+
raw_data = io.read(size)
|
25
|
+
raw ? raw_data : StringIO.new(raw_data)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Only use for uint32 length-prefixed block
|
29
|
+
def loop_length_prefix_io(
|
30
|
+
io, name:, max_bytes: nil, logger: nil, raw: false,
|
31
|
+
ignore_left_size_precheck: false, &block
|
32
|
+
)
|
33
|
+
index = 0
|
34
|
+
until io.eof?
|
35
|
+
logger&.debug "#{name} count ##{index}"
|
36
|
+
buffer = length_prefix_block(
|
37
|
+
io,
|
38
|
+
raw: raw,
|
39
|
+
ignore_left_size_precheck: ignore_left_size_precheck
|
40
|
+
)
|
41
|
+
|
42
|
+
left_bytes_check(buffer, max_bytes, SecurityError) do |left_bytes|
|
43
|
+
"#{name} too short: #{left_bytes} < #{max_bytes}"
|
44
|
+
end
|
45
|
+
|
46
|
+
block.call(buffer)
|
47
|
+
index += 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def left_bytes_check(io, max_bytes, exception, message = nil, &block)
|
52
|
+
return if max_bytes.nil?
|
53
|
+
|
54
|
+
left_bytes = io.size - io.pos
|
55
|
+
return left_bytes if left_bytes.zero?
|
56
|
+
|
57
|
+
message ||= if block_given?
|
58
|
+
block.call(left_bytes)
|
59
|
+
else
|
60
|
+
"IO too short: #{offset} < #{max_bytes}"
|
61
|
+
end
|
62
|
+
|
63
|
+
raise exception, message if left_bytes < max_bytes
|
64
|
+
|
65
|
+
left_bytes
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Signature Block helper
|
70
|
+
module Signatures
|
71
|
+
def singers_block(block_id)
|
72
|
+
info = AppInfo::Android::Signature::Info.new(@version, @parser, logger)
|
73
|
+
raise SecurityError, 'ZIP64 APK not supported' if info.zip64?
|
74
|
+
|
75
|
+
info.signers(block_id)
|
76
|
+
end
|
77
|
+
|
78
|
+
def signed_data_certs(io)
|
79
|
+
certificates = []
|
80
|
+
loop_length_prefix_io(io, name: 'Certificates', raw: true) do |cert_data|
|
81
|
+
certificates << AppInfo::Certificate.parse(cert_data)
|
82
|
+
end
|
83
|
+
certificates
|
84
|
+
end
|
85
|
+
|
86
|
+
def signed_data_digests(io)
|
87
|
+
content_digests = {}
|
88
|
+
loop_length_prefix_io(
|
89
|
+
io,
|
90
|
+
name: 'Digests',
|
91
|
+
max_bytes: AppInfo::Android::Signature::UINT64_SIZE
|
92
|
+
) do |digest|
|
93
|
+
algorithm = digest.read(AppInfo::Android::Signature::UINT32_SIZE).unpack('C*')
|
94
|
+
digest_name = algorithm_match(algorithm)
|
95
|
+
next unless digest_name
|
96
|
+
|
97
|
+
content = length_prefix_block(digest)
|
98
|
+
content_digests[digest_name] = {
|
99
|
+
id: algorithm,
|
100
|
+
content: content
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
content_digests
|
105
|
+
end
|
106
|
+
|
107
|
+
# FIXME: this code not work, need fix.
|
108
|
+
def verify_additional_attrs(attrs, _certs)
|
109
|
+
loop_length_prefix_io(
|
110
|
+
attrs, name: 'Additional Attributes', ignore_left_size_precheck: true
|
111
|
+
) do |attr|
|
112
|
+
id = attr.read(AppInfo::Android::Signature::UINT32_SIZE)
|
113
|
+
logger.debug "ID #{id} / #{id.size} / #{id.unpack('H*')} / #{id.unpack('C*')}"
|
114
|
+
if id.unpack('C*') == AppInfo::Helper::Algorithm::SIG_STRIPPING_PROTECTION_ATTR_ID
|
115
|
+
offset = attr.size - attr.pos
|
116
|
+
if offset < AppInfo::Android::Signature::UINT32_SIZE
|
117
|
+
raise SecurityError,
|
118
|
+
"V2 Signature Scheme Stripping Protection Attribute value too small. Expected #{UINT32_SIZE} bytes, but found #{offset}"
|
119
|
+
end
|
120
|
+
|
121
|
+
# value = attr.read(UINT32_SIZE).unpack1('I')
|
122
|
+
if @version == AppInfo::Android::Signature::Version::V3
|
123
|
+
raise SecurityError,
|
124
|
+
'V2 signature indicates APK is signed using APK Signature Scheme v3, but none was found. Signature stripped?'
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def signature_algorithms(signatures)
|
131
|
+
algorithems = []
|
132
|
+
loop_length_prefix_io(
|
133
|
+
signatures,
|
134
|
+
name: 'Signature Algorithms',
|
135
|
+
max_bytes: AppInfo::Android::Signature::UINT64_SIZE,
|
136
|
+
logger: logger
|
137
|
+
) do |signature|
|
138
|
+
algorithm = signature.read(AppInfo::Android::Signature::UINT32_SIZE).unpack('C*')
|
139
|
+
digest = algorithm_match(algorithm)
|
140
|
+
next unless digest
|
141
|
+
|
142
|
+
signature = length_prefix_block(signature, raw: true)
|
143
|
+
algorithems << {
|
144
|
+
id: algorithm,
|
145
|
+
digest: digest,
|
146
|
+
signature: signature
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
algorithems
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Signature Algorithm helper
|
155
|
+
module Algorithm
|
156
|
+
# Signature certificate identifiers
|
157
|
+
SIG_RSA_PSS_WITH_SHA256 = [0x01, 0x01, 0x00, 0x00].freeze # 0x0101
|
158
|
+
SIG_RSA_PSS_WITH_SHA512 = [0x02, 0x01, 0x00, 0x00].freeze # 0x0102
|
159
|
+
SIG_RSA_PKCS1_V1_5_WITH_SHA256 = [0x03, 0x01, 0x00, 0x00].freeze # 0x0103
|
160
|
+
SIG_RSA_PKCS1_V1_5_WITH_SHA512 = [0x04, 0x01, 0x00, 0x00].freeze # 0x0104
|
161
|
+
SIG_ECDSA_WITH_SHA256 = [0x01, 0x02, 0x00, 0x00].freeze # 0x0201
|
162
|
+
SIG_ECDSA_WITH_SHA512 = [0x02, 0x02, 0x00, 0x00].freeze # 0x0202
|
163
|
+
SIG_DSA_WITH_SHA256 = [0x01, 0x03, 0x00, 0x00].freeze # 0x0301
|
164
|
+
SIG_VERITY_RSA_PKCS1_V1_5_WITH_SHA256 = [0x21, 0x04, 0x00, 0x00].freeze # 0x0421
|
165
|
+
SIG_VERITY_ECDSA_WITH_SHA256 = [0x23, 0x04, 0x00, 0x00].freeze # 0x0423
|
166
|
+
SIG_VERITY_DSA_WITH_SHA256 = [0x25, 0x04, 0x00, 0x00].freeze # 0x0425
|
167
|
+
|
168
|
+
SIG_STRIPPING_PROTECTION_ATTR_ID = [0x0d, 0xf0, 0xef, 0xbe].freeze # 0xbeeff00d
|
169
|
+
|
170
|
+
def best_algorithem(algorithems)
|
171
|
+
methods = algorithems.map { |algorithem| algorithem[:method] }
|
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]
|
175
|
+
end
|
176
|
+
|
177
|
+
def compare_algorithem(source, target)
|
178
|
+
case algorithem_priority(source) <=> algorithem_priority(target)
|
179
|
+
when -1
|
180
|
+
target
|
181
|
+
else
|
182
|
+
source
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def algorithem_priority(algorithm)
|
187
|
+
case algorithm
|
188
|
+
when SIG_RSA_PSS_WITH_SHA256,
|
189
|
+
SIG_RSA_PKCS1_V1_5_WITH_SHA256,
|
190
|
+
SIG_ECDSA_WITH_SHA256,
|
191
|
+
SIG_DSA_WITH_SHA256
|
192
|
+
1
|
193
|
+
when SIG_RSA_PSS_WITH_SHA512,
|
194
|
+
SIG_RSA_PKCS1_V1_5_WITH_SHA512,
|
195
|
+
SIG_ECDSA_WITH_SHA512
|
196
|
+
2
|
197
|
+
when SIG_VERITY_RSA_PKCS1_V1_5_WITH_SHA256,
|
198
|
+
SIG_VERITY_ECDSA_WITH_SHA256,
|
199
|
+
SIG_VERITY_DSA_WITH_SHA256
|
200
|
+
3
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def algorithm_method(algorithm)
|
205
|
+
case algorithm
|
206
|
+
when SIG_RSA_PSS_WITH_SHA256, SIG_RSA_PSS_WITH_SHA512,
|
207
|
+
SIG_RSA_PKCS1_V1_5_WITH_SHA256, SIG_RSA_PKCS1_V1_5_WITH_SHA512,
|
208
|
+
SIG_VERITY_RSA_PKCS1_V1_5_WITH_SHA256
|
209
|
+
:rsa
|
210
|
+
when SIG_ECDSA_WITH_SHA256, SIG_ECDSA_WITH_SHA512, SIG_VERITY_ECDSA_WITH_SHA256
|
211
|
+
:ec
|
212
|
+
when SIG_DSA_WITH_SHA256, SIG_VERITY_DSA_WITH_SHA256
|
213
|
+
:dsa
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def algorithm_match(algorithm)
|
218
|
+
case algorithm
|
219
|
+
when SIG_RSA_PSS_WITH_SHA256, SIG_RSA_PKCS1_V1_5_WITH_SHA256,
|
220
|
+
SIG_ECDSA_WITH_SHA256, SIG_DSA_WITH_SHA256,
|
221
|
+
SIG_VERITY_RSA_PKCS1_V1_5_WITH_SHA256, SIG_VERITY_ECDSA_WITH_SHA256,
|
222
|
+
SIG_VERITY_DSA_WITH_SHA256
|
223
|
+
'SHA256'
|
224
|
+
when SIG_RSA_PSS_WITH_SHA512, SIG_RSA_PKCS1_V1_5_WITH_SHA512, SIG_ECDSA_WITH_SHA512
|
225
|
+
'SHA512'
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|