konogonka/src/main/java/konogonka/Tools/NCA/NCAProvider.java

282 lines
12 KiB
Java

package konogonka.Tools.NCA;
import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock;
import konogonka.xtsaes.XTSAESCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import static konogonka.LoperConverter.getLElong;
// TODO: check file size
public class NCAProvider {
private File file; // File that contains NCA
private long offset; // Offset where NCA actually located
private HashMap<String, String> keys; // hashmap with keys using _0x naming (where x number 0-N)
// Header
private byte[] rsa2048one;
private byte[] rsa2048two;
private String magicnum;
private byte systemOrGcIndicator;
private byte contentType;
private byte cryptoType1; // keyblob index. Considering as number within application/ocean/system
private byte keyIndex; // application/ocean/system (kaek index?)
private long ncaSize; // Size of this NCA (bytes)
private byte[] titleId;
private byte[] sdkVersion; // version ver_revision.ver_micro.vev_minor.ver_major
private byte cryptoType2; // keyblob index. Considering as number within application/ocean/system
private byte[] rightsId;
private byte[] sha256hash0;
private byte[] sha256hash1;
private byte[] sha256hash2;
private byte[] sha256hash3;
private byte[] encryptedKey0;
private byte[] encryptedKey1;
private byte[] encryptedKey2;
private byte[] encryptedKey3;
private byte[] decryptedKey0;
private byte[] decryptedKey1;
private byte[] decryptedKey2;
private byte[] decryptedKey3;
private NCAHeaderTableEntry tableEntry0;
private NCAHeaderTableEntry tableEntry1;
private NCAHeaderTableEntry tableEntry2;
private NCAHeaderTableEntry tableEntry3;
private NCASectionBlock sectionBlock0;
private NCASectionBlock sectionBlock1;
private NCASectionBlock sectionBlock2;
private NCASectionBlock sectionBlock3;
public NCAProvider(File file, HashMap<String, String> keys) throws Exception{
this(file, keys, 0);
}
public NCAProvider (File file, HashMap<String, String> keys, long offsetPosition) throws Exception{
this.keys = keys;
String header_key = keys.get("header_key");
if (header_key == null )
throw new Exception("header_key is not found within key set provided.");
if (header_key.length() != 64)
throw new Exception("header_key is too small or too big. Must be 64 symbols.");
this.file = file;
this.offset = offsetPosition;
KeyParameter key1 = new KeyParameter(
hexStrToByteArray(header_key.substring(0, 32))
);
KeyParameter key2 = new KeyParameter(
hexStrToByteArray(header_key.substring(32, 64))
);
XTSAESCipher xtsaesCipher = new XTSAESCipher(false);
xtsaesCipher.init(false, key1, key2);
//-------------------------------------------------------------------------------------------------------------------------
byte[] decryptedHeader = new byte[0xC00];
RandomAccessFile raf = new RandomAccessFile(file, "r");
byte[] encryptedSequence = new byte[0x200];
byte[] decryptedSequence;
raf.seek(offsetPosition);
for (int i = 0; i < 6; i++){
if (raf.read(encryptedSequence) != 0x200)
throw new Exception("Read error "+i);
decryptedSequence = new byte[0x200];
xtsaesCipher.processDataUnit(encryptedSequence, 0, 0x200, decryptedSequence, 0, i);
System.arraycopy(decryptedSequence, 0, decryptedHeader, i * 0x200, 0x200);
}
getHeader(decryptedHeader);
raf.close();
/*
//---------------------------------------------------------------------
FileInputStream fis = new FileInputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("/tmp/decrypted.nca"));
int i = 0;
byte[] block = new byte[0x200];
while (fis.read(block) != -1){
decryptedSequence = new byte[0x200];
xtsaesCipher.processDataUnit(block, 0, 0x200, decryptedSequence, 0, i++);
bos.write(decryptedSequence);
}
bos.close();
//---------------------------------------------------------------------*/
}
private byte[] hexStrToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
private void getHeader(byte[] decryptedData) throws Exception{
rsa2048one = Arrays.copyOfRange(decryptedData, 0, 0x100);
rsa2048two = Arrays.copyOfRange(decryptedData, 0x100, 0x200);
magicnum = new String(decryptedData, 0x200, 0x4, StandardCharsets.US_ASCII);
systemOrGcIndicator = decryptedData[0x204];
contentType = decryptedData[0x205];
cryptoType1 = decryptedData[0x206];
keyIndex = decryptedData[0x207];
ncaSize = getLElong(decryptedData, 0x208);
titleId = Arrays.copyOfRange(decryptedData, 0x210, 0x21C); // 0x218 ?
sdkVersion = Arrays.copyOfRange(decryptedData, 0x21c, 0x220);
cryptoType2 = decryptedData[0x220];
rightsId = Arrays.copyOfRange(decryptedData, 0x230, 0x240);
byte[] tableBytes = Arrays.copyOfRange(decryptedData, 0x240, 0x280);
byte[] sha256tableBytes = Arrays.copyOfRange(decryptedData, 0x280, 0x300);
sha256hash0 = Arrays.copyOfRange(sha256tableBytes, 0, 0x20);
sha256hash1 = Arrays.copyOfRange(sha256tableBytes, 0x20, 0x40);
sha256hash2 = Arrays.copyOfRange(sha256tableBytes, 0x40, 0x60);
sha256hash3 = Arrays.copyOfRange(sha256tableBytes, 0x60, 0x80);
byte [] encryptedKeysArea = Arrays.copyOfRange(decryptedData, 0x300, 0x340);
encryptedKey0 = Arrays.copyOfRange(encryptedKeysArea, 0, 0x10);
encryptedKey1 = Arrays.copyOfRange(encryptedKeysArea, 0x10, 0x20);
encryptedKey2 = Arrays.copyOfRange(encryptedKeysArea, 0x20, 0x30);
encryptedKey3 = Arrays.copyOfRange(encryptedKeysArea, 0x30, 0x40);
//todo: if nca3 proceed
// If no rights ID (ticket?) exists
if (Arrays.equals(rightsId, new byte[0x10])) {
byte realCryptoType;
if (cryptoType1 < cryptoType2)
realCryptoType = cryptoType2;
else
realCryptoType = cryptoType1;
if (realCryptoType > 0) // TODO: CLARIFY WHY THEH FUCK IS IT FAIR????
realCryptoType -= 1;
String keyAreaKey;
switch (keyIndex){
case 0:
keyAreaKey = keys.get("key_area_key_application_0"+realCryptoType);
System.out.println("Using key_area_key_application_0"+realCryptoType);
break;
case 1:
keyAreaKey = keys.get("key_area_key_ocean_0"+realCryptoType);
System.out.println("Using key_area_key_ocean_0"+realCryptoType);
break;
case 2:
keyAreaKey = keys.get("key_area_key_system_0"+realCryptoType);
System.out.println("Using key_area_key_system_0"+realCryptoType);
break;
default:
keyAreaKey = null;
}
if (keyAreaKey != null){
SecretKeySpec skSpec = new SecretKeySpec(hexStrToByteArray(keyAreaKey), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, skSpec);
decryptedKey0 = cipher.doFinal(encryptedKey0);
decryptedKey1 = cipher.doFinal(encryptedKey1);
decryptedKey2 = cipher.doFinal(encryptedKey2);
decryptedKey3 = cipher.doFinal(encryptedKey3);
}
}
else {
}
tableEntry0 = new NCAHeaderTableEntry(tableBytes);
tableEntry1 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x10, 0x20));
tableEntry2 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x20, 0x30));
tableEntry3 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x30, 0x40));
sectionBlock0 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x400, 0x600));
sectionBlock1 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x600, 0x800));
sectionBlock2 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x800, 0xa00));
sectionBlock3 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0xa00, 0xc00));
}
public byte[] getRsa2048one() { return rsa2048one; }
public byte[] getRsa2048two() { return rsa2048two; }
public String getMagicnum() { return magicnum; }
public byte getSystemOrGcIndicator() { return systemOrGcIndicator; }
public byte getContentType() { return contentType; }
public byte getCryptoType1() { return cryptoType1; }
public byte getKeyIndex() { return keyIndex; }
public long getNcaSize() { return ncaSize; }
public byte[] getTitleId() { return titleId; }
public byte[] getSdkVersion() { return sdkVersion; }
public byte getCryptoType2() { return cryptoType2; }
public byte[] getRightsId() { return rightsId; }
public byte[] getSha256hash0() { return sha256hash0; }
public byte[] getSha256hash1() { return sha256hash1; }
public byte[] getSha256hash2() { return sha256hash2; }
public byte[] getSha256hash3() { return sha256hash3; }
public byte[] getEncryptedKey0() { return encryptedKey0; }
public byte[] getEncryptedKey1() { return encryptedKey1; }
public byte[] getEncryptedKey2() { return encryptedKey2; }
public byte[] getEncryptedKey3() { return encryptedKey3; }
public byte[] getDecryptedKey0() { return decryptedKey0; }
public byte[] getDecryptedKey1() { return decryptedKey1; }
public byte[] getDecryptedKey2() { return decryptedKey2; }
public byte[] getDecryptedKey3() { return decryptedKey3; }
public NCAHeaderTableEntry getTableEntry0() { return tableEntry0; }
public NCAHeaderTableEntry getTableEntry1() { return tableEntry1; }
public NCAHeaderTableEntry getTableEntry2() { return tableEntry2; }
public NCAHeaderTableEntry getTableEntry3() { return tableEntry3; }
public NCASectionBlock getSectionBlock0() { return sectionBlock0; }
public NCASectionBlock getSectionBlock1() { return sectionBlock1; }
public NCASectionBlock getSectionBlock2() { return sectionBlock2; }
public NCASectionBlock getSectionBlock3() { return sectionBlock3; }
/**
* Get content for the selected section
* @param sectionNumber should be 1-4
* */
public NCAContentPFS0 getNCAContentPFS0(int sectionNumber){
switch (sectionNumber){
case 0:
return new NCAContentPFS0(file, offset, sectionBlock0, tableEntry0, decryptedKey2); // TODO: remove decryptedKey2
case 1:
return new NCAContentPFS0(file, offset, sectionBlock1, tableEntry1, decryptedKey2);
case 2:
return new NCAContentPFS0(file, offset, sectionBlock2, tableEntry2, decryptedKey2);
case 3:
return new NCAContentPFS0(file, offset, sectionBlock3, tableEntry3, decryptedKey2);
default:
return null;
}
}
}
// 0 OR 2 crypto type
// 0,1,2 kaek index
//settings.keyset.key_area_keys[ctx->crypto_type][ctx->header.kaek_ind]
/*
0x207 =
0: key_area_key_application_ 0x206 range:[0-6]; usually used 0 or 2
1: key_area_key_ocean [0-6]
2: key_area_key_system [0-6]
if(ncahdr_x206 < ncahdr_x220){ret = ncahdr_x220; } else { ret = ncahdr_x206; } return ret;
ret > 0? ret--
*/