/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.crypto;
import java.nio.ByteBuffer;
import jpcsp.util.Utilities;
public class KIRK {
// PSP specific values.
private int fuseID0;
private int fuseID1;
private byte[] priv_iv = new byte[0x10];
private byte[] prng_data = new byte[0x14];
// KIRK error values.
public static final int PSP_KIRK_NOT_ENABLED = 0x1;
public static final int PSP_KIRK_INVALID_MODE = 0x2;
public static final int PSP_KIRK_INVALID_HEADER_HASH = 0x3;
public static final int PSP_KIRK_INVALID_DATA_HASH = 0x4;
public static final int PSP_KIRK_INVALID_SIG_CHECK = 0x5;
public static final int PSP_KIRK_UNK1 = 0x6;
public static final int PSP_KIRK_UNK2 = 0x7;
public static final int PSP_KIRK_UNK3 = 0x8;
public static final int PSP_KIRK_UNK4 = 0x9;
public static final int PSP_KIRK_UNK5 = 0xA;
public static final int PSP_KIRK_UNK6 = 0xB;
public static final int PSP_KIRK_NOT_INIT = 0xC;
public static final int PSP_KIRK_INVALID_OPERATION = 0xD;
public static final int PSP_KIRK_INVALID_SEED = 0xE;
public static final int PSP_KIRK_INVALID_SIZE = 0xF;
public static final int PSP_KIRK_DATA_SIZE_IS_ZERO = 0x10;
public static final int PSP_SUBCWR_NOT_16_ALGINED = 0x90A;
public static final int PSP_SUBCWR_HEADER_HASH_INVALID = 0x920;
public static final int PSP_SUBCWR_BUFFER_TOO_SMALL = 0x1000;
// KIRK commands.
public static final int PSP_KIRK_CMD_DECRYPT_PRIVATE = 0x1; // Master decryption command, used by firmware modules. Applies CMAC checking.
public static final int PSP_KIRK_CMD_ENCRYPT_SIGN = 0x2; // Used for key type 3 (blacklisting), encrypts and signs data with a ECDSA signature.
public static final int PSP_KIRK_CMD_DECRYPT_SIGN = 0x3; // Used for key type 3 (blacklisting), decrypts and signs data with a ECDSA signature.
public static final int PSP_KIRK_CMD_ENCRYPT = 0x4; // Key table based encryption used for general purposes by several modules.
public static final int PSP_KIRK_CMD_ENCRYPT_FUSE = 0x5; // Fuse ID based encryption used for general purposes by several modules.
public static final int PSP_KIRK_CMD_ENCRYPT_USER = 0x6; // User specified ID based encryption used for general purposes by several modules.
public static final int PSP_KIRK_CMD_DECRYPT = 0x7; // Key table based decryption used for general purposes by several modules.
public static final int PSP_KIRK_CMD_DECRYPT_FUSE = 0x8; // Fuse ID based decryption used for general purposes by several modules.
public static final int PSP_KIRK_CMD_DECRYPT_USER = 0x9; // User specified ID based decryption used for general purposes by several modules.
public static final int PSP_KIRK_CMD_PRIV_SIG_CHECK = 0xA; // Private signature (SCE) checking command.
public static final int PSP_KIRK_CMD_SHA1_HASH = 0xB; // SHA1 hash generating command.
public static final int PSP_KIRK_CMD_ECDSA_GEN_KEYS = 0xC; // ECDSA key generating mul1 command.
public static final int PSP_KIRK_CMD_ECDSA_MULTIPLY_POINT = 0xD; // ECDSA key generating mul2 command.
public static final int PSP_KIRK_CMD_PRNG = 0xE; // Random number generating command.
public static final int PSP_KIRK_CMD_INIT = 0xF; // KIRK initialization command.
public static final int PSP_KIRK_CMD_ECDSA_SIGN = 0x10; // ECDSA signing command.
public static final int PSP_KIRK_CMD_ECDSA_VERIFY = 0x11; // ECDSA checking command.
public static final int PSP_KIRK_CMD_CERT_VERIFY = 0x12; // Certificate checking command.
// KIRK command modes.
public static final int PSP_KIRK_CMD_MODE_CMD1 = 0x1;
public static final int PSP_KIRK_CMD_MODE_CMD2 = 0x2;
public static final int PSP_KIRK_CMD_MODE_CMD3 = 0x3;
public static final int PSP_KIRK_CMD_MODE_ENCRYPT_CBC = 0x4;
public static final int PSP_KIRK_CMD_MODE_DECRYPT_CBC = 0x5;
// KIRK header structs.
private class SHA1_Header {
private int dataSize;
private byte[] data;
public SHA1_Header(ByteBuffer buf) {
dataSize = buf.getInt();
}
private void readData(ByteBuffer buf, int size) {
data = new byte[size];
buf.get(data, 0, size);
}
}
private static class AES128_CBC_Header {
private int mode;
private int unk1;
private int unk2;
private int keySeed;
private int dataSize;
public AES128_CBC_Header(ByteBuffer buf) {
mode = buf.getInt();
unk1 = buf.getInt();
unk2 = buf.getInt();
keySeed = buf.getInt();
dataSize = buf.getInt();
}
}
private static class AES128_CMAC_Header {
private byte[] AES128Key = new byte[16];
private byte[] CMACKey = new byte[16];
private byte[] CMACHeaderHash = new byte[16];
private byte[] CMACDataHash = new byte[16];
private byte[] unk1 = new byte[32];
private int mode;
private byte useECDSAhash;
private byte[] unk2 = new byte[11];
private int dataSize;
private int dataOffset;
private byte[] unk3 = new byte[8];
private byte[] unk4 = new byte[16];
public AES128_CMAC_Header(ByteBuffer buf) {
buf.get(AES128Key, 0, 16);
buf.get(CMACKey, 0, 16);
buf.get(CMACHeaderHash, 0, 16);
buf.get(CMACDataHash, 0, 16);
buf.get(unk1, 0, 32);
mode = buf.getInt();
useECDSAhash = buf.get();
buf.get(unk2, 0, 11);
dataSize = buf.getInt();
dataOffset = buf.getInt();
buf.get(unk3, 0, 8);
buf.get(unk4, 0, 16);
// For PRX, the mode is big-endian, for direct sceKernelUtilsCopyWithRange,
// the mode is little-endian. I don't know how to better differentiate these cases.
if ((mode & 0x00FFFFFF) == 0x000000) {
mode = Integer.reverseBytes(mode);
}
}
static public int SIZEOF() {
return 144;
}
}
private static class AES128_CMAC_ECDSA_Header {
private byte[] AES128Key = new byte[16];
private byte[] ECDSAHeaderSig_r = new byte[20];
private byte[] ECDSAHeaderSig_s = new byte[20];
private byte[] ECDSADataSig_r = new byte[20];
private byte[] ECDSADataSig_s = new byte[20];
private int mode;
private byte useECDSAhash;
private byte[] unk1 = new byte[11];
private int dataSize;
private int dataOffset;
private byte[] unk2 = new byte[8];
private byte[] unk3 = new byte[16];
public AES128_CMAC_ECDSA_Header(ByteBuffer buf) {
buf.get(AES128Key, 0, 16);
buf.get(ECDSAHeaderSig_r, 0, 20);
buf.get(ECDSAHeaderSig_s, 0, 20);
buf.get(ECDSADataSig_r, 0, 20);
buf.get(ECDSADataSig_s, 0, 20);
mode = buf.getInt();
useECDSAhash = buf.get();
buf.get(unk1, 0, 11);
dataSize = buf.getInt();
dataOffset = buf.getInt();
buf.get(unk2, 0, 8);
buf.get(unk3, 0, 16);
}
}
private static class ECDSASig {
private byte[] r = new byte[0x14];
private byte[] s = new byte[0x14];
private ECDSASig() {
}
}
private static class ECDSAPoint {
private byte[] x = new byte[0x14];
private byte[] y = new byte[0x14];
private ECDSAPoint() {
}
private ECDSAPoint(byte[] data) {
System.arraycopy(data, 0, x, 0, 0x14);
System.arraycopy(data, 0x14, y, 0, 0x14);
}
public byte[] toByteArray() {
byte[] point = new byte[0x28];
System.arraycopy(point, 0, x, 0, 0x14);
System.arraycopy(point, 0x14, y, 0, 0x14);
return point;
}
}
private static class ECDSAKeygenCtx {
private byte[] private_key = new byte[0x14];
private ECDSAPoint public_key;
private ByteBuffer out;
private ECDSAKeygenCtx(ByteBuffer output) {
public_key = new ECDSAPoint();
out = output;
}
public void write() {
out.put(private_key);
out.put(public_key.toByteArray());
}
}
private static class ECDSAMultiplyCtx {
private byte[] multiplier = new byte[0x14];
private ECDSAPoint public_key = new ECDSAPoint();
private ByteBuffer out;
private ECDSAMultiplyCtx(ByteBuffer input, ByteBuffer output) {
out = output;
input.get(multiplier, 0, 0x14);
input.get(public_key.x, 0, 0x14);
input.get(public_key.y, 0, 0x14);
}
public void write() {
out.put(multiplier);
out.put(public_key.toByteArray());
}
}
private static class ECDSASignCtx {
private byte[] enc = new byte[0x20];
private byte[] hash = new byte[0x14];
private ECDSASignCtx(ByteBuffer buf) {
buf.get(enc, 0, 0x20);
buf.get(hash, 0, 0x14);
}
}
private static class ECDSAVerifyCtx {
private ECDSAPoint public_key = new ECDSAPoint();
private byte[] hash = new byte[0x14];
private ECDSASig sig = new ECDSASig();
private ECDSAVerifyCtx(ByteBuffer buf) {
buf.get(public_key.x, 0, 0x14);
buf.get(public_key.y, 0, 0x14);
buf.get(hash, 0, 0x14);
buf.get(sig.r, 0, 0x14);
buf.get(sig.s, 0, 0x14);
}
}
// Helper functions.
private static int[] getAESKeyFromSeed(int seed) {
switch (seed) {
case (0x02):
return KeyVault.kirkAESKey20;
case (0x03):
return KeyVault.kirkAESKey1;
case (0x04):
return KeyVault.kirkAESKey2;
case (0x05):
return KeyVault.kirkAESKey3;
case (0x07):
return KeyVault.kirkAESKey21;
case (0x0C):
return KeyVault.kirkAESKey4;
case (0x0D):
return KeyVault.kirkAESKey5;
case (0x0E):
return KeyVault.kirkAESKey6;
case (0x0F):
return KeyVault.kirkAESKey7;
case (0x10):
return KeyVault.kirkAESKey8;
case (0x11):
return KeyVault.kirkAESKey9;
case (0x12):
return KeyVault.kirkAESKey10;
case (0x38):
return KeyVault.kirkAESKey11;
case (0x39):
return KeyVault.kirkAESKey12;
case (0x3A):
return KeyVault.kirkAESKey13;
case (0x44):
return KeyVault.kirkAESKey22;
case (0x4B):
return KeyVault.kirkAESKey14;
case (0x53):
return KeyVault.kirkAESKey15;
case (0x57):
return KeyVault.kirkAESKey16;
case (0x5D):
return KeyVault.kirkAESKey17;
case (0x63):
return KeyVault.kirkAESKey18;
case (0x64):
return KeyVault.kirkAESKey19;
default:
return null;
}
}
public KIRK() {
}
public KIRK(byte[] seed, int seedLength, int fuseid0, int fuseid1) {
// Set up the data for the pseudo random number generator using a
// seed set by the user.
byte[] temp = new byte[0x104];
temp[0] = 0;
temp[1] = 0;
temp[2] = 1;
temp[3] = 0;
ByteBuffer bTemp = ByteBuffer.wrap(temp);
ByteBuffer bPRNG = ByteBuffer.wrap(prng_data);
// Random data to act as a key.
byte[] key = {(byte) 0x07, (byte) 0xAB, (byte) 0xEF, (byte) 0xF8, (byte) 0x96,
(byte) 0x8C, (byte) 0xF3, (byte) 0xD6, (byte) 0x14, (byte) 0xE0, (byte) 0xEB, (byte) 0xB2,
(byte) 0x9D, (byte) 0x8B, (byte) 0x4E, (byte) 0x74};
// Direct call to get the system time.
int systime = (int) System.currentTimeMillis();
// Generate a SHA-1 hash for the PRNG.
if (seedLength > 0) {
byte[] seedBuf = new byte[seedLength + 4];
ByteBuffer bSeedBuf = ByteBuffer.wrap(seedBuf);
SHA1_Header seedHeader = new SHA1_Header(bSeedBuf);
bSeedBuf.rewind();
seedHeader.dataSize = seedLength;
executeKIRKCmd11(bPRNG, bSeedBuf, seedLength + 4);
}
// Use the system time for randomness.
System.arraycopy(prng_data, 0, temp, 4, 0x14);
temp[0x18] = (byte) (systime & 0xFF);
temp[0x19] = (byte) ((systime >> 8) & 0xFF);
temp[0x1A] = (byte) ((systime >> 16) & 0xFF);
temp[0x1B] = (byte) ((systime >> 24) & 0xFF);
// Set the final PRNG number.
System.arraycopy(key, 0, temp, 0x1C, 0x10);
executeKIRKCmd11(bPRNG, bTemp, 0x104);
fuseID0 = fuseid0;
fuseID1 = fuseid1;
}
/*
* KIRK commands: main emulated crypto functions.
*/
// Decrypt with AESCBC128-CMAC header and sig check.
private int executeKIRKCmd1(ByteBuffer out, ByteBuffer in, int size) {
// Return an error if the crypto engine hasn't been initialized.
if (!CryptoEngine.getCryptoEngineStatus()) {
return PSP_KIRK_NOT_INIT;
}
// Copy the input for sig check.
ByteBuffer sigIn = in.duplicate();
sigIn.order(in.order()); // duplicate() does not copy the order()
int headerSize = AES128_CMAC_Header.SIZEOF();
int headerOffset = in.position();
// Read in the CMD1 format header.
AES128_CMAC_Header header = new AES128_CMAC_Header(in);
if (header.mode != PSP_KIRK_CMD_MODE_CMD1) {
return PSP_KIRK_INVALID_MODE; // Only valid for mode CMD1.
}
// Start AES128 processing.
AES128 aes = new AES128("AES/CBC/NoPadding");
// Convert the AES CMD1 key into a real byte array for SecretKeySpec.
byte[] k = new byte[16];
for (int i = 0; i < KeyVault.kirkAESKey0.length; i++) {
k[i] = (byte) KeyVault.kirkAESKey0[i];
}
// Decrypt and extract the new AES and CMAC keys from the top of the data.
byte[] encryptedKeys = new byte[32];
System.arraycopy(header.AES128Key, 0, encryptedKeys, 0, 16);
System.arraycopy(header.CMACKey, 0, encryptedKeys, 16, 16);
byte[] decryptedKeys = aes.decrypt(encryptedKeys, k, priv_iv);
// Check for a valid signature.
int sigCheck = executeKIRKCmd10(sigIn, size);
if (decryptedKeys == null) {
// Only return the sig check result if the keys are invalid
// to allow skipping the CMAC comparision.
// TODO: Trace why the CMAC hashes aren't matching.
return sigCheck;
}
// Get the newly decrypted AES key and proceed with the
// full data decryption.
byte[] aesBuf = new byte[16];
System.arraycopy(decryptedKeys, 0, aesBuf, 0, aesBuf.length);
// Extract the final ELF params.
int elfDataSize = header.dataSize;
int elfDataOffset = header.dataOffset;
// Input buffer for decryption must have a length aligned on 16 bytes
int paddedElfDataSize = Utilities.alignUp(elfDataSize, 15);
// Decrypt all the ELF data.
byte[] inBuf = new byte[paddedElfDataSize];
System.arraycopy(in.array(), elfDataOffset + headerOffset + headerSize, inBuf, 0, paddedElfDataSize);
byte[] outBuf = aes.decrypt(inBuf, aesBuf, priv_iv);
out.clear();
out.put(outBuf);
out.limit(elfDataSize);
in.clear();
return 0;
}
// Encrypt with AESCBC128 using keys from table.
private int executeKIRKCmd4(ByteBuffer out, ByteBuffer in, int size) {
// Return an error if the crypto engine hasn't been initialized.
if (!CryptoEngine.getCryptoEngineStatus()) {
return PSP_KIRK_NOT_INIT;
}
// Read in the CMD4 format header.
AES128_CBC_Header header = new AES128_CBC_Header(in);
if (header.mode != PSP_KIRK_CMD_MODE_ENCRYPT_CBC) {
return PSP_KIRK_INVALID_MODE; // Only valid for mode ENCRYPT_CBC.
}
if (header.dataSize == 0) {
return PSP_KIRK_DATA_SIZE_IS_ZERO;
}
int[] key = getAESKeyFromSeed(header.keySeed);
if (key == null) {
return PSP_KIRK_INVALID_SEED;
}
byte[] encKey = new byte[16];
for (int i = 0; i < encKey.length; i++) {
encKey[i] = (byte) key[i];
}
AES128 aes = new AES128("AES/CBC/NoPadding");
byte[] inBuf = new byte[size];
in.get(inBuf, 0, size);
byte[] outBuf = aes.encrypt(inBuf, encKey, priv_iv);
out.clear();
out.put(outBuf);
in.clear();
return 0;
}
// Encrypt with AESCBC128 using keys from table.
private int executeKIRKCmd5(ByteBuffer out, ByteBuffer in, int size) {
// Return an error if the crypto engine hasn't been initialized.
if (!CryptoEngine.getCryptoEngineStatus()) {
return PSP_KIRK_NOT_INIT;
}
// Read in the CMD4 format header.
AES128_CBC_Header header = new AES128_CBC_Header(in);
if (header.mode != PSP_KIRK_CMD_MODE_ENCRYPT_CBC) {
return PSP_KIRK_INVALID_MODE; // Only valid for mode ENCRYPT_CBC.
}
if (header.dataSize == 0) {
return PSP_KIRK_DATA_SIZE_IS_ZERO;
}
byte[] key = null;
if (header.keySeed == 0x100) {
key = priv_iv;
} else {
return PSP_KIRK_INVALID_SIZE; // Dummy.
}
byte[] encKey = new byte[16];
for (int i = 0; i < encKey.length; i++) {
encKey[i] = (byte) key[i];
}
AES128 aes = new AES128("AES/CBC/NoPadding");
byte[] inBuf = new byte[size];
in.get(inBuf, 0, size);
byte[] outBuf = aes.encrypt(inBuf, encKey, priv_iv);
out.clear();
out.put(outBuf);
in.clear();
return 0;
}
// Decrypt with AESCBC128 using keys from table.
private int executeKIRKCmd7(ByteBuffer out, ByteBuffer in, int size) {
// Return an error if the crypto engine hasn't been initialized.
if (!CryptoEngine.getCryptoEngineStatus()) {
return PSP_KIRK_NOT_INIT;
}
// Read in the CMD7 format header.
AES128_CBC_Header header = new AES128_CBC_Header(in);
if (header.mode != PSP_KIRK_CMD_MODE_DECRYPT_CBC) {
return PSP_KIRK_INVALID_MODE; // Only valid for mode DECRYPT_CBC.
}
if (header.dataSize == 0) {
return PSP_KIRK_DATA_SIZE_IS_ZERO;
}
int[] key = getAESKeyFromSeed(header.keySeed);
if (key == null) {
return PSP_KIRK_INVALID_SEED;
}
byte[] decKey = new byte[16];
for (int i = 0; i < decKey.length; i++) {
decKey[i] = (byte) key[i];
}
AES128 aes = new AES128("AES/CBC/NoPadding");
byte[] inBuf = new byte[size];
in.get(inBuf, 0, size);
byte[] outBuf = aes.decrypt(inBuf, decKey, priv_iv);
out.clear();
out.put(outBuf);
in.clear();
return 0;
}
// Decrypt with AESCBC128 using keys from table.
private int executeKIRKCmd8(ByteBuffer out, ByteBuffer in, int size) {
// Return an error if the crypto engine hasn't been initialized.
if (!CryptoEngine.getCryptoEngineStatus()) {
return PSP_KIRK_NOT_INIT;
}
// Read in the CMD7 format header.
AES128_CBC_Header header = new AES128_CBC_Header(in);
if (header.mode != PSP_KIRK_CMD_MODE_DECRYPT_CBC) {
return PSP_KIRK_INVALID_MODE; // Only valid for mode DECRYPT_CBC.
}
if (header.dataSize == 0) {
return PSP_KIRK_DATA_SIZE_IS_ZERO;
}
byte[] key = null;
if (header.keySeed == 0x100) {
key = priv_iv;
} else {
return PSP_KIRK_INVALID_SIZE; // Dummy.
}
byte[] decKey = new byte[16];
for (int i = 0; i < decKey.length; i++) {
decKey[i] = (byte) key[i];
}
AES128 aes = new AES128("AES/CBC/NoPadding");
byte[] inBuf = new byte[size];
in.get(inBuf, 0, size);
byte[] outBuf = aes.decrypt(inBuf, decKey, priv_iv);
out.clear();
out.put(outBuf);
in.clear();
return 0;
}
// CMAC Sig check.
private int executeKIRKCmd10(ByteBuffer in, int size) {
// Return an error if the crypto engine hasn't been initialized.
if (!CryptoEngine.getCryptoEngineStatus()) {
return PSP_KIRK_NOT_INIT;
}
int headerOffset = in.position();
// Read in the CMD10 format header.
AES128_CMAC_Header header = new AES128_CMAC_Header(in);
if ((header.mode != PSP_KIRK_CMD_MODE_CMD1)
&& (header.mode != PSP_KIRK_CMD_MODE_CMD2)
&& (header.mode != PSP_KIRK_CMD_MODE_CMD3)) {
return PSP_KIRK_INVALID_MODE; // Only valid for modes CMD1, CMD2 and CMD3.
}
if (header.dataSize == 0) {
return PSP_KIRK_DATA_SIZE_IS_ZERO;
}
AES128 aes = new AES128("AES/CBC/NoPadding");
// Convert the AES CMD1 key into a real byte array.
byte[] k = new byte[16];
for (int i = 0; i < KeyVault.kirkAESKey0.length; i++) {
k[i] = (byte) KeyVault.kirkAESKey0[i];
}
// Decrypt and extract the new AES and CMAC keys from the top of the data.
byte[] encryptedKeys = new byte[32];
System.arraycopy(header.AES128Key, 0, encryptedKeys, 0, 16);
System.arraycopy(header.CMACKey, 0, encryptedKeys, 16, 16);
byte[] decryptedKeys = aes.decrypt(encryptedKeys, k, priv_iv);
byte[] cmacHeaderHash = new byte[16];
byte[] cmacDataHash = new byte[16];
byte[] cmacBuf = new byte[16];
System.arraycopy(decryptedKeys, 16, cmacBuf, 0, cmacBuf.length);
// Position the buffer at the CMAC keys offset.
byte[] inBuf = new byte[in.capacity() - 0x60 - headerOffset];
System.arraycopy(in.array(), headerOffset + 0x60, inBuf, 0, inBuf.length);
// Calculate CMAC header hash.
aes.doInitCMAC(cmacBuf);
aes.doUpdateCMAC(inBuf, 0, 0x30);
cmacHeaderHash = aes.doFinalCMAC();
int blockSize = header.dataSize;
if ((blockSize % 16) != 0) {
blockSize += (16 - (blockSize % 16));
}
// Calculate CMAC data hash.
aes.doInitCMAC(cmacBuf);
aes.doUpdateCMAC(inBuf, 0, 0x30 + blockSize + header.dataOffset);
cmacDataHash = aes.doFinalCMAC();
for (int i = 0; i < cmacHeaderHash.length; i++) {
if (cmacHeaderHash[i] != header.CMACHeaderHash[i]) {
return PSP_KIRK_INVALID_HEADER_HASH;
}
}
for (int i = 0; i < cmacDataHash.length; i++) {
if (cmacDataHash[i] != header.CMACDataHash[i]) {
return PSP_KIRK_INVALID_DATA_HASH;
}
}
return 0;
}
// Generate SHA1 hash.
private int executeKIRKCmd11(ByteBuffer out, ByteBuffer in, int size) {
// Return an error if the crypto engine hasn't been initialized.
if (!CryptoEngine.getCryptoEngineStatus()) {
return PSP_KIRK_NOT_INIT;
}
SHA1_Header header = new SHA1_Header(in);
SHA1 sha1 = new SHA1();
size = (size < header.dataSize) ? size : header.dataSize;
header.readData(in, size);
out.clear();
out.put(sha1.doSHA1(header.data, size));
in.clear();
return 0;
}
// Generate ECDSA key pair.
private int executeKIRKCmd12(ByteBuffer out, int size) {
// Return an error if the crypto engine hasn't been initialized.
if (!CryptoEngine.getCryptoEngineStatus()) {
return PSP_KIRK_NOT_INIT;
}
if (size != 0x3C) {
return PSP_KIRK_INVALID_SIZE;
}
// Start the ECDSA context.
ECDSA ecdsa = new ECDSA();
ECDSAKeygenCtx ctx = new ECDSAKeygenCtx(out);
ecdsa.setCurve();
// Generate the private/public key pair and write it back.
ctx.private_key = ecdsa.getPrivateKey();
ctx.public_key = new ECDSAPoint(ecdsa.getPublicKey());
ctx.write();
return 0;
}
// Multiply ECDSA point.
private int executeKIRKCmd13(ByteBuffer out, int outSize, ByteBuffer in, int inSize) {
// Return an error if the crypto engine hasn't been initialized.
if (!CryptoEngine.getCryptoEngineStatus()) {
return PSP_KIRK_NOT_INIT;
}
if ((inSize != 0x3C) || (outSize != 0x28)) {
// Accept inSize==0x3C and outSize==0x3C as this is sent by sceMemab_9BF0C95D from a real PSP
if (outSize != inSize) {
return PSP_KIRK_INVALID_SIZE;
}
}
// Start the ECDSA context.
ECDSA ecdsa = new ECDSA();
ECDSAMultiplyCtx ctx = new ECDSAMultiplyCtx(in, out);
ecdsa.setCurve();
// Multiply the public key.
ecdsa.multiplyPublicKey(ctx.public_key.toByteArray(), ctx.multiplier);
ctx.write();
return 0;
}
// Generate pseudo random number.
private int executeKIRKCmd14(ByteBuffer out, int size) {
// Return an error if the crypto engine hasn't been initialized.
if (!CryptoEngine.getCryptoEngineStatus()) {
return PSP_KIRK_NOT_INIT;
}
// Set up a temporary buffer.
byte[] temp = new byte[0x104];
temp[0] = 0;
temp[1] = 0;
temp[2] = 1;
temp[3] = 0;
ByteBuffer bTemp = ByteBuffer.wrap(temp);
// Random data to act as a key.
byte[] key = {(byte) 0xA7, (byte) 0x2E, (byte) 0x4C, (byte) 0xB6, (byte) 0xC3,
(byte) 0x34, (byte) 0xDF, (byte) 0x85, (byte) 0x70, (byte) 0x01, (byte) 0x49,
(byte) 0xFC, (byte) 0xC0, (byte) 0x87, (byte) 0xC4, (byte) 0x77};
// Direct call to get the system time.
int systime = (int) System.currentTimeMillis();
System.arraycopy(prng_data, 0, temp, 4, 0x14);
temp[0x18] = (byte) (systime & 0xFF);
temp[0x19] = (byte) ((systime >> 8) & 0xFF);
temp[0x1A] = (byte) ((systime >> 16) & 0xFF);
temp[0x1B] = (byte) ((systime >> 24) & 0xFF);
System.arraycopy(key, 0, temp, 0x1C, 0x10);
// Generate a SHA-1 for this PRNG context.
ByteBuffer bPRNG = ByteBuffer.wrap(prng_data);
executeKIRKCmd11(bPRNG, bTemp, 0x104);
out.put(bPRNG.array());
// Process the data recursively.
for (int i = 0; i < size; i += 0x14) {
int remaining = size % 0x14;
int block = size / 0x14;
if (block > 0) {
out.put(bPRNG.array());
executeKIRKCmd14(out, i);
} else {
if (remaining > 0) {
out.put(prng_data, out.position(), remaining);
i += remaining;
}
}
}
out.rewind();
return 0;
}
// Sign data with ECDSA key pair.
private int executeKIRKCmd16(ByteBuffer out, int outSize, ByteBuffer in, int inSize) {
// Return an error if the crypto engine hasn't been initialized.
if (!CryptoEngine.getCryptoEngineStatus()) {
return PSP_KIRK_NOT_INIT;
}
if ((inSize != 0x34) || (outSize != 0x28)) {
return PSP_KIRK_INVALID_SIZE;
}
// TODO
ECDSA ecdsa = new ECDSA();
ECDSASignCtx ctx = new ECDSASignCtx(in);
ECDSASig sig = new ECDSASig();
ecdsa.setCurve();
return 0;
}
// Verify ECDSA signature.
private int executeKIRKCmd17(ByteBuffer in, int size) {
// Return an error if the crypto engine hasn't been initialized.
if (!CryptoEngine.getCryptoEngineStatus()) {
return PSP_KIRK_NOT_INIT;
}
if (size != 0x64) {
return PSP_KIRK_INVALID_SIZE;
}
// TODO
ECDSA ecdsa = new ECDSA();
ECDSAVerifyCtx ctx = new ECDSAVerifyCtx(in);
ecdsa.setCurve();
return 0;
}
/*
* HLE functions: high level implementation of crypto functions from
* several modules which employ various algorithms and communicate with the
* crypto engine in different ways.
*/
/*
* sceUtils - memlmd_01g.prx and memlmd_02g.prx
*/
public void hleUtilsSetFuseID(int id0, int id1) {
fuseID0 = id0;
fuseID1 = id1;
}
public int hleUtilsBufferCopyWithRange(ByteBuffer out, int outsize, ByteBuffer in, int insize, int cmd) {
return hleUtilsBufferCopyWithRange(out, outsize, in, insize, insize, cmd);
}
public int hleUtilsBufferCopyWithRange(ByteBuffer out, int outsize, ByteBuffer in, int insizeAligned, int insize, int cmd) {
switch (cmd) {
case PSP_KIRK_CMD_DECRYPT_PRIVATE:
return executeKIRKCmd1(out, in, insizeAligned);
case PSP_KIRK_CMD_ENCRYPT:
return executeKIRKCmd4(out, in, insizeAligned);
case PSP_KIRK_CMD_ENCRYPT_FUSE:
return executeKIRKCmd5(out, in, insizeAligned);
case PSP_KIRK_CMD_DECRYPT:
return executeKIRKCmd7(out, in, insizeAligned);
case PSP_KIRK_CMD_DECRYPT_FUSE:
return executeKIRKCmd8(out, in, insizeAligned);
case PSP_KIRK_CMD_PRIV_SIG_CHECK:
return executeKIRKCmd10(in, insizeAligned);
case PSP_KIRK_CMD_SHA1_HASH:
return executeKIRKCmd11(out, in, insizeAligned);
case PSP_KIRK_CMD_ECDSA_GEN_KEYS:
return executeKIRKCmd12(out, outsize);
case PSP_KIRK_CMD_ECDSA_MULTIPLY_POINT:
return executeKIRKCmd13(out, outsize, in, insize);
case PSP_KIRK_CMD_PRNG:
return executeKIRKCmd14(out, insizeAligned);
case PSP_KIRK_CMD_ECDSA_SIGN:
return executeKIRKCmd16(out, outsize, in, insize);
case PSP_KIRK_CMD_ECDSA_VERIFY:
return executeKIRKCmd17(in, insize);
default:
return PSP_KIRK_INVALID_OPERATION; // Dummy.
}
}
}