from crypto.PBKDF2 import PBKDF2 from crypto.aes import AESdecryptCBC from crypto.aeswrap import AESUnwrap from crypto.aeswrap import AESwrap from crypto.curve25519 import curve25519 from hashlib import sha256, sha1 from util.bplist import BPlistReader from util.tlv import loopTLVBlocks, tlvToDict import hmac import struct KEYBAG_TAGS = ["VERS", "TYPE", "UUID", "HMCK", "WRAP", "SALT", "ITER"] CLASSKEY_TAGS = ["CLAS","WRAP","WPKY", "KTYP", "PBKY"] #UUID KEYBAG_TYPES = ["System", "Backup", "Escrow", "OTA (icloud)"] SYSTEM_KEYBAG = 0 BACKUP_KEYBAG = 1 ESCROW_KEYBAG = 2 OTA_KEYBAG = 3 #ORed flags in TYPE since iOS 5 FLAG_UIDPLUS = 0x40000000 # UIDPlus hardware key (>= iPad 3) FLAG_UNKNOWN = 0x80000000 WRAP_DEVICE = 1 WRAP_PASSCODE = 2 KEY_TYPES = ["AES", "Curve25519"] PROTECTION_CLASSES={ 1:"NSFileProtectionComplete", 2:"NSFileProtectionCompleteUnlessOpen", 3:"NSFileProtectionCompleteUntilFirstUserAuthentication", 4:"NSFileProtectionNone", 5:"NSFileProtectionRecovery?", 6: "kSecAttrAccessibleWhenUnlocked", 7: "kSecAttrAccessibleAfterFirstUnlock", 8: "kSecAttrAccessibleAlways", 9: "kSecAttrAccessibleWhenUnlockedThisDeviceOnly", 10: "kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly", 11: "kSecAttrAccessibleAlwaysThisDeviceOnly" } """ device key : key 0x835 """ class Keybag(object): def __init__(self, data): self.type = None self.uuid = None self.wrap = None self.deviceKey = None self.unlocked = False self.passcodeComplexity = 0 self.attrs = {} self.classKeys = {} self.KeyBagKeys = None #DATASIGN blob self.parseBinaryBlob(data) @staticmethod def getSystemkbfileWipeID(filename): mkb = BPlistReader.plistWithFile(filename) return mkb["_MKBWIPEID"] @staticmethod def createWithPlist(pldict): k835 = pldict.key835.decode("hex") data = "" if pldict.has_key("KeyBagKeys"): data = pldict["KeyBagKeys"].data else: data = "" keybag = Keybag.createWithDataSignBlob(data, k835) if pldict.has_key("passcodeKey"): if keybag.unlockWithPasscodeKey(pldict["passcodeKey"].decode("hex")): print "Keybag unlocked with passcode key" else: print "FAILed to unlock keybag with passcode key" #HAX: inject DKey keybag.setDKey(pldict) return keybag def setDKey(self, device_infos): self.classKeys[4] = {"CLAS": 4, "KEY": device_infos["DKey"].decode("hex")} @staticmethod def createWithSystemkbfile(filename, bag1key, deviceKey=None): if filename.startswith("bplist"): #HAX mkb = BPlistReader.plistWithString(filename) else: mkb = BPlistReader.plistWithFile(filename) try: decryptedPlist = AESdecryptCBC(mkb["_MKBPAYLOAD"].data, bag1key, mkb["_MKBIV"].data, padding=True) except: print "FAIL: AESdecryptCBC _MKBPAYLOAD => wrong BAG1 key ?" return None if not decryptedPlist.startswith("bplist"): print "FAIL: decrypted _MKBPAYLOAD is not bplist" return None decryptedPlist = BPlistReader.plistWithString(decryptedPlist) blob = decryptedPlist["KeyBagKeys"].data kb = Keybag.createWithDataSignBlob(blob, deviceKey) if decryptedPlist.has_key("OpaqueStuff"): OpaqueStuff = BPlistReader.plistWithString(decryptedPlist["OpaqueStuff"].data) kb.passcodeComplexity = OpaqueStuff.get("keyboardType") return kb @staticmethod def createWithDataSignBlob(blob, deviceKey=None): keybag = tlvToDict(blob) kb = Keybag(keybag.get("DATA", "")) kb.deviceKey = deviceKey kb.KeyBagKeys = blob kb.unlockAlwaysAccessible() if len(keybag.get("SIGN", "")): hmackey = AESUnwrap(deviceKey, kb.attrs["HMCK"]) #hmac key and data are swapped (on purpose or by mistake ?) sigcheck = hmac.new(key=keybag["DATA"], msg=hmackey, digestmod=sha1).digest() #fixed in ios 7 if kb.attrs["VERS"] >= 4: sigcheck = hmac.new(key=hmackey, msg=keybag["DATA"], digestmod=sha1).digest() if sigcheck != keybag.get("SIGN", ""): print "Keybag: SIGN check FAIL" return kb @staticmethod def createWithBackupManifest(manifest, password, deviceKey=None): kb = Keybag(manifest["BackupKeyBag"].data) kb.deviceKey = deviceKey if not kb.unlockBackupKeybagWithPasscode(password): print "Cannot decrypt backup keybag. Wrong password ?" return return kb def isBackupKeybag(self): return self.type == BACKUP_KEYBAG def parseBinaryBlob(self, data): currentClassKey = None for tag, data in loopTLVBlocks(data): if len(data) == 4: data = struct.unpack(">L", data)[0] if tag == "TYPE": self.type = data & 0x3FFFFFFF #ignore the flags if self.type > 3: print "FAIL: keybag type > 3 : %d" % self.type elif tag == "UUID" and self.uuid is None: self.uuid = data elif tag == "WRAP" and self.wrap is None: self.wrap = data elif tag == "UUID": if currentClassKey: self.classKeys[currentClassKey["CLAS"]] = currentClassKey currentClassKey = {"UUID": data} elif tag in CLASSKEY_TAGS: currentClassKey[tag] = data else: self.attrs[tag] = data if currentClassKey: self.classKeys[currentClassKey["CLAS"]] = currentClassKey def getPasscodekeyFromPasscode(self, passcode): if self.type == BACKUP_KEYBAG or self.type == OTA_KEYBAG: return PBKDF2(passcode, self.attrs["SALT"], iterations=self.attrs["ITER"]).read(32) else: #Warning, need to run derivation on device with this result return PBKDF2(passcode, self.attrs["SALT"], iterations=1).read(32) def unlockBackupKeybagWithPasscode(self, passcode): if self.type != BACKUP_KEYBAG and self.type != OTA_KEYBAG: print "unlockBackupKeybagWithPasscode: not a backup keybag" return False return self.unlockWithPasscodeKey(self.getPasscodekeyFromPasscode(passcode)) def unlockAlwaysAccessible(self): for classkey in self.classKeys.values(): k = classkey["WPKY"] if classkey["WRAP"] == WRAP_DEVICE: if not self.deviceKey: continue k = AESdecryptCBC(k, self.deviceKey) classkey["KEY"] = k return True def unlockWithPasscodeKey(self, passcodekey): if self.type != BACKUP_KEYBAG and self.type != OTA_KEYBAG: if not self.deviceKey: print "ERROR, need device key to unlock keybag" return False for classkey in self.classKeys.values(): if not classkey.has_key("WPKY"): continue k = classkey["WPKY"] if classkey["WRAP"] & WRAP_PASSCODE: k = AESUnwrap(passcodekey, classkey["WPKY"]) if not k: return False if classkey["WRAP"] & WRAP_DEVICE: if not self.deviceKey: continue k = AESdecryptCBC(k, self.deviceKey) classkey["KEY"] = k self.unlocked = True return True def unwrapCurve25519(self, persistent_class, persistent_key): assert len(persistent_key) == 0x48 #assert persistent_class == 2 #NSFileProtectionCompleteUnlessOpen mysecret = self.classKeys[persistent_class]["KEY"] mypublic = self.classKeys[persistent_class]["PBKY"] hispublic = persistent_key[:32] shared = curve25519(mysecret, hispublic) md = sha256('\x00\x00\x00\x01' + shared + hispublic + mypublic).digest() return AESUnwrap(md, persistent_key[32:]) def unwrapKeyForClass(self, clas, persistent_key, printError=True): if not self.classKeys.has_key(clas) or not self.classKeys[clas].has_key("KEY"): if printError: print "Keybag key %d missing or locked" % clas return "" ck = self.classKeys[clas]["KEY"] #if self.attrs.get("VERS", 2) >= 3 and clas == 2: if self.attrs.get("VERS", 2) >= 3 and self.classKeys[clas].get("KTYP", 0) == 1: return self.unwrapCurve25519(clas, persistent_key) if len(persistent_key) == 0x28: return AESUnwrap(ck, persistent_key) return def wrapKeyForClass(self, clas, persistent_key): if not self.classKeys.has_key(clas) or not self.classKeys[clas].has_key("KEY"): print "Keybag key %d missing or locked" % clas return "" ck = self.classKeys[clas]["KEY"] return AESwrap(ck, persistent_key) def printClassKeys(self): print "Keybag type : %s keybag (%d)" % (KEYBAG_TYPES[self.type], self.type) print "Keybag version : %d" % self.attrs["VERS"] print "Keybag UUID : %s" % self.uuid.encode("hex") print "-"*128 print "".join(["Class".ljust(53), "WRAP".ljust(5), "Type".ljust(11), "Key".ljust(65), "Public key"]) print "-"*128 for k, ck in self.classKeys.items(): if k == 6: print "" print "".join([PROTECTION_CLASSES.get(k).ljust(53), str(ck.get("WRAP","")).ljust(5), KEY_TYPES[ck.get("KTYP",0)].ljust(11), ck.get("KEY", "").encode("hex").ljust(65), ck.get("PBKY", "").encode("hex")]) print "" def getClearClassKeysDict(self): if self.unlocked: d = {} for ck in self.classKeys.values(): d["%d" % ck["CLAS"]] = ck.get("KEY","").encode("hex") return d