package org.catacombae.hfsexplorer.iphone;
import java.util.Arrays;
import java.util.HashMap;
import java.nio.ByteBuffer;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.catacombae.dmgextractor.Util;
import de.rtner.security.auth.spi.PBKDF2Engine;
import de.rtner.security.auth.spi.PBKDF2Parameters;
import djb.Curve25519;
import java.security.MessageDigest;
import java.io.ByteArrayOutputStream;
public class Keybag {
private static final int WRAP_DEVICE = 1;
private static final int WRAP_PASSCODE = 2;
public static enum Types {SYSTEM_KEYBAG, BACKUP_KEYBAG, ESCROW_KEYBAG, OTA_KEYBAG};
private static final String[] KEYBAG_TAGS = {"VERS", "TYPE", "UUID", "HMCK", "WRAP", "SALT", "ITER"};
private static final String[] CLASSKEY_TAGS = {"CLAS","WRAP","WPKY", "KTYP", "PBKY"};
private HashMap<String, byte[]> attributes;
private HashMap<byte[], HashMap<String, byte[]>> classKeys;
public Boolean unlocked = false; // not sure if needed
public Keybag(byte[] data)
{
this.attributes = new HashMap<String, byte[]>();
this.classKeys = new HashMap<byte[], HashMap<String, byte[]>>();
HashMap<String, byte[]> currentClassKey = new HashMap<String, byte[]>();
for(int i=0; i + 8 < data.length; )
{
String tag = Util.toASCIIString(data, i, 4);
int len = Util.readIntBE(data, i+4);
byte[] value = Arrays.copyOfRange(data, i+8, i+8+len);
if (tag.equals("UUID") && !this.attributes.containsKey("UUID"))
{
this.attributes.put(tag, value);
}
else if (tag.equals("WRAP") && !this.attributes.containsKey("WRAP"))
{
this.attributes.put(tag, value);
}
else if (tag.equals("UUID"))
{
if (!currentClassKey.isEmpty())
{
classKeys.put(currentClassKey.get("CLAS"), currentClassKey);
}
currentClassKey = new HashMap<String, byte[]>();
currentClassKey.put("UUID", value);
}
else if (Arrays.asList(CLASSKEY_TAGS).contains(tag))
{
currentClassKey.put(tag, value);
}
else
{
this.attributes.put(tag, value);
}
i += 8 + len;
}
}
public byte[] getSALT() {
return this.attributes.get("SALT");
}
public int getITER() {
return ByteBuffer.wrap(this.attributes.get("ITER")).getInt();
}
public Types getTYPE() {
return Types.values()[ByteBuffer.wrap(this.attributes.get("TYPE")).getInt()];
}
public Boolean unlockWithPasscodeKey(byte[] passcodeKey) throws Exception {
for (HashMap<String, byte[]> classKey : this.classKeys.values()) {
if (!classKey.containsKey("WPKY"))
continue;
byte[] k = classKey.get("WPKY");
if ((ByteBuffer.wrap(classKey.get("WRAP")).getInt() & WRAP_PASSCODE) != 0) {
k = AESUnwrap(passcodeKey, classKey.get("WPKY"));
}
// this is not needed
/*if ((wrap & WRAP_DEVICE) != 0) {
//python code:
//if not self.deviceKey:
// continue
//k = AESdecryptCBC(k, self.deviceKey)
}*/
classKey.put("KEY", k);
}
this.unlocked = true;
return true;
}
public byte[] getPasscodekeyFromPasscode(byte[] passcode) throws Exception {
int iter = this.getTYPE() == Types.BACKUP_KEYBAG || this.getTYPE() == Types.OTA_KEYBAG ? this.getITER() : 1;
PBKDF2Parameters p = new PBKDF2Parameters("HmacSHA1", null, this.getSALT(), iter);
PBKDF2Engine e = new PBKDF2Engine(p);
return e.deriveKey(passcode, 32);
}
public Boolean unlockBackupKeybagWithPasscode(byte[] passcode) throws Exception {
if (this.getTYPE() == Types.BACKUP_KEYBAG || this.getTYPE() == Types.OTA_KEYBAG) {
return false; // not a backup keybag!
}
return this.unlockWithPasscodeKey(this.getPasscodekeyFromPasscode(passcode));
}
private byte[] AESUnwrap(byte[] key, byte[] wrapped) throws Exception {
Cipher cipher = Cipher.getInstance("AESWrap");
cipher.init(Cipher.UNWRAP_MODE, new SecretKeySpec(key, "AES"));
return cipher.unwrap(wrapped, "AES", Cipher.SECRET_KEY).getEncoded();
}
private byte[] unwrapCurve25519(byte[] persistentClass, byte[] persistentKey) throws Exception {
assert persistentKey.length == 0x48;
byte[] mySecret = this.classKeys.get(persistentClass).get("KEY");
byte[] myPublic = this.classKeys.get(persistentClass).get("PBKY");
byte[] hisPublic = Arrays.copyOfRange(persistentKey, 0, 32);
byte[] shared = new byte[32];
Curve25519.curve(shared, mySecret, hisPublic); // not really sure
ByteArrayOutputStream stream = new ByteArrayOutputStream();
stream.write(new byte[] { 0x00, 0x00, 0x00, 0x01 });
stream.write(shared);
stream.write(hisPublic);
stream.write(myPublic);
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] md = digest.digest(stream.toByteArray());
return AESUnwrap(md, hisPublic);
}
}