Class: MooTool::Models::IMG4::File

Inherits:
Object
  • Object
show all
Includes:
Helpers::IMG4
Defined in:
lib/mootool/models/img4/file.rb

Overview

An IMG4 file, or, occasionally, a file read in from another source

Constant Summary collapse

DER_PAYLOADS =
%w[trst].freeze

Constants included from Helpers::IMG4

Helpers::IMG4::DECODE_TAGS, Helpers::IMG4::FIRMWARE_TAGS, Helpers::IMG4::HASH_LENGTHS, Helpers::IMG4::KEY_INSTANCE_TAGS, Helpers::IMG4::KVP_TAGS, Helpers::IMG4::OCTET_TAGS, Helpers::IMG4::SEQUENCE_TAGS, Helpers::IMG4::SIGNATURE_TAGS

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Helpers::IMG4

#construct, #construct_object, #decode_construct, parse_4cc

Constructor Details

#initialize(der, filename = nil) ⇒ File

Returns a new instance of File.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/mootool/models/img4/file.rb', line 59

def initialize(der, filename = nil)
  MooTool::Models::FileIndex.load '/Users/rickmark/Desktop/index.json'
  @filename = filename

  raw_data = der.is_a?(MooTool::Models::Digest) ? der.value : der

  @hashes = [MooTool::Models::Digest.create(::Digest::SHA384.digest(raw_data))]
  @data = OpenSSL::ASN1.decode(raw_data)
  @type = @data.value[0].value
  @content = {}

  case @type
  when 'IM4P'
    @content[:IM4P] = MooTool::Models::IMG4::IMG4Payload.new(@data)
  when 'IM4M'
    @content[:IM4M] = MooTool::Models::IMG4::IMG4Manifest.new(@data)
  when 'IMG4'
    @content[:IM4P] = MooTool::Models::IMG4::IMG4Payload.new(@data.value[1])
    @content[:IM4M] = MooTool::Models::IMG4::IMG4Manifest.new(@data.value[2])
  when 'secb'
    @value = construct(@data)
    @content[:secb] = @value.drop(1).map do |entry|
      case entry[0]
      when 'trst', 'rssl'
        { entry[0].to_sym => File.parse_certificates(entry.drop(1)) }
      when 'rvok'
        { entry[0].to_sym => entry[1] }
      when 'trpk'
        { entry[0].to_sym => entry.drop(1).map { |e| MooTool::Models::ECCPublicKey.new e } }
      end
    end.reduce(&:merge)
  when 'comb'
    @value = construct(@data)
    @content[:comb] = @value.drop(1).map do |entry|
      { entry[0] => File.new(entry[1]) }
    end.reduce(&:merge)
  else
    @content = @value.map(&:to_h).reduce(&:merge)
  end

  # @content[:validity] = validate_signature
end

Instance Attribute Details

#file_indexObject (readonly)

Returns the value of attribute file_index.



9
10
11
# File 'lib/mootool/models/img4/file.rb', line 9

def file_index
  @file_index
end

#manifestObject (readonly)

Returns the value of attribute manifest.



9
10
11
# File 'lib/mootool/models/img4/file.rb', line 9

def manifest
  @manifest
end

#payloadObject (readonly)

Returns the value of attribute payload.



9
10
11
# File 'lib/mootool/models/img4/file.rb', line 9

def payload
  @payload
end

Class Method Details

.load(path) ⇒ Object



15
16
17
18
19
20
21
22
23
24
# File 'lib/mootool/models/img4/file.rb', line 15

def self.load(path)
  case path
  when FileLocation
    data = ::File.binread(path.fullname)
    new(data, path.fullname)
  when String, Pathname
    data = ::File.binread(path)
    new(data, path)
  end
end

.parse_certificates(certificates) ⇒ Object



35
36
37
38
39
40
41
# File 'lib/mootool/models/img4/file.rb', line 35

def self.parse_certificates(certificates)
  certificates.map do |certificate|
    certificate_data = certificate.value if certificate.is_a?(Models::Digest)
    certificate_data = certificate.to_der if certificate.respond_to?(:to_der)
    Models::Certificate.new OpenSSL::X509::Certificate.new(certificate_data)
  end
end

.parse_signature(signature) ⇒ Object



26
27
28
29
30
31
32
33
# File 'lib/mootool/models/img4/file.rb', line 26

def self.parse_signature(signature)
  signature = signature.value if signature.is_a?(OpenSSL::ASN1::OctetString)
  if signature.size > 128
    Models::Digest.create(signature, 'RSASignature')
  else
    ::MooTool::Models::ECCSignature.create(signature)
  end
end

Instance Method Details

#basenameObject



179
180
181
182
183
# File 'lib/mootool/models/img4/file.rb', line 179

