282 lines
12 KiB
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--
|
|
|
|
*/ |