Class: IosBackupExtractor::Keybag

Inherits:
Object
  • Object
show all
Includes:
NauktisUtils::Logging
Defined in:
lib/ios_backup_extractor/keybag.rb

Constant Summary collapse

KEYBAG_TYPES =
['System', 'Backup', 'Escrow', 'OTA (icloud)']
CLASSKEY_TAGS =
%w(UUID CLAS WRAP WPKY KTYP PBKY)
WRAP_DEVICE =
1
WRAP_PASSCODE =
2

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data, version_major, version_minor) ⇒ Keybag

Returns a new instance of Keybag.



9
10
11
12
13
# File 'lib/ios_backup_extractor/keybag.rb', line 9

def initialize(data, version_major, version_minor)
  @version_major = version_major
  @version_minor = version_minor
  parse_binary_blob(data)
end

Class Method Details

.create_with_backup_manifest(manifest, password, version_major, version_minor) ⇒ Object

Creates a new Keybag



75
76
77
78
79
# File 'lib/ios_backup_extractor/keybag.rb', line 75

def self.create_with_backup_manifest(manifest, password, version_major, version_minor)
  kb = Keybag.new(manifest['BackupKeyBag'], version_major, version_minor)
  kb.unlock_backup_keybag_with_passcode(password)
  kb
end

Instance Method Details

#get_passcode_key_from_passcode(password) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/ios_backup_extractor/keybag.rb', line 58

def get_passcode_key_from_passcode(password)
  raise 'This is not a backup/icloud keybag' unless @type == 1 or @type == 3

  if @version_major == 10 && @version_minor < 2
    return OpenSSL::PKCS5.pbkdf2_hmac_sha1(password, @attributes['SALT'], @attributes['ITER'], 32)
  end

  # Version >= 10.2
  digest = OpenSSL::Digest::SHA256.new
  len = digest.digest_length
  kek = OpenSSL::PKCS5.pbkdf2_hmac(password, @attributes['DPSL'], @attributes['DPIC'], len, digest)
  OpenSSL::PKCS5.pbkdf2_hmac_sha1(kek, @attributes['SALT'], @attributes['ITER'], 32)
end

#parse_binary_blob(data) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/ios_backup_extractor/keybag.rb', line 15

def parse_binary_blob(data)
  @class_keys = {}
  @attributes = {}
  current_class = {}
  loop_tlv_blocks(data) do |tag, value|
    if value.size == 4
      value = value.unpack('L>')[0]
    end
    if tag == 'TYPE'
      @type = value & 0x3FFFFFFF # Ignore the flags
      raise "Error: Keybag type #{@type} > 3" if @type > 3
      logger.debug(self.class){"Keybag of type #{KEYBAG_TYPES[@type]}"}
    end
    @uuid = value if tag == 'UUID' and @uuid.nil?
    @wrap = value if tag == 'WRAP' and @wrap.nil?
    current_class = {} if tag == 'UUID' # New class starts by the UUID tag.
    current_class[tag] = value if CLASSKEY_TAGS.include?(tag)
    @class_keys[current_class['CLAS'] & 0xF] = current_class if current_class.has_key?('CLAS')
    @attributes[tag] = value
  end
end

Prints information about the Keybag



84
85
86
87
88
89
90
# File 'lib/ios_backup_extractor/keybag.rb', line 84

def print_info
  puts '== Keybag'
  puts "Keybag type: #{KEYBAG_TYPES[@type]} keybag (#{@type})"
  puts "Keybag version: #{@attributes['VERS']}"
  puts "Keybag iterations: #{@attributes['ITER']}, iv=#{@attributes['SALT'].unpack('H*')[0]}"
  puts "Keybag UUID: #{@uuid.unpack('H*')[0]}"
end

#unlock_backup_keybag_with_passcode(password) ⇒ Object



37
38
39
40
# File 'lib/ios_backup_extractor/keybag.rb', line 37

def unlock_backup_keybag_with_passcode(password)
  raise 'This is not a backup keybag' unless @type == 1 or @type == 2
  unwrap_class_keys(get_passcode_key_from_passcode(password))
end

#unwrap_class_keys(passcodekey) ⇒ Object



42
43
44
45
46
47
48
49
50
# File 'lib/ios_backup_extractor/keybag.rb', line 42

def unwrap_class_keys(passcodekey)
  @class_keys.each_value do |classkey|
    k = classkey['WPKY']
    if classkey['WRAP'] & WRAP_PASSCODE > 0
      k = AESKeyWrap.unwrap!(classkey['WPKY'].to_s, passcodekey)
      classkey['KEY'] = k
    end
  end
end

#unwrap_key_for_class(protection_class, persistent_key) ⇒ Object



52
53
54
55
56
# File 'lib/ios_backup_extractor/keybag.rb', line 52

def unwrap_key_for_class(protection_class, persistent_key)
  raise "Keybag key #{protection_class} missing or locked" unless @class_keys.has_key?(protection_class) and @class_keys[protection_class].has_key?('KEY')
  raise 'Invalid key length' unless persistent_key.length == 0x28
  AESKeyWrap.unwrap!(persistent_key, @class_keys[protection_class]['KEY'])
end