def basename
  basename = ::File.basename(@filename)
  extension = ::File.extname(basename)
  "#{basename.chomp(extension)}.#{@type}"
end

#extract_payloadObject



185
186
187
188
# File 'lib/mootool/models/img4/file.rb', line 185

def extract_payload
  output_path = ::File.join(::File.dirname(@filename), basename)
  ::File.write(output_path, @payload)
end

#hashesObject



190
191
192
193
194
195
196
197
198
199
200
# File 'lib/mootool/models/img4/file.rb', line 190

def hashes
  result = @hashes.dup

  result += @content[:comb].flat_map { |_k, v| v.hashes } if @content[:comb]

  result += @content[:IM4M].hashes if @content[:IM4M]

  result += @content[:IM4P].hashes if @content[:IM4P]

  result.map { |h| h.respond_to?(:value) ? h.value : h }.uniq.map { |h| Models::Digest.create(h) }
end

#manifest?Boolean

Returns:

  • (Boolean)


175
176
177
# File 'lib/mootool/models/img4/file.rb', line 175

def manifest?
  !@content[:IM4M].nil?
end

#manifest_typeObject



47
48
49
# File 'lib/mootool/models/img4/file.rb', line 47

def manifest_type
  @content[:IM4M]&.type&.to_sym
end

#parse_element(element) ⇒ Object



150
151
152
# File 'lib/mootool/models/img4/file.rb', line 150

def parse_element(element)
  { element.value[0].value.to_sym => element.value[1].value }
end

#parse_pair(input) ⇒ Object



154
155
156
157
158
# File 'lib/mootool/models/img4/file.rb', line 154

def parse_pair(input)
  input.value.to_a.each_slice(2).to_h do |key, value|
    [key.value, value]
  end
end

#payload?Boolean

Returns:

  • (Boolean)


171
172
173
# File 'lib/mootool/models/img4/file.rb', line 171

def payload?
  !@content[:IM4P].nil?
end

#payload_typeObject



43
44
45
# File 'lib/mootool/models/img4/file.rb', line 43

def payload_type
  @content[:IM4P]&.type&.to_sym
end


202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/mootool/models/img4/file.rb', line 202

def print(friendly)
  output = to_h
  if friendly
    mappings = IMG4.mappings

    output.deep_transform_keys! do |key|
      new_key = mappings.dig(key.to_s, 'title') || mappings.dig(key.to_s, 'description') || key
      new_key.respond_to?(:to_sym) ? new_key.to_sym : new_key
    end
  end

  ap(output)
end

#to_hObject



51
52
53
54
55
56
57
# File 'lib/mootool/models/img4/file.rb', line 51

def to_h
  content = @content.transform_values(&:inspect)

  content[:file_type] = @type
  content[:hashes] = hashes
  content
end

#typesObject



160
161
162
163
164
165
# File 'lib/mootool/models/img4/file.rb', line 160

def types
  result = [payload_type]
  result += @content[:comb].keys if @content[:comb]
  result += @content[:secb].keys if @content[:secb]
  result.compact.map(&:to_sym)
end

#validate_signatureObject



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/mootool/models/img4/file.rb', line 106

def validate_signature
  leaf_certificate = validted_certificate_chain
  [OpenSSL::Digest.new('SHA384'), OpenSSL::Digest.new('SHA256')].flat_map do |digest|
    values = %i[IM4M IM4P].map do |kind|
      @content[kind]&.to_bytes
    end.compact

    hashes = values.map do |value|
      OpenSSL::Digest::SHA384.digest(value)
    end.compact

    signatures = %i[IM4P IM4M].map do |kind|
      signature = @content[kind]&.signature
      signature.respond_to?(:value) ? signature.value : signature
    end.compact

    signatures.flat_map do |signature|
      value_result = values.map do |value|
        { digest.hexdigest(value) => leaf_certificate.public_key.verify(digest, signature, value) }
      end

      hashes_result = hashes.map do |hash|
        { digest.hexdigest(hash) => leaf_certificate.public_key.verify(digest, signature, hash) }
      end

      hash_hashes_result = hashes.map do |hash|
        { digest.hexdigest(hash) => leaf_certificate.public_key.verify(digest, signature, digest.digest(hash)) }
      end

      join_result = [
        { digest.hexdigest(hashes.join) => leaf_certificate.public_key.verify(digest, signature, hashes.join) }
      ]

      value_result + hashes_result + hash_hashes_result + join_result
    end
  end.reduce(&:merge)
end

#validted_certificate_chainObject



102
103
104
# File 'lib/mootool/models/img4/file.rb', line 102

def validted_certificate_chain
  @content[:IM4M].certificates.last
end